godot-cli-control 0.2.4__tar.gz → 0.2.5__tar.gz

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.
Files changed (56) hide show
  1. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/PKG-INFO +1 -1
  2. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/CHANGELOG.md +26 -0
  3. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/README.md +23 -1
  4. godot_cli_control-0.2.5/addons/godot_cli_control/bridge/error_codes.gd +23 -0
  5. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/bridge/input_simulation_api.gd +16 -12
  6. godot_cli_control-0.2.5/addons/godot_cli_control/bridge/low_level_api.gd +490 -0
  7. godot_cli_control-0.2.5/addons/godot_cli_control/tests/gut/test_low_level_api.gd +696 -0
  8. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/_version.py +2 -2
  9. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/bridge.py +14 -2
  10. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/cli.py +391 -101
  11. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/client.py +13 -2
  12. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/init_cmd.py +74 -27
  13. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/pytest_plugin.py +3 -3
  14. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/templates/skill/SKILL.md +77 -5
  15. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/test_bridge.py +47 -2
  16. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/test_cli.py +541 -0
  17. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/test_cli_helpers.py +5 -3
  18. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/test_client.py +49 -0
  19. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/test_init.py +121 -0
  20. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/test_pytest_plugin.py +50 -0
  21. godot_cli_control-0.2.4/addons/godot_cli_control/bridge/low_level_api.gd +0 -280
  22. godot_cli_control-0.2.4/addons/godot_cli_control/tests/gut/test_low_level_api.gd +0 -194
  23. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/.gitignore +0 -0
  24. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/LICENSE +0 -0
  25. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/LICENSE +0 -0
  26. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/bin/run_cli_control.ps1 +0 -0
  27. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/bin/run_cli_control.sh +0 -0
  28. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/bridge/game_bridge.gd +0 -0
  29. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/bridge/game_bridge.gd.uid +0 -0
  30. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/bridge/input_simulation_api.gd.uid +0 -0
  31. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/bridge/low_level_api.gd.uid +0 -0
  32. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/plugin.cfg +0 -0
  33. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/plugin.gd +0 -0
  34. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/plugin.gd.uid +0 -0
  35. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/tests/gut/test_game_bridge.gd +0 -0
  36. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/tests/gut/test_input_simulation_api.gd +0 -0
  37. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/tests/run_gut.sh +0 -0
  38. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/tests/test_init_walkthrough.py +0 -0
  39. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/addons/godot_cli_control/tests/test_walkthrough.sh +0 -0
  40. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/pyproject.toml +0 -0
  41. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/README.md +0 -0
  42. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/__init__.py +0 -0
  43. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/__main__.py +0 -0
  44. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/_duration.py +0 -0
  45. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/daemon.py +0 -0
  46. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/registry.py +0 -0
  47. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/runner.py +0 -0
  48. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/skills_install.py +0 -0
  49. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/templates/__init__.py +0 -0
  50. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/godot_cli_control/templates/skill/__init__.py +0 -0
  51. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/__init__.py +0 -0
  52. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/test_daemon.py +0 -0
  53. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/test_duration.py +0 -0
  54. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/test_registry.py +0 -0
  55. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/test_runner.py +0 -0
  56. {godot_cli_control-0.2.4 → godot_cli_control-0.2.5}/python/tests/test_skills_install.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: godot-cli-control
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: WebSocket bridge for headless / scripted control of Godot scenes.
5
5
  Author: kesar
