# tail-fin-cli-core

Shared CLI/daemon helpers for [tail-fin](https://github.com/motosan-dev/tail-fin). Consumed by both the `tail-fin` CLI binary and the `tfd` daemon — the two agree on how to construct `BrowserSession`, resolve cookie paths, and format JSON output.

## What's here

| Helper | Purpose |
|---|---|
| `Ctx` | Connection-mode context (`connect`, `cookies`, `headed`) shared across subcommands + REPL |
| `browser_session(host, headed)` | Connect to an existing Chrome via CDP at `ws://{host}` |
| `launch_browser(headed)` | Launch a fresh headless (or headed) browser — no existing Chrome required |
| `auto_launch_stealth(url, headed)` | Stealth browser fallback for adapters that don't need `--connect` |
| `launch_stealth_session(url, headed)` | Stealth browser navigated to a URL with anti-detection |
| `require_browser(connect, service, action)` | Enforce `--connect`, return a friendly error otherwise |
| `require_browser_session(ctx, service)` | Browser-only adapter: reject `--cookies`, require `--connect`, return `BrowserSession` |
| `resolve_cookies_path(flag, site)` | `"auto"` → `~/.tail-fin/<site>-cookies.txt`; explicit path pass-through |
| `default_cookies_path(site)` | `~/.tail-fin/<site>-cookies.txt` |
| `print_json(value)` | Pretty JSON to stdout |
| `print_list(key, items, count)` | `{ "<key>": items, "count": N }` JSON wrapper |
| `no_mode_error(service, cmd)` | User-facing error when neither `--connect` nor `--cookies` is set |

Site-specific CLI glue (e.g. the Twitter HTTP client constructor) stays in `tail-fin-cli` — this crate is deliberately site-agnostic.

## Usage

```rust
use tail_fin_cli_core::{browser_session, print_json, Ctx};

async fn example(ctx: &Ctx) -> anyhow::Result<()> {
    let host = ctx.connect.as_deref().unwrap_or("127.0.0.1:9222");
    let session = browser_session(host, ctx.headed).await?;
    // ... use session ...
    print_json(&serde_json::json!({ "status": "ok" }))?;
    Ok(())
}
```

## License

[MIT](../../LICENSE)
