kimi-cli 0.41__tar.gz → 0.43__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kimi-cli might be problematic. Click here for more details.

Files changed (92) hide show
  1. {kimi_cli-0.41 → kimi_cli-0.43}/PKG-INFO +21 -20
  2. {kimi_cli-0.41 → kimi_cli-0.43}/README.md +2 -2
  3. {kimi_cli-0.41 → kimi_cli-0.43}/pyproject.toml +21 -20
  4. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/CHANGELOG.md +28 -0
  5. kimi_cli-0.43/src/kimi_cli/__init__.py +192 -0
  6. {kimi_cli-0.41/src/kimi_cli/agents/koder → kimi_cli-0.43/src/kimi_cli/agents/default}/agent.yaml +1 -1
  7. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/agentspec.py +19 -8
  8. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/cli.py +51 -37
  9. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/config.py +33 -14
  10. kimi_cli-0.43/src/kimi_cli/exception.py +16 -0
  11. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/llm.py +31 -3
  12. kimi_cli-0.43/src/kimi_cli/metadata.py +54 -0
  13. kimi_cli-0.43/src/kimi_cli/session.py +81 -0
  14. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/soul/__init__.py +22 -4
  15. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/soul/agent.py +18 -23
  16. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/soul/context.py +0 -5
  17. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/soul/kimisoul.py +40 -25
  18. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/soul/message.py +1 -1
  19. kimi_cli-0.41/src/kimi_cli/soul/globals.py → kimi_cli-0.43/src/kimi_cli/soul/runtime.py +13 -11
  20. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/glob.py +1 -1
  21. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/patch.py +1 -1
  22. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/read.py +1 -1
  23. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/replace.py +1 -1
  24. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/write.py +1 -1
  25. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/task/__init__.py +29 -21
  26. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/web/search.py +3 -0
  27. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/acp/__init__.py +24 -28
  28. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/print/__init__.py +27 -30
  29. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/shell/__init__.py +58 -42
  30. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/shell/keyboard.py +82 -14
  31. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/shell/metacmd.py +3 -8
  32. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/shell/prompt.py +208 -6
  33. kimi_cli-0.43/src/kimi_cli/ui/shell/replay.py +104 -0
  34. kimi_cli-0.43/src/kimi_cli/ui/shell/visualize.py +111 -0
  35. kimi_cli-0.43/src/kimi_cli/utils/message.py +22 -0
  36. kimi_cli-0.43/src/kimi_cli/utils/signals.py +41 -0
  37. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/utils/string.py +8 -0
  38. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/wire/__init__.py +13 -0
  39. kimi_cli-0.41/src/kimi_cli/__init__.py +0 -125
  40. kimi_cli-0.41/src/kimi_cli/agents/koder/README.md +0 -3
  41. kimi_cli-0.41/src/kimi_cli/metadata.py +0 -117
  42. kimi_cli-0.41/src/kimi_cli/ui/shell/visualize.py +0 -114
  43. kimi_cli-0.41/src/kimi_cli/utils/message.py +0 -8
  44. {kimi_cli-0.41/src/kimi_cli/agents/koder → kimi_cli-0.43/src/kimi_cli/agents/default}/sub.yaml +0 -0
  45. {kimi_cli-0.41/src/kimi_cli/agents/koder → kimi_cli-0.43/src/kimi_cli/agents/default}/system.md +0 -0
  46. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/constant.py +0 -0
  47. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/prompts/__init__.py +0 -0
  48. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/prompts/compact.md +0 -0
  49. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/prompts/init.md +0 -0
  50. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/py.typed +0 -0
  51. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/share.py +0 -0
  52. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/soul/approval.py +0 -0
  53. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/soul/compaction.py +0 -0
  54. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/soul/denwarenji.py +0 -0
  55. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/soul/toolset.py +0 -0
  56. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/__init__.py +0 -0
  57. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/bash/__init__.py +0 -0
  58. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/bash/bash.md +0 -0
  59. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/dmail/__init__.py +0 -0
  60. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/dmail/dmail.md +0 -0
  61. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/__init__.py +0 -0
  62. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/glob.md +0 -0
  63. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/grep.md +0 -0
  64. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/grep.py +0 -0
  65. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/patch.md +0 -0
  66. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/read.md +0 -0
  67. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/replace.md +0 -0
  68. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/file/write.md +0 -0
  69. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/mcp.py +0 -0
  70. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/task/task.md +0 -0
  71. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/test.py +0 -0
  72. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/think/__init__.py +0 -0
  73. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/think/think.md +0 -0
  74. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/todo/__init__.py +0 -0
  75. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/todo/set_todo_list.md +0 -0
  76. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/utils.py +0 -0
  77. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/web/__init__.py +0 -0
  78. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/web/fetch.md +0 -0
  79. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/web/fetch.py +0 -0
  80. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/tools/web/search.md +0 -0
  81. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/__init__.py +0 -0
  82. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/shell/console.py +0 -0
  83. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/shell/debug.py +0 -0
  84. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/shell/liveview.py +0 -0
  85. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/shell/setup.py +0 -0
  86. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/ui/shell/update.py +0 -0
  87. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/utils/aiohttp.py +0 -0
  88. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/utils/changelog.py +0 -0
  89. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/utils/logging.py +0 -0
  90. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/utils/path.py +0 -0
  91. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/utils/pyinstaller.py +0 -0
  92. {kimi_cli-0.41 → kimi_cli-0.43}/src/kimi_cli/wire/message.py +0 -0
