diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc6bb07..5827f9c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
diff --git a/README.md b/README.md
index b378248..6d5a55c 100644
--- a/README.md
+++ b/README.md
@@ -115,6 +115,7 @@ Arrow keys switch between Today, 7 Days, 30 Days, Month, and 6 Months (use `--fr
|
| Roo Code | Yes | [roo-code.md](docs/providers/roo-code.md) |
|
| KiloCode | Yes | [kilo-code.md](docs/providers/kilo-code.md) |
|
| Qwen | Yes | [qwen.md](docs/providers/qwen.md) |
+|
| Kimi Code CLI | Yes | [kimi.md](docs/providers/kimi.md) |
|
| Goose | Yes | [goose.md](docs/providers/goose.md) |
|
| Antigravity | Yes | [antigravity.md](docs/providers/antigravity.md) |
|
| Crush | Yes | [crush.md](docs/providers/crush.md) |
@@ -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//` 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///` or `~/.kimi/sessions///`. 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
@@ -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
diff --git a/assets/providers/kimi.svg b/assets/providers/kimi.svg
new file mode 100644
index 0000000..c09b36f
--- /dev/null
+++ b/assets/providers/kimi.svg
@@ -0,0 +1,5 @@
+
diff --git a/docs/architecture.md b/docs/architecture.md
index de3f213..5f31c7c 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -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.
diff --git a/docs/providers/README.md b/docs/providers/README.md
index 02dcc2d..c7bd342 100644
--- a/docs/providers/README.md
+++ b/docs/providers/README.md
@@ -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` |
diff --git a/docs/providers/kimi.md b/docs/providers/kimi.md
new file mode 100644
index 0000000..19d6876
--- /dev/null
+++ b/docs/providers/kimi.md
@@ -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/
+ /
+ /
+ context.jsonl
+ wire.jsonl
+ state.json
+ subagents/
+ /
+ 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::`, 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//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.
diff --git a/gnome/indicator.js b/gnome/indicator.js
index c2f8266..533f644 100644
--- a/gnome/indicator.js
+++ b/gnome/indicator.js
@@ -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' },
];
@@ -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',
};
diff --git a/gnome/prefs.js b/gnome/prefs.js
index 2b9d477..08d4b82 100644
--- a/gnome/prefs.js
+++ b/gnome/prefs.js
@@ -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' },
diff --git a/mac/Sources/CodeBurnMenubar/AppStore.swift b/mac/Sources/CodeBurnMenubar/AppStore.swift
index 94a576f..c901362 100644
--- a/mac/Sources/CodeBurnMenubar/AppStore.swift
+++ b/mac/Sources/CodeBurnMenubar/AppStore.swift
@@ -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"
@@ -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"
diff --git a/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift b/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift
index fdeb716..82f2ceb 100644
--- a/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift
+++ b/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift
@@ -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)
diff --git a/package.json b/package.json
index 10e9f0c..357b43b 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"claude-code",
"cursor",
"codex",
+ "kimi",
"ibm-bob",
"opencode",
"pi",
diff --git a/src/dashboard.tsx b/src/dashboard.tsx
index 4813f1b..4add882 100644
--- a/src/dashboard.tsx
+++ b/src/dashboard.tsx
@@ -55,6 +55,7 @@ const PROVIDER_COLORS: Record = {
'ibm-bob': '#0F62FE',
opencode: '#A78BFA',
pi: '#F472B6',
+ kimi: '#B6E34A',
all: '#FF8C42',
}
@@ -528,6 +529,7 @@ const PROVIDER_DISPLAY_NAMES: Record = {
'ibm-bob': 'IBM Bob',
opencode: 'OpenCode',
pi: 'Pi',
+ kimi: 'Kimi',
}
function getProviderDisplayName(name: string): string { return PROVIDER_DISPLAY_NAMES[name] ?? name }
diff --git a/src/models.ts b/src/models.ts
index 1070cdb..5d48822 100644
--- a/src/models.ts
+++ b/src/models.ts
@@ -172,6 +172,9 @@ const BUILTIN_ALIASES: Record = {
'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
@@ -363,6 +366,7 @@ const autoModelNames: Record = {
'cline-auto': 'Cline (auto)',
'openclaw-auto': 'OpenClaw (auto)',
'qwen-auto': 'Qwen (auto)',
+ 'kimi-auto': 'Kimi (auto)',
}
const SHORT_NAMES: Record = {
@@ -406,6 +410,17 @@ const SHORT_NAMES: Record = {
'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',
diff --git a/src/providers/index.ts b/src/providers/index.ts
index 6aa9e68..b12c09a 100644
--- a/src/providers/index.ts
+++ b/src/providers/index.ts
@@ -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'
@@ -103,7 +104,7 @@ async function loadCrush(): Promise {
}
}
-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 {
const [ag, gs, cursor, opencode, cursorAgent, crush] = await Promise.all([loadAntigravity(), loadGoose(), loadCursor(), loadOpenCode(), loadCursorAgent(), loadCrush()])
diff --git a/src/providers/kimi.ts b/src/providers/kimi.ts
new file mode 100644
index 0000000..75242cc
--- /dev/null
+++ b/src/providers/kimi.ts
@@ -0,0 +1,394 @@
+import { createHash } from 'crypto'
+import { readdir, readFile, stat } from 'fs/promises'
+import { basename, dirname, join } from 'path'
+import { homedir } from 'os'
+
+import { extractBashCommands } from '../bash-utils.js'
+import { readSessionLines } from '../fs-utils.js'
+import { calculateCost, getShortModelName } from '../models.js'
+import type { ParsedProviderCall, Provider, SessionParser, SessionSource } from './types.js'
+
+type JsonObject = Record
+
+const toolNameMap: Record = {
+ Shell: 'Bash',
+ Bash: 'Bash',
+ bash: 'Bash',
+ ReadFile: 'Read',
+ ReadMediaFile: 'Read',
+ WriteFile: 'Write',
+ StrReplaceFile: 'Edit',
+ Grep: 'Grep',
+ Glob: 'Glob',
+ SearchWeb: 'WebSearch',
+ FetchURL: 'WebFetch',
+ Agent: 'Agent',
+ AgentTool: 'Agent',
+ TaskList: 'Agent',
+ TaskOutput: 'Agent',
+ TaskStop: 'Agent',
+ AskUserQuestion: 'AskUser',
+ SetTodoList: 'TodoWrite',
+ Think: 'Think',
+ EnterPlanMode: 'EnterPlanMode',
+ ExitPlanMode: 'ExitPlanMode',
+ SendDMail: 'DMail',
+}
+
+function asObject(value: unknown): JsonObject | null {
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as JsonObject : null
+}
+
+function stringField(obj: JsonObject | null, key: string): string | undefined {
+ const value = obj?.[key]
+ return typeof value === 'string' ? value : undefined
+}
+
+function numericField(obj: JsonObject, ...keys: string[]): number {
+ for (const key of keys) {
+ const raw = obj[key]
+ const n = typeof raw === 'number' ? raw : typeof raw === 'string' ? Number(raw) : NaN
+ if (Number.isFinite(n) && n > 0) return Math.trunc(n)
+ }
+ return 0
+}
+
+function getShareDir(overrideDir?: string): string {
+ return overrideDir ?? process.env['KIMI_SHARE_DIR'] ?? join(homedir(), '.kimi')
+}
+
+function md5(text: string): string {
+ return createHash('md5').update(text, 'utf-8').digest('hex')
+}
+
+function projectNameFromPath(pathValue: string): string {
+ const cleaned = pathValue.replace(/\/+$/, '')
+ return basename(cleaned) || cleaned || 'kimi'
+}
+
+async function loadProjectNames(shareDir: string): Promise