c64bridge 0.8.1 → 0.9.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/steer.prompt.md +55 -0
- package/AGENTS.md +9 -7
- package/README.md +139 -95
- 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 +921 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/device.js +86 -10
- package/dist/mcp-server.js +1 -1
- package/dist/prompts/registry.js +27 -25
- package/dist/rag/knowledgeIndex.js +112 -79
- package/dist/rag/resourceUris.js +93 -0
- package/dist/tools/audio.js +11 -11
- package/dist/tools/debug.js +1 -1
- package/dist/tools/developer.js +13 -13
- package/dist/tools/graphics.js +10 -10
- package/dist/tools/machineControl.js +5 -5
- package/dist/tools/memory.js +19 -11
- package/dist/tools/meta/index.js +1 -1
- package/dist/tools/printer.js +11 -12
- package/dist/tools/programRunners.js +16 -16
- package/dist/tools/rag.js +9 -9
- 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 +1 -1
- package/dist/tools/registry/memory.js +1 -1
- 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/storage.js +15 -15
- package/dist/tools/streaming.js +3 -3
- package/dist/tools/types.js +41 -4
- package/dist/tools/vice.js +2 -5
- package/dist/vice/process.js +79 -2
- package/dist/vice/readiness.js +35 -0
- package/dist/vice/smokeOptions.js +44 -0
- 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_config.schema.json +109 -326
- package/mcp/schemas/c64_debug.schema.json +158 -337
- package/mcp/schemas/c64_disk.schema.json +180 -262
- package/mcp/schemas/c64_drive.schema.json +31 -111
- package/mcp/schemas/c64_extract.schema.json +193 -291
- package/mcp/schemas/c64_graphics.schema.json +208 -308
- package/mcp/schemas/c64_memory.schema.json +94 -144
- package/mcp/schemas/c64_printer.schema.json +151 -205
- package/mcp/schemas/c64_program.schema.json +229 -367
- package/mcp/schemas/c64_rag.schema.json +25 -59
- package/mcp/schemas/c64_select_backend.schema.json +19 -26
- package/mcp/schemas/c64_sound.schema.json +346 -622
- package/mcp/schemas/c64_stream.schema.json +25 -54
- package/mcp/schemas/c64_system.schema.json +79 -212
- package/mcp/schemas/c64_vice.schema.json +24 -55
- package/mcp/server.json +1 -1
- package/mcp/tools.json +1935 -3443
- package/mcp.json +3 -3
- package/package.json +17 -14
- package/scripts/fetch-external-sources.mjs +1 -2
- package/scripts/run-coverage.mjs +26 -1
- package/scripts/run-tests.mjs +2 -2
- package/scripts/run-tests.ts +273 -52
- package/scripts/update-readme.ts +54 -2
- package/server.json +2 -2
|
@@ -252,7 +252,7 @@ export const diskModuleGroup = defineToolModule({
|
|
|
252
252
|
domain: "storage",
|
|
253
253
|
summary: "Grouped disk image management, mounting, and discovery tools.",
|
|
254
254
|
supportedPlatforms: ["c64u", "vice"],
|
|
255
|
-
resources: ["c64://
|
|
255
|
+
resources: ["c64://guide/bootstrap"],
|
|
256
256
|
prompts: ["drive-management"],
|
|
257
257
|
defaultTags: ["storage", "drive"],
|
|
258
258
|
workflowHints: [
|
|
@@ -34,7 +34,7 @@ export const driveModuleGroup = defineToolModule({
|
|
|
34
34
|
domain: "drive",
|
|
35
35
|
summary: "Grouped drive power, reset, ROM, and mode helpers.",
|
|
36
36
|
supportedPlatforms: ["c64u", "vice"],
|
|
37
|
-
resources: ["c64://
|
|
37
|
+
resources: ["c64://guide/bootstrap"],
|
|
38
38
|
defaultTags: ["drive", "hardware"],
|
|
39
39
|
workflowHints: [
|
|
40
40
|
"State the resulting power/mode/ROM so the user can reconcile IEC behaviour.",
|
|
@@ -33,7 +33,7 @@ const extractOperationHandlers = createOperationHandlers(extractOperations);
|
|
|
33
33
|
export const extractModule = defineToolModule({
|
|
34
34
|
domain: "extract",
|
|
35
35
|
summary: "Grouped extraction helpers for sprites, charsets, memory dumps, and diagnostics.",
|
|
36
|
-
resources: ["c64://
|
|
36
|
+
resources: ["c64://guide/bootstrap", "c64://basic/spec", "c64://assembly/6510-spec"],
|
|
37
37
|
defaultTags: ["extract", "diagnostics"],
|
|
38
38
|
workflowHints: [
|
|
39
39
|
"Pause the machine when advised so dumps and sprite scans remain stable.",
|
|
@@ -110,7 +110,7 @@ const graphicsOperationHandlers = createOperationHandlers(graphicsOperations);
|
|
|
110
110
|
export const graphicsModuleGroup = defineToolModule({
|
|
111
111
|
domain: "graphics",
|
|
112
112
|
summary: "Grouped frame capture and graphics rendering helpers.",
|
|
113
|
-
resources: ["c64://
|
|
113
|
+
resources: ["c64://graphics/vic/spec", "c64://basic/spec", "c64://assembly/6510-spec"],
|
|
114
114
|
prompts: ["graphics-demo", "basic-program", "assembly-program"],
|
|
115
115
|
defaultTags: ["graphics", "vic"],
|
|
116
116
|
workflowHints: [
|
|
@@ -31,7 +31,7 @@ export const memoryModuleGroup = defineToolModule({
|
|
|
31
31
|
domain: "memory",
|
|
32
32
|
summary: "Grouped memory, screen, and polling operations.",
|
|
33
33
|
supportedPlatforms: ["c64u", "vice"],
|
|
34
|
-
resources: ["c64://
|
|
34
|
+
resources: ["c64://guide/bootstrap", "c64://basic/spec", "c64://assembly/6510-spec"],
|
|
35
35
|
prompts: ["memory-debug", "basic-program", "assembly-program"],
|
|
36
36
|
defaultTags: ["memory", "debug"],
|
|
37
37
|
workflowHints: [
|
|
@@ -92,7 +92,7 @@ export const platformModuleGroup = defineToolModule({
|
|
|
92
92
|
domain: "platform",
|
|
93
93
|
summary: "Runtime backend selection and platform-status coordination.",
|
|
94
94
|
supportedPlatforms: ["c64u", "vice"],
|
|
95
|
-
resources: ["c64://platform/status", "c64://
|
|
95
|
+
resources: ["c64://platform/status", "c64://guide/bootstrap"],
|
|
96
96
|
prompts: [],
|
|
97
97
|
defaultTags: ["platform", "backend", "grouped"],
|
|
98
98
|
workflowHints: [
|
|
@@ -117,7 +117,7 @@ function invokePrintTool(toolName, rawArgs, ctx) {
|
|
|
117
117
|
export const printerModuleGroup = defineToolModule({
|
|
118
118
|
domain: "printer",
|
|
119
119
|
summary: "Grouped printer text, bitmap, and character definition helpers.",
|
|
120
|
-
resources: ["c64://
|
|
120
|
+
resources: ["c64://guide/bootstrap"],
|
|
121
121
|
defaultTags: ["printer", "device"],
|
|
122
122
|
workflowHints: [
|
|
123
123
|
"Mention device/secondary addresses so the user knows which printer workflow ran.",
|
|
@@ -50,7 +50,7 @@ const programOperationHandlers = createOperationHandlers(programOperations);
|
|
|
50
50
|
export const programModule = defineToolModule({
|
|
51
51
|
domain: "programs",
|
|
52
52
|
summary: "Grouped program upload, run, and orchestration workflows.",
|
|
53
|
-
resources: ["c64://
|
|
53
|
+
resources: ["c64://guide/bootstrap", "c64://basic/spec", "c64://assembly/6510-spec"],
|
|
54
54
|
prompts: ["basic-program", "assembly-program"],
|
|
55
55
|
defaultTags: ["programs", "execution"],
|
|
56
56
|
workflowHints: [
|
|
@@ -18,7 +18,7 @@ const ragOperationHandlers = createOperationHandlers(ragOperations);
|
|
|
18
18
|
export const ragModuleGroup = defineToolModule({
|
|
19
19
|
domain: "rag",
|
|
20
20
|
summary: "Grouped retrieval helpers for BASIC and assembly references.",
|
|
21
|
-
resources: ["c64://
|
|
21
|
+
resources: ["c64://basic/spec", "c64://assembly/6510-spec", "c64://guide/bootstrap"],
|
|
22
22
|
prompts: ["basic-program", "assembly-program"],
|
|
23
23
|
defaultTags: ["rag", "search"],
|
|
24
24
|
workflowHints: [
|
|
@@ -140,7 +140,7 @@ export const soundModuleGroup = defineToolModule({
|
|
|
140
140
|
domain: "audio",
|
|
141
141
|
summary: "Grouped SID control, playback, composition, and analysis operations.",
|
|
142
142
|
supportedPlatforms: ["c64u", "vice"],
|
|
143
|
-
resources: ["c64://
|
|
143
|
+
resources: ["c64://sound/sid/spec", "c64://sound/sidwave/spec", "c64://sound/sid/file-format"],
|
|
144
144
|
prompts: ["sid-music"],
|
|
145
145
|
defaultTags: ["sid", "audio"],
|
|
146
146
|
workflowHints: [
|
|
@@ -18,7 +18,7 @@ const streamOperationHandlers = createOperationHandlers(streamOperations);
|
|
|
18
18
|
export const streamModule = defineToolModule({
|
|
19
19
|
domain: "stream",
|
|
20
20
|
summary: "Grouped streaming helpers for starting and stopping Ultimate capture sessions.",
|
|
21
|
-
resources: ["c64://
|
|
21
|
+
resources: ["c64://guide/index", "c64://sound/sid/spec"],
|
|
22
22
|
prompts: ["sid-music"],
|
|
23
23
|
defaultTags: ["stream", "monitor"],
|
|
24
24
|
workflowHints: [
|
|
@@ -66,7 +66,7 @@ export const systemModuleGroup = defineToolModule({
|
|
|
66
66
|
domain: "system",
|
|
67
67
|
summary: "Grouped machine control and background task orchestration.",
|
|
68
68
|
supportedPlatforms: ["c64u", "vice"],
|
|
69
|
-
resources: ["c64://
|
|
69
|
+
resources: ["c64://guide/bootstrap"],
|
|
70
70
|
prompts: ["memory-debug"],
|
|
71
71
|
defaultTags: ["system", "control"],
|
|
72
72
|
workflowHints: [
|
package/dist/tools/storage.js
CHANGED
|
@@ -144,7 +144,7 @@ export const storageModule = defineToolModule({
|
|
|
144
144
|
domain: "storage",
|
|
145
145
|
summary: "Drive management, disk image creation, and file inspection utilities.",
|
|
146
146
|
supportedPlatforms: ["c64u", "vice"],
|
|
147
|
-
resources: ["c64://
|
|
147
|
+
resources: ["c64://guide/bootstrap"],
|
|
148
148
|
prompts: ["memory-debug"],
|
|
149
149
|
defaultTags: ["drive", "storage"],
|
|
150
150
|
workflowHints: [
|
|
@@ -154,10 +154,10 @@ export const storageModule = defineToolModule({
|
|
|
154
154
|
tools: [
|
|
155
155
|
{
|
|
156
156
|
name: "drives_list",
|
|
157
|
-
description: "List Ultimate drive slots and their currently mounted images. Read c64://
|
|
157
|
+
description: "List Ultimate drive slots and their currently mounted images. Read c64://guide/bootstrap for drive safety.",
|
|
158
158
|
summary: "Fetches firmware drive status including mounted images, power state, and modes.",
|
|
159
159
|
inputSchema: drivesListArgsSchema.jsonSchema,
|
|
160
|
-
relatedResources: ["c64://
|
|
160
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
161
161
|
tags: ["drive", "status"],
|
|
162
162
|
prerequisites: [],
|
|
163
163
|
examples: [
|
|
@@ -193,7 +193,7 @@ export const storageModule = defineToolModule({
|
|
|
193
193
|
description: "Mount a disk image onto a specific Ultimate drive slot.",
|
|
194
194
|
summary: "Validates parameters and instructs the firmware to mount an image with optional type/mode overrides.",
|
|
195
195
|
inputSchema: driveMountArgsSchema.jsonSchema,
|
|
196
|
-
relatedResources: ["c64://
|
|
196
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
197
197
|
tags: ["drive", "mount"],
|
|
198
198
|
prerequisites: ["drives_list"],
|
|
199
199
|
examples: [
|
|
@@ -250,7 +250,7 @@ export const storageModule = defineToolModule({
|
|
|
250
250
|
description: "Remove the currently mounted disk image from an Ultimate drive slot.",
|
|
251
251
|
summary: "Ejects the image and leaves the drive empty without powering it off.",
|
|
252
252
|
inputSchema: driveOnlyArgsSchema.jsonSchema,
|
|
253
|
-
relatedResources: ["c64://
|
|
253
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
254
254
|
tags: ["drive", "unmount"],
|
|
255
255
|
prerequisites: ["drives_list"],
|
|
256
256
|
examples: [
|
|
@@ -292,7 +292,7 @@ export const storageModule = defineToolModule({
|
|
|
292
292
|
description: "Reset the selected Ultimate drive slot.",
|
|
293
293
|
summary: "Issues a IEC reset for the chosen drive without cycling the entire machine.",
|
|
294
294
|
inputSchema: driveOnlyArgsSchema.jsonSchema,
|
|
295
|
-
relatedResources: ["c64://
|
|
295
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
296
296
|
tags: ["drive", "reset"],
|
|
297
297
|
prerequisites: ["drives_list"],
|
|
298
298
|
examples: [
|
|
@@ -334,7 +334,7 @@ export const storageModule = defineToolModule({
|
|
|
334
334
|
description: "Power on a specific Ultimate drive slot.",
|
|
335
335
|
summary: "Ensures the selected drive is reported as powered and ready on the IEC bus.",
|
|
336
336
|
inputSchema: driveOnlyArgsSchema.jsonSchema,
|
|
337
|
-
relatedResources: ["c64://
|
|
337
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
338
338
|
tags: ["drive", "power"],
|
|
339
339
|
prerequisites: ["drives_list"],
|
|
340
340
|
examples: [
|
|
@@ -377,7 +377,7 @@ export const storageModule = defineToolModule({
|
|
|
377
377
|
description: "Power off a specific Ultimate drive slot.",
|
|
378
378
|
summary: "Disables the drive without altering mounted media so it can be re-enabled later.",
|
|
379
379
|
inputSchema: driveOnlyArgsSchema.jsonSchema,
|
|
380
|
-
relatedResources: ["c64://
|
|
380
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
381
381
|
tags: ["drive", "power"],
|
|
382
382
|
prerequisites: ["drives_list"],
|
|
383
383
|
examples: [
|
|
@@ -420,7 +420,7 @@ export const storageModule = defineToolModule({
|
|
|
420
420
|
description: "Temporarily load a custom ROM into an Ultimate drive slot.",
|
|
421
421
|
summary: "Points the drive at an alternative ROM file without flashing permanent storage.",
|
|
422
422
|
inputSchema: driveLoadRomArgsSchema.jsonSchema,
|
|
423
|
-
relatedResources: ["c64://
|
|
423
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
424
424
|
tags: ["drive", "rom"],
|
|
425
425
|
prerequisites: ["drives_list"],
|
|
426
426
|
examples: [
|
|
@@ -463,7 +463,7 @@ export const storageModule = defineToolModule({
|
|
|
463
463
|
description: "Set the emulation mode for an Ultimate drive slot (1541/1571/1581).",
|
|
464
464
|
summary: "Switches the drive hardware profile to match the mounted media and desired behaviour.",
|
|
465
465
|
inputSchema: driveModeArgsSchema.jsonSchema,
|
|
466
|
-
relatedResources: ["c64://
|
|
466
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
467
467
|
tags: ["drive", "mode"],
|
|
468
468
|
prerequisites: ["drives_list"],
|
|
469
469
|
examples: [
|
|
@@ -506,7 +506,7 @@ export const storageModule = defineToolModule({
|
|
|
506
506
|
description: "Inspect metadata for a file on the Ultimate filesystem.",
|
|
507
507
|
summary: "Queries the REST API for file size, type, and other details useful when managing disk images.",
|
|
508
508
|
inputSchema: fileInfoArgsSchema.jsonSchema,
|
|
509
|
-
relatedResources: ["c64://
|
|
509
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
510
510
|
tags: ["storage", "info"],
|
|
511
511
|
prerequisites: [],
|
|
512
512
|
examples: [
|
|
@@ -543,7 +543,7 @@ export const storageModule = defineToolModule({
|
|
|
543
543
|
description: "Create a blank D64 disk image on the Ultimate filesystem.",
|
|
544
544
|
summary: "Allocates a 35 or 40 track D64 image with an optional disk name.",
|
|
545
545
|
inputSchema: createD64ArgsSchema.jsonSchema,
|
|
546
|
-
relatedResources: ["c64://
|
|
546
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
547
547
|
tags: ["disk", "create"],
|
|
548
548
|
prerequisites: ["drives_list"],
|
|
549
549
|
examples: [
|
|
@@ -594,7 +594,7 @@ export const storageModule = defineToolModule({
|
|
|
594
594
|
description: "Create a blank D71 disk image on the Ultimate filesystem.",
|
|
595
595
|
summary: "Allocates a 1571-compatible disk image with optional labelling.",
|
|
596
596
|
inputSchema: createD71ArgsSchema.jsonSchema,
|
|
597
|
-
relatedResources: ["c64://
|
|
597
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
598
598
|
tags: ["disk", "create"],
|
|
599
599
|
prerequisites: ["drives_list"],
|
|
600
600
|
examples: [
|
|
@@ -642,7 +642,7 @@ export const storageModule = defineToolModule({
|
|
|
642
642
|
description: "Create a blank D81 disk image on the Ultimate filesystem.",
|
|
643
643
|
summary: "Allocates a 1581-compatible disk image with optional labelling.",
|
|
644
644
|
inputSchema: createD81ArgsSchema.jsonSchema,
|
|
645
|
-
relatedResources: ["c64://
|
|
645
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
646
646
|
tags: ["disk", "create"],
|
|
647
647
|
prerequisites: ["drives_list"],
|
|
648
648
|
examples: [
|
|
@@ -690,7 +690,7 @@ export const storageModule = defineToolModule({
|
|
|
690
690
|
description: "Create a blank DNP disk image on the Ultimate filesystem.",
|
|
691
691
|
summary: "Allocates a CMD-native DNP image with custom track count and optional label.",
|
|
692
692
|
inputSchema: createDnpArgsSchema.jsonSchema,
|
|
693
|
-
relatedResources: ["c64://
|
|
693
|
+
relatedResources: ["c64://guide/bootstrap"],
|
|
694
694
|
tags: ["disk", "create"],
|
|
695
695
|
prerequisites: [
|
|
696
696
|
"drives_list"
|
package/dist/tools/streaming.js
CHANGED
|
@@ -36,8 +36,8 @@ export const streamingModule = defineToolModule({
|
|
|
36
36
|
domain: "streaming",
|
|
37
37
|
summary: "Long-running or streaming workflows such as audio capture or SID playback monitoring.",
|
|
38
38
|
resources: [
|
|
39
|
-
"c64://
|
|
40
|
-
"c64://
|
|
39
|
+
"c64://sound/sid/spec",
|
|
40
|
+
"c64://guide/index",
|
|
41
41
|
],
|
|
42
42
|
prompts: ["sid-music"],
|
|
43
43
|
defaultLifecycle: "stream",
|
|
@@ -50,7 +50,7 @@ export const streamingModule = defineToolModule({
|
|
|
50
50
|
tools: [
|
|
51
51
|
{
|
|
52
52
|
name: "stream_start",
|
|
53
|
-
description: "Start an Ultimate streaming session (video/audio/debug) targeting a host:port destination. See c64://
|
|
53
|
+
description: "Start an Ultimate streaming session (video/audio/debug) targeting a host:port destination. See c64://guide/index for usage notes.",
|
|
54
54
|
summary: "Validates stream arguments and forwards the request to firmware, returning status metadata.",
|
|
55
55
|
inputSchema: streamStartArgsSchema.jsonSchema,
|
|
56
56
|
tags: ["stream", "start"],
|
package/dist/tools/types.js
CHANGED
|
@@ -30,13 +30,50 @@ export function discriminatedUnionSchema(options) {
|
|
|
30
30
|
if (!variants || variants.length === 0) {
|
|
31
31
|
throw new Error("Discriminated union schemas require at least one variant.");
|
|
32
32
|
}
|
|
33
|
+
// Flatten the discriminated union into a single object schema.
|
|
34
|
+
// Claude's API does not support oneOf/allOf/anyOf at the top level,
|
|
35
|
+
// so we merge all variant properties and use an enum for the discriminator.
|
|
36
|
+
const opValues = [];
|
|
37
|
+
const mergedProperties = {};
|
|
38
|
+
const opDescriptions = [];
|
|
39
|
+
for (const variant of variants) {
|
|
40
|
+
const variantProps = variant.properties ?? {};
|
|
41
|
+
const discProp = variantProps[discriminator];
|
|
42
|
+
const opValue = discProp?.const;
|
|
43
|
+
if (opValue) {
|
|
44
|
+
opValues.push(opValue);
|
|
45
|
+
const variantDesc = variant.description ?? discProp?.description ?? "";
|
|
46
|
+
if (variantDesc) {
|
|
47
|
+
opDescriptions.push(`${opValue}: ${variantDesc}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const [key, propSchema] of Object.entries(variantProps)) {
|
|
51
|
+
if (key === discriminator)
|
|
52
|
+
continue;
|
|
53
|
+
if (!mergedProperties[key]) {
|
|
54
|
+
mergedProperties[key] = propSchema;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const opPropertySchema = opValues.length > 0
|
|
59
|
+
? { type: "string", enum: opValues, description: `Selects the operation.` }
|
|
60
|
+
: { type: "string", description: `Selects the operation.` };
|
|
61
|
+
const fullDescription = description
|
|
62
|
+
? (opDescriptions.length > 0
|
|
63
|
+
? `${description}\n\nOperations:\n${opDescriptions.map((d) => `- ${d}`).join("\n")}`
|
|
64
|
+
: description)
|
|
65
|
+
: (opDescriptions.length > 0
|
|
66
|
+
? `Operations:\n${opDescriptions.map((d) => `- ${d}`).join("\n")}`
|
|
67
|
+
: undefined);
|
|
33
68
|
const schema = {
|
|
34
69
|
type: "object",
|
|
35
|
-
...(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
70
|
+
...(fullDescription ? { description: fullDescription } : {}),
|
|
71
|
+
properties: {
|
|
72
|
+
[discriminator]: opPropertySchema,
|
|
73
|
+
...mergedProperties,
|
|
39
74
|
},
|
|
75
|
+
required: [discriminator],
|
|
76
|
+
additionalProperties: false,
|
|
40
77
|
};
|
|
41
78
|
return schema;
|
|
42
79
|
}
|
package/dist/tools/vice.js
CHANGED
|
@@ -15,11 +15,8 @@ const SAFE_RESOURCE_PREFIXES = [
|
|
|
15
15
|
];
|
|
16
16
|
const resourceValueSchema = {
|
|
17
17
|
jsonSchema: {
|
|
18
|
+
type: ["string", "integer"],
|
|
18
19
|
description: "VICE resource value (string or integer).",
|
|
19
|
-
oneOf: [
|
|
20
|
-
{ type: "string" },
|
|
21
|
-
{ type: "integer" },
|
|
22
|
-
],
|
|
23
20
|
},
|
|
24
21
|
parse(value, path) {
|
|
25
22
|
const resolvedPath = path ?? "$.value";
|
|
@@ -110,7 +107,7 @@ const viceOperationDispatcher = createOperationDispatcher("c64_vice", viceOperat
|
|
|
110
107
|
export const viceModuleGroup = defineToolModule({
|
|
111
108
|
domain: "vice",
|
|
112
109
|
summary: "VICE emulator helpers for safe runtime resource access.",
|
|
113
|
-
resources: ["c64://
|
|
110
|
+
resources: ["c64://memory/map"],
|
|
114
111
|
prompts: ["assembly-program", "memory-debug"],
|
|
115
112
|
defaultTags: ["vice", "emulator"],
|
|
116
113
|
workflowHints: [
|
package/dist/vice/process.js
CHANGED
|
@@ -3,10 +3,37 @@ import fs from "node:fs";
|
|
|
3
3
|
import net from "node:net";
|
|
4
4
|
import { createOutputTailCapture, getDiagnosticsSessionInfo, writeDiagnosticEvent } from "../diagnostics.js";
|
|
5
5
|
const DEFAULT_DISPLAY = ":99";
|
|
6
|
+
function hasDisplayArtifacts(display) {
|
|
7
|
+
const match = /^:([0-9]+)$/.exec(display.trim());
|
|
8
|
+
if (!match) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const displayNumber = match[1];
|
|
12
|
+
return fs.existsSync(`/tmp/.X11-unix/X${displayNumber}`) || fs.existsSync(`/tmp/.X${displayNumber}-lock`);
|
|
13
|
+
}
|
|
14
|
+
export function resolveXvfbDisplayForLaunch(display) {
|
|
15
|
+
const explicitDisplay = process.env.VICE_XVFB_DISPLAY?.trim();
|
|
16
|
+
if (explicitDisplay) {
|
|
17
|
+
return explicitDisplay;
|
|
18
|
+
}
|
|
19
|
+
if (display !== DEFAULT_DISPLAY || !hasDisplayArtifacts(display)) {
|
|
20
|
+
return display;
|
|
21
|
+
}
|
|
22
|
+
const baseDisplay = Number.parseInt(display.slice(1), 10);
|
|
23
|
+
for (let candidate = baseDisplay + 1; candidate < baseDisplay + 100; candidate += 1) {
|
|
24
|
+
const candidateDisplay = `:${candidate}`;
|
|
25
|
+
if (!hasDisplayArtifacts(candidateDisplay)) {
|
|
26
|
+
return candidateDisplay;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return display;
|
|
30
|
+
}
|
|
6
31
|
export function delay(ms) {
|
|
7
32
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
33
|
}
|
|
9
34
|
function hasGraphicalSession() {
|
|
35
|
+
if (process.platform === "darwin" || process.platform === "win32")
|
|
36
|
+
return true;
|
|
10
37
|
return configuredDisplay() !== undefined || configuredWaylandDisplay() !== undefined;
|
|
11
38
|
}
|
|
12
39
|
function configuredDisplay() {
|
|
@@ -64,6 +91,53 @@ export async function waitForPort(host, port, timeoutMs = 4000) {
|
|
|
64
91
|
}
|
|
65
92
|
throw new Error(`Timeout waiting for VICE monitor at ${host}:${port}`);
|
|
66
93
|
}
|
|
94
|
+
async function sendResumeMonitorCommand(host, port, timeoutMs = 1500) {
|
|
95
|
+
const deadline = Date.now() + timeoutMs;
|
|
96
|
+
let lastError;
|
|
97
|
+
while (Date.now() < deadline) {
|
|
98
|
+
try {
|
|
99
|
+
await new Promise((resolve, reject) => {
|
|
100
|
+
const socket = net.connect({ host, port });
|
|
101
|
+
const cleanup = () => {
|
|
102
|
+
socket.removeAllListeners();
|
|
103
|
+
socket.destroy();
|
|
104
|
+
};
|
|
105
|
+
const packet = Buffer.alloc(11);
|
|
106
|
+
packet[0] = 0x02;
|
|
107
|
+
packet[1] = 0x02;
|
|
108
|
+
packet.writeUInt32LE(0, 2);
|
|
109
|
+
packet.writeUInt32LE(1, 6);
|
|
110
|
+
packet[10] = 0xaa;
|
|
111
|
+
socket.setTimeout(500, () => {
|
|
112
|
+
cleanup();
|
|
113
|
+
reject(new Error(`Timed out sending VICE monitor resume command to ${host}:${port}`));
|
|
114
|
+
});
|
|
115
|
+
socket.once("error", (error) => {
|
|
116
|
+
cleanup();
|
|
117
|
+
reject(error);
|
|
118
|
+
});
|
|
119
|
+
socket.once("connect", () => {
|
|
120
|
+
socket.write(packet, (error) => {
|
|
121
|
+
cleanup();
|
|
122
|
+
if (error) {
|
|
123
|
+
reject(error);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
resolve();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
lastError = error;
|
|
134
|
+
await delay(50);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
throw lastError instanceof Error
|
|
138
|
+
? lastError
|
|
139
|
+
: new Error(`Failed to resume VICE monitor at ${host}:${port}`);
|
|
140
|
+
}
|
|
67
141
|
async function waitForXvfb(display, timeoutMs = 5000) {
|
|
68
142
|
const match = /^:([0-9]+)/.exec(display.trim());
|
|
69
143
|
if (!match) {
|
|
@@ -113,7 +187,8 @@ export async function terminateProcess(child, signal = "SIGTERM", timeoutMs = 10
|
|
|
113
187
|
}
|
|
114
188
|
export async function startViceProcess(options) {
|
|
115
189
|
const debugEnabled = process.env.VICE_DEVICE_TEST_DEBUG === "1";
|
|
116
|
-
const { useXvfb, display } = shouldUseXvfb(options.visible);
|
|
190
|
+
const { useXvfb, display: requestedDisplay } = shouldUseXvfb(options.visible);
|
|
191
|
+
const display = useXvfb ? resolveXvfbDisplayForLaunch(requestedDisplay) : requestedDisplay;
|
|
117
192
|
const viceEnv = { ...process.env };
|
|
118
193
|
let xvfb = null;
|
|
119
194
|
const xvfbOutput = createOutputTailCapture("xvfb");
|
|
@@ -157,6 +232,7 @@ export async function startViceProcess(options) {
|
|
|
157
232
|
});
|
|
158
233
|
viceEnv.DISPLAY = display;
|
|
159
234
|
await waitForXvfb(display);
|
|
235
|
+
await delay(300);
|
|
160
236
|
}
|
|
161
237
|
const args = [
|
|
162
238
|
"-binarymonitor",
|
|
@@ -216,7 +292,8 @@ export async function startViceProcess(options) {
|
|
|
216
292
|
child.once("exit", onExit);
|
|
217
293
|
child.once("error", onError);
|
|
218
294
|
waitForPort(options.host, options.port)
|
|
219
|
-
.then(() => {
|
|
295
|
+
.then(async () => {
|
|
296
|
+
await sendResumeMonitorCommand(options.host, options.port);
|
|
220
297
|
if (debugEnabled) {
|
|
221
298
|
console.error("[vice-process] monitor port is ready", { host: options.host, port: options.port });
|
|
222
299
|
}
|
package/dist/vice/readiness.js
CHANGED
|
@@ -30,6 +30,41 @@ export async function waitForScreenPattern(bm, pattern, timeoutMs, tickMs = 50,
|
|
|
30
30
|
await exitMonitorIfNeeded(bm);
|
|
31
31
|
return idx;
|
|
32
32
|
}
|
|
33
|
+
export async function waitForStableScreenPattern(bm, pattern, timeoutMs, tickMs = 50, stableReads = 2, onRead, between) {
|
|
34
|
+
const requiredStableReads = Math.max(1, stableReads);
|
|
35
|
+
const start = nowNs();
|
|
36
|
+
let idx = -1;
|
|
37
|
+
let stableIdx = -1;
|
|
38
|
+
let consecutiveMatches = 0;
|
|
39
|
+
while (msSince(start) < timeoutMs) {
|
|
40
|
+
const t = nowNs();
|
|
41
|
+
const screen = await bm.memGet(0x0400, 0x0400 + 999);
|
|
42
|
+
onRead?.("bm_read_screen", t);
|
|
43
|
+
idx = screen.indexOf(pattern);
|
|
44
|
+
if (idx >= 0) {
|
|
45
|
+
if (idx === stableIdx) {
|
|
46
|
+
consecutiveMatches += 1;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
stableIdx = idx;
|
|
50
|
+
consecutiveMatches = 1;
|
|
51
|
+
}
|
|
52
|
+
if (consecutiveMatches >= requiredStableReads) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
stableIdx = -1;
|
|
58
|
+
consecutiveMatches = 0;
|
|
59
|
+
}
|
|
60
|
+
await exitMonitorIfNeeded(bm);
|
|
61
|
+
if (between)
|
|
62
|
+
await between();
|
|
63
|
+
await new Promise((r) => setTimeout(r, Math.max(1, tickMs)));
|
|
64
|
+
}
|
|
65
|
+
await exitMonitorIfNeeded(bm);
|
|
66
|
+
return consecutiveMatches >= requiredStableReads ? idx : -1;
|
|
67
|
+
}
|
|
33
68
|
/** Wait until any non-blank (non-0x00/0x20) character appears on the text screen. */
|
|
34
69
|
export async function waitForAnyScreenText(bm, timeoutMs, tickMs = 50, onRead, between) {
|
|
35
70
|
const start = nowNs();
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const TRUE_VALUES = new Set(["1", "true", "yes", "on"]);
|
|
2
|
+
const FALSE_VALUES = new Set(["0", "false", "no", "off"]);
|
|
3
|
+
function parseVicePort(value) {
|
|
4
|
+
const normalized = value?.trim();
|
|
5
|
+
if (!normalized) {
|
|
6
|
+
return { configuredPort: 6502, hasExplicitPort: false };
|
|
7
|
+
}
|
|
8
|
+
const parsed = Number(normalized);
|
|
9
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
10
|
+
return { configuredPort: 6502, hasExplicitPort: false };
|
|
11
|
+
}
|
|
12
|
+
return { configuredPort: parsed, hasExplicitPort: true };
|
|
13
|
+
}
|
|
14
|
+
export function parseEnvBoolean(value) {
|
|
15
|
+
const normalized = value?.trim().toLowerCase();
|
|
16
|
+
if (!normalized) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
if (TRUE_VALUES.has(normalized)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (FALSE_VALUES.has(normalized)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
export function resolveViceSmokeOptions(env = process.env, argv = process.argv.slice(2)) {
|
|
28
|
+
const visibleDemo = argv.includes("--visible-demo") || parseEnvBoolean(env.VICE_VISIBLE_DEMO) === true;
|
|
29
|
+
const useMock = String(env.VICE_TEST_TARGET ?? "").trim().toLowerCase() === "mock";
|
|
30
|
+
const { configuredPort, hasExplicitPort } = parseVicePort(env.VICE_PORT);
|
|
31
|
+
const visible = visibleDemo || parseEnvBoolean(env.VICE_VISIBLE) === true;
|
|
32
|
+
const keepOpen = visibleDemo || parseEnvBoolean(env.VICE_KEEP_OPEN) === true;
|
|
33
|
+
const warpSetting = parseEnvBoolean(env.VICE_WARP);
|
|
34
|
+
return {
|
|
35
|
+
useMock,
|
|
36
|
+
configuredPort,
|
|
37
|
+
hasExplicitPort,
|
|
38
|
+
visible,
|
|
39
|
+
keepOpen,
|
|
40
|
+
warp: useMock ? true : visibleDemo ? false : warpSetting ?? true,
|
|
41
|
+
display: env.DISPLAY || ":99",
|
|
42
|
+
visibleDemo,
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dist/viceRunner.js
CHANGED
|
@@ -65,6 +65,8 @@ function which(binary) {
|
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
67
|
function shouldUseXvfb() {
|
|
68
|
+
if (process.platform === "darwin" || process.platform === "win32")
|
|
69
|
+
return false;
|
|
68
70
|
if (process.env.FORCE_XVFB === "1")
|
|
69
71
|
return true;
|
|
70
72
|
const ciValue = (process.env.CI || "").toLowerCase();
|
|
@@ -27,13 +27,13 @@ suite can assert expectations in upcoming steps.
|
|
|
27
27
|
|
|
28
28
|
| Family | Baseline Resources (URIs) | Rationale |
|
|
29
29
|
| --- | --- | --- |
|
|
30
|
-
| `basic-program` | `c64://
|
|
31
|
-
| `assembly-program` | `c64://
|
|
32
|
-
| `sid-music` | `c64://
|
|
33
|
-
| `graphics-demo` | `c64://
|
|
34
|
-
| `printer-job` | `c64://
|
|
35
|
-
| `memory-debug` | `c64://
|
|
36
|
-
| `drive-manager` | `c64://
|
|
30
|
+
| `basic-program` | `c64://basic/spec`, `c64://guide/bootstrap`, `c64://guide/index` | Reinforce BASIC token rules, PETSCII gotchas, workflow expectations. |
|
|
31
|
+
| `assembly-program` | `c64://assembly/6510-spec`, `c64://guide/bootstrap`, `c64://graphics/vic/spec`, `c64://sound/sid/spec` | Provide opcode tables, IRQ setup, VIC/SID register references. |
|
|
32
|
+
| `sid-music` | `c64://sound/sid/spec`, `c64://sound/sidwave/spec`, `c64://sound/sid/file-format` | Supply register map, SIDWAVE format, SID file metadata. |
|
|
33
|
+
| `graphics-demo` | `c64://graphics/vic/spec`, `c64://guide/bootstrap` | Cover VIC mode bits, raster constraints, general workflow guidance. |
|
|
34
|
+
| `printer-job` | `c64://printer/spec`, `c64://printer/commodore/text`, `c64://printer/prompt-guide` | Document PETSCII vs ESC/P workflows, sample templates, and safety checks. |
|
|
35
|
+
| `memory-debug` | `c64://guide/bootstrap`, `c64://assembly/6510-spec`, `c64://guide/index` | Highlight addressing safeguards, workflow rules, and knowledge map summaries. |
|
|
36
|
+
| `drive-manager` | `c64://guide/bootstrap` | Ensure awareness of system-wide safety rules before altering drives. |
|
|
37
37
|
|
|
38
38
|
Notes:
|
|
39
39
|
|