diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 8786c137..1dcafe3a 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -19492,6 +19492,7 @@ function App(): React.JSX.Element { geminiAuthStatus={geminiAuthStatus} cursorProviderAvailable={cursorProviderAvailable} grokProviderAvailable={grokProviderAvailable} + usageSummary={usageSummary} themeAppearance={appearance.themeAppearance || 'system'} composerStyle={appearance.composerStyle || 'default'} userBubbleColor={appearance.userBubbleColor || 'system'} diff --git a/src/renderer/src/assets/css/08-theme-picker-overrides.css b/src/renderer/src/assets/css/08-theme-picker-overrides.css index 6aa7ec56..40fe98a8 100644 --- a/src/renderer/src/assets/css/08-theme-picker-overrides.css +++ b/src/renderer/src/assets/css/08-theme-picker-overrides.css @@ -1562,6 +1562,12 @@ border-color: color-mix(in srgb, #f85149 28%, transparent); background: color-mix(in srgb, #f85149 4%, rgba(255, 255, 255, 0.02)); } +/* Signed in but rate-limited at ~100%. Reads hotter than "CLI not + * found" (deeper red fill) so a quota wall is unmistakable. */ +.first-launch-sheet-provider-card-out-of-usage { + border-color: color-mix(in srgb, #f85149 48%, transparent); + background: color-mix(in srgb, #f85149 8%, rgba(255, 255, 255, 0.02)); +} .first-launch-sheet-provider-card-deemphasised { opacity: 0.65; } @@ -1680,6 +1686,136 @@ background: #f85149; box-shadow: 0 0 0 3px color-mix(in srgb, #f85149 22%, transparent); } +.first-launch-sheet-provider-status-dot-out-of-usage { + background: #f85149; + box-shadow: 0 0 0 3px color-mix(in srgb, #f85149 30%, transparent); +} +/* §1 status-dot legend — decodes the dots so a red dot isn't misread + * as one single failure (onboarding-review 3-facet ask). */ +.first-launch-sheet-status-legend { + list-style: none; + margin: 8px 0 0; + padding: 0; + display: flex; + flex-wrap: wrap; + gap: 6px 16px; + font-size: 0.78em; + color: var(--text-tertiary); +} +.first-launch-sheet-status-legend li { + display: flex; + align-items: center; + gap: 6px; +} +/* Quota bar shown inside an "out of usage" provider card. */ +.first-launch-sheet-provider-card-usage { + margin: 2px 0 0; +} +/* §4 "You stay in control" — safety mock (approval options + presets). */ +.first-launch-sheet-safety { + display: flex; + flex-direction: column; + gap: 10px; + margin: 4px 0 2px; +} +.first-launch-sheet-safety-block { + display: flex; + flex-direction: column; + gap: 6px; +} +.first-launch-sheet-safety-label { + font-size: 0.74em; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-tertiary); +} +.first-launch-sheet-safety-chips { + display: flex; + flex-wrap: wrap; + gap: 6px; +} +.first-launch-sheet-safety-chip { + font-size: 0.8em; + padding: 3px 10px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(255, 255, 255, 0.04); + color: var(--text-secondary); +} +.first-launch-sheet-safety-chip.danger { + border-color: color-mix(in srgb, #f85149 38%, transparent); + color: color-mix(in srgb, #f85149 82%, var(--text-primary)); +} +/* §5 "Track your usage & spend" — illustrative usage mock. */ +.first-launch-sheet-usage-mock { + display: flex; + flex-direction: column; + gap: 8px; + margin: 6px 0 4px; + padding: 10px 12px; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.02); +} +.first-launch-sheet-usage-mock-row { + display: grid; + grid-template-columns: 54px 1fr 38px; + align-items: center; + gap: 10px; +} +.first-launch-sheet-usage-mock-label { + font-size: 0.82em; + color: var(--text-secondary); +} +.first-launch-sheet-usage-mock-pct { + font-size: 0.78em; + color: var(--text-tertiary); + text-align: right; + font-variant-numeric: tabular-nums; +} +/* Official CLI install commands — onboarding §1 + Settings → Providers. */ +.provider-install-commands { + display: flex; + flex-direction: column; + gap: 6px; + margin-top: 8px; +} +.provider-install-row { + display: grid; + grid-template-columns: 60px minmax(0, 1fr) auto; + align-items: center; + gap: 8px; +} +.provider-install-label { + font-size: 0.82em; + font-weight: 600; + color: var(--text-secondary); +} +.provider-install-cmd { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 0.76em; + padding: 4px 8px; + border-radius: 6px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.08); + color: var(--text-primary); + overflow-x: auto; + white-space: nowrap; +} +.provider-install-copy { + flex-shrink: 0; +} +.first-launch-sheet-install, +.settings-provider-install { + margin-top: 10px; +} +.first-launch-sheet-install > summary, +.settings-provider-install > summary { + cursor: pointer; + font-size: 0.84em; + color: var(--text-secondary); + user-select: none; +} .first-launch-sheet-provider-card-description { margin: 0; font-size: 0.85em; @@ -1892,6 +2028,12 @@ .first-launch-sheet-ensemble-chip[data-provider='kimi'] { border-color: color-mix(in srgb, var(--provider-kimi-color) 46%, transparent); } +.first-launch-sheet-ensemble-chip[data-provider='cursor'] { + border-color: color-mix(in srgb, var(--provider-cursor-color) 46%, transparent); +} +.first-launch-sheet-ensemble-chip[data-provider='grok'] { + border-color: color-mix(in srgb, var(--provider-grok-color) 46%, transparent); +} .first-launch-sheet-ensemble-arrow { color: var(--text-tertiary); font-size: 0.9em; diff --git a/src/renderer/src/components/FirstLaunchSheet.test.tsx b/src/renderer/src/components/FirstLaunchSheet.test.tsx index 2dee51f1..498870d1 100644 --- a/src/renderer/src/components/FirstLaunchSheet.test.tsx +++ b/src/renderer/src/components/FirstLaunchSheet.test.tsx @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest' import { renderToStaticMarkup } from 'react-dom/server' import { FirstLaunchSheet } from './FirstLaunchSheet' import type { GeminiAuthStatus, ProviderApiKeyStatus } from '../../../main/store/types' +import type { ModelUsageAggregate } from '../App' /** * Server-rendered smoke tests for FirstLaunchSheet. The component @@ -91,8 +92,10 @@ describe('FirstLaunchSheet', () => { expect(html).toContain('1. Sign in to your providers') expect(html).toContain('2. Add your first workspace') expect(html).toContain('3. Choose your starting look') - expect(html).toContain('4. Try Ensemble chats') - expect(html).toContain('5. Power-user shortcuts') + expect(html).toContain('4. You stay in control') + expect(html).toContain('5. Track your usage') + expect(html).toContain('6. Try Ensemble chats') + expect(html).toContain('7. Power-user shortcuts') }) it('renders the Appearance preference controls and preview surfaces', () => { @@ -136,6 +139,9 @@ describe('FirstLaunchSheet', () => { expect(html).toContain('data-provider="claude"') expect(html).toContain('data-provider="gemini"') expect(html).toContain('data-provider="kimi"') + // Cursor + Grok complete the 6-provider roster (the chips use ). + expect(html).toContain('Cursor') + expect(html).toContain('Grok') expect(html).toContain('Turn / Continuous in the composer') }) @@ -253,6 +259,68 @@ describe('FirstLaunchSheet', () => { expect(html).toContain('Usage credential missing') }) + it('flips a signed-in provider to "out of usage" when its quota window is maxed', () => { + const usageSummary = [ + { + provider: 'codex', + model: 'usage limits', + windows: [ + { + id: 'weekly', + label: 'Weekly', + limitLabel: 'Weekly limit', + usedPercent: 100, + resetAt: '2999-01-01T09:30:00.000Z' + } + ] + } + ] as unknown as ModelUsageAggregate[] + const html = renderToStaticMarkup( + {}} + onOpenSettings={() => {}} + codexStatus={{ available: true, codexUsage: { planType: 'pro', userId: 'u1' } }} + claudeAuthStatus={null} + kimiAuthStatus={null} + geminiAuthStatus={null} + usageSummary={usageSummary} + /> + ) + // Codex was "signed in (pro)" but the maxed window flips it to the + // explicit out-of-usage state: status text + card variant + quota bar. + // (Assert the CARD class, not the dot class — the §1 legend always + // renders an out-of-usage dot, so the dot class is not card-specific.) + expect(html).toContain('100% used') + expect(html).toContain('first-launch-sheet-provider-card-out-of-usage') + expect(html).toContain('quota-progress-bar') + }) + + it('keeps a signed-in provider signed-in when usage is below 100%', () => { + const usageSummary = [ + { + provider: 'codex', + model: 'usage limits', + windows: [{ id: 'weekly', label: 'Weekly', limitLabel: 'Weekly limit', usedPercent: 40 }] + } + ] as unknown as ModelUsageAggregate[] + const html = renderToStaticMarkup( + {}} + onOpenSettings={() => {}} + codexStatus={{ available: true, codexUsage: { planType: 'pro', userId: 'u1' } }} + claudeAuthStatus={null} + kimiAuthStatus={null} + geminiAuthStatus={null} + usageSummary={usageSummary} + /> + ) + expect(html).toContain('Signed in (pro)') + // No CARD should be out-of-usage at 40% (the legend's dot doesn't count). + expect(html).not.toContain('first-launch-sheet-provider-card-out-of-usage') + }) + it('Claude card surfaces "signed in" when apiKeyConfigured is true', () => { const html = renderToStaticMarkup( { expect(html).toContain('Skip for now') expect(html).toContain('Got it') }) + + it('renders official CLI install commands for newcomers', () => { + const html = renderToStaticMarkup( + {}} + onOpenSettings={() => {}} + codexStatus={null} + claudeAuthStatus={null} + kimiAuthStatus={null} + geminiAuthStatus={null} + /> + ) + expect(html).toContain('npm i -g @openai/codex') + expect(html).toContain('https://claude.ai/install.sh') + expect(html).toContain('https://code.kimi.com/install.sh') + expect(html).toContain('Official install commands') + }) }) diff --git a/src/renderer/src/components/FirstLaunchSheet.tsx b/src/renderer/src/components/FirstLaunchSheet.tsx index 8edc68a8..07ef8a90 100644 --- a/src/renderer/src/components/FirstLaunchSheet.tsx +++ b/src/renderer/src/components/FirstLaunchSheet.tsx @@ -16,6 +16,13 @@ import { } from '../lib/providerAuthSummary' import agbenchGhostMark from '../assets/agbench-ghost-mark.svg' import { ProviderGlyph } from './icons/ProviderGlyph' +// 1.0.7-EW — onboarding "out of usage" card state. ModelUsageAggregate is the +// same per-provider quota shape the sidebar Model Usage card consumes; type-only +// import so there's no runtime cycle with App.tsx (mirrors ModelUsageCard). +import type { ModelUsageAggregate } from '../App' +import { formatResetShort } from '../lib/UsageFormat' +import { QuotaProgressBar } from './QuotaProgressBar' +import { ProviderInstallCommands } from './ProviderInstallCommands' type OnboardingProviderId = 'codex' | 'claude' | 'gemini' | 'kimi' | 'cursor' | 'grok' @@ -91,6 +98,12 @@ export interface FirstLaunchSheetProps { * Optional so older hosts / static tests can omit them. */ cursorProviderAvailable?: boolean grokProviderAvailable?: boolean + /** Per-provider quota aggregates (App.tsx#usageSummary). Lets a + * signed-in provider card flip to an explicit "out of usage" state + * when its window hits ~100% — otherwise a rate-limited provider + * just reads as "signed in" and the wall looks like a bug. Optional + * so static tests / older hosts can omit it. */ + usageSummary?: ModelUsageAggregate[] /** Appearance controls are optional so static tests and older hosts * can render the sheet without wiring the preference preview. */ themeAppearance?: ThemeAppearance @@ -115,10 +128,72 @@ interface ProviderRowSpec { deemphasised?: boolean /** When true, the card is marked optional but still actionable. */ optional?: boolean + /** Set when the provider is signed in but its quota window is at + * ~100% — drives the "out of usage" card treatment + progress bar. */ + usage?: { fraction: number; resetAt?: string } } const SHEET_TITLE_ID = 'first-launch-sheet-title' +/** A provider window counts as "out of usage" once its used fraction + * reaches ~100% — at that point the provider rate-limits runs, so the + * card must say so instead of a bare "signed in". 0.999 (not 1.0) + * absorbs float noise from `usedPercent / 100`. */ +const OUT_OF_USAGE_FRACTION = 0.999 + +/** + * Worst (most-consumed) quota window for a provider, derived from the + * same `usageSummary` the sidebar Model Usage card reads. Mirrors + * ModelUsageCard's `fillFractionForWindow`: prefer the honest + * `usedPercent`, fall back to `1 - remainingPercent`. Returns null when + * the provider has no quota data (Cursor/Grok never do; the others only + * after a usage probe). Kept local to avoid a runtime import cycle with + * App.tsx — the duplication is 4 lines. + */ +function worstProviderUsage( + usageSummary: ModelUsageAggregate[] | undefined, + providerId: OnboardingProviderId +): { fraction: number; resetAt?: string } | null { + if (!usageSummary || usageSummary.length === 0) return null + const entry = usageSummary.find( + (e) => e.provider === providerId && e.model === 'usage limits' && (e.windows?.length || 0) > 0 + ) + if (!entry?.windows) return null + let worst: { fraction: number; resetAt?: string } | null = null + for (const w of entry.windows) { + const used = Number.isFinite(w.usedPercent) + ? Math.max(0, Math.min(1, (w.usedPercent as number) / 100)) + : Number.isFinite(w.remainingPercent) + ? Math.max(0, Math.min(1, 1 - (w.remainingPercent as number) / 100)) + : 0 + if (!worst || used > worst.fraction) worst = { fraction: used, resetAt: w.resetAt } + } + return worst +} + +/** + * Flip a signed-in provider row to the "out of usage" state when its + * worst quota window is at ~100%. No-op for every other variant (you + * can't be "out of usage" if you were never signed in) and when there's + * no quota data — so tests/hosts that omit `usageSummary` are unchanged. + */ +function applyOutOfUsage( + row: ProviderRowSpec, + usageSummary: ModelUsageAggregate[] | undefined +): ProviderRowSpec { + if (row.variant !== 'signed-in') return row + const worst = worstProviderUsage(usageSummary, row.id) + if (!worst || worst.fraction < OUT_OF_USAGE_FRACTION) return row + const reset = formatResetShort({ resetAt: worst.resetAt }) + return { + ...row, + variant: 'out-of-usage', + statusText: reset ? `100% used · resets ${reset}` : '100% used', + hint: 'Signed in, but rate-limited right now — wait for the reset, switch provider, or switch model. This is a quota wall, not a bug.', + usage: worst + } +} + const ONBOARDING_THEME_OPTIONS: Array<{ value: ThemeAppearance; label: string }> = [ { value: 'system', label: 'System' }, { value: 'dark', label: 'Dark' }, @@ -294,6 +369,7 @@ export function FirstLaunchSheet({ geminiAuthStatus, cursorProviderAvailable = false, grokProviderAvailable = false, + usageSummary, themeAppearance = 'system', composerStyle = 'default', userBubbleColor = 'system', @@ -337,7 +413,7 @@ export function FirstLaunchSheet({ ) const composerPreview = getOnboardingComposerPreview(composerStyle) - const providerRows: ProviderRowSpec[] = [ + const baseProviderRows: ProviderRowSpec[] = [ { id: 'codex', label: 'Codex', @@ -387,6 +463,9 @@ export function FirstLaunchSheet({ optional: true } ] + // Flip any signed-in provider whose quota window is maxed to the + // explicit "out of usage" state — the tester-confusion fix. + const providerRows = baseProviderRows.map((row) => applyOutOfUsage(row, usageSummary)) return (

AGBench is a multi-provider AI CLI manager. It wraps Codex,{' '} - Claude, Gemini, and Kimi inside one - consistent chrome so you can compare runs side-by-side in the same UI. Each provider - keeps its own auth — sign in to the ones you want to use, skip the rest. + Claude, Gemini, Kimi,{' '} + Cursor, and Grok inside one consistent chrome so you + can run and compare them side-by-side in the same UI. Each provider keeps its own auth — + sign in to the ones you want to use, skip the rest.

1. Sign in to your providers

- Status reflects what AGBench can see right now. Open Settings for inline sign-in flows - (OAuth, API keys, CLI paths). + Status reflects what AGBench can see right now. A red dot can mean two different things + — read the label. Open Settings for inline sign-in flows (OAuth, API keys, CLI paths). +

+
    +
  • + + Ready +
  • +
  • + + Installed · not signed in +
  • +
  • + + CLI not found · install it +
  • +
  • + + Signed in · out of usage (resets later) +
  • +
+

+ Providers sign in three ways: Codex, Cursor, and{' '} + Grok log in through their own CLI in a Terminal;{' '} + Claude and Gemini use in-app OAuth or an API key;{' '} + Kimi takes an API key. AGBench can't see Cursor's or + Grok's CLI login, so those two dots stay amber even after you sign in — that's + expected.

{providerRows.map((row) => ( @@ -463,6 +581,14 @@ export function FirstLaunchSheet({ /> ))}
+
+ Don't have a CLI yet? Official install commands +

+ Run one in your terminal, then come back and sign in. (npm commands need Node 20+; the + curl installers are self-contained.) +

+ +
@@ -722,10 +848,68 @@ export function FirstLaunchSheet({
-

4. Try Ensemble chats

+

4. You stay in control

- New Ensemble puts multiple provider participants in one shared transcript. Turn mode - keeps one active speaker at a time; Continuous mode lets the panel keep moving. Hit the + Agents ask before doing anything risky — those prompts are the safety feature, not + errors. You decide how much rope each run gets. +

+
+
+ When an agent wants to act +
+ Allow once + In this workspace + For this session + Deny +
+
+
+ Start cautious, dial up +
+ Plan / read-only + Default approval + Full workspace access +
+
+
+

+ Runs are sandboxed to the workspace you grant — files outside the project are off-limits + unless you allow a path. Set the mode per run with the composer's coloured + permission chip. +

+
+ +
+

5. Track your usage & spend

+

+ Hit a wall mid-run? It's almost always a provider quota, not a bug. The{' '} + Model Usage card in the sidebar shows how much of each provider's + quota you've used and when it resets. +

+
+
+ Codex + + 78% +
+
+ Claude + + 42% +
+
+

+ Every run also shows a live token + cost tally next to Send, and the dashboard fills in + usage heatmaps and per-provider totals as you go. +

+
+ +
+

6. Try Ensemble chats

+

+ Get one provider working first — Ensemble shines with two or more. New + Ensemble puts multiple provider participants in one shared transcript. Turn mode keeps + one active speaker at a time; Continuous mode lets the panel keep moving. Hit the Work Session button in the composer to run a supervised multi-round autonomy session with one of five presets (One-shot review · Architecture panel · Scout pass · Implementation review · Long-running work session). @@ -757,6 +941,20 @@ export function FirstLaunchSheet({ Reviewer Kimi + + → + + + Editor + Cursor + + + → + + + Scout + Grok +

+ New → New Ensemble @@ -766,11 +964,12 @@ export function FirstLaunchSheet({
-

5. Power-user shortcuts (optional)

+

7. Power-user shortcuts (optional)

  • - @ to reference files. Type @ in the composer to mention - a specific file by path. The agent will read it as part of the turn. + -@ to reference files. Type -@ in the composer to + mention a specific file by path; the agent reads it as part of the turn. Plain{' '} + @ now mentions a sub-agent or Ensemble participant.
  • / for slash commands. Type / at the start of the @@ -782,9 +981,9 @@ export function FirstLaunchSheet({ K for the global command palette.
  • - Permission picker colour-codes the mode. Plan = blue, Default = - neutral, Auto-edit = orange. Read it before you hit Enter so you know how much freedom - the agent has. + Permission picker colour-codes the mode. Plan = blue (read-only), + Default = neutral, Full Workspace Access / Auto-edit = red (can edit files). Read it + before you hit Enter so you know how much freedom the agent has.
  • Fast Mode toggle. Inside the model picker, capable models (Codex @@ -888,6 +1087,14 @@ function ProviderCard({ /> {row.statusText}
+ {row.variant === 'out-of-usage' && row.usage && ( +
+ +
+ )}

{row.description}

{row.hint}

diff --git a/src/renderer/src/components/ProviderInstallCommands.tsx b/src/renderer/src/components/ProviderInstallCommands.tsx new file mode 100644 index 00000000..61f89268 --- /dev/null +++ b/src/renderer/src/components/ProviderInstallCommands.tsx @@ -0,0 +1,93 @@ +import { useState, type ReactElement } from 'react' + +interface ProviderInstallEntry { + id: string + label: string + command: string + /** The vendor the command comes from — shown on hover so a newcomer + * can confirm it's the official source, not a third-party script. */ + source: string +} + +/** + * Official, copy-pasteable CLI install commands — one per provider, each + * taken from that vendor's own published install docs. Surfaced in BOTH + * the first-launch onboarding sheet and Settings → Providers so people + * who live in ChatGPT/Claude.ai and have never touched a terminal can + * get a CLI installed without hunting through six different doc sites. + * + * Keep these in sync with the vendors' official install pages: + * Codex — OpenAI: npm i -g @openai/codex (developers.openai.com/codex/cli) + * Claude — Anthropic: curl -fsSL https://claude.ai/install.sh | bash (code.claude.com/docs/en/setup) + * Gemini — Google: npm i -g @google/gemini-cli (geminicli.com/docs) + * Kimi — Moonshot: curl -LsSf https://code.kimi.com/install.sh (moonshotai.github.io/kimi-cli) + * Cursor — Cursor: curl https://cursor.com/install -fsS | bash (cursor.com/docs/cli/installation) + * Grok — xAI: curl -fsSL https://x.ai/cli/install.sh | bash (x.ai/cli) + * (npm commands need Node 20+; the curl installers are self-contained.) + */ +const PROVIDER_INSTALL_COMMANDS: ProviderInstallEntry[] = [ + { id: 'codex', label: 'Codex', command: 'npm i -g @openai/codex', source: 'OpenAI' }, + { + id: 'claude', + label: 'Claude', + command: 'curl -fsSL https://claude.ai/install.sh | bash', + source: 'Anthropic' + }, + { id: 'gemini', label: 'Gemini', command: 'npm i -g @google/gemini-cli', source: 'Google' }, + { + id: 'kimi', + label: 'Kimi', + command: 'curl -LsSf https://code.kimi.com/install.sh | bash', + source: 'Moonshot' + }, + { + id: 'cursor', + label: 'Cursor', + command: 'curl https://cursor.com/install -fsS | bash', + source: 'Cursor' + }, + { + id: 'grok', + label: 'Grok', + command: 'curl -fsSL https://x.ai/cli/install.sh | bash', + source: 'xAI' + } +] + +/** + * Rows of copyable official install commands. Pure presentation + + * clipboard; the host decides whether to wrap it in a
(we do + * in both call sites to keep the surfaces tidy by default). + */ +export function ProviderInstallCommands(): ReactElement { + const [copiedId, setCopiedId] = useState(null) + + const copy = (entry: ProviderInstallEntry): void => { + void navigator.clipboard?.writeText(entry.command) + setCopiedId(entry.id) + // Transient "Copied" confirmation; clears only if this row is still + // the one showing it (so a quick second copy doesn't flash-clear). + window.setTimeout(() => setCopiedId((cur) => (cur === entry.id ? null : cur)), 1500) + } + + return ( +
+ {PROVIDER_INSTALL_COMMANDS.map((entry) => ( +
+ {entry.label} + + {entry.command} + + +
+ ))} +
+ ) +} diff --git a/src/renderer/src/components/SettingsPanel.tsx b/src/renderer/src/components/SettingsPanel.tsx index 4967aa39..87015cfb 100644 --- a/src/renderer/src/components/SettingsPanel.tsx +++ b/src/renderer/src/components/SettingsPanel.tsx @@ -66,6 +66,7 @@ import { UpdateStatusPane } from './UpdateStatusPane' import { ModelUsageCard } from './ModelUsageCard' import { GrokTelemetryCard } from './GrokTelemetryCard' import { ProviderLogoTile } from './ProviderLogoTile' +import { ProviderInstallCommands } from './ProviderInstallCommands' import type { ModelUsageAggregate } from '../App' import { AGENTBENCH_MCP_TOOLS, type AGBenchMcpToolName } from '../../../main/AgentbenchMcpTools' @@ -2900,9 +2901,9 @@ export function SettingsPanel({ Kimi classifier retry pass

- When enabled, Kimi content-filter retries can escalate from keyword redaction to - a local sentence classifier. Current state: {kimiClassifierStatus}. If disabled - or unavailable, retries stay keyword-only and the failure diagnostic says so. + When enabled, Kimi content-filter retries can escalate from keyword redaction to a + local sentence classifier. Current state: {kimiClassifierStatus}. If disabled or + unavailable, retries stay keyword-only and the failure diagnostic says so.

+
+ Need to install a CLI? Official commands +

+ Run one in your terminal, then sign in below. (npm commands need Node 20+; the + curl installers are self-contained.) +

+ +