feat(macos): PDF Services stamp helper with dual-channel Verae auth
Implements macOS v0: verae-print stamp + install-macos PDF menu items.
Saves {name}.pdf, {name}-verae-timestamp-{RECEIPTID}.pdf, and .verae.json.
Channels: verae (api.veraetime.net JWT) and zapier (zap.verae.ai xVerae- + attachPdf).
This commit is contained in:
parent
e91275a67c
commit
46f2bcd43e
1 changed files with 1259 additions and 24 deletions
221
cmd/verae-print/main.go
Normal file
221
cmd/verae-print/main.go
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/auth"
|
||||
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/job"
|
||||
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/veraeapi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
switch os.Args[1] {
|
||||
case "stamp":
|
||||
os.Exit(runStamp(os.Args[2:]))
|
||||
case "install-macos":
|
||||
os.Exit(runInstallMacOS(os.Args[2:]))
|
||||
case "version", "-version", "--version":
|
||||
fmt.Println(job.HelperVersion)
|
||||
case "help", "-h", "--help":
|
||||
usage()
|
||||
default:
|
||||
// PDF Services may invoke the binary with the PDF path as argv[1]
|
||||
if strings.HasSuffix(strings.ToLower(os.Args[1]), ".pdf") && fileExists(os.Args[1]) {
|
||||
os.Exit(runStamp([]string{"-channel", envOr("VERAE_PRINT_CHANNEL", "verae"), os.Args[1]}))
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "unknown command %q\n", os.Args[1])
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, `verae-print %s — stamp a printed PDF with Verae (digests only)
|
||||
|
||||
Usage:
|
||||
verae-print stamp [--channel=verae|zapier] [--out-dir DIR] [--name STEM] [--open] <file.pdf>
|
||||
verae-print install-macos # install PDF Services menu items
|
||||
verae-print version
|
||||
|
||||
Channels:
|
||||
verae (default) api.veraetime.net + ~/.verae/auth.properties JWT
|
||||
zapier zap.verae.ai + xVerae- token (XVERAE_TOKEN / ~/.verae/xverae-token / Keychain)
|
||||
|
||||
Outputs in --out-dir (default ~/Documents/Verae Timestamps):
|
||||
{name}.pdf
|
||||
{name}-verae-timestamp-{RECEIPTID}.pdf
|
||||
{name}.verae.json
|
||||
`, job.HelperVersion)
|
||||
}
|
||||
|
||||
func runStamp(args []string) int {
|
||||
fs := flag.NewFlagSet("stamp", flag.ExitOnError)
|
||||
channel := fs.String("channel", envOr("VERAE_PRINT_CHANNEL", "verae"), "verae | zapier")
|
||||
outDir := fs.String("out-dir", "", "output directory")
|
||||
name := fs.String("name", "", "document stem override")
|
||||
openFolder := fs.Bool("open", false, "open output folder on success (macOS)")
|
||||
_ = fs.Parse(args)
|
||||
if fs.NArg() < 1 {
|
||||
fmt.Fprintln(os.Stderr, "stamp: PDF path required")
|
||||
return 2
|
||||
}
|
||||
pdfPath := fs.Arg(0)
|
||||
|
||||
ch, err := auth.ParseChannel(*channel)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 2
|
||||
}
|
||||
|
||||
client := &veraeapi.Client{Channel: ch}
|
||||
switch ch {
|
||||
case auth.ChannelZapier:
|
||||
tok, err := auth.LoadXVeraeToken()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
client.XVeraeToken = tok
|
||||
default:
|
||||
tok, err := auth.LoadVeraeJWT()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
client.VeraeToken = tok
|
||||
}
|
||||
|
||||
res, err := job.Run(job.Options{
|
||||
SourcePDF: pdfPath,
|
||||
OutDir: *outDir,
|
||||
DocumentName: *name,
|
||||
Channel: ch,
|
||||
Client: client,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "stamp failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
fmt.Printf("Saved:\n %s\n %s\n %s\nReceipt: %s\nSHA-256: %s\nChannel: %s\n",
|
||||
res.OriginalPDF, res.CertificatePDF, res.ReceiptJSON, res.ReceiptID, res.SHA256, res.Channel)
|
||||
|
||||
if *openFolder {
|
||||
_ = exec.Command("open", filepath.Dir(res.OriginalPDF)).Run()
|
||||
}
|
||||
// macOS notification (best-effort)
|
||||
notify(fmt.Sprintf("Verae timestamp %s", res.ReceiptID), res.CertificatePDF)
|
||||
return 0
|
||||
}
|
||||
|
||||
func runInstallMacOS(args []string) int {
|
||||
fs := flag.NewFlagSet("install-macos", flag.ExitOnError)
|
||||
bin := fs.String("bin", "", "path to verae-print binary (default: this executable)")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
exe := *bin
|
||||
if exe == "" {
|
||||
var err error
|
||||
exe, err = os.Executable()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
exe, _ = filepath.EvalSymlinks(exe)
|
||||
}
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
destDir := filepath.Join(home, "Library", "PDF Services")
|
||||
if err := os.MkdirAll(destDir, 0o755); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
|
||||
items := []struct {
|
||||
name string
|
||||
channel string
|
||||
}{
|
||||
{"Timestamp with Verae", "verae"},
|
||||
{"Timestamp with Verae (Zapier)", "zapier"},
|
||||
}
|
||||
for _, it := range items {
|
||||
appDir := filepath.Join(destDir, it.name+".app", "Contents", "MacOS")
|
||||
if err := os.MkdirAll(appDir, 0o755); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
plist := filepath.Join(destDir, it.name+".app", "Contents", "Info.plist")
|
||||
plistBody := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0"><dict>
|
||||
<key>CFBundleExecutable</key><string>runner</string>
|
||||
<key>CFBundleIdentifier</key><string>ai.verae.print.%s</string>
|
||||
<key>CFBundleName</key><string>%s</string>
|
||||
<key>CFBundlePackageType</key><string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key><string>%s</string>
|
||||
</dict></plist>
|
||||
`, strings.ReplaceAll(it.channel, " ", ""), it.name, job.HelperVersion)
|
||||
if err := os.WriteFile(plist, []byte(plistBody), 0o644); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
runner := filepath.Join(appDir, "runner")
|
||||
script := fmt.Sprintf(`#!/bin/bash
|
||||
set -euo pipefail
|
||||
LOG="${HOME}/Library/Logs/verae-print.log"
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
exec >>"$LOG" 2>&1
|
||||
echo "$(date -u +%%Y-%%m-%%dT%%H:%%MZ) channel=%s args: $*"
|
||||
PDF=""
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
*.pdf|*.PDF) PDF="$a" ;;
|
||||
esac
|
||||
done
|
||||
if [[ -z "${PDF}" && $# -ge 1 ]]; then PDF="$1"; fi
|
||||
if [[ -z "${PDF}" || ! -f "${PDF}" ]]; then
|
||||
osascript -e 'display notification "No PDF received from Print dialog" with title "Verae Timestamp"' || true
|
||||
exit 1
|
||||
fi
|
||||
%q stamp -channel %s -open "$PDF"
|
||||
`, it.channel, exe, it.channel)
|
||||
if err := os.WriteFile(runner, []byte(script), 0o755); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
fmt.Println("Installed:", filepath.Join(destDir, it.name+".app"))
|
||||
}
|
||||
fmt.Println("Print → PDF → Timestamp with Verae (or Zapier)")
|
||||
fmt.Println("Binary:", exe)
|
||||
return 0
|
||||
}
|
||||
|
||||
func notify(title, subtitle string) {
|
||||
script := fmt.Sprintf(`display notification %q with title "Verae Timestamp" subtitle %q`, subtitle, title)
|
||||
_ = exec.Command("osascript", "-e", script).Run()
|
||||
}
|
||||
|
||||
func envOr(k, def string) string {
|
||||
if v := os.Getenv(k); v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func fileExists(p string) bool {
|
||||
st, err := os.Stat(p)
|
||||
return err == nil && !st.IsDir()
|
||||
}
|
||||
Loading…
Reference in a new issue