candycore / sugar-crush
Chat-shell TUI for AI coding assistants — port of charmbracelet/crush. Pluggable Backend interface (ship your own Anthropic / OpenAI / Ollama / shell-out adapter). Markdown rendering of replies via CandyShine, scrollback viewport via SugarBits, input area via SugarBits TextArea.
Requires
- php: >=8.1
- sugarcraft/candy-core: @dev
- sugarcraft/candy-shine: @dev
- sugarcraft/candy-sprinkles: @dev
Requires (Dev)
- phpunit/phpunit: ^10.0
Suggests
- ext-curl: Required by HTTP-based backends you might write (Anthropic, OpenAI, Ollama).
This package is not auto-updated.
Last update: 2026-05-07 16:32:13 UTC
README
SugarCrush
Chat-shell TUI for AI coding assistants — port of charmbracelet/crush. Pluggable backends (ship your own Anthropic / OpenAI / Ollama / shell-out adapter), Markdown rendering of replies via CandyShine, scrollback above a fixed input box.
┌─ SugarCrush ───────────────────────────────────────┐
│ user> explain fiber-based scheduling in PHP │
│ │
│ assistant │
│ ## Fibers (PHP 8.1+) │
│ │
│ Fibers are cooperative units of execution … │
└────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ > how do they relate to ReactPHP?█ │
└────────────────────────────────────────────────────┘
Enter to send · Esc / ^C to quit
Run it
composer install ./bin/sugarcrush
By default it ships with EchoBackend so the binary is runnable offline (the assistant just echoes what you typed). To wire it to a real LLM, set $SUGARCRUSH_BACKEND_CMD to a command that reads JSON history on stdin and writes the reply to stdout:
export SUGARCRUSH_BACKEND_CMD=~/bin/anthropic-stream.sh ./bin/sugarcrush
Sample wrapper script (Anthropic)
#!/usr/bin/env bash # ~/bin/anthropic-stream.sh payload=$(jq -nc --argjson h "$(cat)" \ '{model: "claude-opus-4-7", max_tokens: 4096, messages: $h}') curl -sN https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d "$payload" \ | jq -r '.content[0].text'
chmod +x ~/bin/anthropic-stream.sh and you're done.
The wrapper-script approach is deliberate: keeps the PHP package network-dep-free, lets you swap providers without changing PHP code, and makes prompt-engineering iteration as fast as editing a shell script.
Writing a custom Backend
If you'd rather skip the shell-out dance and integrate the SDK directly, implement the Backend interface in PHP:
use SugarCraft\Crush\{Backend, Chat, Message}; final class MyBackend implements Backend { public function complete(array $history): Message { $reply = /* your call here, returning a string */; return Message::assistant($reply); } } (new Program(new Chat(backend: new MyBackend())))->run();
Architecture
| File | Role |
|---|---|
Role enum |
system / user / assistant — matches every API's wire vocab |
Message |
VO: role, content, createdAt; toWire() for adapters |
Backend interface |
complete(list<Message>): Message |
Backend\EchoBackend |
Offline default — echoes the last user message |
Backend\CommandBackend |
Shells out via proc_open; JSON history → stdin → stdout reply |
AssistantMsg |
Internal Msg — fires when a backend completion arrives |
Chat |
SugarCraft Model — history, input buffer, inFlight gate |
Renderer |
Pure view fn — CandyShine-rendered scrollback + input box |
Test plan
- 21 tests / 43 assertions
Message: factories, wire shape, custom timestampsEchoBackend: echoes most recent user, handles empty historyCommandBackend: history is JSON-piped to stdin, exit code surfaced as error message, missing command handled gracefullyChat: type accumulation, space, UTF-8-aware backspace, Enter submits + clears + arms inFlight, empty submit no-op, AssistantMsg appends + clears inFlight, keystrokes ignored while inFlight, Esc quits, full echo round-trip via the realEchoBackend
Status
Phase 9+ entry #17 — first cut. Single-shot replies (no streaming yet); StreamingBackend interface is the obvious follow-up. Markdown rendering, persistent input buffer, and the inFlight gate are all wired.
