← All 19 apps

SugarCrush

🤖 SugarCrush

Terminal AI coding agent

port of charmbracelet/crush ai chat anthropic openai ollama

SugarCrush code coverage

Terminal AI coding agent. Seven LLM providers (OpenAI, Anthropic, Claude Code, SGLang, Bedrock, Vertex, custom), model-driven tool calling gated by hooks, prompt-injecting skills, sub-agents, an MCP client/server, and SQLite session history — over a candy-core render loop with Markdown replies. Runs offline out of the box.

Install

composer require sugarcraft/sugar-crush

Quickstart

composer install
./bin/sugarcrush

# With no configuration it runs the offline EchoProvider through the full
# engine — zero network, zero keys. Point it at a real model instead:
export SUGARCRUSH_PROVIDER=openai
export OPENAI_API_KEY=sk-...
export SUGARCRUSH_MODEL=gpt-4o     # optional; provider default otherwise
./bin/sugarcrush

# One-shot (never attaches to the TTY, never opens the alt-screen):
sugarcrush -p "explain the Width helper"
sugarcrush -p "audit this" --output-format json | jq -r .result

SUGARCRUSH_PROVIDER accepts openai, anthropic, claude-code, sglang, bedrock, vertex or custom. To skip PHP SDKs entirely, set SUGARCRUSH_BACKEND_CMD to a script that reads JSON history on stdin and writes the reply to stdout. See docs/ENVIRONMENT.md for the full environment-variable reference.

What's in the box

Seven providersOne ProviderInterface — OpenAI, Anthropic, Claude Code CLI, SGLang, Bedrock, Vertex, custom — with capability introspection, streaming, function calling, embeddings and per-model cost.
Bounded agent loopEngineBackend calls the provider, runs the returned tool calls through the hook gate, feeds results back, and repeats until the model answers without calling tools or maxSteps is hit.
Nine built-in toolsBash, Read, Edit, Glob, Grep, WebFetch, WebSearch, Doctor and Skill, all jailed to the project root. Implement Tools\Tool for your own.
Hooks that can say noPre/post-tool-use guards returning allow / deny / modify / ask. Built-ins: ProtectFilesHook, ConfirmRemoveHook, AuditHook, plus BashEscapeDenyHook when a worktree root is set and PermissionGateHook last, which adapts the six-mode PermissionGate onto the same chain. YAML config and external ScriptHook supported, the latter selecting its outcome by exit code.
Progressive skillsFrontmatter SKILL.md files inject prompt context. The system prompt carries only name + description; the model pulls a full body through the Skill tool. 12 built-ins, plus ~/.sugar-crush/skills and <project>/.sugar-crush/skills.
Sub-agents, teams & worktreesSub-agent presets with their own model, tools, skills and permission mode, dispatched through a pcntl_fork worker pool; teammates claim tasks from a flock-backed SQLite task list inside isolated git worktrees.
WorkflowsWorkflowEngine runs multi-stage agent pipelines — sequential, fan-out parallel, chained pipeline, and task-then-verifier — from a PHP DSL or YAML, with SIGINT-safe pause and stage-granularity resume.
MCP client & serverMulti-server client over stdio + HTTP reading .mcp.json with ${VAR} interpolation, plus stdio/HTTP servers to host your own tools. Per-preset mcpServers allowlists are enforced, not decorative.
SQLite sessionsWAL-mode persistence of sessions, messages and tool calls with FK-enforced cascade, fork/rename/resume, and opt-in retention pruning that never drops a session you named.
Real TEA render loopA candy-core Model/Program tree with buffer-diffed output: Markdown-rendered replies via CandyShine, live tool-call lifecycle, unified diffs for Edit results, inline images via candy-mosaic, mouse zones and a command palette.

Source & demos

Try the quickstart →

Key Classes

ClassMethodDescription
ProviderInterfacecomplete(CompleteRequest): CompleteResponseThe single LLM abstraction — one batch completion
ProviderInterfacecompleteStream(CompleteRequest): \GeneratorSame call, yielded as streaming chunks
ProviderInterfacesupportsStreaming(), supportsVision(), contextWindow(), costPer1kTokens(model, direction)Capability + cost introspection, so callers do not guess
EngineBackendnew(provider, model)Factory — bridges the chat Backend seam to the agent engine
EngineBackendwithTools(tools), withHooks(manager), withSkillRegistry(registry), withMaxSteps(n)Fluent wiring of the agent loop (immutable — each returns a new instance)
EngineBackendcomplete(history, ?onToken, ?onEvent): MessageRun one bounded agentic turn, emitting tool-lifecycle events
HookManagerregisterBuiltIns()Registers ProtectFilesHook, ConfirmRemoveHook, AuditHook
HookManagerpreToolUse(HookContext): HookResultGate a tool call — allow / deny / modify / ask
HookManagerresolveAsk(ask, approved, feedback)Settle an answered ASK into a final allow or deny
SkillRegistryfindForPrompt(prompt): arrayMatch discovered SKILL.md files to the current turn
SkillRegistrygetForPaths(paths): arrayPath-scoped skills for files a tool just touched
SkillRegistryisAutoInvocable(name), isUserInvocable(name), isContextFork(name)Frontmatter flag enforcement, not decoration
AgentPreset__construct(name, description, tools, model, permissionMode, skills, mcpServers, …)Readonly sub-agent descriptor — model, tool allow/deny, skills, MCP allowlist, effort, isolation
McpClientstartServers(), stopServers()Launch / tear down the configured stdio + HTTP MCP servers
McpClientlistTools(), callTool(server, tool, args), callToolByName(tool, args)Discover and invoke MCP tools, filtered by the active preset's allowlist
PermissionGateevaluate(ToolCall): PermissionDecisionEvaluate a call against the active mode, with a mode-independent rm-rf circuit breaker
PermissionGatemode(): PermissionModeOne of default, accept-edits, plan, auto, dont-ask, bypass-permissions
WorkflowEnginerun(workflowPath, context): WorkflowResultExecute a YAML multi-stage agent pipeline
WorkflowEnginerunFromPhp(workflowClass, context): WorkflowResultSame, from the PHP builder DSL
WorkflowEnginepause(id), resume(id), getStatus(id)Stage-granularity pause / resume across a signal
SessionStorecreateSession(id, provider, model, ?systemPrompt, ?name)Open a session row in the SQLite (WAL) store
SessionStoreaddMessage(sessionId, message), addToolCall(sessionId, messageId, toolCall)Append a turn and its tool calls, FK-enforced
SessionStorelistSessions(limit), forkSession(id), renameSession(id, name)Back the /sessions, /fork and /rename commands
SessionStorepruneSessions(daysOld, ?exemptSessionId): intOpt-in retention sweep; never drops a named or resuming session

Demos.

VHS-recorded GIFs of every example shipped with the app. Regenerated automatically on every push that touches the source.

Chat

Chat

Full chat session with scrollback + Markdown-rendered replies.