ebc20e0013
Bug fixes: - Fix SSE chunk parsing: buffer partial lines across network boundaries (openai.rs) - Use Arc<dyn Provider> so send_message calls through the trait and reuses the HTTP connection pool, instead of hardcoding OpenAICompatibleProvider per request (app.rs) - Fix Ctrl+Q during streaming: persist partial response before quitting (app.rs) - Fix Ctrl+N duplicate JSONL messages: remove redundant save_current_session call and guard against new session while streaming (app.rs) - Fix code block content never rendered: text was accumulated into the wrong variable (markdown.rs) - Fix status bar stuck on 'Loading models...' after closing model selector (app.rs) - Fix CJK character panic in session title truncation: use char-level slicing (storage.rs) New features: - Session resume with Ctrl+O: list, browse, and load past chat sessions (app.rs, storage.rs) - Code block boxes: full-border rendering (top/left/right/bottom) with solid background for acrylic/transparent terminals (markdown.rs) - Code blocks fill chat area width with CJK-aware display width padding (markdown.rs, app.rs) - Markdown: add heading support (H1-H6), strikethrough, fix bold+italic by combining style stack layers (markdown.rs) Docs: - Replace CLAUDE.md with AGENTS.md containing repo-specific agent guidance
3.0 KiB
3.0 KiB
Meow - Agent Notes
Rust terminal AI chat client (TUI). Single crate, no workspace.
Build & Run
cargo build --release
# binary: target/release/meow
No tests, no lint/typecheck tooling configured. Just cargo build / cargo run.
Architecture (Single Crate)
| File | Responsibility |
|---|---|
src/main.rs |
tokio::main, event loop (100ms tick), dispatches to App |
src/app.rs |
State machine: Chat / ModelSelect / ProviderConfig; draws all UI; spawns stream task |
src/config.rs |
Config / ProviderConfig; loads/saves ~/.meow/config.toml via dirs |
src/message.rs |
Role, Message, ChatSession with uuid + chrono |
src/storage.rs |
JSON Lines persistence at ~/.meow/data/sessions/<uuid>.jsonl |
src/providers/mod.rs |
Provider trait (async_trait), Model, ProviderError |
src/providers/openai.rs |
OpenAICompatibleProvider: /v1/models, /v1/chat/completions SSE |
src/tui/mod.rs |
Tui: raw mode, alternate screen, mouse capture, panic hook restore |
src/tui/input.rs |
Multi-line InputState with cursor movement; Shift+Enter newline, Enter submit |
src/tui/markdown.rs |
pulldown-cmark → ratatui::Line + syntect highlighting (base16-ocean.dark) |
Key Conventions
- Streaming protocol:
Provider::chat_streamreturnsBoxStream<Result<String>>. The spawned tokio task sends chunks through anmpsc::unbounded_channel. Sentinel__STREAM_END__marks completion;App::poll_stream()drains the channel on each tick. - Provider rebuild: After saving config,
App::rebuild_providers()reconstructs theVec<Box<dyn Provider>>fromconfig.providers. Always call this after mutating providers. - First-run behavior: If
config.providersis empty, app boots intoProviderConfigstate.Escin that state quits the app instead of returning to chat. - Ctrl+C while streaming: Stops stream and calls
finish_stream_message()to persist partial response. Do NOT treat as app quit during streaming. - Session persistence:
Storage::append_messageis append-only JSON Lines.save_current_session()iterates messages and appends each (safe to call multiple times; duplicates are harmless in current design). - Syntax highlighting:
syntectusesregex-fancybackend (notonig). Theme hardcoded tobase16-ocean.dark.
Runtime Data Paths
- Config:
~/.meow/config.toml - Sessions:
~/.meow/data/sessions/<uuid>.jsonl
Dependencies to Know
reqwestwithrustls-tls,stream— SSE viabytes_stream()ratatui0.29 +crossterm0.28 — all TUI renderingsyntectwithdefault-themes,default-syntaxes,regex-fancypulldown-cmark0.12 — markdown parser
Adding a New Provider
Only OpenAICompatibleProvider exists. To add a non-OpenAI provider:
- Implement
Providertrait insrc/providers/<name>.rs - Register in
src/providers/mod.rs - Wire instantiation in
App::new()andApp::rebuild_providers()