godot-cli-control 0.2.10__tar.gz → 0.2.12__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.
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/PKG-INFO +3 -2
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/README.md +2 -2
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/README.md +2 -1
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/_version.py +2 -2
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/cli.py +21 -3
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/daemon.py +11 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/init_cmd.py +88 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/templates/skill/SKILL.md +3 -2
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_cli.py +66 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_daemon.py +20 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_init.py +183 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/.gitignore +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/LICENSE +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/CHANGELOG.md +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/LICENSE +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/bin/run_cli_control.ps1 +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/bin/run_cli_control.sh +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/error_codes.gd +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/game_bridge.gd +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/game_bridge.gd.uid +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/input_simulation_api.gd +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/input_simulation_api.gd.uid +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/low_level_api.gd +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/low_level_api.gd.uid +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/plugin.cfg +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/plugin.gd +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/plugin.gd.uid +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/gut/test_game_bridge.gd +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/gut/test_input_simulation_api.gd +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/gut/test_low_level_api.gd +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/run_gut.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/run_gut.sh +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/test_init_walkthrough.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/test_walkthrough.sh +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/pyproject.toml +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/__init__.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/__main__.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/_duration.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/bridge.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/client.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/pytest_plugin.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/registry.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/runner.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/skills_install.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/templates/__init__.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/templates/skill/__init__.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/__init__.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_bridge.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_cli_helpers.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_client.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_duration.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_e2e_input.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_e2e_screenshot_gui.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_pytest_plugin.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_registry.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_runner.py +0 -0
- {godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/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.
|
|
3
|
+
Version: 0.2.12
|
|
4
4
|
Summary: WebSocket bridge for headless / scripted control of Godot scenes.
|
|
5
5
|
Author: kesar
|
|
6
6
|
License: MIT
|
|
@@ -114,7 +114,8 @@ The CLI is the canonical surface — every `GameClient` method has a one-line eq
|
|
|
114
114
|
```bash
|
|
115
115
|
# Lifecycle
|
|
116
116
|
godot-cli-control init [--path DIR] [--force]
|
|
117
|
-
godot-cli-control daemon start [--headless
|
|
117
|
+
godot-cli-control daemon start [--headless | --gui] [--port N --idle-timeout 30m]
|
|
118
|
+
godot-cli-control daemon start --record --movie-path X [--fps N] # 录制需真实渲染器,不能与 --headless 同用
|
|
118
119
|
godot-cli-control daemon stop [--all | --project PATH]
|
|
119
120
|
godot-cli-control daemon status
|
|
120
121
|
godot-cli-control daemon ls # list running daemons across all projects
|
|
@@ -9,13 +9,13 @@ If you have the Python CLI installed (`pipx install godot-cli-control`), the ent
|
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
cd your_godot_project
|
|
12
|
-
godot-cli-control init # copy plugin, patch project.godot, detect Godot
|
|
12
|
+
godot-cli-control init # copy plugin, patch project.godot, detect Godot, gitignore .cli_control/
|
|
13
13
|
godot-cli-control daemon start
|
|
14
14
|
godot-cli-control tree 3
|
|
15
15
|
godot-cli-control daemon stop
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
`init` automates everything described in the manual setup below — copying the addon, editing `project.godot`, and detecting your Godot binary.
|
|
18
|
+
`init` automates everything described in the manual setup below — copying the addon, editing `project.godot`, and detecting your Godot binary. It also appends `.cli_control/` to the project's root `.gitignore` (the daemon's machine-local state dir: detected binary path, pid, port, log, recording intermediates), so that state never gets committed. Pass `--no-gitignore` to skip that step.
|
|
19
19
|
|
|
20
20
|
## Manual Setup (if you prefer)
|
|
21
21
|
|
|
@@ -97,7 +97,8 @@ The CLI is the canonical surface — every `GameClient` method has a one-line eq
|
|
|
97
97
|
```bash
|
|
98
98
|
# Lifecycle
|
|
99
99
|
godot-cli-control init [--path DIR] [--force]
|
|
100
|
-
godot-cli-control daemon start [--headless
|
|
100
|
+
godot-cli-control daemon start [--headless | --gui] [--port N --idle-timeout 30m]
|
|
101
|
+
godot-cli-control daemon start --record --movie-path X [--fps N] # 录制需真实渲染器,不能与 --headless 同用
|
|
101
102
|
godot-cli-control daemon stop [--all | --project PATH]
|
|
102
103
|
godot-cli-control daemon status
|
|
103
104
|
godot-cli-control daemon ls # list running daemons across all projects
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.12'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 12)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -73,11 +73,18 @@ def _resolve_headless(
|
|
|
73
73
|
True,让没显式指定 ``--headless`` / ``--gui`` 的非 TTY 场景(subagent / pipe)
|
|
74
74
|
自动改走 GUI —— headless 下 dummy renderer 拿不到 viewport texture,screenshot
|
|
75
75
|
永远 1006 fail(issue #65)。
|
|
76
|
+
|
|
77
|
+
``--record`` 同理且更狠:Movie Maker 的 ``add_frame()`` 读的也是 viewport
|
|
78
|
+
texture,headless 下拿到 null 直接 SIGSEGV(CultivationWorld #180)。所以没显式
|
|
79
|
+
``--headless`` 时一律翻成 GUI;显式 ``--headless`` 仍返回 True,由
|
|
80
|
+
``daemon.start`` 的 preflight 拒绝(明确用法错,不静默改写用户意图)。
|
|
76
81
|
"""
|
|
77
82
|
if getattr(ns, "headless", False):
|
|
78
83
|
return True
|
|
79
84
|
if getattr(ns, "gui", False):
|
|
80
85
|
return False
|
|
86
|
+
if getattr(ns, "record", False):
|
|
87
|
+
return False
|
|
81
88
|
if force_gui_hint:
|
|
82
89
|
return False
|
|
83
90
|
try:
|
|
@@ -1257,6 +1264,7 @@ def cmd_init(ns: argparse.Namespace) -> int:
|
|
|
1257
1264
|
write_skills=not ns.no_skills,
|
|
1258
1265
|
skills_only=ns.skills_only,
|
|
1259
1266
|
clobber_skills=not ns.skills_no_clobber,
|
|
1267
|
+
write_gitignore=not ns.no_gitignore,
|
|
1260
1268
|
output_format=fmt,
|
|
1261
1269
|
result=result,
|
|
1262
1270
|
)
|
|
@@ -1356,7 +1364,8 @@ def _add_daemon_flags(p: argparse.ArgumentParser) -> None:
|
|
|
1356
1364
|
p.add_argument(
|
|
1357
1365
|
"--record",
|
|
1358
1366
|
action="store_true",
|
|
1359
|
-
help="启动后录制 demo(写到 .cli_control/movie_path
|
|
1367
|
+
help="启动后录制 demo(写到 .cli_control/movie_path)。需真实渲染器,"
|
|
1368
|
+
"不能与 --headless 同用;没指定时会自动开窗(即使非 TTY)。",
|
|
1360
1369
|
)
|
|
1361
1370
|
p.add_argument(
|
|
1362
1371
|
"--movie-path",
|
|
@@ -1367,7 +1376,8 @@ def _add_daemon_flags(p: argparse.ArgumentParser) -> None:
|
|
|
1367
1376
|
headless_grp.add_argument(
|
|
1368
1377
|
"--headless",
|
|
1369
1378
|
action="store_true",
|
|
1370
|
-
help="无窗口模式。默认值:stdout 非 TTY 时自动 headless(CI / pipe / agent)。"
|
|
1379
|
+
help="无窗口模式。默认值:stdout 非 TTY 时自动 headless(CI / pipe / agent)。"
|
|
1380
|
+
"与 --record 互斥(录制需真实渲染器)。",
|
|
1371
1381
|
)
|
|
1372
1382
|
headless_grp.add_argument(
|
|
1373
1383
|
"--gui",
|
|
@@ -1560,7 +1570,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1560
1570
|
help="在 Godot 项目根一键接入插件",
|
|
1561
1571
|
description=(
|
|
1562
1572
|
"复制 addons/godot_cli_control 到目标项目、patch project.godot 启用插件、"
|
|
1563
|
-
"校验 GODOT_BIN
|
|
1573
|
+
"校验 GODOT_BIN、在 .gitignore 忽略 .cli_control/。"
|
|
1564
1574
|
),
|
|
1565
1575
|
epilog=(
|
|
1566
1576
|
"GODOT_BIN 查找顺序:\n"
|
|
@@ -1592,6 +1602,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1592
1602
|
"保证版本与 CLI 帮助同步)。与 --no-skills / --skills-only 都兼容。"
|
|
1593
1603
|
),
|
|
1594
1604
|
)
|
|
1605
|
+
init_p.add_argument(
|
|
1606
|
+
"--no-gitignore",
|
|
1607
|
+
action="store_true",
|
|
1608
|
+
help=(
|
|
1609
|
+
"跳过往项目根 .gitignore 追加 .cli_control/(默认会追加,"
|
|
1610
|
+
"忽略 daemon 的机器本地状态目录)。--skills-only 模式下本就跳过。"
|
|
1611
|
+
),
|
|
1612
|
+
)
|
|
1595
1613
|
skills_group = init_p.add_mutually_exclusive_group()
|
|
1596
1614
|
skills_group.add_argument(
|
|
1597
1615
|
"--no-skills",
|
|
@@ -93,6 +93,17 @@ class Daemon:
|
|
|
93
93
|
)
|
|
94
94
|
if record and not movie_path:
|
|
95
95
|
raise DaemonError("--record requires --movie-path")
|
|
96
|
+
# 录制无法在 headless 下跑:Godot Movie Maker 的 add_frame() 读 viewport
|
|
97
|
+
# texture,headless dummy renderer 拿到 null 直接 SIGSEGV(首帧崩,
|
|
98
|
+
# CultivationWorld #180)。CLI 侧 _resolve_headless 已把「没显式 --headless
|
|
99
|
+
# 的 record」翻成 GUI,所以走到这里还 headless=True 的只可能是用户显式
|
|
100
|
+
# --headless —— 这是用法矛盾,spawn 前拒绝,别让 Godot 段错误。
|
|
101
|
+
if record and headless:
|
|
102
|
+
raise DaemonError(
|
|
103
|
+
"录制与 --headless 冲突:Godot Movie Maker 需要真实渲染器,"
|
|
104
|
+
"headless dummy renderer 拿不到 viewport texture,add_frame() 会"
|
|
105
|
+
" SIGSEGV。去掉 --headless(默认/非 TTY 会自动开窗)或显式 --gui。"
|
|
106
|
+
)
|
|
96
107
|
|
|
97
108
|
bin_path = (
|
|
98
109
|
godot_bin
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
绕过 Godot Editor GUI 启用步骤
|
|
9
9
|
4. 自动检测 Godot 二进制,写入 ``.cli_control/godot_bin``,daemon 启动时
|
|
10
10
|
会优先读取此文件
|
|
11
|
+
5. 在项目根 ``.gitignore`` 追加 ``.cli_control/`` —— daemon 的机器本地状态
|
|
12
|
+
目录(godot_bin 绝对路径 / pid / port / log / 录制中间产物),不应提交
|
|
11
13
|
|
|
12
14
|
完成后用户只需 ``godot-cli-control daemon start`` 即可。
|
|
13
15
|
"""
|
|
@@ -26,6 +28,12 @@ from .daemon import find_godot_binary, reimport_project
|
|
|
26
28
|
PLUGIN_DIR_NAME = "godot_cli_control"
|
|
27
29
|
ADDONS_DIRNAME = "addons"
|
|
28
30
|
|
|
31
|
+
GITIGNORE_NAME = ".gitignore"
|
|
32
|
+
# 需要 init 写进目标项目 .gitignore 的条目。用元组而非单值,给将来"增量加
|
|
33
|
+
# 忽略项"留口子(与 method_blacklist_extra 同样的增量哲学);目前只有 daemon
|
|
34
|
+
# 的机器本地状态目录 .cli_control/。
|
|
35
|
+
GITIGNORE_ENTRIES: tuple[str, ...] = (".cli_control/",)
|
|
36
|
+
|
|
29
37
|
AUTOLOAD_KEY = "GameBridgeNode"
|
|
30
38
|
AUTOLOAD_VALUE = '"*res://addons/godot_cli_control/bridge/game_bridge.gd"'
|
|
31
39
|
PLUGIN_CFG_PATH = "res://addons/godot_cli_control/plugin.cfg"
|
|
@@ -43,6 +51,7 @@ def run_init(
|
|
|
43
51
|
write_skills: bool = True,
|
|
44
52
|
skills_only: bool = False,
|
|
45
53
|
clobber_skills: bool = True,
|
|
54
|
+
write_gitignore: bool = True,
|
|
46
55
|
*,
|
|
47
56
|
output_format: str = "text",
|
|
48
57
|
result: dict[str, Any] | None = None,
|
|
@@ -56,6 +65,9 @@ def run_init(
|
|
|
56
65
|
``clobber_skills=False``:写 skill 时遇到已存在文件就跳过(用户改过
|
|
57
66
|
SKILL.md、希望保留本地版又允许 init 把缺失那条补上时用)。与
|
|
58
67
|
``write_skills=False`` / ``skills_only=True`` 都兼容。
|
|
68
|
+
``write_gitignore=False``:跳过往项目根 ``.gitignore`` 追加 ``.cli_control/``
|
|
69
|
+
(不想让 init 碰 ``.gitignore`` 的用户逃生口)。该步骤属于"项目接入"阶段,
|
|
70
|
+
``skills_only=True`` 时本就跳过。
|
|
59
71
|
|
|
60
72
|
``output_format='json'``:抑制全部人类可读 print;调用方(cli.cmd_init)
|
|
61
73
|
传入 ``result`` 字典,本函数会回填结构化字段,由 cli 侧封 JSON envelope。
|
|
@@ -93,6 +105,7 @@ def run_init(
|
|
|
93
105
|
project_godot_changes=[],
|
|
94
106
|
godot_bin=None,
|
|
95
107
|
skills_written=[],
|
|
108
|
+
gitignore_added=[],
|
|
96
109
|
)
|
|
97
110
|
|
|
98
111
|
if not (project_root / "project.godot").is_file():
|
|
@@ -157,6 +170,15 @@ def run_init(
|
|
|
157
170
|
"(跳过资源重新导入;首次 daemon start 会兜底重建 cache)"
|
|
158
171
|
)
|
|
159
172
|
|
|
173
|
+
# .gitignore:无条件确保(不依赖 godot_bin 是否检测到——daemon 早晚
|
|
174
|
+
# 会建 .cli_control/,提前忽略最稳)。属于"项目接入"阶段,故在
|
|
175
|
+
# not skills_only 块内。
|
|
176
|
+
if write_gitignore:
|
|
177
|
+
added = _ensure_gitignore_entries(project_root)
|
|
178
|
+
if added:
|
|
179
|
+
_say(f"修改 .gitignore:忽略 {', '.join(added)}")
|
|
180
|
+
_record(gitignore_added=added)
|
|
181
|
+
|
|
160
182
|
if write_skills:
|
|
161
183
|
# lazy import:保持与 cli.cmd_init 那侧 `from .init_cmd import run_init`
|
|
162
184
|
# 对称,两边都不在模块顶层 import,避免任一方将来改成顶层 import
|
|
@@ -319,3 +341,69 @@ def _ensure_in_packed_array(
|
|
|
319
341
|
body.rstrip("\n") + "\n" + f'{key}=PackedStringArray("{value}")\n'
|
|
320
342
|
)
|
|
321
343
|
return text[:start] + new_body + text[end:], True
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# ── .gitignore 维护 ──
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _gitignore_has_entry(text: str, entry: str) -> bool:
|
|
350
|
+
"""``.gitignore`` 文本里是否已忽略 ``entry``。
|
|
351
|
+
|
|
352
|
+
宽松匹配:忽略前导 ``/`` 与尾随 ``/``、行首尾空白,所以 ``.cli_control``、
|
|
353
|
+
``.cli_control/``、``/.cli_control/`` 视为同一条,不重复加。注释行(``#`` 开头)
|
|
354
|
+
不算数 —— 用户把它注释掉就是想让它失效,真条目仍要补。
|
|
355
|
+
"""
|
|
356
|
+
target = entry.strip().strip("/")
|
|
357
|
+
for line in text.splitlines():
|
|
358
|
+
stripped = line.strip()
|
|
359
|
+
if not stripped or stripped.startswith("#"):
|
|
360
|
+
continue
|
|
361
|
+
if stripped.strip("/") == target:
|
|
362
|
+
return True
|
|
363
|
+
return False
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _add_gitignore_entries(
|
|
367
|
+
text: str, entries: tuple[str, ...] | list[str]
|
|
368
|
+
) -> tuple[str, list[str]]:
|
|
369
|
+
"""纯函数:把缺失的 ``entries`` 追加到 ``.gitignore`` 文本末尾。
|
|
370
|
+
|
|
371
|
+
返回 ``(新文本, 实际新增的条目列表)``。已存在的条目跳过。追加前确保末尾
|
|
372
|
+
有换行,避免把新条目黏到原末行后面;空文本不留前导空行。行尾统一用
|
|
373
|
+
``\\n``,CRLF 回写交给 IO 包装层(与 ``_patch_project_godot`` 同构)。
|
|
374
|
+
"""
|
|
375
|
+
added: list[str] = []
|
|
376
|
+
out = text
|
|
377
|
+
for entry in entries:
|
|
378
|
+
if _gitignore_has_entry(out, entry):
|
|
379
|
+
continue
|
|
380
|
+
if out and not out.endswith("\n"):
|
|
381
|
+
out += "\n"
|
|
382
|
+
out += entry + "\n"
|
|
383
|
+
added.append(entry)
|
|
384
|
+
return out, added
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _ensure_gitignore_entries(
|
|
388
|
+
project_root: Path, entries: tuple[str, ...] = GITIGNORE_ENTRIES
|
|
389
|
+
) -> list[str]:
|
|
390
|
+
"""确保项目根 ``.gitignore`` 忽略 ``entries``;返回实际新增的条目。
|
|
391
|
+
|
|
392
|
+
文件不存在则创建(LF)。已存在则沿用 ``_patch_project_godot`` 的 bytes+CRLF
|
|
393
|
+
思路:读 bytes、探测原文是否 CRLF、解码时折成 LF 便于匹配,仅在确有新增时
|
|
394
|
+
回写并把行尾翻回原样 —— 保 Windows 上 git diff 不被整文件行尾翻转污染。
|
|
395
|
+
"""
|
|
396
|
+
path = project_root / GITIGNORE_NAME
|
|
397
|
+
if path.exists():
|
|
398
|
+
raw = path.read_bytes()
|
|
399
|
+
is_crlf = b"\r\n" in raw
|
|
400
|
+
text = raw.decode("utf-8").replace("\r\n", "\n")
|
|
401
|
+
else:
|
|
402
|
+
is_crlf = False
|
|
403
|
+
text = ""
|
|
404
|
+
|
|
405
|
+
new_text, added = _add_gitignore_entries(text, entries)
|
|
406
|
+
if added:
|
|
407
|
+
out = new_text.replace("\n", "\r\n") if is_crlf else new_text
|
|
408
|
+
path.write_bytes(out.encode("utf-8"))
|
|
409
|
+
return added
|
|
@@ -85,7 +85,7 @@ $ godot-cli-control --text exists /root/Main
|
|
|
85
85
|
true
|
|
86
86
|
|
|
87
87
|
$ godot-cli-control init
|
|
88
|
-
{"ok": true, "result": {"project_root": "/path/to/proj", "plugin_copied": true, "plugin_overwritten": false, "project_godot_changes": ["autoload/GameBridgeNode", "editor_plugins/enabled"], "godot_bin": "/usr/bin/godot", "skills_written": [".../SKILL.md", ".../SKILL.md"], "skills_only": false, "write_skills": true}}
|
|
88
|
+
{"ok": true, "result": {"project_root": "/path/to/proj", "plugin_copied": true, "plugin_overwritten": false, "project_godot_changes": ["autoload/GameBridgeNode", "editor_plugins/enabled"], "godot_bin": "/usr/bin/godot", "skills_written": [".../SKILL.md", ".../SKILL.md"], "gitignore_added": [".cli_control/"], "skills_only": false, "write_skills": true}}
|
|
89
89
|
|
|
90
90
|
$ godot-cli-control run my_script.py
|
|
91
91
|
{"ok": true, "result": {"exit_code": 0, "script": "my_script.py"}}
|
|
@@ -274,7 +274,7 @@ Or `{"steps": [...]}` wrapper form is also accepted. Steps run strictly serially
|
|
|
274
274
|
The daemon can drive Godot's [Movie Maker](https://docs.godotengine.org/en/stable/tutorials/animation/creating_movies.html) (`--write-movie`) so input you script through this skill is captured to disk. Pipeline: Godot writes raw `.avi`, `daemon stop` shells out to `ffmpeg` to transcode that into `.mp4` next to the original.
|
|
275
275
|
|
|
276
276
|
```bash
|
|
277
|
-
godot-cli-control daemon start --record --movie-path out.avi --
|
|
277
|
+
godot-cli-control daemon start --record --movie-path out.avi --fps 60
|
|
278
278
|
godot-cli-control click /root/Main/StartButton
|
|
279
279
|
godot-cli-control tap jump 0.3
|
|
280
280
|
godot-cli-control daemon stop # → produces out.mp4
|
|
@@ -283,6 +283,7 @@ godot-cli-control daemon stop # → produces out.mp4
|
|
|
283
283
|
Key constraints:
|
|
284
284
|
|
|
285
285
|
- `--record` **requires** `--movie-path` (daemon refuses to start otherwise).
|
|
286
|
+
- `--record` needs a **real renderer**, so it cannot run with `--headless`: Godot Movie Maker's `add_frame()` reads the viewport texture, which the headless dummy renderer leaves null → SIGSEGV on the first frame. The daemon therefore **rejects `--record --headless` with exit code 2** before launching Godot. You don't need to pass `--gui`: when `--record` is set the daemon auto-opens a window even in a non-TTY (subagent / pipe / CI) shell that would otherwise default to headless.
|
|
286
287
|
- The `.mp4` is produced **only when `daemon stop` runs**; `kill -9` leaves the raw `.avi` behind.
|
|
287
288
|
- `ffmpeg` must be on `PATH` for transcoding. If transcoding fails, the raw `.avi` is kept and `daemon stop` exits with code `2` (transcode log at `.cli_control/ffmpeg.log`).
|
|
288
289
|
- `--fps` controls the **fixed simulation framerate** Godot runs at while recording — set it to your target video framerate.
|
|
@@ -723,6 +723,7 @@ def test_cmd_init_catches_unexpected_exception_into_envelope(
|
|
|
723
723
|
no_skills=False,
|
|
724
724
|
skills_only=False,
|
|
725
725
|
skills_no_clobber=False,
|
|
726
|
+
no_gitignore=False,
|
|
726
727
|
output_format=OUTPUT_JSON,
|
|
727
728
|
)
|
|
728
729
|
rc = cmd_init(ns)
|
|
@@ -1258,6 +1259,47 @@ def test_daemon_start_text_mode_keeps_silent_success(
|
|
|
1258
1259
|
assert capsys.readouterr().out == ""
|
|
1259
1260
|
|
|
1260
1261
|
|
|
1262
|
+
def test_daemon_start_rejects_record_with_explicit_headless(
|
|
1263
|
+
tmp_path: Path,
|
|
1264
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1265
|
+
capsys: pytest.CaptureFixture[str],
|
|
1266
|
+
) -> None:
|
|
1267
|
+
"""``daemon start --record --movie-path x --headless`` → ok:false 信封 +
|
|
1268
|
+
EXIT_INFRA_ERROR(2),且 message 点名 headless(CultivationWorld #180)。
|
|
1269
|
+
|
|
1270
|
+
显式 --headless 经 _resolve_headless 仍是 True,被 daemon.start 的 preflight
|
|
1271
|
+
拒绝。find_godot_binary mock 成 None 确保 RED 时不会真去 spawn Godot。"""
|
|
1272
|
+
import json as _json
|
|
1273
|
+
|
|
1274
|
+
(tmp_path / "project.godot").write_text("", encoding="utf-8")
|
|
1275
|
+
monkeypatch.chdir(tmp_path)
|
|
1276
|
+
monkeypatch.setattr(
|
|
1277
|
+
"godot_cli_control.daemon.find_godot_binary", lambda: None
|
|
1278
|
+
)
|
|
1279
|
+
|
|
1280
|
+
from godot_cli_control.cli import (
|
|
1281
|
+
EXIT_INFRA_ERROR,
|
|
1282
|
+
OUTPUT_JSON,
|
|
1283
|
+
cmd_daemon_start,
|
|
1284
|
+
)
|
|
1285
|
+
|
|
1286
|
+
ns = __import__("argparse").Namespace(
|
|
1287
|
+
record=True,
|
|
1288
|
+
movie_path=str(tmp_path / "demo.avi"),
|
|
1289
|
+
headless=True,
|
|
1290
|
+
gui=False,
|
|
1291
|
+
fps=30,
|
|
1292
|
+
port=0,
|
|
1293
|
+
idle_timeout="0",
|
|
1294
|
+
output_format=OUTPUT_JSON,
|
|
1295
|
+
)
|
|
1296
|
+
rc = cmd_daemon_start(ns)
|
|
1297
|
+
assert rc == EXIT_INFRA_ERROR
|
|
1298
|
+
payload = _json.loads(capsys.readouterr().out.strip())
|
|
1299
|
+
assert payload["ok"] is False
|
|
1300
|
+
assert "headless" in payload["error"]["message"]
|
|
1301
|
+
|
|
1302
|
+
|
|
1261
1303
|
def test_daemon_stop_emits_json_envelope(
|
|
1262
1304
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
1263
1305
|
) -> None:
|
|
@@ -1796,6 +1838,30 @@ class TestDaemonHeadlessAutodetect:
|
|
|
1796
1838
|
ns = type("NS", (), {"headless": True, "gui": False})()
|
|
1797
1839
|
assert _resolve_headless(ns, force_gui_hint=True) is True
|
|
1798
1840
|
|
|
1841
|
+
def test_record_flips_pipe_to_gui(
|
|
1842
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
1843
|
+
) -> None:
|
|
1844
|
+
"""--record 没显式 --headless 时强制开窗 —— Movie Maker 需真实渲染器,
|
|
1845
|
+
headless dummy renderer 拿不到 viewport texture 会 SIGSEGV
|
|
1846
|
+
(CultivationWorld #180,同 screenshot/#65)。非 TTY 默认 headless
|
|
1847
|
+
的 subagent/pipe/CI 场景下,--record 也应自动翻成 GUI。"""
|
|
1848
|
+
from godot_cli_control.cli import _resolve_headless
|
|
1849
|
+
|
|
1850
|
+
monkeypatch.setattr("sys.stdout.isatty", lambda: False)
|
|
1851
|
+
ns = type("NS", (), {"headless": False, "gui": False, "record": True})()
|
|
1852
|
+
assert _resolve_headless(ns) is False
|
|
1853
|
+
|
|
1854
|
+
def test_explicit_headless_wins_over_record_flip(
|
|
1855
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
1856
|
+
) -> None:
|
|
1857
|
+
"""显式 --headless + --record 仍返回 True —— 不静默改写用户意图,
|
|
1858
|
+
而是把矛盾交给 daemon.start 的 preflight 拒绝(给明确用法错)。"""
|
|
1859
|
+
from godot_cli_control.cli import _resolve_headless
|
|
1860
|
+
|
|
1861
|
+
monkeypatch.setattr("sys.stdout.isatty", lambda: True)
|
|
1862
|
+
ns = type("NS", (), {"headless": True, "gui": False, "record": True})()
|
|
1863
|
+
assert _resolve_headless(ns) is True
|
|
1864
|
+
|
|
1799
1865
|
|
|
1800
1866
|
class TestScriptLikelyUsesScreenshot:
|
|
1801
1867
|
def test_detects_method_call(self, tmp_path: Path) -> None:
|
|
@@ -80,6 +80,26 @@ def test_start_rejects_record_without_movie_path(tmp_path: Path) -> None:
|
|
|
80
80
|
daemon.start(record=True, movie_path=None)
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
def test_start_rejects_record_with_headless(
|
|
84
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
85
|
+
) -> None:
|
|
86
|
+
"""record + headless 必须在 spawn 前拒绝。
|
|
87
|
+
|
|
88
|
+
Godot Movie Maker 在 headless dummy renderer 下拿不到 viewport texture,
|
|
89
|
+
add_frame() 收到 null 纹理直接 SIGSEGV(CultivationWorld #180,同 #65 截图)。
|
|
90
|
+
这是用法矛盾 —— preflight 报错,别让 Godot 段错误吓人。检查必须在 Godot
|
|
91
|
+
binary 查找/spawn 之前,所以即使 find_godot_binary 被 mock 成 None 也应先命中
|
|
92
|
+
headless 报错。"""
|
|
93
|
+
_touch_godot_project(tmp_path)
|
|
94
|
+
monkeypatch.setattr(
|
|
95
|
+
"godot_cli_control.daemon.find_godot_binary", lambda: None
|
|
96
|
+
)
|
|
97
|
+
daemon = Daemon(tmp_path)
|
|
98
|
+
movie = tmp_path / "rec.avi"
|
|
99
|
+
with pytest.raises(DaemonError, match="headless"):
|
|
100
|
+
daemon.start(record=True, movie_path=str(movie), headless=True)
|
|
101
|
+
|
|
102
|
+
|
|
83
103
|
def test_start_rejects_when_godot_bin_missing(
|
|
84
104
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
85
105
|
) -> None:
|
|
@@ -428,6 +428,7 @@ def test_cmd_init_emits_success_envelope_in_json_mode(
|
|
|
428
428
|
no_skills=False,
|
|
429
429
|
skills_only=False,
|
|
430
430
|
skills_no_clobber=False,
|
|
431
|
+
no_gitignore=False,
|
|
431
432
|
output_format=OUTPUT_JSON,
|
|
432
433
|
)
|
|
433
434
|
rc = cmd_init(ns)
|
|
@@ -459,6 +460,7 @@ def test_cmd_init_emits_error_envelope_on_non_godot_dir(
|
|
|
459
460
|
no_skills=False,
|
|
460
461
|
skills_only=False,
|
|
461
462
|
skills_no_clobber=False,
|
|
463
|
+
no_gitignore=False,
|
|
462
464
|
output_format=OUTPUT_JSON,
|
|
463
465
|
)
|
|
464
466
|
rc = cmd_init(ns)
|
|
@@ -469,3 +471,184 @@ def test_cmd_init_emits_error_envelope_on_non_godot_dir(
|
|
|
469
471
|
assert payload["ok"] is False
|
|
470
472
|
assert payload["error"]["code"] == -1003 # CLIENT_CODE_USAGE
|
|
471
473
|
assert "project.godot" in payload["error"]["message"]
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
# ── .gitignore 维护(确保 .cli_control/ 不被提交)──
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def test_gitignore_adds_entry_to_empty_text() -> None:
|
|
480
|
+
"""纯函数:空文本追加条目。"""
|
|
481
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
482
|
+
|
|
483
|
+
out, added = _add_gitignore_entries("", [".cli_control/"])
|
|
484
|
+
assert added == [".cli_control/"]
|
|
485
|
+
assert out == ".cli_control/\n"
|
|
486
|
+
# 不允许出现前导空行
|
|
487
|
+
assert not out.startswith("\n")
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def test_gitignore_appends_preserving_existing_entries() -> None:
|
|
491
|
+
"""已有别的忽略项时只追加、不动原内容。"""
|
|
492
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
493
|
+
|
|
494
|
+
text = "*.tmp\nbuild/\n"
|
|
495
|
+
out, added = _add_gitignore_entries(text, [".cli_control/"])
|
|
496
|
+
assert added == [".cli_control/"]
|
|
497
|
+
assert "*.tmp" in out
|
|
498
|
+
assert "build/" in out
|
|
499
|
+
assert out.endswith(".cli_control/\n")
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def test_gitignore_idempotent_when_entry_present() -> None:
|
|
503
|
+
"""已含完全相同条目 → 不重复加。"""
|
|
504
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
505
|
+
|
|
506
|
+
text = "build/\n.cli_control/\n"
|
|
507
|
+
out, added = _add_gitignore_entries(text, [".cli_control/"])
|
|
508
|
+
assert added == []
|
|
509
|
+
assert out == text
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
@pytest.mark.parametrize(
|
|
513
|
+
"existing",
|
|
514
|
+
[".cli_control\n", "/.cli_control/\n", "/.cli_control\n", ".cli_control/ \n"],
|
|
515
|
+
)
|
|
516
|
+
def test_gitignore_idempotent_on_equivalent_variants(existing: str) -> None:
|
|
517
|
+
"""带/不带斜杠、带前导 / 、带尾随空白都视为已忽略,不重复加。"""
|
|
518
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
519
|
+
|
|
520
|
+
out, added = _add_gitignore_entries(existing, [".cli_control/"])
|
|
521
|
+
assert added == []
|
|
522
|
+
assert out == existing
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def test_gitignore_commented_line_does_not_count_as_present() -> None:
|
|
526
|
+
"""被注释掉的同名行不算忽略,真条目仍要补上。"""
|
|
527
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
528
|
+
|
|
529
|
+
text = "# .cli_control/\n"
|
|
530
|
+
out, added = _add_gitignore_entries(text, [".cli_control/"])
|
|
531
|
+
assert added == [".cli_control/"]
|
|
532
|
+
assert "# .cli_control/" in out # 注释保留
|
|
533
|
+
assert out.endswith(".cli_control/\n")
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def test_gitignore_appends_newline_when_file_lacks_trailing_one() -> None:
|
|
537
|
+
"""原文件末行无换行符时,新条目必须独立成行、不黏在末行后面。"""
|
|
538
|
+
from godot_cli_control.init_cmd import _add_gitignore_entries
|
|
539
|
+
|
|
540
|
+
out, added = _add_gitignore_entries("build/", [".cli_control/"])
|
|
541
|
+
assert added == [".cli_control/"]
|
|
542
|
+
assert out == "build/\n.cli_control/\n"
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def test_ensure_gitignore_creates_file_when_missing(tmp_path: Path) -> None:
|
|
546
|
+
"""项目根没有 .gitignore → 创建并写入 .cli_control/。"""
|
|
547
|
+
from godot_cli_control.init_cmd import _ensure_gitignore_entries
|
|
548
|
+
|
|
549
|
+
added = _ensure_gitignore_entries(tmp_path)
|
|
550
|
+
gi = tmp_path / ".gitignore"
|
|
551
|
+
assert gi.is_file()
|
|
552
|
+
assert ".cli_control/" in gi.read_text(encoding="utf-8")
|
|
553
|
+
assert added == [".cli_control/"]
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def test_ensure_gitignore_idempotent_across_runs(tmp_path: Path) -> None:
|
|
557
|
+
"""连跑两次只写一条,不重复。"""
|
|
558
|
+
from godot_cli_control.init_cmd import _ensure_gitignore_entries
|
|
559
|
+
|
|
560
|
+
_ensure_gitignore_entries(tmp_path)
|
|
561
|
+
added2 = _ensure_gitignore_entries(tmp_path)
|
|
562
|
+
assert added2 == []
|
|
563
|
+
content = (tmp_path / ".gitignore").read_text(encoding="utf-8")
|
|
564
|
+
assert content.count(".cli_control/") == 1
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def test_ensure_gitignore_preserves_crlf(tmp_path: Path) -> None:
|
|
568
|
+
"""已有 CRLF 的 .gitignore 在追加后必须仍是 CRLF,不混入裸 LF。"""
|
|
569
|
+
from godot_cli_control.init_cmd import _ensure_gitignore_entries
|
|
570
|
+
|
|
571
|
+
gi = tmp_path / ".gitignore"
|
|
572
|
+
gi.write_bytes(b"*.tmp\r\nbuild/\r\n")
|
|
573
|
+
_ensure_gitignore_entries(tmp_path)
|
|
574
|
+
after = gi.read_bytes()
|
|
575
|
+
assert b".cli_control/\r\n" in after
|
|
576
|
+
bare_lf = after.count(b"\n") - after.count(b"\r\n")
|
|
577
|
+
assert bare_lf == 0, f"出现裸 LF:{bare_lf} 处"
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def test_run_init_adds_cli_control_to_gitignore(
|
|
581
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
582
|
+
) -> None:
|
|
583
|
+
"""端到端:run_init 默认在项目根 .gitignore 忽略 .cli_control/。"""
|
|
584
|
+
monkeypatch.delenv("GODOT_BIN", raising=False)
|
|
585
|
+
monkeypatch.setattr(
|
|
586
|
+
"godot_cli_control.init_cmd.find_godot_binary", lambda: None
|
|
587
|
+
)
|
|
588
|
+
proj = _minimal_project(tmp_path)
|
|
589
|
+
assert run_init(proj) == 0
|
|
590
|
+
gi = (proj / ".gitignore").read_text(encoding="utf-8")
|
|
591
|
+
assert ".cli_control/" in gi
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def test_run_init_no_gitignore_skips_gitignore(
|
|
595
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
596
|
+
) -> None:
|
|
597
|
+
"""write_gitignore=False → 不创建 / 不改 .gitignore。"""
|
|
598
|
+
monkeypatch.delenv("GODOT_BIN", raising=False)
|
|
599
|
+
monkeypatch.setattr(
|
|
600
|
+
"godot_cli_control.init_cmd.find_godot_binary", lambda: None
|
|
601
|
+
)
|
|
602
|
+
proj = _minimal_project(tmp_path)
|
|
603
|
+
assert run_init(proj, write_gitignore=False) == 0
|
|
604
|
+
assert not (proj / ".gitignore").exists()
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def test_run_init_skills_only_skips_gitignore(tmp_path: Path) -> None:
|
|
608
|
+
"""skills_only 属于纯刷 SKILL.md,不应碰 .gitignore。"""
|
|
609
|
+
proj = _make_min_godot_project(tmp_path)
|
|
610
|
+
assert run_init(proj, skills_only=True) == 0
|
|
611
|
+
assert not (proj / ".gitignore").exists()
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def test_run_init_json_records_gitignore_added(
|
|
615
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
616
|
+
) -> None:
|
|
617
|
+
"""json 模式 result 必须回填 gitignore_added 字段。"""
|
|
618
|
+
monkeypatch.delenv("GODOT_BIN", raising=False)
|
|
619
|
+
monkeypatch.setattr(
|
|
620
|
+
"godot_cli_control.init_cmd.find_godot_binary", lambda: None
|
|
621
|
+
)
|
|
622
|
+
proj = _make_min_godot_project(tmp_path)
|
|
623
|
+
result: dict[str, object] = {}
|
|
624
|
+
assert run_init(proj, output_format="json", result=result) == 0
|
|
625
|
+
assert result["gitignore_added"] == [".cli_control/"]
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def test_cmd_init_no_gitignore_flag_skips(
|
|
629
|
+
tmp_path: Path,
|
|
630
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
631
|
+
capsys: pytest.CaptureFixture[str],
|
|
632
|
+
) -> None:
|
|
633
|
+
"""CLI 层:--no-gitignore(ns.no_gitignore=True)不写 .gitignore。"""
|
|
634
|
+
import argparse
|
|
635
|
+
|
|
636
|
+
from godot_cli_control.cli import OUTPUT_JSON, cmd_init
|
|
637
|
+
|
|
638
|
+
monkeypatch.delenv("GODOT_BIN", raising=False)
|
|
639
|
+
monkeypatch.setattr(
|
|
640
|
+
"godot_cli_control.init_cmd.find_godot_binary", lambda: None
|
|
641
|
+
)
|
|
642
|
+
proj = _make_min_godot_project(tmp_path)
|
|
643
|
+
ns = argparse.Namespace(
|
|
644
|
+
path=str(proj),
|
|
645
|
+
force=False,
|
|
646
|
+
no_skills=False,
|
|
647
|
+
skills_only=False,
|
|
648
|
+
skills_no_clobber=False,
|
|
649
|
+
no_gitignore=True,
|
|
650
|
+
output_format=OUTPUT_JSON,
|
|
651
|
+
)
|
|
652
|
+
assert cmd_init(ns) == 0
|
|
653
|
+
capsys.readouterr()
|
|
654
|
+
assert not (proj / ".gitignore").exists()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/error_codes.gd
RENAMED
|
File without changes
|
{godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/bridge/game_bridge.gd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/plugin.gd.uid
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/run_gut.py
RENAMED
|
File without changes
|
{godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/addons/godot_cli_control/tests/run_gut.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/pytest_plugin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/skills_install.py
RENAMED
|
File without changes
|
{godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/godot_cli_control/templates/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.2.10 → godot_cli_control-0.2.12}/python/tests/test_e2e_screenshot_gui.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|