chatup 0.2.11__tar.gz → 0.2.13__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 (92) hide show
  1. {chatup-0.2.11/src/chatup.egg-info → chatup-0.2.13}/PKG-INFO +18 -8
  2. {chatup-0.2.11 → chatup-0.2.13}/README.md +7 -1
  3. {chatup-0.2.11 → chatup-0.2.13}/pyproject.toml +11 -6
  4. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/__init__.py +1 -1
  5. chatup-0.2.13/src/chatup/cli.py +38 -0
  6. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/codex.py +60 -34
  7. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/service_credentials.py +10 -6
  8. {chatup-0.2.11 → chatup-0.2.13/src/chatup.egg-info}/PKG-INFO +18 -8
  9. {chatup-0.2.11 → chatup-0.2.13}/src/chatup.egg-info/SOURCES.txt +1 -0
  10. chatup-0.2.13/src/chatup.egg-info/requires.txt +15 -0
  11. {chatup-0.2.11 → chatup-0.2.13}/tests/test_cli_tree_docs.py +2 -0
  12. chatup-0.2.13/tests/test_codex_setup.py +151 -0
  13. {chatup-0.2.11 → chatup-0.2.13}/tests/test_publish_workflow.py +3 -0
  14. {chatup-0.2.11 → chatup-0.2.13}/tests/test_pyproject_metadata.py +5 -3
  15. {chatup-0.2.11 → chatup-0.2.13}/tests/test_service_installers.py +40 -0
  16. chatup-0.2.13/tests/test_version.py +64 -0
  17. chatup-0.2.11/src/chatup/cli.py +0 -111
  18. chatup-0.2.11/src/chatup.egg-info/requires.txt +0 -10
  19. chatup-0.2.11/tests/test_version.py +0 -45
  20. {chatup-0.2.11 → chatup-0.2.13}/LICENSE +0 -0
  21. {chatup-0.2.11 → chatup-0.2.13}/setup.cfg +0 -0
  22. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/chrome_for_testing.py +0 -0
  23. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/chromedriver.py +0 -0
  24. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/commands/__init__.py +0 -0
  25. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/commands/chrome_for_testing.py +0 -0
  26. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/commands/chromedriver.py +0 -0
  27. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/commands/playwright.py +0 -0
  28. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/config.py +0 -0
  29. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/const.py +0 -0
  30. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/interaction/__init__.py +0 -0
  31. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/interaction/command_schema.py +0 -0
  32. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/interaction/policy.py +0 -0
  33. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/interaction/warnings.py +0 -0
  34. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/playwright.py +0 -0
  35. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/runtime/__init__.py +0 -0
  36. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/runtime/_managed_artifact.py +0 -0
  37. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/runtime/chrome_for_testing.py +0 -0
  38. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/runtime/chromedriver.py +0 -0
  39. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/runtime/playwright.py +0 -0
  40. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/__init__.py +0 -0
  41. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/assets/hermes/install.sh +0 -0
  42. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/assets/nvm.sh +0 -0
  43. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/cc_connect.py +0 -0
  44. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/claude.py +0 -0
  45. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/cli.py +0 -0
  46. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/crs.py +0 -0
  47. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/cursor_agent.py +0 -0
  48. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/discourse.py +0 -0
  49. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/docker.py +0 -0
  50. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/elements.py +0 -0
  51. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/frp.py +0 -0
  52. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/gitea.py +0 -0
  53. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/hermes.py +0 -0
  54. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/lark_cli.py +0 -0
  55. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/mode_prompt.py +0 -0
  56. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/mysql.py +0 -0
  57. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/nginx.py +0 -0
  58. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/nodejs.py +0 -0
  59. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/opencode.py +0 -0
  60. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/shell_rc.py +0 -0
  61. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/twikoo.py +0 -0
  62. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/uv.py +0 -0
  63. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/__init__.py +0 -0
  64. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/cli.py +0 -0
  65. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/core.py +0 -0
  66. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/options.py +0 -0
  67. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/render.py +0 -0
  68. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/en/AGENTS.md +0 -0
  69. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/en/discussion/README.md +0 -0
  70. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/en/projects/README.md +0 -0
  71. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/zh/AGENTS.md +0 -0
  72. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/zh/discussion/README.md +0 -0
  73. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/zh/projects/README.md +0 -0
  74. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace.py +0 -0
  75. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/zsh.py +0 -0
  76. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/zulip.py +0 -0
  77. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/utils/__init__.py +0 -0
  78. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/utils/custom_logger.py +0 -0
  79. {chatup-0.2.11 → chatup-0.2.13}/src/chatup/utils/pathing.py +0 -0
  80. {chatup-0.2.11 → chatup-0.2.13}/src/chatup.egg-info/dependency_links.txt +0 -0
  81. {chatup-0.2.11 → chatup-0.2.13}/src/chatup.egg-info/entry_points.txt +0 -0
  82. {chatup-0.2.11 → chatup-0.2.13}/src/chatup.egg-info/top_level.txt +0 -0
  83. {chatup-0.2.11 → chatup-0.2.13}/tests/test_browser_backend_cli.py +0 -0
  84. {chatup-0.2.11 → chatup-0.2.13}/tests/test_chrome_for_testing.py +0 -0
  85. {chatup-0.2.11 → chatup-0.2.13}/tests/test_chromedriver.py +0 -0
  86. {chatup-0.2.11 → chatup-0.2.13}/tests/test_crs_setup.py +0 -0
  87. {chatup-0.2.11 → chatup-0.2.13}/tests/test_cursor_agent_setup.py +0 -0
  88. {chatup-0.2.11 → chatup-0.2.13}/tests/test_playwright.py +0 -0
  89. {chatup-0.2.11 → chatup-0.2.13}/tests/test_playwright_cli.py +0 -0
  90. {chatup-0.2.11 → chatup-0.2.13}/tests/test_setup_cli.py +0 -0
  91. {chatup-0.2.11 → chatup-0.2.13}/tests/test_twikoo_setup.py +0 -0
  92. {chatup-0.2.11 → chatup-0.2.13}/tests/test_workspace_setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chatup
3
- Version: 0.2.11
3
+ Version: 0.2.13
4
4
  Summary: ChatArch setup CLI extracted from ChatTool
5
5
  Author-email: rexwzh <1073853456@qq.com>
6
6
  License-Expression: MIT
