Skip to content

Latest commit

 

History

History
462 lines (379 loc) · 11.6 KB

File metadata and controls

462 lines (379 loc) · 11.6 KB

Snow CLI User Guide - Custom StatusLine

Overview

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

Plugin Directory

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

Export Formats

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.

Hook Structure

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 behavior
  • refreshIntervalMs: optional refresh interval in milliseconds; minimum effective interval is 1000 ms
  • enable: optional, whether to enable this hook, defaults to true, set to false to temporarily disable
  • getItems(context): returns one item, multiple items, or undefined

The getItems result supports:

  • single item object
  • array of item objects
  • undefined or null to render nothing
  • async return values via async getItems()

Render Item Fields

Each render item supports the following fields:

  • id: optional item id; Snow CLI auto-generates one if omitted
  • text: short text used in simple mode
  • detailedText: optional text used in normal mode; falls back to text
  • color: optional Ink color string or hex color
  • priority: optional sort priority; lower values render first

Context Object

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 directory
  • platform: current Node.js platform value, such as darwin, linux, win32
  • language: current Snow CLI language, one of en, zh, zh-TW
  • simpleMode: whether Snow CLI is in simple theme mode
  • labels: localized labels that built-in plugins may reuse
  • system: a ready-to-use snapshot of current StatusLine system state

Available fields under system:

  • system.memory: current Snow CLI process memory, including usageMb and formattedUsage
  • system.modes: current mode flags, including yolo, plan, vulnerabilityHunting, toolSearchEnabled, hybridCompress, simple
  • system.ide: IDE connection state, including connectionStatus, editorContext, selectedTextLength
  • system.backend: backend connection state, including connectionStatus, instanceName
  • system.contextWindow: context window state; when present it includes token metrics, cache metrics, percentage, and totalInputTokens
  • system.codebase: codebase indexing state, including indexing and progress
  • system.watcher: file watcher state, including enabled and fileUpdateNotification
  • system.clipboard: most recent copy feedback, including text, isError, timestamp
  • system.profile: current profile full configuration, including currentName, 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 (excluding apiKey)
  • system.compression: auto-compression state, including blockToast

Example 1: Real Clock Plugin

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,
		};
	},
};

Example 2: Show Current Directory Name

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,
		};
	},
};

Example 3: Use System State

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;
	},
};

Example 4: Return Multiple Status 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,
			},
		];
	},
};

Built-in Git Branch Example

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.

Override Example

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,
		};
	},
};

Error Handling

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

  • text is missing or empty

  • plugin code throws at runtime

Best Practices

  • Keep getItems() fast and lightweight
  • Use a reasonable refresh interval
  • Return undefined when the status should be hidden
  • Use stable id values for predictable ordering and override behavior
  • Prefer detailedText for verbose mode and text for compact mode
  • Restart Snow CLI after editing plugin files

Troubleshooting

Plugin does not appear

Check:

  1. File path is ~/.snow/plugin/statusline/*.js

  2. Snow CLI was restarted

  3. Export format is valid

  4. text is not empty

  5. The plugin does not throw errors during execution

Status order is unexpected

Check priority values.

  • smaller number = earlier render
  • larger number = later render

My plugin does not override built-in Git branch

Make sure the hook id exactly matches:

id: 'builtin.git-branch';

Related Files

  • source/ui/components/common/statusline/useStatusLineHooks.ts
  • source/ui/components/common/statusline/types.ts
  • source/ui/components/common/statusline/gitBranch.ts
  • ~/.snow/plugin/statusline/example-clock.js