Skip to content
Open
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
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
- [2026-07-22] clipped settings panels stay open

A panel cut off by scrolling keeps its title but drops its bottom
border, so it reads as continuing into the more line instead of
finished.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- [2026-07-22] [settings shows the harnesses and scrolls

A harnesses box joins settings: green on when routed, warn off with an
enter hint when installed but not routed, grey when the harness is not
on the machine (binary or config detection). Enter installs or removes
the providers inline — no CLI round-trip. The settings tab now windows
its rows to the terminal height with more indicators, and the 2s
background tick no longer holds the busy flag that was eating
keypresses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>](https://github.com/RubricLab/tokenmaxx/commit/89ba154b91d73db4059f9a44704633141497e0a0)
- [2026-07-22] [the proxy lifts system prompts for the chatgpt backend

Third-party harnesses send standard Responses requests: system messages
in the input array and max_output_tokens set. The ChatGPT codex backend
rejects both. The proxy now adapts oauth-bound openai requests (system
and developer messages move into instructions, max_output_tokens drops)
so openclaw, pi, and hermes work without harness-specific dialects.
Also ships only gpt-5.6-sol — the one model the backend accepts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>](https://github.com/RubricLab/tokenmaxx/commit/b8462fc888c18c3495c913cfb6376d4f865b221b)
- [2026-07-22] install into openclaw, pi, and hermes, fixes #11
- [2026-07-22] session reset time on analytics, fixes #9
- [2026-07-22] logout
- [2026-07-22] api keys and extra usage, fixes #10
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@
"post-commit": "bun x @rubriclab/package post-commit"
},
"type": "module",
"version": "0.0.56"
"version": "0.0.60"
}
66 changes: 60 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import { z } from 'zod'
import { registerClaudeAccount, registerClaudeApiKeyAccount } from './claude.ts'
import { registerCodexAccount, registerOpenAiApiKeyAccount } from './codex.ts'
import {
type HarnessTarget,
harnessStatus,
installClaudeConfig,
installCodexConfig,
installHarnessConfig,
installStatus,
uninstallClaudeConfig,
uninstallCodexConfig
uninstallCodexConfig,
uninstallHarnessConfig
} from './config-install.ts'
import type { Account, ProviderId } from './domain.ts'
import { ApplicationError, errorMessage } from './errors.ts'
Expand Down Expand Up @@ -240,7 +244,7 @@ function help(): string {
'sign in an account · re-run to re-auth',
'add --api-key to use an API key instead'
),
row('install', 'route codex & claude through tokenmaxx'),
row('install [openclaw|pi|hermes]', 'route codex & claude, or a harness'),
row('uninstall', 'restore your original config'),
'',
head('Everyday'),
Expand Down Expand Up @@ -771,8 +775,33 @@ async function configureAutomation(
}
}

