Token导航 LogoToken导航TokenDH.com
研究检索执行命令github未标认证来源可访问许可证需确认审计提醒

kit-extensions套件扩展

Agent Skill

kit-extensions 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

367

周安装

15

GitHub Stars

45

下载量

118
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:kit-extensions(套件扩展)
来源仓库:https://github.com/mark3labs/kit
仓库路径:skills/kit-extensions
安装命令:
npx skills add https://github.com/mark3labs/kit --skill kit-extensions
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/mark3labs/kit --skill kit-extensions

简介

用于查找、检索和筛选相关信息。适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

  • 适合根据关键词或任务场景快速定位候选结果。
  • 通过 GitHub 安装,结合原始 README 核验具体用法。
  • 安装前建议确认权限范围和维护状态,避免触发敏感操作。
  • kit-extensions 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

Kit Extensions Development Guide

Kit extensions are single-file Go programs interpreted at runtime by Yaegi. They hook into Kit's lifecycle, register custom tools and slash commands, display widgets, intercept editor input, render tool output, register and switch color themes, and more.

Extensions can be distributed via git repositories using kit install. Repos can contain single extensions or collections of multiple extensions.

Extension Structure

Every extension must export a package main with an Init(api ext.API) function:

//go:build ignore

package main

import "kit/ext"

func Init(api ext.API) {
    // Register event handlers, tools, commands, etc.
}

The //go:build ignore tag prevents go build from compiling the file directly.

Extension Locations

Extensions are auto-loaded from these directories:

  • ~/.config/kit/extensions/*.go (global, single files)
  • ~/.config/kit/extensions/*/main.go (global, subdirectories)
  • .kit/extensions/*.go (project-local, single files)
  • .kit/extensions/*/main.go (project-local, subdirectories)

Or loaded explicitly:

kit -e path/to/extension.go
kit --extension path/to/extension.go

Import Path

Extensions import the Kit API as "kit/ext". The full standard library is available plus os/exec for subprocess spawning.

API Overview

The Init function receives an ext.API object for registering handlers, and event handlers receive an ext.Context with runtime capabilities.


Lifecycle Events

Kit provides 21 lifecycle events. Each handler receives an event struct and a Context.

Session Events

// Fired when session is loaded/created.
api.OnSessionStart(func(e ext.SessionStartEvent, ctx ext.Context) {
    // e.SessionID string
})

// Fired when Kit is shutting down. Use for cleanup.
api.OnSessionShutdown(func(e ext.SessionShutdownEvent, ctx ext.Context) {
    // No fields.
})

Agent Turn Events

// Before agent starts processing. Can inject system prompt or text.
api.OnBeforeAgentStart(func(e ext.BeforeAgentStartEvent, ctx ext.Context) *ext.BeforeAgentStartResult {
    // e.Prompt string
    // Return nil to pass through.
    // Return &ext.BeforeAgentStartResult{SystemPrompt: &s} to augment system prompt.
    // Return &ext.BeforeAgentStartResult{InjectText: &s} to inject text before prompt.
    return nil
})

// Agent loop has started.
api.OnAgentStart(func(e ext.AgentStartEvent, ctx ext.Context) {
    // e.Prompt string
})

// Agent finished responding.
api.OnAgentEnd(func(e ext.AgentEndEvent, ctx ext.Context) {
    // e.Response string
    // e.StopReason string — "error" (on failure), "completed" (when LLM returns
    //   empty stop reason), or the raw LLM provider value passed through
    //   (e.g. "stop", "length" (max output tokens hit), "tool-calls", "content-filter").
    //   To detect errors, check e.StopReason == "error".
    //   Do NOT compare against "completed" for success — instead check != "error".
})

Tool Events

// Before a tool executes. Can block the call.
api.OnToolCall(func(e ext.ToolCallEvent, ctx ext.Context) *ext.ToolCallResult {
    // e.ToolName string
    // e.ToolCallID string
    // e.Input string — JSON-encoded parameters
    // e.Source string — "llm" or "user"
    // Return nil to allow.
    // Return &ext.ToolCallResult{Block: true, Reason: "..."} to block.
    return nil
})

// Tool execution started (informational only).
api.OnToolExecutionStart(func(e ext.ToolExecutionStartEvent, ctx ext.Context) {
    // e.ToolName string
})

// Tool execution ended (informational only).
api.OnToolExecutionEnd(func(e ext.ToolExecutionEndEvent, ctx ext.Context) {
    // e.ToolName string
})

// After a tool returns. Can modify the result.
api.OnToolResult(func(e ext.ToolResultEvent, ctx ext.Context) *ext.ToolResultResult {
    // e.ToolName string
    // e.Input string
    // e.Content string
    // e.IsError bool
    // Return nil to pass through.
    // Return &ext.ToolResultResult{Content: &s} to replace content.
    // Return &ext.ToolResultResult{IsError: &b} to change error status.
    return nil
})

Tool Call Input Streaming Events

These events fire during the LLM's tool argument generation phase, before the tool call is fully parsed and before OnToolCall fires. They enable UIs to show tool activity immediately rather than waiting for the full argument JSON to finish streaming.

