feat: add Ollama zero-setup usage provider (ollama.local)#27
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4bb66883bc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let client = reqwest::blocking::Client::builder() | ||
| .timeout(HTTP_TIMEOUT) | ||
| .build() |
There was a problem hiding this comment.
Keep redirected Ollama probes local-only
When anything listening on localhost:11434 returns a 30x (for example a non-Ollama service on that port, or a daemon response that redirects unauthenticated users), the default reqwest client follows the Location before this code checks the status. That lets the supposedly local-only plan probe make an outbound request to an arbitrary URL, which breaks the privacy guarantee that the Ollama read stays on the machine; disable redirects for this client and treat 30x as an error instead.
Useful? React with 👍 / 👎.
| codex: true, | ||
| claude: true, | ||
| ollama: false, | ||
| ollama: true, |
There was a problem hiding this comment.
Preserve old configs' disabled Ollama default
For existing pre-v6/partial config files that do not contain enabledServices.ollama, parse_config_raw fills missing fields from AppConfig::default(), so this new default migrates those saved configs to ollama: true. With localEnabled also defaulting true, upgrading users who never opted into Ollama will start seeing the Ollama card and periodic localhost:11434 probes; add an explicit migration/default for missing legacy ollama to keep it false if only fresh configs should opt in.
Useful? React with 👍 / 👎.
| if config.providers.local_enabled { | ||
| providers.push(Box::new(OllamaLocalProvider::new())); | ||
| } |
There was a problem hiding this comment.
Avoid downgrading successful Ollama web readings
When Ollama web readings are enabled and the local daemon also returns a plan, registering this local provider makes the existing merge path treat the plan-only snapshot as a local delta source. Because it has no deltaBaselineAt/deltaUnit, a successful official web snapshot is cloned with lowered confidence and mergeStatus: local_delta_unavailable, even though the web usage data is valid; exclude Ollama plan-only snapshots from the web+local delta merge path or do not register both as mergeable providers.
Useful? React with 👍 / 👎.
|
|
||
| fn map_status_error(status: reqwest::StatusCode) -> UsageProviderError { | ||
| match status.as_u16() { | ||
| 401 | 403 => UsageProviderError::LoginRequired, |
There was a problem hiding this comment.
Do not route daemon sign-in to the web profile login
When the local Ollama daemon returns 401/403, this login_required status feeds the generic loginPromptVisible / startProviderLogin flow, which launches PickGauge's app-owned browser profile for ollama.com rather than signing the user's local daemon in. A signed-out daemon will therefore keep returning 401 after the user clicks the dashboard's Start login button; use a daemon-specific status/action or suppress the generic browser login prompt for ollama.local.
Useful? React with 👍 / 👎.
| Service, UsageConfidence, UsageProvider, UsageProviderError, UsageProviderId, UsageSnapshot, | ||
| UsageSource, | ||
| }; | ||
|
|
There was a problem hiding this comment.
Honor non-default local Ollama hosts
When Ollama is configured to listen on a non-default local address or port (for example via OLLAMA_HOST), the CLI and other clients use that configured daemon, but this provider always probes localhost:11434. Those users will get an Ollama isn't running snapshot even though their local daemon is healthy; read the configured local host (while rejecting non-loopback values if the provider must stay local-only) or expose a setting instead of hard-coding the default.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Valid edge case, deliberately deferred: this slice keeps the zero-setup probe pinned to the loopback default; non-default OLLAMA_HOST setups get an honest "isn't running" state rather than wrong data. Tracked in #29 (with the loopback-only guard you suggest).
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a7c47fdd58
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let client = reqwest::blocking::Client::builder() | ||
| .redirect(reqwest::redirect::Policy::none()) | ||
| .timeout(HTTP_TIMEOUT) | ||
| .build() |
There was a problem hiding this comment.
Disable proxies for daemon reads
When HTTP_PROXY/ALL_PROXY is set and the user has not excluded localhost with NO_PROXY, reqwest's default client will route this http://localhost:11434/api/me call through that proxy. For the new Ollama provider that is supposed to be a local-only daemon read, this can either send the request off-machine or make plan detection fail behind corporate proxies; build this client with proxies disabled, e.g. no_proxy(), before sending the daemon request.
Useful? React with 👍 / 👎.
| fn map_request_error(error: reqwest::Error) -> UsageProviderError { | ||
| if error.is_connect() || error.is_timeout() { | ||
| UsageProviderError::NotConfigured |
There was a problem hiding this comment.
Avoid backing off a stopped daemon
When Ollama is not running, this returns NotConfigured, but the generic refresh path records every provider error into the exponential backoff state; after a few daemon-down refreshes, even a user-triggered Refresh Now is skipped by try_begin_refresh until retry_after expires. For users who start Ollama after PickGauge has already observed it stopped, the card can keep saying “Ollama isn't running” for up to 15 minutes, so this local daemon state needs to avoid failure backoff or allow manual refresh to bypass it.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| if let Some(local) = local { | ||
| if let Some(local) = local.filter(|snapshot| !is_plan_only_local_snapshot(snapshot)) { |
There was a problem hiding this comment.
Preserve daemon plan when web login fails
This filter only excludes plan-only local snapshots after the web snapshot has real percentages; in the earlier !web_has_usage_percent branch, an Ollama web login_required/missing_data snapshot still falls back to the daemon plan via local.or(fake), lowers its confidence, and adds webStatus, causing the valid local plan card to render with a spurious “Login required” note when web readings are enabled but the browser profile is not signed in. Apply the plan-only handling before the unavailable-web fallback as well.
Useful? React with 👍 / 👎.
| UsageSource, | ||
| }; | ||
|
|
||
| const DEFAULT_BASE_URL: &str = "http://localhost:11434"; |
There was a problem hiding this comment.
Respect configured Ollama hosts
This hard-codes only the default daemon address, so users who run Ollama on a different local port or bind address via OLLAMA_HOST will get not_configured even though the CLI and local API are working. For those setups, the zero-setup provider never finds the daemon; derive the endpoint from the same Ollama host configuration, normalizing local bind addresses as needed, before falling back to this default.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Duplicate of the round-1 finding on the same line — deliberately deferred and tracked in #29 (with the loopback-only guard). This slice stays pinned to the zero-setup default.
| let body: OllamaMeResponse = response | ||
| .json() | ||
| .map_err(|_| UsageProviderError::ParseFailed)?; |
There was a problem hiding this comment.
Treat empty /api/me bodies as signed out
If the daemon responds successfully but with no account payload, such as null or an omitted plan while the local install is not signed in, this deserialization path turns it into parse_failed. That shows “Usage data could not be parsed” instead of the intended Ollama sign-in guidance and also makes the provider look broken; handle an empty/missing plan response as login_required before treating malformed JSON as a parser drift.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3b8d5d42c8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| {loggingIn === "ollama" ? "Opening…" : "Sign in to Ollama"} | ||
| </button> | ||
| <p class="hint"> | ||
| Reads the session and weekly limits from your signed-in ollama.com page — no API key, | ||
| nothing leaves this machine except the page load. | ||
| The local daemon reports only your plan. Usage-limit readings are not available yet, and |
There was a problem hiding this comment.
Gate the Ollama sign-in button on web readings
With the new local-daemon copy/default, this Ollama section is visible on fresh configs while providers.webEnabled is still false. The button immediately above still calls startProviderLogin("ollama"), and that backend only prepares a managed browser profile when web readings are enabled, so clicking “Sign in to Ollama” takes the managed-login-unavailable path and tells the user to enable web readings instead of the required ollama signin. Hide/disable this browser sign-in affordance unless web readings are enabled, or replace it with CLI guidance for the local daemon.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| function sourceLabel(snapshot: UsageSnapshot) { | ||
| return snapshot.details.via === "daemon" ? "Local daemon" : sourceLabels[snapshot.source]; |
There was a problem hiding this comment.
Label Ollama daemon failures as daemon checks
When the local Ollama provider fails (for example, daemon stopped or signed out), the error snapshot still has providerId: "ollama.local" but does not include details.via = "daemon", so this helper falls back to sourceLabels.local and displays those new daemon states as “Local estimate.” That is misleading for the not_configured/login_required scenarios because no local estimate was produced; key this label off the Ollama local provider as well, or add the daemon marker to its error snapshots.
Useful? React with 👍 / 👎.
| if error.is_connect() || error.is_timeout() { | ||
| UsageProviderError::NotConfigured |
There was a problem hiding this comment.
Report hung Ollama daemon requests as timeouts
When the Ollama daemon accepts the connection but does not answer within the 3s client timeout, reqwest reports is_timeout() and this branch maps it to NotConfigured. The frontend special-cases that status as “Ollama isn't running,” which sends users down the wrong path for a running but hung or overloaded daemon; return TimedOut for timeout errors and reserve NotConfigured for connection failures.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de5c7af116
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| fn map_status_error(status: reqwest::StatusCode) -> UsageProviderError { | ||
| match status.as_u16() { | ||
| 401 | 403 => UsageProviderError::LoginRequired, | ||
| _ => UsageProviderError::ParseFailed, |
There was a problem hiding this comment.
Map Ollama account outages as unavailable
When a signed-in daemon cannot verify the account or plan, Ollama's /api/me handler returns a 503 account unavailable response rather than malformed JSON; this catch-all turns that into parse_failed, so users see “Usage data could not be parsed” during offline/provider-outage cases instead of an unavailable/network status. Please map 5xx responses, at least 503, to NetworkUnavailable or another unavailable state instead of ParseFailed.
Useful? React with 👍 / 👎.
| - **Usage requests stay provider-only.** To compute real remaining quota, PickGauge calls the same official endpoints the CLIs use — `chatgpt.com/backend-api/codex/usage` and `api.anthropic.com/api/oauth/usage`, plus the providers' OAuth refresh endpoints. No usage measurements, project files, account data, tokens, or provider responses are sent to Pickforge or third-party analytics services. | ||
| - **Anonymous crash reports.** Crash and error reporting is on by default in release builds and can be turned off in Settings → Crash reports. Reports go to Sentry with crash stack traces, OS version, and app version. Native crash dumps include a snapshot of process memory, which may contain fragments of recent in-memory data. Reports never intentionally include usage measurements, project files, or personal data; the hostname is stripped. Crash reporting is disabled in development builds unless `PICKGAUGE_SENTRY_DEBUG=1` is set. | ||
| - **Web reads are opt-in and isolated.** Browser-based reading of the official usage pages is disabled by default. When enabled, it runs only in dedicated, app-owned browser profiles (under `com.pickforge.pickgauge`) that you log into yourself — never your personal browser, never a shared cookie jar. | ||
| - **Ollama plan reads stay local.** When Ollama is enabled, PickGauge sends a read-only request to the local daemon at `http://localhost:11434/api/me`. It retains only the returned plan name; account IDs, email addresses, names, and avatars are never stored, logged, or included in snapshots. |
There was a problem hiding this comment.
Clarify that Ollama plan checks can leave the machine
This privacy note says the Ollama plan read “stay[s] local”, but the local daemon's /api/me implementation calls https://ollama.com/api/me to resolve the signed-in user and plan, so enabling this default provider can still trigger provider-network traffic via the daemon. That mismatch is important for privacy/offline expectations; please reword this to say PickGauge only talks to the local daemon but the daemon may contact Ollama, or avoid the remote-backed check unless the user opts in.
Useful? React with 👍 / 👎.
|
@codex review |
de5c7af to
c563f70
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c563f7088f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| UsageSource, | ||
| }; | ||
|
|
||
| const DEFAULT_BASE_URL: &str = "http://localhost:11434"; |
There was a problem hiding this comment.
Respect configured Ollama hosts
When a user runs Ollama with the documented OLLAMA_HOST setting pointed at a non-default local address or port (for example Docker/WSL or 127.0.0.1:11500), the Ollama CLI can be working while this provider still always posts to localhost:11434; those installs will be reported as not_configured/“Ollama isn't running” and the new zero-setup card never works. Resolve the daemon base URL from OLLAMA_HOST or app config before falling back to the default.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Third occurrence of this finding — deliberately deferred, tracked in #29 (loopback-only OLLAMA_HOST resolution). The failure mode for non-default hosts is an honest "isn't running" state, never wrong data.
| @@ -319,12 +325,12 @@ | |||
| </span> | |||
| </header> | |||
|
|
|||
There was a problem hiding this comment.
Handle Grok errors without an empty gauge area
When the Grok CLI auth is missing or expired, the backend emits a Grok snapshot with no details.plan and no percentages. This new condition falls through to the gauge branch, but windowsFor intentionally returns no rows for Grok, so the card shows a blank gauge area instead of the previous “Plan unavailable” row plus the login note. Treat Grok no-plan snapshots as a plan-unavailable state or render an explicit unavailable row.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b78577b: planOnly() now falls back to "Plan unavailable" for Grok snapshots without a plan, restoring the pre-rebase card (plan row + login note) instead of a blank gauge area.
| - **Usage requests stay provider-only.** To compute real remaining quota or detect a plan, PickGauge calls the same official endpoints the CLIs use — `chatgpt.com/backend-api/codex/usage`, `api.anthropic.com/api/oauth/usage`, and one authenticated `GET grok.com/rest/subscriptions`. Grok never makes an OAuth refresh request. No usage measurements, project files, account data, tokens, or provider responses are sent to Pickforge or third-party analytics services. | ||
| - **Anonymous crash reports.** Crash and error reporting is on by default in release builds and can be turned off in Settings → Crash reports. Reports go to Sentry with crash stack traces, OS version, and app version. Native crash dumps include a snapshot of process memory, which may contain fragments of recent in-memory data. Reports never intentionally include usage measurements, project files, or personal data; the hostname is stripped. Crash reporting is disabled in development builds unless `PICKGAUGE_SENTRY_DEBUG=1` is set. | ||
| - **Web reads are opt-in and isolated.** Browser-based reading of the official usage pages is disabled by default. When enabled, it runs only in dedicated, app-owned browser profiles (under `com.pickforge.pickgauge`) that you log into yourself — never your personal browser, never a shared cookie jar. | ||
| - **Ollama plan reads use your local daemon.** When Ollama is enabled, PickGauge sends a read-only request only to the local daemon at `http://localhost:11434/api/me` and never contacts ollama.com itself. The daemon resolves the account with ollama.com using its own credentials. PickGauge retains only the returned plan name; account IDs, email addresses, names, and avatars are never stored, logged, or included in snapshots. |
There was a problem hiding this comment.
Clarify Ollama web reads in the privacy note
With Official web readings enabled, the refresh path still calls the Ollama web provider (refresh_web_provider_headless(..., Service::Ollama) in src-tauri/src/lib.rs), so PickGauge can contact ollama.com via the sidecar even when this new paragraph says it “never contacts ollama.com itself.” This privacy guarantee should be scoped to the local-daemon provider or the web path should be disabled for Ollama.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b78577b: the privacy bullet now scopes the no-ollama.com claim to the zero-setup daemon read and explicitly notes that the separate opt-in web readings feature does load ollama.com in the isolated profile.
Adds the
ollama.localzero-setup provider: PickGauge asks the running local Ollama daemon (POST http://localhost:11434/api/me) for the signed-in account's plan and shows a plan-only card — no cookies, no browser, nothing leaves the machine.Refs #26 (PR 2 of the 4-PR plan; PR 1 = Grok zero-setup is in flight in parallel, PR 3 web full-gauges and PR 4 headless export remain).
What changed
src-tauri/src/ollama_provider.rscollector;UsageProviderId::OllamaLocal(ollama.local); registered when Ollama + local readings are enabled.confidence: medium,details.planonly — identity fields (email/name/id/avatar) are dropped, never stored or logged.not_configured("Ollama isn't running"), signed out →login_required.enabledServices.ollamaflips totruefor fresh configs; existing saved configs keep their value.Tested
cargo test --locked --all-targets: 232 passed, 1 ignored (live-daemon test — run separately, passed against the real daemon, planpro).bun run checkclean;bun run test: 61 + 15 sidecar passed.Not tested yet
Risks
/api/meis undocumented daemon surface; if its shape drifts we fail asparse_failed, honestly surfaced. Follow-up planned if Cloud usage stats. ollama/ollama#12532 ships a real usage API.