godot-kit 1.0.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.
@@ -0,0 +1,23 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - uses: actions/setup-node@v4
15
+ with:
16
+ node-version: '20'
17
+ registry-url: 'https://registry.npmjs.org'
18
+
19
+ - run: npm ci --ignore-scripts
20
+
21
+ - run: npm publish --access public
22
+ env:
23
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # godot-kit
2
+
3
+ Agentic Godot 4.x development boilerplate. Single command setup, REPL/CLI debugging, gdtoolkit integration, DAP support, scene inspector, profiler, and live log streaming.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Create a new Godot project
9
+ npx godot-kit my-game
10
+
11
+ # Install gdtoolkit
12
+ godot-dev setup
13
+
14
+ # Launch Godot with remote debugger
15
+ godot-dev launch --godot /path/to/godot4
16
+
17
+ # Connect interactive REPL
18
+ godot-dev repl
19
+ ```
20
+
21
+ ## What it creates
22
+
23
+ ```
24
+ my-game/
25
+ ├── project.godot # Godot 4.x project config
26
+ ├── scenes/main.tscn # Main scene
27
+ ├── scripts/main.gd # Main script
28
+ ├── addons/repl_bridge/ # REPL autoload (GDScript)
29
+ │ ├── repl_bridge.gd # Debug bridge with eval, scene dump, logs
30
+ │ └── plugin.cfg
31
+ ├── .gdlintrc # gdtoolkit lint config
32
+ ├── .gdformatrc # gdtoolkit format config
33
+ ├── .vscode/
34
+ │ ├── launch.json # DAP debug config
35
+ │ ├── settings.json # Godot LSP settings
36
+ │ └── extensions.json # Recommends Godot Tools
37
+ ├── Makefile # Task shortcuts
38
+ └── README.md
39
+ ```
40
+
41
+ ## CLI Commands
42
+
43
+ ```bash
44
+ godot-dev repl # Interactive debugger REPL
45
+ godot-dev inspect # Dump scene tree (one-shot)
46
+ godot-dev logs # Stream Godot output logs
47
+ godot-dev lint [files] # Run gdlint
48
+ godot-dev format [files] # Run gdformat
49
+ godot-dev launch [scene] # Launch Godot with debugger
50
+ godot-dev setup # Install gdtoolkit
51
+ ```
52
+
53
+ ## REPL Commands
54
+
55
+ | Command | Description |
56
+ |---------|-------------|
57
+ | `tree` | Dump scene tree |
58
+ | `inspect <id>` | Inspect object properties |
59
+ | `stack` | Show call stack |
60
+ | `vars <frame>` | Show variables at stack frame |
61
+ | `c` / `continue` | Continue execution |
62
+ | `n` / `next` | Step over |
63
+ | `s` / `step` | Step into |
64
+ | `b` / `break` | Pause execution |
65
+ | `bp <file> <line>` | Set/clear breakpoint |
66
+ | `quit` | Exit |
67
+
68
+ ## Debug Architecture
69
+
70
+ ```
71
+ Godot 4 Game
72
+ └─ --remote-debug tcp://127.0.0.1:6007
73
+ └─ Binary Variant Protocol (TCP)
74
+ └─ godot-dev repl / inspect / logs
75
+ └─ lib/debugger-client.js
76
+ └─ lib/protocol.js (encode/decode Godot variants)
77
+ ```
78
+
79
+ ### Protocol Details
80
+
81
+ Godot 4.x remote debugger uses a binary TCP protocol:
82
+ - Each packet: `uint32_LE(payload_size) + uint32_LE(param_count) + [variants...]`
83
+ - First variant is always the command string
84
+ - Remaining variants are parameters
85
+ - Types: NIL, BOOL, INT (32/64), FLOAT (32/64), STRING, VECTOR2/3/4, COLOR, ARRAY, DICTIONARY, OBJECT, etc.
86
+
87
+ ### Ports
88
+
89
+ | Service | Port | Protocol |
90
+ |---------|------|----------|
91
+ | Remote Debugger (game) | 6007 | Binary TCP |
92
+ | Language Server (LSP) | 6005 | JSON-RPC TCP |
93
+ | Debug Adapter (DAP) | 6006 | DAP over TCP |
94
+
95
+ ## gdtoolkit
96
+
97
+ ```bash
98
+ # Install (requires Python 3.7+)
99
+ pip install "gdtoolkit==4.*"
100
+
101
+ # Or via pipx
102
+ pipx install "gdtoolkit==4.*"
103
+ ```
104
+
105
+ Provides `gdlint` and `gdformat` for GDScript linting and formatting.
106
+
107
+ ## VSCode Integration
108
+
109
+ 1. Install [Godot Tools](https://marketplace.visualstudio.com/items?itemName=geequlim.godot-tools)
110
+ 2. Set Godot 4 executable path in VSCode settings
111
+ 3. Press `F5` to launch with DAP debugger
112
+
113
+ ## Programmatic API
114
+
115
+ ```js
116
+ const { GodotDebuggerClient } = require('godot-kit');
117
+
118
+ const client = new GodotDebuggerClient('127.0.0.1', 6007);
119
+ await client.connect();
120
+
121
+ client.on('break', (params) => console.log('Paused at', params));
122
+ client.on('output', (params) => console.log('[godot]', params));
123
+
124
+ client.requestSceneTree();
125
+ client.on('message', (msg) => {
126
+ if (msg.command === 'scene:scene_tree_parse_end') {
127
+ // msg.params contains scene tree data
128
+ }
129
+ });
130
+ ```
131
+
132
+ ## License
133
+
134
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { Command } = require('commander');
5
+ const { GodotDebuggerClient } = require('../lib/debugger-client');
6
+ const { parseSceneNode, formatSceneTree } = require('../lib/scene-tree');
7
+ const { startRepl } = require('../lib/repl-commands');
8
+ const { execSync, spawn } = require('child_process');
9
+
10
+ const program = new Command();
11
+ program.name('godot-dev').description('Agentic Godot 4.x CLI - REPL, debugger, inspector, profiler').version('1.0.0');
12
+
13
+ function makeClient(opts) {
14
+ return new GodotDebuggerClient(opts.host || '127.0.0.1', parseInt(opts.port || '6007'));
15
+ }
16
+
17
+ async function connectOrDie(client) {
18
+ try {
19
+ await client.connect();
20
+ console.log(`Connected to Godot debugger at ${client.host}:${client.port}`);
21
+ } catch (e) {
22
+ console.error(`Cannot connect to Godot at ${client.host}:${client.port}`);
23
+ console.error('Launch Godot with: --remote-debug tcp://127.0.0.1:6007');
24
+ console.error('Or run: godot-dev launch');
25
+ process.exit(1);
26
+ }
27
+ }
28
+
29
+ program.command('repl').description('Interactive REPL connected to running Godot debugger')
30
+ .option('-h, --host <host>', 'Godot host', '127.0.0.1')
31
+ .option('-p, --port <port>', 'Debugger port', '6007')
32
+ .action(async (opts) => {
33
+ const client = makeClient(opts);
34
+ await connectOrDie(client);
35
+ startRepl(client);
36
+ });
37
+
38
+ program.command('inspect').description('Dump scene tree from running Godot game (one-shot)')
39
+ .option('-h, --host <host>', 'Godot host', '127.0.0.1')
40
+ .option('-p, --port <port>', 'Debugger port', '6007')
41
+ .action(async (opts) => {
42
+ const client = makeClient(opts);
43
+ await connectOrDie(client);
44
+ client.requestSceneTree();
45
+ let done = false;
46
+ client.on('message', (msg) => {
47
+ if (String(msg.command) === 'scene:scene_tree_parse_end' && !done) {
48
+ done = true;
49
+ try { formatSceneTree(parseSceneNode(msg.params)).forEach(l => console.log(l)); }
50
+ catch (e) { console.log(JSON.stringify(msg.params, null, 2)); }
51
+ client.disconnect(); process.exit(0);
52
+ }
53
+ });
54
+ setTimeout(() => { if (!done) { console.error('Timeout: no scene tree received.'); client.disconnect(); process.exit(1); } }, 5000);
55
+ });
56
+
57
+ program.command('logs').description('Stream Godot output logs')
58
+ .option('-h, --host <host>', 'Godot host', '127.0.0.1')
59
+ .option('-p, --port <port>', 'Debugger port', '6007')
60
+ .action(async (opts) => {
61
+ const client = makeClient(opts);
62
+ await connectOrDie(client);
63
+ console.log('Streaming Godot logs (Ctrl+C to stop)...');
64
+ client.on('output', (p) => { for (const s of p) { if (typeof s === 'string') process.stdout.write(s + '\n'); } });
65
+ client.on('godot_error', (p) => process.stderr.write('[ERROR] ' + p.join(' ') + '\n'));
66
+ client.on('profile_frame', (p) => console.log('[PROFILE]', JSON.stringify(p)));
67
+ client.on('disconnected', () => { console.log('Godot disconnected.'); process.exit(0); });
68
+ process.on('SIGINT', () => { client.disconnect(); process.exit(0); });
69
+ });
70
+
71
+ program.command('lint [files...]').description('Lint GDScript files using gdtoolkit')
72
+ .action((files) => {
73
+ const targets = files.length ? files : ['.'];
74
+ try {
75
+ const r = execSync(['gdlint', ...targets].join(' '), { stdio: 'pipe', encoding: 'utf8' });
76
+ if (r) console.log(r);
77
+ console.log('\x1b[32mLint passed.\x1b[0m');
78
+ } catch (e) {
79
+ if (e.stdout) process.stdout.write(e.stdout);
80
+ if (e.stderr) process.stderr.write(e.stderr);
81
+ process.exit(e.status || 1);
82
+ }
83
+ });
84
+
85
+ program.command('format [files...]').description('Format GDScript files using gdtoolkit')
86
+ .option('--check', 'Check only, do not write')
87
+ .action((files, opts) => {
88
+ const targets = files.length ? files : ['.'];
89
+ const args = ['gdformat', ...(opts.check ? ['--check'] : []), ...targets];
90
+ try {
91
+ const r = execSync(args.join(' '), { stdio: 'pipe', encoding: 'utf8' });
92
+ if (r) console.log(r);
93
+ console.log('\x1b[32mFormat complete.\x1b[0m');
94
+ } catch (e) {
95
+ if (e.stdout) process.stdout.write(e.stdout);
96
+ if (e.stderr) process.stderr.write(e.stderr);
97
+ process.exit(e.status || 1);
98
+ }
99
+ });
100
+
101
+ program.command('launch [scene]').description('Launch Godot with remote debugger enabled')
102
+ .option('--godot <path>', 'Path to Godot executable', 'godot')
103
+ .option('--project <path>', 'Godot project path', '.')
104
+ .option('-p, --port <port>', 'Debugger port', '6007')
105
+ .option('--profiling', 'Enable profiling')
106
+ .option('--debug-collisions', 'Show collision shapes')
107
+ .option('--debug-navigation', 'Show navigation')
108
+ .action((scene, opts) => {
109
+ const args = ['--path', opts.project, '--remote-debug', `tcp://127.0.0.1:${opts.port}`, '--verbose'];
110
+ if (opts.profiling) args.push('--profiling');
111
+ if (opts.debugCollisions) args.push('--debug-collisions');
112
+ if (opts.debugNavigation) args.push('--debug-navigation');
113
+ if (scene) args.push(scene);
114
+ console.log(`Launching: ${opts.godot} ${args.join(' ')}`);
115
+ const proc = spawn(opts.godot, args, { stdio: 'inherit' });
116
+ proc.on('exit', (code) => process.exit(code || 0));
117
+ });
118
+
119
+ program.command('setup').description('Install gdtoolkit for GDScript linting/formatting')
120
+ .action(() => {
121
+ console.log('Installing gdtoolkit for Godot 4.x...');
122
+ const cmds = ['pip install --upgrade "gdtoolkit==4.*"', 'pip3 install --upgrade "gdtoolkit==4.*"'];
123
+ for (const cmd of cmds) {
124
+ try { execSync(cmd, { stdio: 'inherit' }); console.log('\x1b[32mgdtoolkit installed.\x1b[0m'); return; }
125
+ catch (e) { continue; }
126
+ }
127
+ console.error('Failed. Install Python and pip first.'); process.exit(1);
128
+ });
129
+
130
+ program.parse(process.argv);
package/bin/create.js ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const getTemplates = require('../lib/templates');
7
+
8
+ const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : (process.env.INIT_CWD || process.cwd());
9
+ const projectName = path.basename(targetDir);
10
+
11
+ console.log(`\n godot-kit - Agentic Godot 4.x Boilerplate\n`);
12
+ console.log(` Project: ${projectName}`);
13
+ console.log(` Target: ${targetDir}\n`);
14
+
15
+ fs.mkdirSync(targetDir, { recursive: true });
16
+
17
+ const templates = getTemplates(projectName);
18
+ for (const [relPath, content] of Object.entries(templates)) {
19
+ const full = path.join(targetDir, relPath);
20
+ fs.mkdirSync(path.dirname(full), { recursive: true });
21
+ fs.writeFileSync(full, content, 'utf8');
22
+ console.log(` + ${relPath}`);
23
+ }
24
+
25
+ const readme = `# ${projectName}
26
+
27
+ Scaffolded by [godot-kit](https://github.com/AnEntrypoint/godot-kit).
28
+
29
+ ## Setup
30
+ \`\`\`bash
31
+ npm install -g godot-kit
32
+ godot-dev setup
33
+ \`\`\`
34
+
35
+ ## Commands
36
+ \`\`\`bash
37
+ godot-dev launch --godot /path/to/godot4 # Launch with debugger
38
+ godot-dev repl # Interactive REPL
39
+ godot-dev inspect # Dump scene tree
40
+ godot-dev logs # Stream logs
41
+ godot-dev lint # GDScript lint
42
+ godot-dev format # GDScript format
43
+ \`\`\`
44
+
45
+ ## REPL Commands
46
+ \`continue|c\`, \`next|n\`, \`step|s\`, \`break|b\`, \`stack\`, \`vars <frame>\`, \`tree\`, \`inspect <id>\`, \`bp <file> <line>\`
47
+
48
+ ## Debug Ports
49
+ | Service | Port |
50
+ |---------|------|
51
+ | Remote Debugger | 6007 |
52
+ | LSP | 6005 |
53
+ | DAP | 6006 |
54
+ `;
55
+
56
+ fs.writeFileSync(path.join(targetDir, 'README.md'), readme, 'utf8');
57
+ console.log(` + README.md`);
58
+
59
+ console.log(`
60
+ Done! Next steps:
61
+
62
+ 1. Open project in Godot 4.x editor
63
+ 2. Install gdtoolkit: godot-dev setup
64
+ 3. Launch with debugger: godot-dev launch --godot /path/to/godot4
65
+ 4. Connect REPL: godot-dev repl
66
+ 5. Inspect scene tree: godot-dev inspect
67
+
68
+ VSCode: Install "Godot Tools" extension, use F5 to debug.
69
+ Docs: https://github.com/AnEntrypoint/godot-kit
70
+ `);
@@ -0,0 +1,96 @@
1
+ 'use strict';
2
+
3
+ const net = require('net');
4
+ const { EventEmitter } = require('events');
5
+ const { buildPacket, parsePacket, splitPackets } = require('./protocol');
6
+
7
+ class GodotDebuggerClient extends EventEmitter {
8
+ constructor(host = '127.0.0.1', port = 6007) {
9
+ super();
10
+ this.host = host;
11
+ this.port = port;
12
+ this.socket = null;
13
+ this.buffer = Buffer.alloc(0);
14
+ this.connected = false;
15
+ }
16
+
17
+ connect() {
18
+ return new Promise((resolve, reject) => {
19
+ this.socket = new net.Socket();
20
+ this.socket.connect(this.port, this.host, () => {
21
+ this.connected = true;
22
+ this.emit('connected');
23
+ resolve();
24
+ });
25
+ this.socket.on('data', (chunk) => {
26
+ this.buffer = Buffer.concat([this.buffer, chunk]);
27
+ this._processBuffer();
28
+ });
29
+ this.socket.on('close', () => {
30
+ this.connected = false;
31
+ this.emit('disconnected');
32
+ });
33
+ this.socket.on('error', (err) => {
34
+ this.connected = false;
35
+ if (!this.connected) reject(err);
36
+ else this.emit('error', err);
37
+ });
38
+ });
39
+ }
40
+
41
+ _processBuffer() {
42
+ const packets = splitPackets(this.buffer);
43
+ let consumed = 0;
44
+ for (const pkt of packets) {
45
+ const msg = parsePacket(pkt);
46
+ if (msg) {
47
+ this.emit('message', msg);
48
+ this._handleMessage(msg);
49
+ consumed += pkt.length;
50
+ }
51
+ }
52
+ if (consumed > 0) this.buffer = this.buffer.subarray(consumed);
53
+ }
54
+
55
+ _handleMessage(msg) {
56
+ const cmd = String(msg.command);
57
+ if (cmd === 'debug_enter') this.emit('break', msg.params);
58
+ else if (cmd === 'debug_exit') this.emit('continue', msg.params);
59
+ else if (cmd === 'stack_dump') this.emit('stack_dump', msg.params);
60
+ else if (cmd === 'stack_frame_vars') this.emit('stack_frame_vars', msg.params);
61
+ else if (cmd === 'scene:scene_tree_parse_begin') this.emit('scene_tree_begin', msg.params);
62
+ else if (cmd === 'scene:scene_tree_parse_end') this.emit('scene_tree_end', msg.params);
63
+ else if (cmd === 'scene:inspect_object') this.emit('inspect_object', msg.params);
64
+ else if (cmd === 'output') this.emit('output', msg.params);
65
+ else if (cmd === 'performance:profile_frame') this.emit('profile_frame', msg.params);
66
+ else if (cmd === 'error') this.emit('godot_error', msg.params);
67
+ else this.emit('unknown', msg);
68
+ }
69
+
70
+ send(cmd, params = []) {
71
+ if (!this.connected || !this.socket) return false;
72
+ const buf = buildPacket(cmd, params);
73
+ this.socket.write(buf);
74
+ return true;
75
+ }
76
+
77
+ requestSceneTree() { return this.send('scene:request_scene_tree'); }
78
+ requestInspectObject(objectId) { return this.send('scene:inspect_object', [BigInt(objectId)]); }
79
+ requestStackDump() { return this.send('get_stack_dump'); }
80
+ requestStackFrameVars(frameId) { return this.send('get_stack_frame_vars', [frameId]); }
81
+ sendBreak() { return this.send('break'); }
82
+ sendContinue() { return this.send('continue'); }
83
+ sendNext() { return this.send('next'); }
84
+ sendStep() { return this.send('step'); }
85
+
86
+ setBreakpoint(filePath, line, enabled = true) {
87
+ return this.send('breakpoint', [filePath, line, enabled]);
88
+ }
89
+
90
+ disconnect() {
91
+ if (this.socket) { this.socket.destroy(); this.socket = null; }
92
+ this.connected = false;
93
+ }
94
+ }
95
+
96
+ module.exports = { GodotDebuggerClient };
package/lib/index.js ADDED
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ const { GodotDebuggerClient } = require('./debugger-client');
4
+ const { parseSceneNode, formatSceneTree } = require('./scene-tree');
5
+ const { buildPacket, parsePacket, splitPackets } = require('./protocol');
6
+
7
+ module.exports = { GodotDebuggerClient, parseSceneNode, formatSceneTree, buildPacket, parsePacket, splitPackets };
@@ -0,0 +1,184 @@
1
+ 'use strict';
2
+
3
+ const TYPES = {
4
+ NIL: 0, BOOL: 1, INT: 2, FLOAT: 3, STRING: 4,
5
+ VECTOR2: 5, VECTOR2I: 6, RECT2: 7, RECT2I: 8,
6
+ VECTOR3: 9, VECTOR3I: 10, TRANSFORM2D: 11,
7
+ VECTOR4: 12, VECTOR4I: 13, PLANE: 14,
8
+ QUATERNION: 15, AABB: 16, BASIS: 17,
9
+ TRANSFORM3D: 18, PROJECTION: 19,
10
+ COLOR: 20, STRING_NAME: 21, NODE_PATH: 22,
11
+ RID: 23, OBJECT: 24, CALLABLE: 25, SIGNAL: 26,
12
+ DICTIONARY: 27, ARRAY: 28,
13
+ PACKED_BYTE_ARRAY: 29, PACKED_INT32_ARRAY: 30,
14
+ PACKED_INT64_ARRAY: 31, PACKED_FLOAT32_ARRAY: 32,
15
+ PACKED_FLOAT64_ARRAY: 33, PACKED_STRING_ARRAY: 34,
16
+ PACKED_VECTOR2_ARRAY: 35, PACKED_VECTOR3_ARRAY: 36,
17
+ PACKED_COLOR_ARRAY: 37, PACKED_VECTOR4_ARRAY: 38,
18
+ };
19
+
20
+ const ENCODE_FLAG_64 = 1 << 16;
21
+
22
+ function encodeString(s) {
23
+ const strBuf = Buffer.from(s, 'utf8');
24
+ const pad = (4 - (strBuf.length % 4)) % 4;
25
+ const b = Buffer.alloc(4 + strBuf.length + pad);
26
+ b.writeUInt32LE(strBuf.length, 0);
27
+ strBuf.copy(b, 4);
28
+ return b;
29
+ }
30
+
31
+ function encodeVariant(value) {
32
+ if (value === null || value === undefined) {
33
+ return Buffer.from([0, 0, 0, 0]);
34
+ }
35
+ if (typeof value === 'boolean') {
36
+ const b = Buffer.alloc(8);
37
+ b.writeUInt32LE(TYPES.BOOL, 0);
38
+ b.writeUInt32LE(value ? 1 : 0, 4);
39
+ return b;
40
+ }
41
+ if (typeof value === 'bigint') {
42
+ const b = Buffer.alloc(12);
43
+ b.writeUInt32LE(TYPES.INT | ENCODE_FLAG_64, 0);
44
+ b.writeBigInt64LE(value, 4);
45
+ return b;
46
+ }
47
+ if (typeof value === 'number') {
48
+ if (Number.isInteger(value)) {
49
+ const b = Buffer.alloc(8);
50
+ b.writeUInt32LE(TYPES.INT, 0);
51
+ b.writeInt32LE(value, 4);
52
+ return b;
53
+ }
54
+ const b = Buffer.alloc(8);
55
+ b.writeUInt32LE(TYPES.FLOAT, 0);
56
+ b.writeFloatLE(value, 4);
57
+ return b;
58
+ }
59
+ if (typeof value === 'string') {
60
+ const sb = encodeString(value);
61
+ const b = Buffer.alloc(4 + sb.length);
62
+ b.writeUInt32LE(TYPES.STRING, 0);
63
+ sb.copy(b, 4);
64
+ return b;
65
+ }
66
+ if (Array.isArray(value)) {
67
+ const parts = value.map(encodeVariant);
68
+ const dataLen = 4 + parts.reduce((a, p) => a + p.length, 0);
69
+ const b = Buffer.alloc(4 + dataLen);
70
+ b.writeUInt32LE(TYPES.ARRAY, 0);
71
+ b.writeUInt32LE(value.length, 4);
72
+ let off = 8;
73
+ for (const p of parts) { p.copy(b, off); off += p.length; }
74
+ return b;
75
+ }
76
+ return Buffer.from([0, 0, 0, 0]);
77
+ }
78
+
79
+ function buildPacket(cmd, params = []) {
80
+ const cmdVar = encodeVariant(cmd);
81
+ const paramVars = params.map(encodeVariant);
82
+ const payloadSize = 4 + cmdVar.length + paramVars.reduce((a, p) => a + p.length, 0);
83
+ const buf = Buffer.alloc(4 + payloadSize);
84
+ buf.writeUInt32LE(payloadSize, 0);
85
+ buf.writeUInt32LE(1 + params.length, 4);
86
+ cmdVar.copy(buf, 8);
87
+ let off = 8 + cmdVar.length;
88
+ for (const p of paramVars) { p.copy(buf, off); off += p.length; }
89
+ return buf;
90
+ }
91
+
92
+ function decodeString(buf, offset) {
93
+ const len = buf.readUInt32LE(offset);
94
+ const str = buf.toString('utf8', offset + 4, offset + 4 + len);
95
+ const pad = (4 - (len % 4)) % 4;
96
+ return { value: str, size: 4 + len + pad };
97
+ }
98
+
99
+ function decodeVariant(buf, offset) {
100
+ if (offset + 4 > buf.length) return { value: null, size: 4 };
101
+ const typeRaw = buf.readUInt32LE(offset);
102
+ const type = typeRaw & 0xff;
103
+ const flag64 = !!(typeRaw & ENCODE_FLAG_64);
104
+ offset += 4;
105
+
106
+ switch (type) {
107
+ case TYPES.NIL: return { value: null, size: 4 };
108
+ case TYPES.BOOL: return { value: buf.readUInt32LE(offset) !== 0, size: 8 };
109
+ case TYPES.INT:
110
+ if (flag64) return { value: buf.readBigInt64LE(offset), size: 12 };
111
+ return { value: buf.readInt32LE(offset), size: 8 };
112
+ case TYPES.FLOAT:
113
+ if (flag64) return { value: buf.readDoubleLe ? buf.readDoubleLe(offset) : buf.readDoubleLE(offset), size: 12 };
114
+ return { value: buf.readFloatLE(offset), size: 8 };
115
+ case TYPES.STRING:
116
+ case TYPES.STRING_NAME: {
117
+ const r = decodeString(buf, offset);
118
+ return { value: r.value, size: 4 + r.size };
119
+ }
120
+ case TYPES.NODE_PATH: {
121
+ const r = decodeString(buf, offset);
122
+ return { value: r.value, size: 4 + r.size };
123
+ }
124
+ case TYPES.VECTOR2: return { value: { x: buf.readFloatLE(offset), y: buf.readFloatLE(offset+4) }, size: 4+8 };
125
+ case TYPES.VECTOR3: return { value: { x: buf.readFloatLE(offset), y: buf.readFloatLE(offset+4), z: buf.readFloatLE(offset+8) }, size: 4+12 };
126
+ case TYPES.COLOR: return { value: { r: buf.readFloatLE(offset), g: buf.readFloatLE(offset+4), b: buf.readFloatLE(offset+8), a: buf.readFloatLE(offset+12) }, size: 4+16 };
127
+ case TYPES.OBJECT:
128
+ if (typeRaw & ENCODE_FLAG_64) return { value: { objectId: buf.readBigUInt64LE(offset) }, size: 4+8 };
129
+ return { value: { objectId: buf.readUInt32LE(offset) }, size: 4+4 };
130
+ case TYPES.ARRAY: {
131
+ const count = buf.readUInt32LE(offset);
132
+ let off = offset + 4;
133
+ const items = [];
134
+ for (let i = 0; i < count; i++) {
135
+ const r = decodeVariant(buf, off);
136
+ items.push(r.value);
137
+ off += r.size;
138
+ }
139
+ return { value: items, size: 4 + (off - offset - 4) };
140
+ }
141
+ case TYPES.DICTIONARY: {
142
+ const count = buf.readUInt32LE(offset) & 0x7fffffff;
143
+ let off = offset + 4;
144
+ const dict = {};
145
+ for (let i = 0; i < count; i++) {
146
+ const kr = decodeVariant(buf, off); off += kr.size;
147
+ const vr = decodeVariant(buf, off); off += vr.size;
148
+ dict[String(kr.value)] = vr.value;
149
+ }
150
+ return { value: dict, size: 4 + (off - offset - 4) };
151
+ }
152
+ default: return { value: `<type:${type}>`, size: 4 };
153
+ }
154
+ }
155
+
156
+ function parsePacket(buf) {
157
+ if (buf.length < 8) return null;
158
+ const payloadSize = buf.readUInt32LE(0);
159
+ if (buf.length < 4 + payloadSize) return null;
160
+ const paramCount = buf.readUInt32LE(4);
161
+ const params = [];
162
+ let off = 8;
163
+ for (let i = 0; i < paramCount; i++) {
164
+ const r = decodeVariant(buf, off);
165
+ params.push(r.value);
166
+ off += r.size;
167
+ }
168
+ return { command: params[0], params: params.slice(1), totalSize: 4 + payloadSize };
169
+ }
170
+
171
+ function splitPackets(buf) {
172
+ const packets = [];
173
+ let off = 0;
174
+ while (off + 4 <= buf.length) {
175
+ const payloadSize = buf.readUInt32LE(off);
176
+ const total = 4 + payloadSize;
177
+ if (off + total > buf.length) break;
178
+ packets.push(buf.subarray(off, off + total));
179
+ off += total;
180
+ }
181
+ return packets;
182
+ }
183
+
184
+ module.exports = { buildPacket, parsePacket, splitPackets, TYPES };
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+
3
+ const readline = require('readline');
4
+ const { parseSceneNode, formatSceneTree } = require('./scene-tree');
5
+
6
+ function startRepl(client) {
7
+ client.on('output', (params) => {
8
+ for (const p of params) {
9
+ if (typeof p === 'string') process.stdout.write('\x1b[90m[godot] ' + p + '\x1b[0m\n');
10
+ }
11
+ });
12
+ client.on('break', (params) => {
13
+ console.log(`\x1b[33m[BREAK] ${params[1] || '?'}:${params[2] || 0}\x1b[0m`);
14
+ console.log('Commands: continue|c, next|n, step|s, stack, vars <frame>, tree, inspect <id>, bp <file> <line>');
15
+ });
16
+ client.on('stack_dump', (params) => {
17
+ console.log('\x1b[36m--- Stack Dump ---\x1b[0m');
18
+ for (let i = 0; i < params.length; i += 3) {
19
+ console.log(` Frame ${i / 3}: ${params[i + 1]}:${params[i + 2]} in ${params[i]}`);
20
+ }
21
+ });
22
+ client.on('godot_error', (params) => {
23
+ console.error('\x1b[31m[ERROR]', params.join(' '), '\x1b[0m');
24
+ });
25
+ client.on('disconnected', () => { console.log('\x1b[31mGodot disconnected.\x1b[0m'); process.exit(0); });
26
+ client.on('message', (msg) => handleMessage(msg, client));
27
+
28
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: '\x1b[32mgodot> \x1b[0m' });
29
+ rl.prompt();
30
+ rl.on('line', (line) => { dispatch(line.trim(), client, rl); rl.prompt(); });
31
+ rl.on('close', () => { client.disconnect(); process.exit(0); });
32
+ }
33
+
34
+ function handleMessage(msg, client) {
35
+ const cmd = String(msg.command);
36
+ if (cmd === 'scene:scene_tree_parse_end') {
37
+ try {
38
+ formatSceneTree(parseSceneNode(msg.params)).forEach(l => console.log(l));
39
+ } catch (e) {
40
+ console.log('[scene]', JSON.stringify(msg.params, null, 2));
41
+ }
42
+ }
43
+ if (cmd === 'scene:inspect_object') {
44
+ console.log('\x1b[36m--- Object Properties ---\x1b[0m');
45
+ const props = msg.params[2];
46
+ if (Array.isArray(props)) {
47
+ for (let i = 0; i < props.length; i += 5) {
48
+ console.log(` ${props[i]}: ${JSON.stringify(props[i + 4])}`);
49
+ }
50
+ }
51
+ }
52
+ if (cmd === 'stack_frame_vars') {
53
+ const count = msg.params[0];
54
+ console.log('\x1b[36m--- Variables ---\x1b[0m');
55
+ for (let i = 0; i < count; i++) {
56
+ console.log(` ${msg.params[1 + i * 3]} = ${JSON.stringify(msg.params[1 + i * 3 + 2])}`);
57
+ }
58
+ }
59
+ }
60
+
61
+ function dispatch(line, client, rl) {
62
+ const parts = line.split(/\s+/);
63
+ const cmd = parts[0];
64
+ if (!cmd) return;
65
+ const MAP = {
66
+ quit: () => { client.disconnect(); process.exit(0); },
67
+ exit: () => { client.disconnect(); process.exit(0); },
68
+ c: () => client.sendContinue(),
69
+ continue: () => client.sendContinue(),
70
+ n: () => client.sendNext(),
71
+ next: () => client.sendNext(),
72
+ s: () => client.sendStep(),
73
+ step: () => client.sendStep(),
74
+ b: () => client.sendBreak(),
75
+ break: () => client.sendBreak(),
76
+ stack: () => client.requestStackDump(),
77
+ vars: () => client.requestStackFrameVars(parseInt(parts[1] || '0')),
78
+ tree: () => client.requestSceneTree(),
79
+ inspect: () => client.requestInspectObject(parts[1] || '0'),
80
+ bp: () => client.setBreakpoint(parts[1], parseInt(parts[2]), parts[3] !== 'false'),
81
+ };
82
+ if (MAP[cmd]) MAP[cmd]();
83
+ else console.log('Unknown: continue|c, next|n, step|s, break|b, stack, vars, tree, inspect, bp');
84
+ }
85
+
86
+ module.exports = { startRepl };
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ function parseSceneNode(params, ofs = { offset: 0 }) {
4
+ const childCount = params[ofs.offset++];
5
+ const name = params[ofs.offset++];
6
+ const className = params[ofs.offset++];
7
+ const id = params[ofs.offset++];
8
+ const sceneFilePath = params[ofs.offset++];
9
+ const viewFlags = params[ofs.offset++];
10
+ const children = [];
11
+ for (let i = 0; i < childCount; i++) {
12
+ children.push(parseSceneNode(params, ofs));
13
+ }
14
+ return { name, className, id, sceneFilePath, viewFlags, children };
15
+ }
16
+
17
+ function formatSceneTree(node, indent = 0) {
18
+ const prefix = ' '.repeat(indent);
19
+ const lines = [`${prefix}[${node.className}] ${node.name} (id:${node.id})`];
20
+ if (node.sceneFilePath) lines[0] += ` <${node.sceneFilePath}>`;
21
+ for (const child of node.children) {
22
+ lines.push(...formatSceneTree(child, indent + 1));
23
+ }
24
+ return lines;
25
+ }
26
+
27
+ module.exports = { parseSceneNode, formatSceneTree };
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+
3
+ module.exports = function getTemplates(projectName) {
4
+ return {
5
+ 'project.godot': `; Engine configuration file.
6
+ config_version=5
7
+
8
+ [application]
9
+
10
+ config/name="${projectName}"
11
+ config/features=PackedStringArray("4.3")
12
+ config/icon="res://icon.svg"
13
+ run/main_scene="res://scenes/main.tscn"
14
+
15
+ [autoload]
16
+
17
+ ReplBridge="res://addons/repl_bridge/repl_bridge.gd"
18
+
19
+ [debug]
20
+
21
+ settings/stdout/print_fps=true
22
+ settings/stdout/verbose_stdout=true
23
+ `,
24
+
25
+ 'scenes/main.tscn': `[gd_scene load_steps=2 format=3 uid="uid://main"]
26
+
27
+ [ext_resource type="Script" path="res://scripts/main.gd" id="1_main"]
28
+
29
+ [node name="Main" type="Node"]
30
+ script = ExtResource("1_main")
31
+ `,
32
+
33
+ 'scripts/main.gd': `extends Node
34
+
35
+ func _ready() -> void:
36
+ \tprint("[%s] Ready" % name)
37
+ \tReplBridge.log_info("Game started: " + name)
38
+
39
+ func _process(_delta: float) -> void:
40
+ \tpass
41
+ `,
42
+
43
+ 'addons/repl_bridge/repl_bridge.gd': `extends Node
44
+
45
+ const VERSION := "1.0.0"
46
+ var _log_buffer: Array[String] = []
47
+
48
+ func _ready() -> void:
49
+ \tprint("[ReplBridge] v", VERSION, " initialized")
50
+ \tEngineDebugger.register_message_capture("repl", _on_repl_message)
51
+
52
+ func _on_repl_message(msg: String, data: Array) -> bool:
53
+ \tmatch msg:
54
+ \t\t"ping":
55
+ \t\t\tEngineDebugger.send_message("repl:pong", ["pong", Time.get_ticks_msec()])
56
+ \t\t\treturn true
57
+ \t\t"get_logs":
58
+ \t\t\tEngineDebugger.send_message("repl:logs", _log_buffer.duplicate())
59
+ \t\t\treturn true
60
+ \t\t"get_scene":
61
+ \t\t\tvar tree_data := _dump_scene_tree(get_tree().root, 0)
62
+ \t\t\tEngineDebugger.send_message("repl:scene", tree_data)
63
+ \t\t\treturn true
64
+ \t\t"eval":
65
+ \t\t\tvar expr := Expression.new()
66
+ \t\t\tvar err := expr.parse(data[0] if data.size() > 0 else "")
67
+ \t\t\tif err == OK:
68
+ \t\t\t\tvar result = expr.execute([], self)
69
+ \t\t\t\tEngineDebugger.send_message("repl:eval_result", [str(result)])
70
+ \t\t\telse:
71
+ \t\t\t\tEngineDebugger.send_message("repl:eval_error", [expr.get_error_text()])
72
+ \t\t\treturn true
73
+ \treturn false
74
+
75
+ func log_info(msg: String) -> void:
76
+ \t_log_buffer.append("[INFO] " + msg)
77
+ \tif _log_buffer.size() > 1000: _log_buffer.pop_front()
78
+ \tprint(msg)
79
+
80
+ func _dump_scene_tree(node: Node, depth: int) -> Array:
81
+ \tvar result := []
82
+ \tresult.append({"name": node.name, "class": node.get_class(), "depth": depth})
83
+ \tfor child in node.get_children(): result.append_array(_dump_scene_tree(child, depth + 1))
84
+ \treturn result
85
+ `,
86
+
87
+ 'addons/repl_bridge/plugin.cfg': `[plugin]
88
+ name="ReplBridge"
89
+ description="Remote REPL bridge for godot-kit CLI debugging"
90
+ author="AnEntrypoint"
91
+ version="1.0.0"
92
+ script="repl_bridge.gd"
93
+ `,
94
+
95
+ '.gdlintrc': `# gdtoolkit linter config (Godot 4.x)
96
+ max-line-length = 120
97
+ `,
98
+
99
+ '.gdformatrc': `line_length = 120
100
+ `,
101
+
102
+ '.vscode/settings.json': `{
103
+ "godot_tools.editor_path": "",
104
+ "godot_tools.gdscript_lsp_server_port": 6005,
105
+ "[gdscript]": { "editor.defaultFormatter": "geequlim.godot-tools" }
106
+ }
107
+ `,
108
+
109
+ '.vscode/launch.json': `{
110
+ "version": "0.2.0",
111
+ "configurations": [
112
+ {
113
+ "name": "Debug Godot Game",
114
+ "type": "godot",
115
+ "request": "launch",
116
+ "project": "\${workspaceFolder}",
117
+ "address": "tcp://127.0.0.1",
118
+ "port": 6007,
119
+ "profiling": false
120
+ },
121
+ {
122
+ "name": "Attach to Godot",
123
+ "type": "godot",
124
+ "request": "attach",
125
+ "project": "\${workspaceFolder}",
126
+ "address": "tcp://127.0.0.1",
127
+ "port": 6007
128
+ }
129
+ ]
130
+ }
131
+ `,
132
+
133
+ '.vscode/extensions.json': `{ "recommendations": ["geequlim.godot-tools"] }
134
+ `,
135
+
136
+ 'icon.svg': `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
137
+ <rect width="128" height="128" rx="16" fill="#478cbf"/>
138
+ <text x="64" y="80" font-size="56" text-anchor="middle" fill="white" font-family="sans-serif">G</text>
139
+ </svg>
140
+ `,
141
+
142
+ '.gitignore': `.godot/\n*.import\nexport_presets.cfg\n*.translation\nnode_modules/\n`,
143
+
144
+ 'Makefile': `setup:\n\tgodot-dev setup\nrun:\n\tgodot-dev launch\nrepl:\n\tgodot-dev repl\ninspect:\n\tgodot-dev inspect\nlogs:\n\tgodot-dev logs\nlint:\n\tgodot-dev lint\nformat:\n\tgodot-dev format\n.PHONY: setup run repl inspect logs lint format\n`,
145
+ };
146
+ };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "godot-kit",
3
+ "version": "1.0.0",
4
+ "description": "Agentic Godot 4.x development boilerplate - REPL/CLI debugging, gdtoolkit, DAP, scene inspector, profiler",
5
+ "bin": {
6
+ "godot-kit": "bin/create.js",
7
+ "godot-dev": "bin/cli.js"
8
+ },
9
+ "main": "./lib/index.js",
10
+ "scripts": {
11
+ "test": "node bin/cli.js --help"
12
+ },
13
+ "keywords": [
14
+ "godot",
15
+ "gdscript",
16
+ "game-dev",
17
+ "debugger",
18
+ "repl",
19
+ "boilerplate",
20
+ "gdtoolkit"
21
+ ],
22
+ "author": "AnEntrypoint",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "chalk": "^5.3.0",
26
+ "commander": "^12.1.0",
27
+ "readline": "^1.3.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/AnEntrypoint/godot-kit.git"
35
+ }
36
+ }