Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/claude_p/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,28 @@ def normalize_answer(text: str) -> str:
return text.strip()


# Assistant-message markers used by the interactive `claude` TUI.
# Pre-2.1 builds rendered the leading bullet as U+23FA ("⏺"); 2.1+
# builds emit U+25CF ("●"). Support both so the wrapper keeps
# working across claude CLI versions without a follow-up patch.
ASSISTANT_MARKERS = ("⏺", "●")


def extract_assistant_snapshot(transcript: str) -> str:
clean = clean_terminal(transcript)
marker = clean.rfind("⏺")
if marker < 0:

best_pos = -1
best_marker = ""
for marker in ASSISTANT_MARKERS:
pos = clean.rfind(marker)
if pos > best_pos:
best_pos = pos
best_marker = marker

if best_pos < 0:
return ""
after = clean[marker + len("⏺") :]

after = clean[best_pos + len(best_marker) :]
return normalize_answer(after)


Expand Down
31 changes: 31 additions & 0 deletions tests/test_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

from claude_p import ClaudePOptions
from claude_p.cli import (
ASSISTANT_MARKERS,
build_tui_env,
classify_failure,
extract_assistant_snapshot,
is_terminal_assistant_message,
read_persisted_assistant,
recover_prompt_from_variadic_args,
Expand Down Expand Up @@ -110,3 +112,32 @@ def test_subscription_backend_strips_provider_env(monkeypatch):
assert "ANTHROPIC_AUTH_TOKEN" not in env
assert "ANTHROPIC_BASE_URL" not in env
assert env["NO_COLOR"] == "1"


def test_assistant_markers_includes_both_legacy_and_current():
assert "⏺" in ASSISTANT_MARKERS # U+23FA (legacy claude < 2.1)
assert "●" in ASSISTANT_MARKERS # U+25CF (claude 2.1+)


def test_extract_assistant_snapshot_legacy_marker():
transcript = "❯ ping\n⏺ pong\n"
assert extract_assistant_snapshot(transcript) == "pong"


def test_extract_assistant_snapshot_current_marker():
transcript = "❯ ping\n● pong\n"
assert extract_assistant_snapshot(transcript) == "pong"


def test_extract_assistant_snapshot_prefers_latest_marker_across_variants():
# If both markers appear, use the right-most one regardless of variant —
# that's the most recent assistant turn in the transcript.
transcript = "⏺ old answer\n❯ new prompt\n● new answer\n"
assert extract_assistant_snapshot(transcript) == "new answer"

transcript_reversed = "● old answer\n❯ new prompt\n⏺ new answer\n"
assert extract_assistant_snapshot(transcript_reversed) == "new answer"


def test_extract_assistant_snapshot_returns_empty_when_no_marker():
assert extract_assistant_snapshot("just some text without any marker") == ""