Skip to content

Plugin Protocol

The Sky plugin protocol defines how plugins communicate with the Sky CLI. Both native and WASM plugins use the same protocol.

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

When Sky runs a plugin:

  1. Sky sets environment variables
  2. Sky invokes the plugin with CLI arguments
  3. Plugin checks SKY_PLUGIN_MODE
  4. If metadata: output JSON and exit
  5. If exec: run the command and exit with appropriate code

Sky sets these environment variables when running plugins:

VariableSinceDescription
SKY_PLUGINv1.0Always "1" when running as a plugin
SKY_PLUGIN_MODEv1.0"exec" or "metadata"
SKY_PLUGIN_NAMEv1.0The plugin’s registered name
SKY_WORKSPACE_ROOTv1.1Workspace root directory
SKY_CONFIG_DIRv1.1Sky configuration directory
SKY_OUTPUT_FORMATv1.1"text" or "json"
SKY_NO_COLORv1.1"1" if color should be disabled
SKY_VERBOSEv1.1Verbosity level (0-3)

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)
}

Determines the operation mode:

  • metadata: Output plugin metadata as JSON to stdout and exit
  • exec: Execute the requested command

The root directory of the current workspace. Determined by searching upward from the current directory for:

  1. .sky.yaml or .sky.yml
  2. .git directory

Falls back to the current directory if no markers are found.

The user’s preferred output format:

  • text (default): Human-readable text output
  • json: 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())
}

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") != ""
}

Verbosity level from 0 (quiet) to 3 (debug):

  • 0: Only errors
  • 1: Normal output
  • 2: Verbose output
  • 3: Debug output

When SKY_PLUGIN_MODE=metadata, the plugin must:

  1. Print a JSON object to stdout
  2. Exit with status code 0
  3. Not print any other output (no logs, no banners)
{
"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"
}
]
}
FieldTypeDescription
api_versionintegerMust be 1
namestringPlugin name (must match installed name)
FieldTypeDescription
versionstringSemantic version (e.g., "1.0.0")
summarystringBrief description for sky plugin list
commandsarrayList of commands the plugin provides
FieldTypeDescription
namestringCommand name
summarystringBrief description

When SKY_PLUGIN_MODE=exec, the plugin should:

  1. Parse command-line arguments from os.Args[1:]
  2. Perform the requested operation
  3. Write output to stdout
  4. Write errors to stderr
  5. Exit with appropriate status code
CodeMeaning
0Success
1General error
2Usage error (invalid arguments)

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
}
  • v1.0 plugins work unchanged with v1.1 Sky
  • New environment variables are additive
  • api_version in metadata remains 1
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
}