@@ -1,24 +1,25 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kimi-cli
3
- Version: 0.41
3
+ Version: 0.43
4
4
  Summary: Kimi CLI is your next CLI agent.
5
- Requires-Dist: agent-client-protocol>=0.4.9
6
- Requires-Dist: aiofiles>=25.1.0
7
- Requires-Dist: aiohttp>=3.13.1
8
- Requires-Dist: click>=8.3.0
9
- Requires-Dist: kosong>=0.15.0
10
- Requires-Dist: loguru>=0.7.3
11
- Requires-Dist: patch-ng>=1.19.0
12
- Requires-Dist: prompt-toolkit>=3.0.52
13
- Requires-Dist: pyyaml>=6.0.3
14
- Requires-Dist: rich>=14.2.0
15
- Requires-Dist: ripgrepy>=2.2.0
16
- Requires-Dist: streamingjson>=0.0.5
17
- Requires-Dist: trafilatura>=2.0.0
18
- Requires-Dist: tenacity>=9.1.2
19
- Requires-Dist: fastmcp>=2.12.5
20
- Requires-Dist: pydantic>=2.12.3
21
- Requires-Dist: httpx[socks]>=0.28.0
5
+ Requires-Dist: agent-client-protocol==0.6.2
6
+ Requires-Dist: aiofiles==25.1.0
7
+ Requires-Dist: aiohttp==3.13.2
8
+ Requires-Dist: click==8.3.0
9
+ Requires-Dist: kosong==0.16.1
10
+ Requires-Dist: loguru==0.7.3
11
+ Requires-Dist: patch-ng==1.19.0
12
+ Requires-Dist: prompt-toolkit==3.0.52
13
+ Requires-Dist: pillow==12.0.0
14
+ Requires-Dist: pyyaml==6.0.3
15
+ Requires-Dist: rich==14.2.0
16
+ Requires-Dist: ripgrepy==2.2.0
17
+ Requires-Dist: streamingjson==0.0.5
18
+ Requires-Dist: trafilatura==2.0.0
19
+ Requires-Dist: tenacity==9.1.2
20
+ Requires-Dist: fastmcp==2.12.5
21
+ Requires-Dist: pydantic==2.12.3
22
+ Requires-Dist: httpx[socks]==0.28.1
22
23
  Requires-Python: >=3.13
23
24
  Description-Content-Type: text/markdown
24
25
 
