Skip to main content
xbot
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Script plugins run external scripts (bash, Python, Node, anything executable). They’re the simplest way to create widgets, hooks, and commands without writing Go code.

Overview

Script plugins use runtime: "script" in the manifest. The entry field specifies the command to execute. The script’s stdout becomes widget content or command response.

Manifest

{
  "id": "my-script-plugin",
  "name": "My Script Plugin",
  "version": "1.0.0",
  "runtime": "script",
  "entry": "bash main.sh",
  "activationEvents": ["onStart"],
  "permissions": ["ui.contribute"],
  "contributes": {
    "ui": [
      {
        "id": "status",
        "slot": "statusBarRight",
        "priority": 50,
        "description": "Show status info",
        "refreshInterval": "10s",
        "triggers": ["PostToolUse:Shell"]
      }
    ]
  }
}

Platform-Specific Entry

{
  "entry": "bash main.sh",
  "entry_windows": "powershell main.ps1",
  "entry_darwin": "bash main.sh",
  "entry_linux": "bash main.sh"
}

Platform-specific entries take precedence over the generic entry for the matching OS.

Widget Refresh

Widgets refresh on three triggers:

  1. Periodic: refreshInterval field (e.g. "10s", "1m"). Default: 30 seconds.
  2. Hook-triggered: triggers field (e.g. ["PostToolUse:Shell"]). Fires immediately when the hook matches.
  3. Directory change: When the session’s working directory changes, the script re-runs for the new directory.

Trigger Events

TriggerDescription
PreToolUse:<matcher>Before a tool executes (matcher = tool name pattern)
PostToolUse:<matcher>After a tool executes successfully
PostToolUseFailure:<matcher>After a tool fails
UserPromptSubmitWhen user sends a message
AgentStopWhen the agent stops
SessionStartWhen a session starts
SessionEndWhen a session ends
SubAgentStartWhen a SubAgent starts
SubAgentStopWhen a SubAgent stops
PreCompactBefore context compression
PostCompactAfter context compression
CronFiredWhen a cron job fires
WebhookReceivedWhen a webhook is received

Sync Mode

Set "sync": true on a UI contribution to run the script synchronously on hook triggers. The output is available immediately as hint content for the engine:

{
  "ui": [
    {
      "id": "diff-hint",
      "slot": "infoBar",
      "sync": true,
      "triggers": ["PostToolUse:FileReplace"]
    }
  ]
}

Environment Variables

Scripts receive context via environment variables:

VariableDescriptionAvailable When
XBOT_WORK_DIRCurrent working directoryAlways
XBOT_WIDGET_IDWidget ID being renderedWidget rendering
XBOT_PLUGIN_CONFIGPlugin configuration (JSON)Always (if config exists)
XBOT_HOOK_EVENTHook event nameHook triggers
XBOT_TOOL_NAMETool name that triggered the hookTool hooks
XBOT_TOOL_OUTPUTTool output (truncated to 8KB)PostToolUse hooks
XBOT_TOOL_INPUTTool inputTool hooks
XBOT_MODELCurrent LLM model nameHook events with session context
XBOT_MAX_CONTEXTMax context tokensHook events with session context
XBOT_TOKEN_USAGEToken usage as prompt/completionHook events with token data
XBOT_PROMPT_TOKENSPrompt token countHook events with token data
XBOT_COMP_TOKENSCompletion token countHook events with token data
XBOT_COMMAND_NAMECommand nameCommand execution
XBOT_COMMAND_ARGSCommand argumentsCommand execution

Output Format

Script stdout is parsed for style hints:

FormatStyleDescription
textNormalDefault style
dim|textDimMuted/dimmed text
ok|textSuccessGreen text
warn|textWarningYellow text
err|textErrorRed text
info|textInfoBlue text
accent|textAccentHighlighted text
md|<markdown>RawMulti-line markdown content
diff|<diff>RawMulti-line unified diff (preserves ANSI)

The | separator splits style from content. For md| and diff|, the full multi-line content after the prefix is preserved.

Per-WorkDir Output Cache

Script plugins maintain a per-workDir output cache: workDir → widgetID → output. Each CLI window (different workDir) sees its own content. The cache is:

  • Populated on refresh (periodic or triggered)
  • Evicted when the directory no longer exists
  • Change-detected: NotifyUpdated() only fires when output actually changes

Commands

Scripts can register slash commands:

{
  "contributes": {
    "commands": [
      {
        "name": "/deploy",
        "description": "Deploy the current project"
      }
    ]
  }
}

When the user types /deploy production, the script runs with XBOT_COMMAND_NAME=deploy and XBOT_COMMAND_ARGS=production. The script’s stdout becomes the command response.

Configuration Injection

Plugin configuration is injected as JSON via XBOT_PLUGIN_CONFIG:

#!/bin/bash
# Read plugin config
config=$(echo "$XBOT_PLUGIN_CONFIG" | jq -r '.greeting // "Hello"')
echo "$config, World!"

Complete Example

#!/bin/bash
# main.sh — Git branch widget

# Get current git branch
branch=$(git -C "$XBOT_WORK_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null)

if [ -n "$branch" ]; then
    # Check for uncommitted changes
    if [ -n "$(git -C "$XBOT_WORK_DIR" status --porcelain 2>/dev/null)" ]; then
        echo "warn|$branch*"
    else
        echo "ok|$branch"
    fi
else
    echo "dim|no-git"
fi

See Also