Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
430875f200
111 files changed
+22958
No files matched your search
@@ -0,0 +1,184 @@
|
||||
// Package linkhandler makes lazysecrets:// links open lazysecrets: it
|
||||
// registers the scheme with the operating system, and launches a new
|
||||
// terminal window running "lazysecrets open <link>" when a link is opened.
|
||||
//
|
||||
// The terminal is the one install ran in, detected from its environment,
|
||||
// unless the config names another or gives a command of its own.
|
||||
package linkhandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Terminals lists the terminals launch knows, by name.
|
||||
var Terminals = []string{"kitty", "ghostty", "wezterm", "iterm2", "terminal", "alacritty", "gnome-terminal", "konsole", "foot"}
|
||||
|
||||
// Detect names the terminal from its environment (getenv), or "" if it
|
||||
// isn't one launch knows. Terminal-specific variables come first: inside
|
||||
// tmux, TERM_PROGRAM is "tmux" but the terminal's own variables are
|
||||
// usually inherited.
|
||||
func Detect(getenv func(string) string) string {
|
||||
switch {
|
||||
case getenv("KITTY_WINDOW_ID") != "" || getenv("TERM") == "xterm-kitty":
|
||||
return "kitty"
|
||||
case getenv("GHOSTTY_RESOURCES_DIR") != "" || getenv("TERM") == "xterm-ghostty":
|
||||
return "ghostty"
|
||||
case getenv("WEZTERM_EXECUTABLE") != "" || getenv("WEZTERM_PANE") != "":
|
||||
return "wezterm"
|
||||
case getenv("LC_TERMINAL") == "iTerm2" || getenv("ITERM_SESSION_ID") != "":
|
||||
return "iterm2"
|
||||
case getenv("ALACRITTY_WINDOW_ID") != "" || getenv("ALACRITTY_SOCKET") != "":
|
||||
return "alacritty"
|
||||
case getenv("KONSOLE_VERSION") != "":
|
||||
return "konsole"
|
||||
case getenv("GNOME_TERMINAL_SCREEN") != "":
|
||||
return "gnome-terminal"
|
||||
case getenv("TERM") == "foot":
|
||||
return "foot"
|
||||
}
|
||||
switch getenv("TERM_PROGRAM") {
|
||||
case "iTerm.app":
|
||||
return "iterm2"
|
||||
case "Apple_Terminal":
|
||||
return "terminal"
|
||||
case "WezTerm":
|
||||
return "wezterm"
|
||||
case "ghostty":
|
||||
return "ghostty"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Placeholder in a custom command stands for the command to run in it.
|
||||
const Placeholder = "{cmd}"
|
||||
|
||||
// Command is the command that opens a new window of terminal running
|
||||
// inner, for the operating system goos. custom, if set, is used instead,
|
||||
// with Placeholder replaced by inner's arguments.
|
||||
func Command(goos, terminal string, custom, inner []string) ([]string, error) {
|
||||
if len(custom) > 0 {
|
||||
if !slices.Contains(custom, Placeholder) {
|
||||
return nil, fmt.Errorf("linkHandler.command must contain %s, where the lazysecrets command goes", Placeholder)
|
||||
}
|
||||
var out []string
|
||||
for _, a := range custom {
|
||||
if a == Placeholder {
|
||||
out = append(out, inner...)
|
||||
} else {
|
||||
out = append(out, a)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
if goos == "darwin" {
|
||||
// Apps are started through open(1), so they needn't be on PATH.
|
||||
// -n: a new instance, which opens a window even if one runs.
|
||||
app := func(name string, args ...string) []string {
|
||||
return append([]string{"open", "-na", name, "--args"}, append(args, inner...)...)
|
||||
}
|
||||
switch terminal {
|
||||
case "kitty":
|
||||
// Its own instance, so quit it with the window: kitty
|
||||
// otherwise stays running without windows.
|
||||
return app("kitty", "-o", "macos_quit_when_last_window_closed=yes"), nil
|
||||
case "ghostty":
|
||||
return app("Ghostty", "-e"), nil
|
||||
case "wezterm":
|
||||
return app("WezTerm", "start", "--"), nil
|
||||
case "alacritty":
|
||||
return app("Alacritty", "-e"), nil
|
||||
case "iterm2":
|
||||
return osascript(`tell application "iTerm"
|
||||
activate
|
||||
create window with default profile command %s
|
||||
end tell`, inner), nil
|
||||
case "terminal":
|
||||
return osascript(`tell application "Terminal"
|
||||
activate
|
||||
do script %s
|
||||
end tell`, inner), nil
|
||||
}
|
||||
} else {
|
||||
switch terminal {
|
||||
case "kitty", "foot":
|
||||
return append([]string{terminal}, inner...), nil
|
||||
case "ghostty", "alacritty", "konsole":
|
||||
return append([]string{terminal, "-e"}, inner...), nil
|
||||
case "wezterm":
|
||||
return append([]string{"wezterm", "start", "--"}, inner...), nil
|
||||
case "gnome-terminal":
|
||||
return append([]string{"gnome-terminal", "--"}, inner...), nil
|
||||
}
|
||||
}
|
||||
if terminal == "" {
|
||||
return nil, fmt.Errorf("no terminal set: run lazysecrets handler install from your terminal, or set linkHandler.terminal or linkHandler.command in the config")
|
||||
}
|
||||
return nil, fmt.Errorf("unknown terminal %q (known: %s); linkHandler.command runs any other", terminal, strings.Join(Terminals, ", "))
|
||||
}
|
||||
|
||||
// osascript runs an AppleScript with inner, shell-quoted, as a string
|
||||
// literal in place of %s.
|
||||
func osascript(script string, inner []string) []string {
|
||||
return []string{"osascript", "-e", fmt.Sprintf(script, appleString(shellJoin(inner)))}
|
||||
}
|
||||
|
||||
// shellJoin quotes args for a POSIX shell.
|
||||
func shellJoin(args []string) string {
|
||||
q := make([]string, len(args))
|
||||
for i, a := range args {
|
||||
q[i] = "'" + strings.ReplaceAll(a, "'", `'\''`) + "'"
|
||||
}
|
||||
return strings.Join(q, " ")
|
||||
}
|
||||
|
||||
// appleString is s as an AppleScript string literal.
|
||||
func appleString(s string) string {
|
||||
return `"` + strings.NewReplacer(`\`, `\\`, `"`, `\"`).Replace(s) + `"`
|
||||
}
|
||||
|
||||
// Launch opens a new terminal window running inner, without waiting for
|
||||
// it.
|
||||
func Launch(argv []string) error {
|
||||
cmd := exec.Command(argv[0], argv[1:]...)
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = nil, nil, nil
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmd.Process.Release()
|
||||
}
|
||||
|
||||
// Executable is the path of the running lazysecrets, to register. A
|
||||
// binary from "go run" is deleted when it exits, so it's refused.
|
||||
func Executable() (string, error) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.Contains(exe, "go-build") {
|
||||
return "", fmt.Errorf("this is a temporary binary from go run: build or install lazysecrets first (go install ./cmd/lazysecrets), then run handler install with it")
|
||||
}
|
||||
return exe, nil
|
||||
}
|
||||
|
||||
// passEnv lists the variables carried from the install's environment to
|
||||
// lazysecrets in the new window: apps start with a minimal environment,
|
||||
// without Homebrew's PATH (so no az) or the user's config locations.
|
||||
var passEnv = []string{
|
||||
"PATH", "LAZYSECRETS_CONFIG_FILE", "LAZYSECRETS_ICONS", "XDG_CONFIG_HOME", "XDG_STATE_HOME",
|
||||
"AZURE_CONFIG_DIR", "DATABRICKS_CONFIG_FILE",
|
||||
}
|
||||
|
||||
// Env returns NAME=value for each variable to carry that is set.
|
||||
func Env(getenv func(string) string) []string {
|
||||
var out []string
|
||||
for _, k := range passEnv {
|
||||
if v := getenv(k); v != "" {
|
||||
out = append(out, k+"="+v)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user