@@ -13,15 +13,19 @@ Classifier: Operating System :: OS Independent
13
13
  Requires-Python: >=3.10
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
- Requires-Dist: click>=8.0
17
- Requires-Dist: chatstyle<0.2.0,>=0.1.1
18
- Requires-Dist: chatenv<0.3.0,>=0.2.0
16
+ Requires-Dist: click<9.0,>=8.0
17
+ Requires-Dist: chatstyle<0.3.0,>=0.2.0
18
+ Requires-Dist: chatenv<0.3.0,>=0.2.11
19
19
  Requires-Dist: colorama
20
+ Provides-Extra: dev
21
+ Requires-Dist: build; extra == "dev"
22
+ Requires-Dist: pytest; extra == "dev"
23
+ Requires-Dist: twine; extra == "dev"
20
24
  Provides-Extra: docs
21
25
  Requires-Dist: mkdocs<2.0,>=1.6; extra == "docs"
22
- Requires-Dist: mkdocs-material<10.0,>=9.5; extra == "docs"
23
- Requires-Dist: mkdocs-static-i18n<2.0,>=1.3; extra == "docs"
24
- Requires-Dist: mike<3.0,>=2.1; extra == "docs"
26
+ Requires-Dist: mkdocs-material<9.7,>=9.5; extra == "docs"
27
+ Requires-Dist: mkdocs-static-i18n<2.0,>=1.2; extra == "docs"
28
+ Requires-Dist: mike<3.0,>=2.0; extra == "docs"
25
29
  Dynamic: license-file
26
30
 
27
31
  # ChatUp
@@ -47,6 +51,9 @@ ChatUp 是 ChatArch 的独立环境与工具安装 CLI。它承接原来 `chatto
47
51
 
48
52
  ```bash
49
53
  chatup --help
54
+ chatup --version
55
+ chatup --tree
56
+ chatup --tree-brief
50
57
  chatup doctor
51
58
  chatup uv
52
59
  chatup chrome-for-testing install --channel stable -I
@@ -75,7 +82,7 @@ chatup crs --install-dir ~/.chatarch/crs/local --port 12392 --redis-port 6379
75
82
  | 本地服务安装 | `gitea`、`discourse`、`zulip`、`mysql`、`nginx`、`crs` |
76
83
  | Agent 工具链 | `cc-connect`、`claude`、`codex`、`cursor-agent`、`opencode`、`hermes`、`lark-cli` |
77
84
 
78
- 所有新增安装项默认目录都收敛到 `~/.chatarch/...`,例如 `~/.chatarch/chrome-for-testing`、`~/.chatarch/chromedriver`、`~/.chatarch/playwright`、`~/.chatarch/chattea`、`~/.chatarch/discourse`、`~/.chatarch/zulip`、`~/.chatarch/chatdata`、`~/.chatarch/nginx` 和 `~/.chatarch/crs/local`。`cursor-agent` 额外注册 ChatEnv `CursorAgent` profile,`discourse`/`zulip` 注册管理员凭据 profile,并支持 `-e/--env` 从 env 文件或 profile 快速配置。更完整的能力边界见 `docs/capability-map.md`。
85
+ 所有新增安装项默认目录都收敛到 `~/.chatarch/...`,例如 `~/.chatarch/chrome-for-testing`、`~/.chatarch/chromedriver`、`~/.chatarch/playwright`、`~/.chatarch/chattea`、`~/.chatarch/discourse`、`~/.chatarch/zulip`、`~/.chatarch/chatdata`、`~/.chatarch/nginx` 和 `~/.chatarch/crs/local`。`cursor-agent` 额外注册 ChatEnv `CursorAgent` profile,`discourse`/`zulip` 注册管理员凭据 profile,并支持 `-e/--env` 从 env 文件或 profile 快速配置;显式选择 profile 时不会混入进程环境中的其他账号凭据。CLI 树由 ChatStyle 的注册表渲染。更完整的能力边界见 `docs/capability-map.md`。
79
86
 
80
87
  ## 开发
81
88
 
@@ -84,6 +91,9 @@ python -m pytest -q
84
91
  python -m build
85
92
  python -m twine check dist/*
86
93
  mkdocs build --strict
94
+ chatup --version
95
+ chatup --tree
96
+ chatup --tree-brief
87
97
  ```
88
98
 
89
99
  更多使用说明见文档站的 [快速开始](https://arch.gh.wzhecnu.cn/ChatUp/quickstart/)、[命令参考](https://arch.gh.wzhecnu.cn/ChatUp/commands/) 和 [CLI 能力地图](https://arch.gh.wzhecnu.cn/ChatUp/capability-map/)。
@@ -21,6 +21,9 @@ ChatUp 是 ChatArch 的独立环境与工具安装 CLI。它承接原来 `chatto
21
21
 
22
22
  ```bash
23
23
  chatup --help
24
+ chatup --version
25
+ chatup --tree
26
+ chatup --tree-brief
24
27
  chatup doctor
25
28
  chatup uv
26
29
  chatup chrome-for-testing install --channel stable -I
@@ -49,7 +52,7 @@ chatup crs --install-dir ~/.chatarch/crs/local --port 12392 --redis-port 6379
49
52
  | 本地服务安装 | `gitea`、`discourse`、`zulip`、`mysql`、`nginx`、`crs` |
50
53
  | Agent 工具链 | `cc-connect`、`claude`、`codex`、`cursor-agent`、`opencode`、`hermes`、`lark-cli` |
51
54
 
52
- 所有新增安装项默认目录都收敛到 `~/.chatarch/...`,例如 `~/.chatarch/chrome-for-testing`、`~/.chatarch/chromedriver`、`~/.chatarch/playwright`、`~/.chatarch/chattea`、`~/.chatarch/discourse`、`~/.chatarch/zulip`、`~/.chatarch/chatdata`、`~/.chatarch/nginx` 和 `~/.chatarch/crs/local`。`cursor-agent` 额外注册 ChatEnv `CursorAgent` profile,`discourse`/`zulip` 注册管理员凭据 profile,并支持 `-e/--env` 从 env 文件或 profile 快速配置。更完整的能力边界见 `docs/capability-map.md`。
55
+ 所有新增安装项默认目录都收敛到 `~/.chatarch/...`,例如 `~/.chatarch/chrome-for-testing`、`~/.chatarch/chromedriver`、`~/.chatarch/playwright`、`~/.chatarch/chattea`、`~/.chatarch/discourse`、`~/.chatarch/zulip`、`~/.chatarch/chatdata`、`~/.chatarch/nginx` 和 `~/.chatarch/crs/local`。`cursor-agent` 额外注册 ChatEnv `CursorAgent` profile,`discourse`/`zulip` 注册管理员凭据 profile,并支持 `-e/--env` 从 env 文件或 profile 快速配置;显式选择 profile 时不会混入进程环境中的其他账号凭据。CLI 树由 ChatStyle 的注册表渲染。更完整的能力边界见 `docs/capability-map.md`。
53
56
 
54
57
  ## 开发
55
58
 
@@ -58,6 +61,9 @@ python -m pytest -q
58
61
  python -m build
59
62
  python -m twine check dist/*
60
63
  mkdocs build --strict
64
+ chatup --version
65
+ chatup --tree
66
+ chatup --tree-brief
61
67
  ```
62
68
 
63
69
  更多使用说明见文档站的 [快速开始](https://arch.gh.wzhecnu.cn/ChatUp/quickstart/)、[命令参考](https://arch.gh.wzhecnu.cn/ChatUp/commands/) 和 [CLI 能力地图](https://arch.gh.wzhecnu.cn/ChatUp/capability-map/)。
@@ -10,9 +10,9 @@ readme = "README.md"
10
10
  requires-python = ">=3.10"
11
11
  license = "MIT"
12
12
  dependencies = [
13
- "click>=8.0",
14
- "chatstyle>=0.1.1,<0.2.0",
15
- "chatenv>=0.2.0,<0.3.0",
13
+ "click>=8.0,<9.0",
14
+ "chatstyle>=0.2.0,<0.3.0",
15
+ "chatenv>=0.2.11,<0.3.0",
16
16
  "colorama",
17
17
  ]
18
18
  authors = [{name = "rexwzh", email = "1073853456@qq.com"}]
@@ -28,11 +28,16 @@ Source = "https://github.com/ChatArch/ChatUp"
28
28
  Issues = "https://github.com/ChatArch/ChatUp/issues"
29
29
 
30
30
  [project.optional-dependencies]
31
+ dev = [
32
+ "build",
33
+ "pytest",
34
+ "twine",
35
+ ]
31
36
  docs = [
32
37
  "mkdocs>=1.6,<2.0",
33
- "mkdocs-material>=9.5,<10.0",
34
- "mkdocs-static-i18n>=1.3,<2.0",
35
- "mike>=2.1,<3.0",
38
+ "mkdocs-material>=9.5,<9.7",
39
+ "mkdocs-static-i18n>=1.2,<2.0",
40
+ "mike>=2.0,<3.0",
36
41
  ]
37
42
 
38
43
  [project.scripts]
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.11"
5
+ __version__ = "0.2.13"
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ import click
4
+ from chatstyle import add_tree_option
5
+
6
+ from chatup import __version__
7
+ from chatup.commands.chrome_for_testing import cli as chrome_for_testing_cli
8
+ from chatup.commands.chromedriver import cli as chromedriver_cli
9
+ from chatup.commands.playwright import cli as playwright_cli
10
+ from chatup.setup.cli import register_setup_commands
11
+
12
+
13
+ @click.group(
14
+ name="chatup",
15
+ context_settings={"help_option_names": ["-h", "--help"]},
16
+ invoke_without_command=True,
17
+ no_args_is_help=True,
18
+ )
19
+ @click.version_option(__version__, prog_name="chatup")
20
+ @add_tree_option(renderer_options={"root_name": "chatup"})
21
+ def main() -> None:
22
+ """ChatUp CLI for ChatArch bootstrap workflows."""
23
+
24
+
25
+ @main.command(name="doctor")
26
+ def doctor() -> None:
27
+ """Check that ChatUp is installed and callable."""
28
+ click.echo(f"chatup {__version__} ok")
29
+
30
+
31
+ register_setup_commands(main)
32
+ main.add_command(chrome_for_testing_cli)
33
+ main.add_command(chromedriver_cli)
34
+ main.add_command(playwright_cli)
35
+
36
+
37
+ if __name__ == "__main__":
38
+ main()
@@ -7,6 +7,7 @@ import click
7
7
  from chatenv.configs import OpenAIConfig
8
8
  from chatenv.fields import BaseEnvConfig
9
9
  from chatenv.source_chain import split_config_sources
10
+ from chatenv.store import EnvStore
10
11
  from chatup.const import CHATARCH_ENV_DIR, CHATARCH_ENV_FILE
11
12
  from chatup.interaction import (
12
13
  BACK_VALUE,
@@ -25,7 +26,7 @@ from chatup.setup.nodejs import (
25
26
  )
26
27
  from chatup.utils.custom_logger import setup_logger
27
28
 
28
- DEFAULT_MODEL = "gpt-5.4"
29
+ DEFAULT_MODEL = "gpt-5.5"
29
30
  DEFAULT_BASE_URL = "https://api.openai.com/v1"
30
31
  DEFAULT_AUTH_METHOD = "apikey"
31
32
  logger = setup_logger("setup_codex")
@@ -264,17 +265,24 @@ def _resolve_openai_env_path(env_ref: str) -> Path:
264
265
  )
265
266
 
266
267
 
268
+ def _openai_values_from_mapping(values: dict[str, str]) -> dict[str, str | None]:
269
+ return {
270
+ "openai_api_key": values.get("OPENAI_API_KEY"),
271
+ "base_url": values.get("OPENAI_API_BASE"),
272
+ "model": values.get("OPENAI_API_MODEL"),
273
+ }
274
+
275
+
267
276
  def _load_openai_values_from_env_ref(env_ref: str) -> dict[str, str | None]:
268
- current_values = _snapshot_openai_values()
269
- try:
270
- env_path = _resolve_openai_env_path(env_ref)
271
- BaseEnvConfig.load_all_with_override(
272
- CHATARCH_ENV_DIR,
273
- override_env_file=env_path,
274
- )
275
- return _snapshot_openai_values()
276
- finally:
277
- _restore_openai_values(current_values)
277
+ """Load only the explicitly selected OpenAI env file/profile.
278
+
279
+ `chatup codex -e PROFILE` is an account-selection boundary. Do not merge
280
+ the selected profile with the active/default ChatEnv profile or process
281
+ environment, otherwise a profile missing `OPENAI_API_KEY` can silently write
282
+ a different account's key into `~/.codex/auth.json`.
283
+ """
284
+ env_path = _resolve_openai_env_path(env_ref)
285
+ return _openai_values_from_mapping(EnvStore(CHATARCH_ENV_DIR).load_path(env_path))
278
286
 
279
287
 
280
288
  def setup_codex(
@@ -302,13 +310,19 @@ def setup_codex(
302
310
  if isinstance(model, str) and not model.strip():
303
311
  model = None
304
312
 
305
- api_key = resolve_value(
306
- api_key,
307
- env_config.get("openai_api_key"),
308
- existing_api_key,
309
- env_values.get("OPENAI_API_KEY"),
310
- typed_env_values.get("OPENAI_API_KEY"),
311
- )
313
+ if env_ref:
314
+ api_key = resolve_value(
315
+ api_key,
316
+ env_config.get("openai_api_key"),
317
+ )
318
+ else:
319
+ api_key = resolve_value(
320
+ api_key,
321
+ env_config.get("openai_api_key"),
322
+ existing_api_key,
323
+ env_values.get("OPENAI_API_KEY"),
324
+ typed_env_values.get("OPENAI_API_KEY"),
325
+ )
312
326
  missing_required = not api_key
313
327
  has_existing_config = any(
314
328
  value for key, value in existing.items() if key != "openai_api_key"
@@ -421,22 +435,34 @@ def setup_codex(
421
435
  click.echo(result.stderr.strip(), err=True)
422
436
  raise click.Abort()
423
437
 
424
- base_url = resolve_value(
425
- base_url,
426
- env_config.get("base_url"),
427
- existing.get("base_url"),
428
- env_values.get("OPENAI_API_BASE"),
429
- typed_env_values.get("OPENAI_API_BASE"),
430
- DEFAULT_BASE_URL,
431
- )
432
- model = resolve_value(
433
- model,
434
- env_config.get("model"),
435
- existing.get("model"),
436
- env_values.get("OPENAI_API_MODEL"),
437
- typed_env_values.get("OPENAI_API_MODEL"),
438
- DEFAULT_MODEL,
439
- )
438
+ if env_ref:
439
+ base_url = resolve_value(
440
+ base_url,
441
+ env_config.get("base_url"),
442
+ DEFAULT_BASE_URL,
443
+ )
444
+ model = resolve_value(
445
+ model,
446
+ env_config.get("model"),
447
+ DEFAULT_MODEL,
448
+ )
449
+ else:
450
+ base_url = resolve_value(
451
+ base_url,
452
+ env_config.get("base_url"),
453
+ existing.get("base_url"),
454
+ env_values.get("OPENAI_API_BASE"),
455
+ typed_env_values.get("OPENAI_API_BASE"),
456
+ DEFAULT_BASE_URL,
457
+ )
458
+ model = resolve_value(
459
+ model,
460
+ env_config.get("model"),
461
+ existing.get("model"),
462
+ env_values.get("OPENAI_API_MODEL"),
463
+ typed_env_values.get("OPENAI_API_MODEL"),
464
+ DEFAULT_MODEL,
465
+ )
440
466
 
441
467
  codex_dir.mkdir(parents=True, exist_ok=True)
442
468
 
@@ -29,16 +29,19 @@ def load_chatenv_values(
29
29
  ) -> tuple[dict[str, str], str]:
30
30
  """Load service setup values without printing secrets.
31
31
 
32
- Precedence is current environment > explicit env file/profile values.
33
32
  The env_ref option mirrors Cursor Agent's `-e/--env`: an existing path is
34
33
  parsed as a dotenv-style file; otherwise it is treated as a ChatEnv profile.
34
+ Explicit files/profiles are isolated from process-environment credentials;
35
+ the process environment is used only when no explicit source is selected.
35
36
  """
36
37
 
37
38
  values: dict[str, str] = {}
38
39
  source = "environment"
39
40
  store = EnvStore(get_paths().envs_dir)
41
+ explicit_source = False
40
42
 
41
43
  if env_ref:
44
+ explicit_source = True
42
45
  candidate = Path(env_ref).expanduser()
43
46
  if candidate.exists():
44
47
  values.update(parse_env_file(candidate))
@@ -48,14 +51,15 @@ def load_chatenv_values(
48
51
  source = f"ChatEnv profile {env_ref}"
49
52
 
50
53
  if env_profile:
54
+ explicit_source = True
51
55
  values.update(store.load_profile(config_cls, env_profile))
52
56
  source = f"ChatEnv profile {env_profile}"
53
57
 
54
- for key in config_cls.get_fields():
55
- env_value = os.getenv(key)
56
- if env_value:
57
- values[key] = env_value
58
- source = "environment"
58
+ if not explicit_source:
59
+ for key in config_cls.get_fields():
60
+ env_value = os.getenv(key)
61
+ if env_value:
62
+ values[key] = env_value
59
63
  return values, source
60
64
 
61
65
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chatup
3
- Version: 0.2.11
3
+ Version: 0.2.13
4
4
  Summary: ChatArch setup CLI extracted from ChatTool
5
5
  Author-email: rexwzh <1073853456@qq.com>
6
6
  License-Expression: MIT
@@ -13,15 +13,19 @@ Classifier: Operating System :: OS Independent
13
13
  Requires-Python: >=3.10
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
- Requires-Dist: click>=8.0
17
- Requires-Dist: chatstyle<0.2.0,>=0.1.1
18
- Requires-Dist: chatenv<0.3.0,>=0.2.0
16
+ Requires-Dist: click<9.0,>=8.0
17
+ Requires-Dist: chatstyle<0.3.0,>=0.2.0
18
+ Requires-Dist: chatenv<0.3.0,>=0.2.11
19
19
  Requires-Dist: colorama
20
+ Provides-Extra: dev
21
+ Requires-Dist: build; extra == "dev"
22
+ Requires-Dist: pytest; extra == "dev"
23
+ Requires-Dist: twine; extra == "dev"
20
24
  Provides-Extra: docs
21
25
  Requires-Dist: mkdocs<2.0,>=1.6; extra == "docs"
22
- Requires-Dist: mkdocs-material<10.0,>=9.5; extra == "docs"
23
- Requires-Dist: mkdocs-static-i18n<2.0,>=1.3; extra == "docs"
24
- Requires-Dist: mike<3.0,>=2.1; extra == "docs"
26
+ Requires-Dist: mkdocs-material<9.7,>=9.5; extra == "docs"
27
+ Requires-Dist: mkdocs-static-i18n<2.0,>=1.2; extra == "docs"
28
+ Requires-Dist: mike<3.0,>=2.0; extra == "docs"
25
29
  Dynamic: license-file
26
30
 
27
31
  # ChatUp
@@ -47,6 +51,9 @@ ChatUp 是 ChatArch 的独立环境与工具安装 CLI。它承接原来 `chatto
47
51
 
48
52
  ```bash
49
53
  chatup --help
54
+ chatup --version
55
+ chatup --tree
56
+ chatup --tree-brief
50
57
  chatup doctor
51
58
  chatup uv
52
59
  chatup chrome-for-testing install --channel stable -I
@@ -75,7 +82,7 @@ chatup crs --install-dir ~/.chatarch/crs/local --port 12392 --redis-port 6379
75
82
  | 本地服务安装 | `gitea`、`discourse`、`zulip`、`mysql`、`nginx`、`crs` |
76
83
  | Agent 工具链 | `cc-connect`、`claude`、`codex`、`cursor-agent`、`opencode`、`hermes`、`lark-cli` |
77
84
 
78
- 所有新增安装项默认目录都收敛到 `~/.chatarch/...`,例如 `~/.chatarch/chrome-for-testing`、`~/.chatarch/chromedriver`、`~/.chatarch/playwright`、`~/.chatarch/chattea`、`~/.chatarch/discourse`、`~/.chatarch/zulip`、`~/.chatarch/chatdata`、`~/.chatarch/nginx` 和 `~/.chatarch/crs/local`。`cursor-agent` 额外注册 ChatEnv `CursorAgent` profile,`discourse`/`zulip` 注册管理员凭据 profile,并支持 `-e/--env` 从 env 文件或 profile 快速配置。更完整的能力边界见 `docs/capability-map.md`。
85
+ 所有新增安装项默认目录都收敛到 `~/.chatarch/...`,例如 `~/.chatarch/chrome-for-testing`、`~/.chatarch/chromedriver`、`~/.chatarch/playwright`、`~/.chatarch/chattea`、`~/.chatarch/discourse`、`~/.chatarch/zulip`、`~/.chatarch/chatdata`、`~/.chatarch/nginx` 和 `~/.chatarch/crs/local`。`cursor-agent` 额外注册 ChatEnv `CursorAgent` profile,`discourse`/`zulip` 注册管理员凭据 profile,并支持 `-e/--env` 从 env 文件或 profile 快速配置;显式选择 profile 时不会混入进程环境中的其他账号凭据。CLI 树由 ChatStyle 的注册表渲染。更完整的能力边界见 `docs/capability-map.md`。
79
86
 
80
87
  ## 开发
81
88
 
@@ -84,6 +91,9 @@ python -m pytest -q
84
91
  python -m build
85
92
  python -m twine check dist/*
86
93
  mkdocs build --strict
94
+ chatup --version
95
+ chatup --tree
96
+ chatup --tree-brief
87
97
  ```
88
98
 
89
99
  更多使用说明见文档站的 [快速开始](https://arch.gh.wzhecnu.cn/ChatUp/quickstart/)、[命令参考](https://arch.gh.wzhecnu.cn/ChatUp/commands/) 和 [CLI 能力地图](https://arch.gh.wzhecnu.cn/ChatUp/capability-map/)。
@@ -73,6 +73,7 @@ tests/test_browser_backend_cli.py
73
73
  tests/test_chrome_for_testing.py
74
74
  tests/test_chromedriver.py
75
75
  tests/test_cli_tree_docs.py
76
+ tests/test_codex_setup.py
76
77
  tests/test_crs_setup.py
77
78
  tests/test_cursor_agent_setup.py
78
79
  tests/test_playwright.py
@@ -0,0 +1,15 @@
1
+ click<9.0,>=8.0
2
+ chatstyle<0.3.0,>=0.2.0
3
+ chatenv<0.3.0,>=0.2.11
4
+ colorama
5
+
6
+ [dev]
7
+ build
8
+ pytest
9
+ twine
10
+
11
+ [docs]
12
+ mkdocs<2.0,>=1.6
13
+ mkdocs-material<9.7,>=9.5
14
+ mkdocs-static-i18n<2.0,>=1.2
15
+ mike<3.0,>=2.0
@@ -58,6 +58,8 @@ def test_cli_tree_is_in_mkdocs_nav_and_backend_contracts_are_complete():
58
58
  "remove",
59
59
  "gc",
60
60
  "--version",
61
+ "--tree",
62
+ "--tree-brief",
61
63
  "--channel",
62
64
  "--home",
63
65
  "--platform",
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ import click
6
+ import pytest
7
+
8
+
9
+ def _write_env(path, values):
10
+ path.parent.mkdir(parents=True, exist_ok=True)
11
+ lines = [f"{key}='{value}'" for key, value in values.items()]
12
+ path.write_text("\n".join(lines) + "\n", encoding="utf-8")
13
+
14
+
15
+ def test_codex_env_profile_does_not_backfill_missing_key_from_active_or_process_env(
16
+ tmp_path, monkeypatch
17
+ ):
18
+ import chatup.setup.codex as codex_setup
19
+
20
+ home = tmp_path / "home"
21
+ envs_dir = tmp_path / "chatarch" / "envs"
22
+ openai_dir = envs_dir / "OpenAI"
23
+ monkeypatch.setenv("HOME", str(home))
24
+ monkeypatch.setenv("OPENAI_API_KEY", "process-key-should-not-be-used")
25
+ monkeypatch.setenv("OPENAI_API_BASE", "https://process.example/v1")
26
+ monkeypatch.setenv("OPENAI_API_MODEL", "process-model")
27
+ monkeypatch.setattr(codex_setup, "CHATARCH_ENV_DIR", envs_dir)
28
+ monkeypatch.setattr(codex_setup, "CHATARCH_ENV_FILE", envs_dir / ".env")
29
+ monkeypatch.setattr(codex_setup, "ensure_nodejs_requirement", lambda **kwargs: None)
30
+ monkeypatch.setattr(
31
+ codex_setup,
32
+ "should_install_global_npm_package",
33
+ lambda *args, **kwargs: False,
34
+ )
35
+
36
+ _write_env(
37
+ openai_dir / ".env",
38
+ {
39
+ "OPENAI_API_KEY": "active-key-should-not-be-used",
40
+ "OPENAI_API_BASE": "https://active.example/v1",
41
+ "OPENAI_API_MODEL": "active-model",
42
+ },
43
+ )
44
+ _write_env(
45
+ openai_dir / "apple.env",
46
+ {
47
+ "OPENAI_API_BASE": "https://crs.example/openai/v1",
48
+ "OPENAI_API_MODEL": "gpt-5.5",
49
+ },
50
+ )
51
+
52
+ with pytest.raises(click.Abort):
53
+ codex_setup.setup_codex(env_ref="apple", interactive=False)
54
+
55
+ auth_path = home / ".codex" / "auth.json"
56
+ if auth_path.exists():
57
+ auth_data = json.loads(auth_path.read_text(encoding="utf-8"))
58
+ assert auth_data.get("OPENAI_API_KEY") not in {
59
+ "active-key-should-not-be-used",
60
+ "process-key-should-not-be-used",
61
+ }
62
+
63
+
64
+ def test_codex_env_profile_writes_exact_named_profile_values(tmp_path, monkeypatch):
65
+ import chatup.setup.codex as codex_setup
66
+
67
+ home = tmp_path / "home"
68
+ envs_dir = tmp_path / "chatarch" / "envs"
69
+ openai_dir = envs_dir / "OpenAI"
70
+ monkeypatch.setenv("HOME", str(home))
71
+ monkeypatch.setenv("OPENAI_API_KEY", "process-key-should-not-be-used")
72
+ monkeypatch.setattr(codex_setup, "CHATARCH_ENV_DIR", envs_dir)
73
+ monkeypatch.setattr(codex_setup, "CHATARCH_ENV_FILE", envs_dir / ".env")
74
+ monkeypatch.setattr(codex_setup, "ensure_nodejs_requirement", lambda **kwargs: None)
75
+ monkeypatch.setattr(
76
+ codex_setup,
77
+ "should_install_global_npm_package",
78
+ lambda *args, **kwargs: False,
79
+ )
80
+
81
+ _write_env(
82
+ openai_dir / ".env",
83
+ {
84
+ "OPENAI_API_KEY": "active-key-should-not-be-used",
85
+ "OPENAI_API_BASE": "https://active.example/v1",
86
+ "OPENAI_API_MODEL": "active-model",
87
+ },
88
+ )
89
+ _write_env(
90
+ openai_dir / "apple.env",
91
+ {
92
+ "OPENAI_API_KEY": "apple-key",
93
+ "OPENAI_API_BASE": "https://crs.example/openai/v1",
94
+ "OPENAI_API_MODEL": "gpt-5.5",
95
+ },
96
+ )
97
+
98
+ codex_setup.setup_codex(env_ref="apple", interactive=False)
99
+
100
+ auth_path = home / ".codex" / "auth.json"
101
+ auth_data = json.loads(auth_path.read_text(encoding="utf-8"))
102
+ assert auth_data == {"OPENAI_API_KEY": "apple-key"}
103
+ assert auth_path.stat().st_mode & 0o777 == 0o600
104
+
105
+ config_text = (home / ".codex" / "config.toml").read_text(encoding="utf-8")
106
+ assert 'model = "gpt-5.5"' in config_text
107
+ assert 'base_url = "https://crs.example/openai/v1"' in config_text
108
+ assert "active-key-should-not-be-used" not in config_text
109
+ assert "process-key-should-not-be-used" not in config_text
110
+
111
+
112
+ def test_codex_env_dotenv_ref_loads_active_openai_profile(tmp_path, monkeypatch):
113
+ import chatup.setup.codex as codex_setup
114
+
115
+ home = tmp_path / "home"
116
+ envs_dir = tmp_path / "chatarch" / "envs"
117
+ openai_dir = envs_dir / "OpenAI"
118
+ monkeypatch.setenv("HOME", str(home))
119
+ monkeypatch.setenv("OPENAI_API_KEY", "process-key-should-not-be-used")
120
+ monkeypatch.setattr(codex_setup, "CHATARCH_ENV_DIR", envs_dir)
121
+ monkeypatch.setattr(codex_setup, "CHATARCH_ENV_FILE", envs_dir / ".env")
122
+ monkeypatch.setattr(codex_setup, "ensure_nodejs_requirement", lambda **kwargs: None)
123
+ monkeypatch.setattr(
124
+ codex_setup,
125
+ "should_install_global_npm_package",
126
+ lambda *args, **kwargs: False,
127
+ )
128
+ _write_env(
129
+ openai_dir / ".env",
130
+ {
131
+ "OPENAI_API_KEY": "active-key",
132
+ "OPENAI_API_BASE": "https://active.example/v1",
133
+ "OPENAI_API_MODEL": "gpt-5.5",
134
+ },
135
+ )
136
+
137
+ codex_setup.setup_codex(env_ref=".env", interactive=False)
138
+
139
+ auth_data = json.loads((home / ".codex" / "auth.json").read_text(encoding="utf-8"))
140
+ assert auth_data == {"OPENAI_API_KEY": "active-key"}
141
+ config_text = (home / ".codex" / "config.toml").read_text(encoding="utf-8")
142
+ assert 'model = "gpt-5.5"' in config_text
143
+ assert 'base_url = "https://active.example/v1"' in config_text
144
+ assert "process-key-should-not-be-used" not in config_text
145
+
146
+
147
+ def test_codex_default_model_matches_shared_openai_default():
148
+ from chatenv.configs import OpenAIConfig
149
+ import chatup.setup.codex as codex_setup
150
+
151
+ assert codex_setup.DEFAULT_MODEL == OpenAIConfig.OPENAI_API_MODEL.default
@@ -11,6 +11,9 @@ def test_publish_workflow_matches_active_any_environment_publisher():
11
11
  assert "id-token: write" in workflow
12
12
  assert "pypa/gh-action-pypi-publish@release/v1" in workflow
13
13
  assert "environment: pypi" not in workflow
14
+ assert "git merge-base --is-ancestor" in workflow
15
+ assert "refs/remotes/origin/main" in workflow
16
+ assert "workflow_dispatch" not in workflow
14
17
  assert "Release workflow must run from a tag ref" in workflow
15
18
  assert "GITHUB_REF_TYPE" in workflow
16
19
  assert "GITHUB_REF_NAME" in workflow
@@ -21,8 +21,9 @@ def test_chatup_depends_on_chatenv_and_registers_cursor_agent_config_provider():
21
21
  data = _pyproject()
22
22
 
23
23
  assert data["project"]["requires-python"] == ">=3.10"
24
- assert "chatstyle>=0.1.1,<0.2.0" in data["project"]["dependencies"]
25
- assert "chatenv>=0.2.0,<0.3.0" in data["project"]["dependencies"]
24
+ assert "click>=8.0,<9.0" in data["project"]["dependencies"]
25
+ assert "chatstyle>=0.2.0,<0.3.0" in data["project"]["dependencies"]
26
+ assert "chatenv>=0.2.11,<0.3.0" in data["project"]["dependencies"]
26
27
  entry_points = data["project"]["entry-points"]["chatenv.configs"]
27
28
  assert entry_points["cursor-agent"] == "chatup.config:CursorAgentConfig"
28
29
  assert entry_points["discourse"] == "chatup.config:DiscourseAdminConfig"
@@ -82,4 +83,5 @@ def test_project_metadata_points_to_chatup_docs_site():
82
83
 
83
84
  assert data["project"]["urls"]["Documentation"] == "https://arch.gh.wzhecnu.cn/ChatUp/"
84
85
  assert data["project"]["urls"]["Source"] == "https://github.com/ChatArch/ChatUp"
85
- assert "mkdocs-static-i18n>=1.3,<2.0" in data["project"]["optional-dependencies"]["docs"]
86
+ assert "mkdocs-material>=9.5,<9.7" in data["project"]["optional-dependencies"]["docs"]
87
+ assert "mkdocs-static-i18n>=1.2,<2.0" in data["project"]["optional-dependencies"]["docs"]
@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from click.testing import CliRunner
4
+ from chatenv.paths import get_paths
5
+ from chatenv.store import EnvStore
4
6
 
5
7
  from chatup.cli import main
6
8
  from chatup.config import DiscourseAdminConfig, ZulipAdminConfig
7
9
  from chatup.setup.discourse import setup_discourse
10
+ from chatup.setup.service_credentials import load_chatenv_values
8
11
  from chatup.setup.zulip import setup_zulip
9
12
 
10
13
 
@@ -36,6 +39,43 @@ def test_zulip_admin_config_schema_supports_email_and_mail_alias():
36
39
  ZulipAdminConfig.test()
37
40
 
38
41
 
42
+ def test_explicit_chatenv_profile_isolated_from_process_credentials(tmp_path, monkeypatch):
43
+ monkeypatch.setenv("CHATARCH_HOME", str(tmp_path / "chatarch"))
44
+ monkeypatch.setenv("DISCOURSE_ADMIN_USERNAME", "process-admin")
45
+ monkeypatch.setenv("DISCOURSE_ADMIN_EMAIL", "process@example.com")
46
+ monkeypatch.setenv("DISCOURSE_ADMIN_PASSWORD", "process-password")
47
+ EnvStore(get_paths().envs_dir).save_profile(
48
+ DiscourseAdminConfig,
49
+ "selected",
50
+ {
51
+ "DISCOURSE_ADMIN_USERNAME": "profile-admin",
52
+ "DISCOURSE_ADMIN_PASSWORD": "profile-password",
53
+ },
54
+ )
55
+
56
+ values, source = load_chatenv_values(DiscourseAdminConfig, env_profile="selected")
57
+
58
+ assert source == "ChatEnv profile selected"
59
+ assert values["DISCOURSE_ADMIN_USERNAME"] == "profile-admin"
60
+ assert values["DISCOURSE_ADMIN_PASSWORD"] == "profile-password"
61
+ assert "DISCOURSE_ADMIN_EMAIL" not in values
62
+
63
+
64
+ def test_process_credentials_are_used_without_explicit_source(monkeypatch):
65
+ monkeypatch.setenv("DISCOURSE_ADMIN_USERNAME", "process-admin")
66
+ monkeypatch.setenv("DISCOURSE_ADMIN_EMAIL", "process@example.com")
67
+ monkeypatch.setenv("DISCOURSE_ADMIN_PASSWORD", "process-password")
68
+
69
+ values, source = load_chatenv_values(DiscourseAdminConfig)
70
+
71
+ assert source == "environment"
72
+ assert values == {
73
+ "DISCOURSE_ADMIN_USERNAME": "process-admin",
74
+ "DISCOURSE_ADMIN_EMAIL": "process@example.com",
75
+ "DISCOURSE_ADMIN_PASSWORD": "process-password",
76
+ }
77
+
78
+
39
79
  def test_discourse_setup_writes_admin_env_and_chatarc_internal_app_yml(tmp_path):
40
80
  env = tmp_path / "discourse.env"
41
81
  env.write_text(
@@ -0,0 +1,64 @@
1
+ from click.testing import CliRunner
2
+
3
+ from chatup import __version__
4
+ from chatup.cli import main
5
+
6
+
7
+ def test_version_present():
8
+ assert __version__ == "0.2.13"
9
+
10
+
11
+ def test_cli_help():
12
+ result = CliRunner().invoke(main, ["--help"])
13
+
14
+ assert result.exit_code == 0
15
+ assert main.name == "chatup"
16
+ assert "ChatUp CLI" in result.output
17
+ assert "doctor" in result.output
18
+ assert "--tree" in result.output
19
+ assert "--tree-brief" in result.output
20
+
21
+
22
+ def test_cli_tree_lists_registered_command_surface_with_signatures():
23
+ result = CliRunner().invoke(main, ["--tree"])
24
+
25
+ assert result.exit_code == 0, result.output
26
+ lines = result.output.splitlines()
27
+ assert lines[0] == "chatup"
28
+ assert lines.count("chatup") == 1
29
+ assert all(" # " in line for line in lines[1:])
30
+ assert "--version" in result.output
31
+ assert "--tree" in result.output
32
+ assert "--tree-brief" in result.output
33
+ for command in main.commands:
34
+ assert command in result.output
35
+ for leaf in ("install", "list", "show", "path", "doctor", "remove", "gc"):
36
+ assert leaf in result.output
37
+ assert "[--match-browser MATCH-BROWSER]" in result.output
38
+ assert "[--minimum-age-hours MINIMUM-AGE-HOURS]" in result.output
39
+ assert "Remove one exact managed installation." in result.output
40
+ assert "Install/configure Cursor Agent CLI auth and config files." in result.output
41
+
42
+
43
+ def test_cli_tree_brief_keeps_registered_nodes_and_omits_signatures():
44
+ result = CliRunner().invoke(main, ["--tree-brief"])
45
+
46
+ assert result.exit_code == 0, result.output
47
+ lines = result.output.splitlines()
48
+ assert lines[0] == "chatup"
49
+ assert lines.count("chatup") == 1
50
+ assert all(" # " in line for line in lines[1:])
51
+ for command in main.commands:
52
+ assert command in result.output
53
+ for expected in ("--version", "--tree", "--tree-brief", "install", "remove", "gc"):
54
+ assert expected in result.output
55
+ assert "[--match-browser MATCH-BROWSER]" not in result.output
56
+ assert "[--minimum-age-hours MINIMUM-AGE-HOURS]" not in result.output
57
+ assert "[VERSION]" not in result.output
58
+
59
+
60
+ def test_cli_doctor():
61
+ result = CliRunner().invoke(main, ["doctor"])
62
+
63
+ assert result.exit_code == 0
64
+ assert "chatup 0.2.13 ok" in result.output
@@ -1,111 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import inspect
4
-
5
- import click
6
-
7
- from chatup import __version__
8
- from chatup.commands.chrome_for_testing import cli as chrome_for_testing_cli
9
- from chatup.commands.chromedriver import cli as chromedriver_cli
10
- from chatup.commands.playwright import cli as playwright_cli
11
- from chatup.setup.cli import register_setup_commands
12
-
13
-
14
- def _purpose(command: click.Command) -> str:
15
- text = command.short_help or inspect.getdoc(command.callback) or ""
16
- return " ".join(text.strip().split()).rstrip(".")
17
-
18
-
19
- def _parameter_piece(parameter: click.Parameter) -> str | None:
20
- if getattr(parameter, "hidden", False) or parameter.name == "help":
21
- return None
22
- if isinstance(parameter, click.Argument):
23
- piece = parameter.name.upper().replace("_", "-")
24
- if not parameter.required:
25
- piece = f"[{piece}]"
26
- if parameter.nargs == -1:
27
- piece = f"{piece}..."
28
- return piece
29
- if not isinstance(parameter, click.Option):
30
- return None
31
- option_names = [name for name in (*parameter.opts, *parameter.secondary_opts) if name.startswith("--")]
32
- if not option_names:
33
- option_names = [name for name in (*parameter.opts, *parameter.secondary_opts) if name.startswith("-")]
34
- if not option_names:
35
- return None
36
- if parameter.is_flag or parameter.flag_value is not None:
37
- piece = "/".join(option_names)
38
- else:
39
- metavar = parameter.metavar or parameter.name.upper().replace("_", "-")
40
- piece = f"{'/'.join(option_names)} {metavar}"
41
- if not parameter.required:
42
- piece = f"[{piece}]"
43
- return piece
44
-
45
-
46
- def _command_signature(name: str, command: click.Command) -> str:
47
- pieces = [piece for piece in (_parameter_piece(parameter) for parameter in command.params) if piece]
48
- return " ".join([name, *pieces])
49
-
50
-
51
- def _render_command_tree(command: click.Command, name: str, prefix: str, is_last: bool, lines: list[str]) -> None:
52
- connector = "└── " if is_last else "├── "
53
- line = f"{prefix}{connector}{_command_signature(name, command)}"
54
- purpose = _purpose(command)
55
- if purpose:
56
- line = f"{line} # {purpose}"
57
- lines.append(line)
58
- if not isinstance(command, click.Group):
59
- return
60
- children = [(child_name, child) for child_name, child in command.commands.items() if not child.hidden]
61
- child_prefix = prefix + (" " if is_last else "│ ")
62
- for index, (child_name, child) in enumerate(children):
63
- _render_command_tree(child, child_name, child_prefix, index == len(children) - 1, lines)
64
-
65
-
66
- def _render_cli_tree(root: click.Group) -> str:
67
- children = [(name, command) for name, command in root.commands.items() if not command.hidden]
68
- lines = [f"chatup # {_purpose(root)}"]
69
- root_options = [
70
- ("--help", "Show help for the current command."),
71
- ("--version", "Show package version."),
72
- ("--tree", "Print the registered CLI tree."),
73
- ]
74
- for index, (option, purpose) in enumerate(root_options):
75
- is_last = not children and index == len(root_options) - 1
76
- lines.append(f"{'└──' if is_last else '├──'} {option} # {purpose}")
77
- for index, (child_name, child) in enumerate(children):
78
- _render_command_tree(child, child_name, "", index == len(children) - 1, lines)
79
- return "\n".join(lines)
80
-
81
-
82
- @click.group(
83
- context_settings={"help_option_names": ["-h", "--help"]},
84
- invoke_without_command=True,
85
- no_args_is_help=True,
86
- )
87
- @click.version_option(__version__, prog_name="chatup")
88
- @click.option("--tree", "show_tree", is_flag=True, is_eager=True, help="Print the registered CLI tree.")
89
- @click.pass_context
90
- def main(ctx: click.Context, show_tree: bool) -> None:
91
- """ChatUp CLI for ChatArch bootstrap workflows."""
92
-
93
- if show_tree:
94
- click.echo(_render_cli_tree(ctx.command))
95
- ctx.exit()
96
-
97
-
98
- @main.command(name="doctor")
99
- def doctor() -> None:
100
- """Check that ChatUp is installed and callable."""
101
- click.echo(f"chatup {__version__} ok")
102
-
103
-
104
- register_setup_commands(main)
105
- main.add_command(chrome_for_testing_cli)
106
- main.add_command(chromedriver_cli)
107
- main.add_command(playwright_cli)
108
-
109
-
110
- if __name__ == "__main__":
111
- main()
@@ -1,10 +0,0 @@
1
- click>=8.0
2
- chatstyle<0.2.0,>=0.1.1
3
- chatenv<0.3.0,>=0.2.0
4
- colorama
5
-
6
- [docs]
7
- mkdocs<2.0,>=1.6
8
- mkdocs-material<10.0,>=9.5
9
- mkdocs-static-i18n<2.0,>=1.3
10
- mike<3.0,>=2.1
@@ -1,45 +0,0 @@
1
- from click.testing import CliRunner
2
-
3
- from chatup import __version__
4
- from chatup.cli import main
5
-
6
-
7
- def test_version_present():
8
- assert __version__ == "0.2.11"
9
-
10
-
11
- def test_cli_help():
12
- result = CliRunner().invoke(main, ["--help"])
13
-
14
- assert result.exit_code == 0
15
- assert "ChatUp CLI" in result.output
16
- assert "doctor" in result.output
17
- assert "--tree" in result.output
18
-
19
-
20
- def test_cli_tree_lists_registered_command_surface():
21
- result = CliRunner().invoke(main, ["--tree"])
22
-
23
- assert result.exit_code == 0, result.output
24
- assert "chatup # ChatUp CLI for ChatArch bootstrap workflows" in result.output
25
- assert "├── --help # Show help for the current command." in result.output
26
- assert "├── --version # Show package version." in result.output
27
- assert "├── --tree # Print the registered CLI tree." in result.output
28
- for command in [
29
- "doctor",
30
- "workspace",
31
- "chrome-for-testing",
32
- "chromedriver",
33
- "playwright",
34
- ]:
35
- assert command in result.output
36
- assert "chrome-for-testing" in result.output
37
- assert "│ ├── install" in result.output
38
- assert "│ └── gc" in result.output
39
-
40
-
41
- def test_cli_doctor():
42
- result = CliRunner().invoke(main, ["doctor"])
43
-
44
- assert result.exit_code == 0
45
- assert "chatup 0.2.11 ok" in result.output
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
File without changes
File without changes