// Fires when the LLM begins generating tool call arguments.
// The tool name is known but the full argument JSON is still streaming.
api.OnToolCallInputStart(func(e ext.ToolCallInputStartEvent, ctx ext.Context) {
    // e.ToolCallID string — stable ID for correlating tool lifecycle events
    // e.ToolName string — name of the tool being called
    // e.ToolKind string — "execute", "edit", "read", "search", "agent"
    ctx.PrintInfo("Tool starting: " + e.ToolName)
})

// Fires for each streamed fragment of tool call arguments.
// Useful for live-previewing artifact content or showing a progress indicator.
api.OnToolCallInputDelta(func(e ext.ToolCallInputDeltaEvent, ctx ext.Context) {
    // e.ToolCallID string
    // e.Delta string — JSON fragment of tool arguments
})

// Fires when tool argument streaming is complete, before the tool call
// is parsed and execution begins. Transition UI from "generating args"
// to "executing".
api.OnToolCallInputEnd(func(e ext.ToolCallInputEndEvent, ctx ext.Context) {
    // e.ToolCallID string
})

Full tool lifecycle order: OnToolCallInputStartOnToolCallInputDelta (repeated) → OnToolCallInputEndOnToolCallOnToolExecutionStartOnToolOutput (optional, repeated) → OnToolExecutionEndOnToolResult

Input Events

// User submitted input. Can handle or transform it.
api.OnInput(func(e ext.InputEvent, ctx ext.Context) *ext.InputResult {
    // e.Text string
    // e.Source string — "interactive", "cli", "script", "queue"
    // Return nil to pass through to agent.
    // Return &ext.InputResult{Action: "handled"} to consume without sending to agent.
    // Return &ext.InputResult{Action: "transform", Text: "new text"} to rewrite.
    return nil
})

Streaming Events

api.OnMessageStart(func(e ext.MessageStartEvent, ctx ext.Context) {})
api.OnMessageUpdate(func(e ext.MessageUpdateEvent, ctx ext.Context) {
    // e.Chunk string — streaming text chunk
})
api.OnMessageEnd(func(e ext.MessageEndEvent, ctx ext.Context) {
    // e.Content string — full message content
})

Model Events

api.OnModelChange(func(e ext.ModelChangeEvent, ctx ext.Context) {
    // e.NewModel string
    // e.PreviousModel string
    // e.Source string — "extension" or "user"
})

Context Filtering

// Before messages are sent to the LLM. Can filter, reorder, or inject messages.
api.OnContextPrepare(func(e ext.ContextPrepareEvent, ctx ext.Context) *ext.ContextPrepareResult {
    // e.Messages []ext.ContextMessage
    // Each ContextMessage has: Index int, Role string, Content string
    // Index -1 means a new injected message (not from session).
    // Return nil to pass through.
    // Return &ext.ContextPrepareResult{Messages: msgs} to replace the context window.
    return nil
})

Session Control Events

// Before forking the session tree. Can cancel.
api.OnBeforeFork(func(e ext.BeforeForkEvent, ctx ext.Context) *ext.BeforeForkResult {
    // e.TargetID string, e.IsUserMessage bool, e.UserText string
    return nil // or &ext.BeforeForkResult{Cancel: true, Reason: "..."}
})

// Before switching/clearing session. Can cancel.
api.OnBeforeSessionSwitch(func(e ext.BeforeSessionSwitchEvent, ctx ext.Context) *ext.BeforeSessionSwitchResult {
    // e.Reason string — "new" or "clear"
    return nil // or &ext.BeforeSessionSwitchResult{Cancel: true, Reason: "..."}
})

// Before context compaction. Can cancel.
api.OnBeforeCompact(func(e ext.BeforeCompactEvent, ctx ext.Context) *ext.BeforeCompactResult {
    // e.EstimatedTokens, e.ContextLimit int
    // e.UsagePercent float64, e.MessageCount int, e.IsAutomatic bool
    return nil // or &ext.BeforeCompactResult{Cancel: true, Reason: "..."}
})

Custom Events

// Subscribe to custom events emitted by other extensions.
api.OnCustomEvent("event-name", func(data string) {
    // data is arbitrary string payload
})

// Emit from Context:
ctx.EmitCustomEvent("event-name", "payload")

Registering Tools

Tools are functions the LLM can invoke:

api.RegisterTool(ext.ToolDef{
    Name:        "current_time",
    Description: "Get the current date and time",
    Parameters:  `{"type":"object","properties":{}}`,
    Execute: func(input string) (string, error) {
        return time.Now().Format(time.RFC3339), nil
    },
})

For long-running tools with cancellation and progress:

api.RegisterTool(ext.ToolDef{
    Name:        "slow_task",
    Description: "A long-running task with progress reporting",
    Parameters:  `{"type":"object","properties":{"query":{"type":"string"}}}`,
    ExecuteWithContext: func(input string, tc ext.ToolContext) (string, error) {
        for i := 0; i < 10; i++ {
            if tc.IsCancelled() {
                return "cancelled", nil
            }
            tc.OnProgress(fmt.Sprintf("Step %d/10...", i+1))
            time.Sleep(time.Second)
        }
        return "done", nil
    },
})

Parameters must be a JSON Schema string. The input argument is the JSON-encoded parameters from the LLM.


Registering Slash Commands

Commands are user-facing actions invoked with /name in the input:

api.RegisterCommand(ext.CommandDef{
    Name:        "echo",
    Description: "Echo back the provided text",
    Execute: func(args string, ctx ext.Context) (string, error) {
        ctx.PrintInfo("You said: " + args)
        return "", nil
    },
    // Optional tab-completion:
    Complete: func(prefix string, ctx ext.Context) []string {
        return []string{"hello", "world"}
    },
})

