autocomplete

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package autocomplete provides a fuzzy-matching autocomplete engine for the tmux-webui terminal input bar. It is a 1:1 Go port of the Python autocomplete.py module (Phase 1.7).

Architecture

The package is split into focused files:

  • scanner.go — Scanner interface + Item struct
  • fuzzy.go — fuzzyScore + rankAndFilter helpers
  • path.go — filesystem path completion
  • claude.go — optional ~/.claude scanner + minimal YAML frontmatter parser
  • cache.go — ResourceCache with mutex + time.Ticker auto-refresh
  • complete.go — Complete(query, typeFilter) main entry point (router)

Integration with server.go

Import and wire up in your HTTP server:

import "github.com/operonlab/tmux-webui/internal/autocomplete"

ac := autocomplete.New(autocomplete.Options{
    ClaudeDir: cfg.Autocomplete.ClaudeDir,
})
defer ac.Close()

mux.HandleFunc("GET /api/autocomplete", func(w http.ResponseWriter, r *http.Request) {
    q := r.URL.Query().Get("q")
    t := r.URL.Query().Get("type")
    items := ac.Complete(q, t)
    if items == nil {
        items = []autocomplete.Item{}
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(items)
})

mux.HandleFunc("GET /api/autocomplete/refresh", func(w http.ResponseWriter, r *http.Request) {
    stats := ac.Refresh()
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(stats)
})

mux.HandleFunc("GET /api/autocomplete/stats", func(w http.ResponseWriter, r *http.Request) {
    stats := ac.Stats()
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(stats)
})

Routing rules

Complete() routes by the first character of query (mirroring Python version):

  • "/" prefix → slash items (skills + commands), optional typeFilter "skill"/"command"
  • "@" prefix → at items (agents + mcp servers)
  • "path" typeFilter or "~"/"." prefix or "/" in query → path completion
  • empty query → empty slice

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExpandClaudeDir

func ExpandClaudeDir() string

ExpandClaudeDir returns ~/.claude expanded to an absolute path. Returns empty string if home dir is unavailable.

Types

type BuiltinScanner

type BuiltinScanner struct{}

BuiltinScanner emits the curated list of Claude Code built-in slash commands — the universally available commands a user can type at any session (not loaded from disk, not user-defined). The list is hard-coded because Claude Code does not currently expose a discovery API.

Last verified: claude-code 1.x @ 2026-05-16 against https://code.claude.com/docs/en/commands. When upgrading Claude Code, cross-check that page and bump the date above. Entries marked "[bundled skill]" come from the same docs page (the "Skill" rows in the commands table) — they live inside the CLI rather than in ~/.claude/skills, but they are typed and displayed the same way.

Aliases listed only in a command's description (e.g. /quit for /exit, /bashes for /tasks) are intentionally omitted unless they appear as a standalone row in the docs table — the goal is to mirror the published "/ menu" surface, not every alternative spelling.

func NewBuiltinScanner

func NewBuiltinScanner() *BuiltinScanner

NewBuiltinScanner returns a scanner that always yields the built-in slash command roster.

func (*BuiltinScanner) Scan

func (s *BuiltinScanner) Scan(_ context.Context) []Item

Scan implements Scanner.

type ClaudeDirScanner

type ClaudeDirScanner struct {
	// contains filtered or unexported fields
}

ClaudeDirScanner scans ~/.claude/{skills,commands,agents} and settings.json for autocomplete items. It implements Scanner.

func NewClaudeDirScanner

func NewClaudeDirScanner(claudeDir string) *ClaudeDirScanner

NewClaudeDirScanner returns a scanner rooted at claudeDir (e.g. "~/.claude"). The path must already be expanded (no "~").

func (*ClaudeDirScanner) Scan

func (s *ClaudeDirScanner) Scan(_ context.Context) []Item

Scan implements Scanner. It walks skills, commands, agents, and settings.json.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine is the main autocomplete engine.

func New

func New(opts Options) *Engine

New creates an Engine with the provided options. A background scanner goroutine is started immediately when at least one source is enabled.

func (*Engine) Close

func (e *Engine) Close()

Close stops the background refresh goroutine. Call via defer after New().

func (*Engine) Complete

func (e *Engine) Complete(query, typeFilter string) []Item

Complete routes the query and returns up to e.maxResults suggestions (0 → defaultMaxResults, 15).

Routing rules:

  • typeFilter == "path" → path completion
  • typeFilter == "slash" → slash items (builtin + skill + command)
  • typeFilter == "skill" / "command" / "builtin" → slash items, narrowed by type
  • typeFilter == "" && query starts with "/" → slash items
  • typeFilter == "at" / "agent" / "mcp" / "file" → at items (agents + mcp + files)
  • typeFilter == "" && query starts with "@" → at items
  • typeFilter == "shortcut" → user-defined shortcuts (bare-word trigger)
  • typeFilter == "" && query starts with "~" / "./" → path completion
  • typeFilter == "" && query contains "/" → path completion
  • empty query → empty slice

func (*Engine) CompleteIn

func (e *Engine) CompleteIn(cwd, query, typeFilter string) []Item

CompleteIn is Complete with a working-directory context: when cwd is non-empty, the "@" branch additionally offers files under cwd (fzf-style, relative paths) alongside agents/mcp. The web UI passes the focused pane's cwd so "@parti" completes to a path the CLI inside that pane can resolve.

func (*Engine) Refresh

func (e *Engine) Refresh() Stats

Refresh forces an immediate re-scan and returns the updated Stats.

func (*Engine) Stats

func (e *Engine) Stats() Stats

Stats returns the current cache statistics without triggering a scan.

type Item

type Item struct {
	Name        string `json:"name"`
	DisplayName string `json:"display_name,omitempty"`
	Description string `json:"description,omitempty"`
	Type        string `json:"type"` // "skill" | "command" | "agent" | "mcp" | "builtin" | "path" | "file" | "shortcut"
	Icon        string `json:"icon,omitempty"`
	// Source records origin: empty / "user" for ~/.claude scanner,
	// "plugin:<marketplace>:<plugin>" for marketplace plugins,
	// "builtin" for Claude Code built-in slash commands.
	Source string `json:"source,omitempty"`
	// Insert, when non-empty, is the exact text a pick places in the input
	// buffer instead of Name — shortcuts map a short keyword to a longer
	// command/prompt.
	Insert string `json:"insert,omitempty"`
}

Item represents a single autocomplete suggestion.

type Options

type Options struct {
	// ClaudeDir enables scanning of ~/.claude/{skills,commands,agents}.
	// Pass an empty string to disable; path completion is always enabled.
	// "~" is NOT expanded here — pass os.UserHomeDir() + "/.claude" explicitly,
	// or use ExpandClaudeDir() if you want automatic "~/.claude" expansion.
	ClaudeDir string

	// RefreshInterval controls how often the background scanner runs.
	// Zero defaults to 5 minutes.
	RefreshInterval time.Duration

	// IncludePlugins enables scanning of
	// ~/.claude/plugins/marketplaces/<m>/{external_plugins,plugins}/<p>/{skills,commands,agents}/.
	// Requires ClaudeDir to be set; ignored otherwise.
	IncludePlugins bool

	// IncludeBuiltins enables the curated list of Claude Code built-in slash
	// commands (/compact, /model, /clear, etc.).
	IncludeBuiltins bool

	// MaxResults caps the suggestions returned per query. Zero → defaultMaxResults
	// (15), the historical default.
	MaxResults int

	// SourcePriority orders the slash-item sources for tie-breaking at equal fuzzy
	// score. Empty → the canonical builtins/skills/commands order. A source
	// omitted from the list keeps its default position after the listed ones
	// (reorder, never drop).
	SourcePriority []string

	// ShortcutsFile, when set, enables user-defined shortcut completion from
	// that JSON file (see shortcuts.go for the format). A missing file is
	// fine — the source stays empty until the user creates it.
	ShortcutsFile string
}

Options configures the autocomplete Engine.

func DefaultOptions

func DefaultOptions(claudeDir string) Options

DefaultOptions returns Options with plugin + builtin scanning enabled — the recommended baseline for matching the full Claude Code resource universe. Callers wanting legacy user-only behavior construct Options literally.

type PluginScanner

type PluginScanner struct {
	// contains filtered or unexported fields
}

PluginScanner walks plugin marketplaces under <claudeDir>/plugins/.

Two modes:

  1. **Installed-only** (preferred, matches Claude Code behavior). If <claudeDir>/plugins/installed_plugins.json exists, the scanner reads it and only emits items from plugins the user has actually `/plugin install`ed. Each entry's `installPath` is treated as the plugin root (it normally lives under <claudeDir>/plugins/cache/<marketplace>/ <plugin>/<version>/).

  2. **Fallback whole-tree** (legacy). If the manifest is absent or unparseable, the scanner walks every plugin under <claudeDir>/plugins/marketplaces/<marketplace>/{plugins, external_plugins}/<plugin>/. This is what the original scanner did and is kept so OSS users (and tests using bare marketplace fixtures) continue to work without a manifest.

Both "external_plugins/" and "plugins/" subdir layouts coexist in the wild — the fallback scans either.

func NewPluginScanner

func NewPluginScanner(claudeDir string) *PluginScanner

NewPluginScanner returns a scanner rooted at claudeDir (e.g. "~/.claude"). The path must already be expanded (no "~").

func (*PluginScanner) Scan

func (s *PluginScanner) Scan(_ context.Context) []Item

Scan implements Scanner. Returns nil if neither manifest nor marketplaces dir can be read.

type ResourceCache

type ResourceCache struct {
	// contains filtered or unexported fields
}

ResourceCache holds a snapshot of scanned Claude resources and refreshes them periodically using a time.Ticker. All fields are protected by a Mutex.

func (*ResourceCache) Close

func (c *ResourceCache) Close()

Close stops the background ticker goroutine.

type Scanner

type Scanner interface {
	Scan(ctx context.Context) []Item
}

Scanner is the interface implemented by resource scanners (Claude dir, etc.). Scan performs the full directory walk and returns all discovered items. Implementations must be safe to call concurrently.

type ShortcutScanner

type ShortcutScanner struct {
	// contains filtered or unexported fields
}

ShortcutScanner reads user-defined shortcuts from a JSON file.

func NewShortcutScanner

func NewShortcutScanner(path string) *ShortcutScanner

NewShortcutScanner returns a scanner over the given shortcuts file.

func (*ShortcutScanner) Scan

func (s *ShortcutScanner) Scan(_ context.Context) []Item

Scan implements Scanner. A missing file is normal (feature unused) and returns nil silently; a malformed file is a user error and is logged loud rather than silently yielding an empty menu.

type Stats

type Stats struct {
	Skills     int `json:"skills"`
	Commands   int `json:"commands"`
	Agents     int `json:"agents"`
	MCPServers int `json:"mcp_servers"`
	Builtins   int `json:"builtins"`
	Shortcuts  int `json:"shortcuts"`
}

Stats reports how many items are currently cached.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL