feat: full CLI + TUI parity with the escurel server#127
Merged
Conversation
Add the five agent-surface RPCs the typed client was missing so
downstream consumers (and the forthcoming CLI/TUI) can drive the M7
event triad and the validate dry-run, not just read/browse + chat:
- validate — dry-run the indexer pipeline over draft content
- capture_event — append an event to the global inbox
- list_inbox — unprocessed events
- list_events — an instance's processed event history
- assign_event — bind an inbox event to an instance (→ processed)
Each mirrors the existing authed() one-liner pattern; re-export the new
request/response types plus Event so callers never pin escurel-proto.
Test plan (no-mock, real gateway via escurel-test-support):
crates/escurel-client/tests/client_roundtrip.rs
- validate_accepts_well_formed_page
- capture_inbox_assign_list_events_round_trip (full CRM flow:
capture → inbox → assign → list_events, + inbox-cleared assertion)
- list_inbox_respects_limit
All 13 client_roundtrip tests pass; clippy -D warnings clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rt/import)
Complete the escurel-client surface beyond the agent read/write/event
methods so the forthcoming CLI/TUI can drive the operator surface and
streaming RPCs through the same typed, secret-safe path.
AdminClient (new handle over EscurelAdminClient, mirrors Client's
connect + SecretString custody + Debug-no-leak invariant):
- health (no admin role required — substrate probe)
- tenant_create / _list / _get / _update / _delete
- audit, quota_get, delete_chat_history
- attach_external, embedding_reload
- rebuild, compact_lanes, tenant_export (server-stream)
- tenant_import (client-stream)
Client (agent surface):
- live_session — bidi CRDT co-edit stream
Streaming methods return tonic::Streaming<T> (re-exported) and take
impl futures_core::Stream inputs; futures-core (trait-only) is the one
new runtime dep, keeping the crate a leaf.
Test plan (no-mock, real gateway via escurel-test-support):
admin_roundtrip.rs (6): health, tenant CRUD lifecycle (on-disk
provisioning verified), audit clean-drift, quota_get budget,
delete_chat_history GDPR flow, agent-role-rejected.
streaming_roundtrip.rs (2): rebuild streams to done==total;
tenant_export -> tenant_import round-trip (bytes + on-disk markdown).
live_session has no client-layer e2e — a pure-gRPC client can't open
a CRDT session (open_session is HTTP-MCP-only); wire behaviour is
covered server-side in grpc_live_session.rs.
Full client suite: 21 passed (13 agent + 6 admin + 2 streaming);
fmt + clippy -D warnings clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the hand-rolled tonic CLI with a thin presentation layer over
escurel-client (one typed boundary; adding an RPC means editing the
client once). Restructure into gh/aws-style noun-verb groups for
discoverability by humans and LLM agents:
escurel skill list
escurel instance list --skill <s> [--order-by-at --limit]
escurel resolve <wikilink>
escurel page expand|validate|update <page_id>
escurel link neighbours <page_id> [--direction --link-skill --limit]
escurel search <q> [--k --page-type --skill]
escurel event capture|inbox|list|assign (M7 CRM event triad)
escurel query run <id> --params '{…}'
escurel chat append|list
escurel admin health|tenant|audit|quota|delete-chat-history
|attach-external|embedding-reload|rebuild|compact-lanes
|tenant export|import
New: every previously-missing capability now has a command — the event
quartet + page validate (agent surface) and the entire admin surface
incl. the streaming RPCs (rebuild/compact stream progress; tenant
export/import stream bytes to/from a file). This is full CLI parity
with the Flutter demo's backend reach.
Output: global --format json|table (JSON default — the stable contract
for scripts/agents; generic table renderer for humans). Errors emit
JSON on stderr with a non-zero exit so callers can branch on them.
Layout: src/{main,agent,admin,convert,output}.rs. main dispatches the
admin group to AdminClient and everything else to Client.
Test plan (no-mock, real gateway via escurel-test-support; every
command + its common switches, both --format modes, stdin paths, auth
on/off):
tests/cli_e2e.rs (14) — agent surface incl. the realistic CRM flow
capture → inbox → assign → list, and the JSON-on-stderr error.
tests/cli_admin_e2e.rs (6) — health, tenant CRUD lifecycle, audit,
quota, rebuild progress stream, tenant export→import round-trip,
agent-role rejection.
20 passed; fmt + clippy -D warnings clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
-------
New leaf crate `escurel-tui` providing a k9s-style interactive terminal
UI over the Escurel gateway. Elm-structured so all navigation + render
logic is terminal-free and testable without a TTY:
- `App` (src/app.rs): the nav-stack state machine. Pure
`render(&mut Frame)` (top breadcrumb bar, main list/detail area,
bottom context key-hint bar, `?` help overlay) and
`on_key(&mut self) -> Option<DataRequest>`. Keys: arrows/j/k move,
Enter drills in, Esc/h pops the stack, `/` filters, `q` quits, `?`
toggles help. Drill paths: Skills->Instances,
Instances/Search->Entity, Inbox event->its assigned-instance Entity.
- `Screen` enum: Skills, Instances{skill}, Entity{page_id} (title +
frontmatter + body + outgoing wikilinks + backlinks), Inbox,
Events{instance}, Search{query}. `App::with_screen` launches straight
into any screen (used by the inbox/events test).
- `DataSource` (src/data.rs): wraps `escurel_client::Client`, turning a
`DataRequest` into owned `ScreenData` DTOs via the real RPCs
(list_skills / list_instances / expand+neighbours / list_inbox /
list_events / search). DTO field names track the actual proto
messages (Skill.id/description/is_event_typed, InstanceInfo
page_id/skill/at, ExpandResponse page/body/wikilinks_out, Event
title/status/instance_page_id, Edge backlinks).
- `run(endpoint, token)` (src/lib.rs): thin async event loop with an
RAII `TerminalGuard` that restores raw mode + the alternate screen on
every exit path (Drop). Not unit-tested (needs a TTY); kept minimal.
Pinned ratatui 0.29.0 + crossterm 0.28.1 (both resolved cleanly).
Added the crate to the workspace `members`. Dependencies are spelled
with explicit versions/path deps (the workspace root defines no
`[workspace.dependencies]` table, so `{ workspace = true }` deps do not
resolve here — matched the escurel-cli / escurel-client style instead).
Not yet wired into the `escurel-cli` binary — that launch wiring is a
deliberate follow-up.
Scope note: the Search screen render + data path is implemented, but
there is no key-driven drill-in that *creates* a Search screen from the
current screen set (Search is launched via `App::with_screen`); this
matches the spec's "at least the five CRM-core screens" allowance.
Test plan
---------
No-mock integration tests in `crates/escurel-tui/tests/tui_e2e.rs`,
each spawning a real `EscurelProcess` (AuthMode::TestIssuer), seeding
the customer/acme/initech fixture, minting an Agent token, driving
`DataSource` + `App` directly (no TTY) and asserting the text rendered
to a `ratatui::backend::TestBackend`:
- `navigation_flow_renders_corpus` — Skills render contains "customer";
Enter->Instances render contains "acme"; Enter->Entity render
contains the "Acme" title and the outgoing link "initech".
- `inbox_and_events_render_captured_event` — capture_event onto the
acme instance via the real client, then Events screen render contains
"Renewal call"; Inbox screen render shows the "Inbox" panel.
Both pass (2 passed; 0 failed). `cargo fmt --check -p escurel-tui`,
`cargo clippy -p escurel-tui --all-targets -- -D warnings`, and
`cargo test -p escurel-tui --all-targets` all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
-------
Finish the escurel-tui terminal-UI crate and wire it into the escurel
CLI.
- escurel-tui carries a complete crossterm event loop in src/run.rs: a
panic-safe TerminalGuard restoring raw mode + the alternate screen on
every exit path (Drop), an initial Skills load so the UI is non-empty
on launch, and a poll/draw loop that renders the App, dispatches
App::on_key -> Option<DataRequest> through the DataSource, stores the
result via App::set_data, and breaks on should_quit. No
todo!/unimplemented! remain anywhere in the crate.
- Wire the TUI into the escurel binary: add escurel-tui as a CLI
dependency and a new `escurel ui` subcommand that connects with the
same --server/--token and hands control to escurel_tui::run. It owns
the terminal and emits no JSON, so it returns before the value/emit
path. The exhaustive Command match in agent.rs gained an
unreachable!("ui handled by escurel_tui::run") arm, mirroring the
existing admin arm.
- scripts/verify-tui.sh: exit-code-gated runner for the real-gateway
TestBackend suite (the TUI analogue of scripts/verify-demo.sh).
- Docs: README gains a "CLI & TUI" section (gh-style commands,
--format json|table, escurel ui); CHANGELOG records the client
admin+streaming surface, the rebuilt gh-style CLI, and escurel-tui.
Test plan
---------
- cargo fmt --check (escurel-tui, escurel-cli): clean.
- cargo clippy -p escurel-tui -p escurel-cli --all-targets
-- -D warnings: clean.
- cargo test -p escurel-tui: 2 passed
(tests/tui_e2e.rs::navigation_flow_renders_corpus and
::inbox_then_assign_renders_event_in_events, each against a real
spawned gateway, drawn to a ratatui TestBackend; no mocks).
- cargo build -p escurel-cli: builds; `escurel ui --help` and the
top-level help list the new subcommand.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`#[arg(long, name = "in")]` renames the value placeholder, not the long flag — clap derived `--input` from the field name, so the tested `--in` was rejected. Use `#[arg(long = "in")]`. The cli_admin_e2e export→import round-trip now passes (6/6). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brings the
escurelCLI to full parity with the server surface (everything the Flutter demoapps/escurel-explorecan reach) and adds a k9s-style interactive terminal UI. Built as a layered design so backend logic is written once.The gap this closes: the old CLI covered read/browse + page-write + chat, but was missing the M7 event-sourcing surface (the heart of the CRM demo),
validate, live sessions, and the entire admin/operator surface.M-A —
escurel-clientfull server parityvalidate,capture_event,list_inbox,list_events,assign_event(the event triad), pluslive_session(bidi CRDT).AdminClient: health, tenant CRUD, audit, quota_get, delete_chat_history, attach_external, embedding_reload, and the streaming RPCs (rebuild / compact_lanes / tenant_export / tenant_import).M-B — gh-style noun-verb CLI on
escurel-clientReplaced the hand-rolled tonic with a thin presentation layer. Groups:
skill,instance,page,link,event,query,chat,admin, plus top-levelsearch/resolve. Global--format json|table(JSON default = stable contract for scripts/agents), JSON-on-stderr errors.M-C —
escurel-tui(ratatui)escurel uilaunches a k9s-style browser: Skills/Instances/Entity/Inbox/Events/Search screens, nav-stack + breadcrumb,j/k/Enter/Esc/?//q. Plusscripts/verify-tui.sh, README CLI&TUI section, CHANGELOG.Test plan
All no-mock integration tests against a real gateway (
escurel-test-support::EscurelProcess); every command + its common switches exercised.crates/escurel-client/tests/client_roundtrip.rs(13) — agent surface incl. capture→inbox→assign→list_events flow.crates/escurel-client/tests/admin_roundtrip.rs(6) — tenant CRUD lifecycle, audit, quota, GDPR delete, role rejection.crates/escurel-client/tests/streaming_roundtrip.rs(2) — rebuild stream, tenant export→import round-trip.crates/escurel-cli/tests/cli_e2e.rs(14) — every agent command + switches, both--formatmodes, stdin paths, JSON-on-stderr error, auth on/off.crates/escurel-cli/tests/cli_admin_e2e.rs(6) — admin commands incl. rebuild progress stream + tenant export→import.crates/escurel-tui/tests/tui_e2e.rs(2) — TestBackend render/nav against a real seeded gateway.Full local gate (the bootstrap merge gate per CLAUDE.md) green on the workspace:
cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace --all-targets(87 binaries, 0 failures),cargo build --workspace --release.🤖 Generated with Claude Code