Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions src/cli/__test__/run-cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import { runCli } from '../run-cli'
import { defaultStatsDisplaySettings } from '../../shared/codex'
import type {
AccountRateLimits,
AppSettings,
Expand Down Expand Up @@ -218,7 +219,8 @@
language: 'zh-CN',
theme: 'light',
checkForUpdatesOnStartup: true,
codexDesktopExecutablePath: ''
codexDesktopExecutablePath: '',
statsDisplay: defaultStatsDisplaySettings()
}
const currentSession: CurrentSessionSummary = {
email: 'one@example.com',
Expand Down Expand Up @@ -887,7 +889,8 @@
language: 'zh-CN',
theme: 'light',
checkForUpdatesOnStartup: true,
codexDesktopExecutablePath: ''
codexDesktopExecutablePath: '',
statsDisplay: defaultStatsDisplaySettings()
},
error: null
})
Expand All @@ -907,7 +910,8 @@
language: 'zh-CN',
theme: 'light',
checkForUpdatesOnStartup: false,
codexDesktopExecutablePath: ''
codexDesktopExecutablePath: '',
statsDisplay: defaultStatsDisplaySettings()
},
error: null
})
Expand All @@ -933,10 +937,32 @@
language: 'zh-CN',
theme: 'light',
checkForUpdatesOnStartup: true,
codexDesktopExecutablePath: 'C:\\\\Program Files\\\\Codex\\\\Codex.exe'
codexDesktopExecutablePath: 'C:\\\\Program Files\\\\Codex\\\\Codex.exe',
statsDisplay: defaultStatsDisplaySettings()
},
error: null
})

logSpy.mockClear()
await expect(runCli(runtime as never, ['settings', 'get', 'statsDisplay', '--json'])).resolves.toBe(0)

Check warning on line 947 in src/cli/__test__/run-cli.test.ts

View workflow job for this annotation

GitHub Actions / quality

Replace `runCli(runtime·as·never,·['settings',·'get',·'statsDisplay',·'--json'])` with `⏎······runCli(runtime·as·never,·['settings',·'get',·'statsDisplay',·'--json'])⏎····`
expect(parseJsonLog(logSpy)).toEqual({
ok: true,
data: defaultStatsDisplaySettings(),
error: null
})

logSpy.mockClear()
await expect(
runCli(runtime as never, ['settings', 'set', 'statsDisplay', 'dailyTrend,accountUsage', '--json'])

Check warning on line 956 in src/cli/__test__/run-cli.test.ts

View workflow job for this annotation

GitHub Actions / quality

Replace `'settings',·'set',·'statsDisplay',·'dailyTrend,accountUsage',·'--json'` with `⏎········'settings',⏎········'set',⏎········'statsDisplay',⏎········'dailyTrend,accountUsage',⏎········'--json'⏎······`
).resolves.toBe(0)
expect(runtime.services.settings.update).toHaveBeenCalledWith({
statsDisplay: {
dailyTrend: true,
modelBreakdown: false,
instanceUsage: false,
accountUsage: true
}
})
})

it('covers doctor command', async () => {
Expand Down Expand Up @@ -985,6 +1011,19 @@
}
})

logSpy.mockClear()
await expect(
runCli(runtime as never, ['settings', 'set', 'statsDisplay', 'wat', '--json'])
).resolves.toBe(2)
expect(parseJsonLog(logSpy)).toEqual({
ok: false,
data: null,
error: {
code: 2,
message: 'statsDisplay contains unknown chart key: wat'
}
})

logSpy.mockClear()
await expect(
runCli(runtime as never, ['cost', 'read', '--instance', 'default', '--json'])
Expand Down
21 changes: 21 additions & 0 deletions src/cli/cli-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
ProviderCheckReport,
TokenCostDetail
} from '../shared/codex'
import { serializeStatsDisplaySettings } from '../shared/codex'

