refactor(onboarding): simplify setup flow

This commit is contained in:
banteg
2025-12-29 19:19:01 +04:00
parent db4429af4d
commit d151788edb
3 changed files with 47 additions and 110 deletions
-1
View File
@@ -74,7 +74,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 |
| `--profile NAME` | (codex default) | Codex profile name | | `--profile NAME` | (codex default) | Codex profile name |
| `--setup-demo` | off | Render all onboarding guide variants and exit |
## Usage ## Usage
+1 -13
View File
@@ -21,7 +21,7 @@ from .config import ConfigError, load_telegram_config
from .exec_render import ExecProgressRenderer, render_event_cli from .exec_render import ExecProgressRenderer, render_event_cli
from .logging import setup_logging from .logging import setup_logging
from .rendering import render_markdown from .rendering import render_markdown
from .onboarding import check_setup, demo_results, render_setup_guide from .onboarding import check_setup, render_setup_guide
from .telegram_client import TelegramClient from .telegram_client import TelegramClient
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -678,20 +678,8 @@ def run(
"--profile", "--profile",
help="Codex profile name to pass to `codex --profile`.", help="Codex profile name to pass to `codex --profile`.",
), ),
setup_demo: bool = typer.Option(
False,
"--setup-demo",
help="Render all onboarding guide variants and exit.",
),
) -> None: ) -> None:
setup_logging(debug=debug) setup_logging(debug=debug)
if setup_demo:
for idx, (label, result) in enumerate(demo_results()):
if idx:
typer.echo("", err=True)
typer.echo(f"[setup demo] {label}", err=True)
render_setup_guide(result)
raise typer.Exit(code=0)
setup = check_setup() setup = check_setup()
if not setup.ok: if not setup.ok:
render_setup_guide(setup) render_setup_guide(setup)
+45 -95
View File
@@ -15,13 +15,13 @@ from .constants import HOME_CONFIG_PATH
_OCTOPUS = "\N{OCTOPUS}" _OCTOPUS = "\N{OCTOPUS}"
@dataclass @dataclass(slots=True)
class SetupResult: class SetupResult:
"""Collected setup issues.""" """Collected setup issues."""
missing_codex: bool = False missing_codex: bool = False
missing_or_invalid_config: bool = False missing_or_invalid_config: bool = False
config_path: Path | None = None config_path: Path = HOME_CONFIG_PATH
@property @property
def ok(self) -> bool: def ok(self) -> bool:
@@ -30,32 +30,28 @@ class SetupResult:
def check_setup() -> SetupResult: def check_setup() -> SetupResult:
"""Check all prerequisites and return collected issues.""" """Check all prerequisites and return collected issues."""
result = SetupResult() missing_codex = shutil.which("codex") is None
if not shutil.which("codex"):
result.missing_codex = True
try: try:
config, config_path = load_telegram_config() config, config_path = load_telegram_config()
result.config_path = config_path
except ConfigError: except ConfigError:
result.missing_or_invalid_config = True return SetupResult(
result.config_path = HOME_CONFIG_PATH missing_codex=missing_codex,
return result missing_or_invalid_config=True,
config_path=HOME_CONFIG_PATH,
)
token = config.get("bot_token") token = config.get("bot_token")
if not isinstance(token, str) or not token.strip(): chat_id = config.get("chat_id")
result.missing_or_invalid_config = True
chat_id_value = config.get("chat_id") missing_or_invalid_config = not (isinstance(token, str) and token.strip())
if ( missing_or_invalid_config |= type(chat_id) is not int
chat_id_value is None
or isinstance(chat_id_value, bool)
or not isinstance(chat_id_value, int)
):
result.missing_or_invalid_config = True
return result return SetupResult(
missing_codex=missing_codex,
missing_or_invalid_config=missing_or_invalid_config,
config_path=config_path,
)
def _config_path_display(path: Path) -> str: def _config_path_display(path: Path) -> str:
@@ -67,65 +63,48 @@ def _config_path_display(path: Path) -> str:
return str(path) return str(path)
def _step_marker(step: int) -> str:
return f"{step}."
def render_setup_guide(result: SetupResult) -> None: def render_setup_guide(result: SetupResult) -> None:
"""Render a friendly setup guide panel to stderr.""" """Render a friendly setup guide panel to stderr."""
if result.ok:
return
console = Console(stderr=True) console = Console(stderr=True)
parts: list[str] = [] parts: list[str] = []
step = 0 step = 0
needs_credentials_help = False
def add_step(title: str, *lines: str) -> None:
nonlocal step
step += 1
parts.append(f"[bold yellow]{step}.[/] [bold]{title}[/]")
parts.append("")
parts.extend(lines)
parts.append("")
if result.missing_codex: if result.missing_codex:
step += 1 add_step(
parts.append( "Install the Codex CLI",
f"[bold yellow]{_step_marker(step)}[/] [bold]Install the Codex CLI[/]" " [dim]$[/] npm install -g @openai/codex",
)
parts.append("")
parts.append(" [dim]$[/] npm install -g @openai/codex")
parts.append("")
config_display = (
_config_path_display(result.config_path)
if result.config_path
else _config_path_display(HOME_CONFIG_PATH)
) )
if result.missing_or_invalid_config: if result.missing_or_invalid_config:
step += 1 config_display = _config_path_display(result.config_path)
parts.append(f"[bold yellow]{_step_marker(step)}[/] [bold]Create a config[/]") add_step(
parts.append("") "Create a config",
parts.append(f" [dim]{config_display}[/]") f" [dim]{config_display}[/]",
parts.append("") "",
parts.append(' [cyan]bot_token[/] = [green]"123456789:ABCdef..."[/]') ' [cyan]bot_token[/] = [green]"123456789:ABCdef..."[/]',
parts.append(" [cyan]chat_id[/] = [green]123456789[/]") " [cyan]chat_id[/] = [green]123456789[/]",
parts.append("") "",
needs_credentials_help = True "[dim]" + ("-" * 56) + "[/]",
"",
if needs_credentials_help: "[bold]Getting your Telegram credentials:[/]",
needs_token_help = True "",
needs_chat_id_help = True " [cyan]bot_token[/] create a bot with [link=https://t.me/BotFather]@BotFather[/]",
" [cyan]chat_id[/] get from [link=https://t.me/myidbot]@myidbot[/]",
parts.append("[dim]" + ("-" * 56) + "[/]")
parts.append("")
parts.append("[bold]Getting your Telegram credentials:[/]")
parts.append("")
if needs_token_help:
parts.append(
" [cyan]bot_token[/] create a bot with [link=https://t.me/BotFather]@BotFather[/]"
) )
if needs_chat_id_help:
parts.append(
" [cyan]chat_id[/] get from [link=https://t.me/myidbot]@myidbot[/]"
)
while parts and not parts[-1].strip():
parts.pop()
panel = Panel( panel = Panel(
"\n".join(parts), "\n".join(parts).rstrip(),
title="[bold]Welcome to Takopi![/]", title="[bold]Welcome to Takopi![/]",
subtitle=f"{_OCTOPUS} setup required", subtitle=f"{_OCTOPUS} setup required",
border_style="yellow", border_style="yellow",
@@ -133,32 +112,3 @@ def render_setup_guide(result: SetupResult) -> None:
expand=False, expand=False,
) )
console.print(panel) console.print(panel)
def demo_results() -> list[tuple[str, SetupResult]]:
"""Return sample setup results for previewing all onboarding modes."""
config_path = HOME_CONFIG_PATH
return [
(
"fresh-install",
SetupResult(
missing_codex=True,
missing_or_invalid_config=True,
config_path=config_path,
),
),
(
"missing-codex",
SetupResult(
missing_codex=True,
config_path=config_path,
),
),
(
"missing-or-invalid-config",
SetupResult(
missing_or_invalid_config=True,
config_path=config_path,
),
),
]