c64bridge 0.8.1 → 0.9.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/.github/prompts/steer.prompt.md +55 -0
- package/AGENTS.md +9 -7
- package/README.md +192 -154
- package/data/embeddings_asm.json +1 -1
- package/data/embeddings_basic.json +1 -1
- package/data/embeddings_hardware.json +1 -1
- package/data/embeddings_mixed.json +1 -1
- package/data/embeddings_other.json +1 -1
- package/data/graphics/petscii-style-guide.md +4 -4
- package/data/graphics/sprite-charset-best-practices.md +9 -9
- package/data/vice/vice-binary-monitor-spec.md +938 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/c64Client.js +76 -2
- package/dist/device.js +129 -13
- package/dist/mcp-server.js +1 -1
- package/dist/prompts/registry.js +27 -25
- package/dist/rag/indexer.js +18 -8
- package/dist/rag/knowledgeIndex.js +112 -79
- package/dist/rag/resourceUris.js +93 -0
- package/dist/sidwaveCompiler.js +1 -1
- package/dist/{assemblyConverter.js → tools/assember.js} +10 -1
- package/dist/tools/audio.js +11 -11
- package/dist/{basicConverter.js → tools/basicTokenizer.js} +1 -1
- package/dist/tools/debug.js +159 -6
- package/dist/tools/developer.js +13 -13
- package/dist/tools/disassembler.js +478 -0
- package/dist/tools/graphics.js +10 -10
- package/dist/tools/input.js +338 -0
- package/dist/tools/machineControl.js +5 -5
- package/dist/tools/memory.js +336 -12
- package/dist/tools/meta/index.js +1 -1
- package/dist/tools/printer.js +11 -12
- package/dist/tools/programRunners.js +61 -22
- package/dist/tools/rag.js +9 -9
- package/dist/tools/registry/batch.js +167 -0
- package/dist/tools/registry/config.js +1 -1
- package/dist/tools/registry/disk.js +1 -1
- package/dist/tools/registry/drive.js +1 -1
- package/dist/tools/registry/extract.js +1 -1
- package/dist/tools/registry/graphics.js +102 -2
- package/dist/tools/registry/index.js +4 -0
- package/dist/tools/registry/input.js +2 -0
- package/dist/tools/registry/memory.js +62 -2
- package/dist/tools/registry/platform.js +1 -1
- package/dist/tools/registry/printer.js +1 -1
- package/dist/tools/registry/program.js +1 -1
- package/dist/tools/registry/rag.js +1 -1
- package/dist/tools/registry/sound.js +1 -1
- package/dist/tools/registry/stream.js +1 -1
- package/dist/tools/registry/system.js +1 -1
- package/dist/tools/schema.js +4 -5
- package/dist/tools/storage.js +15 -15
- package/dist/tools/streaming.js +3 -3
- package/dist/tools/symbolRegistry.js +49 -0
- package/dist/tools/types.js +56 -4
- package/dist/tools/vice.js +2 -5
- package/dist/vice/process.js +120 -2
- package/dist/vice/readiness.js +35 -0
- package/dist/vice/smokeOptions.js +44 -0
- package/dist/vice/viceClient.js +5 -1
- package/dist/viceRunner.js +2 -0
- package/doc/plans/prompts/README.md +7 -7
- package/mcp/prompts.json +42 -59
- package/mcp/protocol-examples.json +2 -2
- package/mcp/resources.json +179 -166
- package/mcp/schemas/c64_batch.schema.json +53 -0
- package/mcp/schemas/c64_config.schema.json +525 -283
- package/mcp/schemas/c64_debug.schema.json +763 -304
- package/mcp/schemas/c64_disk.schema.json +499 -243
- package/mcp/schemas/c64_drive.schema.json +165 -90
- package/mcp/schemas/c64_extract.schema.json +533 -274
- package/mcp/schemas/c64_graphics.schema.json +607 -295
- package/mcp/schemas/c64_input.schema.json +136 -0
- package/mcp/schemas/c64_memory.schema.json +637 -128
- package/mcp/schemas/c64_printer.schema.json +393 -196
- package/mcp/schemas/c64_program.schema.json +699 -340
- package/mcp/schemas/c64_rag.schema.json +100 -54
- package/mcp/schemas/c64_select_backend.schema.json +49 -23
- package/mcp/schemas/c64_sound.schema.json +1092 -577
- package/mcp/schemas/c64_stream.schema.json +89 -47
- package/mcp/schemas/c64_system.schema.json +351 -185
- package/mcp/schemas/c64_vice.schema.json +85 -47
- package/mcp/server.json +1 -1
- package/mcp/tools.json +7299 -3346
- package/mcp.json +3 -3
- package/package.json +17 -14
- package/scripts/fetch-external-sources.mjs +1 -2
- package/scripts/run-coverage.mjs +33 -1
- package/scripts/run-tests.mjs +2 -2
- package/scripts/run-tests.ts +275 -52
- package/scripts/update-readme.ts +132 -3
- package/server.json +2 -2
package/dist/tools/memory.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { defineToolModule, OPERATION_DISCRIMINATOR, } from "./types.js";
|
|
3
5
|
import { booleanSchema, numberSchema, objectSchema, optionalSchema, stringSchema } from "./schema.js";
|
|
4
|
-
import { textResult } from "./responses.js";
|
|
6
|
+
import { jsonResult, textResult } from "./responses.js";
|
|
5
7
|
import { ToolError, ToolExecutionError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
|
|
8
|
+
import { disassemble, formatDisassembly } from "./disassembler.js";
|
|
9
|
+
import { getViceSymbols } from "./symbolRegistry.js";
|
|
10
|
+
import { resolveAddressSymbol } from "../knowledge.js";
|
|
6
11
|
function toRecord(details) {
|
|
7
12
|
if (details && typeof details === "object") {
|
|
8
13
|
return details;
|
|
@@ -79,6 +84,18 @@ function supportsMachinePause(ctx) {
|
|
|
79
84
|
const platformId = ctx.platform?.id;
|
|
80
85
|
return platformId !== "vice";
|
|
81
86
|
}
|
|
87
|
+
export const __memoryHelpersForTests = {
|
|
88
|
+
toRecord,
|
|
89
|
+
normaliseFailure,
|
|
90
|
+
cleanHex,
|
|
91
|
+
parseUserHex,
|
|
92
|
+
parseFirmwareHex,
|
|
93
|
+
formatByte,
|
|
94
|
+
resolveAddressLabel,
|
|
95
|
+
resolveLength,
|
|
96
|
+
supportsMachinePause,
|
|
97
|
+
stripOperationDiscriminator,
|
|
98
|
+
};
|
|
82
99
|
const readScreenArgsSchema = objectSchema({
|
|
83
100
|
description: "No arguments are required for reading the current screen contents.",
|
|
84
101
|
properties: {},
|
|
@@ -171,13 +188,21 @@ async function executeReadMemory(rawArgs, ctx) {
|
|
|
171
188
|
const detailRecord = toRecord(result.details) ?? {};
|
|
172
189
|
const resolvedAddress = resolveAddressLabel(detailRecord, parsed.address);
|
|
173
190
|
const resolvedLength = resolveLength(detailRecord) ?? parsed.length;
|
|
174
|
-
|
|
191
|
+
const data = {
|
|
175
192
|
success: true,
|
|
176
193
|
address: resolvedAddress,
|
|
177
194
|
length: resolvedLength,
|
|
178
195
|
hexData: result.data ?? null,
|
|
179
196
|
details: detailRecord,
|
|
180
|
-
}
|
|
197
|
+
};
|
|
198
|
+
const base = textResult(`Read ${resolvedLength} bytes starting at ${resolvedAddress}.`, data);
|
|
199
|
+
return {
|
|
200
|
+
...base,
|
|
201
|
+
structuredContent: {
|
|
202
|
+
type: "json",
|
|
203
|
+
data,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
181
206
|
}
|
|
182
207
|
catch (error) {
|
|
183
208
|
if (error instanceof ToolError) {
|
|
@@ -344,19 +369,318 @@ async function executeWriteMemory(rawArgs, ctx) {
|
|
|
344
369
|
return unknownErrorResult(error);
|
|
345
370
|
}
|
|
346
371
|
}
|
|
372
|
+
const disassembleArgsSchema = objectSchema({
|
|
373
|
+
description: "Parameters for disassembling a block of VICE memory into 6502/6510 instructions.",
|
|
374
|
+
properties: {
|
|
375
|
+
address: stringSchema({
|
|
376
|
+
description: "Start address as $HHHH, decimal, or symbol name.",
|
|
377
|
+
minLength: 1,
|
|
378
|
+
}),
|
|
379
|
+
length: numberSchema({
|
|
380
|
+
description: "Number of bytes to read and disassemble.",
|
|
381
|
+
integer: true,
|
|
382
|
+
minimum: 1,
|
|
383
|
+
maximum: 4096,
|
|
384
|
+
default: 64,
|
|
385
|
+
}),
|
|
386
|
+
instructionCount: optionalSchema(numberSchema({
|
|
387
|
+
description: "Stop after this many instructions (overrides length as a termination condition).",
|
|
388
|
+
integer: true,
|
|
389
|
+
minimum: 1,
|
|
390
|
+
maximum: 512,
|
|
391
|
+
})),
|
|
392
|
+
},
|
|
393
|
+
required: ["address"],
|
|
394
|
+
additionalProperties: false,
|
|
395
|
+
});
|
|
396
|
+
async function executeDisassemble(rawArgs, ctx) {
|
|
397
|
+
try {
|
|
398
|
+
const parsed = disassembleArgsSchema.parse(rawArgs ?? {});
|
|
399
|
+
const length = parsed.length ?? 64;
|
|
400
|
+
ctx.logger.info("Disassembling VICE memory", { address: parsed.address, length });
|
|
401
|
+
const address = parseAddressArg(parsed.address, "$.address");
|
|
402
|
+
const bytes = await ctx.client.readMemoryRaw(address, length);
|
|
403
|
+
const symbols = ctx.platform?.id === "vice" ? getViceSymbols() : undefined;
|
|
404
|
+
const lines = disassemble(bytes, address, parsed.instructionCount, symbols);
|
|
405
|
+
const text = formatDisassembly(lines);
|
|
406
|
+
const addrHex = `$${address.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
407
|
+
return textResult(`Disassembly of ${lines.length} instructions starting at ${addrHex}:\n\n${text}`, {
|
|
408
|
+
success: true,
|
|
409
|
+
address: addrHex,
|
|
410
|
+
bytesRead: bytes.length,
|
|
411
|
+
instructionCount: lines.length,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
if (error instanceof ToolError) {
|
|
416
|
+
return toolErrorResult(error);
|
|
417
|
+
}
|
|
418
|
+
return unknownErrorResult(error);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// ---------------------------------------------------------------------------
|
|
422
|
+
// Shared address-parsing helper
|
|
423
|
+
// ---------------------------------------------------------------------------
|
|
424
|
+
function parseAddressArg(raw, fieldPath) {
|
|
425
|
+
const trimmed = raw.trim();
|
|
426
|
+
let addr;
|
|
427
|
+
if (/^\$[0-9A-Fa-f]+$/.test(trimmed)) {
|
|
428
|
+
addr = parseInt(trimmed.slice(1), 16);
|
|
429
|
+
}
|
|
430
|
+
else if (/^0[xX][0-9A-Fa-f]+$/.test(trimmed)) {
|
|
431
|
+
addr = parseInt(trimmed.slice(2), 16);
|
|
432
|
+
}
|
|
433
|
+
else if (/^[0-9]+$/.test(trimmed)) {
|
|
434
|
+
addr = parseInt(trimmed, 10);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
const resolved = resolveAddressSymbol(trimmed);
|
|
438
|
+
if (resolved === undefined) {
|
|
439
|
+
throw new ToolValidationError(`Unknown address or symbol: ${trimmed}`, { path: fieldPath });
|
|
440
|
+
}
|
|
441
|
+
addr = resolved;
|
|
442
|
+
}
|
|
443
|
+
if (!Number.isInteger(addr) || addr < 0 || addr > 0xffff) {
|
|
444
|
+
throw new ToolValidationError("Address must be in range $0000-$FFFF", { path: fieldPath });
|
|
445
|
+
}
|
|
446
|
+
return addr;
|
|
447
|
+
}
|
|
448
|
+
function fmtAddr(addr) {
|
|
449
|
+
return `$${addr.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
450
|
+
}
|
|
451
|
+
function validateRangeWithinAddressSpace(start, length, fieldPath) {
|
|
452
|
+
const end = start + length - 1;
|
|
453
|
+
if (end > 0xffff) {
|
|
454
|
+
throw new ToolValidationError(`${fieldPath} range exceeds address space; ${fmtAddr(start)} + ${length} bytes would pass $FFFF.`, { path: fieldPath });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// ---------------------------------------------------------------------------
|
|
458
|
+
// copy_memory
|
|
459
|
+
// ---------------------------------------------------------------------------
|
|
460
|
+
const copyMemoryArgsSchema = objectSchema({
|
|
461
|
+
description: "Copy a region of RAM from one address to another.",
|
|
462
|
+
properties: {
|
|
463
|
+
source: stringSchema({ description: "Source start address ($HHHH, decimal, or symbol).", minLength: 1 }),
|
|
464
|
+
dest: stringSchema({ description: "Destination start address.", minLength: 1 }),
|
|
465
|
+
length: numberSchema({ description: "Number of bytes to copy (1–16384).", integer: true, minimum: 1, maximum: 16384 }),
|
|
466
|
+
},
|
|
467
|
+
required: ["source", "dest", "length"],
|
|
468
|
+
additionalProperties: false,
|
|
469
|
+
});
|
|
470
|
+
async function executeCopyMemory(rawArgs, ctx) {
|
|
471
|
+
try {
|
|
472
|
+
const parsed = copyMemoryArgsSchema.parse(rawArgs ?? {});
|
|
473
|
+
const src = parseAddressArg(parsed.source, "$.source");
|
|
474
|
+
const dst = parseAddressArg(parsed.dest, "$.dest");
|
|
475
|
+
const len = parsed.length;
|
|
476
|
+
validateRangeWithinAddressSpace(src, len, "$.source");
|
|
477
|
+
validateRangeWithinAddressSpace(dst, len, "$.dest");
|
|
478
|
+
ctx.logger.info("Copying memory", { src: fmtAddr(src), dst: fmtAddr(dst), len });
|
|
479
|
+
const bytes = await ctx.client.readMemoryRaw(src, len);
|
|
480
|
+
await ctx.client.writeMemoryRaw(dst, bytes);
|
|
481
|
+
return textResult(`Copied ${bytes.length} bytes from ${fmtAddr(src)} to ${fmtAddr(dst)}.`, {
|
|
482
|
+
success: true, source: fmtAddr(src), dest: fmtAddr(dst), bytesCopied: bytes.length,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
if (error instanceof ToolError)
|
|
487
|
+
return toolErrorResult(error);
|
|
488
|
+
return unknownErrorResult(error);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// ---------------------------------------------------------------------------
|
|
492
|
+
// fill_memory
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
494
|
+
const fillMemoryArgsSchema = objectSchema({
|
|
495
|
+
description: "Fill a memory region with a repeating byte pattern.",
|
|
496
|
+
properties: {
|
|
497
|
+
address: stringSchema({ description: "Start address ($HHHH, decimal, or symbol).", minLength: 1 }),
|
|
498
|
+
length: numberSchema({ description: "Number of bytes to fill (1–16384).", integer: true, minimum: 1, maximum: 16384 }),
|
|
499
|
+
pattern: stringSchema({
|
|
500
|
+
description: "Hex bytes to repeat, e.g. 'FF' or 'AA 55'. Space-separated or run together.",
|
|
501
|
+
minLength: 2,
|
|
502
|
+
pattern: /^[\s0-9A-Fa-f$]+$/,
|
|
503
|
+
}),
|
|
504
|
+
},
|
|
505
|
+
required: ["address", "length", "pattern"],
|
|
506
|
+
additionalProperties: false,
|
|
507
|
+
});
|
|
508
|
+
async function executeFillMemory(rawArgs, ctx) {
|
|
509
|
+
try {
|
|
510
|
+
const parsed = fillMemoryArgsSchema.parse(rawArgs ?? {});
|
|
511
|
+
const addr = parseAddressArg(parsed.address, "$.address");
|
|
512
|
+
validateRangeWithinAddressSpace(addr, parsed.length, "$.address");
|
|
513
|
+
const { bytes: patternBytes } = parseUserHex(parsed.pattern, "$.pattern");
|
|
514
|
+
if (patternBytes.length === 0)
|
|
515
|
+
throw new ToolValidationError("Pattern must not be empty", { path: "$.pattern" });
|
|
516
|
+
const buf = new Uint8Array(parsed.length);
|
|
517
|
+
for (let i = 0; i < parsed.length; i++)
|
|
518
|
+
buf[i] = patternBytes[i % patternBytes.length];
|
|
519
|
+
await ctx.client.writeMemoryRaw(addr, buf);
|
|
520
|
+
ctx.logger.info("Filled memory", { address: fmtAddr(addr), length: parsed.length, pattern: parsed.pattern });
|
|
521
|
+
return textResult(`Filled ${parsed.length} bytes at ${fmtAddr(addr)} with pattern ${parsed.pattern.trim()}.`, {
|
|
522
|
+
success: true, address: fmtAddr(addr), length: parsed.length, pattern: parsed.pattern.trim(),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
if (error instanceof ToolError)
|
|
527
|
+
return toolErrorResult(error);
|
|
528
|
+
return unknownErrorResult(error);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// ---------------------------------------------------------------------------
|
|
532
|
+
// search_memory
|
|
533
|
+
// ---------------------------------------------------------------------------
|
|
534
|
+
const searchMemoryArgsSchema = objectSchema({
|
|
535
|
+
description: "Search for a byte pattern within a memory range.",
|
|
536
|
+
properties: {
|
|
537
|
+
startAddress: stringSchema({ description: "Start of search range ($HHHH, decimal, or symbol).", minLength: 1 }),
|
|
538
|
+
endAddress: stringSchema({ description: "End of search range (inclusive).", minLength: 1 }),
|
|
539
|
+
pattern: stringSchema({
|
|
540
|
+
description: "Hex bytes to find, e.g. 'A9 00' for LDA #$00.",
|
|
541
|
+
minLength: 2,
|
|
542
|
+
pattern: /^[\s0-9A-Fa-f$]+$/,
|
|
543
|
+
}),
|
|
544
|
+
maxResults: optionalSchema(numberSchema({ description: "Maximum matches to return (default 10).", integer: true, minimum: 1, maximum: 100, default: 10 })),
|
|
545
|
+
},
|
|
546
|
+
required: ["startAddress", "endAddress", "pattern"],
|
|
547
|
+
additionalProperties: false,
|
|
548
|
+
});
|
|
549
|
+
async function executeSearchMemory(rawArgs, ctx) {
|
|
550
|
+
try {
|
|
551
|
+
const parsed = searchMemoryArgsSchema.parse(rawArgs ?? {});
|
|
552
|
+
const start = parseAddressArg(parsed.startAddress, "$.startAddress");
|
|
553
|
+
const end = parseAddressArg(parsed.endAddress, "$.endAddress");
|
|
554
|
+
if (end < start)
|
|
555
|
+
throw new ToolValidationError("endAddress must be ≥ startAddress", { path: "$.endAddress" });
|
|
556
|
+
const { bytes: needle } = parseUserHex(parsed.pattern, "$.pattern");
|
|
557
|
+
if (needle.length === 0)
|
|
558
|
+
throw new ToolValidationError("Pattern must not be empty", { path: "$.pattern" });
|
|
559
|
+
const maxResults = parsed.maxResults ?? 10;
|
|
560
|
+
const len = end - start + 1;
|
|
561
|
+
ctx.logger.info("Searching memory", { start: fmtAddr(start), end: fmtAddr(end), pattern: parsed.pattern });
|
|
562
|
+
const haystack = await ctx.client.readMemoryRaw(start, len);
|
|
563
|
+
const matches = [];
|
|
564
|
+
outer: for (let i = 0; i <= haystack.length - needle.length && matches.length < maxResults; i++) {
|
|
565
|
+
for (let j = 0; j < needle.length; j++) {
|
|
566
|
+
if (haystack[i + j] !== needle[j])
|
|
567
|
+
continue outer;
|
|
568
|
+
}
|
|
569
|
+
matches.push(fmtAddr(start + i));
|
|
570
|
+
}
|
|
571
|
+
return jsonResult({ found: matches.length, matches, pattern: parsed.pattern.trim(), range: { start: fmtAddr(start), end: fmtAddr(end) } }, { success: true, found: matches.length });
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
if (error instanceof ToolError)
|
|
575
|
+
return toolErrorResult(error);
|
|
576
|
+
return unknownErrorResult(error);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
// ---------------------------------------------------------------------------
|
|
580
|
+
// compare_memory
|
|
581
|
+
// ---------------------------------------------------------------------------
|
|
582
|
+
const compareMemoryArgsSchema = objectSchema({
|
|
583
|
+
description: "Compare two memory regions and report differing bytes.",
|
|
584
|
+
properties: {
|
|
585
|
+
address1: stringSchema({ description: "First region start address.", minLength: 1 }),
|
|
586
|
+
address2: stringSchema({ description: "Second region start address.", minLength: 1 }),
|
|
587
|
+
length: numberSchema({ description: "Number of bytes to compare (1–16384).", integer: true, minimum: 1, maximum: 16384 }),
|
|
588
|
+
maxDiffs: optionalSchema(numberSchema({ description: "Max differences to report (default 10).", integer: true, minimum: 1, maximum: 200, default: 10 })),
|
|
589
|
+
},
|
|
590
|
+
required: ["address1", "address2", "length"],
|
|
591
|
+
additionalProperties: false,
|
|
592
|
+
});
|
|
593
|
+
async function executeCompareMemory(rawArgs, ctx) {
|
|
594
|
+
try {
|
|
595
|
+
const parsed = compareMemoryArgsSchema.parse(rawArgs ?? {});
|
|
596
|
+
const a1 = parseAddressArg(parsed.address1, "$.address1");
|
|
597
|
+
const a2 = parseAddressArg(parsed.address2, "$.address2");
|
|
598
|
+
const len = parsed.length;
|
|
599
|
+
const maxDiffs = parsed.maxDiffs ?? 10;
|
|
600
|
+
ctx.logger.info("Comparing memory regions", { a1: fmtAddr(a1), a2: fmtAddr(a2), len });
|
|
601
|
+
const [r1, r2] = await Promise.all([
|
|
602
|
+
ctx.client.readMemoryRaw(a1, len),
|
|
603
|
+
ctx.client.readMemoryRaw(a2, len),
|
|
604
|
+
]);
|
|
605
|
+
const diffs = [];
|
|
606
|
+
for (let i = 0; i < len && diffs.length < maxDiffs; i++) {
|
|
607
|
+
if (r1[i] !== r2[i]) {
|
|
608
|
+
diffs.push({
|
|
609
|
+
offset: i,
|
|
610
|
+
address1: fmtAddr(a1 + i),
|
|
611
|
+
address2: fmtAddr(a2 + i),
|
|
612
|
+
value1: formatByte(r1[i] ?? 0),
|
|
613
|
+
value2: formatByte(r2[i] ?? 0),
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const identical = diffs.length === 0;
|
|
618
|
+
return jsonResult({ identical, diffCount: diffs.length, diffs, region1: fmtAddr(a1), region2: fmtAddr(a2), length: len }, { success: true, identical, diffCount: diffs.length });
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
if (error instanceof ToolError)
|
|
622
|
+
return toolErrorResult(error);
|
|
623
|
+
return unknownErrorResult(error);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// ---------------------------------------------------------------------------
|
|
627
|
+
// save_memory
|
|
628
|
+
// ---------------------------------------------------------------------------
|
|
629
|
+
const saveMemoryArgsSchema = objectSchema({
|
|
630
|
+
description: "Dump a memory region to a local file, optionally with a PRG load-address header.",
|
|
631
|
+
properties: {
|
|
632
|
+
startAddress: stringSchema({ description: "Start address of region to save.", minLength: 1 }),
|
|
633
|
+
endAddress: stringSchema({ description: "End address (inclusive).", minLength: 1 }),
|
|
634
|
+
filePath: stringSchema({ description: "Absolute or relative path for the output file.", minLength: 1 }),
|
|
635
|
+
asPrg: optionalSchema(booleanSchema({ description: "Prepend a 2-byte little-endian load-address header (PRG format). Default: true.", default: true })),
|
|
636
|
+
},
|
|
637
|
+
required: ["startAddress", "endAddress", "filePath"],
|
|
638
|
+
additionalProperties: false,
|
|
639
|
+
});
|
|
640
|
+
async function executeSaveMemory(rawArgs, ctx) {
|
|
641
|
+
try {
|
|
642
|
+
const parsed = saveMemoryArgsSchema.parse(rawArgs ?? {});
|
|
643
|
+
const start = parseAddressArg(parsed.startAddress, "$.startAddress");
|
|
644
|
+
const end = parseAddressArg(parsed.endAddress, "$.endAddress");
|
|
645
|
+
if (end < start)
|
|
646
|
+
throw new ToolValidationError("endAddress must be ≥ startAddress", { path: "$.endAddress" });
|
|
647
|
+
const asPrg = parsed.asPrg !== false;
|
|
648
|
+
const len = end - start + 1;
|
|
649
|
+
const resolvedPath = path.resolve(parsed.filePath);
|
|
650
|
+
ctx.logger.info("Saving memory to file", { start: fmtAddr(start), end: fmtAddr(end), filePath: resolvedPath, asPrg });
|
|
651
|
+
const bytes = await ctx.client.readMemoryRaw(start, len);
|
|
652
|
+
const header = asPrg ? Buffer.from([start & 0xff, (start >> 8) & 0xff]) : Buffer.alloc(0);
|
|
653
|
+
await fs.promises.mkdir(path.dirname(resolvedPath), { recursive: true });
|
|
654
|
+
await fs.promises.writeFile(resolvedPath, Buffer.concat([header, Buffer.from(bytes)]));
|
|
655
|
+
const fileSize = bytes.length + header.length;
|
|
656
|
+
return textResult(`Saved ${bytes.length} bytes (${fmtAddr(start)}–${fmtAddr(end)}) to ${resolvedPath}${asPrg ? " (PRG, with load address header)" : ""}.`, { success: true, filePath: resolvedPath, bytesRead: bytes.length, fileSize, asPrg, startAddress: fmtAddr(start), endAddress: fmtAddr(end) });
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
if (error instanceof ToolError)
|
|
660
|
+
return toolErrorResult(error);
|
|
661
|
+
return unknownErrorResult(error);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
347
664
|
export const memoryOperationHandlers = {
|
|
348
665
|
read: async (args, ctx) => executeReadMemory(stripOperationDiscriminator(args), ctx),
|
|
349
666
|
write: async (args, ctx) => executeWriteMemory(stripOperationDiscriminator(args), ctx),
|
|
350
667
|
read_screen: async (args, ctx) => executeReadScreen(stripOperationDiscriminator(args), ctx),
|
|
668
|
+
disassemble: async (args, ctx) => executeDisassemble(stripOperationDiscriminator(args), ctx),
|
|
669
|
+
copy_memory: async (args, ctx) => executeCopyMemory(stripOperationDiscriminator(args), ctx),
|
|
670
|
+
fill_memory: async (args, ctx) => executeFillMemory(stripOperationDiscriminator(args), ctx),
|
|
671
|
+
search_memory: async (args, ctx) => executeSearchMemory(stripOperationDiscriminator(args), ctx),
|
|
672
|
+
compare_memory: async (args, ctx) => executeCompareMemory(stripOperationDiscriminator(args), ctx),
|
|
673
|
+
save_memory: async (args, ctx) => executeSaveMemory(stripOperationDiscriminator(args), ctx),
|
|
351
674
|
};
|
|
675
|
+
export { disassembleArgsSchema, copyMemoryArgsSchema, fillMemoryArgsSchema, searchMemoryArgsSchema, compareMemoryArgsSchema, saveMemoryArgsSchema, };
|
|
352
676
|
export const memoryModule = defineToolModule({
|
|
353
677
|
domain: "memory",
|
|
354
678
|
summary: "Screen, main memory, and low-level inspection utilities.",
|
|
355
679
|
supportedPlatforms: ["c64u", "vice"],
|
|
356
680
|
resources: [
|
|
357
|
-
"c64://
|
|
358
|
-
"c64://
|
|
359
|
-
"c64://
|
|
681
|
+
"c64://guide/bootstrap",
|
|
682
|
+
"c64://basic/spec",
|
|
683
|
+
"c64://assembly/6510-spec",
|
|
360
684
|
],
|
|
361
685
|
prompts: ["memory-debug", "basic-program", "assembly-program"],
|
|
362
686
|
defaultTags: ["memory", "debug"],
|
|
@@ -367,10 +691,10 @@ export const memoryModule = defineToolModule({
|
|
|
367
691
|
tools: [
|
|
368
692
|
{
|
|
369
693
|
name: "read_screen",
|
|
370
|
-
description: "Read the current text screen (40x25) and return its ASCII representation. For PETSCII details, see c64://
|
|
694
|
+
description: "Read the current text screen (40x25) and return its ASCII representation. For PETSCII details, see c64://basic/spec.",
|
|
371
695
|
summary: "Fetches screen RAM, converts from PETSCII, and returns the printable output.",
|
|
372
696
|
inputSchema: readScreenArgsSchema.jsonSchema,
|
|
373
|
-
relatedResources: ["c64://
|
|
697
|
+
relatedResources: ["c64://guide/bootstrap", "c64://basic/spec"],
|
|
374
698
|
relatedPrompts: ["memory-debug", "basic-program", "assembly-program"],
|
|
375
699
|
tags: ["screen", "memory"],
|
|
376
700
|
prerequisites: [],
|
|
@@ -390,10 +714,10 @@ export const memoryModule = defineToolModule({
|
|
|
390
714
|
},
|
|
391
715
|
{
|
|
392
716
|
name: "read",
|
|
393
|
-
description: "Read a range of bytes from main memory and return the data as hexadecimal. Consult c64://
|
|
717
|
+
description: "Read a range of bytes from main memory and return the data as hexadecimal. Consult c64://assembly/6510-spec and docs index.",
|
|
394
718
|
summary: "Resolves symbols, reads memory, and returns a hex dump with addressing metadata.",
|
|
395
719
|
inputSchema: readMemoryArgsSchema.jsonSchema,
|
|
396
|
-
relatedResources: ["c64://
|
|
720
|
+
relatedResources: ["c64://guide/bootstrap", "c64://assembly/6510-spec", "c64://guide/index"],
|
|
397
721
|
relatedPrompts: ["memory-debug", "assembly-program"],
|
|
398
722
|
tags: ["memory", "hex"],
|
|
399
723
|
prerequisites: [],
|
|
@@ -414,10 +738,10 @@ export const memoryModule = defineToolModule({
|
|
|
414
738
|
},
|
|
415
739
|
{
|
|
416
740
|
name: "write",
|
|
417
|
-
description: "Write a hexadecimal byte sequence into main memory at the specified address. See c64://
|
|
741
|
+
description: "Write a hexadecimal byte sequence into main memory at the specified address. See c64://guide/bootstrap for safety rules.",
|
|
418
742
|
summary: "Resolves symbols, validates hex data, and writes bytes to RAM.",
|
|
419
743
|
inputSchema: writeMemoryArgsSchema.jsonSchema,
|
|
420
|
-
relatedResources: ["c64://
|
|
744
|
+
relatedResources: ["c64://guide/bootstrap", "c64://assembly/6510-spec", "c64://guide/index"],
|
|
421
745
|
relatedPrompts: ["memory-debug", "assembly-program"],
|
|
422
746
|
tags: ["memory", "hex", "write"],
|
|
423
747
|
prerequisites: ["read"],
|
package/dist/tools/meta/index.js
CHANGED
|
@@ -28,7 +28,7 @@ const allTools = [
|
|
|
28
28
|
export const metaModule = defineToolModule({
|
|
29
29
|
domain: "meta",
|
|
30
30
|
summary: "High-level meta tools that orchestrate multiple MCP actions.",
|
|
31
|
-
resources: ["c64://
|
|
31
|
+
resources: ["c64://guide/bootstrap", "c64://assembly/6510-spec"],
|
|
32
32
|
defaultTags: ["meta", "orchestration", "experimental"],
|
|
33
33
|
supportedPlatforms: ["c64u", "vice"],
|
|
34
34
|
workflowHints: [
|
package/dist/tools/printer.js
CHANGED
|
@@ -227,13 +227,12 @@ export const printerModule = defineToolModule({
|
|
|
227
227
|
domain: "printer",
|
|
228
228
|
summary: "Printer workflow helpers for Commodore MPS and Epson FX devices, including prompt templates.",
|
|
229
229
|
resources: [
|
|
230
|
-
"c64://
|
|
231
|
-
"c64://
|
|
232
|
-
"c64://
|
|
233
|
-
"c64://
|
|
234
|
-
"c64://
|
|
235
|
-
"c64://
|
|
236
|
-
"c64://docs/printer/prompts",
|
|
230
|
+
"c64://printer/spec",
|
|
231
|
+
"c64://printer/commodore/text",
|
|
232
|
+
"c64://printer/commodore/bitmap",
|
|
233
|
+
"c64://printer/epson/text",
|
|
234
|
+
"c64://printer/epson/bitmap",
|
|
235
|
+
"c64://printer/prompt-guide",
|
|
237
236
|
],
|
|
238
237
|
prompts: ["printer-job"],
|
|
239
238
|
defaultTags: ["printer"],
|
|
@@ -244,10 +243,10 @@ export const printerModule = defineToolModule({
|
|
|
244
243
|
tools: [
|
|
245
244
|
{
|
|
246
245
|
name: "print_text",
|
|
247
|
-
description: "Print text on device 4 using Commodore or Epson workflows. See c64://
|
|
246
|
+
description: "Print text on device 4 using Commodore or Epson workflows. See c64://printer/spec.",
|
|
248
247
|
summary: "Generates printer BASIC, opens device 4, prints text, and optionally emits a form feed.",
|
|
249
248
|
inputSchema: printTextArgsSchema.jsonSchema,
|
|
250
|
-
relatedResources: ["c64://
|
|
249
|
+
relatedResources: ["c64://printer/spec"],
|
|
251
250
|
relatedPrompts: ["printer-job"],
|
|
252
251
|
tags: ["text"],
|
|
253
252
|
prerequisites: ["upload_run_basic"],
|
|
@@ -304,7 +303,7 @@ export const printerModule = defineToolModule({
|
|
|
304
303
|
description: "Print a Commodore MPS bit-image row using BIM BASIC helpers.",
|
|
305
304
|
summary: "Generates BASIC that emits Commodore BIM commands for the provided bitmap columns.",
|
|
306
305
|
inputSchema: commodoreBitmapArgsSchema.jsonSchema,
|
|
307
|
-
relatedResources: ["c64://
|
|
306
|
+
relatedResources: ["c64://printer/commodore/bitmap"],
|
|
308
307
|
relatedPrompts: ["printer-job"],
|
|
309
308
|
tags: ["bitmap", "commodore"],
|
|
310
309
|
prerequisites: ["upload_run_basic"],
|
|
@@ -364,7 +363,7 @@ export const printerModule = defineToolModule({
|
|
|
364
363
|
description: "Print an Epson FX bit-image row using ESC/P commands.",
|
|
365
364
|
summary: "Generates BASIC that emits ESC/P graphics commands for Epson printers.",
|
|
366
365
|
inputSchema: epsonBitmapArgsSchema.jsonSchema,
|
|
367
|
-
relatedResources: ["c64://
|
|
366
|
+
relatedResources: ["c64://printer/epson/bitmap"],
|
|
368
367
|
relatedPrompts: ["printer-job"],
|
|
369
368
|
tags: ["bitmap", "epson"],
|
|
370
369
|
prerequisites: ["upload_run_basic"],
|
|
@@ -425,7 +424,7 @@ export const printerModule = defineToolModule({
|
|
|
425
424
|
description: "Define custom characters on Commodore MPS printers using DLL mode.",
|
|
426
425
|
summary: "Uploads custom glyph data (11 columns each) for device 4 DLL mode.",
|
|
427
426
|
inputSchema: commodoreDllArgsSchema.jsonSchema,
|
|
428
|
-
relatedResources: ["c64://
|
|
427
|
+
relatedResources: ["c64://printer/commodore/bitmap"],
|
|
429
428
|
relatedPrompts: ["printer-job"],
|
|
430
429
|
tags: ["dll", "commodore"],
|
|
431
430
|
prerequisites: ["upload_run_basic"],
|