Slash commands run in a dedicated goroutine (not a tea.Cmd), so they can safely block on prompts, I/O, etc.


Registering Keyboard Shortcuts

api.RegisterShortcut(ext.ShortcutDef{
    Key:         "ctrl+alt+p",
    Description: "Toggle plan mode",
}, func(ctx ext.Context) {
    // handler runs when shortcut is pressed
})

Registering Options

Options are configurable values resolved from env vars, config, or defaults:

api.RegisterOption(ext.OptionDef{
    Name:        "my-setting",
    Description: "Controls something",
    Default:     "false",
})

// Read at runtime (resolution: env KIT_OPT_MY_SETTING > config options.my-setting > default):
val := ctx.GetOption("my-setting")

// Set at runtime:
ctx.SetOption("my-setting", "true")

Context API Reference

The ext.Context struct provides runtime capabilities via function fields.

Output

ctx.Print("plain text")                    // plain output
ctx.PrintInfo("styled info block")         // bordered info block
ctx.PrintError("styled error block")       // red error block
ctx.PrintBlock(ext.PrintBlockOpts{         // custom styled block
    Text:        "content",
    BorderColor: "#a6e3a1",
    Subtitle:    "my-ext",
})
ctx.RenderMessage("renderer-name", "content")  // use a registered message renderer

Message Injection

ctx.SendMessage("prompt text")     // inject message and trigger agent turn (queued)
ctx.CancelAndSend("new prompt")   // cancel current turn, clear queue, send new message

Widgets

Persistent UI elements displayed above or below the input area:

ctx.SetWidget(ext.WidgetConfig{
    ID:        "my-widget",
    Placement: ext.WidgetAbove,  // or ext.WidgetBelow
    Content:   ext.WidgetContent{
        Text:     "Status: Active",
        Markdown: false,  // set true for markdown rendering
    },
    Style: ext.WidgetStyle{
        BorderColor: "#a6e3a1",  // hex color
        NoBorder:    false,
    },
    Priority: 0,  // lower values render first
})

ctx.RemoveWidget("my-widget")

Header and Footer

ctx.SetHeader(ext.HeaderFooterConfig{
    Content: ext.WidgetContent{Text: "My Header"},
    Style:   ext.WidgetStyle{BorderColor: "#89b4fa"},
})
ctx.RemoveHeader()

ctx.SetFooter(ext.HeaderFooterConfig{
    Content: ext.WidgetContent{Text: "My Footer"},
    Style:   ext.WidgetStyle{BorderColor: "#585b70"},
})
ctx.RemoveFooter()

Status Bar

ctx.SetStatus("key", "PLAN MODE", 10)  // key, text, priority (lower = further left)
ctx.RemoveStatus("key")

Interactive Prompts

These block until the user responds (safe in slash commands and goroutines):

// Selection list
result := ctx.PromptSelect(ext.PromptSelectConfig{
    Message: "Pick one:",
    Options: []string{"Option A", "Option B", "Option C"},
})
if !result.Cancelled {
    // result.Value string, result.Index int
}

// Yes/No confirmation
result := ctx.PromptConfirm(ext.PromptConfirmConfig{
    Message:      "Are you sure?",
    DefaultValue: false,
})
if !result.Cancelled {
    // result.Value bool
}

// Text input
result := ctx.PromptInput(ext.PromptInputConfig{
    Message:     "Enter name:",
    Placeholder: "my-project",
    Default:     "",
})
if !result.Cancelled {
    // result.Value string
}

// Multi-select (toggle with spacebar, confirm with enter)
result := ctx.PromptMultiSelect(ext.PromptMultiSelectConfig{
    Message: "Select extensions to install:",
    Options: []string{"git", "todo", "weather"},
    DefaultSelected: []int{0, 1, 2},  // pre-selected indices; nil = all selected
})
if !result.Cancelled {
    // result.Values []string — selected option texts
    // result.Indices []int — selected option indices
}

Overlay Dialogs

Modal dialogs with optional action buttons:

result := ctx.ShowOverlay(ext.OverlayConfig{
    Title:   "Confirmation",
    Content: ext.WidgetContent{Text: "Are you sure you want to proceed?", Markdown: true},
    Style:   ext.OverlayStyle{BorderColor: "#f38ba8"},
    Width:   60,          // 0 = 60% of terminal width
    MaxHeight: 20,        // 0 = 80% of terminal height
    Anchor:  ext.OverlayCenter,  // or ext.OverlayTopCenter, ext.OverlayBottomCenter
    Actions: []string{"Confirm", "Cancel"},
})
if !result.Cancelled {
    // result.Action string, result.Index int
}

Editor Interceptor

Wrap the built-in text input with custom key handling and rendering:

ctx.SetEditor(ext.EditorConfig{
    HandleKey: func(key string, currentText string) ext.EditorKeyAction {
        if key == "ctrl+s" {
            return ext.EditorKeyAction{Type: ext.EditorKeySubmit, SubmitText: currentText}
        }
        return ext.EditorKeyAction{Type: ext.EditorKeyPassthrough}
    },
    Render: func(width int, defaultContent string) string {
        return "[custom] " + defaultContent
    },
})