6
6
  License: MIT
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ### Fixed
6
+ - **#52 `set` 走 JSON Array 喂 Vector/Color/Rect 时静默失败**:`set zoom '[1.8, 1.8]'` 等价于 `node.set("zoom", [1.8, 1.8])`,Godot 隐式构造失败 → 实际值是 `Vector2(0,0)` 或被 clamp 到 `0.00001`,但服务端仍返 `{success: true}`。`handle_set_property` 现在查 `get_property_list()` 拿声明类型,把 numeric Array 转成对应 Variant。长度不匹配或元素非数字时 fail-loud 返 `-32602 "value type mismatch ..."`,不再 silent corruption。
7
+ - **sub-path 标量赋值现在真的会写入**:之前 `set <node> position:x 1.8` 调的是 `Object.set("position:x", 1.8)`,Godot 4 的 `Object.set` 把整串当字面属性名找不到就 silent no-op(依旧返 `{success: true}` 但 `position.x` 不变)。改用 `Object.set_indexed(NodePath, value)` 才会按 sub-path 写入。是 #54 review 阶段被新加的 `test_set_subpath_scalar_still_works` 捕获的隐藏 silent-fail。
8
+
9
+ ### Added
10
+ - **#54 全部复合 Variant 都支持 Array → Variant coerce**:从 4-float 简单类型到 16-float 矩阵,全部按 axis-vector 顺序的 flat numeric Array 写入(每 N 元素 = 一个 Vector 轴)。覆盖:`Vector2/2i/3/3i/4/4i`、`Rect2/2i`、`Color`(3-element=RGB / 4-element=RGBA)、`Plane(a,b,c,d)`、`Quaternion(x,y,z,w)`、`AABB(pos 3, size 3)`、`Basis(9 axis-vector)`、`Transform2D(xaxis 2, yaxis 2, origin 2)`、`Transform3D(basis 9, origin 3)`、`Projection(16 axis-vector)`。具体 layout 见 SKILL.md。`Plane.normal` 和 `Quaternion` 不会自动归一化(与 Godot ctor 一致)。
11
+ - **#54 防御性 fallback**:未在 coerce 名单也不在 `_ARRAY_PASSTHROUGH_SAFE_TYPES`(基本类型 / Object / 集合 / Packed*Array)白名单的声明类型 + Array 输入会 fail-loud,避免未来 Godot 加新 compound Variant 时 silent-corrupt 回归。
12
+ - **#54 sub-path + Array 现在 fail-loud**:`set <node> transform:origin '[10, 20, 30]'` 在 Godot 里会 silent-corrupt(Vector3 leaf 不会从 Array 隐式构造,origin 仍是 (0,0,0)),server 现在主动返 `-32602 "sub-path + Array is not supported"` 提示走 top-level 形式(如 `set <node> transform '[basis 9, origin 3]'`)。sub-path 标量赋值(`position:x 1.8`)不变。
13
+
5
14
  ### AI-friendly CLI 改造(多个 BREAKING change)
6
15
 
7
16
  把 CLI 重定位成 AI agent 的一等接口:默认结构化输出、补齐读 / 写 / 发现的 shell 命令、明确退出码契约。shell-only 的 agent 现在不需要写 Python 脚本就能完成全部操作。
@@ -44,6 +53,23 @@
44
53
  #### Out of scope (intentional)
45
54
  - `run <script.py>` 子命令保留旧的人类可读 stderr 输出。它是交互式脚本宿主,不是 RPC,新的 JSON 信封契约只覆盖 RPC 子命令 + daemon 三命令。
46
55
 
56
+ ### AI-friendliness review fixes (2026-05-11)
57
+
58
+ #### Fixed
59
+ - **CLI flag position**: `--json` / `--text` / `--no-json` 现在在 RPC 子命令尾部也接受(之前只能写最前面)。argparse 子 parser 用 `default=argparse.SUPPRESS` + 顶层 `set_defaults` 兜底,避免子 parser 默认值覆盖父 parser 解析结果。
60
+ - **Error code 1004 collision**: `low_level_api.gd` 的 scene tree 超限改用新业务码 `1005 "scene tree too large"`,与 input_simulation `1004 "combo in progress"` 解耦。新增 `error_codes.gd` 集中常量。
61
+ - **pytest fixture default port**: `--godot-cli-port` 默认从 9877 改为 0(OS-assigned),与 `daemon start` 默认对齐;多项目并行测试不再撞端口。
62
+ - **Addon README error-code table**: 之前只列到 1003,补全 1004 / 1005 / 客户端 -1xxx 段。
63
+ - **Scene tree hard limit bypass (DoS fix)**: `handle_get_scene_tree` 入口现在把 `max_nodes` clamp 到硬墙 `_BUILD_TREE_NODE_LIMIT` (5000)。修复前客户端传 `max_nodes=999999` 会让服务端先把整棵超大树构造成 Dictionary 再被 1005 错误丢弃(内存浪费 / OOM 路径)。
64
+ - **Error code 1003 semantic split**: screenshot 在 viewport texture 为 null 时不再借用 `1003 METHOD_NOT_FOUND`,改用新业务码 `1006 RESOURCE_UNAVAILABLE`。1003 现在是纯 schema 错(不应 retry),1006 是 transient 错(短重试可能成功),agent 据此分别处置。
65
+
66
+ #### Added
67
+ - `tree --max-nodes <N>`(默认 200):节点数软上限;超出时响应含 `truncated: true` + `total_nodes`,agent 据此决定分子树。硬墙仍是 5000 节点 → `1005`。
68
+ - `set` / `call --text-value`:禁用 JSON 解析、把 value/args 强制按字符串处理,避开 `null` / `true` / `42` 这类字面量被解析成 Variant 类型的 footgun。
69
+
70
+ #### Changed
71
+ - **BREAKING (轻微)**:`daemon start` / `run` 默认 headless 行为改为基于 `sys.stdout.isatty()` 自动判定 —— pipe / CI / agent shell 默认 headless;交互终端默认开窗。新增 `--gui` 强制开窗 flag。`--headless` 仍可显式传,覆盖自动判。脚本里依赖 "默认会开窗" 的需要加 `--gui`。
72
+
47
73
  ## [0.1.6] - Unreleased
