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.
- package/.github/workflows/publish.yml +23 -0
- package/README.md +134 -0
- package/bin/cli.js +130 -0
- package/bin/create.js +70 -0
- package/lib/debugger-client.js +96 -0
- package/lib/index.js +7 -0
- package/lib/protocol.js +184 -0
- package/lib/repl-commands.js +86 -0
- package/lib/scene-tree.js +27 -0
- package/lib/templates.js +146 -0
- package/package.json +36 -0
|
@@ -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 };
|
package/lib/protocol.js
ADDED
|
@@ -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 };
|
package/lib/templates.js
ADDED
|
@@ -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
|
+
}
|