async function installConfig(context: ApplicationContext): Promise<void> {
const harnessTargets = new Set<HarnessTarget>(['openclaw', 'pi', 'hermes'])

function harnessTarget(value: string | undefined): HarnessTarget | null {
return value !== undefined && harnessTargets.has(value as HarnessTarget)
? (value as HarnessTarget)
: null
}

async function installConfig(context: ApplicationContext, targetArgument?: string): Promise<void> {
const target = harnessTarget(targetArgument)
if (targetArgument !== undefined && target === null) {
throw new ApplicationError('USAGE', 'Usage: tokenmaxx install [openclaw|pi|hermes]')
}
await ensureDaemon(context)
if (target !== null) {
const result = await installHarnessConfig(target, context.paths)
if (!result.applied) {
process.stdout.write(`Left ${result.path} alone: ${result.manual}\n`)
return
}
process.stdout.write(
`${target} now has tokenmaxx-anthropic and tokenmaxx-openai providers (${result.path}).\n` +
`Pick a tokenmaxx model inside ${target} and requests route through the proxy.\n` +
`Undo any time with: tokenmaxx uninstall ${target}\n`
)
return
}
await installCodexConfig(context.paths)
await installClaudeConfig(context.paths)
process.stdout.write(
Expand All @@ -782,7 +811,20 @@ async function installConfig(context: ApplicationContext): Promise<void> {
)
}

async function uninstallConfig(): Promise<void> {
async function uninstallConfig(targetArgument?: string): Promise<void> {
const target = harnessTarget(targetArgument)
if (targetArgument !== undefined && target === null) {
throw new ApplicationError('USAGE', 'Usage: tokenmaxx uninstall [openclaw|pi|hermes]')
}
if (target !== null) {
const result = await uninstallHarnessConfig(target)
process.stdout.write(
result.applied
? `Removed the tokenmaxx providers from ${result.path}.\n`
: (result.manual ?? `${target} was not routed; nothing to restore.`) + '\n'
)
return
}
const codex = await uninstallCodexConfig()
const claude = await uninstallClaudeConfig()
if (codex === null && claude === null) {
Expand Down Expand Up @@ -864,6 +906,18 @@ async function doctor(context: ApplicationContext): Promise<void> {
: 'not routed — run tokenmaxx install'
}\n`
)
for (const harness of await harnessStatus()) {
if (!harness.present) {
continue
}
process.stdout.write(
`${harness.routed ? 'ok ' : 'note '} ${harness.target.padEnd(8)} ${
harness.routed
? 'has the tokenmaxx providers'
: `not routed — run tokenmaxx install ${harness.target}`
}\n`
)
}
process.stdout.write(`state ${context.paths.database}\n`)
const legacyDirectories = [join(context.paths.root, 'codex'), join(context.paths.root, 'claude')]
const legacyDetected = await Promise.all(
Expand Down Expand Up @@ -1005,10 +1059,10 @@ export async function runCli(rawArguments: readonly string[]): Promise<number> {
listAccounts(context)
return 0
case 'install':
await installConfig(context)
await installConfig(context, arguments_[1])
return 0
case 'uninstall':
await uninstallConfig()
await uninstallConfig(arguments_[1])
return 0
case 'daemon':
switch (arguments_[1]) {
Expand Down
1 change: 1 addition & 0 deletions src/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ export async function codexUpstream(input: {
return {
accountId: input.account.id,
baseUrl: upstreamFor('openai'),
dialect: 'chatgpt',
headers: {
authorization: `Bearer ${auth.tokens.access_token}`,
'chatgpt-account-id': codexIdentity(auth).accountId
Expand Down
72 changes: 71 additions & 1 deletion src/config-install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { mkdtempSync, rmSync } from 'node:fs'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { installCodexConfig, installStatus, uninstallCodexConfig } from './config-install.ts'
import {
installCodexConfig,
installHarnessConfig,
installStatus,
uninstallCodexConfig,
uninstallHarnessConfig
} from './config-install.ts'
import { applicationPaths } from './paths.ts'

const legacyBrokenConfig = `model = "gpt-5.6-sol"
Expand Down Expand Up @@ -109,3 +115,67 @@ describe('installCodexConfig', () => {
expect(parsed.model_provider).toBe('tokenmaxx')
})
})

describe('harness installs', () => {
test('openclaw providers merge in and back out without touching the rest', async () => {
process.env.OPENCLAW_CONFIG_PATH = join(home, 'openclaw.json')
await writeFile(
process.env.OPENCLAW_CONFIG_PATH,
JSON.stringify({ agents: { defaults: { model: { primary: 'anthropic/claude-opus-4-8' } } } })
)
const installed = await installHarnessConfig('openclaw', applicationPaths())
expect(installed.applied).toBe(true)
const config = JSON.parse(await readFile(installed.path, 'utf8'))
expect(config.models.providers['tokenmaxx-anthropic'].api).toBe('anthropic-messages')
expect(config.models.providers['tokenmaxx-openai'].baseUrl).toContain('/openai')
expect(config.agents.defaults.model.primary).toBe('anthropic/claude-opus-4-8')
const removed = await uninstallHarnessConfig('openclaw')
expect(removed.applied).toBe(true)
const restored = JSON.parse(await readFile(installed.path, 'utf8'))
expect(restored.models.providers['tokenmaxx-anthropic']).toBeUndefined()
delete process.env.OPENCLAW_CONFIG_PATH
})

test('a json5 openclaw config is left alone with manual instructions', async () => {
process.env.OPENCLAW_CONFIG_PATH = join(home, 'openclaw.json')
await writeFile(process.env.OPENCLAW_CONFIG_PATH, '{\n // my settings\n models: {},\n}\n')
const result = await installHarnessConfig('openclaw', applicationPaths())
expect(result.applied).toBe(false)
expect(result.manual).toContain('models.providers')
expect(await readFile(result.path, 'utf8')).toContain('// my settings')
delete process.env.OPENCLAW_CONFIG_PATH
})

test('pi models.json gains and loses the providers cleanly', async () => {
process.env.PI_CODING_AGENT_DIR = join(home, 'pi-agent')
const installed = await installHarnessConfig('pi', applicationPaths())
expect(installed.applied).toBe(true)
const config = JSON.parse(await readFile(installed.path, 'utf8'))
expect(config.providers['tokenmaxx-anthropic'].baseUrl).toContain('/anthropic')
const removed = await uninstallHarnessConfig('pi')
expect(removed.applied).toBe(true)
expect(JSON.parse(await readFile(installed.path, 'utf8')).providers).toEqual({})
delete process.env.PI_CODING_AGENT_DIR
})

test('hermes gets a marked block that round-trips, and defers when providers exist', async () => {
process.env.HERMES_HOME = join(home, 'hermes')
await mkdir(process.env.HERMES_HOME, { recursive: true })
const configPath = join(process.env.HERMES_HOME, 'config.yaml')
await writeFile(configPath, 'model:\n default: "claude-opus-4-8"\n')
const installed = await installHarnessConfig('hermes', applicationPaths())
expect(installed.applied).toBe(true)
const written = await readFile(configPath, 'utf8')
expect(written).toContain('api_mode: "codex_responses"')
expect(written).toContain('model:')
const removed = await uninstallHarnessConfig('hermes')
expect(removed.applied).toBe(true)
expect(await readFile(configPath, 'utf8')).not.toContain('tokenmaxx-anthropic')

await writeFile(configPath, 'providers:\n mine:\n base_url: "https://example.com"\n')
const deferred = await installHarnessConfig('hermes', applicationPaths())
expect(deferred.applied).toBe(false)
expect(await readFile(configPath, 'utf8')).not.toContain('tokenmaxx')
delete process.env.HERMES_HOME
})
})
Loading
Loading