48
74
 
49
75
  ### Added
@@ -99,7 +99,29 @@ All methods callable via `godot-cli-control <method>` or `from godot_cli_control
99
99
  | `combo_cancel()` | `await client.combo_cancel()` |
100
100
  | `release_all()` | `await client.release_all()` |
101
101
 
102
- Error codes: `-32600` invalid request, `-32601` unknown method, `-32602` invalid params, `1001` node not found, `1002` property not found, `1003` method not found.
102
+ ### Error codes
103
+
104
+ Three numeric ranges share `error.code`; they never overlap, so a single field is unambiguous.
105
+
106
+ | Code | Source | Meaning |
107
+ |---|---|---|
108
+ | `1001` | server | Node not found at the given path |
109
+ | `1002` | server | Property not found / shape mismatch |
110
+ | `1003` | server | Method not found on the node (schema error, don't retry) |
111
+ | `1004` | server | Combo already in progress (call `combo-cancel` to retry) |
112
+ | `1005` | server | Scene tree too large (lower `depth` or pass `--max-nodes`) |
113
+ | `1006` | server | Resource transiently unavailable (e.g. screenshot before viewport ready) — safe to retry |
114
+ | `-32600` | server | Malformed JSON-RPC request |
115
+ | `-32601` | server | Unknown method name |
116
+ | `-32602` | server | Invalid params (incl. blocked methods/properties from the security blacklist, or `set` value-type mismatch — e.g. `Vector2` property given an array of wrong length / non-numeric elements) |
117
+ | `-1001` | client | Connection failure (daemon not running, port wrong, proxy hijacking localhost) |
118
+ | `-1002` | client | Timeout waiting for response |
119
+ | `-1003` | client | CLI usage error (combo missing steps, malformed `--steps-json`, …) |
120
+ | `-1004` | client | Local file IO error (e.g. screenshot can't write the destination) |
121
+ | `-1005` | client | `run <script>` user script raised an uncaught exception — fix the script |
122
+ | `-1099` | client | Internal CLI bug — please file an issue |
123
+
124
+ For full retry guidance see the SKILL.md shipped by `godot-cli-control init` (`.claude/skills/godot-cli-control/SKILL.md` in the target project).
103
125
 
104
126
  ## Activation Modes
105
127
 
@@ -0,0 +1,23 @@
1
+ class_name CliControlErrorCodes
2
+ extends RefCounted
3
+ ## 集中错误码常量。新加业务码必须在这里登记,
4
+ ## 避免 1004 那种隐式撞码(input_sim 用 "combo in progress",
5
+ ## low_level 又用 "scene tree too large")。
6
+ ##
7
+ ## 三段制(详见 SKILL.md 错误码表):
8
+ ## 1xxx 服务端业务码
9
+ ## -32xxx JSON-RPC 标准
10
+ ## -1xxx 客户端(Python)侧;GDScript 这边不会产出
11
+
12
+ const NODE_NOT_FOUND: int = 1001
13
+ const PROPERTY_NOT_FOUND: int = 1002 # 也用于 "node has no 'text' property"
14
+ const METHOD_NOT_FOUND: int = 1003
15
+ const COMBO_IN_PROGRESS: int = 1004
16
+ const SCENE_TREE_TOO_LARGE: int = 1005
17
+ # 资源 transient 不可用(screenshot viewport texture null 等)。
18
+ # 与 1003 拆开:1003 是 schema 错(永久),1006 是时机错(短重试可能成功)。
19
+ const RESOURCE_UNAVAILABLE: int = 1006
20
+
21
+ const INVALID_PARAMS: int = -32602
22
+ const INVALID_REQUEST: int = -32600
23
+ const METHOD_UNKNOWN: int = -32601
@@ -1,6 +1,10 @@
1
1
  class_name InputSimulationApi
2
2
  extends Node
3
3
  ## 输入模拟 API:动作级按键、持续控制、组合序列
4
+ ##
5
+ ## 错误码常量来自 res://addons/godot_cli_control/bridge/error_codes.gd
6
+ ## (class_name CliControlErrorCodes)。靠 Godot 全局 class 注册解析;若 GUT
7
+ ## 测试跑前遇到 "Class 'CliControlErrorCodes' not found",先 import 一次。
4
8
 
5
9
  # 手动按下的动作(无定时器)
6
10
  var _pressed_actions: Dictionary = {}
@@ -46,10 +50,10 @@ func is_combo_active() -> bool:
46
50
 
47
51
  func handle_action_press(params: Dictionary) -> Dictionary:
48
52
  if _combo_active:
49
- return _err(1004, "combo in progress")
53
+ return _err(CliControlErrorCodes.COMBO_IN_PROGRESS, "combo in progress")
50
54
  var action: String = params.get("action", "") as String
51
55
  if not InputMap.has_action(action):
52
- return _err(1003, "Unknown action: %s" % action)
56
+ return _err(CliControlErrorCodes.METHOD_NOT_FOUND, "Unknown action: %s" % action)
53
57
  _do_press(action)
54
58
  _pressed_actions[action] = true
55
59
  return {"success": true}
@@ -57,10 +61,10 @@ func handle_action_press(params: Dictionary) -> Dictionary:
57
61
 
58
62
  func handle_action_release(params: Dictionary) -> Dictionary:
59
63
  if _combo_active:
60
- return _err(1004, "combo in progress")
64
+ return _err(CliControlErrorCodes.COMBO_IN_PROGRESS, "combo in progress")
61
65
  var action: String = params.get("action", "") as String
62
66
  if not InputMap.has_action(action):
63
- return _err(1003, "Unknown action: %s" % action)
67
+ return _err(CliControlErrorCodes.METHOD_NOT_FOUND, "Unknown action: %s" % action)
64
68
  _do_release(action)
65
69
  _pressed_actions.erase(action)
66
70
  _held_actions.erase(action)
@@ -71,10 +75,10 @@ func handle_action_release(params: Dictionary) -> Dictionary:
71
75
  ## 定时器到期后自动释放
72
76
  func handle_action_tap(params: Dictionary) -> Dictionary:
73
77
  if _combo_active:
74
- return _err(1004, "combo in progress")
78
+ return _err(CliControlErrorCodes.COMBO_IN_PROGRESS, "combo in progress")
75
79
  var action: String = params.get("action", "") as String
76
80
  if not InputMap.has_action(action):
77
- return _err(1003, "Unknown action: %s" % action)
81
+ return _err(CliControlErrorCodes.METHOD_NOT_FOUND, "Unknown action: %s" % action)
78
82
  var duration: float = params.get("duration", 0.1) as float
79
83
  _do_press(action)
80
84
  _held_actions[action] = duration
@@ -104,10 +108,10 @@ func handle_list_input_actions(params: Dictionary) -> Dictionary:
104
108
 
105
109
  func handle_hold(params: Dictionary) -> Dictionary:
106
110
  if _combo_active:
107
- return _err(1004, "combo in progress")
111
+ return _err(CliControlErrorCodes.COMBO_IN_PROGRESS, "combo in progress")
108
112
  var action: String = params.get("action", "") as String
109
113
  if not InputMap.has_action(action):
110
- return _err(1003, "Unknown action: %s" % action)
114
+ return _err(CliControlErrorCodes.METHOD_NOT_FOUND, "Unknown action: %s" % action)
111
115
  var duration: float = params.get("duration", 0.0) as float
112
116
  _do_press(action)
113
117
  _held_actions[action] = duration
@@ -137,7 +141,7 @@ func release_all() -> void:
137
141
  func handle_combo(params: Dictionary, request_id: String) -> void:
138
142
  if _combo_active:
139
143
  if _send_response_callback.is_valid():
140
- _send_response_callback.call(request_id, _err(1004, "combo in progress"))
144
+ _send_response_callback.call(request_id, _err(CliControlErrorCodes.COMBO_IN_PROGRESS, "combo in progress"))
141
145
  return
142
146
  var steps: Array = params.get("steps", []) as Array
143
147
  _combo_request_id = request_id
@@ -188,7 +192,7 @@ func _begin_combo_step() -> void:
188
192
  var raw: Variant = _combo_steps[_combo_index]
189
193
  if not raw is Dictionary:
190
194
  _abort_combo_with_error(
191
- -32602, "combo step must be object at index %d" % _combo_index
195
+ CliControlErrorCodes.INVALID_PARAMS, "combo step must be object at index %d" % _combo_index
192
196
  )
193
197
  return
194
198
  var step: Dictionary = raw as Dictionary
@@ -198,7 +202,7 @@ func _begin_combo_step() -> void:
198
202
  var action: String = step["action"] as String
199
203
  if not InputMap.has_action(action):
200
204
  _abort_combo_with_error(
201
- 1003, "Unknown action at combo step %d: %s" % [_combo_index, action]
205
+ CliControlErrorCodes.METHOD_NOT_FOUND, "Unknown action at combo step %d: %s" % [_combo_index, action]
202
206
  )
203
207
  return
204
208
  var duration: float = step.get("duration", 0.1) as float
@@ -207,7 +211,7 @@ func _begin_combo_step() -> void:
207
211
  _combo_timer = duration
208
212
  else:
209
213
  _abort_combo_with_error(
210
- -32602, "combo step missing 'wait' or 'action' at index %d" % _combo_index
214
+ CliControlErrorCodes.INVALID_PARAMS, "combo step missing 'wait' or 'action' at index %d" % _combo_index
211
215
  )
212
216
 
213
217