refactor(onboarding): simplify setup flow
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+46
-96
@@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|||||||
Reference in New Issue
Block a user