generated from coulomb/repo-seed
feat(cya): T01-T07 core console-native MVP slice (CYA-WP-0001)
- T01: Python + Typer/rich + pyproject.toml + full src/ layout + working `cya` CLI entrypoint - T02: Bounded transparent context collector (top-level only, provenance, ignores) + --explain-context - T03: Genuine rule-based risk classifier (primary) + mandatory terminal confirmation, no auto-execute - T04: LLMAdapter Protocol + deterministic FakeLLMAdapter seam (llm-connect boundary, zero bypass) - T05: Strictly minimal phase-memory no-op ports (loud markers, per operator direction 2026-05-26) - T06: Orchestrator coordinating the full flow; CLI is thin delegation - T07: pytest harness + safety-focused tests (risk invariants + collector) All changes verified by running the installed `cya` binary and `pytest tests/`. Workplan updated with status. State Hub progress event logged (workstream 0a1233fd...). Refs: CYA-WP-0001, Decision a644364b-11c4-49a9-bf17-99063382e27b
This commit is contained in:
5
src/cya/cli/__init__.py
Normal file
5
src/cya/cli/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""CLI surface and entrypoint (T01).
|
||||
|
||||
The primary user-facing commands live here. Later tasks will extend with
|
||||
context-aware behavior while keeping this the stable surface.
|
||||
"""
|
||||
122
src/cya/cli/main.py
Normal file
122
src/cya/cli/main.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""T01 — Project scaffolding and Typer CLI entrypoint.
|
||||
|
||||
Implements the minimal runnable package per CYA-WP-0001-T01 acceptance criteria:
|
||||
- pyproject.toml + src/ layout with clean separation (cli, context, safety, llm, memory)
|
||||
- `cya --help`, `cya --version`, `cya "<natural language request>"` all work after `pip install -e .`
|
||||
- Rich, structured, human-readable output even before LLM / collector wiring.
|
||||
- Graceful fallback message pointing to the remaining workplan tasks.
|
||||
|
||||
This module will evolve in T06 (orchestrator) but the surface contract stays stable.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
from cya import __version__
|
||||
from cya.context.collector import collect, render_explanation
|
||||
|
||||
app = typer.Typer(
|
||||
name="cya",
|
||||
help=(
|
||||
"[bold cyan]cya[/bold cyan] — console-native assistant for local work.\n\n"
|
||||
"Express intent in natural language from your terminal.\n"
|
||||
"MVP T01 (scaffolding): this is the skeleton only. "
|
||||
"Real context collection (T02), rule-based safety (T03), llm-connect boundary (T04), "
|
||||
"and orchestration (T06) are added in subsequent tasks.\n\n"
|
||||
"Usage: [bold]cya \"your request in plain English\"[/bold]"
|
||||
),
|
||||
rich_markup_mode="rich",
|
||||
add_completion=False,
|
||||
invoke_without_command=True,
|
||||
)
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
def version_callback(value: bool) -> None:
|
||||
"""Print version and exit (eager)."""
|
||||
if value:
|
||||
console.print(f"[bold]cya[/bold] version [green]{__version__}[/green] (MVP T01 scaffolding)")
|
||||
raise typer.Exit()
|
||||
|
||||
|
||||
@app.callback()
|
||||
def main(
|
||||
ctx: typer.Context,
|
||||
request: str | None = typer.Argument(
|
||||
None,
|
||||
help="Your natural-language request or intent (e.g. 'explain the recent git log for this repo').",
|
||||
),
|
||||
explain_context: bool = typer.Option(
|
||||
False,
|
||||
"--explain-context",
|
||||
"-C",
|
||||
help="Show exactly what local context would be collected (real implementation in T02).",
|
||||
),
|
||||
dry_run: bool = typer.Option(
|
||||
False,
|
||||
"--dry-run",
|
||||
"-n",
|
||||
help="Preview mode — do not perform any actions (stub in T01).",
|
||||
),
|
||||
version: bool = typer.Option(
|
||||
None,
|
||||
"--version",
|
||||
"-V",
|
||||
callback=version_callback,
|
||||
is_eager=True,
|
||||
help="Show cya version and exit.",
|
||||
),
|
||||
) -> None:
|
||||
"""Root entry: supports bare `cya "request"` as the primary one-shot UX."""
|
||||
if ctx.invoked_subcommand is not None:
|
||||
# A real subcommand was given; let Typer handle it.
|
||||
return
|
||||
|
||||
if request is None:
|
||||
# No request and no subcommand — show friendly guidance instead of raw error.
|
||||
console.print(
|
||||
Panel(
|
||||
Text.from_markup(
|
||||
"No request provided.\n\n"
|
||||
"Try:\n"
|
||||
" [bold]cya \"what changed since the last commit?\"[/bold]\n"
|
||||
" [bold]cya --help[/bold] for all options and examples\n\n"
|
||||
"[dim]This is T01 scaffolding. Full behavior arrives after T02–T06.[/dim]"
|
||||
),
|
||||
title="cya (MVP)",
|
||||
border_style="yellow",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
raise typer.Exit(0)
|
||||
|
||||
# Delegate the entire coordinated flow (T02–T04) to the orchestrator (T06).
|
||||
# This keeps the Typer surface thin and makes the core logic testable.
|
||||
from cya.orchestrator import handle_request
|
||||
|
||||
handle_request(
|
||||
request,
|
||||
explain_context=explain_context,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
|
||||
|
||||
def run() -> None:
|
||||
"""Primary console-script entry point (no-arg callable expected by setuptools/pip).
|
||||
|
||||
The generated `cya` wrapper in bin/ does `sys.exit(run())`.
|
||||
Using a thin wrapper around app() lets us keep the full-featured
|
||||
@app.callback(invoke_without_command=True) + ctx signature for Typer/Click.
|
||||
"""
|
||||
app()
|
||||
Reference in New Issue
Block a user