Welcome to Snow CLI! Agentic coding in your terminal.
Want to quickly experience the SSE client? We provide a complete browser test client:
Location: source/test/sse-client/index.html
Simply open this file in your browser and connect to the SSE server to start testing.
SSE (Server-Sent Events) service mode allows you to run Snow CLI as a backend service, providing AI capabilities to external applications. It is perfect for:
- Web application integration
- Mobile app backend
- Third-party tool integration
- Microservice architecture
- Custom chat interfaces
# Use default port 3000 (foreground)
snow --sse
# Specify port
snow --sse --sse-port 8080
# Specify working directory
snow --sse --work-dir /path/to/project
# Custom interaction timeout (default 300000ms, i.e., 5 minutes)
snow --sse --sse-timeout 600000 # Set to 10 minutes
# Combined usage
snow --sse --sse-port 8080 --work-dir /path/to/project --sse-timeout 600000If you don't want to occupy the terminal, you can run the server as a background daemon:
# Start background daemon (default port 3000)
snow --sse-daemon
# Specify different ports (supports multiple instances)
snow --sse-daemon --sse-port 3000
snow --sse-daemon --sse-port 8080
snow --sse-daemon --sse-port 9000
# Specify full parameters
snow --sse-daemon --sse-port 8080 --work-dir /path/to/project --sse-timeout 600000
# Check all daemon status
snow --sse-status
# Stop daemon (three methods)
snow --sse-stop # Stop default port 3000 daemon
snow --sse-stop --sse-port 8080 # Stop by port number
snow --sse-stop 12345 # Stop by PIDDaemon features:
- Supports multiple instances (different ports)
- Runs in background, doesn't occupy terminal
- Individual log file per port:
~/.snow/sse-logs/port-<port>.log - Individual PID file per port:
~/.snow/sse-daemons/port-<port>.pid - Supports stop by port or PID
- Status check shows all running daemons
While the SSE server itself doesn't use the --yolo parameter, you can achieve similar functionality through the following methods:
Method 1: Client message with yoloMode
This is the recommended approach, allowing flexible control over whether each request uses YOLO mode:
// Specify YOLO mode when sending message
await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
type: 'chat',
content: 'Your question',
yoloMode: true, // Enable YOLO mode
}),
});Method 2: Configure auto-approval list
Add commonly used tools to the project's permission configuration file for default auto-approval:
# Edit project permission configuration
vi .snow/permissions.json{
"alwaysApprovedTools": [
"filesystem-read",
"filesystem-edit",
"filesystem-create",
"codebase-search",
"ace-search",
"notebook-add"
]
}This way, tools in the list will be automatically approved without requiring confirmation each time.
Notes:
- SSE server startup doesn't support
--yoloparameter - YOLO mode needs to be enabled via client message's
yoloModefield - Or implement tool auto-approval via
.snow/permissions.jsonconfiguration - Sensitive commands require confirmation even in YOLO mode
After startup, the terminal will display a beautiful server status interface:
✓ SSE server started
Port: 3000 | Working Directory: /Users/xxx/project | ● Running
Available Endpoints:
http://localhost:3000/events
POST http://localhost:3000/message
POST http://localhost:3000/session/create
POST http://localhost:3000/session/load
GET http://localhost:3000/session/list
GET http://localhost:3000/session/rollback-points?sessionId={sessionId}
DELETE http://localhost:3000/session/{sessionId}
GET http://localhost:3000/health
Running Logs:
[14:30:45] SSE service started on port 3000
[14:30:50] Created new session: abc-123
Press Ctrl+C to stop server
Endpoint: GET /events
Establish SSE connection to receive real-time event stream.
const eventSource = new EventSource('http://localhost:3000/events');
eventSource.onmessage = event => {
const data = JSON.parse(event.data);
console.log('Received event:', data);
switch (data.type) {
case 'connected':
console.log(
'Connection successful, connection ID:',
data.data.connectionId,
);
break;
case 'message':
if (data.data.streaming) {
console.log('AI is responding:', data.data.content);
} else if (data.data.role === 'user') {
console.log('User message:', data.data.content);
}
break;
case 'tool_confirmation_request':
// User confirmation needed for tool execution
handleToolConfirmation(data);
break;
case 'complete':
console.log('Conversation complete');
break;
}
};Endpoint: POST /message
Content-Type: application/json
async function sendMessage(content) {
const response = await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'chat',
content: content,
}),
});
return await response.json();
}
// Usage example
await sendMessage('Help me create a React component');async function continueConversation(content, sessionId) {
const response = await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'chat',
content: content,
sessionId: sessionId, // Use session ID to continue conversation
}),
});
return await response.json();
}
// Session ID will be returned in complete event
eventSource.onmessage = event => {
const data = JSON.parse(event.data);
if (data.type === 'complete') {
const sessionId = data.data.sessionId;
console.log('Session ID:', sessionId);
}
};async function sendImageMessage(content, imageFile) {
// Convert image to Base64 Data URI
const reader = new FileReader();
const imageData = await new Promise((resolve, reject) => {
reader.onload = e => resolve(e.target.result);
reader.onerror = reject;
reader.readAsDataURL(imageFile);
});
const response = await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'chat',
content: content || 'Please analyze this image',
images: [
{
data: imageData, // Complete data URI, e.g., data:image/png;base64,iVBORw0KG...
mimeType: imageFile.type, // e.g., image/png, image/jpeg
},
],
}),
});
return await response.json();
}
// Usage example
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async e => {
const file = e.target.files[0];
if (file && file.type.startsWith('image/')) {
await sendImageMessage('What is this?', file);
}
});async function abortTask(sessionId) {
const response = await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'abort',
sessionId: sessionId,
}),
});
return await response.json();
}
// Listen for abort confirmation
eventSource.onmessage = event => {
const data = JSON.parse(event.data);
if (data.type === 'complete' && data.data.cancelled) {
console.log('Task has been aborted by user');
}
};async function sendWithYolo(content) {
const response = await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'chat',
content: content,
yoloMode: true, // Auto-approve all non-sensitive tools
}),
});
return await response.json();
}Endpoint: POST /session/create
Content-Type: application/json
Create a new conversation session, returns session information and automatically binds to current connection.
async function createSession() {
const response = await fetch('http://localhost:3000/session/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
connectionId: 'conn_xxx', // Optional, specify connection ID
}),
});
const data = await response.json();
console.log('Session ID:', data.session.id);
console.log('Created at:', data.session.createdAt);
return data.session;
}Endpoint: POST /session/load
Content-Type: application/json
Load a saved session to restore conversation context.
async function loadSession(sessionId) {
const response = await fetch('http://localhost:3000/session/load', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
sessionId: sessionId,
connectionId: 'conn_xxx', // Optional, specify connection ID
}),
});
const data = await response.json();
if (data.success) {
console.log('Session loaded:', data.session.id);
console.log('Message count:', data.session.messages.length);
return data.session;
} else {
console.error('Load failed:', data.error);
}
}Endpoint: GET /session/list
Query Parameters:
page: Page number, starting from 0 (optional, default 0)pageSize: Items per page (optional, default 20, max 200)q: Search keyword (optional, searches message content in sessions)
Get all saved sessions with pagination and search support.
async function listSessions(page = 0, pageSize = 20, searchQuery = '') {
const params = new URLSearchParams({
page: page.toString(),
pageSize: pageSize.toString(),
});
if (searchQuery) {
params.append('q', searchQuery);
}
const response = await fetch(`http://localhost:3000/session/list?${params}`);
const data = await response.json();
console.log('Total sessions:', data.total);
console.log('Current page:', data.page);
console.log('Page size:', data.pageSize);
console.log('Session list:', data.sessions);
// Session list example
// data.sessions = [
// {
// id: 'abc-123',
// createdAt: '2025-12-30T10:00:00.000Z',
// updatedAt: '2025-12-30T10:30:00.000Z',
// messageCount: 10,
// firstMessage: 'Help me create a function'
// },
// ...
// ]
return data;
}Endpoint: GET /session/rollback-points
Query Parameters:
sessionId: Session ID (required)
Returns a list of user messages in the specified session that can be used as rollback points (demo use).
async function getRollbackPoints(sessionId) {
const params = new URLSearchParams({sessionId});
const response = await fetch(
`http://localhost:3000/session/rollback-points?${params.toString()}`,
);
const data = await response.json();
// Example (key fields):
// {
// success: true,
// sessionId: 'abc-123',
// points: [
// {
// messageIndex: 0,
// role: 'user',
// timestamp: 1730000000000,
// summary: '...',
// hasSnapshot: true,
// snapshot: {timestamp: 1730000000000, fileCount: 12},
// filesToRollbackCount: 5
// }
// ]
// }
return data;
}Endpoint: DELETE /session/{sessionId}
Delete the specified session and all its data.
async function deleteSession(sessionId) {
const response = await fetch(`http://localhost:3000/session/${sessionId}`, {
method: 'DELETE',
});
const data = await response.json();
if (data.success) {
console.log('Session deleted:', data.deleted);
}
return data;
}Endpoint: GET /health
Check server status and current connection count.
async function checkHealth() {
const response = await fetch('http://localhost:3000/health');
const data = await response.json();
console.log('Status:', data.status);
console.log('Connections:', data.connections);
}Connection successful event.
{
type: 'connected',
data: {
connectionId: 'conn_1234567890'
},
timestamp: '2025-12-30T15:30:00.000Z'
}Message event (user or AI).
// User message
{
type: 'message',
data: {
role: 'user',
content: 'Help me create a function'
}
}
// AI streaming response
{
type: 'message',
data: {
role: 'assistant',
content: 'Sure, let me help you...',
streaming: true
}
}
// AI final response
{
type: 'message',
data: {
role: 'assistant',
content: 'Complete response content',
streaming: false
}
}Tool invocation event.
{
type: 'tool_call',
data: {
name: 'filesystem-create',
arguments: {
filePath: 'example.js',
content: '...'
}
}
}Request confirmation for tool execution.
{
type: 'tool_confirmation_request',
data: {
toolCall: {
function: {
name: 'terminal-execute',
arguments: '{"command":"rm -rf node_modules"}'
}
},
isSensitive: true, // Whether it's a sensitive command
sensitiveInfo: {
pattern: 'rm -rf',
description: 'Delete files or directories'
},
availableOptions: [
{value: 'approve', label: 'Approve once'},
{value: 'approve_always', label: 'Always approve'}, // Only for non-sensitive commands
{value: 'reject_with_reply', label: 'Reject with reply'},
{value: 'reject', label: 'Reject and end session'}
]
},
requestId: 'req_1234567890'
}Tool execution result.
{
type: 'tool_result',
data: {
content: 'Execution successful',
status: 'success'
}
}AI asking user a question.
{
type: 'user_question_request',
data: {
question: 'Please select an option',
options: ['Option 1', 'Option 2', 'Option 3'],
multiSelect: false
},
requestId: 'req_1234567890'
}Token usage information.
{
type: 'usage',
data: {
input_tokens: 150,
output_tokens: 200
}
}Error message.
{
type: 'error',
data: {
message: 'Error description',
stack: 'Error stack (optional)'
}
}Conversation completed.
{
type: 'complete',
data: {
usage: {
input_tokens: 150,
output_tokens: 200
},
tokenCount: 350,
sessionId: 'abc-123-def-456', // Session ID
cancelled: false // Whether cancelled by user (optional)
}
}Task abort request (sent by client).
// Client sends abort request
await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
type: 'abort',
sessionId: 'abc-123-def-456'
})
});
// Server responds with abort confirmation
{
type: 'message',
data: {
role: 'assistant',
content: 'Task has been aborted'
},
timestamp: '2025-12-30T15:30:00.000Z'
}
// Followed by complete event
{
type: 'complete',
data: {
usage: {input_tokens: 0, output_tokens: 0},
tokenCount: 0,
sessionId: 'abc-123-def-456',
cancelled: true
}
}When receiving a tool_confirmation_request event, send a confirmation response:
async function handleToolConfirmation(event) {
const toolCall = event.data.toolCall;
const options = event.data.availableOptions;
// Display tool information to user
console.log('Tool:', toolCall.function.name);
console.log('Arguments:', toolCall.function.arguments);
console.log('Available options:', options);
// Send response after user selection
const response = await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'tool_confirmation_response',
requestId: event.requestId,
response: 'approve', // or 'approve_always', 'reject', {type: 'reject_with_reply', reason: '...'}
}),
});
return await response.json();
}| Option | Value | Description | Applicable Scenario |
|---|---|---|---|
| Approve once | 'approve' |
Approve this execution only | All tools |
| Always approve | 'approve_always' |
Approve and add to auto-approval list | Non-sensitive only |
| Reject with reply | {type: 'reject_with_reply', reason: '...'} |
Reject and tell AI the reason | All tools |
| Reject and end | 'reject' |
Reject and end session | All tools |
The system automatically detects sensitive commands (like rm -rf, sudo, etc.). Sensitive commands:
- Will not show "Always approve" option
- Require confirmation even in YOLO mode
- Display warning information and matched command pattern
For sensitive command configuration, refer to: Sensitive Commands Configuration
When receiving a user_question_request event:
async function handleUserQuestion(event) {
const question = event.data.question;
const options = event.data.options;
const multiSelect = event.data.multiSelect;
// Display question and options to user
console.log('Question:', question);
console.log('Options:', options);
// Send response after user selection
const response = await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'user_question_response',
requestId: event.requestId,
response: {
selected: multiSelect ? ['Option 1', 'Option 2'] : 'Option 1',
customInput: '', // Optional custom input
},
}),
});
return await response.json();
}SSE server automatically reads permission configuration file from project root directory:
Location: .snow/permissions.json
{
"alwaysApprovedTools": [
"filesystem-read",
"codebase-search",
"filesystem-edit",
"notebook-add",
"filesystem-create"
]
}- Project-level Configuration: Server reads
.snow/permissions.jsonfrom working directory on startup - Auto-approval: Tools in the list are executed automatically without user confirmation
- Sensitive Commands Priority: Sensitive commands require confirmation even if in auto-approval list
- Dynamic Updates: When user selects "Always approve", the tool is automatically added to configuration file
{
"alwaysApprovedTools": [
"filesystem-read", // Read files
"filesystem-edit", // Hashline edit
"filesystem-create", // Create files
"codebase-search", // Code search
"ace-search", // Unified ACE code search (semantic_search / find_definition / find_references / file_outline / text_search via action)
"notebook-add" // Add note
]
}Carry yoloMode parameter when sending message:
const response = await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
type: 'chat',
content: 'Your question',
yoloMode: true, // Enable YOLO mode
}),
});- Auto-approval: Non-sensitive commands execute automatically
- Sensitive Command Exception: Sensitive commands still require confirmation
- Fast Response: Reduce interaction waiting time
- Suitable for Automation: Script and automation scenarios
Even with YOLO mode enabled:
- Sensitive commands still require confirmation
- Tools not in permission list require first-time confirmation
- Can abort execution at any time through rejection
class SnowAIClient {
constructor(baseUrl = 'http://localhost:3000') {
this.baseUrl = baseUrl;
this.eventSource = null;
this.sessionId = null;
}
// Connect to SSE server
connect() {
return new Promise((resolve, reject) => {
this.eventSource = new EventSource(`${this.baseUrl}/events`);
this.eventSource.onopen = () => {
console.log('Connected to Snow AI');
resolve();
};
this.eventSource.onerror = error => {
console.error('Connection error:', error);
reject(error);
};
this.eventSource.onmessage = event => {
this.handleEvent(JSON.parse(event.data));
};
});
}
// Handle events
handleEvent(event) {
console.log('[Event]', event.type);
switch (event.type) {
case 'tool_confirmation_request':
this.handleToolConfirmation(event);
break;
case 'user_question_request':
this.handleUserQuestion(event);
break;
case 'message':
if (event.data.streaming) {
process.stdout.write(event.data.content);
}
break;
case 'complete':
this.sessionId = event.data.sessionId;
console.log('\nConversation complete, Session ID:', this.sessionId);
break;
}
}
// Handle tool confirmation
async handleToolConfirmation(event) {
const options = event.data.availableOptions;
// Custom confirmation logic can be implemented here
// Example: Auto-approve non-sensitive commands
const decision = event.data.isSensitive ? 'reject' : 'approve';
await this.sendToolConfirmation(event.requestId, decision);
}
// Handle user question
async handleUserQuestion(event) {
// Custom selection logic can be implemented here
const selected = event.data.options[0];
await this.sendUserQuestionResponse(event.requestId, {
selected: selected,
});
}
// Send message
async sendMessage(content, yoloMode = false) {
const payload = {
type: 'chat',
content: content,
};
if (this.sessionId) {
payload.sessionId = this.sessionId;
}
if (yoloMode) {
payload.yoloMode = true;
}
const response = await fetch(`${this.baseUrl}/message`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload),
});
return await response.json();
}
// Send tool confirmation response
async sendToolConfirmation(requestId, decision) {
const response = await fetch(`${this.baseUrl}/message`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
type: 'tool_confirmation_response',
requestId: requestId,
response: decision,
}),
});
return await response.json();
}
// Send user question response
async sendUserQuestionResponse(requestId, answer) {
const response = await fetch(`${this.baseUrl}/message`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
type: 'user_question_response',
requestId: requestId,
response: answer,
}),
});
return await response.json();
}
// Disconnect
disconnect() {
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
}
}
// Usage example
async function main() {
const client = new SnowAIClient();
// Connect
await client.connect();
// Send message (enable YOLO mode)
await client.sendMessage('Help me create a TypeScript function', true);
// Wait for response handling (via event listeners)
}
main();import requests
import json
import sseclient
class SnowAIClient:
def __init__(self, base_url='http://localhost:3000'):
self.base_url = base_url
self.session = requests.Session()
self.session_id = None
def connect(self):
"""Connect to SSE server"""
response = self.session.get(
f'{self.base_url}/events',
stream=True,
headers={'Accept': 'text/event-stream'}
)
client = sseclient.SSEClient(response)
for event in client.events():
data = json.loads(event.data)
self.handle_event(data)
def handle_event(self, event):
"""Handle events"""
print(f"[Event] {event['type']}")
if event['type'] == 'tool_confirmation_request':
self.handle_tool_confirmation(event)
elif event['type'] == 'user_question_request':
self.handle_user_question(event)
elif event['type'] == 'complete':
self.session_id = event['data']['sessionId']
print(f"Session ID: {self.session_id}")
def handle_tool_confirmation(self, event):
"""Handle tool confirmation"""
# Auto-approve non-sensitive commands
decision = 'reject' if event['data']['isSensitive'] else 'approve'
self.send_tool_confirmation_response(event['requestId'], decision)
def handle_user_question(self, event):
"""Handle user question"""
selected = event['data']['options'][0]
self.send_user_question_response(event['requestId'], {'selected': selected})
def send_message(self, content, yolo_mode=False):
"""Send message"""
payload = {
'type': 'chat',
'content': content,
}
if self.session_id:
payload['sessionId'] = self.session_id
if yolo_mode:
payload['yoloMode'] = True
response = self.session.post(
f'{self.base_url}/message',
json=payload
)
return response.json()
def send_tool_confirmation_response(self, request_id, decision):
"""Send tool confirmation response"""
response = self.session.post(
f'{self.base_url}/message',
json={
'type': 'tool_confirmation_response',
'requestId': request_id,
'response': decision
}
)
return response.json()
def send_user_question_response(self, request_id, answer):
"""Send user question response"""
response = self.session.post(
f'{self.base_url}/message',
json={
'type': 'user_question_response',
'requestId': request_id,
'response': answer
}
)
return response.json()
# Usage example
if __name__ == '__main__':
client = SnowAIClient()
# Send message (enable YOLO mode)
client.send_message('Help me create a Python function', yolo_mode=True)
# Listen for events
client.connect()Integrate Snow AI into your web application to provide intelligent programming assistant functionality:
// React component example
import {useState, useEffect, useRef} from 'react';
function AIAssistantChat() {
const [connected, setConnected] = useState(false);
const [messages, setMessages] = useState([]);
const [sessionId, setSessionId] = useState(null);
const eventSourceRef = useRef(null);
// Connect to SSE server
useEffect(() => {
const eventSource = new EventSource('http://localhost:3000/events');
eventSourceRef.current = eventSource;
eventSource.onopen = () => {
setConnected(true);
console.log('Connected to Snow AI');
};
eventSource.onmessage = event => {
const data = JSON.parse(event.data);
handleSSEEvent(data);
};
eventSource.onerror = () => {
setConnected(false);
console.error('Connection lost');
};
return () => {
eventSource.close();
};
}, []);
// Handle SSE events
const handleSSEEvent = data => {
switch (data.type) {
case 'message':
if (data.data.role === 'assistant') {
if (data.data.streaming) {
// Stream update last message
setMessages(prev => {
const newMessages = [...prev];
if (
newMessages.length > 0 &&
newMessages[newMessages.length - 1].role === 'assistant'
) {
newMessages[newMessages.length - 1].content = data.data.content;
} else {
newMessages.push({
role: 'assistant',
content: data.data.content,
});
}
return newMessages;
});
}
}
break;
case 'complete':
setSessionId(data.data.sessionId);
console.log('Conversation complete');
break;
case 'tool_confirmation_request':
// Show tool confirmation dialog
handleToolConfirmation(data);
break;
case 'error':
console.error('Error:', data.data.message);
break;
}
};
// Send message
const sendMessage = async text => {
const newMessage = {role: 'user', content: text};
setMessages(prev => [...prev, newMessage]);
const payload = {
type: 'chat',
content: text,
yoloMode: true, // Auto-approve safe tools
};
if (sessionId) {
payload.sessionId = sessionId;
}
await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload),
});
};
// Handle tool confirmation
const handleToolConfirmation = async event => {
const confirmed = window.confirm(
`AI wants to execute tool: ${event.data.toolCall.function.name}\nAllow?`,
);
await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
type: 'tool_confirmation_response',
requestId: event.requestId,
response: confirmed ? 'approve' : 'reject',
}),
});
};
return (
<div>
<div>Status: {connected ? 'Connected' : 'Disconnected'}</div>
<div>
{messages.map((msg, i) => (
<div key={i}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
</div>
<input
type="text"
onKeyPress={e => {
if (e.key === 'Enter') {
sendMessage(e.target.value);
e.target.value = '';
}
}}
/>
</div>
);
}Provide AI capabilities for mobile applications:
// Express middleware
app.post('/api/ai/chat', async (req, res) => {
const {message, sessionId} = req.body;
// Forward to Snow AI
const response = await fetch('http://localhost:3000/message', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
type: 'chat',
content: message,
sessionId: sessionId,
yoloMode: true,
}),
});
res.json(await response.json());
});Use as AI microservice:
# Kubernetes deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: snow-ai-service
spec:
replicas: 3
selector:
matchLabels:
app: snow-ai
template:
metadata:
labels:
app: snow-ai
spec:
containers:
- name: snow-ai
image: snow-ai:latest
command: ['snow', '--sse', '--sse-port', '3000']
ports:
- containerPort: 3000Snow CLI provides a complete HTML test client:
Location: sse-test-client.html
- Real-time SSE event monitoring
- Beautiful chat interface
- Event log viewer
- YOLO mode toggle
- Tool confirmation UI (with complete option display)
- Session management
- Connection status display
-
Start SSE server:
snow --sse
-
Open
sse-test-client.htmlin browser -
Click "Connect" button
-
Start chatting and testing
// Comprehensive error handling
eventSource.onerror = error => {
console.error('SSE connection error:', error);
// Auto-reconnect
setTimeout(() => {
console.log('Attempting to reconnect...');
connect();
}, 5000);
};// Set timeout for interaction requests
const TIMEOUT = 300000; // 5 minutes (default value, configurable via --sse-timeout parameter)
function waitForResponse(requestId) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Interaction timeout'));
}, TIMEOUT);
// Listen for response
// clearTimeout(timeout) after receiving response
});
}// Persist Session ID
localStorage.setItem('snow-session-id', sessionId);
// Restore Session
const savedSessionId = localStorage.getItem('snow-session-id');
if (savedSessionId) {
await client.sendMessage(
'Continue previous conversation',
false,
savedSessionId,
);
}// Validate and sanitize user input
function sanitizeInput(input) {
// Remove dangerous characters
return input.replace(/[<>]/g, '');
}
// Add authentication in production
const response = await fetch('http://localhost:3000/message', {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiToken}`,
},
// ...
});-
Interactive UI:
- Cannot use Ink terminal interface
- Keyboard shortcuts not supported
-
Plan Mode:
- Interactive plan approval not supported
- All operations execute immediately
-
Local File Access Restrictions:
- Can only access files under server working directory
- Cannot access client-side local files
-
Connection Limit:
- Recommended maximum 100 concurrent connections per server
- Consider load balancing
-
Session Size:
- Long sessions increase memory usage
- Regularly clean up old sessions
-
Network Bandwidth:
- Streaming output continuously occupies connection
- Consider message size limits
-
Authentication and Authorization:
- Must add authentication in production environment
- Implement access control
-
API Key Protection:
- Don't expose API keys on client side
- Use server-side configuration
-
Command Execution Risks:
- Review all tool invocations
- Restrict sensitive operations
Q: What's the difference between SSE server and headless mode?
A: SSE server is a continuously running backend service supporting multiple client connections. Headless mode is single-execution mode that automatically exits after completion. SSE is suitable for web application integration, headless mode for script automation.
Q: How to use different API configurations in SSE mode?
A: SSE server reads configuration files from working directory. Use --work-dir parameter to specify different project directories, each with independent configuration.
Q: Can I run multiple SSE servers simultaneously?
A: Yes, but need to use different ports. For example:
snow --sse --sse-port 3000
snow --sse --sse-port 3001 --work-dir /another-projectQ: Do sessions expire?
A: Sessions don't expire and are permanently saved in ~/.snow/sessions/ directory. However, very long sessions increase token consumption.
Q: How to handle tool confirmation timeout?
A: Tool confirmation has a default 5-minute (300000ms) timeout. If timeout occurs, execution is automatically rejected and error returned. Recommend implementing auto-handling or user prompts in client.
You can customize the timeout duration via --sse-timeout parameter:
# Set to 10 minutes (600000ms)
snow --sse --sse-timeout 600000
# Set to 30 seconds (30000ms)
snow --sse --sse-timeout 30000Q: Does YOLO mode execute all commands?
A: No. Sensitive commands require confirmation even in YOLO mode. YOLO mode only auto-approves safe tools in the permission list.
Q: How to debug SSE connection issues?
A:
- Check server logs (terminal display)
- Use browser developer tools to view network requests
- Use
sse-test-client.htmlfor testing - Check firewall and port usage
Q: Can SSE server run in Docker?
A: Yes. Example Dockerfile:
FROM node:18
RUN npm install -g snow-ai
EXPOSE 3000
CMD ["snow", "--sse", "--sse-port", "3000"]Configuration files used by SSE server:
- API Configuration:
~/.snow/profiles.json - Permission Configuration:
<working-directory>/.snow/permissions.json - Sensitive Commands:
~/.snow/sensitive-commands.json - Session Storage:
~/.snow/sessions/<project-name>/<date>/
For configuration methods, refer to: First Time Configuration
- Headless Mode - Command line quick conversations
- Sensitive Commands Configuration - Configure dangerous commands requiring confirmation
- Async Task Management - Background task management
- Startup Parameters Guide - All startup parameters explained