//go:build darwin package auth import ( "fmt" "os" "os/exec" "strings" ) // RunFirstRunSetup guides the user through customer.verae.ai signup and // xVerae- signing-key paste. Interactive (osascript dialogs). func RunFirstRunSetup() error { if HasXVeraeToken() { return nil } btn, err := osaButton( "Verae Print Stamp needs a customer signing key before it can timestamp documents.\n\nCreate a free account on customer.verae.ai, open Security, and create an xVerae- token.", "Verae customer account", []string{"Create account…", "I already have a key", "Cancel"}, "Create account…", ) if err != nil { return err } switch btn { case "Cancel": return fmt.Errorf("setup cancelled") case "Create account…": _ = exec.Command("open", PortalSignupURL).Start() btn2, err2 := osaButton( "In the browser:\n1. Create your account (or sign in)\n2. Open the Security tab\n3. Create an xVerae- token\n4. Copy it once — it is shown only once\n\nClick Continue when you have copied the key.", "Create your signing key", []string{"Continue", "Cancel"}, "Continue", ) if err2 != nil || btn2 == "Cancel" { return fmt.Errorf("setup cancelled") } } token, err := osaPrompt( "Paste your xVerae- signing key from customer.verae.ai → Security.\n\nIt starts with xVerae- and is shown only once when created.", "Paste signing key", "", ) if err != nil { return err } token = strings.TrimSpace(token) if token == "" { return fmt.Errorf("no key pasted") } if err := SaveXVeraeToken(token); err != nil { return err } _, _ = osaButton( "Signing key saved.\n\nYou can Print → Destination → Verae Timestamping from any app.\nKeys are stored in Keychain (verae.xVeraeToken) and ~/.verae/xverae-token.", "Verae Print Stamp", []string{"OK"}, "OK", ) return nil } func osaButton(message, title string, buttons []string, defaultBtn string) (string, error) { quoted := make([]string, len(buttons)) for i, b := range buttons { quoted[i] = fmt.Sprintf("%q", b) } script := fmt.Sprintf( `button returned of (display dialog %q with title %q buttons {%s} default button %q)`, message, title, strings.Join(quoted, ", "), defaultBtn, ) out, err := exec.Command("osascript", "-e", script).Output() if err != nil { return "", fmt.Errorf("dialog cancelled") } return strings.TrimSpace(string(out)), nil } func osaPrompt(message, title, defaultAnswer string) (string, error) { script := fmt.Sprintf( `text returned of (display dialog %q with title %q default answer %q with hidden answer buttons {"Cancel", "Save key"} default button "Save key")`, message, title, defaultAnswer, ) out, err := exec.Command("osascript", "-e", script).Output() if err != nil { return "", fmt.Errorf("prompt cancelled") } return strings.TrimSpace(string(out)), nil } // EnsureReady runs first-run setup when no signing key is present. func EnsureReady() error { if HasXVeraeToken() { return nil } // Headless (no GUI session): fail with instructions if os.Getenv("SSH_CONNECTION") != "" && os.Getenv("DISPLAY") == "" { return fmt.Errorf("no xVerae- signing key — run: verae-print setup\nOr open %s , create an account, mint a key under Security, then:\n verae-print setup", PortalSignupURL) } return RunFirstRunSetup() }