gflight is a macOS-focused CLI to search Google Flights results, create price watches, and notify via terminal and email.
go build ./cmd/gflight- Configure provider auth (SerpAPI key for Google Flights data):
gflight auth login --provider serpapi --serpapi-key "$GFLIGHT_SERPAPI_KEY"- One-shot search:
gflight search --from SFO --to ATH --depart 2026-06-10 --return 2026-06-24 --json
gflight --plain search --from SFO --to ATH --depart 2026-06-10- Create a watch and run it:
gflight watch create --name summer-athens --from SFO --to ATH --depart 2026-06-10 --return 2026-06-24 --target-price 700 --notify-terminal --notify-email --email-to you@example.com
gflight watch list
gflight watch disable --id w_123
gflight watch enable --id w_123
gflight watch delete --id w_123 --force
gflight watch run --all --once
gflight doctor --json
gflight doctor --strict
gflight completion zsh > ~/.zsh/completions/_gflight
gflight completion path zshCompletion install helpers:
# zsh
mkdir -p "$(dirname "$(gflight completion path zsh)")"
gflight completion zsh > "$(gflight completion path zsh)"
# bash
mkdir -p "$(dirname "$(gflight completion path bash)")"
gflight completion bash > "$(gflight completion path bash)"
# fish
mkdir -p "$(dirname "$(gflight completion path fish)")"
gflight completion fish > "$(gflight completion path fish)"Verification:
test -s "$(gflight completion path zsh)" && echo "zsh completion installed"gflight watch create ...create a saved watch.--plainoutput:watch_id=<id>- Supports
--notify-webhookand optional--webhook-url.
gflight watch listlist existing watches.--plainoutput header:id name enabled target_price from to depart
gflight watch enable --id <watch-id>enable a watch.--plainoutput:watch_id=<id>\tenabled=true
gflight watch disable --id <watch-id>disable a watch.--plainoutput:watch_id=<id>\tenabled=false
gflight watch delete --id <watch-id> --forcedelete a watch.--plainoutput:deleted_id=<id>- Safety: requires
--forceor--confirm <watch-id>.
gflight watch run --all --onceexecutes selected watches and prints a summary.- Requires exactly one selector:
--allor--id <watch-id>. - Exit behavior for provider failures:
- default: exits
4only when all evaluated provider requests fail - strict mode:
--fail-on-provider-errorsexits4on any provider failure
- default: exits
- Human mode summary:
evaluated,triggered,provider_failures,notify_failures. --plainoutput starts with stable summarykey=valuefields, followed by stable alert lines when alerts trigger.- JSON mode returns:
evaluatedtriggeredprovider_failuresnotify_failuresalerts(triggered alert objects)
- Requires exactly one selector:
--jsonfor deterministic structured output.stdoutcarries primary output;stderrcarries diagnostics/alerts.--no-inputavoids prompts.--plainemits stable line-based output for shell pipelines.- For mutation commands, plain output uses stable
key=valuefields. search --plainemits stable TSV header/rows plus a trailingurl=<google_flights_url>line.auth status --plainandnotify test --plainemit stablekey=valuefields.
- For mutation commands, plain output uses stable
--timeoutoverrides provider request timeout per command (search,watch run).doctor --jsonprovides preflight checks for provider auth, writable paths, and notification config.doctor --stricttreats warnings as failures (CI/agent preflight mode).- Query objects in JSON now use normalized
snake_casekeys (for examplequery.from,query.depart,query.sort_by). gflight help <command>provides command-specific help (for examplegflight help watch run,gflight help doctor).- Errors now include actionable
next:hints onstderrwhen a known remediation exists. - Unknown commands/subcommands include typo suggestions when a close match exists (for example
did you mean "watch"?).
0success1generic/runtime failure2invalid usage/validation3auth required/missing credentials4provider/upstream failure6notification delivery failure
Config path: $XDG_CONFIG_HOME/gflight/config.json (fallback ~/.config/gflight/config.json)
State path: $XDG_STATE_HOME/gflight (fallback ~/.local/state/gflight)
Supported config keys:
provider(serpapiorgoogle-url)serp_api_keyprovider_timeout_secondsprovider_retriesprovider_backoff_mswebhook_urlsmtp_hostsmtp_portsmtp_usersmtp_passsmtp_sendernotify_email
Related environment variables:
GFLIGHT_PROVIDER_TIMEOUT_SECONDSGFLIGHT_PROVIDER_RETRIESGFLIGHT_PROVIDER_BACKOFF_MSGFLIGHT_WEBHOOK_URL
Notification channel test examples:
gflight notify test --channel terminal
gflight notify test --channel email --to you@example.com
gflight notify test --channel webhook --url https://example.com/hookWebhook error hints:
- DNS issues are reported as
webhook dns lookup failed. - Timeouts are reported as
webhook timeout. - HTTP
429is reported aswebhook endpoint rate limited. - HTTP
5xxis reported aswebhook endpoint server error.
internal/cli: command handlers and CLI-facing validation/output.internal/cli/watch_cmd_mutation.go: watch create/list/enable/disable/delete command handlers.internal/cli/watch_cmd_run.go: watch run/test command handlers.internal/cli/watch_service.go: watch evaluation/selection/run logic (pure service helpers, unit-tested).internal/cli/auth_service.go: auth status + login mutation/validation helpers.internal/cli/config_service.go: config key get/set mutation/validation helpers.internal/cli/config_validate.go: shared runtime/docter config readiness validation.internal/cli/notify_dispatcher.go: notification abstraction boundary used by CLI orchestration.internal/cli/errors.go: centralized exit-code/error taxonomy mapping.internal/cli/cli_integration_test.go: table-driven CLI integration harness for agent flows.internal/provider: flight data providers (serpapi,google-url).internal/notify: terminal and SMTP notification delivery.internal/watcher: watch persistence store.
Use your scheduler to run:
gflight --json watch run --all --once- Build and choose stable absolute paths:
cd /Users/agis/projects/gflight
go build -o /Users/agis/bin/gflight ./cmd/gflight- Create
~/Library/LaunchAgents/com.agis.gflight.watch.plist:
<?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>Label</key>
<string>com.agis.gflight.watch</string>
<key>ProgramArguments</key>
<array>
<string>/bin/zsh</string>
<string>-lc</string>
<string>GFLIGHT_BIN=/Users/agis/bin/gflight GFLIGHT_CONFIG_HOME=/Users/agis/.config GFLIGHT_STATE_HOME=/Users/agis/.local/state /Users/agis/projects/gflight/scripts/run-watch-once.sh</string>
</array>
<key>StartInterval</key>
<integer>900</integer>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/gflight.watch.out.log</string>
<key>StandardErrorPath</key>
<string>/tmp/gflight.watch.err.log</string>
</dict>
</plist>- Load and verify:
launchctl unload ~/Library/LaunchAgents/com.agis.gflight.watch.plist 2>/dev/null || true
launchctl load ~/Library/LaunchAgents/com.agis.gflight.watch.plist
launchctl list | grep gflight
tail -f /tmp/gflight.watch.out.log /tmp/gflight.watch.err.logEvery 15 minutes:
*/15 * * * * GFLIGHT_BIN=/Users/agis/bin/gflight GFLIGHT_CONFIG_HOME=/Users/agis/.config GFLIGHT_STATE_HOME=/Users/agis/.local/state /Users/agis/projects/gflight/scripts/run-watch-once.sh >> /tmp/gflight.watch.cron.log 2>&1make release-check VERSION=vX.Y.Zmake release-dry-run VERSION=vX.Y.Zmake release VERSION=vX.Y.Z
Release scripts:
scripts/release-check.shscripts/release.shscripts/smoke-real-provider.sh(opt-in real-network smoke)
Optional real-provider smoke during release-check:
export RUN_REAL_PROVIDER_SMOKE=1
export GFLIGHT_SMOKE_REAL=1
export GFLIGHT_SERPAPI_KEY=...
export GFLIGHT_FROM=SFO
export GFLIGHT_TO=ATH
export GFLIGHT_DEPART=2026-06-10
make release-check VERSION=vX.Y.ZManual run:
make smoke-real-providerdocs/README.mdoverview of project docs.