chatup 0.2.0__tar.gz → 0.2.1__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 (55) hide show
  1. {chatup-0.2.0/src/chatup.egg-info → chatup-0.2.1}/PKG-INFO +5 -2
  2. {chatup-0.2.0 → chatup-0.2.1}/README.md +3 -0
  3. {chatup-0.2.0 → chatup-0.2.1}/pyproject.toml +1 -1
  4. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/__init__.py +1 -1
  5. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/elements.py +36 -0
  6. chatup-0.2.1/src/chatup/setup/uv.py +195 -0
  7. {chatup-0.2.0 → chatup-0.2.1/src/chatup.egg-info}/PKG-INFO +5 -2
  8. {chatup-0.2.0 → chatup-0.2.1}/src/chatup.egg-info/SOURCES.txt +1 -0
  9. {chatup-0.2.0 → chatup-0.2.1}/tests/test_pyproject_metadata.py +1 -0
  10. chatup-0.2.1/tests/test_setup_cli.py +218 -0
  11. {chatup-0.2.0 → chatup-0.2.1}/tests/test_version.py +2 -2
  12. chatup-0.2.0/tests/test_setup_cli.py +0 -104
  13. {chatup-0.2.0 → chatup-0.2.1}/LICENSE +0 -0
  14. {chatup-0.2.0 → chatup-0.2.1}/setup.cfg +0 -0
  15. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/cli.py +0 -0
  16. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/const.py +0 -0
  17. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/interaction/__init__.py +0 -0
  18. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/interaction/command_schema.py +0 -0
  19. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/interaction/policy.py +0 -0
  20. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/interaction/warnings.py +0 -0
  21. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/__init__.py +0 -0
  22. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/assets/hermes/install.sh +0 -0
  23. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/assets/nvm.sh +0 -0
  24. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/cc_connect.py +0 -0
  25. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/chrome.py +0 -0
  26. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/claude.py +0 -0
  27. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/cli.py +0 -0
  28. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/codex.py +0 -0
  29. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/docker.py +0 -0
  30. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/frp.py +0 -0
  31. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/hermes.py +0 -0
  32. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/lark_cli.py +0 -0
  33. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/mode_prompt.py +0 -0
  34. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/nodejs.py +0 -0
  35. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/opencode.py +0 -0
  36. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/shell_rc.py +0 -0
  37. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/workspace/__init__.py +0 -0
  38. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/workspace/cli.py +0 -0
  39. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/workspace/core.py +0 -0
  40. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/workspace/options.py +0 -0
  41. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/workspace/render.py +0 -0
  42. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/workspace/templates/default/en/AGENTS.md +0 -0
  43. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/workspace/templates/default/en/projects/README.md +0 -0
  44. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/workspace/templates/default/zh/AGENTS.md +0 -0
  45. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/workspace/templates/default/zh/projects/README.md +0 -0
  46. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/workspace.py +0 -0
  47. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/setup/zsh.py +0 -0
  48. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/utils/__init__.py +0 -0
  49. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/utils/custom_logger.py +0 -0
  50. {chatup-0.2.0 → chatup-0.2.1}/src/chatup/utils/pathing.py +0 -0
  51. {chatup-0.2.0 → chatup-0.2.1}/src/chatup.egg-info/dependency_links.txt +0 -0
  52. {chatup-0.2.0 → chatup-0.2.1}/src/chatup.egg-info/entry_points.txt +0 -0
  53. {chatup-0.2.0 → chatup-0.2.1}/src/chatup.egg-info/requires.txt +0 -0
  54. {chatup-0.2.0 → chatup-0.2.1}/src/chatup.egg-info/top_level.txt +0 -0
  55. {chatup-0.2.0 → chatup-0.2.1}/tests/test_workspace_setup.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chatup
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: ChatArch setup CLI extracted from ChatTool
5
5
  Author-email: rexwzh <1073853456@qq.com>