ctx.ResetEditor()                  // remove interceptor
ctx.SetEditorText("prefilled")     // set editor text content

EditorKeyAction types:

  • ext.EditorKeyPassthrough — let the default editor handle the key
  • ext.EditorKeyConsumed — swallow the key, do nothing
  • ext.EditorKeyRemap — remap to a different key: EditorKeyAction{Type: ext.EditorKeyRemap, RemappedKey: "up"}
  • ext.EditorKeySubmit — submit text: EditorKeyAction{Type: ext.EditorKeySubmit, SubmitText: "text"}

UI Visibility

ctx.SetUIVisibility(ext.UIVisibility{
    HideStartupMessage: true,
    HideStatusBar:      true,
    HideSeparator:      true,
    HideInputHint:      true,
})

Session Data

stats := ctx.GetContextStats()     // .EstimatedTokens, .ContextLimit, .UsagePercent, .MessageCount
msgs := ctx.GetMessages()          // []ext.SessionMessage on current branch
path := ctx.GetSessionPath()       // file path of session JSONL

// Persist custom data in the session tree:
id, err := ctx.AppendEntry("my-type", "data string")
entries := ctx.GetEntries("my-type")  // []ext.ExtensionEntry{ID, EntryType, Data, Timestamp}

Model Management

err := ctx.SetModel("anthropic/claude-sonnet-4-20250514")
models := ctx.GetAvailableModels()  // []ext.ModelInfoEntry

Tool Management

tools := ctx.GetAllTools()              // []ext.ToolInfo{Name, Description, Source, Enabled}
ctx.SetActiveTools([]string{"read", "grep"})  // restrict to these tools only
ctx.SetActiveTools(nil)                 // re-enable all tools

LLM Completions

Make standalone LLM calls (bypasses the agent tool loop):

resp, err := ctx.Complete(ext.CompleteRequest{
    Model:    "",             // empty = current model
    System:   "You are ...",  // optional system prompt
    Prompt:   "Summarize...", // the prompt
    MaxTokens: 1000,          // 0 = provider default
    OnChunk:  func(chunk string) { /* streaming */ },
})
// resp.Text, resp.InputTokens, resp.OutputTokens, resp.Model

TUI Suspension

Temporarily release the terminal for interactive subprocesses:

ctx.SuspendTUI(func() {
    cmd := exec.Command("vim", "file.go")
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Run()
})

Themes

Register, switch, and list color themes at runtime:

// Register a custom theme (empty fields inherit from default).
ctx.RegisterTheme("neon", ext.ThemeColorConfig{
    Primary:    ext.ThemeColor{Light: "#CC00FF", Dark: "#FF00FF"},
    Secondary:  ext.ThemeColor{Light: "#0088CC", Dark: "#00FFFF"},
    Success:    ext.ThemeColor{Light: "#00CC44", Dark: "#00FF66"},
    Warning:    ext.ThemeColor{Light: "#CCAA00", Dark: "#FFFF00"},
    Error:      ext.ThemeColor{Light: "#CC0033", Dark: "#FF0055"},
    Info:       ext.ThemeColor{Light: "#0088CC", Dark: "#00CCFF"},
    Text:       ext.ThemeColor{Light: "#111111", Dark: "#F0F0F0"},
    Background: ext.ThemeColor{Light: "#F0F0F0", Dark: "#0A0A14"},
    MdKeyword:  ext.ThemeColor{Light: "#CC00FF", Dark: "#FF00FF"},
    MdString:   ext.ThemeColor{Light: "#00CC44", Dark: "#00FF66"},
    MdComment:  ext.ThemeColor{Light: "#888888", Dark: "#555555"},
})

// Switch to a theme by name (built-in, file-based, or extension-registered).
err := ctx.SetTheme("neon")

// List all available theme names.
names := ctx.ListThemes()  // []string

ThemeColorConfig fields:

FieldDescription
PrimaryMain brand/accent color
SecondarySecondary accent
SuccessSuccess states
WarningWarning states
ErrorError/critical states
InfoInformational states
TextPrimary text
MutedDimmed text
VeryMutedVery dimmed text
BackgroundBase background
BorderPanel borders
MutedBorderSubtle dividers
SystemSystem messages
ToolTool-related elements
AccentSecondary highlight
HighlightHighlighted regions
MdHeadingMarkdown headings
MdLinkMarkdown links
MdKeywordSyntax: keywords
MdStringSyntax: strings
MdNumberSyntax: numbers
MdCommentSyntax: comments

Each field is an ext.ThemeColor with Light and Dark hex strings. Kit ships 22 built-in themes: kitt, catppuccin, dracula, tokyonight, nord, gruvbox, monokai, solarized, github, one-dark, rose-pine, ayu, material, everforest, kanagawa, amoled, synthwave, vesper, flexoki, matrix, vercel, zenburn.

Users can also drop .yml/.yaml/.json theme files in ~/.config/kit/themes/ (global) or .kit/themes/ (project-local). Extension-registered themes take highest precedence.

Application Control

ctx.Exit()                        // graceful shutdown
err := ctx.ReloadExtensions()     // hot-reload all extensions from disk

Context Fields

ctx.SessionID    // string
ctx.CWD          // string — current working directory
ctx.Model        // string — active model name
ctx.Interactive  // bool — true if running in TUI mode

Tool Renderers

Customize how tool calls are displayed in the TUI:

