Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var ChangelogCmd = &cobra.Command{ Use: "changelog [version]", Short: "show what changed in recent iris releases", Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { limit := changelogCount if len(args) == 1 { limit = 0 } releases, rateLimited, err := FetchReleasesCached(limit, changelogRefresh) if err != nil { if errors.Is(err, updater.ErrRateLimited) { fmt.Fprintln(cmd.ErrOrStderr(), "\033[31m[IRIS] rate limited by GitHub API, try again later\033[0m") return } fmt.Fprintf(cmd.ErrOrStderr(), "\033[31m[IRIS] could not fetch changelog: %v\033[0m\n", err) return } if len(releases) == 0 { cmd.Println("no releases found") return } out := cmd.OutOrStdout() if len(args) == 1 { target := strings.TrimPrefix(args[0], "v") for _, release := range releases { if strings.TrimPrefix(release.TagName, "v") == target { printRelease(out, release, true) if rateLimited { fmt.Fprintln(out) fmt.Fprintln(out, "\033[33m[IRIS] rate limited by GitHub API, showing cached data\033[0m") } return } } fmt.Fprintf(cmd.ErrOrStderr(), "\033[31m[IRIS] release %s not found\033[0m\n", args[0]) return } for i, release := range releases { if i > 0 { fmt.Fprintln(out) } printRelease(out, release, i == 0) } if rateLimited { fmt.Fprintln(out) fmt.Fprintln(out, "\033[33m[IRIS] rate limited by GitHub API, showing cached data\033[0m") } }, }
View Source
var ConfigCmd = &cobra.Command{
Use: "config",
Short: "manage iris configuration",
}
View Source
var ConfigInitCmd = &cobra.Command{ Use: "init", Short: "initialize default configuration file with comments", Run: func(cmd *cobra.Command, args []string) { path, err := config.ConfigPath() if err != nil { fmt.Printf("failed to get config path: %v\n", err) return } if _, statErr := os.Stat(path); statErr == nil { fmt.Printf("config file already exists at %s\n", path) return } _ = os.MkdirAll(filepath.Dir(path), 0755) defaultContent := `# ~/.config/iris/config.toml # iris configuration file [core] # schema version # do not edit this field manually version = 1 # override shell: "bash", "zsh", "fish", keep empty for auto detection shell = "" # run the selected shell as a login shell shell-login = false # startup mode: "last", "spec", "history" # "last" = remember last mode used mode = "last" # enable debug logging debug = false # automatically expand aliases on space expand-alias = true # automatically execute command after accepting suggestion auto-execute = false # 0 = off, 1 = atuin history only, 2 = atuin history + default history atuin-history = 0 # custom atuin database path (leave empty for default) atuin-db-path = "" # probe unknown binaries with ` + "`__complete`" + ` for Cobra-based CLI suggestions. cobra-probe-enabled = true # what navigate-up/navigate-down do while the menu is closed: # "history" = browse iris history, "shell" = leave the key to the shell (e.g. atuin) navigate-closed = "history" [ui] # visual style: "modern" (icons, category pills, shortcut footer) or "classic" (minimalist, centered number, no icons) style = "modern" # enable Nerd Fonts icons in overlay menu nerd-fonts = true # show hidden files with dot prefix hidden-files = false # 0 = off, 1 = on, 2 = ghost text only (menu opens on toggle key) # legacy true/false still accepted ghost-text = 1 # maximum suggestions to display max-suggestions = 100 # maximum suggestion rows shown in the menu max-height = 6 # overlay width, as columns (80) or a share of the terminal ("80%") # 0 keeps the built-in default width max-width = 0 [git] # hide current branch in checkout/switch list filter-active-branch = true # merge remote and local branches with same name deduplicate-branches = true [updater] # check for updates on startup check-on-startup = true # update channel: "stable", "nightly" channel = "stable" # interval between update checks, e.g. "24h", "6h", "30m" check-interval = "24h" # 0 = off (default, notify only), 1 = auto-install, 2 = always confirm first auto-update = 0 [zoxide] # also complete cd from zoxide's frecency database, not just the # children of the current directory (requires zoxide on PATH) extend-cd = false [keybindings] toggle-mode = "ctrl+r" toggle-menu = "shift+tab" select = "tab" navigate-up = "up" navigate-down = "down" navigate-right = "right" ` err = os.WriteFile(path, []byte(defaultContent), 0644) if err != nil { fmt.Printf("failed to write config file: %v\n", err) return } fmt.Printf("initialized config file at %s\n", path) }, }
View Source
var ConfigShowCmd = &cobra.Command{ Use: "show", Short: "show the resolved configuration", Run: func(cmd *cobra.Command, args []string) { enc := toml.NewEncoder(cmd.OutOrStdout()) if err := enc.Encode(config.Get()); err != nil { fmt.Fprintf(cmd.ErrOrStderr(), "failed to encode config: %v\n", err) } }, }
View Source
var InitCmd = &cobra.Command{ Use: "init [bash|zsh|fish]", Short: "Generate the autostart script for your shell", Long: `Add the output of this command to your shell's configuration file to start Iris automatically. For example, add this to your ~/.zshrc: eval "$(iris init zsh)"`, ValidArgs: []string{"bash", "zsh", "fish"}, Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Run: func(cmd *cobra.Command, args []string) { shellName := args[0] switch shellName { case "zsh": fmt.Printf(` # Iris Autostart Hook # a multiplexer pane inherits IRIS_* but runs on its own tty, so those vars # point at an iris that is not driving this terminal if [ -n "$IRIS_PID" ] && [ "$IRIS_PID" != "$PPID" ] && [ "${TTY:-$(tty 2>/dev/null)}" != "$IRIS_TTY" ]; then unset IRIS_PID IRIS_IS_CHILD IRIS_FD IRIS_TTY fi # a non-interactive shell (tool runners sourcing rc files, scripts) has no # prompt to complete, and exec'ing here would seize the tty from the real iris if [[ -o interactive ]] && [ -t 0 ] && [ -z "$IRIS_PID" ] && [ -z "$IRIS_RESCUE" ] && [ -z "$ZSH_EXECUTION_STRING" ]; then export IRIS_ACTIVE_SHELL="zsh" if [ -n "$FNM_MULTISHELL_PATH" ]; then path=(${path:#*fnm_multishells*}) unset FNM_MULTISHELL_PATH fi exec iris fi # Iris Autocomplete Hook if [ -n "$IRIS_PID" ] && [ -n "$IRIS_FD" ]; then # the cursor comes with the line: sending only $LBUFFER made iris drop # everything to the right of the cursor and believe it sat at the end _iris_send_lbuffer() { print -u $IRIS_FD -N -r -- "IRIS_LINE:${#LBUFFER}:$BUFFER" 2>/dev/null } _iris_sync_cwd() { print -u $IRIS_FD -N -r -- "IRIS_CWD:$PWD" 2>/dev/null } _iris_send_aliases() { local a a="$(alias -L 2>/dev/null)" if [[ "$a" != "$_iris_last_aliases" ]]; then _iris_last_aliases="$a" print -u $IRIS_FD -N -r -- "IRIS_ALIASES:$a" 2>/dev/null fi } _iris_precmd() { local iris_exit_code=$? _iris_sync_cwd _iris_send_aliases print -u $IRIS_FD -N -r -- "IRIS_CMD_STOP:$iris_exit_code" 2>/dev/null } _iris_preexec() { print -u $IRIS_FD -N -r -- "IRIS_CMD_START" 2>/dev/null } autoload -Uz add-zle-hook-widget autoload -Uz add-zsh-hook add-zle-hook-widget line-pre-redraw _iris_send_lbuffer add-zsh-hook precmd _iris_precmd add-zsh-hook preexec _iris_preexec add-zsh-hook chpwd _iris_sync_cwd fi `) case "bash": fmt.Printf(` # Iris Autostart Hook # a multiplexer pane inherits IRIS_* but runs on its own tty, so those vars # point at an iris that is not driving this terminal if [ -n "$IRIS_PID" ] && [ "$IRIS_PID" != "$PPID" ] && [ "$(tty 2>/dev/null)" != "$IRIS_TTY" ]; then unset IRIS_PID IRIS_IS_CHILD IRIS_FD IRIS_TTY fi # a non-interactive shell (tool runners sourcing rc files, scripts) has no # prompt to complete, and exec'ing here would seize the tty from the real iris if [[ $- == *i* ]] && [ -t 0 ] && [ -z "$IRIS_PID" ] && [ -z "$IRIS_RESCUE" ] && [ -z "$BASH_EXECUTION_STRING" ]; then export IRIS_ACTIVE_SHELL="bash" if [ -n "$FNM_MULTISHELL_PATH" ]; then PATH="${PATH//:$FNM_MULTISHELL_PATH\/bin:/:}" PATH="${PATH//:$FNM_MULTISHELL_PATH:/:}" PATH="${PATH#:}" PATH="${PATH%%:}" export PATH unset FNM_MULTISHELL_PATH fi exec iris fi # Iris Autocomplete Hook if [ -n "$IRIS_PID" ] && [ -n "$IRIS_FD" ]; then _iris_last_line="" _iris_last_aliases="" _iris_send_aliases() { local a a="$(alias -p 2>/dev/null)" if [[ "$a" != "$_iris_last_aliases" ]]; then _iris_last_aliases="$a" printf 'IRIS_ALIASES:%%s\0' "$a" >&$IRIS_FD 2>/dev/null fi } _iris_bash_cmd_start() { printf 'IRIS_CMD_START\0' >&$IRIS_FD 2>/dev/null } _iris_send_line() { if [[ "$READLINE_LINE" != "$_iris_last_line" ]]; then _iris_last_line="$READLINE_LINE" printf 'IRIS_LINE:%%d:%%s\0' "$READLINE_POINT" "$READLINE_LINE" >&$IRIS_FD 2>/dev/null fi } _iris_precmd() { local iris_exit_code=$? printf 'IRIS_CWD:%%s\0' "$PWD" >&$IRIS_FD 2>/dev/null _iris_send_aliases printf 'IRIS_CMD_STOP:%%d\0' "$iris_exit_code" >&$IRIS_FD 2>/dev/null } trap '_iris_bash_cmd_start' DEBUG PROMPT_COMMAND="_iris_precmd${PROMPT_COMMAND:+;$PROMPT_COMMAND}" bind -x '"\e[200~": _iris_send_line' 2>/dev/null fi `) case "fish": disableFishAutosuggest := "" if config.Get().UI.GhostText != config.GhostTextOff { disableFishAutosuggest = ` set -g fish_autosuggestion_enabled 0 ` } fmt.Printf(` # Iris Autostart Hook # a multiplexer pane inherits IRIS_* but runs on its own tty, so those vars # point at an iris that is not driving this terminal if set -q IRIS_PID; and test "$IRIS_PID" != "$fish_pid"; and test (tty 2>/dev/null) != "$IRIS_TTY" set -e IRIS_PID IRIS_IS_CHILD IRIS_FD IRIS_TTY end # a non-interactive shell (tool runners sourcing rc files, scripts) has no # prompt to complete, and exec'ing here would seize the tty from the real iris if status is-interactive; and test -t 0; and not set -q IRIS_PID; and not set -q IRIS_RESCUE set -gx IRIS_ACTIVE_SHELL "fish" if set -q FNM_MULTISHELL_PATH set -l fnm_idx (contains -i $FNM_MULTISHELL_PATH $fish_user_paths) if test -n "$fnm_idx" set -e fish_user_paths[$fnm_idx] end set -e FNM_MULTISHELL_PATH end exec iris end # Iris Autocomplete Hook if set -q IRIS_PID; and set -q IRIS_FD %s set -g _iris_last_line "" set -g _iris_last_aliases "" set -g _iris_last_abbrs "" set -g _iris_last_func_count 0 function _iris_postexec --on-event fish_postexec printf "IRIS_CMD_STOP:%%d\x00" $status >&$IRIS_FD 2>/dev/null end function _iris_fish_precmd --on-event fish_prompt printf "IRIS_CWD:%%s\x00" "$PWD" >&$IRIS_FD 2>/dev/null # fish abbr is builtin and faster, so we check it first set -l abbrs (abbr --show 2>/dev/null | string collect) if test "$abbrs" != "$_iris_last_abbrs" set -g _iris_last_abbrs "$abbrs" printf "IRIS_ABBRS:%%s\x00" "$abbrs" >&$IRIS_FD 2>/dev/null end # alias in fish is wrapper of function, so we count function first to avoid call alias every prompt set -l func_count (functions -n 2>/dev/null | count) if test "$func_count" != "$_iris_last_func_count" set -g _iris_last_func_count "$func_count" set -l aliases (alias 2>/dev/null | string collect) set -g _iris_last_func_count (functions -n 2>/dev/null | count) set -g _iris_last_aliases "$aliases" printf "IRIS_ALIASES:%%s\x00" "$aliases" >&$IRIS_FD 2>/dev/null end end function _iris_fish_preexec --on-event fish_preexec printf "IRIS_CMD_START\x00" >&$IRIS_FD 2>/dev/null end end `, disableFishAutosuggest) } }, }
View Source
var SetupCmd = &cobra.Command{ Use: "setup [shell]", Short: "Automatically setup iris shell integration and install binary", Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { home, _ := os.UserHomeDir() localBin := filepath.Join(home, ".local", "bin") _ = os.MkdirAll(localBin, 0755) exe, _ := os.Executable() targetExe := filepath.Join(localBin, "iris") fmt.Printf("Installing iris to %s...\n", targetExe) input, err := os.ReadFile(exe) if err != nil { fmt.Printf("Failed to read current executable: %v\n", err) return } _ = os.Remove(targetExe) err = os.WriteFile(targetExe, input, 0755) if err != nil { fmt.Printf("Failed to write to %s: %v\n", targetExe, err) return } var shellName string if len(args) > 0 { shellName = filepath.Base(args[0]) } else { shellPath := os.Getenv("SHELL") shellName = filepath.Base(shellPath) } var configFile string var evalCmd string switch shellName { case "zsh": configFile = filepath.Join(shell.GetZshConfigDir(), ".zshrc") evalCmd = `eval "$(iris init zsh)"` case "bash": configFile = filepath.Join(home, ".bashrc") evalCmd = `eval "$(iris init bash)"` case "fish": configFile = filepath.Join(shell.GetFishConfigDir(), "config.fish") evalCmd = `iris init fish | source` default: fmt.Printf("Unsupported shell: %s. Please add iris init manually.\n", shellName) return } content, readErr := os.ReadFile(configFile) if strings.Contains(string(content), "iris init") { fmt.Printf("Iris is already configured in %s\n", configFile) } else { var newContent string if readErr == nil && len(content) > 0 { newContent = "# Iris Autocomplete\n" + evalCmd + "\n\n" + string(content) } else { newContent = "# Iris Autocomplete\n" + evalCmd + "\n" } if mkdirErr := os.MkdirAll(filepath.Dir(configFile), 0755); mkdirErr != nil { fmt.Printf("Failed to create directory for %s: %v\n", configFile, mkdirErr) return } err = os.WriteFile(configFile, []byte(newContent), 0644) if err != nil { fmt.Printf("Failed to update %s: %v\n", configFile, err) return } fmt.Printf("✓ Added iris integration to top of %s\n", configFile) } if path, err := config.ConfigPath(); err == nil { if _, statErr := os.Stat(path); os.IsNotExist(statErr) { _ = os.MkdirAll(filepath.Dir(path), 0755) defaultContent := `# ~/.config/iris/config.toml # iris configuration file [core] version = 1 shell = "" shell-login = false mode = "last" debug = false expand-alias = true auto-execute = false navigate-closed = "history" [ui] style = "modern" nerd-fonts = true hidden-files = false ghost-text = 1 max-suggestions = 100 max-height = 6 max-width = 0 [git] filter-active-branch = true deduplicate-branches = true [updater] check-on-startup = true channel = "stable" check-interval = "24h" auto-update = 0 [zoxide] extend-cd = false [keybindings] toggle-mode = "ctrl+r" toggle-menu = "shift+tab" select = "tab" navigate-up = "up" navigate-down = "down" navigate-right = "right" ` if errWrite := os.WriteFile(path, []byte(defaultContent), 0644); errWrite == nil { fmt.Printf("✓ Initialized default config file at %s\n", path) } } } fmt.Println("\nSetup complete! Please restart your terminal or run:") fmt.Printf(" \033[32msource %s\033[0m\n", configFile) }, }
View Source
var SpecCmd = &cobra.Command{
Use: "spec",
Short: "manage custom completion specs",
}
View Source
var SpecInitCmd = &cobra.Command{ Use: "init [name]", Short: "initialize a new completion spec file", Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { specsDir := spec.GetUserSpecsDir() if specsDir == "" { fmt.Fprintln(cmd.ErrOrStderr(), "failed to resolve specs directory") return } name := "example" if len(args) > 0 { trimmed := strings.TrimSpace(args[0]) if trimmed != "" { name = trimmed } } baseName := filepath.Base(name) stem := strings.TrimSuffix(strings.TrimSuffix(baseName, ".jsonc"), ".json") if stem == "" || stem == "." { stem = "example" } filePath := filepath.Join(specsDir, stem+".jsonc") if _, err := os.Stat(filePath); err == nil { fmt.Fprintf(cmd.OutOrStdout(), "spec file already exists at %s\n", filePath) return } if err := os.MkdirAll(specsDir, 0755); err != nil { fmt.Fprintf(cmd.ErrOrStderr(), "failed to create specs directory: %v\n", err) return } const schemaURL = "https://raw.githubusercontent.com/versenilvis/iris/main/spec/schema.json" content := fmt.Sprintf(`{ // $schema enables autocomplete and validation in VS Code / Zed "$schema": "%s", "name": "%s", "description": "%s command completion", "options": [ { "name": "--verbose", "aliases": ["-v"], "description": "Enable verbose output" } ], "subcommands": [ { "name": "run", "description": "Run a file", // suggest files by extension "generator": [".go", ".js", ".ts", ".py", ".sh"] }, { "name": "help", "description": "Show help for a command" } ] } `, schemaURL, stem, stem) if err := os.WriteFile(filePath, []byte(content), 0644); err != nil { fmt.Fprintf(cmd.ErrOrStderr(), "failed to write spec file: %v\n", err) return } fmt.Fprintf(cmd.OutOrStdout(), "initialized spec file at %s\n", filePath) }, }
View Source
var SpecPathCmd = &cobra.Command{ Use: "path", Short: "show the specs directory path", Run: func(cmd *cobra.Command, args []string) { dir := spec.GetUserSpecsDir() if dir == "" { fmt.Fprintln(cmd.ErrOrStderr(), "failed to resolve specs directory") return } _ = os.MkdirAll(dir, 0755) fmt.Fprintln(cmd.OutOrStdout(), dir) }, }
View Source
var ThemeCmd = &cobra.Command{
Use: "theme",
Short: "manage iris theme",
}
View Source
var ThemeInitCmd = &cobra.Command{ Use: "init", Short: "initialize default theme file with comments", Run: func(cmd *cobra.Command, args []string) { path, err := config.ThemePath() if err != nil { fmt.Printf("failed to get theme path: %v\n", err) return } if _, statErr := os.Stat(path); statErr == nil { fmt.Printf("theme file already exists at %s\n", path) return } _ = os.MkdirAll(filepath.Dir(path), 0755) defaultContent := `# ~/.config/iris/theme.toml # iris theme file # overlay border color border = "#a277ff" # accent color for selected marker and icon accent = "#61ffca" # muted color for unselected icons muted = "#6d6a7f" # default text color text = "#edecee" # text color when selected text_sel = "#ffffff" # footer shortcut key color key = "#a277ff" # matched text color match = "#61ffca" # description text color desc = "#9692a8" # description text color when selected desc_sel = "#edecee" # selected row background sel_bg = "#3d375e" # selected tag pill text color sel_text = "#110f18" # scroll counter color scroll_info = "#a277ff" # inline ghost text color ghost_text = "#4B4A4C" # "system" tag background color sys = "#1e1d28" # "system" tag selected background color sys_sel = "#a277ff" # "history" tag background color hist = "#1a2d36" # "history" tag selected background color hist_sel = "#61ffca" # "alias" tag background color alias = "#2a2342" # "alias" tag selected background color (and alias word color) alias_sel = "#a277ff" ` err = os.WriteFile(path, []byte(defaultContent), 0644) if err != nil { fmt.Printf("failed to write theme file: %v\n", err) return } fmt.Printf("initialized theme file at %s\n", path) }, }
View Source
var UninstallCmd = &cobra.Command{ Use: "uninstall", Short: "Uninstall Iris and remove shell integrations", Run: func(cmd *cobra.Command, args []string) { fmt.Println("Uninstalling Iris...") home, err := os.UserHomeDir() if err != nil { fmt.Printf("! could not determine home directory: %v\n", err) return } zshrcPath := filepath.Join(shell.GetZshConfigDir(), ".zshrc") configFiles := []string{ zshrcPath, filepath.Join(home, ".bashrc"), filepath.Join(shell.GetFishConfigDir(), "config.fish"), } for _, file := range configFiles { if cleanShellConfig(file) { fmt.Printf("✓ Removed integration from %s\n", file) } } if cfgPath, err := config.ConfigPath(); err == nil { if cfgDir := filepath.Dir(cfgPath); os.RemoveAll(cfgDir) == nil { fmt.Printf("✓ Removed config directory: %s\n", cfgDir) } } if statePath, err := config.StatePath(); err == nil { if stateDir := filepath.Dir(statePath); os.RemoveAll(stateDir) == nil { fmt.Printf("✓ Removed state directory: %s\n", stateDir) } } if cachePath, err := config.CachePath(); err == nil { if os.RemoveAll(cachePath) == nil { fmt.Printf("✓ Removed cache directory: %s\n", cachePath) } } binLocations := []string{ filepath.Join(home, ".local", "bin", "iris"), "/usr/local/bin/iris", } if exe, err := os.Executable(); err == nil && exe != "" { binLocations = append(binLocations, exe) } anyFound := false for _, loc := range binLocations { if _, err := os.Stat(loc); err == nil { anyFound = true if errRemove := os.Remove(loc); errRemove == nil { fmt.Printf("✓ Removed binary: %s\n", loc) } else { fmt.Printf("! Could not remove binary at %s (try with sudo): %v\n", loc, errRemove) } } } if !anyFound { fmt.Println("✓ No leftover binary files found") } _ = os.Remove("iris.log") fmt.Println("\n✓ Iris has been successfully uninstalled") if os.Getenv("IRIS_PID") != "" { fmt.Println("\n⚠️ You are currently inside an active Iris session.") fmt.Println("Iris runs as the parent process of this terminal - do NOT run 'pkill iris'") fmt.Println("as it will immediately close this terminal window.") fmt.Println("\nTo fully exit, simply close this terminal window and open a new one.") fmt.Println("Iris will not start again since the shell config has been cleaned up.") } else { fmt.Println("Please close and reopen your terminal to complete the uninstall.") } }, }
View Source
var UpdateCmd = &cobra.Command{ Use: "update", Short: "Update Iris to the latest release", Run: func(cmd *cobra.Command, args []string) { fmt.Printf("checking for updates (current: %s)...\n", Version) latest, err := updater.FetchLatestVersion() if err != nil { fmt.Printf("\033[31m[IRIS] could not reach update server: %v\033[0m\n", err) return } if Version != "dev" && Version != "" && !updater.IsNewer(Version, latest) { fmt.Printf("\033[32m[IRIS] already up to date (%s)\033[0m\n", Version) state := config.LoadState() state.Updater.SeenVersion = "" _ = config.SaveState(state) return } fmt.Printf("\033[36m[IRIS] updating %s → %s\033[0m\n", Version, latest) runningPrefix := "" if config.Get().Updater.Channel == "nightly" { runningPrefix = fmt.Sprintf("IRIS_RELEASE_TAG=%s ", latest) } fmt.Printf("running: %scurl -sSL %s | sh\n\n", runningPrefix, updater.ResolveInstallScriptURL()) if _, err := updater.PerformUpdate(latest, true); err != nil { fmt.Printf("\n\033[31m[IRIS] update failed: %v\033[0m\n", err) return } state := config.LoadState() state.Updater.SeenVersion = "" _ = config.SaveState(state) fmt.Printf("\n\033[32m[IRIS] restart your terminal to use the new version\033[0m\n") }, }
View Source
var Version = "dev"
Functions ¶
func FetchReleasesCached ¶
Types ¶
This section is empty.
Click to show internal directories.
Click to hide internal directories.