c64bridge 0.5.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.
- package/.github/prompts/README.md +6 -0
- package/.github/prompts/assembly-routine-session.prompt.md +13 -0
- package/.github/prompts/basic-program-session.prompt.md +13 -0
- package/.github/prompts/drive-management-session.prompt.md +13 -0
- package/.github/prompts/petscii-art-session.prompt.md +13 -0
- package/.github/prompts/play-classic-game.prompt.md +13 -0
- package/.github/prompts/sid-music-session.prompt.md +13 -0
- package/AGENTS.md +173 -0
- package/LICENSE +339 -0
- package/README.md +582 -0
- package/data/README.md +16 -0
- package/data/api/basic-api-spec.md +154 -0
- package/data/api/kernal-api-spec.md +68 -0
- package/data/assembly/assembly-spec.md +196 -0
- package/data/assembly/examples/effects/sinewave.asm +5 -0
- package/data/assembly/examples/graphics/sprite/mover.asm +15 -0
- package/data/assembly/examples/sid-file-structure.md +8 -0
- package/data/assembly/examples/sid-overview.md +11 -0
- package/data/audio/examples/Biosphere.sid +0 -0
- package/data/audio/sid-file-structure.md +91 -0
- package/data/audio/sid-programming-best-practices.md +198 -0
- package/data/audio/sid-spec.md +166 -0
- package/data/audio/sidwave.md +131 -0
- package/data/audio/sidwave.schema.json +64 -0
- package/data/basic/basic-spec.md +230 -0
- package/data/basic/examples/audio/alle-meine-entchen-expressive.bas +41 -0
- package/data/basic/examples/audio/c-major-scale-pleasant.bas +29 -0
- package/data/basic/examples/io/joystick.bas +5 -0
- package/data/basic/examples/video/bounce.bas +8 -0
- package/data/basic/examples/video/entchen-petscii.bas +16 -0
- package/data/basic/examples/video/hello-world.bas +4 -0
- package/data/basic/examples/video/wave.bas +7 -0
- package/data/context/bootstrap.md +148 -0
- package/data/context/chat.md +36 -0
- package/data/drive/drive-spec.md +215 -0
- package/data/embeddings_asm.json +1 -0
- package/data/embeddings_basic.json +1 -0
- package/data/embeddings_hardware.json +1 -0
- package/data/embeddings_mixed.json +1 -0
- package/data/embeddings_other.json +1 -0
- package/data/io/cia-spec.md +115 -0
- package/data/io/io-spec.md +124 -0
- package/data/memory/kernal-memory-map.md +227 -0
- package/data/memory/low-memory-map.md +133 -0
- package/data/memory/memory-map.md +186 -0
- package/data/printer/printer-commodore-bitmap.md +31 -0
- package/data/printer/printer-commodore.md +28 -0
- package/data/printer/printer-epson-bitmap.md +29 -0
- package/data/printer/printer-epson.md +30 -0
- package/data/printer/printer-prompts.md +29 -0
- package/data/printer/printer-spec.md +180 -0
- package/data/video/character-set.csv +257 -0
- package/data/video/vic-spec.md +276 -0
- package/dist/assemblyConverter.js +1299 -0
- package/dist/audio/record_and_analyze_audio.js +292 -0
- package/dist/basicConverter.js +193 -0
- package/dist/bootstrap/stdio-logger.js +32 -0
- package/dist/c64Client.js +1052 -0
- package/dist/chargen.js +197 -0
- package/dist/config.js +142 -0
- package/dist/context.js +157 -0
- package/dist/device.js +413 -0
- package/dist/index.js +7 -0
- package/dist/knowledge.js +454 -0
- package/dist/logger.js +182 -0
- package/dist/loggingHttpClient.js +88 -0
- package/dist/mcp-server.js +426 -0
- package/dist/petscii.js +135 -0
- package/dist/petsciiArt.js +356 -0
- package/dist/platform.js +82 -0
- package/dist/prompts/registry.js +560 -0
- package/dist/rag/discover.config.json +41 -0
- package/dist/rag/discover.js +401 -0
- package/dist/rag/embeddings.js +60 -0
- package/dist/rag/externalFetcher.js +934 -0
- package/dist/rag/indexer.js +770 -0
- package/dist/rag/init.js +139 -0
- package/dist/rag/knowledgeIndex.js +361 -0
- package/dist/rag/loggingRetriever.js +46 -0
- package/dist/rag/rateLimiter.js +127 -0
- package/dist/rag/retriever.js +74 -0
- package/dist/rag/sources.csv +31 -0
- package/dist/rag/types.js +5 -0
- package/dist/rag/urlUtils.js +38 -0
- package/dist/sidplayRunner.js +240 -0
- package/dist/sidwave.js +101 -0
- package/dist/sidwaveCompiler.js +329 -0
- package/dist/tools/audio.js +944 -0
- package/dist/tools/developer.js +515 -0
- package/dist/tools/errors.js +59 -0
- package/dist/tools/graphics.js +469 -0
- package/dist/tools/machineControl.js +267 -0
- package/dist/tools/memory.js +224 -0
- package/dist/tools/printer.js +482 -0
- package/dist/tools/programRunners.js +315 -0
- package/dist/tools/rag.js +153 -0
- package/dist/tools/registry.js +58 -0
- package/dist/tools/responses.js +36 -0
- package/dist/tools/schema.js +304 -0
- package/dist/tools/storage.js +740 -0
- package/dist/tools/streaming.js +132 -0
- package/dist/tools/types.js +99 -0
- package/dist/viceRunner.js +239 -0
- package/doc/MCP_SETUP.md +153 -0
- package/doc/README.md +7 -0
- package/doc/developer.md +312 -0
- package/doc/img/logo.png +0 -0
- package/doc/img/prompts/duck_petscii.png +0 -0
- package/doc/img/prompts/duck_song.png +0 -0
- package/doc/img/vscode/vscode-copilot-c64-chat-mode.png +0 -0
- package/doc/img/vscode/vscode-copilot-hello-world.png +0 -0
- package/doc/prompts/README.md +96 -0
- package/doc/prompts/user-prompt-ideas.md +147 -0
- package/doc/rest/c64-openapi.yaml +979 -0
- package/doc/rest/c64-rest-api.md +106 -0
- package/doc/troubleshooting-mcp.md +177 -0
- package/generated/c64/index.js +857 -0
- package/mcp.json +106 -0
- package/package.json +79 -0
- package/scripts/c64-cli.mjs +142 -0
- package/scripts/c64-tool.sh +83 -0
- package/scripts/check-package.mjs +89 -0
- package/scripts/cli.js +61 -0
- package/scripts/fetch-external-sources.mjs +67 -0
- package/scripts/generate-changelog.mjs +131 -0
- package/scripts/postbuild.mjs +101 -0
- package/scripts/prepare-release.mjs +58 -0
- package/scripts/rag-rebuild.mjs +53 -0
- package/scripts/register-ts-node.mjs +6 -0
- package/scripts/run-mcp-check.sh +104 -0
- package/scripts/run-tests.mjs +195 -0
- package/scripts/sid2wav.sh +94 -0
- package/scripts/smoke-run.sh +65 -0
- package/scripts/start-mock-c64.mjs +51 -0
- package/scripts/start-mock.mjs +31 -0
- package/scripts/start.mjs +72 -0
- package/scripts/update-readme.ts +164 -0
- package/scripts/verify-package.mjs +147 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { defineToolModule } from "./types.js";
|
|
2
|
+
import { objectSchema } from "./schema.js";
|
|
3
|
+
import { textResult } from "./responses.js";
|
|
4
|
+
import { ToolError, ToolExecutionError, toolErrorResult, unknownErrorResult, } from "./errors.js";
|
|
5
|
+
function createNoArgsSchema(description) {
|
|
6
|
+
return objectSchema({
|
|
7
|
+
description,
|
|
8
|
+
properties: {},
|
|
9
|
+
additionalProperties: false,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function toRecord(details) {
|
|
13
|
+
if (details && typeof details === "object") {
|
|
14
|
+
return details;
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
function normaliseFailure(details) {
|
|
19
|
+
if (details === undefined || details === null) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (typeof details === "object") {
|
|
23
|
+
return details;
|
|
24
|
+
}
|
|
25
|
+
return { value: details };
|
|
26
|
+
}
|
|
27
|
+
const resetArgsSchema = createNoArgsSchema("No arguments required to reset the C64.");
|
|
28
|
+
const rebootArgsSchema = createNoArgsSchema("No arguments required to reboot the C64 firmware.");
|
|
29
|
+
const pauseArgsSchema = createNoArgsSchema("No arguments required to pause the machine via DMA.");
|
|
30
|
+
const resumeArgsSchema = createNoArgsSchema("No arguments required to resume the machine after a pause.");
|
|
31
|
+
const poweroffArgsSchema = createNoArgsSchema("No arguments required to power off the machine.");
|
|
32
|
+
const menuButtonArgsSchema = createNoArgsSchema("No arguments required to toggle the Ultimate menu button.");
|
|
33
|
+
export const machineControlModule = defineToolModule({
|
|
34
|
+
domain: "machine",
|
|
35
|
+
summary: "Power, reset, pause/resume, and diagnostic controls for the C64 and Ultimate hardware.",
|
|
36
|
+
resources: ["c64://context/bootstrap"],
|
|
37
|
+
prompts: ["memory-debug"],
|
|
38
|
+
defaultTags: ["machine", "control"],
|
|
39
|
+
workflowHints: [
|
|
40
|
+
"Reach for machine controls when the user mentions resets, power states, or DMA pause/resume.",
|
|
41
|
+
"Explain the operational impact (e.g. soft reset vs firmware reboot) so the user knows what changed.",
|
|
42
|
+
],
|
|
43
|
+
tools: [
|
|
44
|
+
{
|
|
45
|
+
name: "reset_c64",
|
|
46
|
+
description: "Reset the C64 via Ultimate firmware. Review c64://context/bootstrap safety rules.",
|
|
47
|
+
summary: "Issues a soft reset, equivalent to power cycling without cutting power.",
|
|
48
|
+
inputSchema: resetArgsSchema.jsonSchema,
|
|
49
|
+
tags: ["reset"],
|
|
50
|
+
prerequisites: [],
|
|
51
|
+
examples: [
|
|
52
|
+
{ name: "Soft reset", description: "Reset machine", arguments: {} },
|
|
53
|
+
],
|
|
54
|
+
workflowHints: [
|
|
55
|
+
"Use when the user wants a quick restart without losing power; mention that memory contents may persist.",
|
|
56
|
+
],
|
|
57
|
+
async execute(args, ctx) {
|
|
58
|
+
try {
|
|
59
|
+
resetArgsSchema.parse(args ?? {});
|
|
60
|
+
ctx.logger.info("Resetting C64");
|
|
61
|
+
const result = await ctx.client.reset();
|
|
62
|
+
if (!result.success) {
|
|
63
|
+
throw new ToolExecutionError("C64 firmware reported failure while resetting", {
|
|
64
|
+
details: normaliseFailure(result.details),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const details = toRecord(result.details) ?? {};
|
|
68
|
+
return textResult("C64 reset command issued successfully.", {
|
|
69
|
+
success: true,
|
|
70
|
+
details,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error instanceof ToolError) {
|
|
75
|
+
return toolErrorResult(error);
|
|
76
|
+
}
|
|
77
|
+
return unknownErrorResult(error);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "reboot_c64",
|
|
83
|
+
description: "Reboot the Ultimate firmware and C64. See c64://context/bootstrap.",
|
|
84
|
+
summary: "Triggers a firmware reboot, useful after configuration changes.",
|
|
85
|
+
inputSchema: rebootArgsSchema.jsonSchema,
|
|
86
|
+
tags: ["reboot"],
|
|
87
|
+
prerequisites: [],
|
|
88
|
+
examples: [
|
|
89
|
+
{ name: "Firmware reboot", description: "Hard reboot", arguments: {} },
|
|
90
|
+
],
|
|
91
|
+
workflowHints: [
|
|
92
|
+
"Choose reboot when configuration changed or hardware is stuck; warn that it will interrupt any running program.",
|
|
93
|
+
],
|
|
94
|
+
async execute(args, ctx) {
|
|
95
|
+
try {
|
|
96
|
+
rebootArgsSchema.parse(args ?? {});
|
|
97
|
+
ctx.logger.info("Rebooting C64");
|
|
98
|
+
const result = await ctx.client.reboot();
|
|
99
|
+
if (!result.success) {
|
|
100
|
+
throw new ToolExecutionError("C64 firmware reported failure while rebooting", {
|
|
101
|
+
details: normaliseFailure(result.details),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const details = toRecord(result.details) ?? {};
|
|
105
|
+
return textResult("C64 reboot command issued successfully.", {
|
|
106
|
+
success: true,
|
|
107
|
+
details,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error instanceof ToolError) {
|
|
112
|
+
return toolErrorResult(error);
|
|
113
|
+
}
|
|
114
|
+
return unknownErrorResult(error);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "pause",
|
|
120
|
+
description: "Pause the machine using DMA halt. See memory safety checklist in c64://context/bootstrap.",
|
|
121
|
+
summary: "Suspends CPU execution until resumed.",
|
|
122
|
+
inputSchema: pauseArgsSchema.jsonSchema,
|
|
123
|
+
tags: ["pause"],
|
|
124
|
+
prerequisites: [],
|
|
125
|
+
examples: [
|
|
126
|
+
{ name: "Pause", description: "Halt CPU", arguments: {} },
|
|
127
|
+
],
|
|
128
|
+
workflowHints: [
|
|
129
|
+
"Pause when the user needs a stable memory snapshot; remind them to resume to continue execution.",
|
|
130
|
+
],
|
|
131
|
+
async execute(args, ctx) {
|
|
132
|
+
try {
|
|
133
|
+
pauseArgsSchema.parse(args ?? {});
|
|
134
|
+
ctx.logger.info("Pausing C64 execution");
|
|
135
|
+
const result = await ctx.client.pause();
|
|
136
|
+
if (!result.success) {
|
|
137
|
+
throw new ToolExecutionError("C64 firmware reported failure while pausing", {
|
|
138
|
+
details: normaliseFailure(result.details),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const details = toRecord(result.details) ?? {};
|
|
142
|
+
return textResult("C64 execution paused.", {
|
|
143
|
+
success: true,
|
|
144
|
+
details,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
if (error instanceof ToolError) {
|
|
149
|
+
return toolErrorResult(error);
|
|
150
|
+
}
|
|
151
|
+
return unknownErrorResult(error);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "resume",
|
|
157
|
+
description: "Resume the machine after a DMA pause.",
|
|
158
|
+
summary: "Releases the DMA halt so the CPU can continue.",
|
|
159
|
+
inputSchema: resumeArgsSchema.jsonSchema,
|
|
160
|
+
tags: ["resume"],
|
|
161
|
+
prerequisites: ["pause"],
|
|
162
|
+
examples: [
|
|
163
|
+
{ name: "Resume", description: "Continue CPU", arguments: {} },
|
|
164
|
+
],
|
|
165
|
+
workflowHints: [
|
|
166
|
+
"Call after a pause or diagnostic halt and confirm the machine is running again.",
|
|
167
|
+
],
|
|
168
|
+
async execute(args, ctx) {
|
|
169
|
+
try {
|
|
170
|
+
resumeArgsSchema.parse(args ?? {});
|
|
171
|
+
ctx.logger.info("Resuming C64 execution");
|
|
172
|
+
const result = await ctx.client.resume();
|
|
173
|
+
if (!result.success) {
|
|
174
|
+
throw new ToolExecutionError("C64 firmware reported failure while resuming", {
|
|
175
|
+
details: normaliseFailure(result.details),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const details = toRecord(result.details) ?? {};
|
|
179
|
+
return textResult("C64 execution resumed.", {
|
|
180
|
+
success: true,
|
|
181
|
+
details,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (error instanceof ToolError) {
|
|
186
|
+
return toolErrorResult(error);
|
|
187
|
+
}
|
|
188
|
+
return unknownErrorResult(error);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "poweroff",
|
|
194
|
+
description: "Power off the machine via Ultimate firmware. See safety notes in c64://context/bootstrap.",
|
|
195
|
+
summary: "Attempts a controlled shutdown through the Ultimate control interface.",
|
|
196
|
+
inputSchema: poweroffArgsSchema.jsonSchema,
|
|
197
|
+
tags: ["power"],
|
|
198
|
+
prerequisites: [],
|
|
199
|
+
examples: [
|
|
200
|
+
{ name: "Power off", description: "Shut down", arguments: {} },
|
|
201
|
+
],
|
|
202
|
+
workflowHints: [
|
|
203
|
+
"Use when the user explicitly requests power off; remind them to power on manually or via drive controls afterwards.",
|
|
204
|
+
],
|
|
205
|
+
async execute(args, ctx) {
|
|
206
|
+
try {
|
|
207
|
+
poweroffArgsSchema.parse(args ?? {});
|
|
208
|
+
ctx.logger.info("Powering off C64");
|
|
209
|
+
const result = await ctx.client.poweroff();
|
|
210
|
+
if (!result.success) {
|
|
211
|
+
throw new ToolExecutionError("C64 firmware reported failure while powering off", {
|
|
212
|
+
details: normaliseFailure(result.details),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const details = toRecord(result.details) ?? {};
|
|
216
|
+
return textResult("Power off command acknowledged.", {
|
|
217
|
+
success: true,
|
|
218
|
+
details,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
if (error instanceof ToolError) {
|
|
223
|
+
return toolErrorResult(error);
|
|
224
|
+
}
|
|
225
|
+
return unknownErrorResult(error);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: "menu_button",
|
|
231
|
+
description: "Toggle the Ultimate 64 menu button.",
|
|
232
|
+
summary: "Simulates the on-device menu button for navigation or exit.",
|
|
233
|
+
inputSchema: menuButtonArgsSchema.jsonSchema,
|
|
234
|
+
tags: ["menu"],
|
|
235
|
+
prerequisites: [],
|
|
236
|
+
examples: [
|
|
237
|
+
{ name: "Menu", description: "Toggle menu", arguments: {} },
|
|
238
|
+
],
|
|
239
|
+
workflowHints: [
|
|
240
|
+
"Use when the user needs to open or close the Ultimate menu; suggest following up with drive operations if relevant.",
|
|
241
|
+
],
|
|
242
|
+
async execute(args, ctx) {
|
|
243
|
+
try {
|
|
244
|
+
menuButtonArgsSchema.parse(args ?? {});
|
|
245
|
+
ctx.logger.info("Toggling Ultimate menu button");
|
|
246
|
+
const result = await ctx.client.menuButton();
|
|
247
|
+
if (!result.success) {
|
|
248
|
+
throw new ToolExecutionError("C64 firmware reported failure while toggling menu button", {
|
|
249
|
+
details: normaliseFailure(result.details),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const details = toRecord(result.details) ?? {};
|
|
253
|
+
return textResult("Menu button command sent.", {
|
|
254
|
+
success: true,
|
|
255
|
+
details,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
if (error instanceof ToolError) {
|
|
260
|
+
return toolErrorResult(error);
|
|
261
|
+
}
|
|
262
|
+
return unknownErrorResult(error);
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { defineToolModule } from "./types.js";
|
|
2
|
+
import { numberSchema, objectSchema, stringSchema } from "./schema.js";
|
|
3
|
+
import { textResult } from "./responses.js";
|
|
4
|
+
import { ToolError, ToolExecutionError, toolErrorResult, unknownErrorResult } from "./errors.js";
|
|
5
|
+
function toRecord(details) {
|
|
6
|
+
if (details && typeof details === "object") {
|
|
7
|
+
return details;
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
function normaliseFailure(details) {
|
|
12
|
+
if (details === undefined || details === null) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
if (typeof details === "object") {
|
|
16
|
+
return details;
|
|
17
|
+
}
|
|
18
|
+
return { value: details };
|
|
19
|
+
}
|
|
20
|
+
const readScreenArgsSchema = objectSchema({
|
|
21
|
+
description: "No arguments are required for reading the current screen contents.",
|
|
22
|
+
properties: {},
|
|
23
|
+
additionalProperties: false,
|
|
24
|
+
});
|
|
25
|
+
const readMemoryArgsSchema = objectSchema({
|
|
26
|
+
description: "Parameters for reading a block of memory from the C64.",
|
|
27
|
+
properties: {
|
|
28
|
+
address: stringSchema({
|
|
29
|
+
description: "Start address expressed as $HHHH, decimal, or a documented symbol name.",
|
|
30
|
+
minLength: 1,
|
|
31
|
+
}),
|
|
32
|
+
length: numberSchema({
|
|
33
|
+
description: "Number of bytes to read starting from the resolved address.",
|
|
34
|
+
integer: true,
|
|
35
|
+
minimum: 1,
|
|
36
|
+
maximum: 4096,
|
|
37
|
+
default: 256,
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
required: ["address"],
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
});
|
|
43
|
+
const writeMemoryArgsSchema = objectSchema({
|
|
44
|
+
description: "Parameters for writing hexadecimal bytes into C64 memory.",
|
|
45
|
+
properties: {
|
|
46
|
+
address: stringSchema({
|
|
47
|
+
description: "Destination address expressed as $HHHH, decimal, or a documented symbol name.",
|
|
48
|
+
minLength: 1,
|
|
49
|
+
}),
|
|
50
|
+
bytes: stringSchema({
|
|
51
|
+
description: "Hex byte sequence like $AABBCC or AA BB CC to write starting at the resolved address.",
|
|
52
|
+
minLength: 2,
|
|
53
|
+
pattern: /^[\s_0-9A-Fa-f$]+$/,
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
required: ["address", "bytes"],
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
});
|
|
59
|
+
export const memoryModule = defineToolModule({
|
|
60
|
+
domain: "memory",
|
|
61
|
+
summary: "Screen, main memory, and low-level inspection utilities.",
|
|
62
|
+
resources: [
|
|
63
|
+
"c64://context/bootstrap",
|
|
64
|
+
"c64://specs/basic",
|
|
65
|
+
"c64://specs/assembly",
|
|
66
|
+
],
|
|
67
|
+
prompts: ["memory-debug", "basic-program", "assembly-program"],
|
|
68
|
+
defaultTags: ["memory", "debug"],
|
|
69
|
+
workflowHints: [
|
|
70
|
+
"Pair memory operations with documentation snippets so addresses and symbols stay meaningful to the user.",
|
|
71
|
+
"Confirm intent before mutating RAM and explain how the change affects the running program.",
|
|
72
|
+
],
|
|
73
|
+
tools: [
|
|
74
|
+
{
|
|
75
|
+
name: "read_screen",
|
|
76
|
+
description: "Read the current text screen (40x25) and return its ASCII representation. For PETSCII details, see c64://specs/basic.",
|
|
77
|
+
summary: "Fetches screen RAM, converts from PETSCII, and returns the printable output.",
|
|
78
|
+
inputSchema: readScreenArgsSchema.jsonSchema,
|
|
79
|
+
relatedResources: ["c64://context/bootstrap", "c64://specs/basic"],
|
|
80
|
+
relatedPrompts: ["memory-debug", "basic-program", "assembly-program"],
|
|
81
|
+
tags: ["screen", "memory"],
|
|
82
|
+
prerequisites: ["upload_and_run_basic"],
|
|
83
|
+
examples: [
|
|
84
|
+
{
|
|
85
|
+
name: "Capture screen",
|
|
86
|
+
description: "Read current 40x25 text",
|
|
87
|
+
arguments: {},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
workflowHints: [
|
|
91
|
+
"Call after running a program when the user asks to see what is on screen; echo the captured text back to them.",
|
|
92
|
+
],
|
|
93
|
+
async execute(args, ctx) {
|
|
94
|
+
try {
|
|
95
|
+
readScreenArgsSchema.parse(args ?? {});
|
|
96
|
+
ctx.logger.info("Reading C64 screen contents");
|
|
97
|
+
const screen = await ctx.client.readScreen();
|
|
98
|
+
return textResult(`Current screen contents:\n${screen}`, {
|
|
99
|
+
success: true,
|
|
100
|
+
screen,
|
|
101
|
+
length: screen.length,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error instanceof ToolError) {
|
|
106
|
+
return toolErrorResult(error);
|
|
107
|
+
}
|
|
108
|
+
return unknownErrorResult(error);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "read_memory",
|
|
114
|
+
description: "Read a range of bytes from main memory and return the data as hexadecimal. Consult c64://specs/assembly and docs index.",
|
|
115
|
+
summary: "Resolves symbols, reads memory, and returns a hex dump with addressing metadata.",
|
|
116
|
+
inputSchema: readMemoryArgsSchema.jsonSchema,
|
|
117
|
+
relatedResources: ["c64://context/bootstrap", "c64://specs/assembly", "c64://docs/index"],
|
|
118
|
+
relatedPrompts: ["memory-debug", "assembly-program"],
|
|
119
|
+
tags: ["memory", "hex"],
|
|
120
|
+
prerequisites: ["pause"],
|
|
121
|
+
examples: [
|
|
122
|
+
{
|
|
123
|
+
name: "Read screen memory",
|
|
124
|
+
description: "Read 8 bytes at $0400",
|
|
125
|
+
arguments: { address: "$0400", length: 8 },
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
workflowHints: [
|
|
129
|
+
"Resolve symbol names before calling so you can explain the chosen address in the response.",
|
|
130
|
+
"Keep reads at or below 4096 bytes; split larger requests into multiple calls if needed.",
|
|
131
|
+
],
|
|
132
|
+
async execute(args, ctx) {
|
|
133
|
+
try {
|
|
134
|
+
const parsed = readMemoryArgsSchema.parse(args ?? {});
|
|
135
|
+
ctx.logger.info("Reading C64 memory", { address: parsed.address, length: parsed.length });
|
|
136
|
+
const result = await ctx.client.readMemory(parsed.address, String(parsed.length));
|
|
137
|
+
if (!result.success) {
|
|
138
|
+
throw new ToolExecutionError("C64 firmware reported failure while reading memory", {
|
|
139
|
+
details: normaliseFailure(result.details),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const detailRecord = toRecord(result.details) ?? {};
|
|
143
|
+
const resolvedAddress = typeof detailRecord.address === "string" ? detailRecord.address : undefined;
|
|
144
|
+
const resolvedLength = typeof detailRecord.length === "number" ? detailRecord.length : undefined;
|
|
145
|
+
const addressLabel = resolvedAddress ? `$${resolvedAddress}` : parsed.address;
|
|
146
|
+
const lengthLabel = resolvedLength ?? parsed.length;
|
|
147
|
+
return textResult(`Read ${lengthLabel} bytes starting at ${addressLabel}.`, {
|
|
148
|
+
success: true,
|
|
149
|
+
address: addressLabel,
|
|
150
|
+
length: lengthLabel,
|
|
151
|
+
hexData: result.data ?? null,
|
|
152
|
+
details: detailRecord,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (error instanceof ToolError) {
|
|
157
|
+
return toolErrorResult(error);
|
|
158
|
+
}
|
|
159
|
+
return unknownErrorResult(error);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "write_memory",
|
|
165
|
+
description: "Write a hexadecimal byte sequence into main memory at the specified address. See c64://context/bootstrap for safety rules.",
|
|
166
|
+
summary: "Resolves symbols, validates hex data, and writes bytes to RAM via Ultimate firmware.",
|
|
167
|
+
inputSchema: writeMemoryArgsSchema.jsonSchema,
|
|
168
|
+
relatedResources: ["c64://context/bootstrap", "c64://specs/assembly", "c64://docs/index"],
|
|
169
|
+
relatedPrompts: ["memory-debug", "assembly-program"],
|
|
170
|
+
tags: ["memory", "hex", "write"],
|
|
171
|
+
prerequisites: ["pause", "read_memory"],
|
|
172
|
+
examples: [
|
|
173
|
+
{
|
|
174
|
+
name: "Write to screen",
|
|
175
|
+
description: "Write $AA55 at $0400",
|
|
176
|
+
arguments: { address: "$0400", bytes: "$AA55" },
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
workflowHints: [
|
|
180
|
+
"Double-check with the user before writing memory and describe the exact bytes you applied.",
|
|
181
|
+
"Consider reading the region first so they can compare before and after states.",
|
|
182
|
+
],
|
|
183
|
+
async execute(args, ctx) {
|
|
184
|
+
try {
|
|
185
|
+
const parsed = writeMemoryArgsSchema.parse(args ?? {});
|
|
186
|
+
ctx.logger.info("Writing C64 memory", { address: parsed.address, bytesLength: parsed.bytes.length });
|
|
187
|
+
const result = await ctx.client.writeMemory(parsed.address, parsed.bytes);
|
|
188
|
+
if (!result.success) {
|
|
189
|
+
throw new ToolExecutionError("C64 firmware reported failure while writing memory", {
|
|
190
|
+
details: normaliseFailure(result.details),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const detailRecord = toRecord(result.details) ?? {};
|
|
194
|
+
let resolvedAddress;
|
|
195
|
+
if (typeof detailRecord.address === "number") {
|
|
196
|
+
resolvedAddress = `$${detailRecord.address.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
197
|
+
}
|
|
198
|
+
else if (typeof detailRecord.address === "string" && detailRecord.address.length > 0) {
|
|
199
|
+
resolvedAddress = detailRecord.address.startsWith("$")
|
|
200
|
+
? detailRecord.address
|
|
201
|
+
: `$${detailRecord.address.toUpperCase()}`;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
resolvedAddress = parsed.address.startsWith("$") ? parsed.address : `$${parsed.address}`;
|
|
205
|
+
}
|
|
206
|
+
const resolvedLength = typeof detailRecord.length === "number" ? detailRecord.length : undefined;
|
|
207
|
+
return textResult(`Wrote ${resolvedLength ?? "the provided"} bytes starting at ${resolvedAddress}.`, {
|
|
208
|
+
success: true,
|
|
209
|
+
address: resolvedAddress,
|
|
210
|
+
length: resolvedLength ?? null,
|
|
211
|
+
bytes: parsed.bytes,
|
|
212
|
+
details: detailRecord,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
if (error instanceof ToolError) {
|
|
217
|
+
return toolErrorResult(error);
|
|
218
|
+
}
|
|
219
|
+
return unknownErrorResult(error);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
});
|