6
6
  License-Expression: MIT
7
7
  Keywords: chatup,chatarch,setup,cli
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.9
10
+ Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: click>=8.0
@@ -25,8 +25,11 @@ ChatUp is the standalone ChatArch setup CLI. It is the first-level replacement f
25
25
  ```bash
26
26
  chatup --help
27
27
  chatup doctor
28
+ chatup uv
28
29
  ```
29
30
 
31
+ `chatup uv` installs `uv` through the official installer when needed, then creates the ChatArch Python environment with pip. Defaults are `--venv ~/.chatarch/venv` and `--python 3.12`; override them when a different runtime path or Python minor version is required.
32
+
30
33
  ## Development
31
34
 
32
35
  ```bash
@@ -7,8 +7,11 @@ ChatUp is the standalone ChatArch setup CLI. It is the first-level replacement f
7
7
  ```bash
8
8
  chatup --help
9
9
  chatup doctor
10
+ chatup uv
10
11
  ```
11
12
 
13
+ `chatup uv` installs `uv` through the official installer when needed, then creates the ChatArch Python environment with pip. Defaults are `--venv ~/.chatarch/venv` and `--python 3.12`; override them when a different runtime path or Python minor version is required.
14
+
12
15
  ## Development
13
16
 
14
17
  ```bash
@@ -7,7 +7,7 @@ name = "chatup"
7
7
  dynamic = ["version"]
8
8
  description = "ChatArch setup CLI extracted from ChatTool"
9
9
  readme = "README.md"
10
- requires-python = ">=3.9"
10
+ requires-python = ">=3.10"
11
11
  license = "MIT"
12
12
  dependencies = [
13
13
  "click>=8.0",
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.0"
5
+ __version__ = "0.2.1"
@@ -14,6 +14,7 @@ from chatup.setup.lark_cli import setup_lark_cli
14
14
  from chatup.setup.opencode import setup_opencode
15
15
  from chatup.setup.zsh import setup_zsh
16
16
  from chatup.setup.nodejs import setup_nodejs
17
+ from chatup.setup.uv import DEFAULT_PYTHON_VERSION, DEFAULT_VENV_PATH, setup_uv
17
18
  from chatup.setup.workspace import setup_workspace
18
19
 
19
20
 
@@ -47,6 +48,10 @@ def nodejs_setup(interactive, log_level):
47
48
  setup_nodejs(interactive=interactive, log_level=log_level)
48
49
 
49
50
 
51
+ def uv_setup(venv, python_version, force, log_level):
52
+ setup_uv(venv=venv, python_version=python_version, force=force, log_level=log_level)
53
+
54
+
50
55
  def docker_setup(sudo, interactive, log_level):
51
56
  setup_docker(interactive=interactive, use_sudo=sudo, log_level=log_level)
52
57
 
@@ -379,6 +384,37 @@ SETUP_COMMAND_ELEMENTS = (
379
384
  ),
380
385
  ),
381
386
  ),