api.RegisterToolRenderer(ext.ToolRenderConfig{
    ToolName:     "bash",
    DisplayName:  "Shell",           // replaces auto-capitalized name
    BorderColor:  "#89b4fa",
    Background:   "",
    BodyMarkdown: true,              // render body through markdown
    RenderHeader: func(toolArgs string, width int) string {
        var args struct{ Command string `json:"command"` }
        json.Unmarshal([]byte(toolArgs), &args)
        return "$ " + args.Command
    },
    RenderBody: func(toolResult string, isError bool, width int) string {
        if isError {
            return "ERROR: " + toolResult
        }
        return toolResult
    },
})

Message Renderers

Define named output styles for ctx.RenderMessage():

api.RegisterMessageRenderer(ext.MessageRendererConfig{
    Name: "success",
    Render: func(content string, width int) string {
        return "  " + content  // green checkmark prefix
    },
})

// Usage in handlers:
ctx.RenderMessage("success", "All tests passed")

Critical Yaegi Constraints

No Named Function References in Struct Fields

Yaegi has a bug where named function references assigned to struct fields return zero values across the interpreter boundary. Always use anonymous closure literals:

// WRONG - will silently return zero values:
func myHandler(key, text string) ext.EditorKeyAction {
    return ext.EditorKeyAction{Type: ext.EditorKeyPassthrough}
}
ctx.SetEditor(ext.EditorConfig{HandleKey: myHandler})

// CORRECT - use anonymous closure:
ctx.SetEditor(ext.EditorConfig{
    HandleKey: func(key, text string) ext.EditorKeyAction {
        return ext.EditorKeyAction{Type: ext.EditorKeyPassthrough}
    },
})

This applies to ALL struct fields that take function values: ToolDef.Execute, CommandDef.Execute, EditorConfig.HandleKey, EditorConfig.Render, ToolRenderConfig.RenderHeader, ToolRenderConfig.RenderBody, etc.

No Interfaces Across the Boundary

All extension-facing API types are concrete structs, never interfaces. Yaegi crashes on interface wrapper generation.

Package-Level Variables for State

Yaegi supports package-level variables captured in closures. This is the standard way to maintain state across event callbacks:

package main

import "kit/ext"

var callCount int
var lastTool string

func Init(api ext.API) {
    api.OnToolResult(func(e ext.ToolResultEvent, ctx ext.Context) *ext.ToolResultResult {
        callCount++
        lastTool = e.ToolName
        return nil
    })
}

Common Patterns

Pattern: Tool Call Blocking

Block dangerous operations by intercepting tool calls:

api.OnToolCall(func(tc ext.ToolCallEvent, ctx ext.Context) *ext.ToolCallResult {
    if tc.ToolName == "bash" {
        var input struct{ Command string `json:"command"` }
        json.Unmarshal([]byte(tc.Input), &input)
        if strings.Contains(input.Command, "rm -rf") {
            return &ext.ToolCallResult{
                Block:  true,
                Reason: "Dangerous command blocked",
            }
        }
    }
    return nil
})

Pattern: System Prompt Injection

Augment the agent's behavior by injecting instructions:

api.OnBeforeAgentStart(func(_ ext.BeforeAgentStartEvent, ctx ext.Context) *ext.BeforeAgentStartResult {
    prompt := "Always respond with bullet points."
    return &ext.BeforeAgentStartResult{SystemPrompt: &prompt}
})

Pattern: Background Processing with SendMessage

Run work in a goroutine and inject results back:

api.RegisterCommand(ext.CommandDef{
    Name: "run",
    Description: "Run a command in the background",
    Execute: func(args string, ctx ext.Context) (string, error) {
        go func() {
            out, err := exec.Command("sh", "-c", args).CombinedOutput()
            if err != nil {
                ctx.SendMessage(fmt.Sprintf("Command failed: %s\n%s", err, out))
                return
            }
            ctx.SendMessage(fmt.Sprintf("Command output:\n```\n%s\n```", out))
        }()
        return "Running in background...", nil
    },
})

Pattern: Ephemeral Context Injection

Inject information into every LLM turn without persisting in session history:

api.OnContextPrepare(func(e ext.ContextPrepareEvent, ctx ext.Context) *ext.ContextPrepareResult {
    data, err := os.ReadFile(".kit/context.md")
    if err != nil {
        return nil
    }
    injected := ext.ContextMessage{
        Index:   -1,  // -1 = new message, not from session
        Role:    "system",
        Content: string(data),
    }
    msgs := append([]ext.ContextMessage{injected}, e.Messages...)
    return &ext.ContextPrepareResult{Messages: msgs}
})

Pattern: Live Widget Updates

Update a widget periodically from a goroutine:

api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {
    go func() {
        ticker := time.NewTicker(time.Second)
        defer ticker.Stop()
        for range ticker.C {
            ctx.SetWidget(ext.WidgetConfig{
                ID:        "clock",
                Placement: ext.WidgetAbove,
                Content:   ext.WidgetContent{Text: time.Now().Format("15:04:05")},
                Style:     ext.WidgetStyle{BorderColor: "#89b4fa"},
            })
        }
    }()
})

Pattern: Custom Theme with Slash Command

Register a theme and provide a slash command shortcut to activate it:

api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {
    ctx.RegisterTheme("neon", ext.ThemeColorConfig{
        Primary:    ext.ThemeColor{Light: "#CC00FF", Dark: "#FF00FF"},
        Secondary:  ext.ThemeColor{Light: "#0088CC", Dark: "#00FFFF"},
        Success:    ext.ThemeColor{Light: "#00CC44", Dark: "#00FF66"},
        Warning:    ext.ThemeColor{Light: "#CCAA00", Dark: "#FFFF00"},
        Error:      ext.ThemeColor{Light: "#CC0033", Dark: "#FF0055"},
        Info:       ext.ThemeColor{Light: "#0088CC", Dark: "#00CCFF"},
        Text:       ext.ThemeColor{Light: "#111111", Dark: "#F0F0F0"},
        Background: ext.ThemeColor{Light: "#F0F0F0", Dark: "#0A0A14"},
    })
})

api.RegisterCommand(ext.CommandDef{
    Name:        "neon",
    Description: "Switch to the neon cyberpunk theme",
    Execute: func(args string, ctx ext.Context) (string, error) {
        if err := ctx.SetTheme("neon"); err != nil {
            return "", err
        }
        return "Neon theme activated!", nil
    },
})

Pattern: Spawning Kit as a Sub-Agent

Use ctx.SpawnSubagent to spawn an isolated child Kit instance. The subagent runs as a subprocess with --json --no-extensions flags, ensuring isolation.

Blocking mode — waits for completion:

_, result, err := ctx.SpawnSubagent(ext.SubagentConfig{
    Prompt:       "Analyze the test files and summarize coverage",
    Model:        "anthropic/claude-haiku-3-5-20241022",  // empty = parent's model
    SystemPrompt: "You are a test analysis expert.",
    Timeout:      2 * time.Minute,  // 0 = 5 minute default
    Blocking:     true,
})
if err != nil {
    ctx.PrintError("spawn failed: " + err.Error())
    return
}
if result.Error != nil {
    ctx.PrintError("subagent failed: " + result.Error.Error())
    return
}
ctx.PrintInfo("Result:\n" + result.Response)
// result.Elapsed, result.ExitCode, result.SessionID
// result.Usage.InputTokens, result.Usage.OutputTokens (if available)

Background mode — returns immediately with a handle:

handle, _, err := ctx.SpawnSubagent(ext.SubagentConfig{
    Prompt: "Write unit tests for UserService",
    OnOutput: func(chunk string) {
        // Live stderr streaming (progress, tool calls, etc.)
    },
    OnEvent: func(event ext.SubagentEvent) {
        // Real-time events: "text", "reasoning", "tool_call",
        // "tool_result", "tool_execution_start", "tool_execution_end",
        // "turn_start", "turn_end"
        // event.Type, event.Content, event.ToolName, event.ToolArgs, etc.
    },
    OnComplete: func(result ext.SubagentResult) {
        ctx.SendMessage("Subagent finished:\n" + result.Response)
    },
})
// handle.Kill()        — terminate the subagent
// handle.Wait()        — block until completion, returns SubagentResult
// <-handle.Done()      — channel that closes on completion

SubagentConfig fields:

FieldTypeDescription
PromptstringTask instruction (required)
ModelstringOverride model ("provider/model"), empty = parent's
SystemPromptstringCustom system prompt, empty = default
Timeouttime.DurationExecution limit, 0 = 5 minutes
BlockingboolWait for completion vs return handle
NoSessionboolDon't persist subagent session file
ParentSessionIDstringLink to parent session (optional)
OnOutputfunc(string)Stderr streaming callback
OnEventfunc(SubagentEvent)Real-time event callback
OnCompletefunc(SubagentResult)Completion callback

SubagentResult fields:

FieldTypeDescription
ResponsestringFinal text response
ErrorerrorNon-nil on failure
ExitCodeintProcess exit code (0 = success)
Elapsedtime.DurationTotal execution time
Usage*SubagentUsageToken usage (InputTokens, OutputTokens)
SessionIDstringSubagent's session ID (if persisted)

You can also spawn Kit as a raw subprocess for simpler cases:

kit --quiet --no-session --no-extensions --system-prompt "You are a reviewer" --model anthropic/claude-sonnet-4-20250514 "Review this code"

Testing Extensions

Kit provides a testing package to help you write unit tests for your extensions:

package main

import (
    "testing"
    "github.com/mark3labs/kit/pkg/extensions/test"
    "github.com/mark3labs/kit/internal/extensions"
)

func TestMyExtension(t *testing.T) {
    harness := test.New(t)
    harness.LoadFile("my-ext.go")

    // Test event handlers
    _, err := harness.Emit(extensions.SessionStartEvent{SessionID: "test"})
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }

    // Verify behavior with assertions
    test.AssertPrinted(t, harness, "session started")
    test.AssertWidgetSet(t, harness, "my-widget")

    // Test tool blocking
    result, _ := harness.Emit(extensions.ToolCallEvent{ToolName: "dangerous"})
    test.AssertBlocked(t, result, "not allowed")
}

Key testing patterns:

  • Load extensions with LoadFile() or LoadString() for inline code
  • Emit events with Emit() to trigger handlers
  • Verify with 25+ assertion helpers: AssertWidgetSet(), AssertToolRegistered(), AssertPrintInfo(), etc.
  • Mock prompts by setting results on harness.Context().SetPromptSelectResult()
  • Test multiple scenarios per extension with isolated harness instances

