Skip to content

Commit 85338e9

Browse files
authored
feat: add built-in update-config skill (#433)
Inline, model-invocable skill to inspect and safely edit kimi-code's own config.toml and tui.toml (copy to a candidate, Edit, validate with kimi doctor, timestamped backup, then overwrite). Also documents tui.toml on the config files page (en + zh).
1 parent 6a4e4c7 commit 85338e9

7 files changed

Lines changed: 220 additions & 3 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@moonshot-ai/agent-core": minor
3+
"@moonshot-ai/kimi-code": minor
4+
---
5+
6+
Add the built-in `update-config` skill — you can now have Kimi edit its own config files.

docs/en/configuration/config-files.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Configuration files
22

3-
Kimi Code CLI writes all long-term preferences — which model to use, which API key to fill in, how many steps an Agent can run per turn — into a single TOML (a plain-text configuration format with a clear structure) file. Change it once and it takes effect on every startup.
3+
Kimi Code CLI writes all long-term preferences — which model to use, which API key to fill in, how many steps an Agent can run per turn — into TOML (a plain-text configuration format with a clear structure) files. Change them once and they take effect on every startup. Agent and runtime settings live in `config.toml`; terminal-UI and client preferences (theme, editor, notifications, auto-update) live in a companion `tui.toml`.
44

55
Default location: `~/.kimi-code/config.toml`, created automatically on first run.
66

@@ -245,6 +245,35 @@ pattern = "Bash"
245245
MCP server declarations are configured in `~/.kimi-code/mcp.json` or the project-local `.kimi-code/mcp.json`, not in `config.toml`. The interactive configuration entry point is `/mcp-config`; see [Model Context Protocol](../customization/mcp.md).
246246
:::
247247

248+
## `tui.toml`
249+
250+
Alongside `config.toml`, the CLI keeps terminal-UI and client preferences in a companion `tui.toml` in the same directory (`~/.kimi-code/tui.toml`, or `$KIMI_CODE_HOME/tui.toml` when overridden). It is created with defaults on first run, and the interactive commands `/config`, `/theme`, and `/editor` write to it for you — so you rarely need to edit it by hand. If the file is malformed, the CLI falls back to defaults and shows a notice instead of failing to start.
251+
252+
| Field | Type | Default | Description |
253+
| --- | --- | --- | --- |
254+
| `theme` | `string` | `auto` | Color theme: `auto` (follow the terminal), `dark`, or `light` |
255+
| `[editor].command` | `string` | `""` | External editor command for composing long input; empty falls back to `$VISUAL` / `$EDITOR` |
256+
| `[notifications].enabled` | `boolean` | `true` | Whether desktop notifications are sent |
257+
| `[notifications].notification_condition` | `string` | `unfocused` | When to notify: `unfocused` (only when the terminal is not focused) or `always` |
258+
| `[upgrade].auto_install` | `boolean` | `true` | Whether new versions are installed automatically |
259+
260+
```toml
261+
# ~/.kimi-code/tui.toml
262+
theme = "auto" # "auto" | "dark" | "light"
263+
264+
[editor]
265+
command = "" # empty uses $VISUAL / $EDITOR
266+
267+
[notifications]
268+
enabled = true
269+
notification_condition = "unfocused" # "unfocused" | "always"
270+
271+
[upgrade]
272+
auto_install = true
273+
```
274+
275+
Changes apply on the next start, or immediately with `/reload-tui` (which reloads only `tui.toml`); `/reload` reloads both `config.toml` and `tui.toml`.
276+
248277
## Next steps
249278

250279
- [Providers and models](./providers.md) — connection examples for each provider type (Kimi, Claude, OpenAI, Gemini)

docs/zh/configuration/config-files.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 配置文件
22

3-
Kimi Code CLI 把所有长期偏好写进一份 TOML(一种结构清晰的纯文本配置格式)文件——比如使用哪个模型、填哪个 API 密钥、Agent 每轮最多跑几步。改一次,每次启动都生效。
3+
Kimi Code CLI 把所有长期偏好写进 `~/.kimi-code/` 下的 TOML(一种结构清晰的纯文本配置格式)文件——比如使用哪个模型、填哪个 API 密钥、Agent 每轮最多跑几步。改一次,每次启动都生效。Agent 与运行时设置放在 `config.toml`,终端界面与客户端偏好(主题、编辑器、通知、自动更新)放在配套的 `tui.toml`
44

55
默认位置:`~/.kimi-code/config.toml`,首次运行时自动创建。
66

@@ -245,6 +245,35 @@ pattern = "Bash"
245245
MCP server 的声明配置写在 `~/.kimi-code/mcp.json` 或项目内 `.kimi-code/mcp.json` 中,不在 `config.toml` 里。交互式配置入口是 `/mcp-config`,详见 [Model Context Protocol](../customization/mcp.md)
246246
:::
247247

248+
## `tui.toml`
249+
250+
除了 `config.toml`,CLI 还在同一目录下用一份配套的 `tui.toml` 保存终端界面与客户端偏好(`~/.kimi-code/tui.toml`,或覆盖后的 `$KIMI_CODE_HOME/tui.toml`)。它在首次运行时以默认值创建,交互式命令 `/config``/theme``/editor` 会自动写入,通常无需手动编辑。文件格式有误时,CLI 会回退到默认值并给出提示,而不是启动失败。
251+
252+
| 字段 | 类型 | 默认值 | 说明 |
253+
| --- | --- | --- | --- |
254+
| `theme` | `string` | `auto` | 配色主题:`auto`(跟随终端)、`dark``light` |
255+
| `[editor].command` | `string` | `""` | 编写长输入用的外部编辑器命令;留空则回退到 `$VISUAL` / `$EDITOR` |
256+
| `[notifications].enabled` | `boolean` | `true` | 是否发送桌面通知 |
257+
| `[notifications].notification_condition` | `string` | `unfocused` | 何时通知:`unfocused`(仅终端失去焦点时)或 `always`(总是) |
258+
| `[upgrade].auto_install` | `boolean` | `true` | 是否自动安装新版本 |
259+
260+
```toml
261+
# ~/.kimi-code/tui.toml
262+
theme = "auto" # "auto" | "dark" | "light"
263+
264+
[editor]
265+
command = "" # 留空则使用 $VISUAL / $EDITOR
266+
267+
[notifications]
268+
enabled = true
269+
notification_condition = "unfocused" # "unfocused" | "always"
270+
271+
[upgrade]
272+
auto_install = true
273+
```
274+
275+
修改在下次启动时生效,或用 `/reload-tui` 立即生效(只重载 `tui.toml`);`/reload` 会同时重载 `config.toml``tui.toml`
276+
248277
## 下一步
249278

250279
- [平台与模型](./providers.md) — 各供应商类型(Kimi、Claude、OpenAI、Gemini)的接入示例
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type { SkillRegistry } from '../registry';
22
import { MCP_CONFIG_SKILL } from './mcp-config';
3+
import { UPDATE_CONFIG_SKILL } from './update-config';
34

45
export function registerBuiltinSkills(registry: SkillRegistry): void {
56
registry.registerBuiltinSkill(MCP_CONFIG_SKILL);
7+
registry.registerBuiltinSkill(UPDATE_CONFIG_SKILL);
68
}
79

8-
export { MCP_CONFIG_SKILL };
10+
export { MCP_CONFIG_SKILL, UPDATE_CONFIG_SKILL };
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
name: update-config
3+
description: Inspect or edit kimi-code's own config — `config.toml` (model, provider, permission, hooks) and `tui.toml` (theme, editor, notifications, auto-update). Use when the user asks what a setting does or wants to change one.
4+
---
5+
6+
# Configure kimi-code (update-config)
7+
8+
Help the user inspect, change, and validate kimi-code's configuration files. The files are **TOML** with **snake_case** keys.
9+
10+
## The two config files
11+
12+
kimi-code has two TOML config files, both under `~/.kimi-code/` (or under `KIMI_CODE_HOME` when set), both snake_case, but with different ownership — decide which one the user means before doing anything:
13+
14+
- **`config.toml`** — agent / runtime settings: `default_model`, `providers`, `models`, `thinking`, `permission`, `hooks`, `loop_control`, etc.
15+
- **`tui.toml`** — terminal-UI / client preferences: `theme`, `[editor].command`, `[notifications]`, `[upgrade].auto_install` (auto-update). These can usually also be changed with the interactive commands `/config`, `/theme`, `/editor`, which is easier — prefer pointing the user at those.
16+
17+
The "read → copy → Edit → validate → back up → overwrite" flow below applies to both files; only **which reload command applies** differs (see Capability 4).
18+
19+
## Prerequisite 1: the official docs are the single source of truth
20+
21+
Before touching any config, use **FetchURL** to fetch the official config docs as the one authoritative reference for fields (key names, types, allowed values, owning section):
22+
23+
```
24+
https://moonshotai.github.io/kimi-code/en/configuration/config-files.html
25+
```
26+
27+
- Use the **snake_case key names and sections exactly as documented** — don't invent them, don't guess camelCase.
28+
- If FetchURL is unavailable or the fetch fails, tell the user plainly that you can't reach the online docs, and ask them to paste the relevant section or confirm whether to proceed from what you already know. **Never edit blindly without an authoritative reference.**
29+
30+
## Prerequisite 2: read the target file before any change
31+
32+
Before any modification, use **Read** on the target config file (decide whether it's `config.toml` or `tui.toml` per the above):
33+
34+
- Location: `~/.kimi-code/config.toml` or `~/.kimi-code/tui.toml` (under `$KIMI_CODE_HOME/` when set). For other scopes/files, defer to the official docs.
35+
- A missing or empty file is fine — you'll create a minimal skeleton later.
36+
- If the file exists but **fails to parse as TOML**, report the error verbatim and **stop** — never overwrite a broken file in place (it could destroy the user's existing config).
37+
38+
---
39+
40+
## Capability 1: explain configuration (read-only, no file changes)
41+
42+
When the user asks "what config is there", "what does this setting do", or "how do I use it":
43+
44+
1. Fetch the official docs (Prerequisite 1).
45+
2. Read the current `config.toml` (Prerequisite 2).
46+
3. Answer against both: list the relevant sections / keys, what each is for, **current value vs default**, and the allowed-value range; say which file and section each lives in.
47+
4. Present it as a compact grouped list or table. **Stay read-only — write no files.**
48+
49+
## Capability 2: make changes for the user (copy → Edit → validate → back up → overwrite)
50+
51+
Don't edit the target file in place, and **don't rewrite it from scratch** — instead copy it, Edit the copy, and keep the original out of any broken state the whole time:
52+
53+
1. **Clarify intent**: which key, what value, and which file (`config.toml` or `tui.toml`). Ask in one line if ambiguous; for discrete choices (e.g. scope) AskUserQuestion is fine, but use plain questions for free-form input.
54+
2. **Read the target file** (Prerequisite 2): Read it to understand the current state and confirm it parses.
55+
3. **Copy out a candidate (do not create from scratch)**: use **Bash** to copy the target verbatim — `cp config.toml config-new.toml` (same directory, `-new` suffix; for tui.toml, `cp tui.toml tui-new.toml`). **Leave the original untouched for now.**
56+
- Only when the target doesn't exist (nothing to copy) should you use **Write** to create a minimal skeleton candidate (e.g. just the comment line `# ~/.kimi-code/config.toml`).
57+
4. **Edit the candidate**: use the **Edit** tool on the candidate to **change/add only the target key** — never rewrite the whole file. That way every existing section, entry, comment, and bit of formatting stays exactly as-is; only what should change changes. The candidate is identical to the original, so use the content you read in step 2 to locate the Edit anchor. Check the change against the official docs (key / section / value type / allowed values, snake_case).
58+
5. **Validate the candidate** (see Capability 3, via `kimi doctor`). **If anything fails, keep Editing the candidate and re-validate, looping until it all passes.**
59+
6. **Back up and overwrite** (only after validation fully passes):
60+
- **Back up the old file — always create a new timestamped backup, keep all of them, never overwrite an existing backup.** Copy this exactly with **Bash** (for config.toml): `cp config.toml "config.toml.$(date +%Y%m%d-%H%M%S).bak"`; for tui.toml: `cp tui.toml "tui.toml.$(date +%Y%m%d-%H%M%S).bak"`. Skip the backup only if the target didn't exist.
61+
- Overwrite with the candidate: `mv config-new.toml config.toml`.
62+
- If reload errors after the overwrite, the user can recover from **the most recent timestamped backup**.
63+
7. Tell the user how to apply it (see Capability 4).
64+
65+
## Capability 3: validate the candidate file (must pass before overwrite)
66+
67+
Use **`kimi doctor`** to validate the candidate you wrote — it doesn't start the TUI and doesn't modify any file; it runs kimi's own parser + schema (syntax and schema together), so it's the authoritative check. Pick the subcommand by which file you changed, and pass the **candidate** path explicitly:
68+
69+
- changed `config.toml``kimi doctor config <config-new.toml path>`
70+
- changed `tui.toml``kimi doctor tui <tui-new.toml path>`
71+
72+
When a path is passed explicitly the file must exist (your candidate does, so that's fine). **Exit code 0 = pass (valid or skipped); non-zero = a specified file is missing or the config is invalid** — show the output verbatim, fix the candidate, and re-run, looping until it's 0.
73+
74+
Then do two checks `kimi doctor` can't:
75+
76+
1. **Cross-check values against the official docs** (single source of truth): are the key / section / enum values as documented, and snake_case? doctor guarantees "schema-valid", but "valid yet not what the user wanted" (e.g. a misspelled model alias) needs the docs.
77+
2. **Completeness**: every existing entry is still present (the candidate fully replaces the target — a dropped line is a deletion).
78+
79+
> To also check whether the currently **active** config is OK overall, run `kimi doctor` with no path (it checks the default `config.toml` + `tui.toml`, showing a missing one as skipped).
80+
81+
## Capability 4: tell the user how to apply changes
82+
83+
Once local validation passes, tell the user how to make the change take effect — **the reload command depends on which file you changed**:
84+
85+
- changed **`config.toml`** → run **`/reload`** in the TUI (reloads the session and applies `config.toml`; it also reloads `tui.toml`).
86+
- changed **`tui.toml`** → run **`/reload-tui`** (reloads only `tui.toml`, lighter); `/reload` works too (reloads both).
87+
- changed both → a single **`/reload`** covers it.
88+
89+
Note: `/reload` is available **only when idle** — if a reply is streaming, press Esc / Ctrl-C to stop first. `kimi doctor` already validated the schema before the overwrite, so reload should apply cleanly; if it still errors, follow the message to fix it or recover from the most recent timestamped backup. If you don't want to reload now, the **next new session** picks it up automatically.
90+
91+
## Don'ts
92+
93+
- **Always back up before overwriting**, with a **timestamped name and all history kept** — don't skip the backup, don't keep only a single `.bak`, don't overwrite an old backup.
94+
- Don't drop unrelated entries (the candidate fully replaces the target — a dropped line is a deletion).
95+
- When you can't reach the docs / have no authoritative reference, don't edit by guessing.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { parseSkillText } from '../parser';
2+
import type { SkillDefinition } from '../types';
3+
import UPDATE_CONFIG_BODY from './update-config.md';
4+
5+
const PSEUDO_PATH = 'builtin://update-config';
6+
7+
const parsed = parseSkillText({
8+
skillMdPath: '/builtin/skills/update-config.md',
9+
skillDirName: 'update-config',
10+
source: 'builtin',
11+
text: UPDATE_CONFIG_BODY,
12+
});
13+
14+
export const UPDATE_CONFIG_SKILL: SkillDefinition = {
15+
...parsed,
16+
path: PSEUDO_PATH,
17+
dir: PSEUDO_PATH,
18+
metadata: {
19+
...parsed.metadata,
20+
type: parsed.metadata.type ?? 'inline',
21+
},
22+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { SkillRegistry, UPDATE_CONFIG_SKILL, registerBuiltinSkills } from '../../src/skill';
4+
5+
describe('builtin skill: update-config', () => {
6+
it('has the expected identity and inline metadata', () => {
7+
expect(UPDATE_CONFIG_SKILL.name).toBe('update-config');
8+
expect(UPDATE_CONFIG_SKILL.source).toBe('builtin');
9+
expect(UPDATE_CONFIG_SKILL.description.length).toBeGreaterThan(0);
10+
expect(UPDATE_CONFIG_SKILL.metadata.type).toBe('inline');
11+
});
12+
13+
it('is model-invocable (does not disable model invocation)', () => {
14+
expect(UPDATE_CONFIG_SKILL.metadata.disableModelInvocation).not.toBe(true);
15+
});
16+
17+
it('pins the doc URL as the single source of truth and references TOML / FetchURL / /reload', () => {
18+
const content = UPDATE_CONFIG_SKILL.content;
19+
expect(content).toContain('config-files.html');
20+
expect(content).toContain('FetchURL');
21+
expect(content).toContain('/reload');
22+
expect(content.toLowerCase()).toContain('toml');
23+
});
24+
25+
it('registers through registerBuiltinSkills and shows up as model-invocable', () => {
26+
const registry = new SkillRegistry();
27+
registerBuiltinSkills(registry);
28+
29+
expect(registry.getSkill('update-config')).toBeDefined();
30+
expect(
31+
registry.listInvocableSkills().some((skill) => skill.name === 'update-config'),
32+
).toBe(true);
33+
});
34+
});

0 commit comments

Comments
 (0)