Skip to content

Commit 5c67f1b

Browse files
committed
feat: add command aliasing via PATH lookup
Signed-off-by: olalekan odukoya <[email protected]>
1 parent ba54094 commit 5c67f1b

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

cmd/limactl/main.go

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
package main
55

66
import (
7+
"context"
78
"errors"
89
"fmt"
910
"os"
11+
"os/exec"
1012
"path/filepath"
1113
"runtime"
1214
"strings"
@@ -40,7 +42,8 @@ func main() {
4042
}
4143
}
4244
}
43-
if err := newApp().Execute(); err != nil {
45+
rootCmd := newApp()
46+
if err := executeWithPluginSupport(rootCmd, os.Args[1:]); err != nil {
4447
handleExitCoder(err)
4548
logrus.Fatal(err)
4649
}
@@ -215,6 +218,67 @@ func handleExitCoder(err error) {
215218
}
216219
}
217220

221+
// executeWithPluginSupport handles command execution with plugin support.
222+
func executeWithPluginSupport(rootCmd *cobra.Command, args []string) error {
223+
if len(args) > 0 {
224+
cmd, _, err := rootCmd.Find(args)
225+
if err != nil || cmd == rootCmd {
226+
if err := runExternalPlugin(rootCmd.Context(), args[0], args[1:]); err == nil {
227+
return nil
228+
}
229+
}
230+
}
231+
232+
rootCmd.SetArgs(args)
233+
return rootCmd.Execute()
234+
}
235+
236+
func runExternalPlugin(ctx context.Context, name string, args []string) error {
237+
if ctx == nil {
238+
ctx = context.Background()
239+
}
240+
241+
externalCmd := "limactl-" + name
242+
execPath, err := exec.LookPath(externalCmd)
243+
if err != nil {
244+
return err
245+
}
246+
247+
if err := updatePathEnv(); err != nil {
248+
logrus.Warnf("failed to update PATH environment: %v", err)
249+
// PATH update failure shouldn't prevent plugin execution
250+
}
251+
252+
logrus.Debugf("found external command: %s", execPath)
253+
254+
cmd := exec.CommandContext(ctx, execPath, args...)
255+
cmd.Stdin = os.Stdin
256+
cmd.Stdout = os.Stdout
257+
cmd.Stderr = os.Stderr
258+
cmd.Env = os.Environ()
259+
260+
return cmd.Run()
261+
}
262+
263+
func updatePathEnv() error {
264+
exe, err := os.Executable()
265+
if err != nil {
266+
return fmt.Errorf("failed to get executable path: %w", err)
267+
}
268+
269+
binDir := filepath.Dir(exe)
270+
currentPath := os.Getenv("PATH")
271+
newPath := binDir + string(filepath.ListSeparator) + currentPath
272+
273+
if err := os.Setenv("PATH", newPath); err != nil {
274+
return fmt.Errorf("failed to set PATH environment: %w", err)
275+
}
276+
277+
logrus.Debugf("updated PATH to prioritize %s", binDir)
278+
279+
return nil
280+
}
281+
218282
// WrapArgsError annotates cobra args error with some context, so the error message is more user-friendly.
219283
func WrapArgsError(argFn cobra.PositionalArgs) cobra.PositionalArgs {
220284
return func(cmd *cobra.Command, args []string) error {

website/content/en/docs/usage/_index.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,61 @@ Then you can connect directly:
7272
ssh lima-default
7373
```
7474

75+
### Command Aliasing (Plugin System)
76+
77+
Lima supports a plugin-like command aliasing system similar to `git`, `kubectl`, and `docker`. When you run a `limactl` command that doesn't exist, Lima will automatically look for an external program named `limactl-<command>` in your system's PATH.
78+
79+
#### Creating Custom Aliases
80+
81+
To create a custom alias, create an executable script with the name `limactl-<alias>` and place it somewhere in your PATH.
82+
83+
**Example: Creating a `ps` alias for listing instances**
84+
85+
1. Create a script called `limactl-ps`:
86+
```bash
87+
#!/bin/sh
88+
# Show instances in a compact format
89+
limactl list --format table "$@"
90+
```
91+
92+
2. Make it executable and place it in your PATH:
93+
```bash
94+
chmod +x limactl-ps
95+
sudo mv limactl-ps /usr/local/bin/
96+
```
97+
98+
3. Now you can use it:
99+
```bash
100+
limactl ps # Shows instances in table format
101+
limactl ps --quiet # Shows only instance names
102+
```
103+
104+
**Example: Creating an `sh` alias**
105+
106+
```bash
107+
#!/bin/sh
108+
# limactl-sh - Connect to an instance shell
109+
limactl shell "$@"
110+
```
111+
112+
After creating this alias:
113+
```bash
114+
limactl sh default # Equivalent to: limactl shell default
115+
limactl sh myinstance bash # Equivalent to: limactl shell myinstance bash
116+
```
117+
118+
#### How It Works
119+
120+
1. When you run `limactl <unknown-command>`, Lima first tries to find a built-in command
121+
2. If no built-in command is found, Lima searches for `limactl-<unknown-command>` in your PATH
122+
3. If found, Lima executes the external program and passes all remaining arguments to it
123+
4. If not found, Lima shows the standard "unknown command" error
124+
125+
This system allows you to:
126+
- Create personal shortcuts and aliases
127+
- Extend Lima's functionality without modifying the core application
128+
- Share custom commands with your team by distributing scripts
129+
75130
### Shell completion
76131
- To enable bash completion, add `source <(limactl completion bash)` to `~/.bash_profile`.
77132
- To enable zsh completion, see `limactl completion zsh --help`

0 commit comments

Comments
 (0)