CLI
The CLI channel turns your terminal into a Sona client. Two modes:
- REPL (default) — interactive prompt with JLine: arrow-history, slash commands, streaming token render.
- JSON — newline-delimited JSON on stdin/stdout, suitable for pipelines, e2e smoke tests, and shell automation.
Unlike Telegram, the CLI channel has no auth handshake — your terminal is the authority. If you don't want the keys to your daemon in ~/.sona/, don't run sona chat.
REPL mode
sona chatThis opens a JLine session against your local daemon. You'll see the user name prompt and a streaming reply:
sona › what time is it in istanbul?
The current time in Istanbul is 03:14 UTC+3 (assuming standard time).
sona › /exitBuilt-in slash commands (intercepted locally by the REPL — they never reach the gateway):
| Command | Effect |
|---|---|
/exit, /quit | Close the REPL, return to your shell. The daemon keeps running. |
/clear | ANSI clear-screen. History is preserved. |
Anything else flows to the gateway as a UnifiedMessage — including text that starts with /, in case you've registered a skill that matches a slash prefix.
Workspace selection
By default sona chat uses the default workspace. To pick a specific one:
sona chat --workspace personalIf the workspace name doesn't match, the CLI prints the list of available workspaces and exits.
Streaming
REPL mode streams the LLM reply token-by-token as it arrives. There is one assistant: prefix per turn, then the deltas concatenate inline, then a newline closes the line. No per-token re-render, no flicker — the assistant prefix is emitted once on the first chunk and the cursor walks forward.
Internally this uses the framework's StreamingChannelAdapter mixin (TNS-440); the gateway routes every chunk through sendChunk(UnifiedChunk.text(...)) and a final UnifiedChunk.done(...). See How It Works for the SPI surface.
JSON mode
sona chat --jsonThis is the same Gateway-to-channel path, but the wire format is newline-delimited JSON on both directions. Useful for piping into Sona from another program, recording golden transcripts in tests, or driving the daemon from a script without an interactive terminal.
Input
One JSON object per line, each with at least a text field:
{"text": "hi"}
{"text": "what time is it in istanbul?"}Other fields on the object are reserved for future use; today they're ignored.
Output
Streaming responses come back as a sequence of chunk records, terminated by a done: true envelope (TNS-458):
{"type":"chunk","content":"Hello","done":false}
{"type":"chunk","content":" ","done":false}
{"type":"chunk","content":"world","done":false}
{"type":"chunk","content":"","done":true}Pipeline-friendly consumers can stream the body by concatenating content fields until done: true; one-shot consumers can buffer until done then read the response in one go.
Errors come back as a single error record:
{"type":"error","message":"invalid JSON: Unexpected character (' ')..."}Sona writes one record per inbound message; a single sona chat --json invocation can handle multiple turns in the same conversation by keeping the process open and piping more text lines in.
Example: shell pipeline
printf '%s\n' \
'{"text":"hi"}' \
'{"text":"summarise the TnsAI framework in one paragraph"}' \
| sona chat --json \
| jq -r 'select(.type=="chunk") | .content' \
| tr -d '\n'This streams two turns through Sona, extracts the content chunks, and strips chunk-boundary newlines so you get the assistant's reply as flowing text.
Sessions
CLI sessions are scoped to your local $USER — every sona chat invocation reuses the same (channelId=cli, senderId=<your-username>) pair and thus the same chat history. Killing and re-running sona chat resumes the same conversation.
If you want a fresh conversation, today the cleanest way is:
rm ~/.sona/sessions/cli:<your-username>.jsonA /new slash command is tracked as a future improvement.
When to use which mode
| Use case | Mode |
|---|---|
| Quick interactive question | REPL |
| Pair-programming with Sona, multi-turn | REPL |
Shell pipeline / xargs over a CSV | JSON |
| E2E test that asserts response shape | JSON |
| Recording a golden transcript for a regression suite | JSON |
| Driving Sona from another program (Python script, Node, …) | JSON |
JSON mode never prints prompts, banners, ANSI colour, or anything other than JSON records — safe to pipe.
Comparison to Telegram
| Telegram | CLI | |
|---|---|---|
| Auth | Pairing code + owner approval | Local user (no handshake) |
| Streaming | Yes (sends one envelope per N tokens) | Yes (REPL) / Yes (JSON, per-chunk records) |
| Media | Photos, documents, voice (inbound) | No |
| Threads | Yes (per-chat threading) | One conversation per $USER |
| Use case | Phone / off-machine | Local dev, CI smoke tests, scripts |
| Latency | Network round-trip via Telegram servers | Direct stdin/stdout |
The two adapters are independent. You can run both at the same time — they're separate sessions even for "the same" person.