export function printHelp(): void {
console.log(`ilc
Expand Down Expand Up @@ -313,4 +314,24 @@ export function printSettings(settings: AppSettings, quiet: boolean): void {
console.log(`theme=${settings.theme}`)
console.log(`checkForUpdatesOnStartup=${settings.checkForUpdatesOnStartup}`)
console.log(`codexDesktopExecutablePath=${settings.codexDesktopExecutablePath}`)
console.log(`showLocalMockData=${settings.showLocalMockData !== false}`)
console.log(`statsDisplay=${serializeStatsDisplaySettings(settings.statsDisplay)}`)
}

export function formatSettingsValue(key: keyof AppSettings, settings: AppSettings): string {
const value = settings[key]

if (key === 'statusBarAccountIds' && Array.isArray(value)) {
return value.join(',')
}

if (key === 'showLocalMockData') {
return String(value !== false)
}

if (key === 'statsDisplay') {
return serializeStatsDisplaySettings(settings.statsDisplay)
}

return String(value ?? '')
}
46 changes: 45 additions & 1 deletion src/cli/cli-parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
CliSettingsKey,
CreateCodexInstanceInput,
CreateCustomProviderInput,
StatsDisplaySettings,
UpdateCodexInstanceInput,
UpdateCustomProviderInput
} from '../shared/codex'
import { normalizeStatsDisplaySettings, statsDisplayKeys } from '../shared/codex'
import { CliError, EXIT_USAGE } from './cli-errors'

export interface CliFlags {
Expand All @@ -27,9 +29,49 @@
'theme',
'checkForUpdatesOnStartup',
'showLocalMockData',
'codexDesktopExecutablePath'
'codexDesktopExecutablePath',
'statsDisplay'
]

function parseStatsDisplay(rawValue: string): StatsDisplaySettings {
const normalizedValue = rawValue.trim().toLowerCase()
if (normalizedValue === 'all') {
return normalizeStatsDisplaySettings()
}

if (normalizedValue === 'none') {
return normalizeStatsDisplaySettings(
Object.fromEntries(statsDisplayKeys.map((key) => [key, false])) as Partial<StatsDisplaySettings>

Check warning on line 44 in src/cli/cli-parsing.ts

View workflow job for this annotation

GitHub Actions / quality

Replace `statsDisplayKeys.map((key)·=>·[key,·false])` with `⏎········statsDisplayKeys.map((key)·=>·[key,·false])⏎······`
)
}

const keys = rawValue
.split(',')
.map((value) => value.trim())
.filter(Boolean)

if (!keys.length) {
throw new CliError(
`statsDisplay must be all, none, or a comma-separated subset of ${statsDisplayKeys.join(', ')}`,
EXIT_USAGE
)
}

const invalidKey = keys.find((key) => !statsDisplayKeys.includes(key as (typeof statsDisplayKeys)[number]))

Check warning on line 60 in src/cli/cli-parsing.ts

View workflow job for this annotation

GitHub Actions / quality

Replace `(key)·=>·!statsDisplayKeys.includes(key·as·(typeof·statsDisplayKeys)[number])` with `⏎····(key)·=>·!statsDisplayKeys.includes(key·as·(typeof·statsDisplayKeys)[number])⏎··`
if (invalidKey) {
throw new CliError(

Check warning on line 62 in src/cli/cli-parsing.ts

View workflow job for this annotation

GitHub Actions / quality

Replace `⏎······`statsDisplay·contains·unknown·chart·key:·${invalidKey}`,⏎······EXIT_USAGE⏎····` with ``statsDisplay·contains·unknown·chart·key:·${invalidKey}`,·EXIT_USAGE`
`statsDisplay contains unknown chart key: ${invalidKey}`,
EXIT_USAGE
)
}

return normalizeStatsDisplaySettings(
Object.fromEntries(
statsDisplayKeys.map((key) => [key, keys.includes(key)])
) as Partial<StatsDisplaySettings>
)
}

export function parseFlags(argv: string[]): { flags: CliFlags; positionals: string[] } {
const flags: CliFlags = {
json: false,
Expand Down Expand Up @@ -147,6 +189,8 @@
return rawValue === 'true'
case 'codexDesktopExecutablePath':
return rawValue.trim()
case 'statsDisplay':
return parseStatsDisplay(rawValue)
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/cli/run-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from './cli-parsing'
import {
accountLabel,
formatSettingsValue,
instanceLabel,
printAccountList,
printDoctorReport,
Expand Down Expand Up @@ -600,8 +601,7 @@ async function execute(

const settingKey = ensureSettingsKey(key)
if (!silent) {
const value = settings[settingKey]
console.log(Array.isArray(value) ? value.join(',') : value)
console.log(formatSettingsValue(settingKey, settings))
}
return { code: EXIT_OK, payload: toCliResult(settings[settingKey]) }
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/codex-account-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
UpdateAccountWakeScheduleInput
} from '../shared/codex'
import type { CodexPlatformAdapter, ProtectedPayload } from '../shared/codex-platform'
import { normalizeStatsDisplaySettings } from '../shared/codex'
import {
type CodexAuthPayload,
type LegacyPersistedState,
Expand Down Expand Up @@ -86,6 +87,9 @@ export class CodexAccountStore {
state.settings = {
...state.settings,
...nextSettings,
statsDisplay: normalizeStatsDisplaySettings(
nextSettings.statsDisplay ?? state.settings.statsDisplay
),
statusBarAccountIds: (
nextSettings.statusBarAccountIds ?? state.settings.statusBarAccountIds
).slice(0, 5)
Expand Down
12 changes: 9 additions & 3 deletions src/main/codex-auth-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
PortOccupant
} from '../shared/codex'
import type { CodexPlatformAdapter, ProtectedPayload } from '../shared/codex-platform'
import { decodeJwtPayload, resolveChatGptAccountIdFromTokens } from '../shared/openai-auth'
import {

Check warning on line 16 in src/main/codex-auth-shared.ts

View workflow job for this annotation

GitHub Actions / quality

Replace `⏎··decodeJwtPayload,⏎··resolveChatGptAccountIdFromTokens⏎` with `·decodeJwtPayload,·resolveChatGptAccountIdFromTokens·`
decodeJwtPayload,
resolveChatGptAccountIdFromTokens
} from '../shared/openai-auth'
import { defaultStatsDisplaySettings, normalizeStatsDisplaySettings } from '../shared/codex'

export interface CodexAuthPayload {
auth_mode?: string
Expand Down Expand Up @@ -61,7 +65,8 @@
theme: 'light',
checkForUpdatesOnStartup: true,
codexDesktopExecutablePath: '',
showLocalMockData: true
showLocalMockData: true,
statsDisplay: defaultStatsDisplaySettings()
}
}

Expand Down Expand Up @@ -138,7 +143,8 @@
tags: parsed.tags ?? [],
settings: {
...defaultSettings(),
...('settings' in parsed ? parsed.settings : {})
...('settings' in parsed ? parsed.settings : {}),
statsDisplay: normalizeStatsDisplaySettings(parsed.settings?.statsDisplay)
},
usageByAccountId: parsed.usageByAccountId ?? {},
usageErrorByAccountId: parsed.usageErrorByAccountId ?? {},
Expand Down
23 changes: 22 additions & 1 deletion src/renderer/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
LoginEvent,
LoginMethod,
PortOccupant,
StatsDisplaySettings,
UpdateAccountWakeScheduleInput,
WakeAccountRequestResult,
WakeAccountRateLimitsInput,
Expand All @@ -41,7 +42,9 @@
import {
filterLocalMockAppSnapshot,
accountTransferFormats,
defaultStatsDisplaySettings,
formatRelativeReset,
normalizeStatsDisplaySettings,
resolveBestAccount,
shouldAutoPollUsage,
supportsWeeklyQuota
Expand All @@ -68,7 +71,8 @@
theme: 'light',
checkForUpdatesOnStartup: true,
codexDesktopExecutablePath: '',
showLocalMockData: true
showLocalMockData: true,
statsDisplay: defaultStatsDisplaySettings()
},
usageByAccountId: {},
usageErrorByAccountId: {},
Expand Down Expand Up @@ -1013,6 +1017,19 @@
)
}

const updateStatsDisplay = async (statsDisplay: StatsDisplaySettings): Promise<void> => {
const current = normalizeStatsDisplaySettings(snapshot.settings.statsDisplay)
const next = normalizeStatsDisplaySettings(statsDisplay)

if (JSON.stringify(current) === JSON.stringify(next)) {
return
}

await runAction('settings:stats-display', () =>
window.codexApp.updateSettings({ statsDisplay: next })
)
}

const openMainPanel = async (): Promise<void> => {
applySnapshot(await window.codexApp.openMainWindow())
}
Expand Down Expand Up @@ -1205,6 +1222,7 @@
language={snapshot.settings.language}
showLocalMockData={snapshot.settings.showLocalMockData !== false}
accounts={snapshot.accounts}
codexInstances={snapshot.codexInstances}
providers={snapshot.providers}
tags={snapshot.tags}
activeAccountId={snapshot.activeAccountId}
Expand All @@ -1215,6 +1233,7 @@
tokenCostErrorByInstanceId={snapshot.tokenCostErrorByInstanceId}
runningTokenCostSummary={snapshot.runningTokenCostSummary}
runningTokenCostInstanceIds={snapshot.runningTokenCostInstanceIds}
statsDisplay={normalizeStatsDisplaySettings(snapshot.settings.statsDisplay)}
wakeSchedulesByAccountId={snapshot.wakeSchedulesByAccountId}
loginActionBusy={loginActionBusy()}
{loginStarting}
Expand Down Expand Up @@ -1248,6 +1267,7 @@
{updateAccountTags}
refreshAccountUsage={(account) => readRateLimits(account, { force: true })}
{updateShowLocalMockData}
{updateStatsDisplay}
{openWakeDialog}
{removeAccount}
{removeAccounts}
Expand Down Expand Up @@ -1456,6 +1476,7 @@
{updatePollingInterval}
{updateCheckForUpdatesOnStartup}
{updateShowLocalMockData}
{updateStatsDisplay}
{updateCodexDesktopExecutablePath}
showCodexDesktopExecutablePath={shouldShowCodexDesktopExecutablePath()}
showLocalMockToggle={appMeta.isPackaged === false}
Expand Down
10 changes: 10 additions & 0 deletions src/renderer/src/components/AccountsPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
AccountTag,
AccountWakeSchedule,
AppLanguage,
CodexInstanceSummary,
CustomProviderDetail,
CustomProviderSummary,
StatsDisplaySettings,
TokenCostDetail,
TokenCostReadOptions,
TokenCostSummary,
Expand Down Expand Up @@ -45,6 +47,7 @@
export let language: AppLanguage
export let showLocalMockData = true
export let accounts: AccountSummary[] = []
export let codexInstances: CodexInstanceSummary[] = []
export let providers: CustomProviderSummary[] = []
export let tags: AccountTag[] = []
export let activeAccountId: string | undefined
Expand All @@ -55,6 +58,7 @@
export let tokenCostErrorByInstanceId: Record<string, string>
export let runningTokenCostSummary: TokenCostSummary | null
export let runningTokenCostInstanceIds: string[]
export let statsDisplay: StatsDisplaySettings
export let wakeSchedulesByAccountId: Record<string, AccountWakeSchedule>
export let loginActionBusy: boolean
export let loginStarting = false
Expand All @@ -77,6 +81,7 @@
export let updateAccountTags: (account: AccountSummary, tagIds: string[]) => Promise<void>
export let refreshAccountUsage: (account: AccountSummary) => void
export let updateShowLocalMockData: (enabled: boolean) => void
export let updateStatsDisplay: (statsDisplay: StatsDisplaySettings) => Promise<void>
export let removeAccount: (account: AccountSummary) => void
export let removeAccounts: (accountIds: string[]) => Promise<void>
export let exportSelectedAccounts: (accountIds: string[]) => Promise<void>
Expand Down Expand Up @@ -379,12 +384,17 @@
<CostStatsView
{copy}
{language}
{accounts}
{codexInstances}
{usageByAccountId}
{tokenCostByInstanceId}
{tokenCostErrorByInstanceId}
{runningTokenCostSummary}
{runningTokenCostInstanceIds}
{statsDisplay}
{compactGhostButton}
{readTokenCost}
{updateStatsDisplay}
/>
{:else if currentView === 'tags'}
<AccountsTagsView
Expand Down
Loading
Loading