chatup 0.2.13__tar.gz → 0.2.14__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.13/src/chatup.egg-info → chatup-0.2.14}/PKG-INFO +2 -1
- {chatup-0.2.13 → chatup-0.2.14}/pyproject.toml +1 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/__init__.py +1 -1
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/codex.py +108 -24
- {chatup-0.2.13 → chatup-0.2.14/src/chatup.egg-info}/PKG-INFO +2 -1
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup.egg-info/requires.txt +1 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_codex_setup.py +146 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_pyproject_metadata.py +1 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_version.py +2 -2
- {chatup-0.2.13 → chatup-0.2.14}/LICENSE +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/README.md +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/setup.cfg +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/chrome_for_testing.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/chromedriver.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/cli.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/commands/__init__.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/commands/chrome_for_testing.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/commands/chromedriver.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/commands/playwright.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/config.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/const.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/interaction/__init__.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/interaction/command_schema.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/interaction/policy.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/interaction/warnings.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/playwright.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/runtime/__init__.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/runtime/_managed_artifact.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/runtime/chrome_for_testing.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/runtime/chromedriver.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/runtime/playwright.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/__init__.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/assets/hermes/install.sh +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/assets/nvm.sh +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/cc_connect.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/claude.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/cli.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/crs.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/cursor_agent.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/discourse.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/docker.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/elements.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/frp.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/gitea.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/hermes.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/lark_cli.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/mode_prompt.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/mysql.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/nginx.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/nodejs.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/opencode.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/service_credentials.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/shell_rc.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/twikoo.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/uv.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/__init__.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/cli.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/core.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/options.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/render.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/en/AGENTS.md +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/en/discussion/README.md +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/en/projects/README.md +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/zh/AGENTS.md +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/zh/discussion/README.md +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/zh/projects/README.md +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/zsh.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/zulip.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/utils/__init__.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/utils/custom_logger.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup/utils/pathing.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup.egg-info/SOURCES.txt +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup.egg-info/dependency_links.txt +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup.egg-info/entry_points.txt +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/src/chatup.egg-info/top_level.txt +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_browser_backend_cli.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_chrome_for_testing.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_chromedriver.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_cli_tree_docs.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_crs_setup.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_cursor_agent_setup.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_playwright.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_playwright_cli.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_publish_workflow.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_service_installers.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_setup_cli.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/tests/test_twikoo_setup.py +0 -0
- {chatup-0.2.13 → chatup-0.2.14}/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.14
|
|
4
4
|
Summary: ChatArch setup CLI extracted from ChatTool
|
|
5
5
|
Author-email: rexwzh <1073853456@qq.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -16,6 +16,7 @@ License-File: LICENSE
|
|
|
16
16
|
Requires-Dist: click<9.0,>=8.0
|
|
17
17
|
Requires-Dist: chatstyle<0.3.0,>=0.2.0
|
|
18
18
|
Requires-Dist: chatenv<0.3.0,>=0.2.11
|
|
19
|
+
Requires-Dist: python-dotenv<2.0,>=1.0
|
|
19
20
|
Requires-Dist: colorama
|
|
20
21
|
Provides-Extra: dev
|
|
21
22
|
Requires-Dist: build; extra == "dev"
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
from pathlib import Path
|
|
6
|
+
import re
|
|
7
|
+
import tempfile
|
|
5
8
|
import click
|
|
9
|
+
from dotenv import dotenv_values
|
|
6
10
|
|
|
7
11
|
from chatenv.configs import OpenAIConfig
|
|
8
12
|
from chatenv.fields import BaseEnvConfig
|
|
9
13
|
from chatenv.source_chain import split_config_sources
|
|
10
|
-
from chatenv.store import EnvStore
|
|
11
14
|
from chatup.const import CHATARCH_ENV_DIR, CHATARCH_ENV_FILE
|
|
12
15
|
from chatup.interaction import (
|
|
13
16
|
BACK_VALUE,
|
|
@@ -29,6 +32,7 @@ from chatup.utils.custom_logger import setup_logger
|
|
|
29
32
|
DEFAULT_MODEL = "gpt-5.5"
|
|
30
33
|
DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
|
31
34
|
DEFAULT_AUTH_METHOD = "apikey"
|
|
35
|
+
_ENV_VAR_REF_RE = re.compile(r"\$(?:\{[^}]+\}|[A-Za-z_][A-Za-z0-9_]*)")
|
|
32
36
|
logger = setup_logger("setup_codex")
|
|
33
37
|
|
|
34
38
|
|
|
@@ -208,6 +212,29 @@ def _write_codex_config(config_path: Path, *, model: str, base_url: str) -> list
|
|
|
208
212
|
return changed_root + changed_provider
|
|
209
213
|
|
|
210
214
|
|
|
215
|
+
def _write_private_text(path: Path, content: str) -> None:
|
|
216
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
217
|
+
fd, tmp_name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
|
218
|
+
try:
|
|
219
|
+
os.chmod(tmp_name, 0o600)
|
|
220
|
+
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
221
|
+
handle.write(content)
|
|
222
|
+
handle.flush()
|
|
223
|
+
os.fsync(handle.fileno())
|
|
224
|
+
os.replace(tmp_name, path)
|
|
225
|
+
path.chmod(0o600)
|
|
226
|
+
except Exception:
|
|
227
|
+
try:
|
|
228
|
+
os.close(fd)
|
|
229
|
+
except OSError:
|
|
230
|
+
pass
|
|
231
|
+
try:
|
|
232
|
+
os.unlink(tmp_name)
|
|
233
|
+
except OSError:
|
|
234
|
+
pass
|
|
235
|
+
raise
|
|
236
|
+
|
|
237
|
+
|
|
211
238
|
def _write_codex_auth(auth_path: Path, api_key: str) -> list[str]:
|
|
212
239
|
auth_data = {}
|
|
213
240
|
if auth_path.exists():
|
|
@@ -219,11 +246,9 @@ def _write_codex_auth(auth_path: Path, api_key: str) -> list[str]:
|
|
|
219
246
|
auth_data = {}
|
|
220
247
|
old_value = auth_data.get("OPENAI_API_KEY")
|
|
221
248
|
auth_data["OPENAI_API_KEY"] = api_key
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
json.dumps(auth_data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
|
249
|
+
_write_private_text(
|
|
250
|
+
auth_path, json.dumps(auth_data, ensure_ascii=False, indent=2) + "\n"
|
|
225
251
|
)
|
|
226
|
-
auth_path.chmod(0o600)
|
|
227
252
|
logger.info(f"Patched auth file: {auth_path}")
|
|
228
253
|
return ["OPENAI_API_KEY"] if old_value != api_key else []
|
|
229
254
|
|
|
@@ -251,11 +276,36 @@ def _restore_openai_values(values: dict[str, str | None]) -> None:
|
|
|
251
276
|
OpenAIConfig.OPENAI_API_MODEL.value = values.get("model")
|
|
252
277
|
|
|
253
278
|
|
|
279
|
+
def _env_ref_has_path_separator(env_ref: str) -> bool:
|
|
280
|
+
return "/" in env_ref or "\\" in env_ref
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _invalid_profile_name(env_ref: str) -> bool:
|
|
284
|
+
if env_ref == ".env":
|
|
285
|
+
return False
|
|
286
|
+
profile_name = env_ref.removesuffix(".env")
|
|
287
|
+
return (
|
|
288
|
+
not profile_name
|
|
289
|
+
or profile_name in {".", ".."}
|
|
290
|
+
or ".." in profile_name
|
|
291
|
+
or _env_ref_has_path_separator(profile_name)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
254
295
|
def _resolve_openai_env_path(env_ref: str) -> Path:
|
|
296
|
+
env_ref = str(env_ref).strip()
|
|
297
|
+
if not env_ref:
|
|
298
|
+
raise click.ClickException("OpenAI profile name cannot be empty.")
|
|
299
|
+
|
|
255
300
|
candidate = Path(env_ref).expanduser()
|
|
256
301
|
if candidate.is_file():
|
|
257
302
|
return candidate
|
|
258
303
|
|
|
304
|
+
if _invalid_profile_name(env_ref) or _env_ref_has_path_separator(env_ref):
|
|
305
|
+
raise click.ClickException(
|
|
306
|
+
"Invalid OpenAI profile name. Profile names cannot contain path separators, '.' or '..'; pass an existing .env file path for file-based config."
|
|
307
|
+
)
|
|
308
|
+
|
|
259
309
|
profile_path = OpenAIConfig.get_profile_env_file(CHATARCH_ENV_DIR, env_ref)
|
|
260
310
|
if profile_path.exists():
|
|
261
311
|
return profile_path
|
|
@@ -273,6 +323,22 @@ def _openai_values_from_mapping(values: dict[str, str]) -> dict[str, str | None]
|
|
|
273
323
|
}
|
|
274
324
|
|
|
275
325
|
|
|
326
|
+
def _load_dotenv_without_interpolation(env_path: Path) -> dict[str, str]:
|
|
327
|
+
return {
|
|
328
|
+
key: value
|
|
329
|
+
for key, value in dotenv_values(env_path, interpolate=False).items()
|
|
330
|
+
if value is not None
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _reject_unresolved_env_references(values: dict[str, str | None]) -> None:
|
|
335
|
+
for field, value in values.items():
|
|
336
|
+
if isinstance(value, str) and _ENV_VAR_REF_RE.search(value):
|
|
337
|
+
raise click.ClickException(
|
|
338
|
+
f"OpenAI profile field {field} contains an unresolved variable reference; selected profiles are loaded without process-environment interpolation."
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
|
|
276
342
|
def _load_openai_values_from_env_ref(env_ref: str) -> dict[str, str | None]:
|
|
277
343
|
"""Load only the explicitly selected OpenAI env file/profile.
|
|
278
344
|
|
|
@@ -282,7 +348,9 @@ def _load_openai_values_from_env_ref(env_ref: str) -> dict[str, str | None]:
|
|
|
282
348
|
a different account's key into `~/.codex/auth.json`.
|
|
283
349
|
"""
|
|
284
350
|
env_path = _resolve_openai_env_path(env_ref)
|
|
285
|
-
|
|
351
|
+
values = _openai_values_from_mapping(_load_dotenv_without_interpolation(env_path))
|
|
352
|
+
_reject_unresolved_env_references(values)
|
|
353
|
+
return values
|
|
286
354
|
|
|
287
355
|
|
|
288
356
|
def setup_codex(
|
|
@@ -391,27 +459,43 @@ def setup_codex(
|
|
|
391
459
|
if api_key == BACK_VALUE:
|
|
392
460
|
return
|
|
393
461
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
462
|
+
if env_ref:
|
|
463
|
+
base_url = prompt_text_value(
|
|
464
|
+
"base_url (optional)",
|
|
465
|
+
base_url,
|
|
466
|
+
env_config.get("base_url"),
|
|
467
|
+
fallback=DEFAULT_BASE_URL,
|
|
468
|
+
)
|
|
469
|
+
else:
|
|
470
|
+
base_url = prompt_text_value(
|
|
471
|
+
"base_url (optional)",
|
|
472
|
+
base_url,
|
|
473
|
+
env_config.get("base_url"),
|
|
474
|
+
existing.get("base_url"),
|
|
475
|
+
env_values.get("OPENAI_API_BASE"),
|
|
476
|
+
typed_env_values.get("OPENAI_API_BASE"),
|
|
477
|
+
fallback=DEFAULT_BASE_URL,
|
|
478
|
+
)
|
|
403
479
|
if base_url == BACK_VALUE:
|
|
404
480
|
return
|
|
405
481
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
482
|
+
if env_ref:
|
|
483
|
+
model = prompt_text_value(
|
|
484
|
+
"default model (optional)",
|
|
485
|
+
model,
|
|
486
|
+
env_config.get("model"),
|
|
487
|
+
fallback=DEFAULT_MODEL,
|
|
488
|
+
)
|
|
489
|
+
else:
|
|
490
|
+
model = prompt_text_value(
|
|
491
|
+
"default model (optional)",
|
|
492
|
+
model,
|
|
493
|
+
env_config.get("model"),
|
|
494
|
+
existing.get("model"),
|
|
495
|
+
env_values.get("OPENAI_API_MODEL"),
|
|
496
|
+
typed_env_values.get("OPENAI_API_MODEL"),
|
|
497
|
+
fallback=DEFAULT_MODEL,
|
|
498
|
+
)
|
|
415
499
|
if model == BACK_VALUE:
|
|
416
500
|
return
|
|
417
501
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chatup
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.14
|
|
4
4
|
Summary: ChatArch setup CLI extracted from ChatTool
|
|
5
5
|
Author-email: rexwzh <1073853456@qq.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -16,6 +16,7 @@ License-File: LICENSE
|
|
|
16
16
|
Requires-Dist: click<9.0,>=8.0
|
|
17
17
|
Requires-Dist: chatstyle<0.3.0,>=0.2.0
|
|
18
18
|
Requires-Dist: chatenv<0.3.0,>=0.2.11
|
|
19
|
+
Requires-Dist: python-dotenv<2.0,>=1.0
|
|
19
20
|
Requires-Dist: colorama
|
|
20
21
|
Provides-Extra: dev
|
|
21
22
|
Requires-Dist: build; extra == "dev"
|
|
@@ -144,6 +144,152 @@ def test_codex_env_dotenv_ref_loads_active_openai_profile(tmp_path, monkeypatch)
|
|
|
144
144
|
assert "process-key-should-not-be-used" not in config_text
|
|
145
145
|
|
|
146
146
|
|
|
147
|
+
def test_codex_env_profile_rejects_dotenv_interpolation_backfill(tmp_path, monkeypatch):
|
|
148
|
+
import chatup.setup.codex as codex_setup
|
|
149
|
+
|
|
150
|
+
home = tmp_path / "home"
|
|
151
|
+
envs_dir = tmp_path / "chatarch" / "envs"
|
|
152
|
+
openai_dir = envs_dir / "OpenAI"
|
|
153
|
+
monkeypatch.setenv("HOME", str(home))
|
|
154
|
+
monkeypatch.setenv("OPENAI_API_KEY", "process-key-should-not-be-used")
|
|
155
|
+
monkeypatch.setattr(codex_setup, "CHATARCH_ENV_DIR", envs_dir)
|
|
156
|
+
monkeypatch.setattr(codex_setup, "CHATARCH_ENV_FILE", envs_dir / ".env")
|
|
157
|
+
monkeypatch.setattr(codex_setup, "ensure_nodejs_requirement", lambda **kwargs: None)
|
|
158
|
+
monkeypatch.setattr(
|
|
159
|
+
codex_setup,
|
|
160
|
+
"should_install_global_npm_package",
|
|
161
|
+
lambda *args, **kwargs: False,
|
|
162
|
+
)
|
|
163
|
+
_write_env(
|
|
164
|
+
openai_dir / "apple.env",
|
|
165
|
+
{
|
|
166
|
+
"OPENAI_API_KEY": "${OPENAI_API_KEY}",
|
|
167
|
+
"OPENAI_API_BASE": "https://crs.example/openai/v1",
|
|
168
|
+
"OPENAI_API_MODEL": "gpt-5.5",
|
|
169
|
+
},
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
with pytest.raises(click.ClickException, match="unresolved variable reference"):
|
|
173
|
+
codex_setup.setup_codex(env_ref="apple", interactive=False)
|
|
174
|
+
|
|
175
|
+
assert not (home / ".codex" / "auth.json").exists()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_codex_env_profile_rejects_path_traversal_name(tmp_path, monkeypatch):
|
|
179
|
+
import chatup.setup.codex as codex_setup
|
|
180
|
+
|
|
181
|
+
home = tmp_path / "home"
|
|
182
|
+
envs_dir = tmp_path / "chatarch" / "envs"
|
|
183
|
+
other_dir = envs_dir / "Other"
|
|
184
|
+
monkeypatch.setenv("HOME", str(home))
|
|
185
|
+
monkeypatch.setattr(codex_setup, "CHATARCH_ENV_DIR", envs_dir)
|
|
186
|
+
monkeypatch.setattr(codex_setup, "CHATARCH_ENV_FILE", envs_dir / ".env")
|
|
187
|
+
monkeypatch.setattr(codex_setup, "ensure_nodejs_requirement", lambda **kwargs: None)
|
|
188
|
+
monkeypatch.setattr(
|
|
189
|
+
codex_setup,
|
|
190
|
+
"should_install_global_npm_package",
|
|
191
|
+
lambda *args, **kwargs: False,
|
|
192
|
+
)
|
|
193
|
+
_write_env(
|
|
194
|
+
other_dir / "secret.env",
|
|
195
|
+
{
|
|
196
|
+
"OPENAI_API_KEY": "other-key-should-not-be-read",
|
|
197
|
+
"OPENAI_API_BASE": "https://other.example/v1",
|
|
198
|
+
"OPENAI_API_MODEL": "other-model",
|
|
199
|
+
},
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
with pytest.raises(click.ClickException, match="profile name"):
|
|
203
|
+
codex_setup.setup_codex(env_ref="../Other/secret", interactive=False)
|
|
204
|
+
|
|
205
|
+
assert not (home / ".codex" / "auth.json").exists()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_codex_env_profile_prompt_defaults_stay_inside_selected_profile(
|
|
209
|
+
tmp_path, monkeypatch
|
|
210
|
+
):
|
|
211
|
+
import chatup.setup.codex as codex_setup
|
|
212
|
+
|
|
213
|
+
home = tmp_path / "home"
|
|
214
|
+
envs_dir = tmp_path / "chatarch" / "envs"
|
|
215
|
+
openai_dir = envs_dir / "OpenAI"
|
|
216
|
+
codex_dir = home / ".codex"
|
|
217
|
+
codex_dir.mkdir(parents=True)
|
|
218
|
+
(codex_dir / "config.toml").write_text(
|
|
219
|
+
'model_provider = "crs"\n'
|
|
220
|
+
'model = "existing-model"\n\n'
|
|
221
|
+
'[model_providers.crs]\n'
|
|
222
|
+
'base_url = "https://existing.example/v1"\n',
|
|
223
|
+
encoding="utf-8",
|
|
224
|
+
)
|
|
225
|
+
monkeypatch.setenv("HOME", str(home))
|
|
226
|
+
monkeypatch.setenv("OPENAI_API_BASE", "https://process.example/v1")
|
|
227
|
+
monkeypatch.setenv("OPENAI_API_MODEL", "process-model")
|
|
228
|
+
monkeypatch.setattr(codex_setup, "CHATARCH_ENV_DIR", envs_dir)
|
|
229
|
+
monkeypatch.setattr(codex_setup, "CHATARCH_ENV_FILE", envs_dir / ".env")
|
|
230
|
+
monkeypatch.setattr(codex_setup, "ensure_nodejs_requirement", lambda **kwargs: None)
|
|
231
|
+
monkeypatch.setattr(
|
|
232
|
+
codex_setup,
|
|
233
|
+
"should_install_global_npm_package",
|
|
234
|
+
lambda *args, **kwargs: False,
|
|
235
|
+
)
|
|
236
|
+
monkeypatch.setattr(
|
|
237
|
+
codex_setup,
|
|
238
|
+
"resolve_interactive_mode",
|
|
239
|
+
lambda **kwargs: (True, True, True, False, True),
|
|
240
|
+
)
|
|
241
|
+
monkeypatch.setattr(codex_setup, "abort_if_force_without_tty", lambda *a, **k: None)
|
|
242
|
+
monkeypatch.setattr(codex_setup, "abort_if_missing_without_tty", lambda *a, **k: None)
|
|
243
|
+
monkeypatch.setattr(
|
|
244
|
+
codex_setup,
|
|
245
|
+
"resolve_install_only_mode",
|
|
246
|
+
lambda **kwargs: (False, False),
|
|
247
|
+
)
|
|
248
|
+
monkeypatch.setattr(
|
|
249
|
+
codex_setup,
|
|
250
|
+
"prompt_sensitive_value",
|
|
251
|
+
lambda _label, value, _masker: value,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
prompt_candidates = []
|
|
255
|
+
|
|
256
|
+
def fake_prompt_text_value(_label, *candidates, fallback=None):
|
|
257
|
+
prompt_candidates.extend(candidates)
|
|
258
|
+
for candidate in candidates:
|
|
259
|
+
if candidate:
|
|
260
|
+
return candidate
|
|
261
|
+
return fallback
|
|
262
|
+
|
|
263
|
+
monkeypatch.setattr(codex_setup, "prompt_text_value", fake_prompt_text_value)
|
|
264
|
+
_write_env(
|
|
265
|
+
openai_dir / ".env",
|
|
266
|
+
{
|
|
267
|
+
"OPENAI_API_BASE": "https://active.example/v1",
|
|
268
|
+
"OPENAI_API_MODEL": "active-model",
|
|
269
|
+
},
|
|
270
|
+
)
|
|
271
|
+
_write_env(
|
|
272
|
+
openai_dir / "apple.env",
|
|
273
|
+
{
|
|
274
|
+
"OPENAI_API_KEY": "apple-key",
|
|
275
|
+
"OPENAI_API_BASE": "https://apple.example/v1",
|
|
276
|
+
"OPENAI_API_MODEL": "apple-model",
|
|
277
|
+
},
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
codex_setup.setup_codex(env_ref="apple", interactive=True)
|
|
281
|
+
|
|
282
|
+
assert "https://existing.example/v1" not in prompt_candidates
|
|
283
|
+
assert "existing-model" not in prompt_candidates
|
|
284
|
+
assert "https://process.example/v1" not in prompt_candidates
|
|
285
|
+
assert "process-model" not in prompt_candidates
|
|
286
|
+
assert "https://active.example/v1" not in prompt_candidates
|
|
287
|
+
assert "active-model" not in prompt_candidates
|
|
288
|
+
config_text = (codex_dir / "config.toml").read_text(encoding="utf-8")
|
|
289
|
+
assert 'base_url = "https://apple.example/v1"' in config_text
|
|
290
|
+
assert 'model = "apple-model"' in config_text
|
|
291
|
+
|
|
292
|
+
|
|
147
293
|
def test_codex_default_model_matches_shared_openai_default():
|
|
148
294
|
from chatenv.configs import OpenAIConfig
|
|
149
295
|
import chatup.setup.codex as codex_setup
|
|
@@ -24,6 +24,7 @@ def test_chatup_depends_on_chatenv_and_registers_cursor_agent_config_provider():
|
|
|
24
24
|
assert "click>=8.0,<9.0" in data["project"]["dependencies"]
|
|
25
25
|
assert "chatstyle>=0.2.0,<0.3.0" in data["project"]["dependencies"]
|
|
26
26
|
assert "chatenv>=0.2.11,<0.3.0" in data["project"]["dependencies"]
|
|
27
|
+
assert "python-dotenv>=1.0,<2.0" in data["project"]["dependencies"]
|
|
27
28
|
entry_points = data["project"]["entry-points"]["chatenv.configs"]
|
|
28
29
|
assert entry_points["cursor-agent"] == "chatup.config:CursorAgentConfig"
|
|
29
30
|
assert entry_points["discourse"] == "chatup.config:DiscourseAdminConfig"
|
|
@@ -5,7 +5,7 @@ from chatup.cli import main
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def test_version_present():
|
|
8
|
-
assert __version__ == "0.2.
|
|
8
|
+
assert __version__ == "0.2.14"
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def test_cli_help():
|
|
@@ -61,4 +61,4 @@ def test_cli_doctor():
|
|
|
61
61
|
result = CliRunner().invoke(main, ["doctor"])
|
|
62
62
|
|
|
63
63
|
assert result.exit_code == 0
|
|
64
|
-
assert "chatup 0.2.
|
|
64
|
+
assert "chatup 0.2.14 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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/en/discussion/README.md
RENAMED
|
File without changes
|
{chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/en/projects/README.md
RENAMED
|
File without changes
|
|
File without changes
|
{chatup-0.2.13 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/zh/discussion/README.md
RENAMED
|
File without changes
|
{chatup-0.2.13 → chatup-0.2.14}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|