"Hard to find, but keeps everything running smoothly."
G-Spot is a lightweight, cross-platform system tray application for macOS, Windows, and Linux that continuously monitors Google Cloud SDK authentication, tracks active GCP project configurations, inspects billing, and prevents AI coding agents (Gemini, Claude Code, Cursor, Copilot, LangChain, etc.) from timing out due to expired OAuth or Application Default Credentials (ADC).
#34A853): Both User (gcloud auth) and ADC (application-default) tokens are active.#FBBC05): Partial authentication (only one set of tokens is active).#EA4335): Both tokens are expired, missing, or disconnected.#4285F4): Initializing / actively refreshing.#80868B): Offline — no network reachable, so credential status is unknown. Distinguished from Red deliberately: a red icon on a plane is a false alarm, and false alarms train you to ignore the icon.G-Spot: Active, G-Spot: Partial Auth, G-Spot: Expired, or G-Spot: Offline (no network) alongside the active project ID (e.g. G-Spot: Active (my-project-id)).Account: dev@company.com (~48m left)).[Re-Authenticate Now] action button when authentication is lost, immediately opening the terminal login flow.Popup on Auth Loss: [ON/OFF] with an instant preview action Test Auth Loss Popup Now.Terminal.app on macOS, Command Prompt / PowerShell on Windows, or standard Linux emulators) to run gcloud auth login && gcloud auth application-default login interactively.gcloud project directly from the tray context menu without opening a shell.To keep the primary tray menu lightweight and clutter-free, pro-developer utilities are housed in the Developer & Agent Tools submenu:
export GOOGLE_CLOUD_PROJECT="active-project-id"
export CLOUDSDK_CORE_PROJECT="active-project-id"
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/application_default_credentials.json"
export VERTEX_AI_PROJECT="active-project-id"
GOOGLE_APPLICATION_CREDENTIALS, CLOUDSDK_AUTH_ACCESS_TOKEN). The full report is copied to your clipboard — a nine-field diagnostic doesn't fit in a notification banner.gcloud configuration profiles (default, staging, production).gcloud config config-helper --min-expiry) when fewer than 10 minutes remain. If the refresh does not extend the token, the underlying session has expired and G-Spot says so rather than reporting a success it didn't achieve.Desktop Notifications: [ON/OFF] to enable or disable system notification toasts and banners when credential status changes.Popup on Auth Loss: [ON/OFF] to enable or disable the interactive modal dialog window when authentication is lost, with an instant preview action Test Auth Loss Popup Now.gcloud CLI and ADC, not just the web console) is a Google Workspace admin policy, configurable between 1 and 24 hours by someone holding the Security Settings privilege. G-Spot detects whether your active account belongs to a Workspace domain and either links you to the right admin page or explains why no such policy exists for a personal account.gcloud) installed and initialized..exe (Recommended)GSPOT.exe directly from windows/GSPOT.exe or the root GSPOT.exe (also available on the latest GitHub Release).GSPOT.exe to launch.^ overflow menu)..dmg (Recommended)G-Spot-1.0.0.dmg directly from macos/G-Spot-1.0.0.dmg or the root G-Spot-1.0.0.dmg (also available on the latest GitHub Release)..dmg and drag G-Spot.app into your Applications folder.Note: On first launch macOS may show a Gatekeeper warning since the app is not notarized. Right-click the app → Open → Open to bypass.
Requires Python 3.9+.
# 1. Clone the repository
git clone https://github.com/Somnora/GSPOT.git && cd GSPOT
# 2. Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# 3. Install dependencies
pip install -r requirements.txt
# 4. Launch G-Spot
python main.py
.exe):pip install pyinstaller
.\windows\build_windows.bat
# Output: dist\GSPOT.exe and windows\GSPOT.exe
.dmg):pip install pyinstaller
python macos/generate_icns.py # Generate macOS .icns app icon
pyinstaller macos/G-Spot.spec --noconfirm --clean
bash macos/build_dmg.sh # Output: dist/G-Spot-1.0.0.dmg
python main.py --help
# Options:
# -i, --interval SECONDS Polling check interval in seconds (default: 300 / 5 minutes)
# --debug Enable verbose debug logging
# -v, --version Display version information
#
# Headless mode (no tray, no display required):
# --status Print current status and exit 0
# --json With --status, print JSON instead of one line
# --check Exit 0 if at least --min-minutes remain, else 1
# --renew Force a non-interactive refresh; exit 0 on success
# --min-minutes N Minutes required (--check: 30, --renew: 55)
--status, --check and --renew run without a tray and without a display. They
never import pystray, so they work over SSH, in a container, or in CI where
importing a GUI toolkit would raise.
The failure this tool exists to prevent is starting a long agent run on a token that dies halfway through. Gate the run instead of being notified about it:
python main.py --check --min-minutes 30 || python main.py --renew
Exit 0 means you have at least 30 minutes and can safely start. Exit 1 means the
credential could not be brought up to that mark and only an interactive
gcloud auth login will help.
$ python main.py --status
GREEN | you@example.com | my-project | ~42m left | user=ok adc=ok
$ python main.py --status --json
{
"state": "green",
"account": "you@example.com",
"project": "my-project",
"token_expiry_minutes": 42,
"adc_valid": true,
"user_valid": true,
"network_reachable": true
}
state is one of green, yellow, red, offline, searching. JSON goes to
stdout and diagnostics to stderr, so --status --json 2>/dev/null is safe to
pipe straight into jq. --status always exits 0 — it reports, it does not gate.
network_reachable is only actively probed when every auth check has already
failed, so true means "not proven unreachable" rather than "just pinged".
| Command | 0 | 1 |
|---|---|---|
--status | always | — |
--check | at least --min-minutes remain | too little time, expired, offline, unknown expiry, or no gcloud |
--renew | refreshed, or already above --min-minutes | refresh could not extend the credential |
--check fails closed: anything it cannot positively verify is a failure, since
a false pass costs you a dead agent run. Partial auth (one of user/ADC missing)
still exits 0 if there is enough time, but prints a warning to stderr.
--renew exits 0 when the token was already comfortable and gcloud declined to
refresh it — that is the normal case in the one-liner above, not an error.
To have G-Spot run automatically in the background on system boot without showing a terminal window:
~/Library/LaunchAgents/com.user.gspot.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.user.gspot</string>
<key>ProgramArguments</key>
<array>
<string>/Users/YOUR_USERNAME/Desktop/G_Spot/.venv/bin/python</string>
<string>/Users/YOUR_USERNAME/Desktop/G_Spot/main.py</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/gspot.log</string>
<key>StandardErrorPath</key>
<string>/tmp/gspot.err</string>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/com.user.gspot.plist
pythonw.exe)Win + R, type shell:startup, and hit Enter."C:\path\to\G_Spot\.venv\Scripts\pythonw.exe" "C:\path\to\G_Spot\main.py"
(Using pythonw.exe runs Python without opening a Command Prompt window).G-Spot, and click Finish.C:\path\to\G_Spot\.venv\Scripts\pythonw.exemain.pyC:\path\to\G_Spot~/.config/autostart)Create ~/.config/autostart/gspot.desktop:
[Desktop Entry]
Type=Application
Name=G-Spot
Comment=Google Status, Project, & Optimization Tool
Exec=/path/to/G_Spot/.venv/bin/python /path/to/G_Spot/main.py
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
~/.config/systemd/user/gspot.service:[Unit]
Description=G-Spot Background Tray Service
After=graphical-session.target
[Service]
Type=simple
WorkingDirectory=/path/to/G_Spot
ExecStart=/path/to/G_Spot/.venv/bin/python main.py
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user enable --now gspot.service
G-Spot/
├── .github/
│ └── workflows/
│ └── release.yml # CI: Build .app, package .dmg, publish GitHub Release on tag
├── assets/
│ ├── icons/ # Monotone SVG icons (active, degraded, expired, actions)
│ ├── preview/ # Pre-rendered PNG previews of each icon state
│ └── icon.icns # macOS app icon bundle (generated by scripts/generate_icns.py)
├── gspot/
│ ├── __init__.py # Package metadata & tagline
│ ├── app.py # Pystray tray application loop & streamlined menu hierarchy
│ ├── clipboard.py # Cross-platform clipboard copy helper (pbcopy / clip / xclip)
│ ├── config.py # State enums (GREEN/YELLOW/RED), polling constants, status dataclass
│ ├── gcloud.py # Subprocess wrappers for auth, ADC, projects, configs, diagnostics
│ ├── headless.py # --status / --check / --renew CLI; imports no GUI dependencies
│ ├── icons.py # High-DPI 8x supersampled Pillow dynamic tray icon generator
│ ├── notifications.py # Desktop alert dispatcher + interactive modal popups
│ ├── settings.py # Persisted user preferences (~/.config/gspot/config.json)
│ └── terminal.py # Native interactive terminal launcher for re-authentication
├── scripts/
│ ├── generate_icns.py # Renders app icon into macOS .iconset → .icns
│ └── build_dmg.sh # Packages G-Spot.app into a distributable .dmg installer
├── tests/
│ ├── __init__.py
│ ├── test_auth_lifecycle.py # 40 tests for state transitions, offline handling, auto-renew
│ ├── test_gcloud.py # 25 tests for gcloud wrappers, billing, and the health doctor
│ ├── test_headless.py # 22 tests for headless exit codes and display independence
│ └── test_settings.py # 12 tests for preference persistence and corrupt-file fallback
├── main.py # CLI entrypoint; dispatches headless mode before loading the tray
├── G-Spot.spec # PyInstaller build specification
├── requirements.txt # Dependencies (pystray, Pillow, plyer)
└── README.md
Preferences you set from the tray menu (auto-renew, popup alerts, check interval) persist to:
| Platform | Path |
|---|---|
| macOS / Linux | ~/.config/gspot/config.json (respects XDG_CONFIG_HOME) |
| Windows | %APPDATA%\gspot\config.json |
The file is written atomically and is safe to edit by hand; out-of-range or malformed values fall
back to defaults rather than preventing startup. --interval overrides the saved value for a
single run without rewriting it.
G-Spot cannot extend how long your credentials last, and neither can any other local tool. Access
tokens live about an hour and refresh transparently; what actually kills a long agent run is the
session behind them expiring. That session length is set by a Google Workspace administrator
(Admin console → Security → Access and data control → Google Cloud session control), ranges from
1 to 24 hours, and applies to the gcloud CLI and ADC just as much as to the web console. There
is no gcloud flag or config property that changes it.
What G-Spot does instead: refresh while refreshing still works, tell you clearly when it stops
working, and put the relevant admin page one click away under Developer & Agent Tools → Session
Policy. Personal @gmail.com accounts have no Workspace org and therefore no such policy — the
menu says so rather than linking you to a page you'd get denied on.
source .venv/bin/activate
python -m unittest discover -s tests -v
The suite mocks every gcloud invocation and network call, so it runs without a Google Cloud SDK
installation and without touching your real credentials.
MIT License. Built to keep AI agents authenticated and uninterrupted.
3 commits
Python
95.9%
PowerShell
1.6%
Batchfile
1.6%
"Hard to find, but keeps everything running smoothly."
G-Spot is a lightweight, cross-platform system tray application for macOS, Windows, and Linux that continuously monitors Google Cloud SDK authentication, tracks active GCP project configurations, inspects billing, and prevents AI coding agents (Gemini, Claude Code, Cursor, Copilot, LangChain, etc.) from timing out due to expired OAuth or Application Default Credentials (ADC).
#34A853): Both User (gcloud auth) and ADC (application-default) tokens are active.#FBBC05): Partial authentication (only one set of tokens is active).#EA4335): Both tokens are expired, missing, or disconnected.#4285F4): Initializing / actively refreshing.#80868B): Offline — no network reachable, so credential status is unknown. Distinguished from Red deliberately: a red icon on a plane is a false alarm, and false alarms train you to ignore the icon.G-Spot: Active, G-Spot: Partial Auth, G-Spot: Expired, or G-Spot: Offline (no network) alongside the active project ID (e.g. G-Spot: Active (my-project-id)).Account: dev@company.com (~48m left)).[Re-Authenticate Now] action button when authentication is lost, immediately opening the terminal login flow.Popup on Auth Loss: [ON/OFF] with an instant preview action Test Auth Loss Popup Now.Terminal.app on macOS, Command Prompt / PowerShell on Windows, or standard Linux emulators) to run gcloud auth login && gcloud auth application-default login interactively.gcloud project directly from the tray context menu without opening a shell.To keep the primary tray menu lightweight and clutter-free, pro-developer utilities are housed in the Developer & Agent Tools submenu:
export GOOGLE_CLOUD_PROJECT="active-project-id"
export CLOUDSDK_CORE_PROJECT="active-project-id"
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/application_default_credentials.json"
export VERTEX_AI_PROJECT="active-project-id"
GOOGLE_APPLICATION_CREDENTIALS, CLOUDSDK_AUTH_ACCESS_TOKEN). The full report is copied to your clipboard — a nine-field diagnostic doesn't fit in a notification banner.gcloud configuration profiles (default, staging, production).gcloud config config-helper --min-expiry) when fewer than 10 minutes remain. If the refresh does not extend the token, the underlying session has expired and G-Spot says so rather than reporting a success it didn't achieve.Desktop Notifications: [ON/OFF] to enable or disable system notification toasts and banners when credential status changes.Popup on Auth Loss: [ON/OFF] to enable or disable the interactive modal dialog window when authentication is lost, with an instant preview action Test Auth Loss Popup Now.gcloud CLI and ADC, not just the web console) is a Google Workspace admin policy, configurable between 1 and 24 hours by someone holding the Security Settings privilege. G-Spot detects whether your active account belongs to a Workspace domain and either links you to the right admin page or explains why no such policy exists for a personal account.gcloud) installed and initialized..exe (Recommended)GSPOT.exe directly from windows/GSPOT.exe or the root GSPOT.exe (also available on the latest GitHub Release).GSPOT.exe to launch.^ overflow menu)..dmg (Recommended)G-Spot-1.0.0.dmg directly from macos/G-Spot-1.0.0.dmg or the root G-Spot-1.0.0.dmg (also available on the latest GitHub Release)..dmg and drag G-Spot.app into your Applications folder.Note: On first launch macOS may show a Gatekeeper warning since the app is not notarized. Right-click the app → Open → Open to bypass.
Requires Python 3.9+.
# 1. Clone the repository
git clone https://github.com/Somnora/GSPOT.git && cd GSPOT
# 2. Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# 3. Install dependencies
pip install -r requirements.txt
# 4. Launch G-Spot
python main.py
.exe):pip install pyinstaller
.\windows\build_windows.bat
# Output: dist\GSPOT.exe and windows\GSPOT.exe
.dmg):pip install pyinstaller
python macos/generate_icns.py # Generate macOS .icns app icon
pyinstaller macos/G-Spot.spec --noconfirm --clean
bash macos/build_dmg.sh # Output: dist/G-Spot-1.0.0.dmg
python main.py --help
# Options:
# -i, --interval SECONDS Polling check interval in seconds (default: 300 / 5 minutes)
# --debug Enable verbose debug logging
# -v, --version Display version information
#
# Headless mode (no tray, no display required):
# --status Print current status and exit 0
# --json With --status, print JSON instead of one line
# --check Exit 0 if at least --min-minutes remain, else 1
# --renew Force a non-interactive refresh; exit 0 on success
# --min-minutes N Minutes required (--check: 30, --renew: 55)
--status, --check and --renew run without a tray and without a display. They
never import pystray, so they work over SSH, in a container, or in CI where
importing a GUI toolkit would raise.
The failure this tool exists to prevent is starting a long agent run on a token that dies halfway through. Gate the run instead of being notified about it:
python main.py --check --min-minutes 30 || python main.py --renew
Exit 0 means you have at least 30 minutes and can safely start. Exit 1 means the
credential could not be brought up to that mark and only an interactive
gcloud auth login will help.
$ python main.py --status
GREEN | you@example.com | my-project | ~42m left | user=ok adc=ok
$ python main.py --status --json
{
"state": "green",
"account": "you@example.com",
"project": "my-project",
"token_expiry_minutes": 42,
"adc_valid": true,
"user_valid": true,
"network_reachable": true
}
state is one of green, yellow, red, offline, searching. JSON goes to
stdout and diagnostics to stderr, so --status --json 2>/dev/null is safe to
pipe straight into jq. --status always exits 0 — it reports, it does not gate.
network_reachable is only actively probed when every auth check has already
failed, so true means "not proven unreachable" rather than "just pinged".
| Command | 0 | 1 |
|---|---|---|
--status | always | — |
--check | at least --min-minutes remain | too little time, expired, offline, unknown expiry, or no gcloud |
--renew | refreshed, or already above --min-minutes | refresh could not extend the credential |
--check fails closed: anything it cannot positively verify is a failure, since
a false pass costs you a dead agent run. Partial auth (one of user/ADC missing)
still exits 0 if there is enough time, but prints a warning to stderr.
--renew exits 0 when the token was already comfortable and gcloud declined to
refresh it — that is the normal case in the one-liner above, not an error.
To have G-Spot run automatically in the background on system boot without showing a terminal window:
~/Library/LaunchAgents/com.user.gspot.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.user.gspot</string>
<key>ProgramArguments</key>
<array>
<string>/Users/YOUR_USERNAME/Desktop/G_Spot/.venv/bin/python</string>
<string>/Users/YOUR_USERNAME/Desktop/G_Spot/main.py</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/gspot.log</string>
<key>StandardErrorPath</key>
<string>/tmp/gspot.err</string>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/com.user.gspot.plist
pythonw.exe)Win + R, type shell:startup, and hit Enter."C:\path\to\G_Spot\.venv\Scripts\pythonw.exe" "C:\path\to\G_Spot\main.py"
(Using pythonw.exe runs Python without opening a Command Prompt window).G-Spot, and click Finish.C:\path\to\G_Spot\.venv\Scripts\pythonw.exemain.pyC:\path\to\G_Spot~/.config/autostart)Create ~/.config/autostart/gspot.desktop:
[Desktop Entry]
Type=Application
Name=G-Spot
Comment=Google Status, Project, & Optimization Tool
Exec=/path/to/G_Spot/.venv/bin/python /path/to/G_Spot/main.py
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
~/.config/systemd/user/gspot.service:[Unit]
Description=G-Spot Background Tray Service
After=graphical-session.target
[Service]
Type=simple
WorkingDirectory=/path/to/G_Spot
ExecStart=/path/to/G_Spot/.venv/bin/python main.py
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user enable --now gspot.service
G-Spot/
├── .github/
│ └── workflows/
│ └── release.yml # CI: Build .app, package .dmg, publish GitHub Release on tag
├── assets/
│ ├── icons/ # Monotone SVG icons (active, degraded, expired, actions)
│ ├── preview/ # Pre-rendered PNG previews of each icon state
│ └── icon.icns # macOS app icon bundle (generated by scripts/generate_icns.py)
├── gspot/
│ ├── __init__.py # Package metadata & tagline
│ ├── app.py # Pystray tray application loop & streamlined menu hierarchy
│ ├── clipboard.py # Cross-platform clipboard copy helper (pbcopy / clip / xclip)
│ ├── config.py # State enums (GREEN/YELLOW/RED), polling constants, status dataclass
│ ├── gcloud.py # Subprocess wrappers for auth, ADC, projects, configs, diagnostics
│ ├── headless.py # --status / --check / --renew CLI; imports no GUI dependencies
│ ├── icons.py # High-DPI 8x supersampled Pillow dynamic tray icon generator
│ ├── notifications.py # Desktop alert dispatcher + interactive modal popups
│ ├── settings.py # Persisted user preferences (~/.config/gspot/config.json)
│ └── terminal.py # Native interactive terminal launcher for re-authentication
├── scripts/
│ ├── generate_icns.py # Renders app icon into macOS .iconset → .icns
│ └── build_dmg.sh # Packages G-Spot.app into a distributable .dmg installer
├── tests/
│ ├── __init__.py
│ ├── test_auth_lifecycle.py # 40 tests for state transitions, offline handling, auto-renew
│ ├── test_gcloud.py # 25 tests for gcloud wrappers, billing, and the health doctor
│ ├── test_headless.py # 22 tests for headless exit codes and display independence
│ └── test_settings.py # 12 tests for preference persistence and corrupt-file fallback
├── main.py # CLI entrypoint; dispatches headless mode before loading the tray
├── G-Spot.spec # PyInstaller build specification
├── requirements.txt # Dependencies (pystray, Pillow, plyer)
└── README.md
Preferences you set from the tray menu (auto-renew, popup alerts, check interval) persist to:
| Platform | Path |
|---|---|
| macOS / Linux | ~/.config/gspot/config.json (respects XDG_CONFIG_HOME) |
| Windows | %APPDATA%\gspot\config.json |
The file is written atomically and is safe to edit by hand; out-of-range or malformed values fall
back to defaults rather than preventing startup. --interval overrides the saved value for a
single run without rewriting it.
G-Spot cannot extend how long your credentials last, and neither can any other local tool. Access
tokens live about an hour and refresh transparently; what actually kills a long agent run is the
session behind them expiring. That session length is set by a Google Workspace administrator
(Admin console → Security → Access and data control → Google Cloud session control), ranges from
1 to 24 hours, and applies to the gcloud CLI and ADC just as much as to the web console. There
is no gcloud flag or config property that changes it.
What G-Spot does instead: refresh while refreshing still works, tell you clearly when it stops
working, and put the relevant admin page one click away under Developer & Agent Tools → Session
Policy. Personal @gmail.com accounts have no Workspace org and therefore no such policy — the
menu says so rather than linking you to a page you'd get denied on.
source .venv/bin/activate
python -m unittest discover -s tests -v
The suite mocks every gcloud invocation and network call, so it runs without a Google Cloud SDK
installation and without touching your real credentials.
MIT License. Built to keep AI agents authenticated and uninterrupted.
3 commits
Python
95.9%
PowerShell
1.6%
Batchfile
1.6%