387
+ SetupCommandElement(
388
+ name="uv",
389
+ help="Install uv and create the ChatArch Python 3.12 environment with pip.",
390
+ callback=uv_setup,
391
+ options=(
392
+ LOG_LEVEL_OPTION,
393
+ SetupOptionElement(
394
+ param_decls=("--venv", "--venv-path"),
395
+ kwargs={
396
+ "default": str(DEFAULT_VENV_PATH),
397
+ "show_default": True,
398
+ "help": "Target ChatArch Python virtual environment path.",
399
+ },
400
+ ),
401
+ SetupOptionElement(
402
+ param_decls=("--python", "--python-version", "python_version"),
403
+ kwargs={
404
+ "default": DEFAULT_PYTHON_VERSION,
405
+ "show_default": True,
406
+ "help": "Python version to install through uv and use for the venv.",
407
+ },
408
+ ),
409
+ SetupOptionElement(
410
+ param_decls=("--force", "-f"),
411
+ kwargs={
412
+ "is_flag": True,
413
+ "help": "Clear and recreate the target environment if it already exists.",
414
+ },
415
+ ),
416
+ ),
417
+ ),
382
418
  SetupCommandElement(
383
419
  name="codex",
384
420
  help="Configure Codex CLI and config files.",
@@ -0,0 +1,195 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import shlex
5
+ import shutil
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ import click
10
+
11
+ from chatup.utils.custom_logger import setup_logger
12
+
13
+ DEFAULT_PYTHON_VERSION = "3.12"
14
+ DEFAULT_VENV_PATH = Path("~/.chatarch/venv")
15
+ UV_INSTALLER_URL = "https://astral.sh/uv/install.sh"
16
+
17
+ logger = setup_logger("setup_uv")
18
+
19
+
20
+ def _configure_logger(log_level: str = "INFO"):
21
+ global logger
22
+ logger = setup_logger("setup_uv", log_level=str(log_level).upper())
23
+ return logger
24
+
25
+
26
+ def _display_command(command: list[str] | tuple[str, ...]) -> str:
27
+ return " ".join(shlex.quote(str(part)) for part in command)
28
+
29
+
30
+ def _run_command(command: list[str], **kwargs) -> subprocess.CompletedProcess[str]:
31
+ click.echo(f"Running: {_display_command(command)}")
32
+ return subprocess.run(command, capture_output=True, text=True, **kwargs)
33
+
34
+
35
+ def _candidate_uv_paths() -> tuple[Path, ...]:
36
+ return (
37
+ Path.home() / ".local" / "bin" / "uv",
38
+ Path.home() / ".cargo" / "bin" / "uv",
39
+ )
40
+
41
+
42
+ def find_uv() -> str | None:
43
+ uv = shutil.which("uv")
44
+ if uv:
45
+ return uv
46
+ for candidate in _candidate_uv_paths():
47
+ if candidate.is_file() and os.access(candidate, os.X_OK):
48
+ return str(candidate)
49
+ return None
50
+
51
+
52
+ def install_uv_with_official_script() -> str:
53
+ command = ["sh", "-c", f"curl -LsSf {shlex.quote(UV_INSTALLER_URL)} | sh"]
54
+ result = _run_command(command)
55
+ if result.returncode != 0:
56
+ message = (result.stderr or result.stdout or "uv installer failed").strip()
57
+ raise click.ClickException(f"Failed to install uv: {message}")
58
+
59
+ uv = find_uv()
60
+ if not uv:
61
+ raise click.ClickException(
62
+ "uv installer finished, but `uv` was not found on PATH or in ~/.local/bin. "
63
+ "Open a new shell or add the installer bin directory to PATH."
64
+ )
65
+ return uv
66
+
67
+
68
+ def ensure_uv_installed() -> str:
69
+ uv = find_uv()
70
+ if uv:
71
+ click.echo(f"uv found: {uv}")
72
+ return uv
73
+
74
+ click.echo("uv not found; installing via the official uv installer script.")
75
+ uv = install_uv_with_official_script()
76
+ click.echo(f"uv installed: {uv}")
77
+ return uv
78
+
79
+
80
+ def _venv_python_path(venv_path: Path) -> Path:
81
+ if os.name == "nt":
82
+ return venv_path / "Scripts" / "python.exe"
83
+ return venv_path / "bin" / "python"
84
+
85
+
86
+ def _python_minor_version(python_bin: Path) -> str | None:
87
+ if not python_bin.exists():
88
+ return None
89
+ result = subprocess.run(
90
+ [str(python_bin), "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"],
91
+ capture_output=True,
92
+ text=True,
93
+ )
94
+ if result.returncode != 0:
95
+ return None
96
+ return result.stdout.strip()
97
+
98
+
99
+ def _venv_has_pip(python_bin: Path) -> bool:
100
+ if not python_bin.exists():
101
+ return False
102
+ result = subprocess.run(
103
+ [str(python_bin), "-m", "pip", "--version"], capture_output=True, text=True
104
+ )
105
+ return result.returncode == 0
106
+
107
+
108
+ def is_chatarch_python_ready(venv_path: Path, python_version: str) -> bool:
109
+ python_bin = _venv_python_path(venv_path)
110
+ current_version = _python_minor_version(python_bin)
111
+ return current_version == python_version and _venv_has_pip(python_bin)
112
+
113
+
114
+ def create_chatarch_python_env(
115
+ uv_bin: str, venv_path: Path, python_version: str, *, force: bool = False
116
+ ) -> None:
117
+ install_result = _run_command([uv_bin, "python", "install", python_version])
118
+ if install_result.returncode != 0:
119
+ message = (install_result.stderr or install_result.stdout).strip()
120
+ raise click.ClickException(f"Failed to install Python {python_version} via uv: {message}")
121
+
122
+ command = [uv_bin, "venv", "--python", python_version, "--seed"]
123
+ if force:
124
+ command.extend(("--clear", "--force"))
125
+ elif venv_path.exists():
126
+ command.append("--allow-existing")
127
+ command.append(str(venv_path))
128
+ venv_result = _run_command(command)
129
+ if venv_result.returncode != 0:
130
+ message = (venv_result.stderr or venv_result.stdout).strip()
131
+ raise click.ClickException(f"Failed to create ChatArch Python env at {venv_path}: {message}")
132
+
133
+
134
+ def setup_uv(
135
+ venv: str | None = None,
136
+ python_version: str = DEFAULT_PYTHON_VERSION,
137
+ force: bool = False,
138
+ log_level: str = "INFO",
139
+ ) -> dict[str, str]:
140
+ _configure_logger(log_level)
141
+ target = Path(venv or str(DEFAULT_VENV_PATH)).expanduser()
142
+ python_version = str(python_version).strip() or DEFAULT_PYTHON_VERSION
143
+
144
+ uv_bin = ensure_uv_installed()
145
+ click.echo(f"ChatArch Python env: {target}")
146
+ click.echo(f"Requested Python: {python_version}")
147
+
148
+ if is_chatarch_python_ready(target, python_version) and not force:
149
+ click.echo("ChatArch Python environment already ready.")
150
+ return {
151
+ "uv": uv_bin,
152
+ "venv": str(target),
153
+ "python": str(_venv_python_path(target)),
154
+ "python_version": python_version,
155
+ "status": "ready",
156
+ }
157
+
158
+ python_bin = _venv_python_path(target)
159
+ current_version = _python_minor_version(python_bin)
160
+ if target.exists() and not force:
161
+ if current_version is None:
162
+ raise click.ClickException(
163
+ f"Target path exists but is not a Python virtual environment: {target}. "
164
+ "Pass --force to clear it or choose another --venv path."
165
+ )
166
+ if current_version != python_version:
167
+ raise click.ClickException(
168
+ f"Existing ChatArch Python env at {target} uses Python {current_version}, "
169
+ f"but Python {python_version} was requested. Pass --force to recreate it "
170
+ "or choose another --venv path."
171
+ )
172
+ click.echo("ChatArch Python environment exists but pip is missing; seeding pip with uv.")
173
+ elif target.exists() and force:
174
+ click.echo("Recreating existing ChatArch Python environment with uv.")
175
+ else:
176
+ click.echo("ChatArch Python environment not found; creating it with uv.")
177
+
178
+ create_chatarch_python_env(uv_bin, target, python_version, force=force)
179
+
180
+ if not is_chatarch_python_ready(target, python_version):
181
+ raise click.ClickException(
182
+ f"ChatArch Python env was created at {target}, but Python {python_version} with pip was not verified."
183
+ )
184
+
185
+ python_bin = _venv_python_path(target)
186
+ click.echo(f"ChatArch Python environment ready: {target}")
187
+ click.echo(f"Activate with: source {shlex.quote(str(target / 'bin' / 'activate'))}")
188
+ click.echo(f"Verified pip: {python_bin} -m pip --version")
189
+ return {
190
+ "uv": uv_bin,
191
+ "venv": str(target),
192
+ "python": str(python_bin),
193
+ "python_version": python_version,
194
+ "status": "created",
195
+ }
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chatup
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: ChatArch setup CLI extracted from ChatTool
5
5
  Author-email: rexwzh <1073853456@qq.com>
6
6
  License-Expression: MIT
7
7
  Keywords: chatup,chatarch,setup,cli
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.9
10
+ Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: click>=8.0
@@ -25,8 +25,11 @@ ChatUp is the standalone ChatArch setup CLI. It is the first-level replacement f
25
25
  ```bash
26
26
  chatup --help
27
27
  chatup doctor
28
+ chatup uv
28
29
  ```
29
30
 
31
+ `chatup uv` installs `uv` through the official installer when needed, then creates the ChatArch Python environment with pip. Defaults are `--venv ~/.chatarch/venv` and `--python 3.12`; override them when a different runtime path or Python minor version is required.
32
+
30
33
  ## Development
31
34
 
32
35
  ```bash
@@ -29,6 +29,7 @@ src/chatup/setup/mode_prompt.py
29
29
  src/chatup/setup/nodejs.py
30
30
  src/chatup/setup/opencode.py
31
31
  src/chatup/setup/shell_rc.py
32
+ src/chatup/setup/uv.py
32
33
  src/chatup/setup/workspace.py
33
34
  src/chatup/setup/zsh.py
34
35
  src/chatup/setup/assets/nvm.sh
@@ -18,6 +18,7 @@ def _pyproject() -> dict:
18
18
  def test_chatup_depends_on_chatenv_without_registering_config_provider():
19
19
  data = _pyproject()
20
20
 
21
+ assert data["project"]["requires-python"] == ">=3.10"
21
22
  assert "chatstyle>=0.1.0,<0.2.0" in data["project"]["dependencies"]
22
23
  assert "chatenv>=0.2.0,<0.3.0" in data["project"]["dependencies"]
23
24
  assert "entry-points" not in data["project"] or "chatenv.configs" not in data["project"]["entry-points"]
@@ -0,0 +1,218 @@
1
+ from click.testing import CliRunner
2
+
3
+ from chatup.cli import main
4
+
5
+
6
+ def test_chatup_root_help_lists_setup_commands_without_setup_group_or_alias():
7
+ result = CliRunner().invoke(main, ["--help"])
8
+
9
+ assert result.exit_code == 0
10
+ for command in [
11
+ "workspace",
12
+ "nodejs",
13
+ "uv",
14
+ "codex",
15
+ "claude",
16
+ "opencode",
17
+ "hermes",
18
+ "lark-cli",
19
+ "docker",
20
+ "zsh",
21
+ ]:
22
+ assert command in result.output
23
+ assert "setup" not in result.output
24
+ assert "\n alias " not in result.output
25
+
26
+
27
+ def test_chatup_workspace_help_keeps_interactive_flags():
28
+ result = CliRunner().invoke(main, ["workspace", "--help"])
29
+
30
+ assert result.exit_code == 0
31
+ assert "--with-chattool" in result.output
32
+ assert "--with-chatblog" in result.output
33
+ assert "--with-memory" in result.output
34
+ assert "-i, --interactive" in result.output
35
+ assert "-I, --no-interactive" in result.output
36
+
37
+
38
+ def test_top_level_setup_commands_expose_help():
39
+ commands = [
40
+ "cc-connect",
41
+ "chrome",
42
+ "claude",
43
+ "codex",
44
+ "docker",
45
+ "frp",
46
+ "hermes",
47
+ "lark-cli",
48
+ "nodejs",
49
+ "opencode",
50
+ "uv",
51
+ "workspace",
52
+ "zsh",
53
+ ]
54
+
55
+ for command in commands:
56
+ result = CliRunner().invoke(main, [command, "--help"])
57
+ assert result.exit_code == 0, command
58
+ assert "--help" in result.output
59
+ assert "Usage: chatup setup" not in result.output
60
+
61
+
62
+ def test_setup_group_and_alias_command_are_not_registered():
63
+ setup_result = CliRunner().invoke(main, ["setup", "--help"])
64
+ alias_result = CliRunner().invoke(main, ["alias", "--help"])
65
+
66
+ assert setup_result.exit_code != 0
67
+ assert "No such command" in setup_result.output
68
+ assert alias_result.exit_code != 0
69
+ assert "No such command" in alias_result.output
70
+
71
+
72
+ def test_opencode_no_longer_exposes_legacy_chatloop_preset():
73
+ result = CliRunner().invoke(main, ["opencode", "--help"])
74
+
75
+ assert result.exit_code == 0
76
+ assert "auto-loop" in result.output
77
+ assert "chatloop" not in result.output.lower()
78
+
79
+
80
+ def test_cc_connect_installs_chatarch_package(monkeypatch):
81
+ import chatup.setup.cc_connect as cc_connect
82
+
83
+ commands = []
84
+ monkeypatch.setattr(cc_connect, "ensure_nodejs_requirement", lambda **kwargs: None)
85
+ monkeypatch.setattr(
86
+ cc_connect,
87
+ "should_install_global_npm_package",
88
+ lambda package_name, display_name, **kwargs: (
89
+ package_name == "@chatarch/cc-connect" and display_name == "cc-connect"
90
+ ),
91
+ )
92
+
93
+ class Result:
94
+ returncode = 0
95
+ stderr = ""
96
+
97
+ def fake_run_npm_command(args):
98
+ commands.append(args)
99
+ return Result()
100
+
101
+ monkeypatch.setattr(cc_connect, "run_npm_command", fake_run_npm_command)
102
+
103
+ result = CliRunner().invoke(main, ["cc-connect", "-I"])
104
+
105
+ assert result.exit_code == 0, result.output
106
+ assert commands == [["install", "-g", "@chatarch/cc-connect"]]
107
+
108
+
109
+ def test_uv_help_exposes_defaults():
110
+ result = CliRunner().invoke(main, ["uv", "--help"])
111
+
112
+ assert result.exit_code == 0
113
+ assert "--venv" in result.output
114
+ assert "--python" in result.output
115
+ assert "~/.chatarch/venv" in result.output or "/.chatarch/venv" in result.output
116
+ assert "3.12" in result.output
117
+ assert "--force" in result.output
118
+
119
+
120
+ def test_uv_setup_creates_default_chatarch_python_env(monkeypatch, tmp_path):
121
+ import chatup.setup.uv as uv_setup
122
+
123
+ created = []
124
+ readiness = iter([False, True])
125
+ monkeypatch.setattr(uv_setup, "DEFAULT_VENV_PATH", tmp_path / "default-venv")
126
+ monkeypatch.setattr(uv_setup, "ensure_uv_installed", lambda: "/usr/local/bin/uv")
127
+ monkeypatch.setattr(uv_setup, "is_chatarch_python_ready", lambda path, version: next(readiness))
128
+ monkeypatch.setattr(
129
+ uv_setup,
130
+ "create_chatarch_python_env",
131
+ lambda uv_bin, path, version, *, force=False: created.append((uv_bin, path, version, force)),
132
+ )
133
+
134
+ result = uv_setup.setup_uv()
135
+
136
+ assert result["status"] == "created"
137
+ assert result["venv"] == str(tmp_path / "default-venv")
138
+ assert result["python_version"] == "3.12"
139
+ assert created == [("/usr/local/bin/uv", tmp_path / "default-venv", "3.12", False)]
140
+
141
+
142
+ def test_uv_setup_accepts_custom_path_python_and_force(monkeypatch, tmp_path):
143
+ import chatup.setup.uv as uv_setup
144
+
145
+ target = tmp_path / "custom-venv"
146
+ created = []
147
+ readiness = iter([False, True])
148
+ monkeypatch.setattr(uv_setup, "ensure_uv_installed", lambda: "/opt/bin/uv")
149
+ monkeypatch.setattr(uv_setup, "is_chatarch_python_ready", lambda path, version: next(readiness))
150
+ monkeypatch.setattr(
151
+ uv_setup,
152
+ "create_chatarch_python_env",
153
+ lambda uv_bin, path, version, *, force=False: created.append((uv_bin, path, version, force)),
154
+ )
155
+
156
+ result = CliRunner().invoke(main, ["uv", "--venv", str(target), "--python", "3.11", "--force"])
157
+
158
+ assert result.exit_code == 0, result.output
159
+ assert created == [("/opt/bin/uv", target, "3.11", True)]
160
+
161
+
162
+ def test_ensure_uv_installed_uses_official_installer_when_missing(monkeypatch):
163
+ import chatup.setup.uv as uv_setup
164
+
165
+ monkeypatch.setattr(uv_setup, "find_uv", lambda: None)
166
+ monkeypatch.setattr(uv_setup, "install_uv_with_official_script", lambda: "/home/me/.local/bin/uv")
167
+
168
+ assert uv_setup.ensure_uv_installed() == "/home/me/.local/bin/uv"
169
+
170
+
171
+ def test_create_chatarch_python_env_seeds_pip_and_force_clears(monkeypatch, tmp_path):
172
+ import chatup.setup.uv as uv_setup
173
+
174
+ commands = []
175
+
176
+ class Result:
177
+ returncode = 0
178
+ stdout = ""
179
+ stderr = ""
180
+
181
+ def fake_run_command(command, **kwargs):
182
+ commands.append(command)
183
+ return Result()
184
+
185
+ monkeypatch.setattr(uv_setup, "_run_command", fake_run_command)
186
+
187
+ uv_setup.create_chatarch_python_env("uv", tmp_path / "venv", "3.12", force=True)
188
+
189
+ assert commands == [
190
+ ["uv", "python", "install", "3.12"],
191
+ ["uv", "venv", "--python", "3.12", "--seed", "--clear", "--force", str(tmp_path / "venv")],
192
+ ]
193
+
194
+
195
+ def test_create_chatarch_python_env_allows_existing_same_version_venv(monkeypatch, tmp_path):
196
+ import chatup.setup.uv as uv_setup
197
+
198
+ target = tmp_path / "venv"
199
+ target.mkdir()
200
+ commands = []
201
+
202
+ class Result:
203
+ returncode = 0
204
+ stdout = ""
205
+ stderr = ""
206
+
207
+ def fake_run_command(command, **kwargs):
208
+ commands.append(command)
209
+ return Result()
210
+
211
+ monkeypatch.setattr(uv_setup, "_run_command", fake_run_command)
212
+
213
+ uv_setup.create_chatarch_python_env("uv", target, "3.12", force=False)
214
+
215
+ assert commands == [
216
+ ["uv", "python", "install", "3.12"],
217
+ ["uv", "venv", "--python", "3.12", "--seed", "--allow-existing", str(target)],
218
+ ]
@@ -5,7 +5,7 @@ from chatup.cli import main
5
5
 
6
6
 
7
7
  def test_version_present():
8
- assert __version__ == "0.2.0"
8
+ assert __version__ == "0.2.1"
9
9
 
10
10
 
11
11
  def test_cli_help():
@@ -20,4 +20,4 @@ def test_cli_doctor():
20
20
  result = CliRunner().invoke(main, ["doctor"])
21
21
 
22
22
  assert result.exit_code == 0
23
- assert "chatup 0.2.0 ok" in result.output
23
+ assert "chatup 0.2.1 ok" in result.output
@@ -1,104 +0,0 @@
1
- from click.testing import CliRunner
2
-
3
- from chatup.cli import main
4
-
5
-
6
- def test_chatup_root_help_lists_setup_commands_without_setup_group_or_alias():
7
- result = CliRunner().invoke(main, ["--help"])
8
-
9
- assert result.exit_code == 0
10
- for command in [
11
- "workspace",
12
- "nodejs",
13
- "codex",
14
- "claude",
15
- "opencode",
16
- "hermes",
17
- "lark-cli",
18
- "docker",
19
- "zsh",
20
- ]:
21
- assert command in result.output
22
- assert "setup" not in result.output
23
- assert "\n alias " not in result.output
24
-
25
-
26
- def test_chatup_workspace_help_keeps_interactive_flags():
27
- result = CliRunner().invoke(main, ["workspace", "--help"])
28
-
29
- assert result.exit_code == 0
30
- assert "--with-chattool" in result.output
31
- assert "--with-chatblog" in result.output
32
- assert "--with-memory" in result.output
33
- assert "-i, --interactive" in result.output
34
- assert "-I, --no-interactive" in result.output
35
-
36
-
37
- def test_top_level_setup_commands_expose_help():
38
- commands = [
39
- "cc-connect",
40
- "chrome",
41
- "claude",
42
- "codex",
43
- "docker",
44
- "frp",
45
- "hermes",
46
- "lark-cli",
47
- "nodejs",
48
- "opencode",
49
- "workspace",
50
- "zsh",
51
- ]
52
-
53
- for command in commands:
54
- result = CliRunner().invoke(main, [command, "--help"])
55
- assert result.exit_code == 0, command
56
- assert "--help" in result.output
57
- assert "Usage: chatup setup" not in result.output
58
-
59
-
60
- def test_setup_group_and_alias_command_are_not_registered():
61
- setup_result = CliRunner().invoke(main, ["setup", "--help"])
62
- alias_result = CliRunner().invoke(main, ["alias", "--help"])
63
-
64
- assert setup_result.exit_code != 0
65
- assert "No such command" in setup_result.output
66
- assert alias_result.exit_code != 0
67
- assert "No such command" in alias_result.output
68
-
69
-
70
- def test_opencode_no_longer_exposes_legacy_chatloop_preset():
71
- result = CliRunner().invoke(main, ["opencode", "--help"])
72
-
73
- assert result.exit_code == 0
74
- assert "auto-loop" in result.output
75
- assert "chatloop" not in result.output.lower()
76
-
77
-
78
- def test_cc_connect_installs_chatarch_package(monkeypatch):
79
- import chatup.setup.cc_connect as cc_connect
80
-
81
- commands = []
82
- monkeypatch.setattr(cc_connect, "ensure_nodejs_requirement", lambda **kwargs: None)
83
- monkeypatch.setattr(
84
- cc_connect,
85
- "should_install_global_npm_package",
86
- lambda package_name, display_name, **kwargs: (
87
- package_name == "@chatarch/cc-connect" and display_name == "cc-connect"
88
- ),
89
- )
90
-
91
- class Result:
92
- returncode = 0
93
- stderr = ""
94
-
95
- def fake_run_npm_command(args):
96
- commands.append(args)
97
- return Result()
98
-
99
- monkeypatch.setattr(cc_connect, "run_npm_command", fake_run_npm_command)
100
-
101
- result = CliRunner().invoke(main, ["cc-connect", "-I"])
102
-
103
- assert result.exit_code == 0, result.output
104
- assert commands == [["install", "-g", "@chatarch/cc-connect"]]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes