diff --git a/internal/cli/pinentry/pinentry.go b/internal/cli/pinentry/pinentry.go index 5dea2a98..6df0dd25 100644 --- a/internal/cli/pinentry/pinentry.go +++ b/internal/cli/pinentry/pinentry.go @@ -31,8 +31,10 @@ const ( ) // dialogScript shows the message passed as the first argument (never interpolated into the script) -// and prints the entered PIN to stdout. +// and prints the entered PIN to stdout. `activate` brings osascript to the front first: when the CLI +// runs in the background (e.g. from an agent) the dialog otherwise opens without keyboard focus. const dialogScript = `on run argv + activate set reply to display dialog (item 1 of argv) with title "rack-gateway" default answer "" ¬ with hidden answer buttons {"Cancel", "Approve"} default button "Approve" cancel button "Cancel" ¬ with icon caution giving up after 170 diff --git a/internal/cli/pinentry/pinentry_test.go b/internal/cli/pinentry/pinentry_test.go index 5a416504..fa867095 100644 --- a/internal/cli/pinentry/pinentry_test.go +++ b/internal/cli/pinentry/pinentry_test.go @@ -97,3 +97,12 @@ func TestDescribeCommandTruncatesLongCommands(t *testing.T) { t.Fatalf("describeCommand length = %d, want %d ending in ...", len(got), maxCommandLength+3) } } + +func TestDialogScriptActivatesBeforeShowingDialog(t *testing.T) { + activate := strings.Index(dialogScript, "\tactivate\n") + dialog := strings.Index(dialogScript, "display dialog") + if activate < 0 || activate > dialog { + t.Fatalf("dialog script must activate osascript before the dialog so the PIN field has focus:\n%s", + dialogScript) + } +}