Documentation
¶
Overview ¶
Package gemini tracks Google Gemini prompt quota usage by reading conversation history via Gemini's internal web APIs.
Google provides no way to see how many Pro or Thinking prompts you've used today. This package counts your actual usage by authenticating with browser cookies, listing today's conversations, and classifying each turn by model type.
Authentication ¶
All operations require a CookieProvider to supply Google session cookies. Several built-in providers are available:
- BraveCookies / ChromeCookies — read from the browser's encrypted cookie database
- FileCookies — read from a JSON file
- StaticCookies — use hardcoded cookie values
- ExecCookies — shell out to a helper binary (useful for launchd services)
Browser providers should be wrapped with CachedProvider to avoid repeated macOS Keychain prompts:
provider := gemini.CachedProvider(gemini.BraveCookies("Profile 2"))
One-shot usage ¶
FetchUsage performs a single query and returns the current quota snapshot:
usage, err := gemini.FetchUsage(ctx, provider)
fmt.Printf("Pro: %d/%d remaining\n", usage.Pro.Remaining, usage.Pro.Limit)
Continuous tracking ¶
NewTracker polls at a regular interval and delivers results via a channel and/or callback. It automatically refreshes cookies and stops when the context is cancelled:
tracker, err := gemini.NewTracker(ctx, provider,
gemini.WithInterval(30*time.Second),
gemini.WithCookieRefresh(6*time.Hour),
)
for usage := range tracker.Updates() {
// ...
}
Configuration ¶
Both NewClient and NewTracker accept functional options for customization: WithProLimit, WithThinkingLimit, WithResetTimezone, WithHTTPTimeout, WithLogger, WithInterval, WithCookieRefresh, and WithCallback.
Index ¶
- Constants
- Variables
- func DefaultCacheDir() string
- type CacheKeyer
- type CacheOption
- type ChatInfo
- type Client
- type ClientOption
- type CookieProvider
- func BraveCookies(profile string) CookieProvider
- func CachedProvider(inner CookieProvider, opts ...CacheOption) CookieProvider
- func ChromeCookies(profile string) CookieProvider
- func ExecCookies(binPath string, args ...string) CookieProvider
- func FileCookies(path string) CookieProvider
- func StaticCookies(psid, psidts string) CookieProvider
- type Invalidator
- type QuotaInfo
- type Tracker
- type TrackerOption
- type TurnCounts
- type Usage
Constants ¶
const ( DefaultHTTPTimeout = 30 * time.Second DefaultProLimit = 100 DefaultThinkingLimit = 300 DefaultPollInterval = 60 * time.Second )
Defaults for client options.
Variables ¶
var ErrAuthExpired = errors.New("gemini auth expired: cookies are no longer valid")
ErrAuthExpired indicates that the session cookies are no longer valid. The batchexecute endpoint returned a redirect or HTML login page instead of API data. Refresh cookies and retry.
Functions ¶
func DefaultCacheDir ¶
func DefaultCacheDir() string
DefaultCacheDir returns the directory used for cookie cache files.
Types ¶
type CacheKeyer ¶
type CacheKeyer interface {
CacheKey() string
}
CacheKeyer is an optional interface that CookieProviders can implement to provide a unique key for cache file naming. CachedProvider uses this to derive a profile-specific default cache filename automatically.
type CacheOption ¶
type CacheOption func(*cachedProvider)
CacheOption configures the CachedProvider.
func WithCachePath ¶
func WithCachePath(path string) CacheOption
WithCachePath sets the file path for the cookie cache. Default: ~/.config/gemini-usage/cookies.json (via os.UserConfigDir).
func WithCacheTTL ¶
func WithCacheTTL(d time.Duration) CacheOption
WithCacheTTL sets how long cached cookies are reused before refreshing from the inner provider. Default: 12 hours.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to Gemini's internal web APIs.
func NewClient ¶
func NewClient(ctx context.Context, provider CookieProvider, opts ...ClientOption) (*Client, error)
NewClient creates a Client authenticated with the given CookieProvider. It fetches cookies, sets up the HTTP client, and extracts session tokens.
func (*Client) CountTurnsInChat ¶
func (c *Client) CountTurnsInChat(ctx context.Context, cid string, since time.Time) (TurnCounts, error)
CountTurnsInChat reads a conversation and counts user turns by quota bucket. Only turns with a timestamp at or after `since` are counted; older turns in a resumed conversation are skipped.
func (*Client) CountUsageSince ¶
CountUsageSince counts all user turns by quota bucket since the given time.
type ClientOption ¶
type ClientOption func(*clientConfig)
ClientOption configures a Client.
func WithHTTPTimeout ¶
func WithHTTPTimeout(d time.Duration) ClientOption
WithHTTPTimeout sets the HTTP client timeout. Default: 30s.
func WithLogger ¶
func WithLogger(l *slog.Logger) ClientOption
WithLogger sets the structured logger. Default: slog.Default().
func WithProLimit ¶
func WithProLimit(n int) ClientOption
WithProLimit sets the daily Pro prompt quota. Default: 100.
func WithResetTimezone ¶
func WithResetTimezone(loc *time.Location) ClientOption
WithResetTimezone sets the timezone for daily quota resets. Default: system local timezone.
func WithThinkingLimit ¶
func WithThinkingLimit(n int) ClientOption
WithThinkingLimit sets the daily Thinking prompt quota. Default: 300.
type CookieProvider ¶
CookieProvider retrieves Google cookies for Gemini authentication.
func BraveCookies ¶
func BraveCookies(profile string) CookieProvider
BraveCookies returns a CookieProvider that reads cookies from Brave's encrypted cookie database. On macOS this triggers a Keychain prompt.
If profile is empty, it defaults to "Profile 1". Wrap with CachedProvider to avoid repeated Keychain prompts.
provider := gemini.CachedProvider(gemini.BraveCookies(""))
func CachedProvider ¶
func CachedProvider(inner CookieProvider, opts ...CacheOption) CookieProvider
CachedProvider wraps another CookieProvider with on-disk caching. This avoids repeated Keychain prompts when using browser-based providers.
provider := gemini.CachedProvider(gemini.BraveCookies(""), gemini.WithCacheTTL(6*time.Hour))
func ChromeCookies ¶
func ChromeCookies(profile string) CookieProvider
ChromeCookies returns a CookieProvider that reads cookies from Chrome's encrypted cookie database. On macOS this triggers a Keychain prompt.
If profile is empty, it defaults to "Default". Wrap with CachedProvider to avoid repeated Keychain prompts.
provider := gemini.CachedProvider(gemini.ChromeCookies(""))
func ExecCookies ¶
func ExecCookies(binPath string, args ...string) CookieProvider
ExecCookies returns a CookieProvider that obtains cookies by executing an external binary and reading JSON from its stdout. This is useful for macOS launchd services where Keychain prompts cannot appear interactively: compile the helper binary (cmd/gemini-cookies) once, grant it Keychain access, and point ExecCookies at it. Recompiling your own program won't re-trigger Keychain prompts since the helper binary stays unchanged.
provider := gemini.CachedProvider(
gemini.ExecCookies("/usr/local/bin/gemini-cookies", "-browser", "brave", "-profile", "Profile 2"),
)
func FileCookies ¶
func FileCookies(path string) CookieProvider
FileCookies returns a CookieProvider that reads cookies from a JSON file. The file format matches the cookie cache format: either a cache object with metadata or a plain array of cookie objects.
provider := gemini.FileCookies("/path/to/cookies.json")
func StaticCookies ¶
func StaticCookies(psid, psidts string) CookieProvider
StaticCookies returns a CookieProvider that always returns the same cookies. Use this when you already have cookie values from another source.
provider := gemini.StaticCookies("PSID_VALUE", "PSIDTS_VALUE")
type Invalidator ¶
type Invalidator interface {
Invalidate() error
}
Invalidator is an optional interface that CookieProviders can implement to allow cache invalidation (e.g. CachedProvider).
type QuotaInfo ¶
type QuotaInfo struct {
Used int `json:"used"`
Limit int `json:"limit"`
Remaining int `json:"remaining"`
}
QuotaInfo holds usage data for a single quota bucket.
type Tracker ¶
type Tracker struct {
// contains filtered or unexported fields
}
Tracker continuously polls Gemini usage and delivers results via a channel and/or callback. It automatically refreshes cookies on a configurable interval and on auth errors.
The Tracker stops when the context is cancelled.
func NewTracker ¶
func NewTracker(ctx context.Context, provider CookieProvider, opts ...TrackerOption) (*Tracker, error)
NewTracker creates a Tracker that polls usage at a regular interval. The tracker starts immediately; consume results from Updates(). Cancel the context to stop the tracker.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
tracker, err := gemini.NewTracker(ctx, provider, gemini.WithInterval(30*time.Second))
for usage := range tracker.Updates() { ... }
type TrackerOption ¶
type TrackerOption func(*trackerConfig)
TrackerOption configures a Tracker.
func WithCallback ¶
func WithCallback(fn func(*Usage)) TrackerOption
WithCallback sets a function called on each usage update. The callback runs in the tracker's goroutine; keep it fast.
func WithClientOptions ¶
func WithClientOptions(opts ...ClientOption) TrackerOption
WithClientOptions passes ClientOption values through to the underlying Client.
func WithCookieRefresh ¶
func WithCookieRefresh(d time.Duration) TrackerOption
WithCookieRefresh sets how often to re-extract cookies from the provider. This creates a fresh Client with new cookies on the given interval. Default: 6 hours.
func WithInterval ¶
func WithInterval(d time.Duration) TrackerOption
WithInterval sets the polling interval. Default: 60s.
type TurnCounts ¶
TurnCounts holds per-bucket turn counts for a chat.
type Usage ¶
type Usage struct {
Pro QuotaInfo `json:"pro"`
Thinking QuotaInfo `json:"thinking"`
Flash int `json:"flash"`
LastUpdated time.Time `json:"last_updated"`
Error string `json:"error,omitempty"`
}
Usage holds the complete usage snapshot.
func FetchUsage ¶
func FetchUsage(ctx context.Context, provider CookieProvider, opts ...ClientOption) (*Usage, error)
FetchUsage performs a single usage fetch and returns the result. If authentication fails (including silent failures detected during API calls), it invalidates the cookie cache and retries with fresh cookies.