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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 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.8 - 2026-05-10

### Added (CLI)
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,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 @@ -380,7 +381,9 @@ These are starting points, not verdicts. A 60% cache hit on a single experimenta

**Roo Code / KiloCode** are Cline-family VS Code extensions. CodeBurn reads `ui_messages.json` from each task directory in VS Code's `globalStorage`, filtering `type: "say"` entries with `say: "api_req_started"` to extract token counts.

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 @@ -390,6 +393,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.
6 changes: 3 additions & 3 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 eighteen providers across two tiers:
`src/providers/index.ts` registers nineteen providers across two tiers:

- **Eager**: `claude`, `codex`, `copilot`, `droid`, `gemini`, `kilo-code`, `kiro`, `openclaw`, `pi`, `omp`, `qwen`, `roo-code`. Imported at module load.
- **Eager**: `claude`, `codex`, `copilot`, `droid`, `gemini`, `kilo-code`, `kiro`, `openclaw`, `pi`, `omp`, `qwen`, `kimi`, `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 Expand Up @@ -181,7 +181,7 @@ The `prepublishOnly` hook in `package.json` runs `npm run build` so `npm publish

- `tests/` root (27 files) covers CLI, parser, optimize, cache, format, models, plans.
- `tests/security/` (1 file) covers prototype-pollution guards.
- `tests/providers/` (14 files) covers per-provider parsing.
- `tests/providers/` (15 files) covers per-provider parsing.
- `tests/fixtures/` holds redacted real-world session data.

Five providers ship without dedicated test files today: `antigravity`, `claude`, `gemini`, `goose`, `qwen`. Closing this gap is a standing good-first-issue.
Expand Down
1 change: 1 addition & 0 deletions docs/providers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ For the architectural picture, see `../architecture.md`.
| [Gemini](gemini.md) | JSON / JSONL | `src/providers/gemini.ts` | none |
| [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 @@ -726,6 +726,7 @@ enum ProviderFilter: String, CaseIterable, Identifiable {
case droid = "Droid"
case gemini = "Gemini"
case kiro = "Kiro"
case kimi = "Kimi"
case kiloCode = "KiloCode"
case openclaw = "OpenClaw"
case opencode = "OpenCode"
Expand Down Expand Up @@ -758,6 +759,7 @@ enum ProviderFilter: String, CaseIterable, Identifiable {
case .gemini: "gemini"
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 @@ -347,6 +347,7 @@ extension ProviderFilter {
case .gemini: return Color(red: 0x44/255.0, green: 0x85/255.0, blue: 0xF4/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",
"opencode",
"pi",
"ai-coding",
Expand Down
2 changes: 2 additions & 0 deletions src/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const PROVIDER_COLORS: Record<string, string> = {
cursor: '#00B4D8',
opencode: '#A78BFA',
pi: '#F472B6',
kimi: '#B6E34A',
all: '#FF8C42',
}

Expand Down Expand Up @@ -515,6 +516,7 @@ const PROVIDER_DISPLAY_NAMES: Record<string, string> = {
cursor: 'Cursor',
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 @@ -170,6 +170,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 @@ -355,6 +358,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 @@ -398,6 +402,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 @@ -5,6 +5,7 @@ import { droid } from './droid.js'
import { gemini } from './gemini.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 @@ -101,7 +102,7 @@ async function loadCrush(): Promise<Provider | null> {
}
}

const coreProviders: Provider[] = [claude, codex, copilot, droid, gemini, kiloCode, kiro, openclaw, pi, omp, qwen, rooCode]
const coreProviders: Provider[] = [claude, codex, copilot, droid, gemini, kiloCode, kiro, openclaw, pi, omp, qwen, kimi, 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