@henols/c64-re-tools 2.1.0 → 2.1.1
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/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +1 -0
- package/skills/c64-assembler/scripts/assembler.ts +10 -0
- package/skills/c64-disk/SKILL.md +2 -2
- package/skills/c64-disk/scripts/disk.ts +24 -10
- package/skills/c64-disk/scripts/lib/containers.ts +9 -9
- package/skills/c64-docs/scripts/lib/links.ts +3 -3
- package/skills/c64-docs/scripts/lib/reference.ts +2 -2
- package/skills/c64-emulator/SKILL.md +8 -2
- package/skills/c64-knowledge/SKILL.md +3 -1
- package/skills/c64-knowledge/scripts/knowledge.ts +59 -43
- package/skills/c64-memory-map/SKILL.md +1 -1
- package/skills/c64-memory-map/scripts/memory-map.ts +3 -2
- package/skills/c64-provenance/SKILL.md +5 -4
- package/skills/c64-provenance/scripts/lib/library.ts +18 -6
- package/skills/c64-provenance/scripts/lib/separate.ts +45 -6
- package/skills/c64-provenance/scripts/provenance.ts +5 -4
- package/skills/c64-source/SKILL.md +2 -0
- package/skills/c64-source/scripts/lib/source.ts +2 -2
- package/skills/c64-source/scripts/source.ts +10 -6
- package/skills/c64-static-analysis/scripts/lib/idioms.ts +6 -2
- package/skills/c64-static-analysis/scripts/static-analysis.ts +4 -3
- package/skills/c64-testing/SKILL.md +11 -1
- package/skills/c64-testing/references/recording-format.md +2 -0
- package/skills/c64-testing/references/scenario-format.md +6 -0
- package/skills/c64-testing/scripts/lib/diff-files.ts +63 -13
- package/skills/c64-testing/scripts/lib/diff.ts +3 -3
- package/skills/c64-testing/scripts/lib/recording.ts +47 -12
- package/skills/c64-testing/scripts/lib/replay.ts +11 -0
- package/skills/c64-testing/scripts/lib/runs.ts +64 -4
- package/skills/c64-testing/scripts/lib/scenario.ts +43 -6
- package/skills/c64-testing/scripts/testing.ts +15 -2
- package/skills/c64-unpacker/SKILL.md +9 -5
- package/skills/c64-unpacker/scripts/lib/evidence.ts +4 -5
- package/skills/c64-unpacker/scripts/lib/loads.ts +3 -2
- package/skills/c64-unpacker/scripts/unpacker.ts +75 -25
- package/src/host/vice/session.ts +99 -10
- package/src/host-client/image.ts +32 -0
- package/src/host-client/steps.ts +106 -6
- package/src/host-client/tools.ts +1 -1
- package/src/host-client/transfer.ts +35 -8
- package/src/knowledge/expression.ts +1 -1
- package/src/knowledge/history.ts +19 -2
- package/src/knowledge/names.ts +2 -1
- package/src/knowledge/program.ts +1 -16
- package/src/knowledge/read.ts +43 -0
- package/src/knowledge/schema.ts +3 -2
- package/src/knowledge/text.ts +1 -0
- package/src/knowledge/write.ts +40 -6
- package/src/mcp/output.ts +5 -0
- package/src/mcp/server.ts +19 -4
- package/src/mcp/tools/debug.ts +7 -6
- package/src/mcp/tools/execution.ts +18 -5
- package/src/mcp/tools/media.ts +14 -2
- package/src/mcp/tools/memory.ts +7 -3
- package/src/mcp/tools/video.ts +5 -3
- package/src/project.ts +1 -1
- package/src/protocol/vice.ts +4 -1
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
import { formatC64Address } from "../../c64.ts";
|
|
6
|
+
import { isRefusal } from "../../errors.ts";
|
|
6
7
|
import { EXECUTION_ACTIONS, MAX_EXECUTION_COUNT, MAX_HISTORY, MAX_TIMEOUT_FRAMES, POINT_STOP_REASONS, RUN_STATES, SPACES, STOP_REASONS, type RunTarget } from "../../protocol/vice.ts";
|
|
7
8
|
import { Byte, SpaceInput, TOOL_INPUTS } from "../inputs.ts";
|
|
8
|
-
import { AddressOutput, defineTool, toCondition, ToolOutput } from "../server.ts";
|
|
9
|
+
import { AddressOutput, defineTool, toCondition, ToolOutput, type ViceSessionApi } from "../server.ts";
|
|
9
10
|
import { c64Observe } from "./video.ts";
|
|
10
11
|
|
|
11
12
|
export const c64Execution = defineTool({
|
|
@@ -19,6 +20,8 @@ export const c64Execution = defineTool({
|
|
|
19
20
|
"advance-frames runs exactly count video frames (count is necessary). It stops at the same raster position, count frames later. " +
|
|
20
21
|
"A breakpoint, a watchpoint or a JAM opcode can stop it before that. advancedFrames tells how many frames ran. " +
|
|
21
22
|
"stopReason tells why it stopped: frames (all frames ran), breakpoint, watchpoint or jam. pointId gives the id of the breakpoint or watchpoint. " +
|
|
23
|
+
"space tells which CPU stopped: c64, or drive8 for a breakpoint or watchpoint in the disk drive. pc is the address in that space. " +
|
|
24
|
+
"A CPU that a JAM stopped runs again only after c64_reset, a snapshot restore or c64_autostart. " +
|
|
22
25
|
"To wait for a breakpoint or watchpoint after resume, use advance-frames with a large count. " +
|
|
23
26
|
"step, next, until-return and advance-frames first stop a running CPU. Use count only with step, next and advance-frames. " +
|
|
24
27
|
"step, next and until-return work only in space c64. For the drive, use c64_run_until with an address in space drive8.",
|
|
@@ -42,6 +45,7 @@ export const c64Execution = defineTool({
|
|
|
42
45
|
advancedFrames: z.number().int().min(0).max(MAX_EXECUTION_COUNT).optional().describe("frames advance-frames ran"),
|
|
43
46
|
stopReason: z.enum(["frames", ...POINT_STOP_REASONS]).optional().describe("advance-frames: why it stopped"),
|
|
44
47
|
pointId: z.number().int().min(1).optional().describe("the id of the breakpoint or watchpoint that stopped advance-frames"),
|
|
48
|
+
space: z.enum(SPACES).optional().describe("advance-frames: the CPU that stopped, c64 or drive8. pc is the address in that space"),
|
|
45
49
|
}),
|
|
46
50
|
readOnly: false,
|
|
47
51
|
async run(input, session) {
|
|
@@ -54,6 +58,7 @@ export const c64Execution = defineTool({
|
|
|
54
58
|
...(result.advancedFrames === undefined ? {} : { advancedFrames: result.advancedFrames }),
|
|
55
59
|
...(result.stopReason === undefined ? {} : { stopReason: result.stopReason }),
|
|
56
60
|
...(result.pointId === undefined ? {} : { pointId: result.pointId }),
|
|
61
|
+
...(result.space === undefined ? {} : { space: result.space }),
|
|
57
62
|
};
|
|
58
63
|
},
|
|
59
64
|
});
|
|
@@ -93,6 +98,7 @@ export const c64RunUntil = defineTool({
|
|
|
93
98
|
history: z
|
|
94
99
|
.array(z.object({ address: AddressOutput, bytes: z.string(), text: z.string(), a: Byte, x: Byte, y: Byte, sp: Byte, rasterLine: z.number().int().min(0).optional(), rasterCycle: z.number().int().min(0).optional() }))
|
|
95
100
|
.optional(),
|
|
101
|
+
historyMissing: z.string().optional().describe("why the result has no history, for example an old history after a JAM"),
|
|
96
102
|
}),
|
|
97
103
|
readOnly: false,
|
|
98
104
|
async run(input, session) {
|
|
@@ -108,10 +114,7 @@ export const c64RunUntil = defineTool({
|
|
|
108
114
|
}
|
|
109
115
|
const result = await session.runUntil({ target, timeoutFrames: input.timeoutFrames });
|
|
110
116
|
const answer = { ...result, pc: formatC64Address(result.pc) };
|
|
111
|
-
const history =
|
|
112
|
-
input.history === undefined
|
|
113
|
-
? {}
|
|
114
|
-
: { history: (await session.cpuHistory({ limit: input.history, space: result.space })).entries.map((entry) => ({ ...entry, address: formatC64Address(entry.address) })) };
|
|
117
|
+
const history = input.history === undefined ? {} : await runHistory(session, input.history, result.space);
|
|
115
118
|
if (input.observe === undefined) return { ...answer, ...history };
|
|
116
119
|
// The observation at the stop: an image of the screen comes with the result as c64_observe gives it.
|
|
117
120
|
const observed = await c64Observe.run(input.observe, session);
|
|
@@ -120,4 +123,14 @@ export const c64RunUntil = defineTool({
|
|
|
120
123
|
},
|
|
121
124
|
});
|
|
122
125
|
|
|
126
|
+
/** The history at the stop; the run result stays when the session refuses the history. */
|
|
127
|
+
async function runHistory(session: ViceSessionApi, limit: number, space: "c64" | "drive8") {
|
|
128
|
+
try {
|
|
129
|
+
return { history: (await session.cpuHistory({ limit, space })).entries.map((entry) => ({ ...entry, address: formatC64Address(entry.address) })) };
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (isRefusal(error) && error.code === "operation-failed") return { historyMissing: error.message };
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
123
136
|
export const executionTools = [c64Execution, c64RunUntil];
|
package/src/mcp/tools/media.ts
CHANGED
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
// c64_snapshot. Paths are relative to the project folder; the host-client
|
|
3
3
|
// reads the file and the Host Runtime receives only its bytes.
|
|
4
4
|
|
|
5
|
+
import { extname } from "node:path";
|
|
6
|
+
|
|
5
7
|
import { z } from "zod";
|
|
6
8
|
|
|
7
9
|
import { formatC64Address } from "../../c64.ts";
|
|
8
10
|
import { projectFolder, readProjectFile } from "../../host-client/transfer.ts";
|
|
11
|
+
import { WireFailure } from "../../protocol/messages.ts";
|
|
9
12
|
import { AUTOSTART_TYPES, DISK_TYPES, MAX_SNAPSHOTS, RUN_STATES } from "../../protocol/vice.ts";
|
|
10
13
|
import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
11
14
|
import { AddressInput } from "../inputs.ts";
|
|
12
|
-
import { AddressOutput, defineTool, requireFields, TransientName } from "../server.ts";
|
|
15
|
+
import { AddressOutput, defineTool, requireFields, requireOut, TransientName } from "../server.ts";
|
|
13
16
|
|
|
14
17
|
const ProjectPath = z.string().min(1).describe("path relative to the project folder, for example build/game.prg");
|
|
15
18
|
|
|
@@ -63,6 +66,10 @@ export const c64ProgramLoad = defineTool({
|
|
|
63
66
|
outputSchema: z.object({ state: z.enum(RUN_STATES), loadAddress: AddressOutput, size: z.number().int().min(1).max(0x10000) }),
|
|
64
67
|
readOnly: false,
|
|
65
68
|
async run(input, session) {
|
|
69
|
+
const type = extname(input.path).slice(1).toLowerCase();
|
|
70
|
+
if (type !== "prg" && (AUTOSTART_TYPES as readonly string[]).includes(type)) {
|
|
71
|
+
throw new WireFailure("invalid-input", `${input.path} is a .${type} image, not a PRG. Use c64_autostart, or c64_disk_attach for a disk image.`);
|
|
72
|
+
}
|
|
66
73
|
const root = projectFolder(input.project);
|
|
67
74
|
const result = await session.programLoad(input.address === undefined ? { path: input.path, root } : { path: input.path, address: input.address, root });
|
|
68
75
|
return { state: result.state, loadAddress: formatC64Address(result.loadAddress), size: result.size };
|
|
@@ -118,12 +125,17 @@ export const c64Snapshot = defineTool({
|
|
|
118
125
|
requireFields(action, fields, []);
|
|
119
126
|
return session.snapshot({ action });
|
|
120
127
|
}
|
|
128
|
+
if (action === "restore") {
|
|
129
|
+
if (fields.name !== undefined && fields.file !== undefined) throw new WireFailure("invalid-input", "Action restore takes name or file, not both.");
|
|
130
|
+
if (fields.name === undefined && fields.file === undefined) throw new WireFailure("invalid-input", "Action restore needs name (a snapshot of this session) or file (a .vsf in the project).");
|
|
131
|
+
}
|
|
121
132
|
if (action === "restore" && fields.file !== undefined) {
|
|
122
133
|
requireFields(action, fields, ["file", "project"], ["file"]);
|
|
123
134
|
return session.snapshotRestore(readProjectFile(fields.file, { root: projectFolder(fields.project) }).bytes);
|
|
124
135
|
}
|
|
125
136
|
if (action === "save") {
|
|
126
|
-
|
|
137
|
+
requireOut(fields);
|
|
138
|
+
requireFields(action, fields, ["name", "out", "project"], ["name"]);
|
|
127
139
|
const saved = await session.snapshot({ action, name: fields.name! });
|
|
128
140
|
return fields.out === undefined ? saved : { ...saved, ...writeOut(fields.out, await session.snapshotExport(fields.name!), fields.project) };
|
|
129
141
|
}
|
package/src/mcp/tools/memory.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { z } from "zod";
|
|
|
6
6
|
import { formatC64Address } from "../../c64.ts";
|
|
7
7
|
import { WireFailure } from "../../protocol/messages.ts";
|
|
8
8
|
import { MAX_COMPARE_DIFFERENCES, MAX_COMPARE_SIZE, MAX_DISASSEMBLE, MAX_MEMORY_READ, MAX_MEMORY_WRITE, MAX_SEARCH_PATTERN, MAX_SEARCH_RESULTS, type RegisterValues } from "../../protocol/vice.ts";
|
|
9
|
-
import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
9
|
+
import { linesText, OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
10
10
|
import { AddressInput, Byte, MemoryViewInput, SpaceInput, TOOL_INPUTS } from "../inputs.ts";
|
|
11
11
|
import { AddressOutput, defineTool, HexData, requireFields } from "../server.ts";
|
|
12
12
|
|
|
@@ -142,7 +142,11 @@ export const c64MemorySearch = defineTool({
|
|
|
142
142
|
throw new WireFailure("invalid-input", `pattern has ${input.pattern.length} bytes. The limit is ${MAX_SEARCH_PATTERN}.`);
|
|
143
143
|
}
|
|
144
144
|
if (input.pattern.every((token) => token === null)) throw new WireFailure("invalid-input", "pattern needs at least one byte that is not ??.");
|
|
145
|
-
const
|
|
145
|
+
const end = input.end ?? 0xffff;
|
|
146
|
+
if (end < input.start) throw new WireFailure("invalid-input", "end is lower than start.");
|
|
147
|
+
// VICE refuses a range shorter than the pattern. Such a range has no match.
|
|
148
|
+
if (end - input.start + 1 < input.pattern.length) return { matches: [] };
|
|
149
|
+
const result = await session.memorySearch({ ...input, end });
|
|
146
150
|
return { matches: result.matches.map(formatC64Address) };
|
|
147
151
|
},
|
|
148
152
|
});
|
|
@@ -220,7 +224,7 @@ export const c64Disassemble = defineTool({
|
|
|
220
224
|
address = instruction.address + instruction.bytes.length / 2;
|
|
221
225
|
}
|
|
222
226
|
}
|
|
223
|
-
return { count: lines.length, ...writeOut(out,
|
|
227
|
+
return { count: lines.length, ...writeOut(out, linesText(lines), project) };
|
|
224
228
|
},
|
|
225
229
|
});
|
|
226
230
|
|
package/src/mcp/tools/video.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { WireFailure } from "../../protocol/messages.ts";
|
|
|
8
8
|
import { CIA_SELECTIONS, MAX_MASK_RECTANGLES, MAX_MEMORY_READ, MAX_OBSERVE_BYTES, MAX_OBSERVE_RANGES, SPACES, VICII_MODES, type ObserveParams } from "../../protocol/vice.ts";
|
|
9
9
|
import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
10
10
|
import { AddressInput, Byte, MemoryViewInput, SpaceInput } from "../inputs.ts";
|
|
11
|
-
import { AddressOutput, defineTool, HexData, requireFields, ToolOutput, TransientName } from "../server.ts";
|
|
11
|
+
import { AddressOutput, defineTool, HexData, requireFields, requireOut, ToolOutput, TransientName } from "../server.ts";
|
|
12
12
|
|
|
13
13
|
const Color = z.number().int().min(0).max(15);
|
|
14
14
|
const NO_CHANGE = "This does not change the run state of the machine.";
|
|
@@ -170,14 +170,16 @@ export const c64Screen = defineTool({
|
|
|
170
170
|
async run(input, session) {
|
|
171
171
|
const { action, ...fields } = input;
|
|
172
172
|
if (action === "capture") {
|
|
173
|
-
|
|
173
|
+
requireOut(fields);
|
|
174
|
+
requireFields(action, fields, ["baseline", "out", "project"]);
|
|
174
175
|
const shot = await session.screenCapture(fields.baseline);
|
|
175
176
|
const result = { width: shot.width, height: shot.height, ...(shot.baseline === undefined ? {} : { baseline: shot.baseline }) };
|
|
176
177
|
if (fields.out !== undefined) return { ...result, ...writeOut(fields.out, Buffer.from(shot.png, "base64"), fields.project) };
|
|
177
178
|
return new ToolOutput(result, [{ type: "image", data: shot.png, mimeType: "image/png" }]);
|
|
178
179
|
}
|
|
179
180
|
if (action === "compare") {
|
|
180
|
-
|
|
181
|
+
requireOut(fields);
|
|
182
|
+
requireFields(action, fields, ["baseline", "maxMismatchRatio", "mask", "includeDiff", "out", "project"], ["baseline"]);
|
|
181
183
|
const { diffPng, ...result } = await session.screenCompare({
|
|
182
184
|
baseline: fields.baseline!,
|
|
183
185
|
maxMismatchRatio: fields.maxMismatchRatio ?? 0,
|
package/src/project.ts
CHANGED
|
@@ -119,7 +119,7 @@ export function namedProject(folder: string, cwd: string = process.cwd()): strin
|
|
|
119
119
|
if (path === homedir()) throw new Refusal("invalid-input", "The home folder is not a project folder.");
|
|
120
120
|
const outer = foldersUpFrom(path).slice(1).find(isProject);
|
|
121
121
|
if (outer !== undefined) {
|
|
122
|
-
throw new Refusal("invalid-input", `${folder} is inside the project ${relative(cwd, outer) ||
|
|
122
|
+
throw new Refusal("invalid-input", `${folder} is inside the project ${relative(cwd, outer) || outer}. Give that project folder.`);
|
|
123
123
|
}
|
|
124
124
|
return path;
|
|
125
125
|
}
|
package/src/protocol/vice.ts
CHANGED
|
@@ -462,6 +462,8 @@ export interface ExecutionResult {
|
|
|
462
462
|
stopReason?: "frames" | PointStopReason;
|
|
463
463
|
/** The id of the breakpoint or watchpoint that stopped the run. */
|
|
464
464
|
pointId?: number;
|
|
465
|
+
/** advance-frames only: the CPU that stopped, and so the space of pc. */
|
|
466
|
+
space?: "c64" | "drive8";
|
|
465
467
|
}
|
|
466
468
|
|
|
467
469
|
/** c64_run_until targets. */
|
|
@@ -1171,7 +1173,8 @@ export function validateViceResult<O extends ViceOperation>(op: O, value: unknow
|
|
|
1171
1173
|
(value.executed === undefined || isInteger(value.executed, 0, MAX_EXECUTION_COUNT)) &&
|
|
1172
1174
|
(value.advancedFrames === undefined || isInteger(value.advancedFrames, 0, MAX_EXECUTION_COUNT)) &&
|
|
1173
1175
|
(value.stopReason === undefined || value.stopReason === "frames" || isOneOf(POINT_STOP_REASONS, value.stopReason)) &&
|
|
1174
|
-
(value.pointId === undefined || isInteger(value.pointId, 1, Number.MAX_SAFE_INTEGER))
|
|
1176
|
+
(value.pointId === undefined || isInteger(value.pointId, 1, Number.MAX_SAFE_INTEGER)) &&
|
|
1177
|
+
(value.space === undefined || isOneOf(SPACES, value.space));
|
|
1175
1178
|
if (!ok) throw new ProtocolError("execution result is malformed");
|
|
1176
1179
|
break;
|
|
1177
1180
|
}
|