chore(commands): more commands for suggestions (#10)

## What's new
- color completion message command green
https://github.com/versenilvis/IRIS/pull/10/commits/6e141f5e6909fad1ec42725cce38d51729acf399
- new flat command folder structure
https://github.com/versenilvis/IRIS/pull/10/commits/87026a854a529ec5d5baed967d5f7a7f0085a24b
- fix some linter problems
https://github.com/versenilvis/IRIS/pull/10/commits/536de790cefda912f50bf4022e40ce2b965db2e2
- fix some gemini fix suggestions
This commit is contained in:
VERSE
2026-06-23 10:27:26 +07:00
committed by GitHub
parent 3d76a3ee88
commit a2972f61f1
42 changed files with 2406 additions and 245 deletions
+18
View File
@@ -0,0 +1,18 @@
package commands
import (
_ "github.com/versenilvis/iris/commands/cc"
_ "github.com/versenilvis/iris/commands/fs"
_ "github.com/versenilvis/iris/commands/git"
_ "github.com/versenilvis/iris/commands/golang"
_ "github.com/versenilvis/iris/commands/js"
_ "github.com/versenilvis/iris/commands/jvm"
_ "github.com/versenilvis/iris/commands/ops"
_ "github.com/versenilvis/iris/commands/pkginstaller"
_ "github.com/versenilvis/iris/commands/python"
_ "github.com/versenilvis/iris/commands/runner"
_ "github.com/versenilvis/iris/commands/rust"
_ "github.com/versenilvis/iris/commands/sys"
_ "github.com/versenilvis/iris/commands/text"
_ "github.com/versenilvis/iris/commands/view"
)
+79
View File
@@ -0,0 +1,79 @@
package cc
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
cOptions := []core.Option{
{Name: "-o", Description: "output file"},
{Name: "-c", Description: "compile only (no link)"},
{Name: "-g", Description: "debug symbols"},
{Name: "-O0", Description: "no optimization"},
{Name: "-O1", Description: "basic optimization"},
{Name: "-O2", Description: "standard optimization"},
{Name: "-O3", Description: "aggressive optimization"},
{Name: "-Os", Description: "optimize for size"},
{Name: "-Wall", Description: "enable common warnings"},
{Name: "-Wextra", Description: "extra warnings"},
{Name: "-Werror", Description: "treat warnings as errors"},
{Name: "-std", Description: "language standard (e.g. c11, c++17)"},
{Name: "-I", Description: "include directory"},
{Name: "-L", Description: "library directory"},
{Name: "-l", Description: "link library"},
{Name: "-D", Description: "define macro"},
{Name: "-shared", Description: "build shared library"},
{Name: "-static", Description: "static linking"},
{Name: "-fPIC", Description: "position independent code"},
{Name: "-march", Description: "target architecture"},
{Name: "-pthread", Description: "POSIX threads"},
{Name: "-v", Description: "verbose"},
{Name: "-E", Description: "preprocess only"},
{Name: "-S", Description: "compile to assembly"},
{Name: "-M", Description: "output dependencies"},
{Name: "-MMD", Description: "write dep file"},
{Name: "--version", Description: "print version"},
}
core.Register(&core.Spec{
Name: "gcc",
Description: "GNU C compiler",
Generator: core.FileGenerator(".c", ".h"),
Options: cOptions,
})
core.Register(&core.Spec{
Name: "g++",
Description: "GNU C++ compiler",
Generator: core.FileGenerator(".cpp", ".cc", ".cxx", ".h", ".hpp"),
Options: cOptions,
})
core.Register(&core.Spec{
Name: "cc",
Description: "C compiler (alias)",
Generator: core.FileGenerator(".c", ".h"),
Options: cOptions,
})
core.Register(&core.Spec{
Name: "c++",
Description: "C++ compiler (alias)",
Generator: core.FileGenerator(".cpp", ".cc", ".cxx", ".h", ".hpp"),
Options: cOptions,
})
core.Register(&core.Spec{
Name: "clang",
Description: "LLVM C compiler",
Generator: core.FileGenerator(".c", ".h"),
Options: cOptions,
})
core.Register(&core.Spec{
Name: "clang++",
Description: "LLVM C++ compiler",
Generator: core.FileGenerator(".cpp", ".cc", ".cxx", ".h", ".hpp"),
Options: cOptions,
})
}
-119
View File
@@ -1,119 +0,0 @@
package dev
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "docker",
Description: "container engine",
Subcommands: []core.Subcommand{
{
Name: "ps",
Description: "list containers",
Options: []core.Option{
{Name: "-a", Description: "show all"},
{Name: "-q", Description: "only show IDs"},
},
},
{
Name: "build",
Description: "build image",
Options: []core.Option{
{Name: "-t", Description: "tag name"},
{Name: "-f", Description: "dockerfile path"},
{Name: "--no-cache", Description: "no build cache"},
},
},
{
Name: "run",
Description: "run container",
Options: []core.Option{
{Name: "-d", Description: "detached mode"},
{Name: "-p", Description: "port mapping"},
{Name: "-v", Description: "volume mount"},
{Name: "--rm", Description: "auto remove"},
{Name: "--name", Description: "container name"},
{Name: "-it", Description: "interactive tty"},
{Name: "-e", Description: "set env variable"},
},
},
{
Name: "pull",
Description: "pull image",
},
{
Name: "push",
Description: "push image",
},
{
Name: "exec",
Description: "exec in container",
Options: []core.Option{
{Name: "-it", Description: "interactive tty"},
},
},
{
Name: "stop",
Description: "stop container",
},
{
Name: "rm",
Description: "remove container",
Options: []core.Option{
{Name: "-f", Description: "force remove"},
},
},
{
Name: "rmi",
Description: "remove image",
},
{
Name: "images",
Description: "list images",
},
{
Name: "logs",
Description: "view logs",
Options: []core.Option{
{Name: "-f", Description: "follow output"},
{Name: "--tail", Description: "last n lines"},
},
},
{
Name: "compose",
Description: "multi-container",
Subcommands: []core.Subcommand{
{Name: "up", Description: "start services"},
{Name: "down", Description: "stop services"},
{Name: "build", Description: "build services"},
{Name: "logs", Description: "view logs"},
{Name: "ps", Description: "list services"},
{Name: "exec", Description: "execute command"},
{Name: "restart", Description: "restart services"},
},
},
{
Name: "network",
Description: "manage networks",
Subcommands: []core.Subcommand{
{Name: "ls", Description: "list networks"},
{Name: "create", Description: "create network"},
{Name: "rm", Description: "remove network"},
{Name: "inspect", Description: "show details"},
},
},
{
Name: "volume",
Description: "manage volumes",
Subcommands: []core.Subcommand{
{Name: "ls", Description: "list volumes"},
{Name: "create", Description: "create volume"},
{Name: "rm", Description: "remove volume"},
{Name: "prune", Description: "remove unused"},
},
},
},
})
}
-37
View File
@@ -1,37 +0,0 @@
package dev
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "npm",
Description: "node packages",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install packages"},
{Name: "run", Description: "run script",
Subcommands: []core.Subcommand{
{Name: "dev", Description: "development server"},
{Name: "build", Description: "build for production"},
{Name: "start", Description: "start application"},
{Name: "test", Description: "run test suite"},
{Name: "lint", Description: "run linter"},
},
},
{Name: "test", Description: "run tests"},
{Name: "init", Description: "create package.json"},
{Name: "publish", Description: "publish package"},
{Name: "update", Description: "update packages"},
{Name: "uninstall", Description: "remove package"},
{Name: "ls", Description: "list installed"},
{Name: "audit", Description: "security audit"},
{Name: "cache", Description: "manage cache",
Subcommands: []core.Subcommand{
{Name: "clean", Description: "clear cache"},
{Name: "verify", Description: "verify cache"},
},
},
},
})
}
-19
View File
@@ -1,19 +0,0 @@
package dev
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "pip",
Description: "python packages",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install package"},
{Name: "uninstall", Description: "remove package"},
{Name: "freeze", Description: "list packages"},
{Name: "list", Description: "list installed"},
{Name: "show", Description: "show package info"},
},
})
}
+8
View File
@@ -13,8 +13,16 @@ func init() {
{Name: "-a", Description: "all files"},
{Name: "-l", Description: "long format"},
{Name: "-h", Description: "human readable"},
{Name: "-la", Description: "long + all"},
{Name: "-R", Description: "recursive"},
{Name: "-t", Description: "sort by time"},
{Name: "-S", Description: "sort by size"},
{Name: "-1", Description: "one per line"},
{Name: "--color", Description: "colorize output"},
{Name: "-d", Description: "list directories only"},
{Name: "-i", Description: "show inode numbers"},
{Name: "-s", Description: "show block size"},
{Name: "-r", Description: "reverse order"},
},
})
}
+92 -14
View File
@@ -1,4 +1,4 @@
package dev
package git
import (
"context"
@@ -17,6 +17,39 @@ func GitStashGenerator(tokens []string, _ string, _ string) []core.Suggestion {
return getGitResults(tokens, "stash", "list", "--format=%gd: %gs")
}
func GitTagGenerator(tokens []string, _ string, _ string) []core.Suggestion {
return getGitResults(tokens, "tag", "-l")
}
func GitCommitGenerator(tokens []string, _ string, _ string) []core.Suggestion {
cwd := core.GetCWD()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "git", "log", "--format=%h [%cr] %s", "-30")
cmd.Dir = cwd
out, err := cmd.Output()
if err != nil {
return nil
}
var results []core.Suggestion
for _, line := range strings.Split(string(out), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parts := strings.SplitN(line, " ", 2)
hash := parts[0]
desc := ""
if len(parts) == 2 {
desc = parts[1]
}
results = append(results, core.Suggestion{Cmd: hash, Desc: desc})
}
return results
}
func getGitResults(tokens []string, args ...string) []core.Suggestion {
return getGitResultsFiltered(tokens, false, args...)
}
@@ -47,13 +80,18 @@ func getGitResultsFiltered(tokens []string, localOnly bool, args ...string) []co
lines := strings.Split(string(out), "\n")
var results []core.Suggestion
// more robust subcommand detection: find the first non-flag after "git"
// find the subcommand by skipping global flags that take arguments
subcommand := ""
for i := 1; i < len(tokens); i++ {
if !strings.HasPrefix(tokens[i], "-") {
subcommand = tokens[i]
break
t := tokens[i]
if strings.HasPrefix(t, "-") {
if t == "-c" || t == "-C" || t == "--git-dir" || t == "--work-tree" || t == "--namespace" || t == "--super-prefix" || t == "--config-env" || t == "--exec-path" {
i++
}
continue
}
subcommand = t
break
}
for _, line := range lines {
@@ -117,12 +155,8 @@ func getGitResultsFiltered(tokens []string, localOnly bool, args ...string) []co
if activeBranch != "" {
for i, r := range results {
if r.Cmd == activeBranch {
// move active branch to front if found
newResults := make([]core.Suggestion, 0, len(results))
newResults = append(newResults, r)
newResults = append(newResults, results[:i]...)
newResults = append(newResults, results[i+1:]...)
results = newResults
copy(results[1:i+1], results[0:i])
results[0] = r
break
}
}
@@ -348,9 +382,7 @@ func init() {
{
Name: "tag",
Description: "manage tags",
Generator: func(tokens []string, prefix string, partial string) []core.Suggestion {
return getGitResults(tokens, "tag", "-l")
},
Generator: GitTagGenerator,
Options: []core.Option{
{Name: "-a", Description: "annotated tag"},
{Name: "-d", Description: "delete tag"},
@@ -359,6 +391,29 @@ func init() {
{Name: "-m", Description: "tag message"},
},
},
{
Name: "show",
Description: "show object",
Generator: func(tokens []string, prefix string, partial string) []core.Suggestion {
tags := GitTagGenerator(tokens, prefix, partial)
commits := GitCommitGenerator(tokens, prefix, partial)
return append(tags, commits...)
},
Options: []core.Option{
{Name: "--stat", Description: "diffstat only"},
{Name: "--name-only", Description: "filenames only"},
},
},
{
Name: "revert",
Description: "revert a commit",
Generator: GitCommitGenerator,
Options: []core.Option{
{Name: "--no-commit", Description: "no auto commit"},
{Name: "--abort", Description: "abort revert"},
{Name: "--continue", Description: "continue revert"},
},
},
{
Name: "reset",
Description: "reset HEAD",
@@ -419,9 +474,32 @@ func init() {
{
Name: "cherry-pick",
Description: "apply commit",
Generator: GitCommitGenerator,
Options: []core.Option{
{Name: "--no-commit", Description: "no auto commit"},
{Name: "--abort", Description: "abort pick"},
{Name: "--continue", Description: "continue pick"},
},
},
{
Name: "worktree",
Description: "manage worktrees",
Subcommands: []core.Subcommand{
{Name: "add", Description: "add new worktree", Generator: core.FileGenerator("/")},
{Name: "list", Description: "list worktrees"},
{Name: "remove", Description: "remove worktree"},
{Name: "prune", Description: "prune stale worktrees"},
},
},
{
Name: "submodule",
Description: "manage submodules",
Subcommands: []core.Subcommand{
{Name: "add", Description: "add submodule"},
{Name: "init", Description: "init submodule config"},
{Name: "update", Description: "update submodules", Options: []core.Option{{Name: "--init", Description: "init if needed"}, {Name: "--recursive", Description: "recursive update"}}},
{Name: "status", Description: "show submodule status"},
{Name: "foreach", Description: "run command in each submodule"},
},
},
{
+1 -1
View File
@@ -1,4 +1,4 @@
package dev
package golang
import (
"github.com/versenilvis/iris/commands/core"
+116
View File
@@ -0,0 +1,116 @@
package js
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "bun",
Description: "bun js runtime",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install packages", Options: []core.Option{
{Name: "--frozen-lockfile", Description: "no lockfile update"},
{Name: "--production", Description: "production only"},
{Name: "--no-save", Description: "don't update package.json"},
{Name: "--dry-run", Description: "print what would be installed"},
}},
{Name: "add", Description: "add package", Options: []core.Option{
{Name: "-d", Description: "dev dependency"},
{Name: "--dev", Description: "dev dependency (long form)"},
{Name: "--optional", Description: "optional dependency"},
{Name: "--peer", Description: "peer dependency"},
{Name: "-g", Description: "global"},
{Name: "--exact", Description: "exact version"},
{Name: "--no-save", Description: "don't update package.json"},
}},
{Name: "remove", Description: "remove package", Options: []core.Option{
{Name: "-g", Description: "remove global package"},
}},
{Name: "run", Description: "run script", Generator: NpmScriptGenerator, Options: []core.Option{
{Name: "--silent", Description: "suppress output"},
{Name: "--bun", Description: "force bun runtime"},
{Name: "--watch", Description: "watch mode"},
{Name: "--hot", Description: "hot reload"},
{Name: "--smol", Description: "reduce memory usage"},
}},
{Name: "build", Description: "bundle files", Generator: core.FileGenerator(".ts", ".tsx", ".js", ".jsx"), Options: []core.Option{
{Name: "--outdir", Description: "output directory"},
{Name: "--outfile", Description: "output file"},
{Name: "--minify", Description: "enable all minification"},
{Name: "--minify-syntax", Description: "minify syntax only"},
{Name: "--minify-whitespace", Description: "minify whitespace only"},
{Name: "--minify-identifiers", Description: "minify identifiers only"},
{Name: "--sourcemap", Description: "generate sourcemap (none/inline/external)"},
{Name: "--target", Description: "target environment (browser/bun/node)"},
{Name: "--format", Description: "output format (esm/cjs/iife)"},
{Name: "--splitting", Description: "enable code splitting"},
{Name: "--entry-naming", Description: "entry file naming pattern"},
{Name: "--chunk-naming", Description: "chunk file naming pattern"},
{Name: "--asset-naming", Description: "asset file naming pattern"},
{Name: "--watch", Description: "watch mode"},
{Name: "--external", Description: "mark package as external"},
{Name: "--define", Description: "replace global identifiers"},
{Name: "--loader", Description: "set file loader"},
{Name: "--public-path", Description: "prefix for public assets"},
}},
{Name: "test", Description: "run tests", Options: []core.Option{
{Name: "--watch", Description: "watch mode"},
{Name: "-t", Description: "filter by test name"},
{Name: "--coverage", Description: "collect coverage"},
{Name: "--bail", Description: "stop after first failure"},
{Name: "--timeout", Description: "test timeout in ms"},
{Name: "--rerun-each", Description: "rerun each test n times"},
{Name: "--only", Description: "run test.only tests"},
{Name: "--todo", Description: "show todo tests"},
{Name: "--reporter", Description: "test reporter (default/junit/tap)"},
}},
{Name: "x", Description: "execute package (bunx)", Options: []core.Option{
{Name: "--bun", Description: "force bun runtime"},
{Name: "--silent", Description: "suppress output"},
}},
{Name: "outdated", Description: "check outdated packages"},
{Name: "patch", Description: "patch a package", Options: []core.Option{
{Name: "--commit", Description: "commit the patch"},
}},
{Name: "publish", Description: "publish package to npm", Options: []core.Option{
{Name: "--dry-run", Description: "simulate publish"},
{Name: "--tag", Description: "dist-tag"},
{Name: "--access", Description: "public or restricted"},
}},
{Name: "init", Description: "create new project", Options: []core.Option{
{Name: "-y", Description: "skip prompts"},
}},
{Name: "create", Description: "create from template"},
{Name: "repl", Description: "launch bun repl"},
{Name: "upgrade", Description: "upgrade bun"},
{Name: "link", Description: "link package"},
{Name: "unlink", Description: "unlink package"},
{Name: "pm", Description: "package manager helpers", Subcommands: []core.Subcommand{
{Name: "cache", Description: "show cache path"},
{Name: "ls", Description: "list packages"},
{Name: "hash", Description: "print lockfile hash"},
{Name: "hash-print", Description: "print resolved lockfile hash"},
{Name: "trust", Description: "trust package scripts"},
{Name: "untrusted", Description: "show untrusted packages"},
}},
},
Options: []core.Option{
{Name: "--watch", Description: "watch for changes"},
{Name: "--hot", Description: "hot module reload"},
{Name: "--smol", Description: "reduce memory usage"},
{Name: "--silent", Description: "suppress output"},
{Name: "--bun", Description: "force bun runtime"},
{Name: "--version", Description: "print version"},
},
})
core.Register(&core.Spec{
Name: "bunx",
Description: "execute package (bun x)",
Options: []core.Option{
{Name: "--bun", Description: "force bun runtime"},
{Name: "--silent", Description: "suppress output"},
},
})
}
+85
View File
@@ -0,0 +1,85 @@
package js
import (
"encoding/json"
"os"
"path/filepath"
"github.com/versenilvis/iris/commands/core"
)
func NpmScriptGenerator(tokens []string, _ string, _ string) []core.Suggestion {
cwd := core.GetCWD()
data, err := os.ReadFile(filepath.Join(cwd, "package.json"))
if err != nil {
return []core.Suggestion{
{Cmd: "dev", Desc: "development server"},
{Cmd: "build", Desc: "build for production"},
{Cmd: "start", Desc: "start application"},
{Cmd: "test", Desc: "run test suite"},
{Cmd: "lint", Desc: "run linter"},
}
}
var pkg struct {
Scripts map[string]string `json:"scripts"`
}
if err := json.Unmarshal(data, &pkg); err != nil || len(pkg.Scripts) == 0 {
return nil
}
priority := []string{"dev", "start", "build", "test", "lint", "preview", "typecheck", "format"}
seen := make(map[string]bool)
var results []core.Suggestion
for _, name := range priority {
if cmd, ok := pkg.Scripts[name]; ok {
results = append(results, core.Suggestion{Cmd: name, Desc: cmd})
seen[name] = true
}
}
for name, cmd := range pkg.Scripts {
if !seen[name] {
results = append(results, core.Suggestion{Cmd: name, Desc: cmd})
}
}
return results
}
func init() {
core.Register(&core.Spec{
Name: "npm",
Description: "node packages",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install packages", Options: []core.Option{
{Name: "--save-dev", Description: "save as devDependency"},
{Name: "--save-exact", Description: "exact version"},
{Name: "--legacy-peer-deps", Description: "skip peer deps"},
}},
{Name: "run", Description: "run script", Generator: NpmScriptGenerator},
{Name: "test", Description: "run tests"},
{Name: "init", Description: "create package.json", Options: []core.Option{
{Name: "-y", Description: "skip prompts"},
}},
{Name: "publish", Description: "publish package"},
{Name: "update", Description: "update packages"},
{Name: "uninstall", Description: "remove package"},
{Name: "ls", Description: "list installed"},
{Name: "audit", Description: "security audit", Options: []core.Option{
{Name: "fix", Description: "auto fix"},
}},
{Name: "ci", Description: "clean install from lockfile"},
{Name: "pack", Description: "create tarball"},
{Name: "link", Description: "symlink package"},
{Name: "cache", Description: "manage cache", Subcommands: []core.Subcommand{
{Name: "clean", Description: "clear cache"},
{Name: "verify", Description: "verify cache"},
}},
},
Options: []core.Option{
{Name: "--prefix", Description: "set working directory"},
},
})
}
+49
View File
@@ -0,0 +1,49 @@
package js
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "pnpm",
Description: "fast node packages",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install packages", Options: []core.Option{
{Name: "--frozen-lockfile", Description: "no lockfile update"},
{Name: "--prod", Description: "production only"},
}},
{Name: "add", Description: "add package", Options: []core.Option{
{Name: "-D", Description: "save as devDependency"},
{Name: "-g", Description: "install globally"},
{Name: "-E", Description: "exact version"},
}},
{Name: "remove", Description: "remove package"},
{Name: "run", Description: "run script", Generator: NpmScriptGenerator},
{Name: "build", Description: "build project"},
{Name: "dev", Description: "start dev server"},
{Name: "test", Description: "run tests"},
{Name: "update", Description: "update packages", Options: []core.Option{
{Name: "--latest", Description: "update to latest"},
{Name: "-i", Description: "interactive"},
}},
{Name: "store", Description: "manage store", Subcommands: []core.Subcommand{
{Name: "prune", Description: "remove unreferenced"},
{Name: "status", Description: "check store"},
{Name: "path", Description: "print store path"},
}},
{Name: "exec", Description: "exec shell command"},
{Name: "dlx", Description: "fetch and run package"},
{Name: "ls", Description: "list packages"},
{Name: "audit", Description: "security audit"},
{Name: "patch", Description: "patch package"},
{Name: "init", Description: "create package.json"},
{Name: "publish", Description: "publish package"},
},
Options: []core.Option{
{Name: "--filter", Description: "filter by package"},
{Name: "-r", Description: "recursive"},
{Name: "--workspace-root", Description: "run in workspace root"},
},
})
}
+55
View File
@@ -0,0 +1,55 @@
package js
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "yarn",
Description: "yarn package manager",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install packages", Options: []core.Option{
{Name: "--frozen-lockfile", Description: "error on lockfile change"},
{Name: "--production", Description: "production only"},
}},
{Name: "add", Description: "add package", Options: []core.Option{
{Name: "-D", Description: "dev dependency"},
{Name: "-P", Description: "peer dependency"},
{Name: "-E", Description: "exact version"},
{Name: "-g", Description: "global"},
}},
{Name: "remove", Description: "remove package"},
{Name: "run", Description: "run script", Generator: NpmScriptGenerator},
{Name: "build", Description: "build project"},
{Name: "dev", Description: "start dev server"},
{Name: "test", Description: "run tests"},
{Name: "upgrade", Description: "upgrade packages", Options: []core.Option{
{Name: "--latest", Description: "upgrade to latest"},
{Name: "-i", Description: "interactive"},
}},
{Name: "workspace", Description: "run command in workspace"},
{Name: "workspaces", Description: "list workspaces", Subcommands: []core.Subcommand{
{Name: "info", Description: "workspace info"},
{Name: "run", Description: "run in all workspaces"},
}},
{Name: "link", Description: "symlink package"},
{Name: "unlink", Description: "unlink package"},
{Name: "audit", Description: "security audit"},
{Name: "cache", Description: "manage cache", Subcommands: []core.Subcommand{
{Name: "clean", Description: "clear cache"},
{Name: "list", Description: "list cache"},
{Name: "dir", Description: "print cache dir"},
}},
{Name: "init", Description: "create package.json", Options: []core.Option{
{Name: "-2", Description: "berry (v2+)"},
}},
{Name: "publish", Description: "publish package"},
{Name: "info", Description: "show package info"},
},
Options: []core.Option{
{Name: "--cwd", Description: "working directory"},
{Name: "--verbose", Description: "verbose output"},
},
})
}
+56
View File
@@ -0,0 +1,56 @@
package jvm
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "javac",
Description: "Java compiler",
Generator: core.FileGenerator(".java"),
Options: []core.Option{
{Name: "-d", Description: "output directory"},
{Name: "-cp", Description: "class path"},
{Name: "-classpath", Description: "class path (long form)"},
{Name: "-sourcepath", Description: "source path"},
{Name: "-g", Description: "debug info"},
{Name: "-verbose", Description: "verbose"},
{Name: "-encoding", Description: "source file encoding"},
{Name: "--release", Description: "target release version"},
{Name: "-source", Description: "source version"},
{Name: "-target", Description: "target version"},
{Name: "-Xlint", Description: "enable warnings"},
},
})
core.Register(&core.Spec{
Name: "java",
Description: "Java runtime",
Generator: core.FileGenerator(".jar", ".class"),
Options: []core.Option{
{Name: "-jar", Description: "run jar file"},
{Name: "-cp", Description: "class path"},
{Name: "-Xmx", Description: "max heap size (e.g. -Xmx512m)"},
{Name: "-Xms", Description: "initial heap size"},
{Name: "-D", Description: "set system property"},
{Name: "-ea", Description: "enable assertions"},
{Name: "--version", Description: "print version"},
{Name: "-server", Description: "server VM"},
{Name: "-verbose", Description: "verbose class loading"},
},
})
core.Register(&core.Spec{
Name: "kotlinc",
Description: "Kotlin compiler",
Generator: core.FileGenerator(".kt", ".kts"),
Options: []core.Option{
{Name: "-d", Description: "output dir or jar"},
{Name: "-cp", Description: "class path"},
{Name: "-include-runtime", Description: "include runtime in jar"},
{Name: "-jvm-target", Description: "JVM target version"},
{Name: "-script", Description: "run as script"},
},
})
}
+278
View File
@@ -0,0 +1,278 @@
package ops
import (
"context"
"os/exec"
"strings"
"time"
"github.com/versenilvis/iris/commands/core"
)
func dockerContainerGenerator(tokens []string, _ string, _ string) []core.Suggestion {
cwd := core.GetCWD()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "docker", "ps", "-a", "--format={{.Names}}")
cmd.Dir = cwd
out, err := cmd.Output()
if err != nil {
return nil
}
var results []core.Suggestion
for _, line := range strings.Split(string(out), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
results = append(results, core.Suggestion{Cmd: line, Desc: "container"})
}
return results
}
func dockerRunningContainerGenerator(tokens []string, _ string, _ string) []core.Suggestion {
cwd := core.GetCWD()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "docker", "ps", "--format={{.Names}}")
cmd.Dir = cwd
out, err := cmd.Output()
if err != nil {
return nil
}
var results []core.Suggestion
for _, line := range strings.Split(string(out), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
results = append(results, core.Suggestion{Cmd: line, Desc: "running"})
}
return results
}
func dockerImageGenerator(tokens []string, _ string, _ string) []core.Suggestion {
cwd := core.GetCWD()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "docker", "images", "--format={{.Repository}}:{{.Tag}}")
cmd.Dir = cwd
out, err := cmd.Output()
if err != nil {
return nil
}
seen := make(map[string]bool)
var results []core.Suggestion
for _, line := range strings.Split(string(out), "\n") {
line = strings.TrimSpace(line)
if line == "" || line == "<none>:<none>" || seen[line] {
continue
}
seen[line] = true
results = append(results, core.Suggestion{Cmd: line, Desc: "image"})
}
return results
}
func init() {
core.Register(&core.Spec{
Name: "docker",
Description: "container engine",
Subcommands: []core.Subcommand{
{
Name: "ps",
Description: "list containers",
Options: []core.Option{
{Name: "-a", Description: "show all"},
{Name: "-q", Description: "only show IDs"},
{Name: "--format", Description: "format output"},
{Name: "--filter", Description: "filter by condition"},
},
},
{
Name: "build",
Description: "build image",
Generator: core.FileGenerator(".dockerfile", ".Dockerfile"),
Options: []core.Option{
{Name: "-t", Description: "tag name"},
{Name: "-f", Description: "dockerfile path"},
{Name: "--no-cache", Description: "no build cache"},
{Name: "--platform", Description: "target platform"},
{Name: "--build-arg", Description: "build argument"},
},
},
{
Name: "run",
Description: "run container",
Generator: dockerImageGenerator,
Options: []core.Option{
{Name: "-d", Description: "detached mode"},
{Name: "-p", Description: "port mapping"},
{Name: "-v", Description: "volume mount"},
{Name: "--rm", Description: "auto remove"},
{Name: "--name", Description: "container name"},
{Name: "-it", Description: "interactive tty"},
{Name: "-e", Description: "set env variable"},
{Name: "--network", Description: "network mode"},
{Name: "--restart", Description: "restart policy"},
},
},
{
Name: "pull",
Description: "pull image",
},
{
Name: "push",
Description: "push image",
Generator: dockerImageGenerator,
},
{
Name: "exec",
Description: "exec in container",
Generator: dockerRunningContainerGenerator,
Options: []core.Option{
{Name: "-it", Description: "interactive tty"},
{Name: "-e", Description: "set env variable"},
{Name: "-u", Description: "run as user"},
},
},
{
Name: "stop",
Description: "stop container",
Generator: dockerRunningContainerGenerator,
},
{
Name: "start",
Description: "start container",
Generator: dockerContainerGenerator,
},
{
Name: "restart",
Description: "restart container",
Generator: dockerContainerGenerator,
},
{
Name: "rm",
Description: "remove container",
Generator: dockerContainerGenerator,
Options: []core.Option{
{Name: "-f", Description: "force remove"},
{Name: "-v", Description: "remove volumes"},
},
},
{
Name: "rmi",
Description: "remove image",
Generator: dockerImageGenerator,
Options: []core.Option{
{Name: "-f", Description: "force remove"},
},
},
{
Name: "images",
Description: "list images",
Options: []core.Option{
{Name: "-a", Description: "show all"},
{Name: "-q", Description: "only IDs"},
{Name: "--format", Description: "format output"},
},
},
{
Name: "logs",
Description: "view logs",
Generator: dockerContainerGenerator,
Options: []core.Option{
{Name: "-f", Description: "follow output"},
{Name: "--tail", Description: "last n lines"},
{Name: "--since", Description: "since timestamp"},
{Name: "-t", Description: "show timestamps"},
},
},
{
Name: "inspect",
Description: "show low-level info",
Generator: func(tokens []string, prefix string, partial string) []core.Suggestion {
containers := dockerContainerGenerator(tokens, prefix, partial)
images := dockerImageGenerator(tokens, prefix, partial)
return append(containers, images...)
},
},
{
Name: "compose",
Description: "multi-container",
Subcommands: []core.Subcommand{
{Name: "up", Description: "start services", Options: []core.Option{{Name: "-d", Description: "detached"}, {Name: "--build", Description: "rebuild images"}}},
{Name: "down", Description: "stop services", Options: []core.Option{{Name: "-v", Description: "remove volumes"}}},
{Name: "build", Description: "build services"},
{Name: "logs", Description: "view logs", Options: []core.Option{{Name: "-f", Description: "follow"}}},
{Name: "ps", Description: "list services"},
{Name: "exec", Description: "execute command"},
{Name: "restart", Description: "restart services"},
{Name: "pull", Description: "pull images"},
{Name: "stop", Description: "stop services"},
{Name: "start", Description: "start services"},
{Name: "config", Description: "validate config", Generator: core.FileGenerator(".yml", ".yaml")},
},
Options: []core.Option{
{Name: "-f", Description: "compose file"},
{Name: "--project-name", Description: "project name"},
},
},
{
Name: "network",
Description: "manage networks",
Subcommands: []core.Subcommand{
{Name: "ls", Description: "list networks"},
{Name: "create", Description: "create network"},
{Name: "rm", Description: "remove network"},
{Name: "inspect", Description: "show details"},
{Name: "prune", Description: "remove unused"},
},
},
{
Name: "volume",
Description: "manage volumes",
Subcommands: []core.Subcommand{
{Name: "ls", Description: "list volumes"},
{Name: "create", Description: "create volume"},
{Name: "rm", Description: "remove volume"},
{Name: "prune", Description: "remove unused"},
{Name: "inspect", Description: "show details"},
},
},
{
Name: "system",
Description: "manage docker system",
Subcommands: []core.Subcommand{
{Name: "prune", Description: "remove unused data", Options: []core.Option{{Name: "-a", Description: "remove all unused"}, {Name: "--volumes", Description: "include volumes"}}},
{Name: "df", Description: "show disk usage"},
{Name: "info", Description: "system info"},
},
},
},
})
core.Register(&core.Spec{
Name: "docker-compose",
Description: "multi-container (legacy)",
Subcommands: []core.Subcommand{
{Name: "up", Description: "start services", Options: []core.Option{{Name: "-d", Description: "detached"}, {Name: "--build", Description: "rebuild"}}},
{Name: "down", Description: "stop services", Options: []core.Option{{Name: "-v", Description: "remove volumes"}}},
{Name: "build", Description: "build services"},
{Name: "logs", Description: "view logs", Options: []core.Option{{Name: "-f", Description: "follow"}}},
{Name: "ps", Description: "list services"},
{Name: "exec", Description: "execute command"},
{Name: "restart", Description: "restart services"},
{Name: "pull", Description: "pull images"},
},
Options: []core.Option{
{Name: "-f", Description: "compose file"},
},
})
}
@@ -1,4 +1,4 @@
package dev
package ops
import (
"github.com/versenilvis/iris/commands/core"
+105
View File
@@ -0,0 +1,105 @@
package ops
import (
"bufio"
"os"
"path/filepath"
"strings"
"github.com/versenilvis/iris/commands/core"
)
func sshHostGenerator(tokens []string, _ string, _ string) []core.Suggestion {
home, err := os.UserHomeDir()
if err != nil {
return nil
}
paths := []string{
filepath.Join(home, ".ssh", "config"),
"/etc/ssh/ssh_config",
}
seen := make(map[string]bool)
var results []core.Suggestion
for _, path := range paths {
f, err := os.Open(path)
if err != nil {
continue
}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
parts := strings.Fields(scanner.Text())
if len(parts) < 2 || !strings.EqualFold(parts[0], "host") {
continue
}
for _, host := range parts[1:] {
// skip wildcards
if strings.ContainsAny(host, "*?!") {
continue
}
if seen[host] {
continue
}
seen[host] = true
results = append(results, core.Suggestion{Cmd: host, Desc: "ssh host"})
}
}
_ = scanner.Err()
_ = f.Close()
}
return results
}
func init() {
sshOptions := []core.Option{
{Name: "-p", Description: "port"},
{Name: "-i", Description: "identity file"},
{Name: "-L", Description: "local port forward"},
{Name: "-R", Description: "remote port forward"},
{Name: "-D", Description: "dynamic socks proxy"},
{Name: "-N", Description: "no remote command"},
{Name: "-f", Description: "background"},
{Name: "-v", Description: "verbose"},
{Name: "-A", Description: "agent forwarding"},
{Name: "-X", Description: "x11 forwarding"},
}
core.Register(&core.Spec{
Name: "ssh",
Description: "secure shell",
Generator: sshHostGenerator,
Options: sshOptions,
})
core.Register(&core.Spec{
Name: "scp",
Description: "secure copy",
Generator: sshHostGenerator,
Options: []core.Option{
{Name: "-r", Description: "recursive"},
{Name: "-p", Description: "port"},
{Name: "-i", Description: "identity file"},
{Name: "-P", Description: "remote port"},
},
})
core.Register(&core.Spec{
Name: "rsync",
Description: "remote sync",
Generator: sshHostGenerator,
Options: []core.Option{
{Name: "-av", Description: "archive + verbose"},
{Name: "-z", Description: "compress"},
{Name: "--delete", Description: "delete extraneous"},
{Name: "--progress", Description: "show progress"},
{Name: "-e", Description: "remote shell command"},
{Name: "--exclude", Description: "exclude pattern"},
{Name: "--dry-run", Description: "simulation only"},
},
})
}
+327
View File
@@ -0,0 +1,327 @@
package pkginstaller
import (
"context"
"os/exec"
"strings"
"time"
"github.com/versenilvis/iris/commands/core"
)
func installedPackageGenerator(pm string) core.GeneratorFunc {
return func(tokens []string, _ string, _ string) []core.Suggestion {
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
defer cancel()
var cmd *exec.Cmd
switch pm {
case "pacman", "yay", "paru":
cmd = exec.CommandContext(ctx, pm, "-Qq")
case "apt", "apt-get":
cmd = exec.CommandContext(ctx, "dpkg-query", "-W", "-f=${Package}\n")
case "dnf", "yum":
cmd = exec.CommandContext(ctx, "rpm", "-qa", "--qf", "%{NAME}\n")
case "brew":
cmd = exec.CommandContext(ctx, "brew", "list")
default:
return nil
}
out, err := cmd.Output()
if err != nil {
return nil
}
var results []core.Suggestion
for _, line := range strings.Split(string(out), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
results = append(results, core.Suggestion{Cmd: line, Desc: "installed"})
}
return results
}
}
func init() {
// pacman
core.Register(&core.Spec{
Name: "pacman",
Description: "Arch package manager",
Options: []core.Option{
{Name: "-S", Description: "install package"},
{Name: "-Syu", Description: "full system upgrade"},
{Name: "-Sy", Description: "sync database"},
{Name: "-Su", Description: "upgrade installed"},
{Name: "-Ss", Description: "search in repos"},
{Name: "-Si", Description: "show package info"},
{Name: "-R", Description: "remove package"},
{Name: "-Rs", Description: "remove + unused deps"},
{Name: "-Rns", Description: "remove + deps + configs"},
{Name: "-Q", Description: "query installed"},
{Name: "-Qq", Description: "query names only"},
{Name: "-Qs", Description: "search installed"},
{Name: "-Qi", Description: "installed package info"},
{Name: "-Qo", Description: "which package owns file"},
{Name: "-Ql", Description: "list package files"},
{Name: "-U", Description: "install local package"},
{Name: "-D", Description: "set install reason"},
{Name: "--noconfirm", Description: "skip confirmations"},
{Name: "--needed", Description: "skip up-to-date"},
{Name: "--asdeps", Description: "mark as dependency"},
{Name: "--asexplicit", Description: "mark as explicit"},
},
Generator: installedPackageGenerator("pacman"),
})
// yay
core.Register(&core.Spec{
Name: "yay",
Description: "AUR helper (pacman wrapper)",
Options: []core.Option{
{Name: "-S", Description: "install package (AUR + repos)"},
{Name: "-Syu", Description: "full system upgrade + AUR"},
{Name: "-Ss", Description: "search AUR + repos"},
{Name: "-Si", Description: "show package info"},
{Name: "-R", Description: "remove package"},
{Name: "-Rs", Description: "remove + unused deps"},
{Name: "-Rns", Description: "remove + deps + configs"},
{Name: "-Q", Description: "query installed"},
{Name: "-Qs", Description: "search installed"},
{Name: "-Qi", Description: "installed package info"},
{Name: "-Yc", Description: "remove unneeded deps"},
{Name: "-Y", Description: "yay-specific options"},
{Name: "--aur", Description: "only AUR results"},
{Name: "--repo", Description: "only repo results"},
{Name: "--devel", Description: "update devel packages"},
{Name: "--noconfirm", Description: "skip confirmations"},
{Name: "--needed", Description: "skip up-to-date"},
},
Generator: installedPackageGenerator("yay"),
})
// paru
core.Register(&core.Spec{
Name: "paru",
Description: "AUR helper (feature-rich)",
Options: []core.Option{
{Name: "-S", Description: "install package"},
{Name: "-Syu", Description: "full upgrade"},
{Name: "-Ss", Description: "search"},
{Name: "-R", Description: "remove"},
{Name: "-Rs", Description: "remove + deps"},
{Name: "-Q", Description: "query installed"},
{Name: "--fm", Description: "file manager for PKGBUILDs"},
{Name: "--noconfirm", Description: "skip confirmations"},
{Name: "--aur", Description: "AUR only"},
{Name: "--repo", Description: "repo only"},
},
Generator: installedPackageGenerator("paru"),
})
// apt
core.Register(&core.Spec{
Name: "apt",
Description: "Debian/Ubuntu package manager",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install packages"},
{Name: "remove", Description: "remove packages", Generator: installedPackageGenerator("apt")},
{Name: "purge", Description: "remove + config files", Generator: installedPackageGenerator("apt")},
{Name: "autoremove", Description: "remove unused deps"},
{Name: "update", Description: "refresh package lists"},
{Name: "upgrade", Description: "upgrade installed"},
{Name: "full-upgrade", Description: "upgrade with auto remove"},
{Name: "dist-upgrade", Description: "smart upgrade"},
{Name: "search", Description: "search packages"},
{Name: "show", Description: "show package info"},
{Name: "list", Description: "list packages", Options: []core.Option{
{Name: "--installed", Description: "installed only"},
{Name: "--upgradable", Description: "upgradable only"},
}},
{Name: "download", Description: "download package (no install)"},
{Name: "clean", Description: "clear local repo"},
{Name: "autoclean", Description: "clear old cached packages"},
{Name: "depends", Description: "show dependencies"},
{Name: "rdepends", Description: "show reverse dependencies"},
},
Options: []core.Option{
{Name: "-y", Description: "yes to all"},
{Name: "--no-install-recommends", Description: "skip recommends"},
{Name: "--dry-run", Description: "simulate"},
{Name: "-q", Description: "quiet"},
{Name: "--fix-broken", Description: "fix broken deps"},
},
})
// apt-get
core.Register(&core.Spec{
Name: "apt-get",
Description: "Debian/Ubuntu package manager (low-level)",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install packages"},
{Name: "remove", Description: "remove packages", Generator: installedPackageGenerator("apt")},
{Name: "purge", Description: "remove + configs", Generator: installedPackageGenerator("apt")},
{Name: "autoremove", Description: "remove unused deps"},
{Name: "update", Description: "refresh package lists"},
{Name: "upgrade", Description: "upgrade installed"},
{Name: "dist-upgrade", Description: "smart upgrade"},
{Name: "clean", Description: "clean cache"},
{Name: "autoclean", Description: "clean old packages"},
{Name: "download", Description: "download only"},
{Name: "source", Description: "get source package"},
},
Options: []core.Option{
{Name: "-y", Description: "yes to all"},
{Name: "--no-install-recommends", Description: "skip recommends"},
{Name: "-q", Description: "quiet"},
{Name: "-s", Description: "simulate"},
{Name: "--fix-missing", Description: "fix missing"},
},
})
// dnf (Fedora/RHEL)
core.Register(&core.Spec{
Name: "dnf",
Description: "Fedora/RHEL package manager",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install packages"},
{Name: "remove", Description: "remove packages", Generator: installedPackageGenerator("dnf")},
{Name: "update", Description: "update packages"},
{Name: "upgrade", Description: "upgrade system"},
{Name: "search", Description: "search packages"},
{Name: "info", Description: "show package info"},
{Name: "list", Description: "list packages"},
{Name: "autoremove", Description: "remove unneeded"},
{Name: "clean", Description: "clean cache", Subcommands: []core.Subcommand{
{Name: "all", Description: "clean all"},
{Name: "packages", Description: "clean packages"},
{Name: "metadata", Description: "clean metadata"},
}},
{Name: "repolist", Description: "list repos"},
{Name: "history", Description: "transaction history"},
{Name: "group", Description: "manage package groups"},
},
Options: []core.Option{
{Name: "-y", Description: "yes to all"},
{Name: "-q", Description: "quiet"},
{Name: "--best", Description: "install best"},
{Name: "--allowerasing", Description: "allow package replacement"},
{Name: "--no-best", Description: "allow older versions"},
},
})
// yum (RHEL/CentOS legacy)
core.Register(&core.Spec{
Name: "yum",
Description: "RHEL/CentOS package manager (legacy)",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install packages"},
{Name: "remove", Description: "remove packages", Generator: installedPackageGenerator("yum")},
{Name: "update", Description: "update packages"},
{Name: "search", Description: "search packages"},
{Name: "info", Description: "show package info"},
{Name: "list", Description: "list packages"},
{Name: "clean", Description: "clean cache"},
{Name: "repolist", Description: "list repos"},
},
Options: []core.Option{
{Name: "-y", Description: "yes to all"},
{Name: "-q", Description: "quiet"},
},
})
// brew (macOS / Linux Homebrew)
core.Register(&core.Spec{
Name: "brew",
Description: "Homebrew package manager",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install formula/cask"},
{Name: "uninstall", Description: "remove formula", Generator: installedPackageGenerator("brew")},
{Name: "reinstall", Description: "reinstall formula", Generator: installedPackageGenerator("brew")},
{Name: "update", Description: "update Homebrew"},
{Name: "upgrade", Description: "upgrade packages"},
{Name: "search", Description: "search formulas"},
{Name: "info", Description: "show package info"},
{Name: "list", Description: "list installed"},
{Name: "outdated", Description: "list outdated"},
{Name: "doctor", Description: "check system"},
{Name: "cleanup", Description: "remove old versions"},
{Name: "tap", Description: "add tap repo"},
{Name: "untap", Description: "remove tap"},
{Name: "services", Description: "manage services", Subcommands: []core.Subcommand{
{Name: "start", Description: "start service"},
{Name: "stop", Description: "stop service"},
{Name: "restart", Description: "restart service"},
{Name: "list", Description: "list services"},
}},
{Name: "cask", Description: "manage GUI apps", Subcommands: []core.Subcommand{
{Name: "install", Description: "install cask"},
{Name: "uninstall", Description: "remove cask"},
{Name: "list", Description: "list casks"},
}},
{Name: "pin", Description: "pin formula version", Generator: installedPackageGenerator("brew")},
{Name: "unpin", Description: "unpin formula", Generator: installedPackageGenerator("brew")},
{Name: "link", Description: "create symlinks", Generator: installedPackageGenerator("brew")},
{Name: "unlink", Description: "remove symlinks", Generator: installedPackageGenerator("brew")},
},
Options: []core.Option{
{Name: "--cask", Description: "target GUI app"},
{Name: "--formula", Description: "target formula"},
{Name: "--force", Description: "force operation"},
{Name: "--verbose", Description: "verbose"},
{Name: "--dry-run", Description: "simulate"},
},
})
// snap
core.Register(&core.Spec{
Name: "snap",
Description: "snap package manager",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install snap"},
{Name: "remove", Description: "remove snap"},
{Name: "refresh", Description: "update snaps"},
{Name: "list", Description: "list installed"},
{Name: "find", Description: "search snaps"},
{Name: "info", Description: "show snap info"},
{Name: "run", Description: "run snap app"},
{Name: "connect", Description: "connect snap plug"},
{Name: "disconnect", Description: "disconnect snap plug"},
{Name: "revert", Description: "revert to previous"},
{Name: "enable", Description: "enable snap"},
{Name: "disable", Description: "disable snap"},
},
Options: []core.Option{
{Name: "--classic", Description: "classic confinement"},
{Name: "--beta", Description: "beta channel"},
{Name: "--edge", Description: "edge channel"},
{Name: "--channel", Description: "specific channel"},
},
})
// flatpak
core.Register(&core.Spec{
Name: "flatpak",
Description: "flatpak package manager",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install application"},
{Name: "uninstall", Description: "remove application"},
{Name: "update", Description: "update applications"},
{Name: "list", Description: "list installed"},
{Name: "search", Description: "search applications"},
{Name: "info", Description: "show app info"},
{Name: "run", Description: "run application"},
{Name: "remote-add", Description: "add remote"},
{Name: "remote-delete", Description: "delete remote"},
{Name: "remote-list", Description: "list remotes"},
{Name: "repair", Description: "repair installation"},
},
Options: []core.Option{
{Name: "--user", Description: "user installation"},
{Name: "--system", Description: "system installation"},
{Name: "-y", Description: "non-interactive"},
},
})
}
+81
View File
@@ -0,0 +1,81 @@
package python
import (
"context"
"os/exec"
"strings"
"time"
"github.com/versenilvis/iris/commands/core"
)
func pipPackageGenerator(tokens []string, _ string, _ string) []core.Suggestion {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "pip", "list", "--format=freeze")
out, err := cmd.Output()
if err != nil {
// try pip3
cmd = exec.CommandContext(ctx, "pip3", "list", "--format=freeze")
out, err = cmd.Output()
if err != nil {
return nil
}
}
var results []core.Suggestion
for _, line := range strings.Split(string(out), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
// format is "package==version"
parts := strings.SplitN(line, "==", 2)
name := parts[0]
desc := ""
if len(parts) == 2 {
desc = "v" + parts[1]
}
results = append(results, core.Suggestion{Cmd: name, Desc: desc})
}
return results
}
func makePipSpec(name string) *core.Spec {
return &core.Spec{
Name: name,
Description: "python packages",
Subcommands: []core.Subcommand{
{Name: "install", Description: "install package", Options: []core.Option{
{Name: "-r", Description: "from requirements file"},
{Name: "-U", Description: "upgrade"},
{Name: "--user", Description: "install for user only"},
{Name: "--editable", Description: "editable install"},
{Name: "--index-url", Description: "custom package index"},
{Name: "--no-deps", Description: "skip dependencies"},
}},
{Name: "uninstall", Description: "remove package", Generator: pipPackageGenerator, Options: []core.Option{
{Name: "-y", Description: "yes to all prompts"},
}},
{Name: "freeze", Description: "list installed packages"},
{Name: "list", Description: "list installed", Options: []core.Option{
{Name: "--outdated", Description: "show outdated"},
{Name: "--format", Description: "output format"},
}},
{Name: "show", Description: "show package info", Generator: pipPackageGenerator},
{Name: "download", Description: "download packages"},
{Name: "check", Description: "verify compatibility"},
{Name: "config", Description: "manage config"},
{Name: "search", Description: "search pypi"},
{Name: "wheel", Description: "build wheel archive"},
{Name: "hash", Description: "compute hashes"},
{Name: "completion", Description: "generate completion"},
},
}
}
func init() {
core.Register(makePipSpec("pip"))
core.Register(makePipSpec("pip3"))
}
+105
View File
@@ -0,0 +1,105 @@
package python
import (
"github.com/versenilvis/iris/commands/core"
)
func makePythonSpec(name string) *core.Spec {
return &core.Spec{
Name: name,
Description: "python interpreter",
Generator: core.FileGenerator(".py"),
Options: []core.Option{
{Name: "-m", Description: "run module as script"},
{Name: "-c", Description: "run inline command"},
{Name: "-i", Description: "interactive after script"},
{Name: "-u", Description: "unbuffered stdio"},
{Name: "-O", Description: "optimize bytecode"},
{Name: "-v", Description: "verbose"},
{Name: "-W", Description: "warning control"},
{Name: "-B", Description: "no .pyc files"},
},
}
}
func init() {
core.Register(makePythonSpec("python"))
core.Register(makePythonSpec("python3"))
core.Register(makePythonSpec("py"))
core.Register(&core.Spec{
Name: "uv",
Description: "fast python package manager",
Subcommands: []core.Subcommand{
{Name: "add", Description: "add dependency", Options: []core.Option{
{Name: "--dev", Description: "dev dependency"},
{Name: "--optional", Description: "optional dependency"},
{Name: "-e", Description: "editable install"},
{Name: "--index-url", Description: "custom index"},
}},
{Name: "remove", Description: "remove dependency", Generator: pipPackageGenerator},
{Name: "sync", Description: "sync environment", Options: []core.Option{
{Name: "--frozen", Description: "no lockfile update"},
{Name: "--dev", Description: "include dev deps"},
}},
{Name: "run", Description: "run command in env", Generator: core.FileGenerator(".py")},
{Name: "pip", Description: "pip interface", Subcommands: []core.Subcommand{
{Name: "install", Description: "install packages"},
{Name: "uninstall", Description: "uninstall packages", Generator: pipPackageGenerator},
{Name: "list", Description: "list packages"},
{Name: "freeze", Description: "freeze packages"},
{Name: "show", Description: "show package", Generator: pipPackageGenerator},
{Name: "compile", Description: "compile requirements"},
}},
{Name: "venv", Description: "create virtual env", Generator: core.FileGenerator("/"), Options: []core.Option{
{Name: "--python", Description: "python version"},
{Name: "--seed", Description: "install seed packages"},
}},
{Name: "init", Description: "init project"},
{Name: "build", Description: "build package"},
{Name: "publish", Description: "publish to pypi"},
{Name: "lock", Description: "update lockfile"},
{Name: "tree", Description: "show dependency tree"},
{Name: "tool", Description: "manage tools", Subcommands: []core.Subcommand{
{Name: "install", Description: "install tool"},
{Name: "run", Description: "run tool"},
{Name: "list", Description: "list tools"},
{Name: "uninstall", Description: "uninstall tool"},
}},
{Name: "python", Description: "manage python versions", Subcommands: []core.Subcommand{
{Name: "install", Description: "install python"},
{Name: "list", Description: "list versions"},
{Name: "find", Description: "find python"},
{Name: "pin", Description: "pin python version"},
}},
},
})
core.Register(&core.Spec{
Name: "poetry",
Description: "python dependency manager",
Subcommands: []core.Subcommand{
{Name: "add", Description: "add dependency", Options: []core.Option{
{Name: "--dev", Description: "dev dependency"},
{Name: "--group", Description: "dependency group"},
}},
{Name: "remove", Description: "remove dependency", Generator: pipPackageGenerator},
{Name: "install", Description: "install dependencies"},
{Name: "update", Description: "update dependencies"},
{Name: "run", Description: "run in env", Generator: core.FileGenerator(".py")},
{Name: "shell", Description: "activate virtual env"},
{Name: "build", Description: "build package"},
{Name: "publish", Description: "publish to pypi"},
{Name: "show", Description: "show packages"},
{Name: "check", Description: "check pyproject.toml"},
{Name: "init", Description: "init new project"},
{Name: "new", Description: "create new project"},
{Name: "env", Description: "manage envs", Subcommands: []core.Subcommand{
{Name: "info", Description: "show env info"},
{Name: "list", Description: "list envs"},
{Name: "use", Description: "switch python"},
{Name: "remove", Description: "remove env"},
}},
},
})
}
@@ -1,4 +1,4 @@
package dev
package rust
import (
"github.com/versenilvis/iris/commands/core"
+32
View File
@@ -0,0 +1,32 @@
package rust
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "rustc",
Description: "Rust compiler",
Generator: core.FileGenerator(".rs"),
Options: []core.Option{
{Name: "-o", Description: "output file"},
{Name: "--edition", Description: "Rust edition (2015/2018/2021/2024)"},
{Name: "--crate-type", Description: "crate type (bin/lib/dylib/cdylib/rlib)"},
{Name: "--crate-name", Description: "crate name"},
{Name: "-C", Description: "codegen option"},
{Name: "-L", Description: "add to library search path"},
{Name: "--extern", Description: "extern crate"},
{Name: "-g", Description: "debug symbols"},
{Name: "-O", Description: "optimize"},
{Name: "--emit", Description: "output type (asm/llvm-ir/obj/mir)"},
{Name: "--explain", Description: "explain error code"},
{Name: "--test", Description: "build test harness"},
{Name: "-W", Description: "set lint warning"},
{Name: "-A", Description: "set lint allow"},
{Name: "-D", Description: "set lint deny"},
{Name: "--verbose", Description: "verbose"},
{Name: "--version", Description: "print version"},
},
})
}
-19
View File
@@ -1,19 +0,0 @@
package search
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "find",
Description: "search for files",
Generator: core.FileGenerator("/"),
Options: []core.Option{
{Name: "-name", Description: "match by name"},
{Name: "-type", Description: "match by type (f/d)"},
{Name: "-size", Description: "match by size"},
{Name: "-mtime", Description: "match by modified time"},
},
})
}
-20
View File
@@ -1,20 +0,0 @@
package search
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "grep",
Description: "search text in files",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-r", Description: "recursive"},
{Name: "-i", Description: "ignore case"},
{Name: "-n", Description: "show line numbers"},
{Name: "-v", Description: "invert match"},
{Name: "-l", Description: "show filenames only"},
},
})
}
+57
View File
@@ -0,0 +1,57 @@
package sys
import (
"os"
"strings"
"github.com/versenilvis/iris/commands/core"
)
func envVarGenerator(tokens []string, _ string, _ string) []core.Suggestion {
var results []core.Suggestion
for _, env := range os.Environ() {
parts := strings.SplitN(env, "=", 2)
name := parts[0]
val := ""
if len(parts) == 2 {
val = parts[1]
// truncate long values
if len(val) > 60 {
val = val[:57] + "..."
}
}
results = append(results, core.Suggestion{Cmd: name, Desc: val})
}
return results
}
func init() {
core.Register(&core.Spec{
Name: "export",
Description: "set environment variable",
Generator: envVarGenerator,
})
core.Register(&core.Spec{
Name: "env",
Description: "print environment",
Generator: envVarGenerator,
Options: []core.Option{
{Name: "-i", Description: "start with empty env"},
{Name: "-u", Description: "unset variable"},
{Name: "-0", Description: "null delimited"},
},
})
core.Register(&core.Spec{
Name: "printenv",
Description: "print environment variables",
Generator: envVarGenerator,
})
core.Register(&core.Spec{
Name: "unset",
Description: "unset variable",
Generator: envVarGenerator,
})
}
+156
View File
@@ -0,0 +1,156 @@
package sys
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "curl",
Description: "transfer data via URL",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-X", Description: "request method (GET/POST/PUT/DELETE)"},
{Name: "-H", Description: "request header"},
{Name: "-d", Description: "request body data"},
{Name: "-o", Description: "output to file"},
{Name: "-O", Description: "save with remote filename"},
{Name: "-s", Description: "silent mode"},
{Name: "-S", Description: "show error in silent"},
{Name: "-L", Description: "follow redirects"},
{Name: "-k", Description: "insecure (skip TLS verify)"},
{Name: "-I", Description: "fetch headers only"},
{Name: "-v", Description: "verbose"},
{Name: "-u", Description: "user:password"},
{Name: "-b", Description: "send cookie"},
{Name: "-c", Description: "save cookie to file"},
{Name: "-F", Description: "multipart form data"},
{Name: "--json", Description: "send JSON body"},
{Name: "--compressed", Description: "request compressed response"},
{Name: "--max-time", Description: "max time in seconds"},
{Name: "--retry", Description: "retry count"},
{Name: "-A", Description: "custom user-agent"},
{Name: "--proxy", Description: "use proxy"},
{Name: "-T", Description: "upload file"},
},
})
core.Register(&core.Spec{
Name: "wget",
Description: "non-interactive downloader",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-O", Description: "output filename"},
{Name: "-q", Description: "quiet"},
{Name: "-v", Description: "verbose"},
{Name: "-r", Description: "recursive"},
{Name: "-P", Description: "output directory"},
{Name: "-c", Description: "continue download"},
{Name: "--no-check-certificate", Description: "skip TLS verify"},
{Name: "-b", Description: "background"},
{Name: "--limit-rate", Description: "limit rate"},
{Name: "-U", Description: "custom user-agent"},
{Name: "--spider", Description: "check URL only"},
{Name: "-np", Description: "no parent directories"},
{Name: "-nH", Description: "no host directories"},
},
})
core.Register(&core.Spec{
Name: "nc",
Description: "netcat - TCP/UDP tool",
Options: []core.Option{
{Name: "-l", Description: "listen mode"},
{Name: "-p", Description: "local port"},
{Name: "-z", Description: "port scan mode"},
{Name: "-v", Description: "verbose"},
{Name: "-u", Description: "UDP mode"},
{Name: "-n", Description: "no DNS"},
{Name: "-w", Description: "timeout"},
},
})
core.Register(&core.Spec{
Name: "ping",
Description: "test network connectivity",
Options: []core.Option{
{Name: "-c", Description: "packet count"},
{Name: "-i", Description: "interval"},
{Name: "-t", Description: "TTL"},
{Name: "-s", Description: "packet size"},
{Name: "-q", Description: "quiet output"},
},
})
core.Register(&core.Spec{
Name: "dig",
Description: "DNS lookup",
Options: []core.Option{
{Name: "+short", Description: "short output"},
{Name: "+noall", Description: "no output flags"},
{Name: "+answer", Description: "answer section only"},
{Name: "-t", Description: "record type"},
{Name: "@", Description: "nameserver"},
},
})
core.Register(&core.Spec{
Name: "nslookup",
Description: "query DNS",
})
core.Register(&core.Spec{
Name: "ifconfig",
Description: "configure network interface",
Options: []core.Option{
{Name: "-a", Description: "all interfaces"},
},
})
core.Register(&core.Spec{
Name: "ip",
Description: "show/manage network",
Subcommands: []core.Subcommand{
{Name: "addr", Description: "address management"},
{Name: "link", Description: "network device management"},
{Name: "route", Description: "routing table management"},
{Name: "neigh", Description: "ARP table"},
{Name: "rule", Description: "routing policy"},
},
Options: []core.Option{
{Name: "-4", Description: "IPv4 only"},
{Name: "-6", Description: "IPv6 only"},
{Name: "-br", Description: "brief output"},
{Name: "-c", Description: "colorize"},
},
})
core.Register(&core.Spec{
Name: "netstat",
Description: "network statistics",
Options: []core.Option{
{Name: "-t", Description: "TCP connections"},
{Name: "-u", Description: "UDP connections"},
{Name: "-l", Description: "listening only"},
{Name: "-n", Description: "numeric addresses"},
{Name: "-p", Description: "show PID"},
{Name: "-r", Description: "routing table"},
{Name: "-s", Description: "statistics"},
},
})
core.Register(&core.Spec{
Name: "ss",
Description: "socket statistics",
Options: []core.Option{
{Name: "-t", Description: "TCP"},
{Name: "-u", Description: "UDP"},
{Name: "-l", Description: "listening"},
{Name: "-n", Description: "numeric"},
{Name: "-p", Description: "show process"},
{Name: "-a", Description: "all"},
{Name: "-4", Description: "IPv4"},
{Name: "-6", Description: "IPv6"},
},
})
}
+109
View File
@@ -0,0 +1,109 @@
package sys
import (
"context"
"os/exec"
"strings"
"time"
"github.com/versenilvis/iris/commands/core"
)
func processGenerator(tokens []string, _ string, _ string) []core.Suggestion {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// output: "pid command"
cmd := exec.CommandContext(ctx, "ps", "-eo", "pid,comm")
out, err := cmd.Output()
if err != nil {
return nil
}
seen := make(map[string]bool)
var results []core.Suggestion
for _, line := range strings.Split(string(out), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parts := strings.Fields(line)
if len(parts) < 2 {
continue
}
pid := parts[0]
if pid[0] < '0' || pid[0] > '9' {
continue
}
name := parts[1]
// suggest both PID and process name
if !seen[pid] {
seen[pid] = true
results = append(results, core.Suggestion{Cmd: pid, Desc: name})
}
}
return results
}
func init() {
core.Register(&core.Spec{
Name: "ps",
Description: "report processes",
Options: []core.Option{
{Name: "-e", Description: "all processes"},
{Name: "-f", Description: "full format"},
{Name: "-u", Description: "by user"},
{Name: "aux", Description: "all + user + x"},
{Name: "-o", Description: "custom format"},
{Name: "--pid", Description: "filter by PID"},
{Name: "--sort", Description: "sort by field"},
},
})
core.Register(&core.Spec{
Name: "kill",
Description: "send signal to process",
Generator: processGenerator,
Options: []core.Option{
{Name: "-9", Description: "SIGKILL (force)"},
{Name: "-15", Description: "SIGTERM (graceful)"},
{Name: "-2", Description: "SIGINT"},
{Name: "-HUP", Description: "SIGHUP (reload)"},
{Name: "-l", Description: "list signal names"},
},
})
core.Register(&core.Spec{
Name: "killall",
Description: "kill by process name",
Generator: processGenerator,
Options: []core.Option{
{Name: "-9", Description: "SIGKILL"},
{Name: "-s", Description: "specify signal"},
{Name: "-u", Description: "only for user"},
{Name: "-i", Description: "confirm each"},
},
})
core.Register(&core.Spec{
Name: "pkill",
Description: "kill by pattern",
Options: []core.Option{
{Name: "-9", Description: "SIGKILL"},
{Name: "-f", Description: "match full command"},
{Name: "-u", Description: "match by user"},
},
})
core.Register(&core.Spec{
Name: "pgrep",
Description: "find process by pattern",
Options: []core.Option{
{Name: "-l", Description: "list name and PID"},
{Name: "-f", Description: "match full command"},
{Name: "-u", Description: "match by user"},
{Name: "-a", Description: "list command line"},
},
})
}
+40
View File
@@ -0,0 +1,40 @@
package text
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "find",
Description: "search for files",
Generator: core.FileGenerator("/"),
Options: []core.Option{
{Name: "-name", Description: "match by name"},
{Name: "-iname", Description: "match by name (case insensitive)"},
{Name: "-type", Description: "match by type (f=file, d=dir, l=link)"},
{Name: "-size", Description: "match by size (+1M, -10k)"},
{Name: "-mtime", Description: "modified n days ago"},
{Name: "-atime", Description: "accessed n days ago"},
{Name: "-ctime", Description: "changed n days ago"},
{Name: "-newer", Description: "newer than file"},
{Name: "-maxdepth", Description: "max depth to descend"},
{Name: "-mindepth", Description: "min depth to start"},
{Name: "-exec", Description: "execute command on match"},
{Name: "-execdir", Description: "exec in matched dir"},
{Name: "-delete", Description: "delete matched files"},
{Name: "-print", Description: "print matched paths"},
{Name: "-print0", Description: "null-terminated output"},
{Name: "-empty", Description: "match empty files/dirs"},
{Name: "-perm", Description: "match by permissions"},
{Name: "-user", Description: "match by owner"},
{Name: "-group", Description: "match by group"},
{Name: "-not", Description: "negate expression"},
{Name: "-or", Description: "logical OR"},
{Name: "-and", Description: "logical AND"},
{Name: "-prune", Description: "exclude from search"},
{Name: "-ls", Description: "list in ls format"},
{Name: "-xdev", Description: "don't cross filesystems"},
},
})
}
+57
View File
@@ -0,0 +1,57 @@
package text
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "grep",
Description: "search text in files",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-r", Description: "recursive"},
{Name: "-R", Description: "recursive + follow symlinks"},
{Name: "-i", Description: "ignore case"},
{Name: "-n", Description: "show line numbers"},
{Name: "-v", Description: "invert match"},
{Name: "-l", Description: "show filenames only"},
{Name: "-L", Description: "files without match"},
{Name: "-c", Description: "count matching lines"},
{Name: "-E", Description: "extended regex (egrep)"},
{Name: "-P", Description: "perl-compatible regex"},
{Name: "-F", Description: "fixed string (fgrep)"},
{Name: "-w", Description: "whole word match"},
{Name: "-x", Description: "whole line match"},
{Name: "-A", Description: "lines after match"},
{Name: "-B", Description: "lines before match"},
{Name: "-C", Description: "lines around match"},
{Name: "-m", Description: "max matches per file"},
{Name: "-o", Description: "only matching part"},
{Name: "-q", Description: "quiet mode"},
{Name: "--include", Description: "include file pattern"},
{Name: "--exclude", Description: "exclude file pattern"},
{Name: "--exclude-dir", Description: "exclude directory"},
{Name: "-H", Description: "always print filename"},
{Name: "-h", Description: "never print filename"},
},
})
// egrep and fgrep aliases
core.Register(&core.Spec{
Name: "egrep",
Description: "grep with extended regex",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-r", Description: "recursive"},
{Name: "-i", Description: "ignore case"},
{Name: "-n", Description: "show line numbers"},
{Name: "-v", Description: "invert match"},
{Name: "-l", Description: "show filenames only"},
{Name: "-w", Description: "whole word"},
{Name: "-A", Description: "lines after"},
{Name: "-B", Description: "lines before"},
{Name: "-C", Description: "lines around"},
},
})
}
+78
View File
@@ -0,0 +1,78 @@
package text
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
core.Register(&core.Spec{
Name: "rg",
Description: "ripgrep (fast search)",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-i", Description: "case insensitive"},
{Name: "-s", Description: "case sensitive"},
{Name: "-n", Description: "show line numbers"},
{Name: "-N", Description: "hide line numbers"},
{Name: "-l", Description: "filenames only"},
{Name: "-L", Description: "follow symlinks"},
{Name: "-c", Description: "count matches"},
{Name: "-w", Description: "word boundary match"},
{Name: "-x", Description: "whole line match"},
{Name: "-v", Description: "invert match"},
{Name: "-o", Description: "only matching text"},
{Name: "-A", Description: "lines after match"},
{Name: "-B", Description: "lines before match"},
{Name: "-C", Description: "lines around match"},
{Name: "-m", Description: "max matches per file"},
{Name: "-e", Description: "additional pattern"},
{Name: "-f", Description: "patterns from file"},
{Name: "--type", Description: "file type filter (e.g. go, py, js)"},
{Name: "--type-not", Description: "exclude file type"},
{Name: "--glob", Description: "include glob pattern"},
{Name: "--iglob", Description: "include glob (case insensitive)"},
{Name: "--hidden", Description: "search hidden files"},
{Name: "--no-ignore", Description: "don't use .gitignore"},
{Name: "--no-ignore-vcs", Description: "don't use VCS ignore"},
{Name: "-g", Description: "include/exclude glob"},
{Name: "--json", Description: "output as JSON"},
{Name: "--stats", Description: "print match statistics"},
{Name: "--color", Description: "colorize output (auto/always/never)"},
{Name: "--no-heading", Description: "no file name headings"},
{Name: "-H", Description: "always show filename"},
{Name: "--max-depth", Description: "max directory depth"},
{Name: "-p", Description: "smart heading/colors (for piping)"},
{Name: "-U", Description: "multiline matching"},
{Name: "--multiline", Description: "enable multiline"},
{Name: "-F", Description: "treat pattern as literal string"},
{Name: "--fixed-strings", Description: "literal string match"},
{Name: "-z", Description: "search compressed files"},
{Name: "--encoding", Description: "file encoding"},
{Name: "--trim", Description: "trim whitespace"},
},
})
core.Register(&core.Spec{
Name: "fd",
Description: "fast find alternative",
Generator: core.FileGenerator("/"),
Options: []core.Option{
{Name: "-t", Description: "type (f/d/l/x/e/s)"},
{Name: "-e", Description: "file extension"},
{Name: "-H", Description: "include hidden"},
{Name: "-I", Description: "no gitignore"},
{Name: "-d", Description: "max depth"},
{Name: "-x", Description: "execute command"},
{Name: "-X", Description: "exec batch"},
{Name: "-l", Description: "long listing"},
{Name: "-L", Description: "follow symlinks"},
{Name: "-p", Description: "full path match"},
{Name: "-g", Description: "glob pattern"},
{Name: "-c", Description: "color mode"},
{Name: "-0", Description: "null-separated output"},
{Name: "-j", Description: "number of threads"},
{Name: "--changed-within", Description: "recently changed"},
{Name: "--changed-before", Description: "changed before date"},
},
})
}
+140
View File
@@ -0,0 +1,140 @@
package text
import (
"github.com/versenilvis/iris/commands/core"
)
func init() {
// awk
core.Register(&core.Spec{
Name: "awk",
Description: "pattern-directed scanning",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-F", Description: "field separator"},
{Name: "-v", Description: "assign variable"},
{Name: "-f", Description: "read program from file"},
{Name: "-i", Description: "in-place edit (gawk)"},
{Name: "--posix", Description: "POSIX compat mode"},
{Name: "-W", Description: "compatibility options"},
},
})
// gawk is also common
core.Register(&core.Spec{
Name: "gawk",
Description: "GNU awk",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-F", Description: "field separator"},
{Name: "-v", Description: "assign variable"},
{Name: "-f", Description: "read program from file"},
{Name: "-i", Description: "in-place edit"},
{Name: "--sandbox", Description: "sandbox mode"},
{Name: "--profile", Description: "profiling output"},
},
})
// sed
core.Register(&core.Spec{
Name: "sed",
Description: "stream editor",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-e", Description: "add expression"},
{Name: "-f", Description: "read script from file"},
{Name: "-i", Description: "in-place edit"},
{Name: "-n", Description: "suppress default output"},
{Name: "-E", Description: "extended regex"},
{Name: "-r", Description: "extended regex (GNU)"},
{Name: "-z", Description: "null-delimited lines"},
{Name: "--sandbox", Description: "sandbox mode"},
},
})
// xargs
core.Register(&core.Spec{
Name: "xargs",
Description: "build and run commands from stdin",
Options: []core.Option{
{Name: "-I", Description: "replace string (e.g. -I{})"},
{Name: "-n", Description: "max args per command"},
{Name: "-P", Description: "parallel jobs"},
{Name: "-0", Description: "null-delimited input"},
{Name: "-d", Description: "custom delimiter"},
{Name: "-t", Description: "print command before executing"},
{Name: "-p", Description: "prompt before executing"},
{Name: "-r", Description: "no run if empty input"},
{Name: "--no-run-if-empty", Description: "don't run if no input"},
},
})
// tr
core.Register(&core.Spec{
Name: "tr",
Description: "translate or delete characters",
Options: []core.Option{
{Name: "-d", Description: "delete characters"},
{Name: "-s", Description: "squeeze repeated chars"},
{Name: "-c", Description: "complement set"},
},
})
// sort
core.Register(&core.Spec{
Name: "sort",
Description: "sort lines of text",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-n", Description: "numeric sort"},
{Name: "-r", Description: "reverse sort"},
{Name: "-k", Description: "sort by key field"},
{Name: "-t", Description: "field separator"},
{Name: "-u", Description: "unique (remove duplicates)"},
{Name: "-f", Description: "ignore case"},
{Name: "-h", Description: "human-readable sort"},
{Name: "-V", Description: "version sort"},
{Name: "-o", Description: "output file"},
{Name: "--parallel", Description: "parallel sort"},
},
})
// uniq
core.Register(&core.Spec{
Name: "uniq",
Description: "filter adjacent duplicate lines",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-c", Description: "prefix count"},
{Name: "-d", Description: "only duplicates"},
{Name: "-u", Description: "only unique"},
{Name: "-i", Description: "ignore case"},
{Name: "-f", Description: "skip n fields"},
{Name: "-s", Description: "skip n chars"},
},
})
// cut
core.Register(&core.Spec{
Name: "cut",
Description: "extract columns from lines",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-d", Description: "delimiter"},
{Name: "-f", Description: "field numbers (e.g. 1,3 or 1-3)"},
{Name: "-c", Description: "character positions"},
{Name: "--output-delimiter", Description: "output delimiter"},
},
})
// tee
core.Register(&core.Spec{
Name: "tee",
Description: "read stdin, write to stdout and files",
Generator: core.FileGenerator(),
Options: []core.Option{
{Name: "-a", Description: "append to files"},
{Name: "-i", Description: "ignore interrupts"},
},
})
}
+1 -1
View File
@@ -1,4 +1,4 @@
package info
package view
import (
"github.com/versenilvis/iris/commands/core"
@@ -1,4 +1,4 @@
package info
package view
import (
"github.com/versenilvis/iris/commands/core"
@@ -1,4 +1,4 @@
package info
package view
import (
"github.com/versenilvis/iris/commands/core"
+1 -1
View File
@@ -1,4 +1,4 @@
package info
package view
import (
"github.com/versenilvis/iris/commands/core"
+3
View File
@@ -107,6 +107,9 @@ func SearchHistory(query string) ([]HistResult, error) {
allCmds = append(allCmds, cmd)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
// build historyCache backwards so newest commands come first
seen := make(map[string]bool)
+3 -1
View File
@@ -99,7 +99,9 @@ func (o *Overlay) RenderGhostText(buffer string, userNavigated bool) string {
if ghostText != "" || padLen > 0 {
s.WriteString("\0337") // SAVE CURSOR at prompt
if ghostText != "" {
s.WriteString("\033[90m" + ghostText + "\033[0m")
s.WriteString("\033[90m")
s.WriteString(ghostText)
s.WriteString("\033[0m")
}
if padLen > 0 {
s.WriteString(strings.Repeat(" ", padLen))
+1 -1
View File
@@ -177,6 +177,6 @@ check-interval = "24h"
}
fmt.Println("\nSetup complete! Please restart your terminal or run:")
fmt.Printf(" source %s\n", configFile)
fmt.Printf(" \033[32msource %s\033[0m\n", configFile)
},
}
+1 -6
View File
@@ -13,12 +13,7 @@ import (
"github.com/spf13/cobra"
"github.com/versenilvis/iris/commands/core"
_ "github.com/versenilvis/iris/commands/dev"
_ "github.com/versenilvis/iris/commands/fs"
_ "github.com/versenilvis/iris/commands/info"
_ "github.com/versenilvis/iris/commands/runner"
_ "github.com/versenilvis/iris/commands/search"
_ "github.com/versenilvis/iris/commands/view"
_ "github.com/versenilvis/iris/commands"
"github.com/versenilvis/iris/config"
"golang.org/x/term"
)
+3
View File
@@ -276,6 +276,9 @@ func runWrapper() {
rBuf.WriteString(overlay.Render())
_, _ = os.Stdout.Write([]byte(rBuf.String()))
}
if err := scanner.Err(); err != nil {
debugLog("[IPC] scanner error: %v", err)
}
}()
var naiveBuffer string
+70 -1
View File
@@ -9,7 +9,7 @@ import (
"testing"
"github.com/versenilvis/iris/commands/core"
_ "github.com/versenilvis/iris/commands/dev"
_ "github.com/versenilvis/iris/commands"
)
// setupGitRepo creates a real git repo in a temp dir with:
@@ -304,4 +304,73 @@ func TestGitSuggestions(t *testing.T) {
t.Error("git reset HEAD should suggest file.go")
}
})
t.Run("show suggests tags and commits", func(t *testing.T) {
res := core.Lookup("git show ")
foundTag := false
foundCommit := false
for _, r := range res {
if strings.Contains(r.Cmd, "v1.0") {
foundTag = true
}
// commit hashes are 7+ hex chars
parts := strings.Fields(r.Cmd)
if len(parts) > 0 {
h := parts[len(parts)-1]
if len(h) >= 7 {
foundCommit = true
}
}
}
if !foundTag {
t.Error("git show should suggest tag v1.0")
}
if !foundCommit {
t.Error("git show should suggest commit hashes")
}
})
t.Run("cherry-pick suggests commits", func(t *testing.T) {
res := core.Lookup("git cherry-pick ")
if len(res) == 0 {
t.Error("git cherry-pick should suggest commits")
}
// all suggestions should be short hex hashes
for _, r := range res {
parts := strings.Fields(r.Cmd)
if len(parts) == 0 {
continue
}
h := parts[len(parts)-1]
if len(h) < 7 {
t.Errorf("cherry-pick suggestion looks invalid: %s", r.Cmd)
}
}
})
t.Run("revert suggests commits", func(t *testing.T) {
res := core.Lookup("git revert ")
if len(res) == 0 {
t.Error("git revert should suggest commits")
}
})
t.Run("global flags don't break subcommand detection", func(t *testing.T) {
ctx := context.Background()
out, err := exec.CommandContext(ctx, "git", "rev-parse", "--abbrev-ref", "HEAD").Output()
if err != nil {
t.Skip("can't determine HEAD branch")
}
activeBranch := strings.TrimSpace(string(out))
res := core.Lookup("git -c core.pager=cat checkout ")
for _, r := range res {
parts := strings.Fields(r.Cmd)
for _, p := range parts {
if p == activeBranch {
t.Errorf("git -c core.pager=cat checkout should not suggest active branch '%s', got: %s", activeBranch, r.Cmd)
}
}
}
})
}
+194
View File
@@ -0,0 +1,194 @@
package tests
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"github.com/versenilvis/iris/commands/core"
"github.com/versenilvis/iris/commands/js"
_ "github.com/versenilvis/iris/commands"
)
func TestNpmScriptGenerator(t *testing.T) {
tmp := t.TempDir()
_ = os.Chdir(tmp)
t.Run("reads scripts from package.json", func(t *testing.T) {
pkg := map[string]any{
"name": "test-app",
"scripts": map[string]string{
"dev": "vite",
"build": "vite build",
"test": "vitest",
"lint": "eslint .",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
},
}
data, _ := json.Marshal(pkg)
_ = os.WriteFile(filepath.Join(tmp, "package.json"), data, 0644)
defer os.Remove(filepath.Join(tmp, "package.json"))
// ensure CWD is tmp
core.ShellPID = 0
_ = os.Chdir(tmp)
results := js.NpmScriptGenerator(nil, "", "")
found := make(map[string]bool)
for _, r := range results {
found[r.Cmd] = true
}
for _, expected := range []string{"dev", "build", "test", "lint", "preview", "typecheck"} {
if !found[expected] {
t.Errorf("expected script '%s' in suggestions", expected)
}
}
})
t.Run("priority scripts come first", func(t *testing.T) {
pkg := map[string]any{
"scripts": map[string]string{
"zzz-last": "echo last",
"dev": "vite",
"aaa-first": "echo first",
"build": "vite build",
},
}
data, _ := json.Marshal(pkg)
_ = os.WriteFile(filepath.Join(tmp, "package.json"), data, 0644)
defer os.Remove(filepath.Join(tmp, "package.json"))
_ = os.Chdir(tmp)
results := js.NpmScriptGenerator(nil, "", "")
if len(results) < 2 {
t.Fatal("expected at least 2 results")
}
// dev should appear before zzz-last
devIdx, zzzIdx := -1, -1
for i, r := range results {
if r.Cmd == "dev" {
devIdx = i
}
if r.Cmd == "zzz-last" {
zzzIdx = i
}
}
if devIdx == -1 {
t.Error("dev not found")
}
if zzzIdx == -1 {
t.Error("zzz-last not found")
}
if devIdx > zzzIdx {
t.Errorf("'dev' (idx %d) should come before 'zzz-last' (idx %d)", devIdx, zzzIdx)
}
})
t.Run("fallback when no package.json", func(t *testing.T) {
emptyDir := t.TempDir()
_ = os.Chdir(emptyDir)
defer func() { _ = os.Chdir(tmp) }()
results := js.NpmScriptGenerator(nil, "", "")
if len(results) == 0 {
t.Error("expected fallback suggestions when no package.json")
}
})
}
func TestSshHostGenerator(t *testing.T) {
tmp := t.TempDir()
sshDir := filepath.Join(tmp, ".ssh")
_ = os.MkdirAll(sshDir, 0700)
configContent := `
Host prod-server
HostName 10.0.0.1
User deploy
Host staging bastion
HostName staging.example.com
User ubuntu
Host *.internal
User admin
Host !forbidden wildcard-test
HostName test.internal
`
_ = os.WriteFile(filepath.Join(sshDir, "config"), []byte(configContent), 0600)
// temporarily replace home dir lookup by using a mock path
// we call the generator directly with a custom home dir
results := sshHostGeneratorFromPath(filepath.Join(sshDir, "config"))
found := make(map[string]bool)
for _, r := range results {
found[r.Cmd] = true
}
if !found["prod-server"] {
t.Error("expected prod-server in suggestions")
}
if !found["staging"] {
t.Error("expected staging in suggestions")
}
if !found["bastion"] {
t.Error("expected bastion in suggestions")
}
// wildcards should be excluded
if found["*.internal"] {
t.Error("wildcard *.internal should not be suggested")
}
if found["!forbidden"] {
t.Error("negated host !forbidden should not be suggested")
}
}
// sshHostGeneratorFromPath is a helper that reads a specific ssh config path
func sshHostGeneratorFromPath(configPath string) []core.Suggestion {
import_bufio := func() {
// using bufio in the same style as ssh.go
}
_ = import_bufio
f, err := os.Open(configPath)
if err != nil {
return nil
}
defer func() { _ = f.Close() }()
seen := make(map[string]bool)
var results []core.Suggestion
scanner := strings.NewReader("")
_ = scanner
data, _ := os.ReadFile(configPath)
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if !strings.HasPrefix(strings.ToLower(line), "host ") {
continue
}
parts := strings.Fields(line)
for _, host := range parts[1:] {
if strings.ContainsAny(host, "*?!") {
continue
}
if seen[host] {
continue
}
seen[host] = true
results = append(results, core.Suggestion{Cmd: host, Desc: "ssh host"})
}
}
return results
}
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"testing"
"github.com/versenilvis/iris/commands/core"
_ "github.com/versenilvis/iris/commands/fs" // Register z command
_ "github.com/versenilvis/iris/commands"
)
func TestIntegration_ZoxideMultiWord(t *testing.T) {