c64bridge 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/prompts/README.md +6 -0
- package/.github/prompts/assembly-routine-session.prompt.md +13 -0
- package/.github/prompts/basic-program-session.prompt.md +13 -0
- package/.github/prompts/drive-management-session.prompt.md +13 -0
- package/.github/prompts/petscii-art-session.prompt.md +13 -0
- package/.github/prompts/play-classic-game.prompt.md +13 -0
- package/.github/prompts/sid-music-session.prompt.md +13 -0
- package/AGENTS.md +173 -0
- package/LICENSE +339 -0
- package/README.md +582 -0
- package/data/README.md +16 -0
- package/data/api/basic-api-spec.md +154 -0
- package/data/api/kernal-api-spec.md +68 -0
- package/data/assembly/assembly-spec.md +196 -0
- package/data/assembly/examples/effects/sinewave.asm +5 -0
- package/data/assembly/examples/graphics/sprite/mover.asm +15 -0
- package/data/assembly/examples/sid-file-structure.md +8 -0
- package/data/assembly/examples/sid-overview.md +11 -0
- package/data/audio/examples/Biosphere.sid +0 -0
- package/data/audio/sid-file-structure.md +91 -0
- package/data/audio/sid-programming-best-practices.md +198 -0
- package/data/audio/sid-spec.md +166 -0
- package/data/audio/sidwave.md +131 -0
- package/data/audio/sidwave.schema.json +64 -0
- package/data/basic/basic-spec.md +230 -0
- package/data/basic/examples/audio/alle-meine-entchen-expressive.bas +41 -0
- package/data/basic/examples/audio/c-major-scale-pleasant.bas +29 -0
- package/data/basic/examples/io/joystick.bas +5 -0
- package/data/basic/examples/video/bounce.bas +8 -0
- package/data/basic/examples/video/entchen-petscii.bas +16 -0
- package/data/basic/examples/video/hello-world.bas +4 -0
- package/data/basic/examples/video/wave.bas +7 -0
- package/data/context/bootstrap.md +148 -0
- package/data/context/chat.md +36 -0
- package/data/drive/drive-spec.md +215 -0
- package/data/embeddings_asm.json +1 -0
- package/data/embeddings_basic.json +1 -0
- package/data/embeddings_hardware.json +1 -0
- package/data/embeddings_mixed.json +1 -0
- package/data/embeddings_other.json +1 -0
- package/data/io/cia-spec.md +115 -0
- package/data/io/io-spec.md +124 -0
- package/data/memory/kernal-memory-map.md +227 -0
- package/data/memory/low-memory-map.md +133 -0
- package/data/memory/memory-map.md +186 -0
- package/data/printer/printer-commodore-bitmap.md +31 -0
- package/data/printer/printer-commodore.md +28 -0
- package/data/printer/printer-epson-bitmap.md +29 -0
- package/data/printer/printer-epson.md +30 -0
- package/data/printer/printer-prompts.md +29 -0
- package/data/printer/printer-spec.md +180 -0
- package/data/video/character-set.csv +257 -0
- package/data/video/vic-spec.md +276 -0
- package/dist/assemblyConverter.js +1299 -0
- package/dist/audio/record_and_analyze_audio.js +292 -0
- package/dist/basicConverter.js +193 -0
- package/dist/bootstrap/stdio-logger.js +32 -0
- package/dist/c64Client.js +1052 -0
- package/dist/chargen.js +197 -0
- package/dist/config.js +142 -0
- package/dist/context.js +157 -0
- package/dist/device.js +413 -0
- package/dist/index.js +7 -0
- package/dist/knowledge.js +454 -0
- package/dist/logger.js +182 -0
- package/dist/loggingHttpClient.js +88 -0
- package/dist/mcp-server.js +426 -0
- package/dist/petscii.js +135 -0
- package/dist/petsciiArt.js +356 -0
- package/dist/platform.js +82 -0
- package/dist/prompts/registry.js +560 -0
- package/dist/rag/discover.config.json +41 -0
- package/dist/rag/discover.js +401 -0
- package/dist/rag/embeddings.js +60 -0
- package/dist/rag/externalFetcher.js +934 -0
- package/dist/rag/indexer.js +770 -0
- package/dist/rag/init.js +139 -0
- package/dist/rag/knowledgeIndex.js +361 -0
- package/dist/rag/loggingRetriever.js +46 -0
- package/dist/rag/rateLimiter.js +127 -0
- package/dist/rag/retriever.js +74 -0
- package/dist/rag/sources.csv +31 -0
- package/dist/rag/types.js +5 -0
- package/dist/rag/urlUtils.js +38 -0
- package/dist/sidplayRunner.js +240 -0
- package/dist/sidwave.js +101 -0
- package/dist/sidwaveCompiler.js +329 -0
- package/dist/tools/audio.js +944 -0
- package/dist/tools/developer.js +515 -0
- package/dist/tools/errors.js +59 -0
- package/dist/tools/graphics.js +469 -0
- package/dist/tools/machineControl.js +267 -0
- package/dist/tools/memory.js +224 -0
- package/dist/tools/printer.js +482 -0
- package/dist/tools/programRunners.js +315 -0
- package/dist/tools/rag.js +153 -0
- package/dist/tools/registry.js +58 -0
- package/dist/tools/responses.js +36 -0
- package/dist/tools/schema.js +304 -0
- package/dist/tools/storage.js +740 -0
- package/dist/tools/streaming.js +132 -0
- package/dist/tools/types.js +99 -0
- package/dist/viceRunner.js +239 -0
- package/doc/MCP_SETUP.md +153 -0
- package/doc/README.md +7 -0
- package/doc/developer.md +312 -0
- package/doc/img/logo.png +0 -0
- package/doc/img/prompts/duck_petscii.png +0 -0
- package/doc/img/prompts/duck_song.png +0 -0
- package/doc/img/vscode/vscode-copilot-c64-chat-mode.png +0 -0
- package/doc/img/vscode/vscode-copilot-hello-world.png +0 -0
- package/doc/prompts/README.md +96 -0
- package/doc/prompts/user-prompt-ideas.md +147 -0
- package/doc/rest/c64-openapi.yaml +979 -0
- package/doc/rest/c64-rest-api.md +106 -0
- package/doc/troubleshooting-mcp.md +177 -0
- package/generated/c64/index.js +857 -0
- package/mcp.json +106 -0
- package/package.json +79 -0
- package/scripts/c64-cli.mjs +142 -0
- package/scripts/c64-tool.sh +83 -0
- package/scripts/check-package.mjs +89 -0
- package/scripts/cli.js +61 -0
- package/scripts/fetch-external-sources.mjs +67 -0
- package/scripts/generate-changelog.mjs +131 -0
- package/scripts/postbuild.mjs +101 -0
- package/scripts/prepare-release.mjs +58 -0
- package/scripts/rag-rebuild.mjs +53 -0
- package/scripts/register-ts-node.mjs +6 -0
- package/scripts/run-mcp-check.sh +104 -0
- package/scripts/run-tests.mjs +195 -0
- package/scripts/sid2wav.sh +94 -0
- package/scripts/smoke-run.sh +65 -0
- package/scripts/start-mock-c64.mjs +51 -0
- package/scripts/start-mock.mjs +31 -0
- package/scripts/start.mjs +72 -0
- package/scripts/update-readme.ts +164 -0
- package/scripts/verify-package.mjs +147 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/*
|
|
2
|
+
C64 Bridge - An MCP Server for the Commodore 64 Ultimate
|
|
3
|
+
Copyright (C) 2025 Christian Gleissner
|
|
4
|
+
|
|
5
|
+
Licensed under the GNU General Public License v2.0 or later.
|
|
6
|
+
See <https://www.gnu.org/licenses/> for details.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
export const MEMORY_MAP = [
|
|
12
|
+
{ name: "zero_page", start: 0x0000, end: 0x00ff, description: "Zero page (system vectors, pointers, variables)" },
|
|
13
|
+
{ name: "stack", start: 0x0100, end: 0x01ff, description: "6502 hardware stack" },
|
|
14
|
+
{ name: "basic_input_buffer", start: 0x0200, end: 0x02ff, description: "BASIC input buffer / screen editor workspace" },
|
|
15
|
+
{ name: "screen_ram", start: 0x0400, end: 0x07e7, description: "Default text screen RAM (1000 bytes)" },
|
|
16
|
+
{ name: "basic_program", start: 0x0801, end: 0x9fff, description: "Default BASIC program area (grows upward)" },
|
|
17
|
+
{ name: "vic_ii_registers", start: 0xd000, end: 0xd3ff, description: "VIC-II registers (mirrored)" },
|
|
18
|
+
{ name: "sid_registers", start: 0xd400, end: 0xd7ff, description: "SID registers (mirrored)" },
|
|
19
|
+
{ name: "color_ram", start: 0xd800, end: 0xdbff, description: "Color RAM (nibbles, 1000 cells + extras)" },
|
|
20
|
+
{ name: "cia1_registers", start: 0xdc00, end: 0xdcff, description: "CIA1 (keyboard, joystick, timers)" },
|
|
21
|
+
{ name: "cia2_registers", start: 0xdd00, end: 0xddff, description: "CIA2 (serial IEC, timers)" },
|
|
22
|
+
{ name: "io_area", start: 0xd000, end: 0xdfff, description: "I/O area (VIC/SID/CIAs/Color RAM / Char ROM when mapped)" },
|
|
23
|
+
{ name: "kernal_rom", start: 0xe000, end: 0xffff, description: "KERNAL ROM (may be banked out)" },
|
|
24
|
+
];
|
|
25
|
+
const SYMBOLS = Object.freeze({
|
|
26
|
+
// Common symbols
|
|
27
|
+
screen: 0x0400,
|
|
28
|
+
screen_ram: 0x0400,
|
|
29
|
+
basic: 0x0801,
|
|
30
|
+
basic_start: 0x0801,
|
|
31
|
+
color: 0xd800,
|
|
32
|
+
color_ram: 0xd800,
|
|
33
|
+
vic: 0xd000,
|
|
34
|
+
sid: 0xd400,
|
|
35
|
+
cia1: 0xdc00,
|
|
36
|
+
cia2: 0xdd00,
|
|
37
|
+
kernal: 0xe000,
|
|
38
|
+
});
|
|
39
|
+
export function formatAddress(address) {
|
|
40
|
+
return address.toString(16).toUpperCase().padStart(4, "0");
|
|
41
|
+
}
|
|
42
|
+
export function resolveAddressSymbol(input) {
|
|
43
|
+
const key = input.trim().toLowerCase().replace(/\s+/g, "_");
|
|
44
|
+
return SYMBOLS[key];
|
|
45
|
+
}
|
|
46
|
+
export function listSymbols() {
|
|
47
|
+
return Object.entries(SYMBOLS)
|
|
48
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
49
|
+
.map(([name, address]) => ({ name, address, hex: `$${formatAddress(address)}` }));
|
|
50
|
+
}
|
|
51
|
+
export function listMemoryMap() {
|
|
52
|
+
return MEMORY_MAP.slice().sort((a, b) => a.start - b.start);
|
|
53
|
+
}
|
|
54
|
+
// Commodore BASIC v2 concise language specification for guidance and validation.
|
|
55
|
+
// This content is intended as a knowledge base for prompts and tool help.
|
|
56
|
+
export const basic_spec = `
|
|
57
|
+
# Commodore BASIC v2 – Concise Language Specification
|
|
58
|
+
|
|
59
|
+
## When to Use REST API vs BASIC/Assembly
|
|
60
|
+
**IMPORTANT: Use REST API for direct hardware manipulation**
|
|
61
|
+
- If user says "direct", "directly change", "poke", or mentions "direct" + "RAM/SID/VIC", use write_memory REST API
|
|
62
|
+
- If user asks for sound generation, use REST API SID manipulation (unless they want a complete program)
|
|
63
|
+
- REST API endpoints: write_memory (address, bytes), read_memory (address, length)
|
|
64
|
+
- Examples: "directly set border red" → write_memory(53280, "02"), NOT POKE 53280,2
|
|
65
|
+
- Examples: "make a sound" → write_memory SID registers, NOT BASIC sound code
|
|
66
|
+
- Only generate BASIC/ASM code when user explicitly asks for programs/code
|
|
67
|
+
|
|
68
|
+
## SID Sound Generation via REST API
|
|
69
|
+
**Use write_memory for direct SID chip manipulation:**
|
|
70
|
+
|
|
71
|
+
Key SID Registers (decimal addresses):
|
|
72
|
+
- Volume Control: 54296 ($D418) - Set to 15 ($0F) for maximum volume
|
|
73
|
+
- Voice 1 Frequency Low: 54272 ($D400)
|
|
74
|
+
- Voice 1 Frequency High: 54273 ($D401)
|
|
75
|
+
- Voice 1 Control: 54276 ($D404) - Waveform selection + Gate control
|
|
76
|
+
- Voice 1 Attack/Decay: 54277 ($D405) - Envelope timing
|
|
77
|
+
- Voice 1 Sustain/Release: 54278 ($D406) - Envelope levels
|
|
78
|
+
- Voice 2 starts at 54279 ($D407), Voice 3 at 54286 ($D40E)
|
|
79
|
+
|
|
80
|
+
**SID Frequency Register Values for C Major Scale (C4-C5):**
|
|
81
|
+
|
|
82
|
+
**PAL vs NTSC Clock Differences:**
|
|
83
|
+
- PAL: 985,248 Hz SID clock
|
|
84
|
+
- NTSC: 1,022,727 Hz SID clock
|
|
85
|
+
- Formula: freq_value = (frequency_Hz × 65536) / clock_rate
|
|
86
|
+
|
|
87
|
+
**Complete SID Frequency Lookup Table (All Notes, Multiple Octaves):**
|
|
88
|
+
|
|
89
|
+
**NTSC Frequencies (1,022,727 Hz clock):**
|
|
90
|
+
| Note | Hz | SID Value | Lo | Hi | Note | Hz | SID Value | Lo | Hi |
|
|
91
|
+
|-------|---------|-----------|-------|-------|-------|---------|-----------|-------|-------|
|
|
92
|
+
| C2 | 65.41 | 1646 | $6E | $06 | C#2 | 69.30 | 1744 | $D0 | $06 |
|
|
93
|
+
| D2 | 73.42 | 1848 | $38 | $07 | D#2 | 77.78 | 1958 | $A6 | $07 |
|
|
94
|
+
| E2 | 82.41 | 2072 | $18 | $08 | F2 | 87.31 | 2196 | $94 | $08 |
|
|
95
|
+
| F#2 | 92.50 | 2328 | $18 | $09 | G2 | 98.00 | 2465 | $A1 | $09 |
|
|
96
|
+
| G#2 | 103.83 | 2612 | $34 | $0A | A2 | 110.00 | 2767 | $CF | $0A |
|
|
97
|
+
| A#2 | 116.54 | 2932 | $74 | $0B | B2 | 123.47 | 3106 | $22 | $0C |
|
|
98
|
+
| C3 | 130.81 | 3291 | $DB | $0C | C#3 | 138.59 | 3488 | $A0 | $0D |
|
|
99
|
+
| D3 | 146.83 | 3696 | $70 | $0E | D#3 | 155.56 | 3915 | $4B | $0F |
|
|
100
|
+
| E3 | 164.81 | 4145 | $31 | $10 | F3 | 174.61 | 4392 | $28 | $11 |
|
|
101
|
+
| F#3 | 185.00 | 4655 | $2F | $12 | G3 | 196.00 | 4930 | $42 | $13 |
|
|
102
|
+
| G#3 | 207.65 | 5223 | $67 | $14 | A3 | 220.00 | 5534 | $9E | $15 |
|
|
103
|
+
| A#3 | 233.08 | 5863 | $E7 | $16 | B3 | 246.94 | 6211 | $43 | $18 |
|
|
104
|
+
| C4 | 261.63 | 6583 | $B7 | $19 | C#4 | 277.18 | 6976 | $40 | $1B |
|
|
105
|
+
| D4 | 293.66 | 7392 | $E0 | $1C | D#4 | 311.13 | 7830 | $96 | $1E |
|
|
106
|
+
| E4 | 329.63 | 8289 | $61 | $20 | F4 | 349.23 | 8784 | $50 | $22 |
|
|
107
|
+
| F#4 | 369.99 | 9310 | $5E | $24 | G4 | 392.00 | 9859 | $83 | $26 |
|
|
108
|
+
| G#4 | 415.30 | 10446 | $CE | $28 | A4 | 440.00 | 11068 | $CC | $2B |
|
|
109
|
+
| A#4 | 466.16 | 11726 | $CE | $2D | B4 | 493.88 | 12422 | $86 | $30 |
|
|
110
|
+
| C5 | 523.25 | 13166 | $4E | $33 | C#5 | 554.37 | 13952 | $80 | $36 |
|
|
111
|
+
| D5 | 587.33 | 14784 | $C0 | $39 | D#5 | 622.25 | 15659 | $2B | $3D |
|
|
112
|
+
| E5 | 659.25 | 16578 | $C2 | $40 | F5 | 698.46 | 17568 | $A0 | $44 |
|
|
113
|
+
| F#5 | 739.99 | 18620 | $BC | $48 | G5 | 783.99 | 19718 | $06 | $4D |
|
|
114
|
+
| G#5 | 830.61 | 20892 | $9C | $51 | A5 | 880.00 | 22137 | $99 | $56 |
|
|
115
|
+
|
|
116
|
+
**PAL Frequencies (985,248 Hz clock):**
|
|
117
|
+
| Note | Hz | SID Value | Lo | Hi | Note | Hz | SID Value | Lo | Hi |
|
|
118
|
+
|-------|---------|-----------|-------|-------|-------|---------|-----------|-------|-------|
|
|
119
|
+
| C2 | 65.41 | 1710 | $AE | $06 | C#2 | 69.30 | 1812 | $14 | $07 |
|
|
120
|
+
| D2 | 73.42 | 1920 | $80 | $07 | D#2 | 77.78 | 2034 | $F2 | $07 |
|
|
121
|
+
| E2 | 82.41 | 2153 | $69 | $08 | F2 | 87.31 | 2281 | $E9 | $08 |
|
|
122
|
+
| F#2 | 92.50 | 2418 | $72 | $09 | G2 | 98.00 | 2560 | $00 | $0A |
|
|
123
|
+
| G#2 | 103.83 | 2712 | $98 | $0A | A2 | 110.00 | 2874 | $3A | $0B |
|
|
124
|
+
| A#2 | 116.54 | 3046 | $E6 | $0B | B2 | 123.47 | 3227 | $9B | $0C |
|
|
125
|
+
| C3 | 130.81 | 3421 | $5D | $0D | C#3 | 138.59 | 3623 | $27 | $0E |
|
|
126
|
+
| D3 | 146.83 | 3840 | $00 | $0F | D#3 | 155.56 | 4068 | $E4 | $0F |
|
|
127
|
+
| E3 | 164.81 | 4306 | $D2 | $10 | F3 | 174.61 | 4562 | $D2 | $11 |
|
|
128
|
+
| F#3 | 185.00 | 4835 | $E3 | $12 | G3 | 196.00 | 5120 | $00 | $14 |
|
|
129
|
+
| G#3 | 207.65 | 5424 | $30 | $15 | A3 | 220.00 | 5749 | $75 | $16 |
|
|
130
|
+
| A#3 | 233.08 | 6093 | $CD | $17 | B3 | 246.94 | 6454 | $36 | $19 |
|
|
131
|
+
| C4 | 261.63 | 6842 | $BA | $1A | C#4 | 277.18 | 7246 | $4E | $1C |
|
|
132
|
+
| D4 | 293.66 | 7680 | $00 | $1E | D#4 | 311.13 | 8136 | $C8 | $1F |
|
|
133
|
+
| E4 | 329.63 | 8612 | $A4 | $21 | F4 | 349.23 | 9125 | $A5 | $23 |
|
|
134
|
+
| F#4 | 369.99 | 9670 | $C6 | $25 | G4 | 392.00 | 10240 | $00 | $28 |
|
|
135
|
+
| G#4 | 415.30 | 10849 | $61 | $2A | A4 | 440.00 | 11498 | $EA | $2C |
|
|
136
|
+
| A#4 | 466.16 | 12186 | $9A | $2F | B4 | 493.88 | 12908 | $6C | $32 |
|
|
137
|
+
| C5 | 523.25 | 13684 | $74 | $35 | C#5 | 554.37 | 14492 | $9C | $38 |
|
|
138
|
+
| D5 | 587.33 | 15360 | $00 | $3C | D#5 | 622.25 | 16272 | $90 | $3F |
|
|
139
|
+
|
|
140
|
+
**Usage Notes:**
|
|
141
|
+
- All frequencies calculated with precise formulas for accurate tuning
|
|
142
|
+
- Sharps/flats (# symbols) are essential for proper key signatures
|
|
143
|
+
- Multiple octaves available: C2-A5 covers full musical range
|
|
144
|
+
- Use C#/Db, D#/Eb, F#/Gb, G#/Ab, A#/Bb for chromatic scales and proper harmony
|
|
145
|
+
|
|
146
|
+
**System Detection:** Check $02A6 (PAL=$01, NTSC=$00) or use appropriate table based on user's system type.
|
|
147
|
+
|
|
148
|
+
**Key Signature Reference:**
|
|
149
|
+
- C Major: C D E F G A B (no sharps/flats)
|
|
150
|
+
- G Major: C D E F# G A B (one sharp: F#)
|
|
151
|
+
- D Major: C# D E F# G A B (two sharps: F#, C#)
|
|
152
|
+
- F Major: C D Eb F G A Bb (one flat: Bb)
|
|
153
|
+
- This comprehensive table ensures all notes are perfectly in tune for any key!
|
|
154
|
+
|
|
155
|
+
**Default Pleasant Sound - C Major Chord:**
|
|
156
|
+
Use multiple voices for harmony instead of single harsh tones:
|
|
157
|
+
|
|
158
|
+
NTSC System:
|
|
159
|
+
- Voice 1 (C4): Low=$B7, High=$19 (freq=6583)
|
|
160
|
+
- Voice 2 (E4): Low=$61, High=$20 (freq=8289)
|
|
161
|
+
- Voice 3 (G4): Low=$71, High=$26 (freq=9841)
|
|
162
|
+
|
|
163
|
+
PAL System:
|
|
164
|
+
- Voice 1 (C4): Low=$34, High=$1B (freq=6964)
|
|
165
|
+
- Voice 2 (E4): Low=$52, High=$22 (freq=8786)
|
|
166
|
+
- Voice 3 (G4): Low=$CB, High=$28 (freq=10443)
|
|
167
|
+
|
|
168
|
+
Waveform: Triangle wave ($10) + Gate ($01) = $11 (pleasant, not harsh)
|
|
169
|
+
ADSR: Attack/Decay=$31 (gentle), Sustain/Release=$F6 (smooth)
|
|
170
|
+
|
|
171
|
+
**Sound Generation Sequence:**
|
|
172
|
+
1. Set volume: write_memory(54296, '0F')
|
|
173
|
+
2. Configure ADSR envelope for each voice
|
|
174
|
+
3. Set frequencies for desired notes
|
|
175
|
+
4. Trigger with waveform + gate: write_memory(control_reg, waveform + '1')
|
|
176
|
+
5. Stop by clearing gate: write_memory(control_reg, waveform + '0')
|
|
177
|
+
|
|
178
|
+
**Advanced SID Music Techniques:**
|
|
179
|
+
|
|
180
|
+
**Multi-Voice Arrangements:**
|
|
181
|
+
- Voice 1: 54272-54278 (freq, pulse, control, attack/decay, sustain/release)
|
|
182
|
+
- Voice 2: 54279-54285 (same register layout, +7 from Voice 1)
|
|
183
|
+
- Voice 3: 54286-54292 (same register layout, +7 from Voice 2)
|
|
184
|
+
- Use different waveforms for different musical roles
|
|
185
|
+
|
|
186
|
+
**Waveform Selection (Control Register bits 4-7):**
|
|
187
|
+
- Triangle ($10): Smooth, mellow - perfect for bass lines and soft accompaniment
|
|
188
|
+
- Sawtooth ($20): Bright, cutting - excellent for lead melodies
|
|
189
|
+
- Pulse ($40): Rich harmonics - versatile for chords and melodies (requires pulse width setting)
|
|
190
|
+
- Noise ($80): Percussion and sound effects
|
|
191
|
+
|
|
192
|
+
**ADSR Envelope Settings:**
|
|
193
|
+
- Gentle/Romantic: Attack/Decay=$31, Sustain/Release=$F6-$F8
|
|
194
|
+
- Percussive/Rhythmic: Attack/Decay=$09-$41, Sustain/Release=$F0-$F4
|
|
195
|
+
- Lead Melody: Attack/Decay=$09, Sustain/Release=$F0 (quick attack, sustain for clarity)
|
|
196
|
+
- Soft Accompaniment: Attack/Decay=$31-$51, Sustain/Release=$A6-$C8
|
|
197
|
+
|
|
198
|
+
**Musical Arrangement Techniques:**
|
|
199
|
+
- Arpeggios: Play chord notes in sequence across voices for flowing accompaniment
|
|
200
|
+
- Lead + Harmony: Voice 1 sawtooth melody, Voices 2+3 triangle/pulse harmony
|
|
201
|
+
- Chord Progressions: Simultaneous 3-voice chords with appropriate voice spacing
|
|
202
|
+
- Classical Style: Bright lead (sawtooth) with soft arpeggiated accompaniment (triangle)
|
|
203
|
+
|
|
204
|
+
**Popular Song Examples (3-chord progressions):**
|
|
205
|
+
- C-F-G: Many folk and country songs
|
|
206
|
+
- G-C-D: Classic rock and ballad progression
|
|
207
|
+
- A minor arpeggios: A-C-E-A for classical accompaniment
|
|
208
|
+
- Use faster tempos (0.6-0.8s chords) for upbeat, slower (1.2-1.5s) for ballads
|
|
209
|
+
|
|
210
|
+
## Program Structure
|
|
211
|
+
- Programs consist of numbered lines: 1–63999 recommended (absolute max 65535).
|
|
212
|
+
- Each line: lineNumber <space?> statement [{ : statement } ...]
|
|
213
|
+
- Statements on a line are separated by ':'; execution proceeds in line-number order.
|
|
214
|
+
- Comments: REM <text> (or ' in some editors, but not v2); everything until end-of-line.
|
|
215
|
+
- Whitespace is ignored outside strings; keywords are case-insensitive and stored tokenized.
|
|
216
|
+
|
|
217
|
+
## Data Types and Variables
|
|
218
|
+
- Types: numeric (floating point, 5-byte) and string.
|
|
219
|
+
- Numeric variable: A–Z or A–Z followed by digits (e.g., A, X1, SCORE10). No underscore.
|
|
220
|
+
- String variable: same as numeric but with trailing $ (e.g., A$, NAME$).
|
|
221
|
+
- Arrays: DIM name(size[,size...]) for numeric; DIM name$(...) for strings.
|
|
222
|
+
- Default array bounds: not allocated until DIM; indices start at 0 unless explicitly designed otherwise.
|
|
223
|
+
- Constants: numbers (e.g., 10, 3.14, .5, 1E3); strings in double quotes.
|
|
224
|
+
|
|
225
|
+
## Operators and Precedence (high → low)
|
|
226
|
+
1. Unary: -x, NOT x
|
|
227
|
+
2. Exponentiation: x ^ y
|
|
228
|
+
3. Multiplication/Division: x * y, x / y
|
|
229
|
+
4. Addition/Subtraction: x + y, x - y
|
|
230
|
+
5. Comparisons: =, <>, <, <=, >, >= (note: V2 uses =, <, > tokens; <= and >= are parsed as combinations)
|
|
231
|
+
6. Logical: AND, OR (non-short-circuit; numeric 0=false, nonzero=true)
|
|
232
|
+
Parentheses may be used to group.
|
|
233
|
+
|
|
234
|
+
## Control Flow Statements
|
|
235
|
+
- END: terminate program.
|
|
236
|
+
- STOP: break execution (like END but indicates stop).
|
|
237
|
+
- CONT: continue after STOP if possible.
|
|
238
|
+
- GOTO line: jump unconditionally.
|
|
239
|
+
- GOSUB line: call subroutine; RETURN: resume after call. Nesting depth limited by stack.
|
|
240
|
+
- IF expr THEN target
|
|
241
|
+
- target: line number (GOTO implicit), or single statement after THEN.
|
|
242
|
+
- ELSE is not in v2; use IF ... THEN ... : GOTO ... style.
|
|
243
|
+
- FOR var = start TO end [STEP step]
|
|
244
|
+
- NEXT [var]
|
|
245
|
+
- Loop variable is numeric; step defaults to 1; loop executes while (var <= end) for positive step, (var >= end) for negative.
|
|
246
|
+
- NEXT may list multiple variables: NEXT I,J
|
|
247
|
+
|
|
248
|
+
## Input/Output and Devices
|
|
249
|
+
- PRINT [#dev,] [exprs]
|
|
250
|
+
- Separators: ; concatenates without spacing; , tab to next zone (every 10 columns).
|
|
251
|
+
- PRINT# is device output to open channel.
|
|
252
|
+
- INPUT [#dev,] list
|
|
253
|
+
- Prompts with '?'; use INPUT# for device channel.
|
|
254
|
+
- GET [#dev,] var: read single character (no echo), or from device with GET#.
|
|
255
|
+
- TAB(n), SPC(n) in PRINT:
|
|
256
|
+
- TAB(n): move to column n (1-based). SPC(n): print n spaces.
|
|
257
|
+
- CMD dev: make dev the default output device until PRINT# or CMD closed.
|
|
258
|
+
- OPEN dev,[sec],sa,"filename[,params]"; CLOSE dev
|
|
259
|
+
- dev: device number (e.g., 8 for disk); sec is secondary address; sa usually 0 for read, 1 for write.
|
|
260
|
+
- LOAD "name",dev[,sa]; SAVE "name",dev[,sa]
|
|
261
|
+
- On C64, dev=8 is disk drive; without dev uses default.
|
|
262
|
+
- VERIFY "name",dev: compare program in memory with storage.
|
|
263
|
+
|
|
264
|
+
## Data and Variables
|
|
265
|
+
- LET var = expr (LET optional).
|
|
266
|
+
- READ var[, var...]; DATA literal[, literal...]; RESTORE resets DATA pointer to first DATA.
|
|
267
|
+
|
|
268
|
+
## String Handling
|
|
269
|
+
- Concatenation: A$ + B$
|
|
270
|
+
- Substrings: LEFT$(A$,n), MID$(A$,start[,len]), RIGHT$(A$,n)
|
|
271
|
+
- Conversion: STR$(n) -> string; VAL(A$) -> numeric; CHR$(n) -> 1-char string; ASC(A$) -> code of first char.
|
|
272
|
+
- LEN(A$) -> length in characters.
|
|
273
|
+
- **Quotes in strings**: Cannot use " directly inside "...". Use CHR$(34) for quote character.
|
|
274
|
+
Example: PRINT "He said " + CHR$(34) + "Hello!" + CHR$(34) + " to me"
|
|
275
|
+
|
|
276
|
+
## Numeric Functions
|
|
277
|
+
- ABS, SGN, INT, RND([n]), SQR, LOG, EXP, SIN, COS, TAN, ATN, FRE(x), POS(x), USR(x)
|
|
278
|
+
- RND behavior: RND(0) repeats last sequence; RND(1) yields [0,1); RND(n<0) seeds with n.
|
|
279
|
+
- USR(x): calls machine-language routine pointed by vector at $0311/$0312; return value in FAC.
|
|
280
|
+
- PEEK(addr) reads byte; POKE addr, byte writes byte.
|
|
281
|
+
|
|
282
|
+
## Screen and Keyboard Helpers
|
|
283
|
+
- POKE 646,color: set default text color; PRINT CHR$(147) clears screen.
|
|
284
|
+
- GET A$: non-blocking single key; returns "" if none.
|
|
285
|
+
|
|
286
|
+
## Memory and Addresses
|
|
287
|
+
- Default BASIC start: $0801. Program and variables share memory up to top-of-BASIC pointer.
|
|
288
|
+
- Important areas: Screen $0400–$07E7; Color RAM $D800–$DBE7; I/O/VIC/SID at $D000+.
|
|
289
|
+
|
|
290
|
+
## Errors and Limits (common)
|
|
291
|
+
- Syntax errors at tokenization or runtime: ?SYNTAX ERROR, ?TYPE MISMATCH, ?OUT OF MEMORY, ?REDIM'D ARRAY, etc.
|
|
292
|
+
- Line length: ~80 visible chars editor limit; interpreter line storage larger but practical limits apply.
|
|
293
|
+
- Variable namespaces: A and A$ are distinct; arrays share namespace with scalars.
|
|
294
|
+
|
|
295
|
+
## Grammar (informal)
|
|
296
|
+
- program := { line }+
|
|
297
|
+
- line := lineNumber [statement] { ':' statement }
|
|
298
|
+
- statement :=
|
|
299
|
+
END | STOP | CONT |
|
|
300
|
+
GOTO lineNumber |
|
|
301
|
+
GOSUB lineNumber | RETURN |
|
|
302
|
+
FOR var '=' expr TO expr [STEP expr] | NEXT [var {',' var}] |
|
|
303
|
+
IF expr THEN ( lineNumber | statement ) |
|
|
304
|
+
LET? var '=' expr |
|
|
305
|
+
PRINT [ printlist ] | PRINT# expr, [ printlist ] |
|
|
306
|
+
INPUT [ inputlist ] | INPUT# expr, [ inputlist ] |
|
|
307
|
+
GET var | GET# expr, var |
|
|
308
|
+
READ varlist | DATA datalist | RESTORE |
|
|
309
|
+
DIM dimlist |
|
|
310
|
+
OPEN expr[,expr][,expr][,string] | CLOSE expr |
|
|
311
|
+
LOAD string[,expr][,expr] | SAVE string[,expr][,expr] | VERIFY string[,expr] |
|
|
312
|
+
POKE expr, expr | SYS expr | WAIT expr[,expr[,expr]] |
|
|
313
|
+
ON expr GOTO linelist | ON expr GOSUB linelist
|
|
314
|
+
|
|
315
|
+
## Token List (selected)
|
|
316
|
+
END, FOR, NEXT, DATA, INPUT, INPUT#, DIM, READ, LET, GOTO, RUN, IF, RESTORE, GOSUB, RETURN, REM, STOP, ON, WAIT, LOAD, SAVE, VERIFY, DEF, POKE, PRINT#, PRINT, CONT, LIST, CLR, CMD, SYS, OPEN, CLOSE, GET, NEW, TAB(, TO, FN, SPC(, THEN, NOT, STEP, AND, OR, >, =, <, SGN, INT, ABS, USR, FRE, POS, SQR, RND, LOG, EXP, COS, SIN, TAN, ATN, PEEK, LEN, STR$, VAL, ASC, CHR$, LEFT$, RIGHT$, MID$, GO
|
|
317
|
+
|
|
318
|
+
## Notes and Dialect Differences
|
|
319
|
+
- ELSE is not part of v2; no WHILE/REPEAT; no LOCAL variables.
|
|
320
|
+
- Disk commands via PRINT# to command channel (15) on device 8, e.g., PRINT#15,"S0:NAME".
|
|
321
|
+
- Some editors accept '?' as PRINT abbreviation; stored as PRINT token.
|
|
322
|
+
`;
|
|
323
|
+
export function getBasicV2Spec() {
|
|
324
|
+
return basic_spec;
|
|
325
|
+
}
|
|
326
|
+
export function searchBasicV2Spec(query) {
|
|
327
|
+
return searchMarkdownSections(basic_spec, query);
|
|
328
|
+
}
|
|
329
|
+
const ASM_GUIDE_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../data/assembly/assembly-spec.md");
|
|
330
|
+
let cachedAsmReference = null;
|
|
331
|
+
export function getAsmQuickReference() {
|
|
332
|
+
if (cachedAsmReference)
|
|
333
|
+
return cachedAsmReference;
|
|
334
|
+
try {
|
|
335
|
+
cachedAsmReference = readFileSync(ASM_GUIDE_PATH, "utf8");
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
cachedAsmReference = "# 6502 Assembly Quick Reference\n\nReference file not found.";
|
|
339
|
+
}
|
|
340
|
+
return cachedAsmReference;
|
|
341
|
+
}
|
|
342
|
+
export function searchAsmQuickReference(query) {
|
|
343
|
+
const guide = getAsmQuickReference();
|
|
344
|
+
return searchMarkdownSections(guide, query);
|
|
345
|
+
}
|
|
346
|
+
// --- VIC-II Knowledge Base (Concise but practical for graphics/sprites) ---
|
|
347
|
+
export const vic_spec = `
|
|
348
|
+
# VIC-II Technical Knowledge Base (Concise, PAL/NTSC, timing-driven)
|
|
349
|
+
|
|
350
|
+
## Overview
|
|
351
|
+
The VIC-II (MOS 6567 NTSC / 6569 PAL) is the video interface in the Commodore 64. It handles text/bitmap graphics, sprites, colour, fine scrolling, raster IRQs, and DRAM refresh. All effects are raster and cycle-timed.
|
|
352
|
+
|
|
353
|
+
## Video Standards
|
|
354
|
+
- PAL 6569: ~312 raster lines, ~63 cycles/line, 50.12 Hz frame rate, CPU ~0.985 MHz.
|
|
355
|
+
- NTSC 6567: ~262 raster lines, ~65 cycles/line, 59.83 Hz frame rate, CPU ~1.023 MHz.
|
|
356
|
+
- Note: Exact values vary slightly by chip revision and board. The CPU clock is derived from the VIC, hence different between PAL and NTSC.
|
|
357
|
+
|
|
358
|
+
## Text and Bitmap Modes (Built-in)
|
|
359
|
+
| Mode | Resolution | Cell granularity | Colours per cell | Notes |
|
|
360
|
+
|------|------------|------------------|------------------|-------|
|
|
361
|
+
| Text (Hi-res) | 40×25 characters (320×200 px) | 8×8 | 1 fg + bg | Default. Foreground per-character via Colour RAM; BG via $D021. |
|
|
362
|
+
| Multicolour Text | 40×25 (effective 160×200 px) | 4×8 | Up to 3 + BG | Double-wide pixels horizontally; shares $D021–$D023 colours. |
|
|
363
|
+
| Extended Background (ECM) | 40×25 | 8×8 | 4 BG | Top 2 bits of char code select BG from $D021–$D024. |
|
|
364
|
+
| Bitmap Hi-Res | 320×200 | 8×8 | 2 per block | BMM=1, MCM=0. Colours per 8×8 from screen RAM nybbles. |
|
|
365
|
+
| Bitmap Multicolour | 160×200 | 4×8 | 3 + BG | BMM=1, MCM=1. Colours per 4×8 from screen RAM nybbles. |
|
|
366
|
+
|
|
367
|
+
## Special “Research” Modes (Demo-scene techniques)
|
|
368
|
+
These are not official hardware modes; they exploit timing and badline behaviour to increase colour detail:
|
|
369
|
+
- FLI (Flexible Line Interpreter): Forces a badline every raster line (by changing $D011 Y-scroll each line) so that colour nybbles are reloaded per line, yielding more colours per character row in bitmap or char modes. Costs: very heavy DMA (badline each line) and tight cycle budgets.
|
|
370
|
+
- AFLI/IFLI (Advanced/Interlaced FLI): Variants combining hi-res and multicolour assets and/or alternating frames to increase apparent colour count at the cost of flicker and extreme timing.
|
|
371
|
+
- NUFLI, SHIFLI and derivatives: Advanced compressors of colour clashes using sprite overlays plus FLI. Require multiplexing and per-line colour changes.
|
|
372
|
+
These modes demand stable raster IRQs, avoiding writes during critical cycles (especially on badlines) and often sprite multiplexing.
|
|
373
|
+
|
|
374
|
+
## Sprite System
|
|
375
|
+
- 8 hardware sprites (0–7), nominal 24×21 px; multicolour halves horizontal resolution to 12×21 with 3 + transparent colours.
|
|
376
|
+
- Control: position $D000–$D00F (+$D010 MSBs), enable $D015, multicolour $D01C, behind/priority $D01B, expand X $D01D, expand Y $D017.
|
|
377
|
+
- Colour: shared $D025/$D026; per-sprite colours $D027–$D02E.
|
|
378
|
+
- Data: 63 bytes/sprite (3 bytes/row × 21 rows). Sprite pointers at $07F8–$07FF hold (address/64).
|
|
379
|
+
- Limit: max 8 sprites on any raster line (hardware). Exceed width with multiplexing via raster IRQ repositioning.
|
|
380
|
+
|
|
381
|
+
## Raster and Timing Essentials
|
|
382
|
+
- Raster register: $D012 (low 8 bits), high bit in $D011 bit 7 is NOT the high raster bit; note: $D011 bit 7=screen on/off; high raster bit is bit 7 of $D011? On C64: bit 7 of $D011 is actually the 9th raster bit when read via $D011; the same register contains vertical scroll (lower 3 bits), ECM and BMM bits.
|
|
383
|
+
- Badlines: Occur when (screen enabled) AND (rasterY between first and last text row) AND ((rasterY & 7) == (Y-scroll bits in $D011)). On a badline the VIC fetches 40 character pointers, stealing about 40 cycles, leaving ~23 CPU cycles on PAL. There are 25 potential badlines for the 25 text rows per frame (when screen on).
|
|
384
|
+
- Sprite DMA: Each visible sprite steals ~3 cycles per raster line while fetching its graphics. Fetch windows are fixed per sprite index; budget carefully on heavy lines. These steals are in addition to badline character fetches.
|
|
385
|
+
- Per-line CPU cycles (approximate):
|
|
386
|
+
- PAL non-badline: ~63 − 3×(active sprites on that line)
|
|
387
|
+
- PAL badline: ~23 − 3×(active sprites not covered by the 40-char fetch window)
|
|
388
|
+
- NTSC has ~65 cycles per line; proportions similar. Exact overlap depends on chip revision; measure on target hardware if cycle-exact code is required.
|
|
389
|
+
|
|
390
|
+
## Opening Borders (precise effect windows)
|
|
391
|
+
Border visibility depends on the scroll bits and internal fetch state. The classic windows are:
|
|
392
|
+
- Top/Bottom border: Toggle $D011 bit 3 (denoted RSEL/Y-Enable) around the transition into/out of the main display to trick the VIC into considering display active. Common practice is to change $D011 as the raster passes the last visible row (e.g., lines $F8–$FB on PAL) with stable-cycle IRQs.
|
|
393
|
+
- Side borders: Write to $D016 (horizontal scroll) within a narrow cycle window early in a raster line of the visible area (roughly cycles 24–26 on PAL for the canonical trick) to prevent the side border from opening. Maintain matching scroll values per line to keep it open. This must be timed each line you want the side border suppressed.
|
|
394
|
+
Notes: Exact cycle numbers vary slightly (PAL vs NTSC and chip revisions). Always establish a stable raster using a delay-tuned IRQ prologue and verify on real hardware.
|
|
395
|
+
|
|
396
|
+
## PAL/NTSC Geometry
|
|
397
|
+
- Text columns/rows: 40×25 in both PAL and NTSC when borders are normal.
|
|
398
|
+
- Visible bitmap area: 320×200 (hi-res) or 160×200 (multicolour). The full frame has additional border scanlines and cycles that are not visible unless border tricks are used.
|
|
399
|
+
- Frame/refresh rates: PAL ~50.12 Hz; NTSC ~59.83 Hz. Colour subcarrier and pixel clocks differ, affecting exact cycle lengths.
|
|
400
|
+
|
|
401
|
+
## Useful Registers (subset)
|
|
402
|
+
$D011: CTRL1 (bit7=High raster bit when reading; writing: bit7 Screen on/off), bit6 ECM, bit5 BMM, bit4 (unused), bits 0–2=Y scroll.
|
|
403
|
+
$D016: CTRL2 (bit4 MCM, bits 0–2 X scroll).
|
|
404
|
+
$D015: Sprite enable, $D01C: Sprite multicolour, $D01D: Sprite X expand, $D017: Sprite Y expand.
|
|
405
|
+
$D020/$D021: Border/background, $D022–$D024: shared multicolours, $D025/$D026: sprite shared colours.
|
|
406
|
+
$07F8–$07FF: Sprite data pointers (address/64).
|
|
407
|
+
|
|
408
|
+
## Best Practices for Raster Effects
|
|
409
|
+
- Do not write to $D011/$D016 during badlines unless you deliberately trigger FLI behaviour.
|
|
410
|
+
- Align IRQs to known cycle counts; use a stable raster entry sequence (acknowledge $D019, delay with a tuned instruction sequence, then effect writes).
|
|
411
|
+
- Budget for sprite DMA on lines with many sprites; schedule heavy updates on non-badlines.
|
|
412
|
+
- Disable screen ($D011 bit7=0) temporarily to suppress badlines when maximum CPU time is needed.
|
|
413
|
+
|
|
414
|
+
## References
|
|
415
|
+
- Zimmers.net: VIC-II register and memory maps
|
|
416
|
+
- C64 Programmer’s Reference Guide (graphics and raster chapters)
|
|
417
|
+
- Codebase64: VIC-II timing tables, badline definitions, border opening write windows, and FLI/IFLI techniques
|
|
418
|
+
`;
|
|
419
|
+
export function getVicIISpec() {
|
|
420
|
+
return vic_spec;
|
|
421
|
+
}
|
|
422
|
+
export function searchVicIISpec(query) {
|
|
423
|
+
return searchMarkdownSections(vic_spec, query);
|
|
424
|
+
}
|
|
425
|
+
function searchMarkdownSections(content, query) {
|
|
426
|
+
if (!query || typeof query !== "string") {
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
const normalized = query.trim().toLowerCase();
|
|
430
|
+
const sections = [];
|
|
431
|
+
const lines = content.split(/\r?\n/);
|
|
432
|
+
let currentHeading = "";
|
|
433
|
+
let currentBuffer = [];
|
|
434
|
+
const flush = () => {
|
|
435
|
+
if (!currentHeading)
|
|
436
|
+
return;
|
|
437
|
+
const content = currentBuffer.join("\n").trim();
|
|
438
|
+
sections.push({ heading: currentHeading, content });
|
|
439
|
+
};
|
|
440
|
+
for (const line of lines) {
|
|
441
|
+
const headingMatch = /^(##+)\s+(.*)$/.exec(line);
|
|
442
|
+
if (headingMatch) {
|
|
443
|
+
flush();
|
|
444
|
+
currentHeading = headingMatch[2].trim();
|
|
445
|
+
currentBuffer = [];
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
currentBuffer.push(line);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
flush();
|
|
452
|
+
const matches = sections.filter((s) => s.heading.toLowerCase().includes(normalized) || s.content.toLowerCase().includes(normalized));
|
|
453
|
+
return matches;
|
|
454
|
+
}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
const LEVEL_ORDER = {
|
|
3
|
+
debug: 0,
|
|
4
|
+
info: 1,
|
|
5
|
+
warn: 2,
|
|
6
|
+
error: 3,
|
|
7
|
+
};
|
|
8
|
+
const DEFAULT_LEVEL = "info";
|
|
9
|
+
const ACTIVE_LEVEL = normaliseLevel(process.env.LOG_LEVEL) ?? DEFAULT_LEVEL;
|
|
10
|
+
const ACTIVE_THRESHOLD = LEVEL_ORDER[ACTIVE_LEVEL];
|
|
11
|
+
const IS_TEST_ENV = process.env.NODE_ENV === "test";
|
|
12
|
+
const LOGGER_CACHE = new Map();
|
|
13
|
+
export function loggerFor(prefix) {
|
|
14
|
+
if (LOGGER_CACHE.has(prefix)) {
|
|
15
|
+
return LOGGER_CACHE.get(prefix);
|
|
16
|
+
}
|
|
17
|
+
const prefixed = createPrefixedLogger(prefix);
|
|
18
|
+
LOGGER_CACHE.set(prefix, prefixed);
|
|
19
|
+
return prefixed;
|
|
20
|
+
}
|
|
21
|
+
function createPrefixedLogger(prefix) {
|
|
22
|
+
const render = (level, message, details) => {
|
|
23
|
+
if (shouldSkip(level)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const consoleMethod = selectConsole(level);
|
|
27
|
+
const label = `[${prefix}] ${message}`;
|
|
28
|
+
if (details && Object.keys(details).length > 0) {
|
|
29
|
+
consoleMethod(label, details);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
consoleMethod(label);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
prefix,
|
|
37
|
+
debug(message, details) {
|
|
38
|
+
render("debug", message, details);
|
|
39
|
+
},
|
|
40
|
+
info(message, details) {
|
|
41
|
+
render("info", message, details);
|
|
42
|
+
},
|
|
43
|
+
warn(message, details) {
|
|
44
|
+
render("warn", message, details);
|
|
45
|
+
},
|
|
46
|
+
error(message, details) {
|
|
47
|
+
render("error", message, details);
|
|
48
|
+
},
|
|
49
|
+
isDebugEnabled() {
|
|
50
|
+
return !shouldSkip("debug");
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function shouldSkip(level) {
|
|
55
|
+
if (IS_TEST_ENV) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const order = LEVEL_ORDER[level];
|
|
59
|
+
return order < ACTIVE_THRESHOLD;
|
|
60
|
+
}
|
|
61
|
+
function selectConsole(level) {
|
|
62
|
+
if (level === "debug" && typeof console.debug === "function") {
|
|
63
|
+
return console.debug.bind(console);
|
|
64
|
+
}
|
|
65
|
+
if (level === "info" && typeof console.info === "function") {
|
|
66
|
+
return console.info.bind(console);
|
|
67
|
+
}
|
|
68
|
+
if (level === "warn" && typeof console.warn === "function") {
|
|
69
|
+
return console.warn.bind(console);
|
|
70
|
+
}
|
|
71
|
+
if (level === "error" && typeof console.error === "function") {
|
|
72
|
+
return console.error.bind(console);
|
|
73
|
+
}
|
|
74
|
+
return console.log.bind(console);
|
|
75
|
+
}
|
|
76
|
+
function normaliseLevel(raw) {
|
|
77
|
+
if (!raw)
|
|
78
|
+
return undefined;
|
|
79
|
+
const lowered = raw.trim().toLowerCase();
|
|
80
|
+
if (lowered === "debug" || lowered === "info" || lowered === "warn" || lowered === "error") {
|
|
81
|
+
return lowered;
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
export function payloadByteLength(payload) {
|
|
86
|
+
if (payload === null || payload === undefined) {
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
const buf = asBuffer(payload);
|
|
90
|
+
if (buf) {
|
|
91
|
+
return buf.byteLength;
|
|
92
|
+
}
|
|
93
|
+
if (typeof payload === "string") {
|
|
94
|
+
return Buffer.byteLength(payload, "utf8");
|
|
95
|
+
}
|
|
96
|
+
if (payload instanceof URLSearchParams) {
|
|
97
|
+
return Buffer.byteLength(payload.toString(), "utf8");
|
|
98
|
+
}
|
|
99
|
+
if (isPlainObject(payload) || Array.isArray(payload)) {
|
|
100
|
+
try {
|
|
101
|
+
return Buffer.byteLength(JSON.stringify(payload));
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return Buffer.byteLength(String(payload));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (typeof payload === "number" || typeof payload === "boolean" || typeof payload === "bigint") {
|
|
108
|
+
return Buffer.byteLength(String(payload));
|
|
109
|
+
}
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
export function formatPayloadForDebug(payload) {
|
|
113
|
+
if (payload === null || payload === undefined) {
|
|
114
|
+
return payload;
|
|
115
|
+
}
|
|
116
|
+
const buf = asBuffer(payload);
|
|
117
|
+
if (buf) {
|
|
118
|
+
return buf.toString("hex");
|
|
119
|
+
}
|
|
120
|
+
if (payload instanceof URLSearchParams) {
|
|
121
|
+
return payload.toString();
|
|
122
|
+
}
|
|
123
|
+
if (isFormData(payload)) {
|
|
124
|
+
const entries = {};
|
|
125
|
+
payload.forEach((value, key) => {
|
|
126
|
+
if (!entries[key])
|
|
127
|
+
entries[key] = [];
|
|
128
|
+
if (isBlob(value)) {
|
|
129
|
+
entries[key].push(`[Blob size=${value.size}]`);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
entries[key].push(value);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return entries;
|
|
136
|
+
}
|
|
137
|
+
if (typeof payload === "object") {
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(JSON.stringify(payload));
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return payload;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return payload;
|
|
146
|
+
}
|
|
147
|
+
function asBuffer(payload) {
|
|
148
|
+
if (Buffer.isBuffer(payload)) {
|
|
149
|
+
return payload;
|
|
150
|
+
}
|
|
151
|
+
if (payload instanceof ArrayBuffer) {
|
|
152
|
+
return Buffer.from(payload);
|
|
153
|
+
}
|
|
154
|
+
if (ArrayBuffer.isView(payload)) {
|
|
155
|
+
const arrayView = payload;
|
|
156
|
+
return Buffer.from(arrayView.buffer, arrayView.byteOffset, arrayView.byteLength);
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
function isPlainObject(value) {
|
|
161
|
+
return typeof value === "object" && value !== null && value.constructor === Object;
|
|
162
|
+
}
|
|
163
|
+
function isFormData(value) {
|
|
164
|
+
return typeof FormData !== "undefined" && value instanceof FormData;
|
|
165
|
+
}
|
|
166
|
+
function isBlob(value) {
|
|
167
|
+
const ctor = globalThis.Blob;
|
|
168
|
+
return typeof ctor === "function" && value instanceof ctor;
|
|
169
|
+
}
|
|
170
|
+
export function formatErrorMessage(error) {
|
|
171
|
+
if (error instanceof Error) {
|
|
172
|
+
const message = error.message || error.name || "Error";
|
|
173
|
+
return collapseWhitespace(message);
|
|
174
|
+
}
|
|
175
|
+
if (error === null || error === undefined) {
|
|
176
|
+
return "unknown error";
|
|
177
|
+
}
|
|
178
|
+
return collapseWhitespace(String(error));
|
|
179
|
+
}
|
|
180
|
+
function collapseWhitespace(input) {
|
|
181
|
+
return input.replace(/\s+/g, " ").trim();
|
|
182
|
+
}
|