cli

package
v0.7.3 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: 0BSD Imports: 19 Imported by: 0

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"
View Source
var VersionCmd = &cobra.Command{
	Use:   "version",
	Short: "Print the current Iris version",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Printf("iris %s\n", Version)
	},
}

Functions

func FetchReleases

func FetchReleases(limit int) ([]updater.Release, error)

func FetchReleasesCached

func FetchReleasesCached(limit int, refresh bool) (releases []updater.Release, rateLimited bool, err error)

func Register

func Register(rootCmd *cobra.Command, version string)

Types

This section is empty.

Jump to

Keyboard shortcuts

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