Skip to content

feat: shimmer spinner + enhanced CLI UX for agent responses#337

Merged
qhkm merged 2 commits intomainfrom
feat/shimmer-ux
Mar 12, 2026
Merged

feat: shimmer spinner + enhanced CLI UX for agent responses#337
qhkm merged 2 commits intomainfrom
feat/shimmer-ux

Conversation

@qhkm
Copy link
Copy Markdown
Owner

@qhkm qhkm commented Mar 12, 2026

Summary

  • Gradient text shimmer animation on "Thinking..." while waiting for LLM response
  • Step numbering with checkmarks (✓/✗) for tool execution progress
  • Tool argument hints (e.g. read_file → calculator.py)
  • Separator line between tool progress and final response
  • Dimmed Claude subscription token warning

Before

2026-03-12T18:27:19Z  WARN zeptoclaw::providers::registry: Using Claude subscription...
  [read_file] Running... done (0.0s)
  [edit_file] Running... done (0.0s)

After

Using Claude subscription token (unofficial)...     ← dimmed
  ⠋ Thinking...                                     ← shimmer gradient wave
  ✓ Step 1 · read_file → calculator.py (0.1s)
  ✓ Step 2 · edit_file → src/main.rs (0.0s)
  ✗ Step 3 · shell → cargo build (5.0s: exit code 1)

  ────────────────────────────────────────

  Here's what I changed: ...

Changes

  • New: src/cli/shimmer.rs — ShimmerSpinner, format helpers, arg extraction
  • Modified: src/agent/loop.rs — Thinking/ThinkingDone/ResponseReady phases, args_json field
  • Modified: src/cli/agent.rs — Shimmer-aware feedback printer
  • Modified: src/providers/registry.rs — Dim warning with ANSI escape

Test plan

  • cargo fmt -- --check passes
  • cargo clippy -- -D warnings passes
  • cargo test --lib — 3126 passed, 0 failed
  • Manual: zeptoclaw agent --template coder -m "..." shows shimmer + steps
  • Manual: interactive mode shows shimmer between turns

Closes #336

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Animated CLI progress indicator with shimmer and spinner during processing.
    • New feedback states for "thinking" and final "response ready" visible in the UI.
    • Tool feedback can include concise argument hints alongside status.
  • Improvements

    • Richer, line-updating tool status messages with elapsed time and response separators.
    • Less intrusive dimmed warning output for unofficial credential notices.
  • Tests

    • Updated tests to cover new feedback fields and formatting.

Add gradient text shimmer animation while AI is thinking, step numbering
with checkmarks for tool execution, tool argument hints, and a separator
line before the final response. Dim the Claude subscription warning.

- New src/cli/shimmer.rs: ShimmerSpinner (ANSI 256-color gradient wave),
  format_tool_start/done/failed helpers, extract_args_hint, separator
- ToolFeedbackPhase: add Thinking, ThinkingDone, ResponseReady phases
- ToolFeedback: add args_json field for argument display hints
- Agent loop: emit Thinking/ThinkingDone around all LLM calls,
  ResponseReady before final response
- CLI feedback printer: shimmer on think, step numbers, checkmarks,
  overwrite-in-place for done/failed, separator before response
- Dim Claude subscription token warning with ANSI dim escape

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 12, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds a CLI shimmer + spinner UX and instruments the agent loop to emit new feedback phases (Thinking, ThinkingDone, ResponseReady) and include raw tool argument JSON in ToolFeedback events; integrates a ShimmerSpinner and formatted progress output into the CLI.

Changes

Cohort / File(s) Summary
Agent Loop Instrumentation
src/agent/loop.rs
Added pub args_json: Option<String> to ToolFeedback and three new ToolFeedbackPhase variants (Thinking, ThinkingDone, ResponseReady). Emitted Thinking/ThinkingDone/ResponseReady around LLM calls and propagated raw tool args into Starting/Done/Failed feedback paths. Tests updated for new fields/formatting.
CLI Feedback Integration
src/cli/agent.rs
Replaced simple stderr feedback with shimmer-based progress UI hooks. Added local state (step, shimmer, had_tools), handlers for Thinking/ThinkingDone/Starting/Done/Failed/ResponseReady, hint extraction usage, and ANSI line-overwrite behavior.
Shimmer UI Module
src/cli/shimmer.rs
New module providing ShimmerSpinner (start/stop/Drop), animated gradient + spinner output, formatting helpers (format_tool_start, format_tool_done, format_tool_failed), print_response_separator, and extract_args_hint with tests.
CLI Module Registration
src/cli/mod.rs
Added pub(crate) mod shimmer; to register the new CLI shimmer module.
Providers: Registry Message
src/providers/registry.rs
Replaced tracing::warn! with a dimmed eprintln! message about Claude tokens, moving output to subdued stderr.

Sequence Diagram

sequenceDiagram
    participant AgentLoop as Agent Loop
    participant Feedback as Feedback Handler
    participant Shimmer as Shimmer UI
    participant Tool as Tool Executor
    participant Stderr as stderr

    AgentLoop->>Feedback: emit Thinking
    Feedback->>Shimmer: start shimmer
    Shimmer->>Stderr: render "Thinking..." (gradient + spinner)

    AgentLoop->>AgentLoop: call LLM

    AgentLoop->>Feedback: emit ThinkingDone
    Feedback->>Shimmer: stop shimmer

    AgentLoop->>Feedback: emit Starting(tool, args_json)
    Feedback->>Shimmer: extract hint, format_tool_start
    Shimmer->>Stderr: overwrite line with step + tool + hint

    Tool->>Tool: execute tool

    AgentLoop->>Feedback: emit Done(tool, args_json, elapsed)
    Feedback->>Shimmer: format_tool_done (✓)
    Shimmer->>Stderr: overwrite line with completion

    AgentLoop->>Feedback: emit ResponseReady
    Feedback->>Shimmer: stop, print_response_separator
    Shimmer->>Stderr: render separator
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I shimmer while I spin and think,
Numbers tick and braille dots blink,
Hints tucked in a JSON seam,
Steps checkmarked as we dream,
A cheerful CLI hop and wink.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: adding a shimmer spinner UI component and enhancing CLI UX for agent responses, which aligns directly with the primary objective.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #336: shimmer animation with spinner, step numbering with hints, success/failure markers, timing display, and separator lines.
Out of Scope Changes check ✅ Passed All changes are within scope: shimmer module and agent loop phases support the primary UX goal, CLI feedback integration implements the UX display, and the registry warning dimming is a minor related enhancement.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/shimmer-ux
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tools that finish in under 1 second now show "3ms" instead of "0.0s".
Slower operations still show "1.2s" format.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/agent/loop.rs (2)

306-313: ⚠️ Potential issue | 🟠 Major

Add a stable tool-call id to ToolFeedback.

Read-only batches still run in parallel, but this payload only exposes tool_name and raw args. The CLI cannot reliably match Starting to Done/Failed when two calls share a name/args or finish out of order, so step lines get mis-numbered or overwritten. Carry the call id here and use it as the correlation key in the renderer.

Suggested direction
 pub struct ToolFeedback {
+    /// Stable tool call id for correlating start/completion in the CLI.
+    pub tool_call_id: Option<String>,
     /// Name of the tool being executed.
     pub tool_name: String,
     /// Current phase of execution.
     pub phase: ToolFeedbackPhase,
     /// Raw JSON arguments for extracting display hints.
     pub args_json: Option<String>,
 }
 let _ = tx.send(ToolFeedback {
+    tool_call_id: Some(id.clone()),
     tool_name: name.clone(),
     phase: ToolFeedbackPhase::Starting,
     args_json: Some(raw_args.clone()),
 });
Based on learnings: Applies to src/agent/**/*.rs : Use `futures::future::join_all` for parallel tool execution.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agent/loop.rs` around lines 306 - 313, ToolFeedback currently only
carries tool_name and args_json so parallel/read-only batches can't correlate
Start/Done/Failed; add a stable per-call identifier field (e.g., call_id: String
or Uuid) to the ToolFeedback struct and populate it wherever ToolFeedback values
are constructed (look for usages of ToolFeedback and the ToolFeedbackPhase enum)
so each tool invocation gets a unique, stable id; update any code paths that
create feedback to pass the new id and adjust the renderer/correlation logic to
use this call_id as the correlation key instead of tool_name/args_json; ensure
any serialization/deserialization and tests are updated accordingly.

317-337: ⚠️ Potential issue | 🟠 Major

Wire the new feedback phases through process_message_streaming() too.

The CLI now relies on Thinking, ThinkingDone, and ResponseReady for the shimmer/separator flow, but the streaming path still only emits tool start/done/fail events in this file. zeptoclaw agent --stream therefore misses the wait indicator before the first token and never closes a tool batch cleanly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agent/loop.rs` around lines 317 - 337, process_message_streaming()
currently only emits tool start/done/fail events so the CLI misses the pre-token
shimmer and final separator; update process_message_streaming() to emit
ToolFeedbackPhase::Thinking before the LLM begins sending tokens,
ToolFeedbackPhase::ThinkingDone when the LLM finishes its token stream (or when
control transfers to tools), and ToolFeedbackPhase::ResponseReady when the
entire streaming/batch completes; ensure these are sent through the same
feedback channel/stream used for tool events so the CLI receives Thinking,
ThinkingDone, and ResponseReady in addition to the existing Starting/Done/Failed
notifications.
🧹 Nitpick comments (1)
src/providers/registry.rs (1)

412-415: Keep terminal styling out of resolve_credential().

This path is used by non-interactive entry points too, so hardcoded ANSI eprintln! here bypasses the configured tracing sink and can leak raw escape codes into daemon/panel/stdio consumers. I’d keep the warning structured here and let the CLI decide whether to dim it when stderr is a TTY.

As per coding guidelines, "Preserve existing module boundaries and public APIs unless explicitly requested to change them."

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/agent/loop.rs`:
- Around line 979-1000: The code sends ToolFeedbackPhase::Thinking before
awaiting provider.chat(...) but returns early on error and never sends
ThinkingDone; modify the chat call in the function (and the similar block
handling lines ~1452-1472) so that the ThinkingDone ToolFeedback is emitted in
both success and error paths—e.g., await provider.chat(...).await inside a match
or use combinators so you send ToolFeedback { phase: ThinkingDone } in both arms
before returning the Ok(response) or propagating the error; reference the
tool_feedback_tx, ToolFeedback, ToolFeedbackPhase::Thinking,
ToolFeedbackPhase::ThinkingDone, and provider.chat(...) to locate and update the
blocks.

In `@src/cli/agent.rs`:
- Around line 167-173: After handling ToolFeedbackPhase::ResponseReady (where
shimmer is stopped and response separator printed), reset the per-turn printer
state by clearing the step counter and had_tools flag so they don't persist
across turns; specifically, in the ToolFeedbackPhase::ResponseReady branch (near
shimmer.take(), s.stop(), and print_response_separator()) assign step = 0 (or
its initial value) and had_tools = false (ensure step and had_tools are declared
mutable) so subsequent turns don't keep incrementing step or always print
separators.

In `@src/cli/shimmer.rs`:
- Around line 151-156: The error truncation is using byte slices which can panic
on UTF-8 boundaries and also leaks bytes before the `content` special-case in
`extract_args_hint()`; replace the byte-slice truncation with a UTF-8-safe
truncation helper (e.g., take the first N Unicode scalar values via
.chars().take(N).collect::<String>() or a reusable helper fn like
`truncate_utf8(s: &str, max_chars: usize) -> String`) and use it in place of the
current `&error[..N]` logic that produces `short_error`; also move the `content`
special-case check in `extract_args_hint()` to run before truncation so long
payloads return the `writing N chars` message rather than leaking the first
bytes. Ensure you update both occurrences referenced (the `short_error` block
and the similar block around lines 197-212) to call the helper.

---

Outside diff comments:
In `@src/agent/loop.rs`:
- Around line 306-313: ToolFeedback currently only carries tool_name and
args_json so parallel/read-only batches can't correlate Start/Done/Failed; add a
stable per-call identifier field (e.g., call_id: String or Uuid) to the
ToolFeedback struct and populate it wherever ToolFeedback values are constructed
(look for usages of ToolFeedback and the ToolFeedbackPhase enum) so each tool
invocation gets a unique, stable id; update any code paths that create feedback
to pass the new id and adjust the renderer/correlation logic to use this call_id
as the correlation key instead of tool_name/args_json; ensure any
serialization/deserialization and tests are updated accordingly.
- Around line 317-337: process_message_streaming() currently only emits tool
start/done/fail events so the CLI misses the pre-token shimmer and final
separator; update process_message_streaming() to emit
ToolFeedbackPhase::Thinking before the LLM begins sending tokens,
ToolFeedbackPhase::ThinkingDone when the LLM finishes its token stream (or when
control transfers to tools), and ToolFeedbackPhase::ResponseReady when the
entire streaming/batch completes; ensure these are sent through the same
feedback channel/stream used for tool events so the CLI receives Thinking,
ThinkingDone, and ResponseReady in addition to the existing Starting/Done/Failed
notifications.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2631539e-6b7c-4c2c-811b-2aa90c779dc5

📥 Commits

Reviewing files that changed from the base of the PR and between b64cb54 and 6140bda.

📒 Files selected for processing (5)
  • src/agent/loop.rs
  • src/cli/agent.rs
  • src/cli/mod.rs
  • src/cli/shimmer.rs
  • src/providers/registry.rs

Comment on lines +979 to +1000
// Send thinking feedback
if let Some(tx) = self.tool_feedback_tx.read().await.as_ref() {
let _ = tx.send(ToolFeedback {
tool_name: String::new(),
phase: ToolFeedbackPhase::Thinking,
args_json: None,
});
}

// Call LLM -- provider lock is NOT held during this await
let mut response = provider
.chat(messages, tool_definitions, model, options.clone())
.await?;

// Send thinking done feedback
if let Some(tx) = self.tool_feedback_tx.read().await.as_ref() {
let _ = tx.send(ToolFeedback {
tool_name: String::new(),
phase: ToolFeedbackPhase::ThinkingDone,
args_json: None,
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the thinking lifecycle exception-safe.

Both blocks emit Thinking and then immediately await? the provider call. If chat() errors, the function returns before ThinkingDone, which leaves the CLI shimmer running and the cursor hidden for the rest of the session. Emit the stop event from a match/guard so it always fires on both success and error paths.

Also applies to: 1452-1472

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agent/loop.rs` around lines 979 - 1000, The code sends
ToolFeedbackPhase::Thinking before awaiting provider.chat(...) but returns early
on error and never sends ThinkingDone; modify the chat call in the function (and
the similar block handling lines ~1452-1472) so that the ThinkingDone
ToolFeedback is emitted in both success and error paths—e.g., await
provider.chat(...).await inside a match or use combinators so you send
ToolFeedback { phase: ThinkingDone } in both arms before returning the
Ok(response) or propagating the error; reference the tool_feedback_tx,
ToolFeedback, ToolFeedbackPhase::Thinking, ToolFeedbackPhase::ThinkingDone, and
provider.chat(...) to locate and update the blocks.

Comment on lines +167 to +173
ToolFeedbackPhase::ResponseReady => {
if let Some(s) = shimmer.take() {
s.stop();
}
if had_tools {
print_response_separator();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reset the per-turn printer state after ResponseReady.

step and had_tools live for the entire REPL session, and this branch never clears them. After the first tool-using turn, later turns keep incrementing step numbers and even plain-text replies still print the separator because had_tools stays true.

Suggested fix
                 ToolFeedbackPhase::ResponseReady => {
                     if let Some(s) = shimmer.take() {
                         s.stop();
                     }
                     if had_tools {
                         print_response_separator();
                     }
+                    step = 0;
+                    had_tools = false;
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ToolFeedbackPhase::ResponseReady => {
if let Some(s) = shimmer.take() {
s.stop();
}
if had_tools {
print_response_separator();
}
ToolFeedbackPhase::ResponseReady => {
if let Some(s) = shimmer.take() {
s.stop();
}
if had_tools {
print_response_separator();
}
step = 0;
had_tools = false;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/agent.rs` around lines 167 - 173, After handling
ToolFeedbackPhase::ResponseReady (where shimmer is stopped and response
separator printed), reset the per-turn printer state by clearing the step
counter and had_tools flag so they don't persist across turns; specifically, in
the ToolFeedbackPhase::ResponseReady branch (near shimmer.take(), s.stop(), and
print_response_separator()) assign step = 0 (or its initial value) and had_tools
= false (ensure step and had_tools are declared mutable) so subsequent turns
don't keep incrementing step or always print separators.

Comment on lines +151 to +156
// Truncate error to first 80 chars for display
let short_error = if error.len() > 80 {
format!("{}…", &error[..80])
} else {
error.to_string()
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use a UTF-8-safe truncation helper here.

Both &[..N] slices are byte-based, so a Unicode path or error can panic the CLI once it crosses the cutoff. In extract_args_hint(), this also runs before the content special-case, so long payloads leak their first 50 bytes instead of returning writing N chars.

Suggested fix
+fn truncate_for_display(s: &str, max_chars: usize) -> String {
+    let mut out = String::new();
+    for (idx, ch) in s.chars().enumerate() {
+        if idx == max_chars {
+            out.push('…');
+            return out;
+        }
+        out.push(ch);
+    }
+    out
+}
+
 ...
-    let short_error = if error.len() > 80 {
-        format!("{}…", &error[..80])
+    let short_error = if error.chars().count() > 80 {
+        truncate_for_display(error, 80)
     } else {
         error.to_string()
     };
 ...
-            if s.len() > 50 {
-                return Some(format!("{}…", &s[..50]));
-            }
             // For "content" key, show just "writing N chars"
             if *key == "content" {
-                return Some(format!("writing {} chars", s.len()));
+                return Some(format!("writing {} chars", s.chars().count()));
+            }
+            if s.chars().count() > 50 {
+                return Some(truncate_for_display(&s, 50));
             }

Also applies to: 197-212

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/shimmer.rs` around lines 151 - 156, The error truncation is using
byte slices which can panic on UTF-8 boundaries and also leaks bytes before the
`content` special-case in `extract_args_hint()`; replace the byte-slice
truncation with a UTF-8-safe truncation helper (e.g., take the first N Unicode
scalar values via .chars().take(N).collect::<String>() or a reusable helper fn
like `truncate_utf8(s: &str, max_chars: usize) -> String`) and use it in place
of the current `&error[..N]` logic that produces `short_error`; also move the
`content` special-case check in `extract_args_hint()` to run before truncation
so long payloads return the `writing N chars` message rather than leaking the
first bytes. Ensure you update both occurrences referenced (the `short_error`
block and the similar block around lines 197-212) to call the helper.

@qhkm qhkm merged commit 397f4ec into main Mar 12, 2026
8 of 9 checks passed
@qhkm qhkm deleted the feat/shimmer-ux branch March 12, 2026 19:08
taqtiqa-mark pushed a commit to taqtiqa-mark/zeptoclaw that referenced this pull request Mar 25, 2026
## Summary
- Gradient text shimmer animation on "Thinking..." while waiting for LLM
response
- Step numbering with checkmarks (✓/✗) for tool execution progress
- Tool argument hints (e.g. `read_file → calculator.py`)
- Separator line between tool progress and final response
- Dimmed Claude subscription token warning

## Before
```
2026-03-12T18:27:19Z  WARN zeptoclaw::providers::registry: Using Claude subscription...
  [read_file] Running... done (0.0s)
  [edit_file] Running... done (0.0s)
```

## After
```
Using Claude subscription token (unofficial)...     ← dimmed
  ⠋ Thinking...                                     ← shimmer gradient wave
  ✓ Step 1 · read_file → calculator.py (0.1s)
  ✓ Step 2 · edit_file → src/main.rs (0.0s)
  ✗ Step 3 · shell → cargo build (5.0s: exit code 1)

  ────────────────────────────────────────

  Here's what I changed: ...
```

## Changes
- **New:** `src/cli/shimmer.rs` — ShimmerSpinner, format helpers, arg
extraction
- **Modified:** `src/agent/loop.rs` —
Thinking/ThinkingDone/ResponseReady phases, args_json field
- **Modified:** `src/cli/agent.rs` — Shimmer-aware feedback printer
- **Modified:** `src/providers/registry.rs` — Dim warning with ANSI
escape

## Test plan
- [x] `cargo fmt -- --check` passes
- [x] `cargo clippy -- -D warnings` passes
- [x] `cargo test --lib` — 3126 passed, 0 failed
- [ ] Manual: `zeptoclaw agent --template coder -m "..."` shows shimmer
+ steps
- [ ] Manual: interactive mode shows shimmer between turns

Closes qhkm#336

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Animated CLI progress indicator with shimmer and spinner during
processing.
* New feedback states for "thinking" and final "response ready" visible
in the UI.
  * Tool feedback can include concise argument hints alongside status.

* **Improvements**
* Richer, line-updating tool status messages with elapsed time and
response separators.
* Less intrusive dimmed warning output for unofficial credential
notices.

* **Tests**
  * Updated tests to cover new feedback fields and formatting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: shimmer + spinner CLI loading UX for agent responses

1 participant