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.
- {chatup-0.2.11/src/chatup.egg-info → chatup-0.2.13}/PKG-INFO +18 -8
- {chatup-0.2.11 → chatup-0.2.13}/README.md +7 -1
- {chatup-0.2.11 → chatup-0.2.13}/pyproject.toml +11 -6
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/__init__.py +1 -1
- chatup-0.2.13/src/chatup/cli.py +38 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/codex.py +60 -34
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/service_credentials.py +10 -6
- {chatup-0.2.11 → chatup-0.2.13/src/chatup.egg-info}/PKG-INFO +18 -8
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup.egg-info/SOURCES.txt +1 -0
- chatup-0.2.13/src/chatup.egg-info/requires.txt +15 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_cli_tree_docs.py +2 -0
- chatup-0.2.13/tests/test_codex_setup.py +151 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_publish_workflow.py +3 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_pyproject_metadata.py +5 -3
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_service_installers.py +40 -0
- chatup-0.2.13/tests/test_version.py +64 -0
- chatup-0.2.11/src/chatup/cli.py +0 -111
- chatup-0.2.11/src/chatup.egg-info/requires.txt +0 -10
- chatup-0.2.11/tests/test_version.py +0 -45
- {chatup-0.2.11 → chatup-0.2.13}/LICENSE +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/setup.cfg +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/chrome_for_testing.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/chromedriver.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/commands/__init__.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/commands/chrome_for_testing.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/commands/chromedriver.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/commands/playwright.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/config.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/const.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/interaction/__init__.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/interaction/command_schema.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/interaction/policy.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/interaction/warnings.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/playwright.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/runtime/__init__.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/runtime/_managed_artifact.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/runtime/chrome_for_testing.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/runtime/chromedriver.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/runtime/playwright.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/__init__.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/assets/hermes/install.sh +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/assets/nvm.sh +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/cc_connect.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/claude.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/cli.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/crs.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/cursor_agent.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/discourse.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/docker.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/elements.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/frp.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/gitea.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/hermes.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/lark_cli.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/mode_prompt.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/mysql.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/nginx.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/nodejs.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/opencode.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/shell_rc.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/twikoo.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/uv.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/__init__.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/cli.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/core.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/options.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/render.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/en/AGENTS.md +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/en/discussion/README.md +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/en/projects/README.md +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/zh/AGENTS.md +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/zh/discussion/README.md +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/zh/projects/README.md +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/zsh.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/zulip.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/utils/__init__.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/utils/custom_logger.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup/utils/pathing.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup.egg-info/dependency_links.txt +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup.egg-info/entry_points.txt +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/src/chatup.egg-info/top_level.txt +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_browser_backend_cli.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_chrome_for_testing.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_chromedriver.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_crs_setup.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_cursor_agent_setup.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_playwright.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_playwright_cli.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_setup_cli.py +0 -0
- {chatup-0.2.11 → chatup-0.2.13}/tests/test_twikoo_setup.py +0 -0
- {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.
|
|
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
|
|
17
|
-
Requires-Dist: chatstyle<0.
|
|
18
|
-
Requires-Dist: chatenv<0.3.0,>=0.2.
|
|
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<
|
|
23
|
-
Requires-Dist: mkdocs-static-i18n<2.0,>=1.
|
|
24
|
-
Requires-Dist: mike<3.0,>=2.
|
|
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
|
|
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
|
|
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.
|
|
15
|
-
"chatenv>=0.2.
|
|
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,<
|
|
34
|
-
"mkdocs-static-i18n>=1.
|
|
35
|
-
"mike>=2.
|
|
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]
|
|
@@ -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.
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
306
|
-
api_key
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
425
|
-
base_url
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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.
|
|
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
|
|
17
|
-
Requires-Dist: chatstyle<0.
|
|
18
|
-
Requires-Dist: chatenv<0.3.0,>=0.2.
|
|
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<
|
|
23
|
-
Requires-Dist: mkdocs-static-i18n<2.0,>=1.
|
|
24
|
-
Requires-Dist: mike<3.0,>=2.
|
|
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
|
|
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/)。
|
|
@@ -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 "
|
|
25
|
-
assert "
|
|
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-
|
|
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
|
chatup-0.2.11/src/chatup/cli.py
DELETED
|
@@ -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,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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/en/discussion/README.md
RENAMED
|
File without changes
|
{chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/en/projects/README.md
RENAMED
|
File without changes
|
|
File without changes
|
{chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/zh/discussion/README.md
RENAMED
|
File without changes
|
{chatup-0.2.11 → chatup-0.2.13}/src/chatup/setup/workspace/templates/default/zh/projects/README.md
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
|
|
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
|