See examples/extensions/tool-logger_test.go for a complete example with 14 test cases.

CLI Testing Commands

# Validate syntax of all discovered extensions
kit extensions validate

# List loaded extensions
kit extensions list

# Run with a specific extension
kit -e path/to/extension.go

# Run with multiple extensions
kit -e ext1.go -e ext2.go

# Disable all extensions
kit --no-extensions

# Generate an example extension scaffold
kit extensions init

Distributing Extensions via Git Repositories

Extensions can be distributed and installed from git repositories using kit install. This enables sharing extensions with others and maintaining versioned collections.

Repository Structure

Extensions support two organization patterns within a repo:

Single-file extensions (simple, standalone):

my-extension-repo/
├── weather.go           # Single extension file
├── todo.go              # Another extension
└── README.md            # Installation and usage docs

Multi-file extensions (with main.go entry point):

my-extension-repo/
├── git-tools/
│   ├── main.go          # Entry point
│   ├── helpers.go       # Supporting code
│   └── config.go        # Configuration
├── todo/
│   ├── main.go          # Entry point
│   └── storage.go       # Storage logic
└── README.md

Hybrid approach (single files + subdirectories with main.go):

my-extensions/
├── weather.go           # Single file extension
├── calculator.go        # Single file extension
├── git-tools/
│   ├── main.go          # Multi-file extension
│   └── utils.go
└── README.md

Installing from Git

Users install extensions using the kit install command:

# Install from GitHub (latest)
kit install github.com/user/repo

# Pin to a specific version/tag
kit install github.com/user/repo@v1.0.0
kit install github.com/user/repo@main
kit install github.com/user/repo@abc1234

# Install locally in project (./.kit/git/)
kit install github.com/user/repo --local

# Interactive selection for repos with multiple extensions
kit install github.com/user/collection --select

Supported URL formats:

  • github.com/user/repo — Shorthand (defaults to HTTPS)
  • git:github.com/user/repo — Git prefix format
  • https://github.com/user/repo — HTTPS URL
  • ssh://git@github.com/user/repo — SSH URL
  • git@github.com:user/repo — SSH shorthand

Managing Installed Extensions

# Update an installed extension (skips pinned versions)
kit install github.com/user/repo --update

# Remove an installed extension
kit install github.com/user/repo --uninstall

# List all loaded extensions
kit extensions list

# Validate all extensions
kit extensions validate

Extension Selection

For repos containing multiple extensions, users can select which to install:

# Interactive selection
kit install github.com/user/collection --select

This prompts the user to choose which extensions to install. Selected extensions are recorded in the manifest, and only those are loaded at runtime (others in the repo are ignored).

README Template for Extension Repos

Include this in your extension repo's README.md:

# My Kit Extensions

