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
package/mcp.json
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "c64bridge",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Stdio-based Model Context Protocol (MCP) server that bridges Commodore 64 Ultimate hardware with AI agents.",
|
|
5
|
+
"entry": "dist/index.js",
|
|
6
|
+
"type": "stdio",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"mcp",
|
|
9
|
+
"mcp-server",
|
|
10
|
+
"model context protocol",
|
|
11
|
+
"openai",
|
|
12
|
+
"ultimate64",
|
|
13
|
+
"commodore64",
|
|
14
|
+
"commodore 64",
|
|
15
|
+
"c64",
|
|
16
|
+
"c64u",
|
|
17
|
+
"sid",
|
|
18
|
+
"basic",
|
|
19
|
+
"assembly",
|
|
20
|
+
"retro",
|
|
21
|
+
"retrocomputing",
|
|
22
|
+
"retrogaming",
|
|
23
|
+
"vintage computing",
|
|
24
|
+
"hardware integration",
|
|
25
|
+
"c64 development",
|
|
26
|
+
"commodore 64 ultimate",
|
|
27
|
+
"ultimate 64"
|
|
28
|
+
],
|
|
29
|
+
"env": {
|
|
30
|
+
"LOG_LEVEL": {
|
|
31
|
+
"description": "Logger verbosity (debug, info, warn, error)",
|
|
32
|
+
"default": "info"
|
|
33
|
+
},
|
|
34
|
+
"C64BRIDGE_CONFIG": {
|
|
35
|
+
"description": "Path to configuration JSON",
|
|
36
|
+
"default": "~/.c64bridge.json"
|
|
37
|
+
},
|
|
38
|
+
"C64_MODE": {
|
|
39
|
+
"description": "Select active backend (c64u for Ultimate hardware, vice for emulator)",
|
|
40
|
+
"default": "c64u"
|
|
41
|
+
},
|
|
42
|
+
"RAG_EMBEDDINGS_DIR": {
|
|
43
|
+
"description": "Directory containing RAG embedding JSON files",
|
|
44
|
+
"default": "data"
|
|
45
|
+
},
|
|
46
|
+
"RAG_REINDEX_INTERVAL_MS": {
|
|
47
|
+
"description": "Periodic reindex interval in ms (0 disables)",
|
|
48
|
+
"default": "0"
|
|
49
|
+
},
|
|
50
|
+
"RAG_BUILD_ON_START": {
|
|
51
|
+
"description": "Set to 1 to rebuild embeddings on server start"
|
|
52
|
+
},
|
|
53
|
+
"RAG_DOC_FILES": {
|
|
54
|
+
"description": "Comma-separated extra docs to include in RAG"
|
|
55
|
+
},
|
|
56
|
+
"RAG_DISCOVER_FORCE_REFRESH": {
|
|
57
|
+
"description": "Set to 1 to ignore cached discovery results when fetching external docs"
|
|
58
|
+
},
|
|
59
|
+
"GITHUB_TOKEN": {
|
|
60
|
+
"description": "Personal access token used for optional RAG discovery against GitHub"
|
|
61
|
+
},
|
|
62
|
+
"C64_TEST_TARGET": {
|
|
63
|
+
"description": "Overrides integration tests to hit mock or real hardware (mock|real)"
|
|
64
|
+
},
|
|
65
|
+
"VICE_MODE": {
|
|
66
|
+
"description": "Default video standard for VICE audio capture (ntsc|pal)",
|
|
67
|
+
"default": "ntsc"
|
|
68
|
+
},
|
|
69
|
+
"VICE_LIMIT_CYCLES": {
|
|
70
|
+
"description": "Maximum CPU cycles to render when VICE generates audio",
|
|
71
|
+
"default": "120000000"
|
|
72
|
+
},
|
|
73
|
+
"VICE_BINARY": {
|
|
74
|
+
"description": "VICE binary to launch for audio capture",
|
|
75
|
+
"default": "x64sc"
|
|
76
|
+
},
|
|
77
|
+
"VICE_RUN_TIMEOUT_MS": {
|
|
78
|
+
"description": "Timeout for headless VICE runs in milliseconds",
|
|
79
|
+
"default": "10000"
|
|
80
|
+
},
|
|
81
|
+
"FORCE_XVFB": {
|
|
82
|
+
"description": "Set to 1 to force xvfb-run when executing VICE",
|
|
83
|
+
"default": "0"
|
|
84
|
+
},
|
|
85
|
+
"SIDPLAY_MODE": {
|
|
86
|
+
"description": "Default SID playback mode (ntsc|pal)",
|
|
87
|
+
"default": "ntsc"
|
|
88
|
+
},
|
|
89
|
+
"SIDPLAY_LIMIT_CYCLES": {
|
|
90
|
+
"description": "Maximum CPU cycles to render when sidplayfp generates audio",
|
|
91
|
+
"default": "120000000"
|
|
92
|
+
},
|
|
93
|
+
"SIDPLAY_BINARY": {
|
|
94
|
+
"description": "sidplayfp binary to launch when generating audio",
|
|
95
|
+
"default": "sidplayfp"
|
|
96
|
+
},
|
|
97
|
+
"SIDPLAYFP_BINARY": {
|
|
98
|
+
"description": "Legacy alias for SIDPLAY_BINARY (sidplayfp executable name)"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"repository": {
|
|
102
|
+
"type": "git",
|
|
103
|
+
"url": "https://github.com/chrisgleissner/c64bridge"
|
|
104
|
+
},
|
|
105
|
+
"license": "GPL-2.0-only"
|
|
106
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "c64bridge",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Local MCP server for controlling a Commodore 64 via Ultimate 64 REST API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"c64bridge": "scripts/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node scripts/start.mjs",
|
|
12
|
+
"mcp": "node --loader ts-node/esm src/mcp-server.ts",
|
|
13
|
+
"mcp:build": "npm run build && node dist/mcp-server.js",
|
|
14
|
+
"build": "tsc -p tsconfig.json && node scripts/postbuild.mjs && node --import ./scripts/register-ts-node.mjs scripts/update-readme.ts",
|
|
15
|
+
"lint": "npm run check",
|
|
16
|
+
"test": "node scripts/run-tests.mjs",
|
|
17
|
+
"coverage": "c8 -r text -r lcov node scripts/run-tests.mjs",
|
|
18
|
+
"check:package": "node scripts/check-package.mjs",
|
|
19
|
+
"check:run-local": "bash scripts/run-mcp-check.sh local 5",
|
|
20
|
+
"check:run-npm": "bash scripts/run-mcp-check.sh npm 5",
|
|
21
|
+
"check": "npm run build && npm test",
|
|
22
|
+
"build:rag": "node --import ./scripts/register-ts-node.mjs scripts/rag-rebuild.mjs",
|
|
23
|
+
"api:generate": "npx --yes swagger-typescript-api@12.0.4 -p doc/rest/c64-openapi.yaml -o generated/c64 -n index.ts --axios --single-http-client --module-name-first-tag",
|
|
24
|
+
"c64:tool": "bash scripts/c64-tool.sh",
|
|
25
|
+
"rag:rebuild": "node --import ./scripts/register-ts-node.mjs scripts/rag-rebuild.mjs",
|
|
26
|
+
"rag:fetch": "node scripts/fetch-external-sources.mjs",
|
|
27
|
+
"rag:discover": "node --import ./scripts/register-ts-node.mjs src/rag/discover.ts",
|
|
28
|
+
"changelog:generate": "node scripts/generate-changelog.mjs",
|
|
29
|
+
"release:prepare": "node scripts/prepare-release.mjs"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"mcp-server",
|
|
34
|
+
"model-context-protocol",
|
|
35
|
+
"c64",
|
|
36
|
+
"commodore-64",
|
|
37
|
+
"commodore-64-ultimate",
|
|
38
|
+
"ultimate-64",
|
|
39
|
+
"retro-computing",
|
|
40
|
+
"llm"
|
|
41
|
+
],
|
|
42
|
+
"author": "Christian Gleissner",
|
|
43
|
+
"license": "GPL-2.0-only",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.20.2",
|
|
49
|
+
"axios": "^1.7.7",
|
|
50
|
+
"fastify": "^4.28.1",
|
|
51
|
+
"meyda": "^5.6.2",
|
|
52
|
+
"octokit": "^4.0.2",
|
|
53
|
+
"pitchfinder": "^2.2.0",
|
|
54
|
+
"reflect-metadata": "^0.1.13",
|
|
55
|
+
"yaml": "^2.6.0"
|
|
56
|
+
},
|
|
57
|
+
"optionalDependencies": {
|
|
58
|
+
"naudiodon": "^2.3.6"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/node": "^20.19.23",
|
|
62
|
+
"ts-node": "^10.9.2",
|
|
63
|
+
"typescript": "^5.4.5",
|
|
64
|
+
"c8": "^9.1.0"
|
|
65
|
+
},
|
|
66
|
+
"files": [
|
|
67
|
+
"mcp.json",
|
|
68
|
+
"AGENTS.md",
|
|
69
|
+
"LICENSE",
|
|
70
|
+
"README.md",
|
|
71
|
+
"dist/**",
|
|
72
|
+
"data/**",
|
|
73
|
+
"doc/**",
|
|
74
|
+
"scripts/**",
|
|
75
|
+
"generated/**/*.js",
|
|
76
|
+
".github/prompts/**"
|
|
77
|
+
],
|
|
78
|
+
"prepack": "npm run build"
|
|
79
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, extname, basename, resolve } from "node:path";
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { basicToPrg } from "../src/basicConverter.ts";
|
|
6
|
+
import { loadConfig } from "../src/config.ts";
|
|
7
|
+
import { C64Client } from "../src/c64Client.ts";
|
|
8
|
+
|
|
9
|
+
const booleanFlags = new Set(["run"]);
|
|
10
|
+
|
|
11
|
+
function parseOptions(args) {
|
|
12
|
+
const options = new Map();
|
|
13
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
14
|
+
const token = args[i];
|
|
15
|
+
if (!token.startsWith("--")) {
|
|
16
|
+
throw new Error(`Unexpected argument: ${token}`);
|
|
17
|
+
}
|
|
18
|
+
const key = token.slice(2);
|
|
19
|
+
if (booleanFlags.has(key)) {
|
|
20
|
+
options.set(key, true);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const value = args[i + 1];
|
|
24
|
+
if (!value || value.startsWith("--")) {
|
|
25
|
+
throw new Error(`Missing value for option: --${key}`);
|
|
26
|
+
}
|
|
27
|
+
options.set(key, value);
|
|
28
|
+
i += 1;
|
|
29
|
+
}
|
|
30
|
+
return options;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function ensureOutputPath(path) {
|
|
34
|
+
await mkdir(dirname(path), { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveOutputPath(inputPath, suppliedOutput) {
|
|
38
|
+
if (suppliedOutput) {
|
|
39
|
+
return resolve(process.cwd(), suppliedOutput);
|
|
40
|
+
}
|
|
41
|
+
const base = basename(inputPath, extname(inputPath)) || "program";
|
|
42
|
+
return resolve(process.cwd(), "artifacts", `${base}.prg`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function printResult(result) {
|
|
46
|
+
if (!result) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (result.success) {
|
|
50
|
+
console.log("C64 responded:", JSON.stringify(result.details, null, 2));
|
|
51
|
+
} else {
|
|
52
|
+
console.error("C64 returned an error:", JSON.stringify(result.details, null, 2));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function handleConvertBasic(args, { autoRun }) {
|
|
57
|
+
const options = parseOptions(args);
|
|
58
|
+
const inputPath = options.get("input");
|
|
59
|
+
if (!inputPath) {
|
|
60
|
+
throw new Error("Please provide --input <path-to-basic-file>");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const outputPath = resolveOutputPath(inputPath, options.get("output"));
|
|
64
|
+
const contents = await readFile(resolve(process.cwd(), inputPath), "utf8");
|
|
65
|
+
const prg = basicToPrg(contents);
|
|
66
|
+
await ensureOutputPath(outputPath);
|
|
67
|
+
await writeFile(outputPath, prg);
|
|
68
|
+
console.log(`PRG written to ${outputPath}`);
|
|
69
|
+
|
|
70
|
+
if (autoRun || options.get("run")) {
|
|
71
|
+
const result = await runPrgOnHardware(prg);
|
|
72
|
+
printResult(result);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function handleRunPrg(args) {
|
|
77
|
+
const options = parseOptions(args);
|
|
78
|
+
const inputPath = options.get("input");
|
|
79
|
+
if (!inputPath) {
|
|
80
|
+
throw new Error("Please provide --input <path-to-prg-file>");
|
|
81
|
+
}
|
|
82
|
+
const prg = await readFile(resolve(process.cwd(), inputPath));
|
|
83
|
+
const result = await runPrgOnHardware(prg);
|
|
84
|
+
printResult(result);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function runPrgOnHardware(prg) {
|
|
88
|
+
const config = loadConfig();
|
|
89
|
+
const target = config.baseUrl ?? `http://${config.c64_host}`;
|
|
90
|
+
const client = new C64Client(target);
|
|
91
|
+
console.log(`Uploading PRG (${prg.length} bytes) to ${target} ...`);
|
|
92
|
+
return client.runPrg(prg);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function printHelp() {
|
|
96
|
+
console.log(`Usage:
|
|
97
|
+
node --import ./scripts/register-ts-node.mjs scripts/c64-cli.mjs <command> [options]
|
|
98
|
+
|
|
99
|
+
Commands:
|
|
100
|
+
convert-basic Convert a BASIC text file into a PRG. Options: --input <path> [--output <path>] [--run]
|
|
101
|
+
run-basic Convert a BASIC text file and immediately run it. Options: --input <path> [--output <path>]
|
|
102
|
+
run-prg Upload and run an existing PRG file. Options: --input <path>
|
|
103
|
+
|
|
104
|
+
Examples:
|
|
105
|
+
node --import ./scripts/register-ts-node.mjs scripts/c64-cli.mjs convert-basic --input demos/hello.bas
|
|
106
|
+
node --import ./scripts/register-ts-node.mjs scripts/c64-cli.mjs run-basic --input demos/hello.bas
|
|
107
|
+
node --import ./scripts/register-ts-node.mjs scripts/c64-cli.mjs run-prg --input artifacts/demo-basic.prg
|
|
108
|
+
`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function main() {
|
|
112
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
113
|
+
if (!command || command === "--help" || command === "-h") {
|
|
114
|
+
printHelp();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
switch (command) {
|
|
120
|
+
case "convert-basic":
|
|
121
|
+
await handleConvertBasic(rest, { autoRun: false });
|
|
122
|
+
break;
|
|
123
|
+
case "run-basic":
|
|
124
|
+
await handleConvertBasic(rest, { autoRun: true });
|
|
125
|
+
break;
|
|
126
|
+
case "run-prg":
|
|
127
|
+
await handleRunPrg(rest);
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
throw new Error(`Unknown command: ${command}`);
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error instanceof Error) {
|
|
134
|
+
console.error(error.message);
|
|
135
|
+
} else {
|
|
136
|
+
console.error(error);
|
|
137
|
+
}
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
await main();
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
CLI_CMD=(node --import "${REPO_ROOT}/scripts/register-ts-node.mjs" "${REPO_ROOT}/scripts/c64-cli.mjs")
|
|
7
|
+
|
|
8
|
+
function prompt_path() {
|
|
9
|
+
local prompt="$1"
|
|
10
|
+
local value
|
|
11
|
+
read -r -p "${prompt}: " value
|
|
12
|
+
echo "${value}"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function run_cli() {
|
|
16
|
+
echo ""
|
|
17
|
+
"${CLI_CMD[@]}" "$@"
|
|
18
|
+
echo ""
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function convert_basic() {
|
|
22
|
+
local input output
|
|
23
|
+
input=$(prompt_path "Enter path to BASIC source file")
|
|
24
|
+
output=$(prompt_path "Optional PRG output path (leave blank for default)")
|
|
25
|
+
if [[ -z "${input}" ]]; then
|
|
26
|
+
echo "No input provided."
|
|
27
|
+
return
|
|
28
|
+
fi
|
|
29
|
+
if [[ -n "${output}" ]]; then
|
|
30
|
+
run_cli convert-basic --input "${input}" --output "${output}"
|
|
31
|
+
else
|
|
32
|
+
run_cli convert-basic --input "${input}"
|
|
33
|
+
fi
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function convert_and_run() {
|
|
37
|
+
local input output
|
|
38
|
+
input=$(prompt_path "Enter path to BASIC source file")
|
|
39
|
+
output=$(prompt_path "Optional PRG output path (leave blank for default)")
|
|
40
|
+
if [[ -z "${input}" ]]; then
|
|
41
|
+
echo "No input provided."
|
|
42
|
+
return
|
|
43
|
+
fi
|
|
44
|
+
if [[ -n "${output}" ]]; then
|
|
45
|
+
run_cli convert-basic --input "${input}" --output "${output}" --run
|
|
46
|
+
else
|
|
47
|
+
run_cli convert-basic --input "${input}" --run
|
|
48
|
+
fi
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function run_existing_prg() {
|
|
52
|
+
local input
|
|
53
|
+
input=$(prompt_path "Enter path to PRG file")
|
|
54
|
+
if [[ -z "${input}" ]]; then
|
|
55
|
+
echo "No input provided."
|
|
56
|
+
return
|
|
57
|
+
fi
|
|
58
|
+
run_cli run-prg --input "${input}"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function main_menu() {
|
|
62
|
+
while true; do
|
|
63
|
+
cat <<'EOF'
|
|
64
|
+
========================================
|
|
65
|
+
C64 Bridge Utility
|
|
66
|
+
1) Convert BASIC to PRG
|
|
67
|
+
2) Convert BASIC to PRG and run on C64
|
|
68
|
+
3) Run existing PRG on C64
|
|
69
|
+
4) Quit
|
|
70
|
+
========================================
|
|
71
|
+
EOF
|
|
72
|
+
read -r -p "Select an option: " choice
|
|
73
|
+
case "${choice}" in
|
|
74
|
+
1) convert_basic ;;
|
|
75
|
+
2) convert_and_run ;;
|
|
76
|
+
3) run_existing_prg ;;
|
|
77
|
+
4) exit 0 ;;
|
|
78
|
+
*) echo "Invalid choice: ${choice}" ;;
|
|
79
|
+
esac
|
|
80
|
+
done
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main_menu
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const root = process.cwd();
|
|
7
|
+
|
|
8
|
+
function run(cmd, args, opts = {}) {
|
|
9
|
+
return execFileSync(cmd, args, { stdio: 'pipe', encoding: 'utf8', ...opts });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function listPackFiles() {
|
|
13
|
+
const json = run('npm', ['pack', '--dry-run', '--json'], { cwd: root });
|
|
14
|
+
const parsed = JSON.parse(json);
|
|
15
|
+
const files = parsed?.[0]?.files || [];
|
|
16
|
+
return files.map((f) => f.path);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function assertIncludes(files, required) {
|
|
20
|
+
for (const r of required) {
|
|
21
|
+
if (!files.includes(r)) {
|
|
22
|
+
throw new Error(`Package missing required file: ${r}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function assertNoDuplicates(files) {
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
for (const f of files) {
|
|
30
|
+
const lower = f.toLowerCase();
|
|
31
|
+
if (seen.has(lower)) {
|
|
32
|
+
throw new Error(`Duplicate entry in package tarball: ${f}`);
|
|
33
|
+
}
|
|
34
|
+
seen.add(lower);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function spotCheckTarball(included) {
|
|
39
|
+
// Also inspect the actual tarball via system tar to double-verify
|
|
40
|
+
const json = run('npm', ['pack', '--json'], { cwd: root });
|
|
41
|
+
const parsed = JSON.parse(json);
|
|
42
|
+
const filename = parsed?.[0]?.filename;
|
|
43
|
+
if (!filename) throw new Error('npm pack did not return filename');
|
|
44
|
+
|
|
45
|
+
const tgzPath = path.resolve(root, filename);
|
|
46
|
+
const listing = run('tar', ['-tzf', tgzPath], { cwd: root });
|
|
47
|
+
const lines = listing.split(/\r?\n/).filter(Boolean);
|
|
48
|
+
const seen = new Set();
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
const p = line.replace(/^package\//, '');
|
|
51
|
+
const lower = p.toLowerCase();
|
|
52
|
+
if (seen.has(lower)) {
|
|
53
|
+
throw new Error(`Duplicate path detected inside tarball: ${p}`);
|
|
54
|
+
}
|
|
55
|
+
seen.add(lower);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const r of included) {
|
|
59
|
+
if (!seen.has(r.toLowerCase())) {
|
|
60
|
+
throw new Error(`Tarball missing required file: ${r}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await fs.unlink(tgzPath).catch(() => {});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function main() {
|
|
68
|
+
const files = await listPackFiles();
|
|
69
|
+
|
|
70
|
+
const required = [
|
|
71
|
+
'README.md',
|
|
72
|
+
'data/assembly/assembly-spec.md',
|
|
73
|
+
'data/embeddings_basic.json',
|
|
74
|
+
'dist/index.js',
|
|
75
|
+
'dist/rag/discover.config.json',
|
|
76
|
+
'dist/rag/sources.csv',
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
await assertIncludes(files, required);
|
|
80
|
+
await assertNoDuplicates(files);
|
|
81
|
+
await spotCheckTarball(required);
|
|
82
|
+
|
|
83
|
+
console.log('Package checks passed. Required files present and no duplicates found.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main().catch((err) => {
|
|
87
|
+
console.error(err.message || err);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
package/scripts/cli.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Lightweight CLI launcher for the compiled server
|
|
3
|
+
// Usage: `npx c64bridge` or after local install `c64bridge`
|
|
4
|
+
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { stat } from 'node:fs/promises';
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
|
|
10
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
const candidates = [
|
|
13
|
+
'../dist/index.js',
|
|
14
|
+
'../dist/src/index.js',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
async function findEntry() {
|
|
18
|
+
for (const c of candidates) {
|
|
19
|
+
const p = path.resolve(here, c);
|
|
20
|
+
try {
|
|
21
|
+
const s = await stat(p);
|
|
22
|
+
if (s.isFile()) return p;
|
|
23
|
+
} catch {
|
|
24
|
+
// not found, continue
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Fallback: try reading package.json's main field
|
|
29
|
+
try {
|
|
30
|
+
const pkgPath = path.resolve(here, '../package.json');
|
|
31
|
+
const raw = await readFile(pkgPath, 'utf8');
|
|
32
|
+
const pkg = JSON.parse(raw);
|
|
33
|
+
if (pkg && pkg.main) {
|
|
34
|
+
const mainPath = path.resolve(here, '..', pkg.main);
|
|
35
|
+
try {
|
|
36
|
+
const s = await stat(mainPath);
|
|
37
|
+
if (s.isFile()) return mainPath;
|
|
38
|
+
} catch {
|
|
39
|
+
// ignore
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// ignore
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const entry = await findEntry();
|
|
50
|
+
if (!entry) {
|
|
51
|
+
console.error('Cannot find compiled entrypoint. Expected dist/index.js or dist/src/index.js.');
|
|
52
|
+
console.error('If you installed from npm, this likely means the published package is missing compiled files.');
|
|
53
|
+
console.error('Workarounds:');
|
|
54
|
+
console.error(' - Use a local checkout and run `npm run build` before `node scripts/cli.js`');
|
|
55
|
+
console.error(' - Or wait for a patched release that includes compiled artifacts.');
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
} else {
|
|
58
|
+
// import the resolved entry
|
|
59
|
+
const entryUrl = new URL(`file://${entry}`);
|
|
60
|
+
await import(entryUrl.href);
|
|
61
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
import { register } from 'node:module';
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
10
|
+
const tsNodeLoader = pathToFileURL(path.join(projectRoot, 'node_modules', 'ts-node', 'esm.mjs')).href;
|
|
11
|
+
register(tsNodeLoader, pathToFileURL(projectRoot));
|
|
12
|
+
const { fetchFromCsv } = await import('../src/rag/externalFetcher.ts');
|
|
13
|
+
// Resolve sources.csv from env, dist/rag, or src/rag (in that order)
|
|
14
|
+
let csvPath = process.env.RAG_SOURCES_CSV;
|
|
15
|
+
if (!csvPath) {
|
|
16
|
+
const distCsv = path.join(projectRoot, 'dist', 'rag', 'sources.csv');
|
|
17
|
+
try {
|
|
18
|
+
await import('node:fs/promises').then((fs) => fs.access(distCsv)).catch(() => { throw new Error('nope'); });
|
|
19
|
+
csvPath = distCsv;
|
|
20
|
+
} catch {
|
|
21
|
+
csvPath = path.join(projectRoot, 'src', 'rag', 'sources.csv');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const outDir = process.env.RAG_EXTERNAL_DIR || path.join(projectRoot, 'external');
|
|
25
|
+
const defaultDepth = Number(process.env.RAG_DEFAULT_DEPTH || 5);
|
|
26
|
+
const perDomainRps = Number(process.env.RAG_RPS || 5);
|
|
27
|
+
const maxRequestsPerSeed = Number(process.env.RAG_MAX_REQUESTS || 500);
|
|
28
|
+
|
|
29
|
+
const colors = {
|
|
30
|
+
reset: '\x1b[0m',
|
|
31
|
+
info: '\x1b[36m',
|
|
32
|
+
warn: '\x1b[33m',
|
|
33
|
+
error: '\x1b[31m',
|
|
34
|
+
start: '\x1b[34m',
|
|
35
|
+
success: '\x1b[32m',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const log = (entry) => {
|
|
39
|
+
const { level = 'info', event, data } = entry;
|
|
40
|
+
let color = colors.info;
|
|
41
|
+
if (event === 'request_start') color = colors.start;
|
|
42
|
+
else if (event === 'download_success') color = colors.success;
|
|
43
|
+
else if (level === 'warn') color = colors.warn;
|
|
44
|
+
else if (level === 'error') color = colors.error;
|
|
45
|
+
const prefix = event === 'download_success' ? '✅ ' : (event === 'http_error' || level === 'error' || level === 'warn') ? '❌ ' : '';
|
|
46
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), level, event, data });
|
|
47
|
+
process.stdout.write(`${color}${prefix}${line}${colors.reset}\n`);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const summaries = await fetchFromCsv({
|
|
51
|
+
csvPath,
|
|
52
|
+
outDir,
|
|
53
|
+
defaultDepth,
|
|
54
|
+
perDomainRps,
|
|
55
|
+
maxRequestsPerSeed,
|
|
56
|
+
log,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const total = summaries.reduce((a, s) => a + s.visited, 0);
|
|
60
|
+
const dl = summaries.reduce((a, s) => a + s.downloaded, 0);
|
|
61
|
+
log({ level: 'info', event: 'session_summary', data: { seeds: summaries.length, totalVisited: total, totalDownloaded: dl } });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
main().catch((err) => {
|
|
65
|
+
console.error(err);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|