refactor: drop --cd and simplify config lookup

This commit is contained in:
banteg
2025-12-29 18:15:05 +04:00
parent 4a2eafdc3f
commit f3f0a1fea3
5 changed files with 15 additions and 45 deletions
+3 -3
View File
@@ -73,15 +73,15 @@ def render_markdown(md: str) -> tuple[str, list[dict[str, Any]]]:
### `config.py` — Configuration Loading ### `config.py` — Configuration Loading
```python ```python
def load_telegram_config(path=None, *, base_dir=None) -> tuple[dict, Path]: def load_telegram_config(path=None) -> tuple[dict, Path]:
# Loads <base_dir>/codex/takopi.toml (if set), then ./codex/takopi.toml, then ~/.codex/takopi.toml # Loads ./.codex/takopi.toml, then ~/.codex/takopi.toml
``` ```
### `constants.py` — Shared Constants ### `constants.py` — Shared Constants
```python ```python
TELEGRAM_HARD_LIMIT = 4096 # Max message length TELEGRAM_HARD_LIMIT = 4096 # Max message length
LOCAL_CONFIG_NAME = codex/takopi.toml LOCAL_CONFIG_NAME = .codex/takopi.toml
HOME_CONFIG_PATH = ~/.codex/takopi.toml HOME_CONFIG_PATH = ~/.codex/takopi.toml
``` ```
-3
View File
@@ -46,8 +46,6 @@ chat_id = 123456789
The bridge only accepts messages where the chat ID equals the sender ID and both match `chat_id` (i.e., private chat with that user). The bridge only accepts messages where the chat ID equals the sender ID and both match `chat_id` (i.e., private chat with that user).
When you pass `--cd`, Takopi looks for `.codex/takopi.toml` under that directory first.
### Codex Profile (Optional) ### Codex Profile (Optional)
Create a Codex profile in `~/.codex/config.toml`: Create a Codex profile in `~/.codex/config.toml`:
@@ -75,7 +73,6 @@ uv run takopi
|------|---------|-------------| |------|---------|-------------|
| `--final-notify` / `--no-final-notify` | `--final-notify` | Send final response as new message (vs. edit) | | `--final-notify` / `--no-final-notify` | `--final-notify` | Send final response as new message (vs. edit) |
| `--debug` / `--no-debug` | `--no-debug` | Enable verbose logging | | `--debug` / `--no-debug` | `--no-debug` | Enable verbose logging |
| `--cd PATH` | cwd | Working directory for Codex |
| `--profile NAME` | (codex default) | Codex profile name | | `--profile NAME` | (codex default) | Codex profile name |
## Usage ## Usage
+8 -15
View File
@@ -46,16 +46,10 @@ def _missing_config_message(primary: Path, alternate: Path | None = None) -> str
) )
def _config_candidates(base_dir: Path | None) -> list[Path]: def _config_candidates() -> list[Path]:
candidates: list[Path] = [] candidates = [Path.cwd() / LOCAL_CONFIG_NAME, HOME_CONFIG_PATH]
if base_dir is not None: if candidates[0] == candidates[1]:
candidates.append(base_dir / LOCAL_CONFIG_NAME) return [candidates[0]]
cwd = Path.cwd()
if base_dir is None or base_dir != cwd:
candidates.append(cwd / LOCAL_CONFIG_NAME)
candidates.append(HOME_CONFIG_PATH)
return candidates return candidates
@@ -72,17 +66,16 @@ def _read_config(cfg_path: Path) -> dict:
raise ConfigError(f"Malformed TOML in {cfg_path}: {e}") from None raise ConfigError(f"Malformed TOML in {cfg_path}: {e}") from None
def load_telegram_config( def load_telegram_config(path: str | Path | None = None) -> tuple[dict, Path]:
path: str | Path | None = None, *, base_dir: str | Path | None = None
) -> tuple[dict, Path]:
if path: if path:
cfg_path = Path(path).expanduser() cfg_path = Path(path).expanduser()
return _read_config(cfg_path), cfg_path return _read_config(cfg_path), cfg_path
base = Path(base_dir).expanduser() if base_dir is not None else None candidates = _config_candidates()
candidates = _config_candidates(base)
for candidate in candidates: for candidate in candidates:
if candidate.is_file(): if candidate.is_file():
return _read_config(candidate), candidate return _read_config(candidate), candidate
if len(candidates) == 1:
raise ConfigError(_missing_config_message(candidates[0]))
raise ConfigError(_missing_config_message(HOME_CONFIG_PATH, candidates[0])) raise ConfigError(_missing_config_message(HOME_CONFIG_PATH, candidates[0]))
+3 -23
View File
@@ -194,11 +194,9 @@ class CodexExecRunner:
def __init__( def __init__(
self, self,
codex_cmd: str, codex_cmd: str,
workspace: str | None,
extra_args: list[str], extra_args: list[str],
) -> None: ) -> None:
self.codex_cmd = codex_cmd self.codex_cmd = codex_cmd
self.workspace = workspace
self.extra_args = extra_args self.extra_args = extra_args
# Per-session locks to prevent concurrent resumes to the same session_id. # Per-session locks to prevent concurrent resumes to the same session_id.
@@ -219,14 +217,10 @@ class CodexExecRunner:
session_id: str | None, session_id: str | None,
on_event: EventCallback | None = None, on_event: EventCallback | None = None,
) -> tuple[str, str, bool]: ) -> tuple[str, str, bool]:
logger.info( logger.info("[codex] start run session_id=%r", session_id)
"[codex] start run session_id=%r workspace=%r", session_id, self.workspace
)
logger.debug("[codex] prompt: %s", prompt) logger.debug("[codex] prompt: %s", prompt)
args = [self.codex_cmd] args = [self.codex_cmd]
args.extend(self.extra_args) args.extend(self.extra_args)
if self.workspace:
args.extend(["--cd", self.workspace])
args.extend(["exec", "--json"]) args.extend(["exec", "--json"])
# Always pipe prompt via stdin ("-") to avoid quoting issues. # Always pipe prompt via stdin ("-") to avoid quoting issues.
@@ -355,19 +349,11 @@ class BridgeConfig:
def _parse_bridge_config( def _parse_bridge_config(
*, *,
final_notify: bool, final_notify: bool,
cd: str | None,
profile: str | None, profile: str | None,
) -> BridgeConfig: ) -> BridgeConfig:
startup_pwd = os.getcwd() startup_pwd = os.getcwd()
workspace = None
if cd is not None:
expanded_cd = os.path.expanduser(cd)
if not os.path.isdir(expanded_cd):
raise ConfigError(f"--cd must be an existing directory: {expanded_cd}")
workspace = expanded_cd
startup_pwd = expanded_cd
config, config_path = load_telegram_config(base_dir=workspace) config, config_path = load_telegram_config()
try: try:
token = config["bot_token"] token = config["bot_token"]
except KeyError: except KeyError:
@@ -396,7 +382,7 @@ def _parse_bridge_config(
extra_args.extend(["--profile", profile]) extra_args.extend(["--profile", profile])
bot = TelegramClient(token) bot = TelegramClient(token)
runner = CodexExecRunner(codex_cmd=codex_cmd, workspace=workspace, extra_args=extra_args) runner = CodexExecRunner(codex_cmd=codex_cmd, extra_args=extra_args)
return BridgeConfig( return BridgeConfig(
bot=bot, bot=bot,
@@ -691,11 +677,6 @@ def run(
"--debug/--no-debug", "--debug/--no-debug",
help="Log codex JSONL, Telegram requests, and rendered messages.", help="Log codex JSONL, Telegram requests, and rendered messages.",
), ),
cd: str | None = typer.Option(
None,
"--cd",
help="Pass through to `codex --cd`.",
),
profile: str | None = typer.Option( profile: str | None = typer.Option(
None, None,
"--profile", "--profile",
@@ -706,7 +687,6 @@ def run(
try: try:
cfg = _parse_bridge_config( cfg = _parse_bridge_config(
final_notify=final_notify, final_notify=final_notify,
cd=cd,
profile=profile, profile=profile,
) )
except ConfigError as e: except ConfigError as e:
+1 -1
View File
@@ -4,7 +4,7 @@ from takopi.exec_bridge import CodexExecRunner
def test_run_serialized_serializes_same_session() -> None: def test_run_serialized_serializes_same_session() -> None:
runner = CodexExecRunner(codex_cmd="codex", workspace=None, extra_args=[]) runner = CodexExecRunner(codex_cmd="codex", extra_args=[])
gate = asyncio.Event() gate = asyncio.Event()
in_flight = 0 in_flight = 0
max_in_flight = 0 max_in_flight = 0