A collection of extensions for [Kit](https://github.com/mark3labs/kit).

## Installation

### Install all extensions
\`\`\`bash
kit install github.com/username/repo
\`\`\`

### Install specific extensions
\`\`\`bash
kit install github.com/username/repo --select
\`\`\`

### Install locally in a project
\`\`\`bash
kit install github.com/username/repo --local
\`\`\`

## Extensions

### Extension Name
Description of what it does.

- **Path**: `./ext-name/main.go` or `./ext-name.go`
- **Commands**: `/command-name`
- **Tools**: `tool_name`

## Requirements

- Kit vX.Y.Z+
- Any other dependencies

## Update

\`\`\`bash
kit install github.com/username/repo --update
\`\`\`

Storage Locations

Installed extensions are stored at:

  • Global: ~/.local/share/kit/git/<host>/<owner>/<repo>/
  • Project-local: ./.kit/git/<host>/<owner>/<repo>/
  • Manifest: packages.json in respective directories

Complete Example: Plan Mode

A full extension that restricts the agent to read-only tools, with a slash command, keyboard shortcut, option, status bar indicator, and system prompt injection:

//go:build ignore

package main

import (
    "strings"
    "kit/ext"
)

func Init(api ext.API) {
    readOnlyTools := []string{"read", "grep", "find", "ls"}
    var planActive bool

    api.RegisterOption(ext.OptionDef{
        Name:        "plan",
        Description: "Start in plan mode (read-only tools)",
        Default:     "false",
    })

    api.RegisterShortcut(ext.ShortcutDef{
        Key:         "ctrl+alt+p",
        Description: "Toggle plan/explore mode",
    }, func(ctx ext.Context) {
        planActive = !planActive
        applyMode(ctx, planActive, readOnlyTools)
    })

    api.RegisterCommand(ext.CommandDef{
        Name:        "plan",
        Description: "Toggle plan/explore mode",
        Execute: func(args string, ctx ext.Context) (string, error) {
            planActive = !planActive
            applyMode(ctx, planActive, readOnlyTools)
            return "", nil
        },
    })

    api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {
        if strings.ToLower(ctx.GetOption("plan")) == "true" {
            planActive = true
            applyMode(ctx, true, readOnlyTools)
        }
    })

    api.OnBeforeAgentStart(func(_ ext.BeforeAgentStartEvent, ctx ext.Context) *ext.BeforeAgentStartResult {
        if !planActive {
            return nil
        }
        prompt := `You are in PLAN MODE (read-only). You can ONLY read and search.
Focus on understanding, analysis, and generating plans.`
        return &ext.BeforeAgentStartResult{SystemPrompt: &prompt}
    })
}

func applyMode(ctx ext.Context, active bool, tools []string) {
    if active {
        ctx.SetActiveTools(tools)
        ctx.SetStatus("plan-mode", "PLAN MODE (read-only)", 10)
        ctx.PrintInfo("Plan mode ON")
    } else {
        ctx.SetActiveTools(nil)
        ctx.RemoveStatus("plan-mode")
        ctx.PrintInfo("Plan mode OFF")
    }
}

Bridged SDK APIs (New)

Extensions can now access powerful internal SDK capabilities that enable advanced features like conversation tree navigation, dynamic skill loading, template parsing, and model resolution.

Tree Navigation

Navigate the conversation tree, summarize branches, and implement "fresh context" loops:

// Get a specific node by ID with full metadata and children
node := ctx.GetTreeNode("entry-id")
// node.ID, node.ParentID, node.Type ("message"/"branch_summary"/etc)
// node.Role, node.Content, node.Model, node.Children ([]string)

// Get the current branch from root to leaf
branch := ctx.GetCurrentBranch()  // []ext.TreeNode

// Get child entry IDs of a node
children := ctx.GetChildren("entry-id")  // []string

// Navigate/fork to a different entry in the tree
result := ctx.NavigateTo("entry-id")  // ext.TreeNavigationResult{Success, Error}

// Summarize a range of the branch using LLM
summary := ctx.SummarizeBranch("from-id", "to-id")  // string

// Collapse a branch range into a summary entry (fresh context primitive)
result := ctx.CollapseBranch("from-id", "to-id", "summary text")

Skill Loading

Load and inject skills dynamically at runtime:

// Discover skills from standard locations
result := ctx.DiscoverSkills()  // ext.SkillLoadResult{Skills, Error}
// Standard locations: ~/.config/kit/skills/, .kit/skills/, .agents/skills/

// Load a specific skill file
skill, err := ctx.LoadSkill("/path/to/skill.md")  // (*ext.Skill, error string)
// skill.Name, skill.Description, skill.Content, skill.Tags, skill.When

// Load all skills from a directory
result := ctx.LoadSkillsFromDir("/path/to/skills")  // ext.SkillLoadResult

// Inject a skill as context (pre-loads for next turn)
err := ctx.InjectSkillAsContext("skill-name")  // error string

// Inject a skill file directly
err := ctx.InjectRawSkillAsContext("/path/to/skill.md")  // error string

// Get all discovered skills
skills := ctx.GetAvailableSkills()  // []ext.Skill

Template Parsing

Parse and render templates with variable substitution:

// Parse a template to extract {{variables}}
tpl := ctx.ParseTemplate("name", "Hello {{name}}, welcome to {{place}}!")
// tpl.Name, tpl.Content, tpl.Variables ([]string)

// Render a template with variable values
vars := map[string]string{"name": "Alice", "place": "Kit"}
rendered := ctx.RenderTemplate(tpl, vars)  // "Hello Alice, welcome to Kit!"

// Parse command-line style arguments
pattern := ext.ArgumentPattern{
    Positional: []string{"command", "target"},  // $1, $2
    Rest:       "args",                         // $@
    Flags:      map[string]string{"--loop": "loop", "-f": "force"},
}
result := ctx.ParseArguments("deploy staging --loop 5", pattern)
// result.Vars["command"] = "deploy"
// result.Vars["target"] = "staging"
// result.Flags["--loop"] = "5"

// Simple positional argument parsing ($1, $2, $@)
args := ctx.SimpleParseArguments("deploy staging --force", 2)
// args[0] = "deploy staging --force" (full input)
// args[1] = "deploy" ($1)
// args[2] = "staging" ($2)
// args[3] = "--force" ($@)

// Evaluate model conditionals with wildcards
matches := ctx.EvaluateModelConditional("claude-*")  // bool
// Patterns: * matches any, ? matches single char, comma = OR

// Render content with <if-model> conditionals
content := `<if-model is="claude-*">Hi Claude<else>Hi there</if-model>`
rendered := ctx.RenderWithModelConditionals(content)  // based on current model

Model Resolution

Resolve model fallback chains and query capabilities:

// Resolve a chain of model preferences (tries each until available)
result := ctx.ResolveModelChain([]string{
    "anthropic/claude-opus-4",
    "anthropic/claude-sonnet-4",
    "openai/gpt-4o",
})
// result.Model (selected), result.Capabilities, result.Attempted, result.Error

// Get capabilities for a specific model
caps, err := ctx.GetModelCapabilities("anthropic/claude-sonnet-4")
// caps.Provider, caps.ModelID, caps.ContextLimit, caps.Reasoning, caps.Streaming

// Check if a model is available (provider exists)
available := ctx.CheckModelAvailable("anthropic/claude-sonnet-4")  // bool

// Get current provider/model ID
provider := ctx.GetCurrentProvider()  // "anthropic"
modelID := ctx.GetCurrentModelID()    // "claude-sonnet-4"

Key Files for Reference

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

36.05%
按下载量换算43

Claude

29.57%
按下载量换算35

Cursor

17.09%
按下载量换算20

Gemini CLI

8.08%
按下载量换算10

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

执行命令

安装流程涉及命令执行,可能通过 npx skills add https://github.com/mark3labs/kit --skill kit-extensions 联网下载 Skill 或依赖。用户安装前应确认命令来源、仓库内容和执行环境。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills