feat(cli): TTY-aware return + OSC52 clipboard with terminal gating
This commit is contained in:
parent
06f4b87af1
commit
81122f8607
2 changed files with 74 additions and 0 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
@ -144,6 +145,43 @@ func bwGet(run cmdRunner, env []string, field, name string) (string, error) {
|
||||||
return run("bw", bwGetArgs(field, name), env)
|
return run("bw", bwGetArgs(field, name), env)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func returnMode(isTTY bool) string {
|
||||||
|
if isTTY {
|
||||||
|
return "clipboard"
|
||||||
|
}
|
||||||
|
return "stdout"
|
||||||
|
}
|
||||||
|
|
||||||
|
// stdoutIsTTY reports whether stdout is a character device (a terminal).
|
||||||
|
func stdoutIsTTY() bool {
|
||||||
|
fi, err := os.Stdout.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return fi.Mode()&os.ModeCharDevice != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// osc52 returns the OSC 52 escape that makes the local terminal copy payload to
|
||||||
|
// the system clipboard (works over SSH; no X11). osc52clear copies empty.
|
||||||
|
func osc52(payload string) string {
|
||||||
|
return "\x1b]52;c;" + base64.StdEncoding.EncodeToString([]byte(payload)) + "\a"
|
||||||
|
}
|
||||||
|
func osc52clear() string { return "\x1b]52;c;\a" }
|
||||||
|
|
||||||
|
// terminalAllowed gates OSC 52: only terminals known to honor clipboard writes,
|
||||||
|
// else we'd dump the secret's base64 into scrollback on unsupported terminals.
|
||||||
|
func terminalAllowed(term, termProgram string) bool {
|
||||||
|
t := strings.ToLower(term)
|
||||||
|
p := strings.ToLower(termProgram)
|
||||||
|
for _, ok := range []string{"kitty", "alacritty", "foot", "wezterm", "ghostty", "tmux", "screen"} {
|
||||||
|
if strings.Contains(t, ok) || strings.Contains(p, ok) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// xterm proper supports it only when the program is a known-good emulator.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func vaultSetup(args []string) error { return fmt.Errorf("not implemented") }
|
func vaultSetup(args []string) error { return fmt.Errorf("not implemented") }
|
||||||
func vaultStatus(args []string) error { return fmt.Errorf("not implemented") }
|
func vaultStatus(args []string) error { return fmt.Errorf("not implemented") }
|
||||||
func vaultList(args []string) error { return fmt.Errorf("not implemented") }
|
func vaultList(args []string) error { return fmt.Errorf("not implemented") }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -140,3 +141,38 @@ func TestBwUnlockReturnsSession(t *testing.T) {
|
||||||
t.Fatalf("unlock argv = %v", last)
|
t.Fatalf("unlock argv = %v", last)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReturnMode(t *testing.T) {
|
||||||
|
if returnMode(true) != "clipboard" || returnMode(false) != "stdout" {
|
||||||
|
t.Fatal("returnMode wrong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOSC52Encode(t *testing.T) {
|
||||||
|
got := osc52("secret")
|
||||||
|
want := "\x1b]52;c;" + base64.StdEncoding.EncodeToString([]byte("secret")) + "\a"
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("osc52 = %q want %q", got, want)
|
||||||
|
}
|
||||||
|
if osc52clear() != "\x1b]52;c;\a" {
|
||||||
|
t.Fatalf("osc52clear wrong: %q", osc52clear())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTerminalAllowed(t *testing.T) {
|
||||||
|
allow := []struct{ term, prog string }{
|
||||||
|
{"xterm-kitty", ""}, {"alacritty", ""}, {"foot", ""}, {"tmux-256color", ""},
|
||||||
|
{"screen-256color", ""}, {"xterm-256color", "WezTerm"}, {"xterm-256color", "ghostty"},
|
||||||
|
}
|
||||||
|
for _, c := range allow {
|
||||||
|
if !terminalAllowed(c.term, c.prog) {
|
||||||
|
t.Errorf("terminalAllowed(%q,%q) = false, want true", c.term, c.prog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deny := []struct{ term, prog string }{{"dumb", ""}, {"", ""}, {"vt100", ""}}
|
||||||
|
for _, c := range deny {
|
||||||
|
if terminalAllowed(c.term, c.prog) {
|
||||||
|
t.Errorf("terminalAllowed(%q,%q) = true, want false", c.term, c.prog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue