Skip to content
Merged
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## Unreleased

### Added (CLI)
- **Kimi Code CLI provider.** CodeBurn now reads Kimi session usage from
`$KIMI_SHARE_DIR/sessions/` or `~/.kimi/sessions/`, including subagent
`wire.jsonl` files. The parser consumes Kimi's official `StatusUpdate`
token usage fields (`input_other`, `input_cache_read`,
`input_cache_creation`, `output`), normalizes Kimi tool names such as
`Shell`, `ReadFile`, and `WriteFile`, and maps hidden managed Kimi Code
model aliases to priced Kimi K2 entries.

## 0.9.9 - 2026-05-15

### Added (CLI)
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Arrow keys switch between Today, 7 Days, 30 Days, Month, and 6 Months (use `--fr
| <img src="assets/providers/roo-code.png" width="28" /> | Roo Code | Yes | [roo-code.md](docs/providers/roo-code.md) |
| <img src="assets/providers/kilo-code.png" width="28" /> | KiloCode | Yes | [kilo-code.md](docs/providers/kilo-code.md) |
| <img src="assets/providers/qwen.png" width="28" /> | Qwen | Yes | [qwen.md](docs/providers/qwen.md) |
| <img src="assets/providers/kimi.svg" width="28" /> | Kimi Code CLI | Yes | [kimi.md](docs/providers/kimi.md) |
| <img src="assets/providers/goose.png" width="28" /> | Goose | Yes | [goose.md](docs/providers/goose.md) |
| <img src="assets/providers/antigravity.png" width="28" /> | Antigravity | Yes | [antigravity.md](docs/providers/antigravity.md) |
| <img src="assets/providers/crush.png" width="28" /> | Crush | Yes | [crush.md](docs/providers/crush.md) |
Expand Down Expand Up @@ -384,7 +385,9 @@ These are starting points, not verdicts. A 60% cache hit on a single experimenta

**IBM Bob** stores IDE task history in `User/globalStorage/ibm.bob-code/tasks/<task-id>/` under the IBM Bob application data directory. CodeBurn reads `ui_messages.json` for API request token/cost records and `api_conversation_history.json` for the selected model, with support for both GA (`IBM Bob`) and preview (`Bob-IDE`) app data folders.

CodeBurn deduplicates messages (by API message ID for Claude, by cumulative token cross-check for Codex, by conversation/timestamp for Cursor, by session ID for Gemini, by session+message ID for OpenCode, by responseId for Pi/OMP), filters by date range per entry, and classifies each turn.
**Kimi Code CLI** stores session logs under `$KIMI_SHARE_DIR/sessions/<workdir-hash>/<session-id>/` or `~/.kimi/sessions/<workdir-hash>/<session-id>/`. CodeBurn reads `wire.jsonl` `StatusUpdate.token_usage` records, maps `input_other`, `input_cache_read`, `input_cache_creation`, and `output` into the standard token columns, and includes subagent sessions under each session's `subagents/` folder.

CodeBurn deduplicates messages (by API message ID for Claude, by cumulative token cross-check for Codex, by conversation/timestamp for Cursor, by session ID for Gemini, by session+message ID for OpenCode, by responseId for Pi/OMP, by session+message ID for Kimi), filters by date range per entry, and classifies each turn.

## Environment Variables

Expand All @@ -394,6 +397,8 @@ CodeBurn deduplicates messages (by API message ID for Claude, by cumulative toke
| `CLAUDE_CONFIG_DIRS` | OS-delimited list of Claude data directories to scan together (e.g. `~/.claude-work:~/.claude-personal`). Sessions merge into one row per project. Overrides `CLAUDE_CONFIG_DIR` when set. |
| `CODEX_HOME` | Override Codex data directory (default: `~/.codex`) |
| `FACTORY_DIR` | Override Droid data directory (default: `~/.factory`) |
| `KIMI_SHARE_DIR` | Override Kimi Code CLI share directory (default: `~/.kimi`) |
| `KIMI_MODEL_NAME` | Override Kimi model name when Kimi sessions do not record the model |
| `QWEN_DATA_DIR` | Override Qwen data directory (default: `~/.qwen/projects`) |

## Sponsoring CodeBurn
Expand Down
5 changes: 5 additions & 0 deletions assets/providers/kimi.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ type Provider = {
}
```

`src/providers/index.ts` registers twenty providers across two tiers:
`src/providers/index.ts` registers twenty-one providers across two tiers:

- **Eager**: `claude`, `cline`, `codex`, `copilot`, `droid`, `gemini`, `ibm-bob`, `kilo-code`, `kiro`, `openclaw`, `pi`, `omp`, `qwen`, `roo-code`. Imported at module load.
- **Eager**: `claude`, `cline`, `codex`, `copilot`, `droid`, `gemini`, `ibm-bob`, `kilo-code`, `kiro`, `kimi`, `openclaw`, `pi`, `omp`, `qwen`, `roo-code`. Imported at module load.
- **Lazy**: `antigravity`, `goose`, `cursor`, `opencode`, `cursor-agent`, `crush`. Imported via dynamic `import()` so the heavy dependencies (SQLite, protobuf) do not touch users who do not have those tools installed.

Both lists hit the same `getAllProviders()` aggregator. A failed lazy import is silent and excludes that provider from the run.
Expand Down
1 change: 1 addition & 0 deletions docs/providers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ For the architectural picture, see `../architecture.md`.
| [IBM Bob](ibm-bob.md) | JSON | `src/providers/ibm-bob.ts` | `tests/providers/ibm-bob.test.ts` |
| [KiloCode](kilo-code.md) | JSON | `src/providers/kilo-code.ts` | `tests/providers/kilo-code.test.ts` |
| [Kiro](kiro.md) | JSON | `src/providers/kiro.ts` | `tests/providers/kiro.test.ts` |
| [Kimi](kimi.md) | JSONL | `src/providers/kimi.ts` | `tests/providers/kimi.test.ts` |
| [OpenClaw](openclaw.md) | JSONL | `src/providers/openclaw.ts` | `tests/providers/openclaw.test.ts` |
| [Pi](pi.md) | JSONL | `src/providers/pi.ts` | `tests/providers/pi.test.ts` |
| [OMP](omp.md) | JSONL | `src/providers/pi.ts` | `tests/providers/omp.test.ts` |
Expand Down
62 changes: 62 additions & 0 deletions docs/providers/kimi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Kimi

Kimi Code CLI session parser.

- **Source:** `src/providers/kimi.ts`
- **Loading:** eager (`src/providers/index.ts`)
- **Test:** `tests/providers/kimi.test.ts`

## Where it reads from

`$KIMI_SHARE_DIR/sessions/` if set, otherwise `~/.kimi/sessions/`.

Kimi stores sessions by work-directory hash:

```text
~/.kimi/
kimi.json
config.toml
sessions/
<workdir-md5>/
<session-id>/
context.jsonl
wire.jsonl
state.json
subagents/
<agent-id>/
context.jsonl
wire.jsonl
```

`kimi.json` maps each work-directory hash back to the original working path. CodeBurn uses that to display the project basename; if the metadata file is missing, the hash directory name is used.

## Storage Format

CodeBurn reads `wire.jsonl`. Each data line is a persisted wire record:

```json
{"timestamp":1776162403,"message":{"type":"StatusUpdate","payload":{"message_id":"msg-1","token_usage":{"input_other":100,"input_cache_read":25,"input_cache_creation":10,"output":40}}}}
```

`TurnBegin` / `SteerInput` provide the user prompt, `ToolCall` / `ToolCallRequest` provide tool names and shell commands, and `StatusUpdate.token_usage` provides the billable token counts.

## Caching

None.

## Deduplication

Per `kimi:<session-id>:<message_id>`, falling back to the status-update line index if the message id is absent.

## Quirks

- Kimi's official `TokenUsage` separates `input_other`, `input_cache_read`, `input_cache_creation`, and `output`. CodeBurn maps those directly into input, cache read, cache write, and output.
- The current Kimi wire schema does not persist the model on every usage update. CodeBurn uses `KIMI_MODEL_NAME` when set, then the active `~/.kimi/config.toml` default model, then `kimi-auto`.
- `kimi-auto`, `kimi-code`, and `kimi-for-coding` are priced as `kimi-k2-thinking` so managed Kimi Code sessions do not show as `$0` when the exact backend model is hidden.
- Subagent sessions are discovered from `subagents/<agent-id>/wire.jsonl` and parsed as separate Kimi sessions under the same project.

## When Fixing A Bug Here

1. Reproduce with a tiny `wire.jsonl` fixture in `tests/providers/kimi.test.ts`.
2. If token totals look wrong, inspect `StatusUpdate.token_usage` first; `context.jsonl` only stores context checkpoints and cumulative counts, not per-step billing detail.
3. If tools are missing, check whether Kimi emitted `ToolCall`, `ToolCallRequest`, or nested `SubagentEvent`; CodeBurn intentionally counts subagent wire files separately to avoid double-counting parent mirrors.
2 changes: 2 additions & 0 deletions gnome/indicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const PROVIDERS = [
{ id: 'gemini', label: 'Gemini' },
{ id: 'kilo-code', label: 'Kilo Code' },
{ id: 'kiro', label: 'Kiro' },
{ id: 'kimi', label: 'Kimi' },
{ id: 'roo-code', label: 'Roo Code' },
];

Expand Down Expand Up @@ -69,6 +70,7 @@ const PROVIDER_PATHS = {
codex: '.codex/sessions',
cursor: '.config/Cursor/User/globalStorage/state.vscdb',
copilot: '.copilot/session-state',
kimi: '.kimi/sessions',
pi: '.pi/agent/sessions',
};

Expand Down
1 change: 1 addition & 0 deletions gnome/prefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const PROVIDERS = [
{ id: 'goose', label: 'Goose' },
{ id: 'kilo-code', label: 'Kilo Code' },
{ id: 'kiro', label: 'Kiro' },
{ id: 'kimi', label: 'Kimi' },
{ id: 'openclaw', label: 'OpenClaw' },
{ id: 'opencode', label: 'OpenCode' },
{ id: 'pi', label: 'Pi' },
Expand Down
2 changes: 2 additions & 0 deletions mac/Sources/CodeBurnMenubar/AppStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,7 @@ enum ProviderFilter: String, CaseIterable, Identifiable {
case gemini = "Gemini"
case ibmBob = "IBM Bob"
case kiro = "Kiro"
case kimi = "Kimi"
case kiloCode = "KiloCode"
case openclaw = "OpenClaw"
case opencode = "OpenCode"
Expand Down Expand Up @@ -893,6 +894,7 @@ enum ProviderFilter: String, CaseIterable, Identifiable {
case .ibmBob: "ibm-bob"
case .kiloCode: "kilo-code"
case .kiro: "kiro"
case .kimi: "kimi"
case .openclaw: "openclaw"
case .opencode: "opencode"
case .pi: "pi"
Expand Down
1 change: 1 addition & 0 deletions mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ extension ProviderFilter {
case .ibmBob: return Color(red: 0x0F/255.0, green: 0x62/255.0, blue: 0xFE/255.0)
case .kiloCode: return Color(red: 0x00/255.0, green: 0x96/255.0, blue: 0x88/255.0)
case .kiro: return Color(red: 0x4A/255.0, green: 0x9E/255.0, blue: 0xC4/255.0)
case .kimi: return Color(red: 0xA4/255.0, green: 0xC6/255.0, blue: 0x39/255.0)
case .openclaw: return Color(red: 0xDA/255.0, green: 0x70/255.0, blue: 0x56/255.0)
case .opencode: return Color(red: 0x5B/255.0, green: 0x83/255.0, blue: 0x5B/255.0)
case .pi: return Color(red: 0xB2/255.0, green: 0x6B/255.0, blue: 0x3D/255.0)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"claude-code",
"cursor",
"codex",
"kimi",
"ibm-bob",
"opencode",
"pi",
Expand Down
2 changes: 2 additions & 0 deletions src/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const PROVIDER_COLORS: Record<string, string> = {
'ibm-bob': '#0F62FE',
opencode: '#A78BFA',
pi: '#F472B6',
kimi: '#B6E34A',
all: '#FF8C42',
}

Expand Down Expand Up @@ -528,6 +529,7 @@ const PROVIDER_DISPLAY_NAMES: Record<string, string> = {
'ibm-bob': 'IBM Bob',
opencode: 'OpenCode',
pi: 'Pi',
kimi: 'Kimi',
}
function getProviderDisplayName(name: string): string { return PROVIDER_DISPLAY_NAMES[name] ?? name }

Expand Down
15 changes: 15 additions & 0 deletions src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ const BUILTIN_ALIASES: Record<string, string> = {
'cline-auto': 'claude-sonnet-4-5',
'openclaw-auto': 'claude-sonnet-4-5',
'qwen-auto': 'claude-sonnet-4-5',
'kimi-auto': 'kimi-k2-thinking',
'kimi-code': 'kimi-k2-thinking',
'kimi-for-coding': 'kimi-k2-thinking',
// Cursor emits dot-version tier-last names plus tier/reasoning suffixes
// that LiteLLM does not index (`-high`, `-low`, `-medium`, `-thinking`,
// `-high-thinking`, `-fast-mode`). Missing aliases here surface as $0 in
Expand Down Expand Up @@ -363,6 +366,7 @@ const autoModelNames: Record<string, string> = {
'cline-auto': 'Cline (auto)',
'openclaw-auto': 'OpenClaw (auto)',
'qwen-auto': 'Qwen (auto)',
'kimi-auto': 'Kimi (auto)',
}

const SHORT_NAMES: Record<string, string> = {
Expand Down Expand Up @@ -406,6 +410,17 @@ const SHORT_NAMES: Record<string, string> = {
'gemini-3-flash-preview': 'Gemini 3 Flash',
'gemini-2.5-pro': 'Gemini 2.5 Pro',
'gemini-2.5-flash': 'Gemini 2.5 Flash',
'kimi-k2-thinking-turbo': 'Kimi K2 Thinking Turbo',
'kimi-k2-thinking': 'Kimi K2 Thinking',
'kimi-thinking-preview': 'Kimi Thinking',
'kimi-k2.6': 'Kimi K2.6',
'kimi-k2.5': 'Kimi K2.5',
'kimi-k2p5': 'Kimi K2.5',
'kimi-k2-instruct': 'Kimi K2 Instruct',
'kimi-k2-0905': 'Kimi K2',
'kimi-k2': 'Kimi K2',
'kimi-latest': 'Kimi Latest',
'moonshot-v1': 'Moonshot v1',
'deepseek-coder-max': 'DeepSeek Coder Max',
'deepseek-coder': 'DeepSeek Coder',
'deepseek-r1': 'DeepSeek R1',
Expand Down
3 changes: 2 additions & 1 deletion src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { gemini } from './gemini.js'
import { ibmBob } from './ibm-bob.js'
import { kiloCode } from './kilo-code.js'
import { kiro } from './kiro.js'
import { kimi } from './kimi.js'
import { openclaw } from './openclaw.js'
import { pi, omp } from './pi.js'
import { qwen } from './qwen.js'
Expand Down Expand Up @@ -103,7 +104,7 @@ async function loadCrush(): Promise<Provider | null> {
}
}

const coreProviders: Provider[] = [claude, cline, codex, copilot, droid, gemini, ibmBob, kiloCode, kiro, openclaw, pi, omp, qwen, rooCode]
const coreProviders: Provider[] = [claude, cline, codex, copilot, droid, gemini, ibmBob, kiloCode, kiro, kimi, openclaw, pi, omp, qwen, rooCode]

export async function getAllProviders(): Promise<Provider[]> {
const [ag, gs, cursor, opencode, cursorAgent, crush] = await Promise.all([loadAntigravity(), loadGoose(), loadCursor(), loadOpenCode(), loadCursorAgent(), loadCrush()])
Expand Down
Loading
Loading