Plugin Protocol
The Sky plugin protocol defines how plugins communicate with the Sky CLI. Both native and WASM plugins use the same protocol.
Protocol Version
Section titled “Protocol Version”The current protocol version is v1.1.
- v1.0: Initial release with core environment variables
- v1.1: Added workspace root, config dir, output format, and verbosity
Execution Flow
Section titled “Execution Flow”When Sky runs a plugin:
- Sky sets environment variables
- Sky invokes the plugin with CLI arguments
- Plugin checks
SKY_PLUGIN_MODE - If
metadata: output JSON and exit - If
exec: run the command and exit with appropriate code
Environment Variables
Section titled “Environment Variables”Sky sets these environment variables when running plugins:
| Variable | Since | Description |
|---|---|---|
SKY_PLUGIN | v1.0 | Always "1" when running as a plugin |
SKY_PLUGIN_MODE | v1.0 | "exec" or "metadata" |
SKY_PLUGIN_NAME | v1.0 | The plugin’s registered name |
SKY_WORKSPACE_ROOT | v1.1 | Workspace root directory |
SKY_CONFIG_DIR | v1.1 | Sky configuration directory |
SKY_OUTPUT_FORMAT | v1.1 | "text" or "json" |
SKY_NO_COLOR | v1.1 | "1" if color should be disabled |
SKY_VERBOSE | v1.1 | Verbosity level (0-3) |
SKY_PLUGIN
Section titled “SKY_PLUGIN”Always set to "1" when a plugin is run by Sky. Use this to detect if
your binary is running as a plugin or standalone.
if os.Getenv("SKY_PLUGIN") != "1" { fmt.Fprintln(os.Stderr, "This is a Sky plugin. Run via: sky my-plugin") os.Exit(1)}SKY_PLUGIN_MODE
Section titled “SKY_PLUGIN_MODE”Determines the operation mode:
metadata: Output plugin metadata as JSON to stdout and exitexec: Execute the requested command
SKY_WORKSPACE_ROOT
Section titled “SKY_WORKSPACE_ROOT”The root directory of the current workspace. Determined by searching upward from the current directory for:
.sky.yamlor.sky.yml.gitdirectory
Falls back to the current directory if no markers are found.
SKY_OUTPUT_FORMAT
Section titled “SKY_OUTPUT_FORMAT”The user’s preferred output format:
text(default): Human-readable text outputjson: Machine-readable JSON output
Respect this when your plugin produces structured output:
if os.Getenv("SKY_OUTPUT_FORMAT") == "json" { json.NewEncoder(os.Stdout).Encode(result)} else { fmt.Println(result.String())}SKY_NO_COLOR
Section titled “SKY_NO_COLOR”Set to "1" when color output should be disabled. Also check the
standard NO_COLOR environment variable.
func noColor() bool { return os.Getenv("SKY_NO_COLOR") == "1" || os.Getenv("NO_COLOR") != ""}SKY_VERBOSE
Section titled “SKY_VERBOSE”Verbosity level from 0 (quiet) to 3 (debug):
0: Only errors1: Normal output2: Verbose output3: Debug output
Metadata Mode
Section titled “Metadata Mode”When SKY_PLUGIN_MODE=metadata, the plugin must:
- Print a JSON object to stdout
- Exit with status code
0 - Not print any other output (no logs, no banners)
Metadata JSON Schema
Section titled “Metadata JSON Schema”{ "api_version": 1, "name": "my-plugin", "version": "1.0.0", "summary": "A brief description", "commands": [ { "name": "my-plugin", "summary": "Main command description" }, { "name": "subcommand", "summary": "Subcommand description" } ]}Required Fields
Section titled “Required Fields”| Field | Type | Description |
|---|---|---|
api_version | integer | Must be 1 |
name | string | Plugin name (must match installed name) |
Optional Fields
Section titled “Optional Fields”| Field | Type | Description |
|---|---|---|
version | string | Semantic version (e.g., "1.0.0") |
summary | string | Brief description for sky plugin list |
commands | array | List of commands the plugin provides |
Command Object
Section titled “Command Object”| Field | Type | Description |
|---|---|---|
name | string | Command name |
summary | string | Brief description |
Execution Mode
Section titled “Execution Mode”When SKY_PLUGIN_MODE=exec, the plugin should:
- Parse command-line arguments from
os.Args[1:] - Perform the requested operation
- Write output to stdout
- Write errors to stderr
- Exit with appropriate status code
Exit Codes
Section titled “Exit Codes”| Code | Meaning |
|---|---|
0 | Success |
1 | General error |
2 | Usage error (invalid arguments) |
Backward Compatibility
Section titled “Backward Compatibility”For Plugin Authors
Section titled “For Plugin Authors”When reading v1.1 environment variables, always provide fallbacks:
func workspaceRoot() string { if root := os.Getenv("SKY_WORKSPACE_ROOT"); root != "" { return root } cwd, _ := os.Getwd() return cwd}For Sky Maintainers
Section titled “For Sky Maintainers”- v1.0 plugins work unchanged with v1.1 Sky
- New environment variables are additive
api_versionin metadata remains1
Example Implementation
Section titled “Example Implementation”package main
import ( "encoding/json" "fmt" "os")
func main() { // Check if running as a plugin if os.Getenv("SKY_PLUGIN") != "1" { fmt.Fprintln(os.Stderr, "Run via: sky my-plugin") os.Exit(1) }
// Handle metadata mode if os.Getenv("SKY_PLUGIN_MODE") == "metadata" { json.NewEncoder(os.Stdout).Encode(map[string]any{ "api_version": 1, "name": "my-plugin", "version": "1.0.0", "summary": "Does something useful", }) return }
// Handle execution mode args := os.Args[1:] if err := run(args); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) }}
func run(args []string) error { // Plugin logic here return nil}