mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 07:03:25 +00:00
feat: show (sub) indicator in footer when using OAuth subscription
This commit is contained in:
parent
8cd3151c2a
commit
bc838b021d
4 changed files with 69 additions and 4 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **OAuth Login Status Indicator**: The `/login` provider selector now shows "✓ logged in" next to providers where you're already authenticated. This makes it clear at a glance whether you're using your Claude Pro/Max subscription. ([#88](https://github.com/badlogic/pi-mono/pull/88) by [@steipete](https://github.com/steipete))
|
- **OAuth Login Status Indicator**: The `/login` provider selector now shows "✓ logged in" next to providers where you're already authenticated. This makes it clear at a glance whether you're using your Claude Pro/Max subscription. ([#88](https://github.com/badlogic/pi-mono/pull/88) by [@steipete](https://github.com/steipete))
|
||||||
|
- **Subscription Cost Indicator**: The footer now shows "(sub)" next to the cost when using an OAuth subscription (e.g., `$0.123 (sub)`). This makes it visible without needing `/login` that you're using your Claude Pro/Max subscription.
|
||||||
|
|
||||||
## [0.11.5] - 2025-12-01
|
## [0.11.5] - 2025-12-01
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import AjvModule from "ajv";
|
||||||
import { existsSync, readFileSync } from "fs";
|
import { existsSync, readFileSync } from "fs";
|
||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { getOAuthToken } from "./oauth/index.js";
|
import { getOAuthToken, type SupportedOAuthProvider } from "./oauth/index.js";
|
||||||
|
import { loadOAuthCredentials } from "./oauth/storage.js";
|
||||||
|
|
||||||
// Handle both default and named exports
|
// Handle both default and named exports
|
||||||
const Ajv = (AjvModule as any).default || AjvModule;
|
const Ajv = (AjvModule as any).default || AjvModule;
|
||||||
|
|
@ -292,3 +293,56 @@ export function findModel(provider: string, modelId: string): { model: Model<Api
|
||||||
const model = allModels.find((m) => m.provider === provider && m.id === modelId) || null;
|
const model = allModels.find((m) => m.provider === provider && m.id === modelId) || null;
|
||||||
return { model, error: null };
|
return { model, error: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping from model provider to OAuth provider ID.
|
||||||
|
* Only providers that support OAuth are listed here.
|
||||||
|
*/
|
||||||
|
const providerToOAuthProvider: Record<string, SupportedOAuthProvider> = {
|
||||||
|
anthropic: "anthropic",
|
||||||
|
// Add more mappings as OAuth support is added for other providers
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cache for OAuth status per provider (avoids file reads on every render)
|
||||||
|
const oauthStatusCache: Map<string, boolean> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the OAuth status cache.
|
||||||
|
* Call this after login/logout operations.
|
||||||
|
*/
|
||||||
|
export function invalidateOAuthCache(): void {
|
||||||
|
oauthStatusCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a model is using OAuth credentials (subscription).
|
||||||
|
* This checks if OAuth credentials exist and would be used for the model,
|
||||||
|
* without actually fetching or refreshing the token.
|
||||||
|
* Results are cached until invalidateOAuthCache() is called.
|
||||||
|
*/
|
||||||
|
export function isModelUsingOAuth(model: Model<Api>): boolean {
|
||||||
|
const oauthProvider = providerToOAuthProvider[model.provider];
|
||||||
|
if (!oauthProvider) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cache first
|
||||||
|
if (oauthStatusCache.has(oauthProvider)) {
|
||||||
|
return oauthStatusCache.get(oauthProvider)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if OAuth credentials exist for this provider
|
||||||
|
let usingOAuth = false;
|
||||||
|
const credentials = loadOAuthCredentials(oauthProvider);
|
||||||
|
if (credentials) {
|
||||||
|
usingOAuth = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for manual OAuth token env var (for Anthropic)
|
||||||
|
if (!usingOAuth && model.provider === "anthropic" && process.env.ANTHROPIC_OAUTH_TOKEN) {
|
||||||
|
usingOAuth = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
oauthStatusCache.set(oauthProvider, usingOAuth);
|
||||||
|
return usingOAuth;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import type { AssistantMessage } from "@mariozechner/pi-ai";
|
||||||
import { type Component, visibleWidth } from "@mariozechner/pi-tui";
|
import { type Component, visibleWidth } from "@mariozechner/pi-tui";
|
||||||
import { existsSync, type FSWatcher, readFileSync, watch } from "fs";
|
import { existsSync, type FSWatcher, readFileSync, watch } from "fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
import { isModelUsingOAuth } from "../model-config.js";
|
||||||
import { theme } from "../theme/theme.js";
|
import { theme } from "../theme/theme.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -169,7 +170,13 @@ export class FooterComponent implements Component {
|
||||||
if (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);
|
if (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);
|
||||||
if (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);
|
if (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);
|
||||||
if (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);
|
if (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);
|
||||||
if (totalCost) statsParts.push(`$${totalCost.toFixed(3)}`);
|
|
||||||
|
// Show cost with "(sub)" indicator if using OAuth subscription
|
||||||
|
const usingSubscription = this.state.model ? isModelUsingOAuth(this.state.model) : false;
|
||||||
|
if (totalCost || usingSubscription) {
|
||||||
|
const costStr = `$${totalCost.toFixed(3)}${usingSubscription ? " (sub)" : ""}`;
|
||||||
|
statsParts.push(costStr);
|
||||||
|
}
|
||||||
|
|
||||||
// Colorize context percentage based on usage
|
// Colorize context percentage based on usage
|
||||||
let contextPercentStr: string;
|
let contextPercentStr: string;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
import { getChangelogPath, parseChangelog } from "../changelog.js";
|
import { getChangelogPath, parseChangelog } from "../changelog.js";
|
||||||
import { exportSessionToHtml } from "../export-html.js";
|
import { exportSessionToHtml } from "../export-html.js";
|
||||||
import { getApiKeyForModel, getAvailableModels } from "../model-config.js";
|
import { getApiKeyForModel, getAvailableModels, invalidateOAuthCache } from "../model-config.js";
|
||||||
import { listOAuthProviders, login, logout } from "../oauth/index.js";
|
import { listOAuthProviders, login, logout } from "../oauth/index.js";
|
||||||
import type { SessionManager } from "../session-manager.js";
|
import type { SessionManager } from "../session-manager.js";
|
||||||
import type { SettingsManager } from "../settings-manager.js";
|
import type { SettingsManager } from "../settings-manager.js";
|
||||||
|
|
@ -1318,7 +1318,8 @@ export class TuiRenderer {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Success
|
// Success - invalidate OAuth cache so footer updates
|
||||||
|
invalidateOAuthCache();
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(
|
this.chatContainer.addChild(
|
||||||
new Text(theme.fg("success", `✓ Successfully logged in to ${providerId}`), 1, 0),
|
new Text(theme.fg("success", `✓ Successfully logged in to ${providerId}`), 1, 0),
|
||||||
|
|
@ -1335,6 +1336,8 @@ export class TuiRenderer {
|
||||||
try {
|
try {
|
||||||
await logout(providerId);
|
await logout(providerId);
|
||||||
|
|
||||||
|
// Invalidate OAuth cache so footer updates
|
||||||
|
invalidateOAuthCache();
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
this.chatContainer.addChild(new Spacer(1));
|
||||||
this.chatContainer.addChild(
|
this.chatContainer.addChild(
|
||||||
new Text(theme.fg("success", `✓ Successfully logged out of ${providerId}`), 1, 0),
|
new Text(theme.fg("success", `✓ Successfully logged out of ${providerId}`), 1, 0),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue