Snow CLI supports loading custom StatusLine plugins from your user directory. You can place one or more JavaScript files in ~/.snow/plugin/statusline/, and Snow CLI will load them on startup.
Use this feature when you want to:
- Show your own environment status
- Display project-specific hints
- Add time, directory, branch, service, or local machine indicators
- Switch status text by Simplified Chinese, Traditional Chinese, and English
- Override a built-in StatusLine plugin with your own implementation
Snow CLI currently loads StatusLine plugins from:
~/.snow/plugin/statusline/Supported file extensions:
.js.mjs.cjs
Notes:
- Plugins are loaded from the user directory only
- Snow CLI sorts plugin files by filename before loading
- Restart Snow CLI after adding or modifying a plugin file
A plugin module can export in any of these forms:
export default { ... }export const statusLineHook = { ... }export const statusLineHooks = [{ ... }, { ... }]If multiple plugins use the same hook id, the later loaded plugin overrides the earlier one.
Each StatusLine hook uses this structure:
export default {
id: 'custom.example',
refreshIntervalMs: 60000,
getItems(context) {
return {
id: 'custom-example-item',
text: 'Hello',
detailedText: 'Hello from custom status line',
color: 'cyan',
priority: 200,
};
},
};Field description:
id: unique hook id, used for merging and override behaviorrefreshIntervalMs: optional refresh interval in milliseconds; minimum effective interval is 1000 msenable: optional, whether to enable this hook, defaults totrue, set tofalseto temporarily disablegetItems(context): returns one item, multiple items, orundefined
The getItems result supports:
- single item object
- array of item objects
undefinedornullto render nothing- async return values via
async getItems()
Each render item supports the following fields:
id: optional item id; Snow CLI auto-generates one if omittedtext: short text used in simple modedetailedText: optional text used in normal mode; falls back totextcolor: optional Ink color string or hex colorpriority: optional sort priority; lower values render first
getItems(context) receives this context object:
{
cwd: '/absolute/current/working/directory',
platform: 'darwin',
language: 'en',
simpleMode: false,
labels: {
gitBranch: 'Git Branch',
},
system: {
memory: {
usageMb: 186,
formattedUsage: '186 MB',
},
modes: {
yolo: false,
plan: true,
vulnerabilityHunting: false,
toolSearchEnabled: true,
hybridCompress: false,
simple: false,
},
ide: {
connectionStatus: 'connected',
editorContext: {
activeFile: '/path/to/file.ts',
selectedText: 'const answer = 42;',
cursorPosition: {line: 10, character: 5},
workspaceFolder: '/path/to/workspace',
},
selectedTextLength: 18,
},
backend: {
connectionStatus: 'connected',
instanceName: 'default',
},
contextWindow: {
inputTokens: 18234,
maxContextTokens: 128000,
cacheCreationTokens: 2048,
cacheReadTokens: 8192,
percentage: 22.3,
totalInputTokens: 28474,
hasAnthropicCache: true,
hasOpenAICache: false,
hasAnyCache: true,
},
codebase: {
indexing: true,
progress: {
totalFiles: 100,
processedFiles: 42,
totalChunks: 320,
currentFile: 'source/app.ts',
status: 'indexing',
},
},
watcher: {
enabled: true,
fileUpdateNotification: {
file: 'source/app.ts',
timestamp: 1710000000000,
},
},
clipboard: {
text: 'Input copied',
isError: false,
timestamp: 1710000000000,
},
profile: {
currentName: 'default',
baseUrl: 'https://api.openai.com/v1',
requestMethod: 'chat',
advancedModel: 'gpt-4o',
basicModel: 'gpt-4o-mini',
maxContextTokens: 128000,
maxTokens: 4096,
anthropicBeta: false,
anthropicCacheTTL: '5m',
thinkingEnabled: false,
thinkingType: 'adaptive',
thinkingBudgetTokens: 4096,
thinkingEffort: 'medium',
geminiThinkingEnabled: false,
geminiThinkingLevel: 'high',
responsesReasoningEnabled: false,
responsesReasoningEffort: 'medium',
responsesFastMode: false,
responsesVerbosity: 'medium',
anthropicSpeed: 'standard',
enablePromptOptimization: true,
enableAutoCompress: true,
autoCompressThreshold: 80,
showThinking: true,
streamIdleTimeoutSec: 180,
systemPromptId: ['default'],
customHeadersSchemeId: 'default',
toolResultTokenLimit: 100000,
streamingDisplay: false,
},
compression: {
blockToast: null,
},
},
}Field description:
cwd: current Snow CLI working directoryplatform: current Node.js platform value, such asdarwin,linux,win32language: current Snow CLI language, one ofen,zh,zh-TWsimpleMode: whether Snow CLI is in simple theme modelabels: localized labels that built-in plugins may reusesystem: a ready-to-use snapshot of current StatusLine system state
Available fields under system:
system.memory: current Snow CLI process memory, includingusageMbandformattedUsagesystem.modes: current mode flags, includingyolo,plan,vulnerabilityHunting,toolSearchEnabled,hybridCompress,simplesystem.ide: IDE connection state, includingconnectionStatus,editorContext,selectedTextLengthsystem.backend: backend connection state, includingconnectionStatus,instanceNamesystem.contextWindow: context window state; when present it includes token metrics, cache metrics,percentage, andtotalInputTokenssystem.codebase: codebase indexing state, includingindexingandprogresssystem.watcher: file watcher state, includingenabledandfileUpdateNotificationsystem.clipboard: most recent copy feedback, includingtext,isError,timestampsystem.profile: current profile full configuration, includingcurrentName,baseUrl,requestMethod,advancedModel,basicModel,maxContextTokens,maxTokens,anthropicBeta,anthropicCacheTTL,thinkingEnabled,thinkingType,thinkingBudgetTokens,thinkingEffort,geminiThinkingEnabled,geminiThinkingLevel,responsesReasoningEnabled,responsesReasoningEffort,responsesFastMode,responsesVerbosity,anthropicSpeed,enablePromptOptimization,enableAutoCompress,autoCompressThreshold,showThinking,streamIdleTimeoutSec,systemPromptId,customHeadersSchemeId,toolResultTokenLimit,streamingDisplay(excludingapiKey)system.compression: auto-compression state, includingblockToast
A real working example file already exists in your user directory:
~/.snow/plugin/statusline/example-clock.js
Its content is:
```js
const messages = {
en: {
label: 'Current Time',
directory: 'Directory',
},
zh: {
label: '当前时间',
directory: '目录',
},
'zh-TW': {
label: '當前時間',
directory: '目錄',
},
};
export default {
id: 'custom.example-clock',
refreshIntervalMs: 60_000,
getItems(context) {
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const clock = `${hours}:${minutes}`;
const message = messages[context.language] || messages.en;
return {
id: 'custom-example-clock',
text: `◷ ${clock}`,
detailedText: `◷ ${message.label}: ${clock} · ${message.directory}: ${context.cwd}`,
color: '#A78BFA',
priority: 200,
};
},
};import path from 'node:path';
export default {
id: 'custom.cwd-name',
refreshIntervalMs: 5000,
getItems(context) {
const folderName = path.basename(context.cwd);
return {
text: `DIR ${folderName}`,
detailedText: `Current Folder: ${folderName}`,
color: 'green',
priority: 150,
};
},
};export default {
id: 'custom.system-status',
refreshIntervalMs: 3000,
getItems(context) {
const items = [];
if (context.system.ide.connectionStatus === 'connected') {
const activeFile = context.system.ide.editorContext?.activeFile;
items.push({
id: 'custom-system-ide',
text: activeFile ? 'IDE ON' : 'IDE READY',
detailedText: activeFile
? `IDE connected · Active file: ${activeFile}`
: 'IDE connected',
color: '#22C55E',
priority: 120,
});
}
if (context.system.contextWindow) {
items.push({
id: 'custom-system-context',
text: `CTX ${context.system.contextWindow.percentage.toFixed(1)}%`,
detailedText: `Context used: ${context.system.contextWindow.totalInputTokens} tokens`,
color: 'cyan',
priority: 130,
});
}
items.push({
id: 'custom-system-memory',
text: `MEM ${context.system.memory.formattedUsage}`,
detailedText: `Current memory usage: ${context.system.memory.formattedUsage}`,
color: 'yellow',
priority: 140,
});
return items;
},
};export default {
id: 'custom.multi-status',
refreshIntervalMs: 30000,
getItems() {
const now = new Date();
return [
{
text: `T ${String(now.getHours()).padStart(2, '0')}:${String(
now.getMinutes(),
).padStart(2, '0')}`,
color: 'cyan',
priority: 100,
},
{
text: 'ENV DEV',
detailedText: 'Environment: Development',
color: 'yellow',
priority: 110,
},
];
},
};Snow CLI already includes a built-in Git branch StatusLine plugin.
Reference implementation:
source/ui/components/common/statusline/gitBranch.ts
This built-in hook:
- uses hook id
builtin.git-branch - refreshes every 10 seconds
- reads the current Git branch from
context.cwd - renders short and detailed text separately
If you create another plugin with the same hook id, you can override the built-in behavior.
export default {
id: 'builtin.git-branch',
refreshIntervalMs: 15000,
async getItems(context) {
return {
text: '⑂ custom-branch',
detailedText: `⑂ Custom Git Branch (${context.cwd})`,
color: 'magenta',
priority: 100,
};
},
};If a plugin fails:
- Snow CLI skips the broken result for that refresh cycle
- the error is written to the Snow CLI log
- other plugins continue to run
Common problems:
-
file is not in
~/.snow/plugin/statusline/ -
file extension is not supported
-
exported value is not a valid hook object
-
textis missing or empty -
plugin code throws at runtime
- Keep
getItems()fast and lightweight - Use a reasonable refresh interval
- Return
undefinedwhen the status should be hidden - Use stable
idvalues for predictable ordering and override behavior - Prefer
detailedTextfor verbose mode andtextfor compact mode - Restart Snow CLI after editing plugin files
Check:
-
File path is
~/.snow/plugin/statusline/*.js -
Snow CLI was restarted
-
Export format is valid
-
textis not empty -
The plugin does not throw errors during execution
Check priority values.
- smaller number = earlier render
- larger number = later render
Make sure the hook id exactly matches:
id: 'builtin.git-branch';source/ui/components/common/statusline/useStatusLineHooks.tssource/ui/components/common/statusline/types.tssource/ui/components/common/statusline/gitBranch.ts~/.snow/plugin/statusline/example-clock.js