chatup 0.2.12__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.12/src/chatup.egg-info → chatup-0.2.14}/PKG-INFO +3 -2
- {chatup-0.2.12 → chatup-0.2.14}/pyproject.toml +2 -1
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/__init__.py +1 -1
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/codex.py +166 -56
- {chatup-0.2.12 → chatup-0.2.14/src/chatup.egg-info}/PKG-INFO +3 -2
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup.egg-info/SOURCES.txt +1 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup.egg-info/requires.txt +2 -1
- chatup-0.2.14/tests/test_codex_setup.py +297 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_pyproject_metadata.py +2 -1
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_version.py +2 -2
- {chatup-0.2.12 → chatup-0.2.14}/LICENSE +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/README.md +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/setup.cfg +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/chrome_for_testing.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/chromedriver.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/cli.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/commands/__init__.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/commands/chrome_for_testing.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/commands/chromedriver.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/commands/playwright.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/config.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/const.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/interaction/__init__.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/interaction/command_schema.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/interaction/policy.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/interaction/warnings.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/playwright.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/runtime/__init__.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/runtime/_managed_artifact.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/runtime/chrome_for_testing.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/runtime/chromedriver.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/runtime/playwright.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/__init__.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/assets/hermes/install.sh +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/assets/nvm.sh +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/cc_connect.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/claude.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/cli.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/crs.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/cursor_agent.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/discourse.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/docker.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/elements.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/frp.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/gitea.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/hermes.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/lark_cli.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/mode_prompt.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/mysql.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/nginx.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/nodejs.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/opencode.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/service_credentials.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/shell_rc.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/twikoo.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/uv.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/__init__.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/cli.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/core.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/options.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/render.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/en/AGENTS.md +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/en/discussion/README.md +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/en/projects/README.md +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/zh/AGENTS.md +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/zh/discussion/README.md +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/zh/projects/README.md +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/zsh.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/zulip.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/utils/__init__.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/utils/custom_logger.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup/utils/pathing.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup.egg-info/dependency_links.txt +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup.egg-info/entry_points.txt +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/src/chatup.egg-info/top_level.txt +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_browser_backend_cli.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_chrome_for_testing.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_chromedriver.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_cli_tree_docs.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_crs_setup.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_cursor_agent_setup.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_playwright.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_playwright_cli.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_publish_workflow.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_service_installers.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_setup_cli.py +0 -0
- {chatup-0.2.12 → chatup-0.2.14}/tests/test_twikoo_setup.py +0 -0
- {chatup-0.2.12 → 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
|
|
@@ -15,7 +15,8 @@ Description-Content-Type: text/markdown
|
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: click<9.0,>=8.0
|
|
17
17
|
Requires-Dist: chatstyle<0.3.0,>=0.2.0
|
|
18
|
-
Requires-Dist: chatenv<0.3.0,>=0.2.
|
|
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,8 +1,12 @@
|
|
|
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
|
|
@@ -25,9 +29,10 @@ from chatup.setup.nodejs import (
|
|
|
25
29
|
)
|
|
26
30
|
from chatup.utils.custom_logger import setup_logger
|
|
27
31
|
|
|
28
|
-
DEFAULT_MODEL = "gpt-5.
|
|
32
|
+
DEFAULT_MODEL = "gpt-5.5"
|
|
29
33
|
DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
|
30
34
|
DEFAULT_AUTH_METHOD = "apikey"
|
|
35
|
+
_ENV_VAR_REF_RE = re.compile(r"\$(?:\{[^}]+\}|[A-Za-z_][A-Za-z0-9_]*)")
|
|
31
36
|
logger = setup_logger("setup_codex")
|
|
32
37
|
|
|
33
38
|
|
|
@@ -207,6 +212,29 @@ def _write_codex_config(config_path: Path, *, model: str, base_url: str) -> list
|
|
|
207
212
|
return changed_root + changed_provider
|
|
208
213
|
|
|
209
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
|
+
|
|
210
238
|
def _write_codex_auth(auth_path: Path, api_key: str) -> list[str]:
|
|
211
239
|
auth_data = {}
|
|
212
240
|
if auth_path.exists():
|
|
@@ -218,11 +246,9 @@ def _write_codex_auth(auth_path: Path, api_key: str) -> list[str]:
|
|
|
218
246
|
auth_data = {}
|
|
219
247
|
old_value = auth_data.get("OPENAI_API_KEY")
|
|
220
248
|
auth_data["OPENAI_API_KEY"] = api_key
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
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"
|
|
224
251
|
)
|
|
225
|
-
auth_path.chmod(0o600)
|
|
226
252
|
logger.info(f"Patched auth file: {auth_path}")
|
|
227
253
|
return ["OPENAI_API_KEY"] if old_value != api_key else []
|
|
228
254
|
|
|
@@ -250,11 +276,36 @@ def _restore_openai_values(values: dict[str, str | None]) -> None:
|
|
|
250
276
|
OpenAIConfig.OPENAI_API_MODEL.value = values.get("model")
|
|
251
277
|
|
|
252
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
|
+
|
|
253
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
|
+
|
|
254
300
|
candidate = Path(env_ref).expanduser()
|
|
255
301
|
if candidate.is_file():
|
|
256
302
|
return candidate
|
|
257
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
|
+
|
|
258
309
|
profile_path = OpenAIConfig.get_profile_env_file(CHATARCH_ENV_DIR, env_ref)
|
|
259
310
|
if profile_path.exists():
|
|
260
311
|
return profile_path
|
|
@@ -264,17 +315,42 @@ def _resolve_openai_env_path(env_ref: str) -> Path:
|
|
|
264
315
|
)
|
|
265
316
|
|
|
266
317
|
|
|
318
|
+
def _openai_values_from_mapping(values: dict[str, str]) -> dict[str, str | None]:
|
|
319
|
+
return {
|
|
320
|
+
"openai_api_key": values.get("OPENAI_API_KEY"),
|
|
321
|
+
"base_url": values.get("OPENAI_API_BASE"),
|
|
322
|
+
"model": values.get("OPENAI_API_MODEL"),
|
|
323
|
+
}
|
|
324
|
+
|
|
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
|
+
|
|
267
342
|
def _load_openai_values_from_env_ref(env_ref: str) -> dict[str, str | None]:
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
343
|
+
"""Load only the explicitly selected OpenAI env file/profile.
|
|
344
|
+
|
|
345
|
+
`chatup codex -e PROFILE` is an account-selection boundary. Do not merge
|
|
346
|
+
the selected profile with the active/default ChatEnv profile or process
|
|
347
|
+
environment, otherwise a profile missing `OPENAI_API_KEY` can silently write
|
|
348
|
+
a different account's key into `~/.codex/auth.json`.
|
|
349
|
+
"""
|
|
350
|
+
env_path = _resolve_openai_env_path(env_ref)
|
|
351
|
+
values = _openai_values_from_mapping(_load_dotenv_without_interpolation(env_path))
|
|
352
|
+
_reject_unresolved_env_references(values)
|
|
353
|
+
return values
|
|
278
354
|
|
|
279
355
|
|
|
280
356
|
def setup_codex(
|
|
@@ -302,13 +378,19 @@ def setup_codex(
|
|
|
302
378
|
if isinstance(model, str) and not model.strip():
|
|
303
379
|
model = None
|
|
304
380
|
|
|
305
|
-
|
|
306
|
-
api_key
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
381
|
+
if env_ref:
|
|
382
|
+
api_key = resolve_value(
|
|
383
|
+
api_key,
|
|
384
|
+
env_config.get("openai_api_key"),
|
|
385
|
+
)
|
|
386
|
+
else:
|
|
387
|
+
api_key = resolve_value(
|
|
388
|
+
api_key,
|
|
389
|
+
env_config.get("openai_api_key"),
|
|
390
|
+
existing_api_key,
|
|
391
|
+
env_values.get("OPENAI_API_KEY"),
|
|
392
|
+
typed_env_values.get("OPENAI_API_KEY"),
|
|
393
|
+
)
|
|
312
394
|
missing_required = not api_key
|
|
313
395
|
has_existing_config = any(
|
|
314
396
|
value for key, value in existing.items() if key != "openai_api_key"
|
|
@@ -377,27 +459,43 @@ def setup_codex(
|
|
|
377
459
|
if api_key == BACK_VALUE:
|
|
378
460
|
return
|
|
379
461
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
+
)
|
|
389
479
|
if base_url == BACK_VALUE:
|
|
390
480
|
return
|
|
391
481
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
+
)
|
|
401
499
|
if model == BACK_VALUE:
|
|
402
500
|
return
|
|
403
501
|
|
|
@@ -421,22 +519,34 @@ def setup_codex(
|
|
|
421
519
|
click.echo(result.stderr.strip(), err=True)
|
|
422
520
|
raise click.Abort()
|
|
423
521
|
|
|
424
|
-
|
|
425
|
-
base_url
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
522
|
+
if env_ref:
|
|
523
|
+
base_url = resolve_value(
|
|
524
|
+
base_url,
|
|
525
|
+
env_config.get("base_url"),
|
|
526
|
+
DEFAULT_BASE_URL,
|
|
527
|
+
)
|
|
528
|
+
model = resolve_value(
|
|
529
|
+
model,
|
|
530
|
+
env_config.get("model"),
|
|
531
|
+
DEFAULT_MODEL,
|
|
532
|
+
)
|
|
533
|
+
else:
|
|
534
|
+
base_url = resolve_value(
|
|
535
|
+
base_url,
|
|
536
|
+
env_config.get("base_url"),
|
|
537
|
+
existing.get("base_url"),
|
|
538
|
+
env_values.get("OPENAI_API_BASE"),
|
|
539
|
+
typed_env_values.get("OPENAI_API_BASE"),
|
|
540
|
+
DEFAULT_BASE_URL,
|
|
541
|
+
)
|
|
542
|
+
model = resolve_value(
|
|
543
|
+
model,
|
|
544
|
+
env_config.get("model"),
|
|
545
|
+
existing.get("model"),
|
|
546
|
+
env_values.get("OPENAI_API_MODEL"),
|
|
547
|
+
typed_env_values.get("OPENAI_API_MODEL"),
|
|
548
|
+
DEFAULT_MODEL,
|
|
549
|
+
)
|
|
440
550
|
|
|
441
551
|
codex_dir.mkdir(parents=True, exist_ok=True)
|
|
442
552
|
|
|
@@ -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
|
|
@@ -15,7 +15,8 @@ Description-Content-Type: text/markdown
|
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: click<9.0,>=8.0
|
|
17
17
|
Requires-Dist: chatstyle<0.3.0,>=0.2.0
|
|
18
|
-
Requires-Dist: chatenv<0.3.0,>=0.2.
|
|
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"
|
|
@@ -0,0 +1,297 @@
|
|
|
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_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
|
+
|
|
293
|
+
def test_codex_default_model_matches_shared_openai_default():
|
|
294
|
+
from chatenv.configs import OpenAIConfig
|
|
295
|
+
import chatup.setup.codex as codex_setup
|
|
296
|
+
|
|
297
|
+
assert codex_setup.DEFAULT_MODEL == OpenAIConfig.OPENAI_API_MODEL.default
|
|
@@ -23,7 +23,8 @@ def test_chatup_depends_on_chatenv_and_registers_cursor_agent_config_provider():
|
|
|
23
23
|
assert data["project"]["requires-python"] == ">=3.10"
|
|
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
|
-
assert "chatenv>=0.2.
|
|
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.12 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/en/discussion/README.md
RENAMED
|
File without changes
|
{chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/en/projects/README.md
RENAMED
|
File without changes
|
|
File without changes
|
{chatup-0.2.12 → chatup-0.2.14}/src/chatup/setup/workspace/templates/default/zh/discussion/README.md
RENAMED
|
File without changes
|
{chatup-0.2.12 → 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
|