@@ -84,7 +85,7 @@ After setup, Kimi CLI will be ready to use. You can send `/help` to get more inf
84
85
 
85
86
  ### Shell mode
86
87
 
87
- Kimi CLI is not only a coding agent, but also a shell. You can switch the mode by pressing `Ctrl-K`. In shell mode, you can directly run shell commands without leaving Kimi CLI.
88
+ Kimi CLI is not only a coding agent, but also a shell. You can switch the mode by pressing `Ctrl-X`. In shell mode, you can directly run shell commands without leaving Kimi CLI.
88
89
 
89
90
  > [!NOTE]
90
91
  > Built-in shell commands like `cd` are not supported yet.
@@ -109,7 +110,7 @@ Then add `kimi-cli` to your Zsh plugin list in `~/.zshrc`:
109
110
  plugins=(... kimi-cli)
110
111
  ```
111
112
 
112
- After restarting Zsh, you can switch to agent mode by pressing `Ctrl-K`.
113
+ After restarting Zsh, you can switch to agent mode by pressing `Ctrl-X`.
113
114
 
114
115
  ### ACP support
115
116
 
@@ -60,7 +60,7 @@ After setup, Kimi CLI will be ready to use. You can send `/help` to get more inf
60
60
 
61
61
  ### Shell mode
62
62
 
63
- Kimi CLI is not only a coding agent, but also a shell. You can switch the mode by pressing `Ctrl-K`. In shell mode, you can directly run shell commands without leaving Kimi CLI.
63
+ Kimi CLI is not only a coding agent, but also a shell. You can switch the mode by pressing `Ctrl-X`. In shell mode, you can directly run shell commands without leaving Kimi CLI.
64
64
 
65
65
  > [!NOTE]
66
66
  > Built-in shell commands like `cd` are not supported yet.
@@ -85,7 +85,7 @@ Then add `kimi-cli` to your Zsh plugin list in `~/.zshrc`:
85
85
  plugins=(... kimi-cli)
86
86
  ```
87
87
 
88
- After restarting Zsh, you can switch to agent mode by pressing `Ctrl-K`.
88
+ After restarting Zsh, you can switch to agent mode by pressing `Ctrl-X`.
89
89
 
90
90
  ### ACP support
91
91
 
@@ -1,34 +1,35 @@
1
1
  [project]
2
2
  name = "kimi-cli"
3
- version = "0.41"
3
+ version = "0.43"
4
4
  description = "Kimi CLI is your next CLI agent."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
7
7
  dependencies = [
8
- "agent-client-protocol>=0.4.9",
9
- "aiofiles>=25.1.0",
10
- "aiohttp>=3.13.1",
11
- "click>=8.3.0",
12
- "kosong>=0.15.0",
13
- "loguru>=0.7.3",
14
- "patch-ng>=1.19.0",
15
- "prompt-toolkit>=3.0.52",
16
- "pyyaml>=6.0.3",
17
- "rich>=14.2.0",
18
- "ripgrepy>=2.2.0",
19
- "streamingjson>=0.0.5",
20
- "trafilatura>=2.0.0",
21
- "tenacity>=9.1.2",
22
- "fastmcp>=2.12.5",
23
- "pydantic>=2.12.3",
24
- "httpx[socks]>=0.28.0",
8
+ "agent-client-protocol==0.6.2",
9
+ "aiofiles==25.1.0",
10
+ "aiohttp==3.13.2",
11
+ "click==8.3.0",
12
+ "kosong==0.16.1",
13
+ "loguru==0.7.3",
14
+ "patch-ng==1.19.0",
15
+ "prompt-toolkit==3.0.52",
16
+ "pillow==12.0.0",
17
+ "pyyaml==6.0.3",
18
+ "rich==14.2.0",
19
+ "ripgrepy==2.2.0",
20
+ "streamingjson==0.0.5",
21
+ "trafilatura==2.0.0",
22
+ "tenacity==9.1.2",
23
+ "fastmcp==2.12.5",
24
+ "pydantic==2.12.3",
25
+ "httpx[socks]==0.28.1",
25
26
  ]
