From 22ebcbb7bc0c4264be6ec839f7709a16c9ac879f Mon Sep 17 00:00:00 2001 From: yousali Date: Mon, 10 Nov 2025 23:09:33 +0800 Subject: [PATCH 1/6] feat: add module-specific log level controls - add logging schema/config validation so ~/.kimi/config.json can persist levels - introduce -L/--log-level CLI overrides and share loaded config with runtime - implement loguru filter for per-module thresholds, update docs/tests --- README.md | 24 ++++++++++ src/kimi_cli/app.py | 6 ++- src/kimi_cli/cli.py | 72 ++++++++++++++++++++++++---- src/kimi_cli/config.py | 27 +++++++++++ src/kimi_cli/utils/logging.py | 89 ++++++++++++++++++++++++++++++++++- tests/test_config.py | 7 ++- tests/test_logging_levels.py | 53 +++++++++++++++++++++ 7 files changed, 266 insertions(+), 12 deletions(-) create mode 100644 tests/test_logging_levels.py diff --git a/README.md b/README.md index b2389eb5..0886be7d 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,30 @@ Run `kimi` with `--mcp-config-file` option to connect to the specified MCP serve kimi --mcp-config-file /path/to/mcp.json ``` +### Logging configuration + +Kimi CLI writes logs to `~/.kimi/logs/kimi.log`. By default the log level is `INFO` (or `TRACE` when `--debug` is passed). Use `-L/--log-level` to override a specific module or the default level: + +```sh +# Increase verbosity for tools while keeping the rest quiet +kimi -L kimi_cli.tools=DEBUG -L WARNING +``` + +You can also persist the mapping in `~/.kimi/config.json`: + +```json +{ + "logging": { + "levels": { + "default": "INFO", + "kosong": "WARNING" + } + } +} +``` + +Module names act as prefixes, so `kimi_cli.tools` applies to all nested modules under that namespace. + ## Development To develop Kimi CLI, run: diff --git a/src/kimi_cli/app.py b/src/kimi_cli/app.py index 530b4a44..7610d51e 100644 --- a/src/kimi_cli/app.py +++ b/src/kimi_cli/app.py @@ -9,7 +9,7 @@ from kimi_cli.agentspec import DEFAULT_AGENT_FILE from kimi_cli.cli import InputFormat, OutputFormat -from kimi_cli.config import LLMModel, LLMProvider, load_config +from kimi_cli.config import Config, LLMModel, LLMProvider, load_config from kimi_cli.llm import augment_provider_with_env_vars, create_llm from kimi_cli.session import Session from kimi_cli.soul.agent import load_agent @@ -28,6 +28,7 @@ async def create( stream: bool = True, # TODO: remove this when we have a correct print mode impl mcp_configs: list[dict[str, Any]] | None = None, config_file: Path | None = None, + config: Config | None = None, model_name: str | None = None, agent_file: Path | None = None, ) -> "KimiCLI": @@ -39,6 +40,7 @@ async def create( yolo (bool, optional): Approve all actions without confirmation. Defaults to False. stream (bool, optional): Use stream mode when calling LLM API. Defaults to True. config_file (Path | None, optional): Path to the configuration file. Defaults to None. + config (Config | None, optional): Preloaded configuration. Defaults to None. model_name (str | None, optional): Name of the model to use. Defaults to None. agent_file (Path | None, optional): Path to the agent file. Defaults to None. @@ -47,7 +49,7 @@ async def create( ConfigError(KimiCLIException): When the configuration is invalid. AgentSpecError(KimiCLIException): When the agent specification is invalid. """ - config = load_config(config_file) + config = config or load_config(config_file) logger.info("Loaded config: {config}", config=config) model: LLMModel | None = None diff --git a/src/kimi_cli/cli.py b/src/kimi_cli/cli.py index 8621dcb6..ec34241b 100644 --- a/src/kimi_cli/cli.py +++ b/src/kimi_cli/cli.py @@ -21,6 +21,10 @@ class Reload(Exception): OutputFormat = Literal["text", "stream-json"] +_LOG_LEVEL_OPTION = "--log-level" +_DEFAULT_LOG_LEVEL_KEY = "default" + + @click.command(context_settings=dict(help_option_names=["-h", "--help"])) @click.version_option(VERSION) @click.option( @@ -35,6 +39,16 @@ class Reload(Exception): default=False, help="Log debug information. Default: no.", ) +@click.option( + "--log-level", + "-L", + "log_level_override", + multiple=True, + help=( + "Override log level per module. Use `module=LEVEL` to target a specific module " + "(e.g. `-L kimi_cli.tools=DEBUG`) or just `LEVEL` to change the default level." + ), +) @click.option( "--agent-file", type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path), @@ -140,6 +154,7 @@ class Reload(Exception): def kimi( verbose: bool, debug: bool, + log_level_override: tuple[str, ...], agent_file: Path | None, model_name: str | None, work_dir: Path, @@ -156,8 +171,10 @@ def kimi( from kimi_cli.app import KimiCLI from kimi_cli.session import Session from kimi_cli.share import get_share_dir - from kimi_cli.utils.logging import logger + from kimi_cli.config import load_config + from kimi_cli.utils.logging import configure_file_logging, logger + config = load_config() def _noop_echo(*args: Any, **kwargs: Any): pass @@ -165,13 +182,18 @@ def _noop_echo(*args: Any, **kwargs: Any): if debug: logger.enable("kosong") - logger.add( - get_share_dir() / "logs" / "kimi.log", - # FIXME: configure level for different modules - level="TRACE" if debug else "INFO", - rotation="06:00", - retention="10 days", - ) + config_levels = dict(config.logging.levels) + cli_levels = _parse_log_level_overrides(log_level_override) + merged_levels = {**config_levels, **cli_levels} + base_level = "TRACE" if debug else "INFO" + try: + configure_file_logging( + get_share_dir() / "logs" / "kimi.log", + base_level=base_level, + module_levels=merged_levels, + ) + except ValueError as exc: + raise click.BadOptionUsage("--log-level", str(exc)) from exc work_dir = work_dir.absolute() if continue_: @@ -220,6 +242,7 @@ async def _run() -> bool: mcp_configs=mcp_configs, model_name=model_name, agent_file=agent_file, + config=config, ) match ui: case "shell": @@ -249,6 +272,39 @@ async def _run() -> bool: continue +def _parse_log_level_overrides(values: tuple[str, ...]) -> dict[str, str]: + overrides: dict[str, str] = {} + for raw in values: + entry = raw.strip() + if not entry: + raise click.BadOptionUsage(_LOG_LEVEL_OPTION, "Log level override cannot be empty") + if "=" in entry: + module, level = entry.split("=", 1) + module = module.strip() + if not module: + raise click.BadOptionUsage( + _LOG_LEVEL_OPTION, + "Module name is required before '=' when using --log-level", + ) + else: + module = _DEFAULT_LOG_LEVEL_KEY + level = entry + level = level.strip() + if not level: + raise click.BadOptionUsage(_LOG_LEVEL_OPTION, "Log level cannot be empty") + overrides[_normalize_module_key(module)] = level + return overrides + + +def _normalize_module_key(module: str) -> str: + cleaned = module.strip().rstrip(".") + if not cleaned: + return _DEFAULT_LOG_LEVEL_KEY + if cleaned.lower() == _DEFAULT_LOG_LEVEL_KEY: + return _DEFAULT_LOG_LEVEL_KEY + return cleaned + + def main(): kimi() diff --git a/src/kimi_cli/config.py b/src/kimi_cli/config.py index 3f4cc5d0..8a082a62 100644 --- a/src/kimi_cli/config.py +++ b/src/kimi_cli/config.py @@ -73,6 +73,31 @@ class Services(BaseModel): """Moonshot Search configuration.""" +class LoggingConfig(BaseModel): + """Logging configuration.""" + + levels: dict[str, str] = Field( + default_factory=dict, + description="Mapping of module prefixes to log levels.", + ) + + @model_validator(mode="after") + def validate_levels(self) -> "LoggingConfig": + normalized: dict[str, str] = {} + for module, level in self.levels.items(): + module_name = module.strip() + if not module_name: + raise ValueError("Module name in logging.levels cannot be empty") + level_name = level.strip().upper() + try: + logger.level(level_name) + except ValueError as exc: # pragma: no cover - loguru raises ValueError + raise ValueError(f"Invalid log level '{level_name}' for module '{module_name}'") from exc + normalized[module_name] = level_name + self.levels = normalized + return self + + class Config(BaseModel): """Main configuration structure.""" @@ -83,6 +108,7 @@ class Config(BaseModel): ) loop_control: LoopControl = Field(default_factory=LoopControl, description="Agent loop control") services: Services = Field(default_factory=Services, description="Services configuration") + logging: LoggingConfig = Field(default_factory=LoggingConfig, description="Logging configuration") @model_validator(mode="after") def validate_model(self) -> Self: @@ -106,6 +132,7 @@ def get_default_config() -> Config: models={}, providers={}, services=Services(), + logging=LoggingConfig(), ) diff --git a/src/kimi_cli/utils/logging.py b/src/kimi_cli/utils/logging.py index 1026c9f6..7cbfc17c 100644 --- a/src/kimi_cli/utils/logging.py +++ b/src/kimi_cli/utils/logging.py @@ -1,10 +1,97 @@ -from typing import IO +from __future__ import annotations + +from pathlib import Path +from typing import IO, Mapping from loguru import logger +MODULE_ROOTS = ("kimi_cli", "kosong") +DEFAULT_LEVEL_KEY = "default" + logger.remove() +def configure_file_logging( + log_file: Path, + *, + base_level: str, + module_levels: Mapping[str, str] | None = None, + rotation: str = "06:00", + retention: str = "10 days", +) -> None: + """Configure the global loguru logger with per-module filtering.""" + + logger.remove() + log_file.parent.mkdir(parents=True, exist_ok=True) + normalized_levels = _normalize_levels(module_levels or {}, base_level) + module_filter = _ModuleLevelFilter(normalized_levels) + logger.add( + log_file, + level="TRACE", # capture everything, filter decides what to keep + rotation=rotation, + retention=retention, + filter=module_filter, + ) + logger.debug("Configured log levels: {levels}", levels=normalized_levels) + + +def _normalize_levels(levels: Mapping[str, str], base_level: str) -> dict[str, int]: + normalized: dict[str, int] = {} + for module, level_name in levels.items(): + key = module.strip().rstrip(".") or DEFAULT_LEVEL_KEY + if key.lower() == DEFAULT_LEVEL_KEY: + key = DEFAULT_LEVEL_KEY + normalized[key] = _level_to_no(level_name) + if DEFAULT_LEVEL_KEY not in normalized: + normalized[DEFAULT_LEVEL_KEY] = _level_to_no(base_level) + return normalized + + +def _level_to_no(level_name: str) -> int: + normalized = level_name.strip().upper() + try: + return logger.level(normalized).no + except ValueError as exc: # pragma: no cover - loguru raises ValueError + raise ValueError(f"Invalid log level '{level_name}'") from exc + + +class _ModuleLevelFilter: + """Filter that enforces module-specific log levels.""" + + def __init__(self, levels: Mapping[str, int]) -> None: + self._levels = dict(levels) + self._module_keys = sorted( + (key for key in self._levels if key != DEFAULT_LEVEL_KEY), + key=len, + reverse=True, + ) + + def __call__(self, record: dict) -> bool: + module_path = self._derive_module_path(record) + threshold = self._resolve_threshold(module_path) + return record["level"].no >= threshold + + def _resolve_threshold(self, module_path: str | None) -> int: + if module_path: + for key in self._module_keys: + if module_path == key or module_path.startswith(f"{key}."): + return self._levels[key] + return self._levels[DEFAULT_LEVEL_KEY] + + @staticmethod + def _derive_module_path(record: dict) -> str | None: + file_info = record.get("file") + path_str = getattr(file_info, "path", None) + if not path_str: + return None + path = Path(path_str) + module_parts = path.with_suffix("").parts + for idx, part in enumerate(module_parts): + if part in MODULE_ROOTS: + return ".".join(module_parts[idx:]) + return record.get("module") + + class StreamToLogger(IO[str]): def __init__(self, level: str = "ERROR"): self._level = level diff --git a/tests/test_config.py b/tests/test_config.py index a1490333..9c7529a9 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,6 +2,7 @@ from kimi_cli.config import ( Config, + LoggingConfig, Services, get_default_config, ) @@ -15,6 +16,7 @@ def test_default_config(): models={}, providers={}, services=Services(), + logging=LoggingConfig(levels={}), ) ) @@ -31,7 +33,10 @@ def test_default_config_dump(): "max_steps_per_run": 100, "max_retries_per_step": 3 }, - "services": {} + "services": {}, + "logging": { + "levels": {} + } }\ """ ) diff --git a/tests/test_logging_levels.py b/tests/test_logging_levels.py new file mode 100644 index 00000000..f13a0d27 --- /dev/null +++ b/tests/test_logging_levels.py @@ -0,0 +1,53 @@ +import types + +import click +import pytest + +from kimi_cli.cli import _parse_log_level_overrides +from kimi_cli.utils.logging import _ModuleLevelFilter + + +def _make_record(path: str, level_no: int): + return { + "file": types.SimpleNamespace(path=path), + "module": path.rsplit("/", 1)[-1].split(".")[0], + "level": types.SimpleNamespace(no=level_no), + } + + +def test_parse_log_level_overrides_accepts_default_and_modules(): + overrides = _parse_log_level_overrides( + ( + "debug", + " kimi_cli.tools = warning ", + "kosong=TRACE", + ) + ) + assert overrides == { + "default": "debug", + "kimi_cli.tools": "warning", + "kosong": "TRACE", + } + + +def test_parse_log_level_overrides_rejects_missing_module(): + with pytest.raises(click.BadOptionUsage): + _parse_log_level_overrides(("=INFO",)) + + +def test_module_level_filter_prefers_more_specific_prefix(): + levels = { + "default": 30, + "kimi_cli.tools": 20, + "kimi_cli.tools.file": 10, + } + module_filter = _ModuleLevelFilter(levels) + + record = _make_record("/tmp/src/kimi_cli/tools/file/grep.py", 15) + assert module_filter(record) is True # threshold 10 + + record_low = _make_record("/tmp/src/kimi_cli/tools/file/grep.py", 5) + assert module_filter(record_low) is False + + record_default = _make_record("/tmp/src/kimi_cli/ui/app.py", 25) + assert module_filter(record_default) is False # default threshold 30 From b8eddb663dd421c1801554f84a07bcc341b7dfb6 Mon Sep 17 00:00:00 2001 From: yousali Date: Tue, 11 Nov 2025 19:35:10 +0800 Subject: [PATCH 2/6] fix: make per-module logging case-insensitive and document validation steps --- README.md | 2 +- src/kimi_cli/cli.py | 10 ++++--- src/kimi_cli/config.py | 14 +++++++--- src/kimi_cli/utils/logging.py | 23 ++++++++++------ tests/test_logging_levels.py | 49 ++++++++++++++++++++++++++++++----- 5 files changed, 76 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 0886be7d..fe44a1bc 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ You can also persist the mapping in `~/.kimi/config.json`: } ``` -Module names act as prefixes, so `kimi_cli.tools` applies to all nested modules under that namespace. +Module names act as prefixes and are case-insensitive, so `KIMI_CLI.Tools` works the same as `kimi_cli.tools` for all nested modules. ## Development diff --git a/src/kimi_cli/cli.py b/src/kimi_cli/cli.py index ec34241b..b4385e8c 100644 --- a/src/kimi_cli/cli.py +++ b/src/kimi_cli/cli.py @@ -169,12 +169,13 @@ def kimi( ): """Kimi, your next CLI agent.""" from kimi_cli.app import KimiCLI + from kimi_cli.config import load_config from kimi_cli.session import Session from kimi_cli.share import get_share_dir - from kimi_cli.config import load_config from kimi_cli.utils.logging import configure_file_logging, logger config = load_config() + def _noop_echo(*args: Any, **kwargs: Any): pass @@ -298,11 +299,12 @@ def _parse_log_level_overrides(values: tuple[str, ...]) -> dict[str, str]: def _normalize_module_key(module: str) -> str: cleaned = module.strip().rstrip(".") - if not cleaned: + normalized = cleaned.lower() + if not normalized: return _DEFAULT_LOG_LEVEL_KEY - if cleaned.lower() == _DEFAULT_LOG_LEVEL_KEY: + if normalized == _DEFAULT_LOG_LEVEL_KEY: return _DEFAULT_LOG_LEVEL_KEY - return cleaned + return normalized def main(): diff --git a/src/kimi_cli/config.py b/src/kimi_cli/config.py index 8a082a62..a04c5e2c 100644 --- a/src/kimi_cli/config.py +++ b/src/kimi_cli/config.py @@ -92,8 +92,13 @@ def validate_levels(self) -> "LoggingConfig": try: logger.level(level_name) except ValueError as exc: # pragma: no cover - loguru raises ValueError - raise ValueError(f"Invalid log level '{level_name}' for module '{module_name}'") from exc - normalized[module_name] = level_name + raise ValueError( + f"Invalid log level '{level_name}' for module '{module_name}'" + ) from exc + key = module_name.rstrip(".").lower() + if not key: + key = "default" + normalized[key] = level_name self.levels = normalized return self @@ -108,7 +113,10 @@ class Config(BaseModel): ) loop_control: LoopControl = Field(default_factory=LoopControl, description="Agent loop control") services: Services = Field(default_factory=Services, description="Services configuration") - logging: LoggingConfig = Field(default_factory=LoggingConfig, description="Logging configuration") + logging: LoggingConfig = Field( + default_factory=LoggingConfig, + description="Logging configuration", + ) @model_validator(mode="after") def validate_model(self) -> Self: diff --git a/src/kimi_cli/utils/logging.py b/src/kimi_cli/utils/logging.py index 7cbfc17c..b2bac3d0 100644 --- a/src/kimi_cli/utils/logging.py +++ b/src/kimi_cli/utils/logging.py @@ -1,10 +1,16 @@ from __future__ import annotations +from collections.abc import Mapping from pathlib import Path -from typing import IO, Mapping +from typing import IO, TYPE_CHECKING, Any from loguru import logger +if TYPE_CHECKING: + from loguru import Record +else: # pragma: no cover - runtime fallback for typing-only import + Record = dict[str, Any] # type: ignore[assignment] + MODULE_ROOTS = ("kimi_cli", "kosong") DEFAULT_LEVEL_KEY = "default" @@ -38,8 +44,8 @@ def configure_file_logging( def _normalize_levels(levels: Mapping[str, str], base_level: str) -> dict[str, int]: normalized: dict[str, int] = {} for module, level_name in levels.items(): - key = module.strip().rstrip(".") or DEFAULT_LEVEL_KEY - if key.lower() == DEFAULT_LEVEL_KEY: + key = module.strip().rstrip(".").lower() or DEFAULT_LEVEL_KEY + if key == DEFAULT_LEVEL_KEY: key = DEFAULT_LEVEL_KEY normalized[key] = _level_to_no(level_name) if DEFAULT_LEVEL_KEY not in normalized: @@ -66,7 +72,7 @@ def __init__(self, levels: Mapping[str, int]) -> None: reverse=True, ) - def __call__(self, record: dict) -> bool: + def __call__(self, record: Record) -> bool: module_path = self._derive_module_path(record) threshold = self._resolve_threshold(module_path) return record["level"].no >= threshold @@ -79,7 +85,7 @@ def _resolve_threshold(self, module_path: str | None) -> int: return self._levels[DEFAULT_LEVEL_KEY] @staticmethod - def _derive_module_path(record: dict) -> str | None: + def _derive_module_path(record: Record) -> str | None: file_info = record.get("file") path_str = getattr(file_info, "path", None) if not path_str: @@ -87,9 +93,10 @@ def _derive_module_path(record: dict) -> str | None: path = Path(path_str) module_parts = path.with_suffix("").parts for idx, part in enumerate(module_parts): - if part in MODULE_ROOTS: - return ".".join(module_parts[idx:]) - return record.get("module") + if part.lower() in MODULE_ROOTS: + return ".".join(module_parts[idx:]).lower() + module_name = record.get("module") + return module_name.lower() if module_name else None class StreamToLogger(IO[str]): diff --git a/tests/test_logging_levels.py b/tests/test_logging_levels.py index f13a0d27..73a872a9 100644 --- a/tests/test_logging_levels.py +++ b/tests/test_logging_levels.py @@ -1,4 +1,7 @@ import types +from datetime import datetime, timedelta +from pathlib import Path +from typing import TYPE_CHECKING, Any, cast import click import pytest @@ -6,13 +9,32 @@ from kimi_cli.cli import _parse_log_level_overrides from kimi_cli.utils.logging import _ModuleLevelFilter +if TYPE_CHECKING: + from loguru import Record +else: # pragma: no cover - typing fallback + Record = dict[str, Any] # type: ignore[assignment] -def _make_record(path: str, level_no: int): - return { - "file": types.SimpleNamespace(path=path), - "module": path.rsplit("/", 1)[-1].split(".")[0], - "level": types.SimpleNamespace(no=level_no), - } + +def _make_record(path: str, level_no: int) -> "Record": + module = Path(path).stem + return cast( + Record, + { + "elapsed": timedelta(), + "exception": None, + "extra": {}, + "file": types.SimpleNamespace(path=path, name=Path(path).name), + "function": "func", + "level": types.SimpleNamespace(name="X", no=level_no, icon=""), + "line": 0, + "message": "", + "module": module, + "name": None, + "process": types.SimpleNamespace(id=0, name="proc"), + "thread": types.SimpleNamespace(id=0, name="thread"), + "time": datetime.now(), + }, + ) def test_parse_log_level_overrides_accepts_default_and_modules(): @@ -30,6 +52,11 @@ def test_parse_log_level_overrides_accepts_default_and_modules(): } +def test_parse_log_level_overrides_is_case_insensitive(): + overrides = _parse_log_level_overrides(("KIMI_CLI.Tools=info",)) + assert overrides == {"kimi_cli.tools": "info"} + + def test_parse_log_level_overrides_rejects_missing_module(): with pytest.raises(click.BadOptionUsage): _parse_log_level_overrides(("=INFO",)) @@ -51,3 +78,13 @@ def test_module_level_filter_prefers_more_specific_prefix(): record_default = _make_record("/tmp/src/kimi_cli/ui/app.py", 25) assert module_filter(record_default) is False # default threshold 30 + + +def test_module_level_filter_is_case_insensitive(): + levels = { + "default": 30, + "kimi_cli.tools": 20, + } + module_filter = _ModuleLevelFilter(levels) + record = _make_record("/tmp/src/KIMI_CLI/Tools/file/grep.py", 25) + assert module_filter(record) is True From 856eb3216d608fc4b7e9290798f0df3e29be7536 Mon Sep 17 00:00:00 2001 From: yousali Date: Tue, 11 Nov 2025 23:01:00 +0800 Subject: [PATCH 3/6] fix format err --- src/kimi_cli/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kimi_cli/cli.py b/src/kimi_cli/cli.py index 2dccf7ac..f53627eb 100644 --- a/src/kimi_cli/cli.py +++ b/src/kimi_cli/cli.py @@ -1,9 +1,9 @@ import asyncio import json import sys -from collections.abc import Callable +from collections.abc import Callable, Iterable from pathlib import Path -from typing import Annotated, Any, Iterable, Literal +from typing import Annotated, Any, Literal import typer From f7c4b3284b2d014a99a65393da87237aac9b81a6 Mon Sep 17 00:00:00 2001 From: yousali Date: Wed, 12 Nov 2025 12:58:19 +0800 Subject: [PATCH 4/6] rm redundant logic --- src/kimi_cli/utils/logging.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/kimi_cli/utils/logging.py b/src/kimi_cli/utils/logging.py index b2bac3d0..8eef13d6 100644 --- a/src/kimi_cli/utils/logging.py +++ b/src/kimi_cli/utils/logging.py @@ -45,8 +45,6 @@ def _normalize_levels(levels: Mapping[str, str], base_level: str) -> dict[str, i normalized: dict[str, int] = {} for module, level_name in levels.items(): key = module.strip().rstrip(".").lower() or DEFAULT_LEVEL_KEY - if key == DEFAULT_LEVEL_KEY: - key = DEFAULT_LEVEL_KEY normalized[key] = _level_to_no(level_name) if DEFAULT_LEVEL_KEY not in normalized: normalized[DEFAULT_LEVEL_KEY] = _level_to_no(base_level) From 36f32e3bde49b215a5f1a73440e784cdaf64303a Mon Sep 17 00:00:00 2001 From: yousali Date: Wed, 12 Nov 2025 13:00:51 +0800 Subject: [PATCH 5/6] fix conflict --- src/kimi_cli/utils/logging.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/kimi_cli/utils/logging.py b/src/kimi_cli/utils/logging.py index e6033ef8..8eef13d6 100644 --- a/src/kimi_cli/utils/logging.py +++ b/src/kimi_cli/utils/logging.py @@ -1,12 +1,8 @@ from __future__ import annotations -<<<<<<< HEAD from collections.abc import Mapping from pathlib import Path from typing import IO, TYPE_CHECKING, Any -======= -from typing import IO ->>>>>>> upstream/main from loguru import logger From d1e5d43c081fce674b757a79a74f7fe569830480 Mon Sep 17 00:00:00 2001 From: yousali Date: Wed, 12 Nov 2025 13:04:21 +0800 Subject: [PATCH 6/6] fix ci err --- src/kimi_cli/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kimi_cli/config.py b/src/kimi_cli/config.py index aceb8109..930ffe2a 100644 --- a/src/kimi_cli/config.py +++ b/src/kimi_cli/config.py @@ -82,7 +82,7 @@ class LoggingConfig(BaseModel): ) @model_validator(mode="after") - def validate_levels(self) -> "LoggingConfig": + def validate_levels(self) -> LoggingConfig: normalized: dict[str, str] = {} for module, level in self.levels.items(): module_name = module.strip()