26
27
 
27
28
  [dependency-groups]
28
29
  dev = [
29
- "inline-snapshot[black]>=0.30.1",
30
+ "inline-snapshot[black]>=0.31.0",
30
31
  "pyinstaller>=6.16.0",
31
- "pyright>=1.1.406",
32
+ "pyright>=1.1.407",
32
33
  "pytest>=8.4.2",
33
34
  "pytest-asyncio>=1.2.0",
34
35
  "ruff>=0.14.1",
@@ -9,6 +9,34 @@ Internal builds may append content to the Unreleased section.
9
9
  Only write entries that are worth mentioning to users.
10
10
  -->
11
11
 
12
+ ## [0.43] - 2025-10-30
13
+
14
+ ### Added
15
+
16
+ - Basic Windows support (experimental)
17
+ - Display warnings when base URL or API key is overridden in environment variables
18
+ - Support image input if the LLM model supports it
19
+ - Replay recent context history when continuing a session
20
+
21
+ ### Fixed
22
+
23
+ - Ensure new line after executing shell commands
24
+
25
+ ## [0.42] - 2025-10-28
26
+
27
+ ### Added
28
+
29
+ - Support Ctrl-J or Alt-Enter to insert a new line
30
+
31
+ ### Changed
32
+
33
+ - Change mode switch shortcut from Ctrl-K to Ctrl-X
34
+ - Improve overall robustness
35
+
36
+ ### Fixed
37
+
38
+ - Fix ACP server `no attribute` error
39
+
12
40
  ## [0.41] - 2025-10-26
13
41
 
14
42
  ### Fixed
@@ -0,0 +1,192 @@
1
+ import contextlib
2
+ import os
3
+ import warnings
4
+ from collections.abc import Generator
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from pydantic import SecretStr
9
+
10
+ from kimi_cli.agentspec import DEFAULT_AGENT_FILE
11
+ from kimi_cli.config import LLMModel, LLMProvider, load_config
12
+ from kimi_cli.llm import augment_provider_with_env_vars, create_llm
13
+ from kimi_cli.session import Session
14
+ from kimi_cli.soul.agent import load_agent
15
+ from kimi_cli.soul.context import Context
16
+ from kimi_cli.soul.kimisoul import KimiSoul
17
+ from kimi_cli.soul.runtime import Runtime
18
+ from kimi_cli.ui.acp import ACPServer
19
+ from kimi_cli.ui.print import InputFormat, OutputFormat, PrintApp
20
+ from kimi_cli.ui.shell import ShellApp, WelcomeInfoItem
21
+ from kimi_cli.utils.logging import StreamToLogger, logger
22
+
23
+
24
+ class KimiCLI:
25
+ @staticmethod
26
+ async def create(
27
+ session: Session,
28
+ *,
29
+ yolo: bool = False,
30
+ stream: bool = True, # TODO: remove this when we have a correct print mode impl
31
+ mcp_configs: list[dict[str, Any]] | None = None,
32
+ config_file: Path | None = None,
33
+ model_name: str | None = None,
34
+ agent_file: Path | None = None,
35
+ ) -> "KimiCLI":
36
+ """
37
+ Create a KimiCLI instance.
38
+
39
+ Args:
40
+ session (Session): A session created by `Session.create` or `Session.continue_`.
41
+ yolo (bool, optional): Approve all actions without confirmation. Defaults to False.
42
+ stream (bool, optional): Use stream mode when calling LLM API. Defaults to True.
43
+ config_file (Path | None, optional): Path to the configuration file. Defaults to None.
44
+ model_name (str | None, optional): Name of the model to use. Defaults to None.
45
+ agent_file (Path | None, optional): Path to the agent file. Defaults to None.
46
+
47
+ Raises:
48
+ FileNotFoundError: When the agent file is not found.
49
+ ConfigError(KimiCLIException): When the configuration is invalid.
50
+ AgentSpecError(KimiCLIException): When the agent specification is invalid.
51
+ """
52
+ config = load_config(config_file)
53
+ logger.info("Loaded config: {config}", config=config)
54
+
55
+ model: LLMModel | None = None
56
+ provider: LLMProvider | None = None
57
+
58
+ # try to use config file
59
+ if not model_name and config.default_model:
60
+ # no --model specified && default model is set in config
61
+ model = config.models[config.default_model]
62
+ provider = config.providers[model.provider]
63
+ if model_name and model_name in config.models:
64
+ # --model specified && model is set in config
65
+ model = config.models[model_name]
66
+ provider = config.providers[model.provider]
67
+
68
+ if not model:
69
+ model = LLMModel(provider="", model="", max_context_size=100_000)
70
+ provider = LLMProvider(type="kimi", base_url="", api_key=SecretStr(""))
71
+
72
+ # try overwrite with environment variables
73
+ assert provider is not None
74
+ assert model is not None
75
+ env_overrides = augment_provider_with_env_vars(provider, model)
76
+
77
+ if not provider.base_url or not model.model:
78
+ llm = None
79
+ else:
80
+ logger.info("Using LLM provider: {provider}", provider=provider)
81
+ logger.info("Using LLM model: {model}", model=model)
82
+ llm = create_llm(provider, model, stream=stream, session_id=session.id)
83
+
84
+ welcome_info = [
85
+ WelcomeInfoItem(name="Directory", value=str(session.work_dir)),
86
+ WelcomeInfoItem(name="Session", value=session.id),
87
+ ]
88
+ if base_url := env_overrides.get("KIMI_BASE_URL"):
89
+ welcome_info.append(
90
+ WelcomeInfoItem(
91
+ name="API URL",
92
+ value=f"{base_url} (from KIMI_BASE_URL)",
93
+ level=WelcomeInfoItem.Level.WARN,
94
+ )
95
+ )
96
+ if not llm:
97
+ welcome_info.append(
98
+ WelcomeInfoItem(
99
+ name="Model",
100
+ value="not set, send /setup to configure",
101
+ level=WelcomeInfoItem.Level.WARN,
102
+ )
103
+ )
104
+ elif "KIMI_MODEL_NAME" in env_overrides:
105
+ welcome_info.append(
106
+ WelcomeInfoItem(
107
+ name="Model",
108
+ value=f"{model.model} (from KIMI_MODEL_NAME)",
109
+ level=WelcomeInfoItem.Level.WARN,
110
+ )
111
+ )
112
+ else:
113
+ welcome_info.append(
114
+ WelcomeInfoItem(
115
+ name="Model",
116
+ value=model.model,
117
+ level=WelcomeInfoItem.Level.INFO,
118
+ )
119
+ )
120
+
121
+ runtime = await Runtime.create(config, llm, session, yolo)
122
+
123
+ if agent_file is None:
124
+ agent_file = DEFAULT_AGENT_FILE
125
+ agent = await load_agent(agent_file, runtime, mcp_configs=mcp_configs or [])
126
+
127
+ context = Context(session.history_file)
128
+ await context.restore()
129
+
130
+ soul = KimiSoul(
131
+ agent,
132
+ runtime,
133
+ context=context,
134
+ )
135
+ return KimiCLI(soul, session, welcome_info)
136
+
137
+ def __init__(
138
+ self,
139
+ soul: KimiSoul,
140
+ session: Session,
141
+ welcome_info: list[WelcomeInfoItem],
142
+ ) -> None:
143
+ self._soul = soul
144
+ self._session = session
145
+ self._welcome_info = welcome_info
146
+
147
+ @property
148
+ def soul(self) -> KimiSoul:
149
+ """Get the KimiSoul instance."""
150
+ return self._soul
151
+
152
+ @property
153
+ def session(self) -> Session:
154
+ """Get the Session instance."""
155
+ return self._session
156
+
157
+ @contextlib.contextmanager
158
+ def _app_env(self) -> Generator[None]:
159
+ original_cwd = Path.cwd()
160
+ os.chdir(self._session.work_dir)
161
+ try:
162
+ # to ignore possible warnings from dateparser
163
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
164
+ with contextlib.redirect_stderr(StreamToLogger()):
165
+ yield
166
+ finally:
167
+ os.chdir(original_cwd)
168
+
169
+ async def run_shell_mode(self, command: str | None = None) -> bool:
170
+ with self._app_env():
171
+ app = ShellApp(self._soul, welcome_info=self._welcome_info)
172
+ return await app.run(command)
173
+
174
+ async def run_print_mode(
175
+ self,
176
+ input_format: InputFormat,
177
+ output_format: OutputFormat,
178
+ command: str | None = None,
179
+ ) -> bool:
180
+ with self._app_env():
181
+ app = PrintApp(
182
+ self._soul,
183
+ input_format,
184
+ output_format,
185
+ self._session.history_file,
186
+ )
187
+ return await app.run(command)
188
+
189
+ async def run_acp_server(self) -> bool:
190
+ with self._app_env():
191
+ app = ACPServer(self._soul)
192
+ return await app.run()
@@ -19,6 +19,6 @@ agent:
19
19
  - "kimi_cli.tools.web:SearchWeb"
20
20
  - "kimi_cli.tools.web:FetchURL"
21
21
  subagents:
22
- koder:
22
+ coder:
23
23
  path: ./sub.yaml
24
24
  description: "Good at general software engineering tasks."
@@ -4,12 +4,14 @@ from typing import Any, NamedTuple
4
4
  import yaml
5
5
  from pydantic import BaseModel, Field
6
6
 
7
+ from kimi_cli.exception import AgentSpecError
8
+
7
9
 
8
10
  def get_agents_dir() -> Path:
9
11
  return Path(__file__).parent / "agents"
10
12
 
11
13
 
12
- DEFAULT_AGENT_FILE = get_agents_dir() / "koder" / "agent.yaml"
14
+ DEFAULT_AGENT_FILE = get_agents_dir() / "default" / "agent.yaml"
13
15
 
14
16
 
15
17
  class AgentSpec(BaseModel):
@@ -47,15 +49,21 @@ class ResolvedAgentSpec(NamedTuple):
47
49
 
48
50
 
49
51
  def load_agent_spec(agent_file: Path) -> ResolvedAgentSpec:
50
- """Load agent specification from file."""
52
+ """
53
+ Load agent specification from file.
54
+
55
+ Raises:
56
+ FileNotFoundError: If the agent spec file is not found.
57
+ AgentSpecError: If the agent spec is not valid.
58
+ """
51
59
  agent_spec = _load_agent_spec(agent_file)
52
60
  assert agent_spec.extend is None, "agent extension should be recursively resolved"
53
61
  if agent_spec.name is None:
54
- raise ValueError("Agent name is required")
62
+ raise AgentSpecError("Agent name is required")
55
63
  if agent_spec.system_prompt_path is None:
56
- raise ValueError("System prompt path is required")
64
+ raise AgentSpecError("System prompt path is required")
57
65
  if agent_spec.tools is None:
58
- raise ValueError("Tools are required")
66
+ raise AgentSpecError("Tools are required")
59
67
  return ResolvedAgentSpec(
60
68
  name=agent_spec.name,
61
69
  system_prompt_path=agent_spec.system_prompt_path,
@@ -68,12 +76,15 @@ def load_agent_spec(agent_file: Path) -> ResolvedAgentSpec:
68
76
 
69
77
  def _load_agent_spec(agent_file: Path) -> AgentSpec:
70
78
  assert agent_file.is_file(), "expect agent file to exist"
71
- with open(agent_file, encoding="utf-8") as f:
72
- data: dict[str, Any] = yaml.safe_load(f)
79
+ try:
80
+ with open(agent_file, encoding="utf-8") as f:
81
+ data: dict[str, Any] = yaml.safe_load(f)
82
+ except yaml.YAMLError as e:
83
+ raise AgentSpecError(f"Invalid YAML in agent spec file: {e}") from e
73
84
 
74
85
  version = data.get("version", 1)
75
86
  if version != 1:
76
- raise ValueError(f"Unsupported agent spec version: {version}")
87
+ raise AgentSpecError(f"Unsupported agent spec version: {version}")
77
88
 
78
89
  agent_spec = AgentSpec(**data.get("agent", {}))
79
90
  if agent_spec.system_prompt_path is not None:
@@ -1,15 +1,15 @@
1
1
  import asyncio
2
2
  import json
3
3
  import sys
4
+ from collections.abc import Callable
4
5
  from pathlib import Path
6
+ from typing import Any, Literal, get_args
5
7
 
6
8
  import click
7
9
 
8
- from kimi_cli import UIMode, kimi_run
9
- from kimi_cli.agentspec import DEFAULT_AGENT_FILE
10
- from kimi_cli.config import ConfigError, load_config
10
+ from kimi_cli import KimiCLI
11
11
  from kimi_cli.constant import VERSION
12
- from kimi_cli.metadata import continue_session, new_session
12
+ from kimi_cli.session import Session
13
13
  from kimi_cli.share import get_share_dir
14
14
  from kimi_cli.ui.print import InputFormat, OutputFormat
15
15
  from kimi_cli.utils.logging import logger
@@ -21,6 +21,9 @@ class Reload(Exception):
21
21
  pass
22
22
 
23
23
 
24
+ UIMode = Literal["shell", "print", "acp"]
25
+
26
+
24
27
  @click.command(context_settings=dict(help_option_names=["-h", "--help"]))
25
28
  @click.version_option(VERSION)
26
29
  @click.option(
@@ -38,8 +41,8 @@ class Reload(Exception):
38
41
  @click.option(
39
42
  "--agent-file",
40
43
  type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
41
- default=DEFAULT_AGENT_FILE,
42
- help="Custom agent specification file. Default: builtin Kimi Koder.",
44
+ default=None,
45
+ help="Custom agent specification file. Default: builtin default agent.",
43
46
  )
44
47
  @click.option(
45
48
  "--model",
@@ -77,7 +80,7 @@ class Reload(Exception):
77
80
  @click.option(
78
81
  "--ui",
79
82
  "ui",
80
- type=click.Choice(["shell", "print", "acp"]),
83
+ type=click.Choice(get_args(UIMode)),
81
84
  default="shell",
82
85
  help="UI mode to use. Default: shell.",
83
86
  )
@@ -85,7 +88,7 @@ class Reload(Exception):
85
88
  "--print",
86
89
  "ui",
87
90
  flag_value="print",
88
- help="Run in print mode. Shortcut for `--ui print`.",
91
+ help="Run in print mode. Shortcut for `--ui print`. Note: print mode implicitly adds `--yolo`.",
89
92
  )
90
93
  @click.option(
91
94
  "--acp",
@@ -95,7 +98,7 @@ class Reload(Exception):
95
98
  )
96
99
  @click.option(
97
100
  "--input-format",
98
- type=click.Choice(["text", "stream-json"]),
101
+ type=click.Choice(get_args(InputFormat)),
99
102
  default=None,
100
103
  help=(
101
104
  "Input format to use. Must be used with `--print` "
@@ -105,7 +108,7 @@ class Reload(Exception):
105
108
  )
106
109
  @click.option(
107
110
  "--output-format",
108
- type=click.Choice(["text", "stream-json"]),
111
+ type=click.Choice(get_args(OutputFormat)),
109
112
  default=None,
110
113
  help="Output format to use. Must be used with `--print`. Default: text.",
111
114
  )
@@ -140,7 +143,7 @@ class Reload(Exception):
140
143
  def kimi(
141
144
  verbose: bool,
142
145
  debug: bool,
143
- agent_file: Path,
146
+ agent_file: Path | None,
144
147
  model_name: str | None,
145
148
  work_dir: Path,
146
149
  continue_: bool,
@@ -153,7 +156,11 @@ def kimi(
153
156
  yolo: bool,
154
157
  ):
155
158
  """Kimi, your next CLI agent."""
156
- echo = click.echo if verbose else lambda *args, **kwargs: None
159
+
160
+ def _noop_echo(*args: Any, **kwargs: Any):
161
+ pass
162
+
163
+ echo: Callable[..., None] = click.echo if verbose else _noop_echo
157
164
 
158
165
  logger.add(
159
166
  get_share_dir() / "logs" / "kimi.log",
@@ -163,19 +170,23 @@ def kimi(
163
170
  )
164
171
 
165
172
  work_dir = work_dir.absolute()
166
-
167
173
  if continue_:
168
- session = continue_session(work_dir)
174
+ session = Session.continue_(work_dir)
169
175
  if session is None:
170
176
  raise click.BadOptionUsage(
171
177
  "--continue", "No previous session found for the working directory"
172
178
  )
173
179
  echo(f"✓ Continuing previous session: {session.id}")
174
180
  else:
175
- session = new_session(work_dir)
181
+ session = Session.create(work_dir)
176
182
  echo(f"✓ Created new session: {session.id}")
177
183
  echo(f"✓ Session history file: {session.history_file}")
178
184
 
185
+ if command is not None:
186
+ command = command.strip()
187
+ if not command:
188
+ raise click.BadOptionUsage("--command", "Command cannot be empty")
189
+
179
190
  if input_format is not None and ui != "print":
180
191
  raise click.BadOptionUsage(
181
192
  "--input-format",
@@ -188,7 +199,7 @@ def kimi(
188
199
  )
189
200
 
190
201
  try:
191
- mcp_configs = [json.loads(conf.read_text()) for conf in mcp_config_file]
202
+ mcp_configs = [json.loads(conf.read_text(encoding="utf-8")) for conf in mcp_config_file]
192
203
  except json.JSONDecodeError as e:
193
204
  raise click.BadOptionUsage("--mcp-config-file", f"Invalid JSON: {e}") from e
194
205
 
@@ -197,29 +208,32 @@ def kimi(
197
208
  except json.JSONDecodeError as e:
198
209
  raise click.BadOptionUsage("--mcp-config", f"Invalid JSON: {e}") from e
199
210
 
211
+ async def _run() -> bool:
212
+ instance = await KimiCLI.create(
213
+ session,
214
+ yolo=yolo or (ui == "print"), # print mode implies yolo
215
+ stream=ui != "print", # use non-streaming mode only for print UI
216
+ mcp_configs=mcp_configs,
217
+ model_name=model_name,
218
+ agent_file=agent_file,
219
+ )
220
+ match ui:
221
+ case "shell":
222
+ return await instance.run_shell_mode(command)
223
+ case "print":
224
+ return await instance.run_print_mode(
225
+ input_format or "text",
226
+ output_format or "text",
227
+ command,
228
+ )
229
+ case "acp":
230
+ if command is not None:
231
+ logger.warning("ACP server ignores command argument")
232
+ return await instance.run_acp_server()
233
+
200
234
  while True:
201
235
  try:
202
- try:
203
- config = load_config()
204
- except ConfigError as e:
205
- raise click.ClickException(f"Failed to load config: {e}") from e
206
- echo(f"✓ Loaded config: {config}")
207
-
208
- succeeded = asyncio.run(
209
- kimi_run(
210
- config=config,
211
- model_name=model_name,
212
- work_dir=work_dir,
213
- session=session,
214
- command=command,
215
- agent_file=agent_file,
216
- ui=ui,
217
- input_format=input_format,
218
- output_format=output_format,
219
- mcp_configs=mcp_configs,
220
- yolo=yolo,
221
- )
222
- )
236
+ succeeded = asyncio.run(_run())
223
237
  if not succeeded:
224
238
  sys.exit(1)
225
239
  break