kimi-cli 0.40__tar.gz → 0.41__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 (89) hide show
  1. {kimi_cli-0.40 → kimi_cli-0.41}/PKG-INFO +34 -1
  2. {kimi_cli-0.40 → kimi_cli-0.41}/README.md +33 -0
  3. {kimi_cli-0.40 → kimi_cli-0.41}/pyproject.toml +8 -2
  4. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/CHANGELOG.md +12 -0
  5. kimi_cli-0.41/src/kimi_cli/__init__.py +125 -0
  6. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/agents/koder/system.md +1 -1
  7. kimi_cli-0.41/src/kimi_cli/agentspec.py +104 -0
  8. kimi_cli-0.40/src/kimi_cli/__init__.py → kimi_cli-0.41/src/kimi_cli/cli.py +12 -164
  9. kimi_cli-0.41/src/kimi_cli/constant.py +4 -0
  10. kimi_cli-0.40/src/kimi_cli/utils/provider.py → kimi_cli-0.41/src/kimi_cli/llm.py +12 -5
  11. kimi_cli-0.41/src/kimi_cli/prompts/__init__.py +4 -0
  12. kimi_cli-0.41/src/kimi_cli/soul/__init__.py +155 -0
  13. kimi_cli-0.41/src/kimi_cli/soul/agent.py +157 -0
  14. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/soul/approval.py +1 -1
  15. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/soul/compaction.py +4 -4
  16. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/soul/context.py +5 -0
  17. kimi_cli-0.41/src/kimi_cli/soul/globals.py +92 -0
  18. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/soul/kimisoul.py +21 -26
  19. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/dmail/__init__.py +1 -1
  20. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/glob.md +1 -1
  21. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/glob.py +2 -2
  22. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/grep.py +1 -1
  23. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/patch.py +2 -2
  24. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/read.py +1 -1
  25. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/replace.py +2 -2
  26. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/write.py +2 -2
  27. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/task/__init__.py +23 -22
  28. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/task/task.md +1 -1
  29. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/todo/__init__.py +1 -1
  30. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/utils.py +1 -1
  31. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/web/search.py +2 -2
  32. kimi_cli-0.41/src/kimi_cli/ui/__init__.py +0 -0
  33. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/acp/__init__.py +8 -9
  34. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/print/__init__.py +17 -35
  35. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/shell/__init__.py +5 -13
  36. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/shell/liveview.py +1 -1
  37. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/shell/metacmd.py +3 -3
  38. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/shell/setup.py +5 -5
  39. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/shell/update.py +2 -2
  40. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/shell/visualize.py +10 -7
  41. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/utils/changelog.py +3 -1
  42. kimi_cli-0.41/src/kimi_cli/wire/__init__.py +57 -0
  43. kimi_cli-0.40/src/kimi_cli/soul/wire.py → kimi_cli-0.41/src/kimi_cli/wire/message.py +4 -39
  44. kimi_cli-0.40/src/kimi_cli/agent.py +0 -261
  45. kimi_cli-0.40/src/kimi_cli/llm.py +0 -8
  46. kimi_cli-0.40/src/kimi_cli/prompts/__init__.py +0 -4
  47. kimi_cli-0.40/src/kimi_cli/soul/__init__.py +0 -59
  48. kimi_cli-0.40/src/kimi_cli/ui/__init__.py +0 -69
  49. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/agents/koder/README.md +0 -0
  50. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/agents/koder/agent.yaml +0 -0
  51. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/agents/koder/sub.yaml +0 -0
  52. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/config.py +0 -0
  53. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/metadata.py +0 -0
  54. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/prompts/compact.md +0 -0
  55. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/prompts/init.md +0 -0
  56. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/py.typed +0 -0
  57. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/share.py +0 -0
  58. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/soul/denwarenji.py +0 -0
  59. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/soul/message.py +0 -0
  60. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/soul/toolset.py +0 -0
  61. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/__init__.py +0 -0
  62. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/bash/__init__.py +0 -0
  63. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/bash/bash.md +0 -0
  64. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/dmail/dmail.md +0 -0
  65. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/__init__.py +0 -0
  66. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/grep.md +0 -0
  67. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/patch.md +0 -0
  68. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/read.md +0 -0
  69. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/replace.md +0 -0
  70. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/file/write.md +0 -0
  71. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/mcp.py +0 -0
  72. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/test.py +0 -0
  73. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/think/__init__.py +0 -0
  74. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/think/think.md +0 -0
  75. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/todo/set_todo_list.md +0 -0
  76. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/web/__init__.py +0 -0
  77. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/web/fetch.md +0 -0
  78. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/web/fetch.py +0 -0
  79. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/tools/web/search.md +0 -0
  80. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/shell/console.py +0 -0
  81. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/shell/debug.py +0 -0
  82. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/shell/keyboard.py +0 -0
  83. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/ui/shell/prompt.py +0 -0
  84. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/utils/aiohttp.py +0 -0
  85. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/utils/logging.py +0 -0
  86. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/utils/message.py +0 -0
  87. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/utils/path.py +0 -0
  88. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/utils/pyinstaller.py +0 -0
  89. {kimi_cli-0.40 → kimi_cli-0.41}/src/kimi_cli/utils/string.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kimi-cli
3
- Version: 0.40
3
+ Version: 0.41
4
4
  Summary: Kimi CLI is your next CLI agent.
5
5
  Requires-Dist: agent-client-protocol>=0.4.9
6
6
  Requires-Dist: aiofiles>=25.1.0
@@ -24,6 +24,11 @@ Description-Content-Type: text/markdown
24
24
 
25
25
  # Kimi CLI
26
26
 
27
+ [![Commit Activity](https://img.shields.io/github/commit-activity/w/MoonshotAI/kimi-cli)](https://github.com/MoonshotAI/kimi-cli/graphs/commit-activity)
28
+ [![Checks](https://img.shields.io/github/check-runs/MoonshotAI/kimi-cli/main)](https://github.com/MoonshotAI/kimi-cli/actions)
29
+ [![Version](https://img.shields.io/pypi/v/kimi-cli)](https://pypi.org/project/kimi-cli/)
30
+ [![Downloads](https://img.shields.io/pypi/dw/kimi-cli)](https://pypistats.org/packages/kimi-cli)
31
+
27
32
  [中文](https://www.kimi.com/coding/docs/kimi-cli.html)
28
33
 
29
34
  Kimi CLI is a new CLI agent that can help you with your software development tasks and terminal operations.
@@ -152,3 +157,31 @@ Run `kimi` with `--mcp-config-file` option to connect to the specified MCP serve
152
157
  ```sh
153
158
  kimi --mcp-config-file /path/to/mcp.json
154
159
  ```
160
+
161
+ ## Development
162
+
163
+ To develop Kimi CLI, run:
164
+
165
+ ```sh
166
+ git clone https://github.com/MoonshotAI/kimi-cli.git
167
+ cd kimi-cli
168
+
169
+ make prepare # prepare the development environment
170
+ ```
171
+
172
+ Then you can start working on Kimi CLI.
173
+
174
+ Refer to the following commands after you make changes:
175
+
176
+ ```sh
177
+ uv run kimi # run Kimi CLI
178
+
179
+ make format # format code
180
+ make check # run linting and type checking
181
+ make test # run tests
182
+ make help # show all make targets
183
+ ```
184
+
185
+ ## Contributing
186
+
187
+ We welcome contributions to Kimi CLI! Please refer to [CONTRIBUTING.md](./CONTRIBUTING.md) for more information.
@@ -1,5 +1,10 @@
1
1
  # Kimi CLI
2
2
 
3
+ [![Commit Activity](https://img.shields.io/github/commit-activity/w/MoonshotAI/kimi-cli)](https://github.com/MoonshotAI/kimi-cli/graphs/commit-activity)
4
+ [![Checks](https://img.shields.io/github/check-runs/MoonshotAI/kimi-cli/main)](https://github.com/MoonshotAI/kimi-cli/actions)
5
+ [![Version](https://img.shields.io/pypi/v/kimi-cli)](https://pypi.org/project/kimi-cli/)
6
+ [![Downloads](https://img.shields.io/pypi/dw/kimi-cli)](https://pypistats.org/packages/kimi-cli)
7
+
3
8
  [中文](https://www.kimi.com/coding/docs/kimi-cli.html)
4
9
 
5
10
  Kimi CLI is a new CLI agent that can help you with your software development tasks and terminal operations.
@@ -128,3 +133,31 @@ Run `kimi` with `--mcp-config-file` option to connect to the specified MCP serve
128
133
  ```sh
129
134
  kimi --mcp-config-file /path/to/mcp.json
130
135
  ```
136
+
137
+ ## Development
138
+
139
+ To develop Kimi CLI, run:
140
+
141
+ ```sh
142
+ git clone https://github.com/MoonshotAI/kimi-cli.git
143
+ cd kimi-cli
144
+
145
+ make prepare # prepare the development environment
146
+ ```
147
+
148
+ Then you can start working on Kimi CLI.
149
+
150
+ Refer to the following commands after you make changes:
151
+
152
+ ```sh
153
+ uv run kimi # run Kimi CLI
154
+
155
+ make format # format code
156
+ make check # run linting and type checking
157
+ make test # run tests
158
+ make help # show all make targets
159
+ ```
160
+
161
+ ## Contributing
162
+
163
+ We welcome contributions to Kimi CLI! Please refer to [CONTRIBUTING.md](./CONTRIBUTING.md) for more information.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kimi-cli"
3
- version = "0.40"
3
+ version = "0.41"
4
4
  description = "Kimi CLI is your next CLI agent."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -43,7 +43,7 @@ module-name = ["kimi_cli"]
43
43
  source-exclude = ["tests/**/*", "src/kimi_cli/deps/**/*"]
44
44
 
45
45
  [project.scripts]
46
- kimi = "kimi_cli:main"
46
+ kimi = "kimi_cli.cli:main"
47
47
 
48
48
  [tool.ruff]
49
49
  line-length = 100
@@ -57,3 +57,9 @@ select = [
57
57
  "SIM", # flake8-simplify
58
58
  "I", # isort
59
59
  ]
60
+
61
+ [tool.typos.files]
62
+ extend-exclude = ["kimi.spec", "pyinstaller.py"]
63
+
64
+ [tool.typos.default.extend-words]
65
+ datas = "datas"
@@ -9,6 +9,18 @@ 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.41] - 2025-10-26
13
+
14
+ ### Fixed
15
+
16
+ - Fix a bug for Glob tool when no matching files are found
17
+ - Ensure reading files with UTF-8 encoding
18
+
19
+ ### Changed
20
+
21
+ - Disable reading command/query from stdin in shell mode
22
+ - Clarify the API platform selection in `/setup` meta command
23
+
12
24
  ## [0.40] - 2025-10-24
13
25
 
14
26
  ### Added
@@ -0,0 +1,125 @@
1
+ import contextlib
2
+ import os
3
+ import warnings
4
+ from pathlib import Path
5
+ from typing import Any, Literal
6
+
7
+ import click
8
+ from pydantic import SecretStr
9
+
10
+ from kimi_cli.agentspec import DEFAULT_AGENT_FILE
11
+ from kimi_cli.config import Config, LLMModel, LLMProvider
12
+ from kimi_cli.llm import augment_provider_with_env_vars, create_llm
13
+ from kimi_cli.metadata import Session
14
+ from kimi_cli.soul.agent import load_agent_with_mcp
15
+ from kimi_cli.soul.context import Context
16
+ from kimi_cli.soul.globals import AgentGlobals
17
+ from kimi_cli.soul.kimisoul import KimiSoul
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
21
+ from kimi_cli.utils.logging import StreamToLogger, logger
22
+
23
+ UIMode = Literal["shell", "print", "acp"]
24
+
25
+
26
+ async def kimi_run(
27
+ *,
28
+ config: Config,
29
+ model_name: str | None,
30
+ work_dir: Path,
31
+ session: Session,
32
+ command: str | None = None,
33
+ agent_file: Path = DEFAULT_AGENT_FILE,
34
+ ui: UIMode = "shell",
35
+ input_format: InputFormat | None = None,
36
+ output_format: OutputFormat | None = None,
37
+ mcp_configs: list[dict[str, Any]] | None = None,
38
+ yolo: bool = False,
39
+ ) -> bool:
40
+ """Run Kimi CLI."""
41
+ model: LLMModel | None = None
42
+ provider: LLMProvider | None = None
43
+
44
+ # try to use config file
45
+ if not model_name and config.default_model:
46
+ # no --model specified && default model is set in config
47
+ model = config.models[config.default_model]
48
+ provider = config.providers[model.provider]
49
+ if model_name and model_name in config.models:
50
+ # --model specified && model is set in config
51
+ model = config.models[model_name]
52
+ provider = config.providers[model.provider]
53
+
54
+ if not model:
55
+ model = LLMModel(provider="", model="", max_context_size=100_000)
56
+ provider = LLMProvider(type="kimi", base_url="", api_key=SecretStr(""))
57
+
58
+ # try overwrite with environment variables
59
+ assert provider is not None
60
+ assert model is not None
61
+ augment_provider_with_env_vars(provider, model)
62
+
63
+ if not provider.base_url or not model.model:
64
+ llm = None
65
+ else:
66
+ logger.info("Using LLM provider: {provider}", provider=provider)
67
+ logger.info("Using LLM model: {model}", model=model)
68
+ stream = ui != "print" # use non-streaming mode only for print UI
69
+ llm = create_llm(provider, model, stream=stream, session_id=session.id)
70
+
71
+ agent_globals = await AgentGlobals.create(config, llm, session, yolo)
72
+ try:
73
+ agent = await load_agent_with_mcp(agent_file, agent_globals, mcp_configs or [])
74
+ except ValueError as e:
75
+ raise click.BadParameter(f"Failed to load agent: {e}") from e
76
+
77
+ if command is not None:
78
+ command = command.strip()
79
+ if not command:
80
+ raise click.BadParameter("Command cannot be empty")
81
+
82
+ context = Context(session.history_file)
83
+ await context.restore()
84
+
85
+ soul = KimiSoul(
86
+ agent,
87
+ agent_globals,
88
+ context=context,
89
+ loop_control=config.loop_control,
90
+ )
91
+
92
+ original_cwd = Path.cwd()
93
+ os.chdir(work_dir)
94
+
95
+ try:
96
+ if ui == "shell":
97
+ app = ShellApp(
98
+ soul,
99
+ welcome_info={
100
+ "Directory": str(work_dir),
101
+ "Session": session.id,
102
+ },
103
+ )
104
+ # to ignore possible warnings from dateparser
105
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
106
+ with contextlib.redirect_stderr(StreamToLogger()):
107
+ return await app.run(command)
108
+ elif ui == "print":
109
+ soul._approval.set_yolo(True) # print mode implies yolo mode
110
+ app = PrintApp(
111
+ soul,
112
+ input_format or "text",
113
+ output_format or "text",
114
+ context.file_backend,
115
+ )
116
+ return await app.run(command)
117
+ elif ui == "acp":
118
+ if command is not None:
119
+ logger.warning("ACP server ignores command argument")
120
+ app = ACPServer(soul)
121
+ return await app.run()
122
+ else:
123
+ raise click.BadParameter(f"Invalid UI mode: {ui}")
124
+ finally:
125
+ os.chdir(original_cwd)
@@ -47,7 +47,7 @@ The operating environment is not in a sandbox. Any action especially mutation yo
47
47
 
48
48
  The current working directory is `${KIMI_WORK_DIR}`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify the absolute path. Tools may require absolute paths for some parameters, if so, you should strictly follow the requirements.
49
49
 
50
- The `ls -la` output of current working directory is:
50
+ The directory listing of current working directory is:
51
51
 
52
52
  ```
53
53
  ${KIMI_WORK_DIR_LS}
@@ -0,0 +1,104 @@
1
+ from pathlib import Path
2
+ from typing import Any, NamedTuple
3
+
4
+ import yaml
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ def get_agents_dir() -> Path:
9
+ return Path(__file__).parent / "agents"
10
+
11
+
12
+ DEFAULT_AGENT_FILE = get_agents_dir() / "koder" / "agent.yaml"
13
+
14
+
15
+ class AgentSpec(BaseModel):
16
+ """Agent specification."""
17
+
18
+ extend: str | None = Field(default=None, description="Agent file to extend")
19
+ name: str | None = Field(default=None, description="Agent name") # required
20
+ system_prompt_path: Path | None = Field(
21
+ default=None, description="System prompt path"
22
+ ) # required
23
+ system_prompt_args: dict[str, str] = Field(
24
+ default_factory=dict, description="System prompt arguments"
25
+ )
26
+ tools: list[str] | None = Field(default=None, description="Tools") # required
27
+ exclude_tools: list[str] | None = Field(default=None, description="Tools to exclude")
28
+ subagents: dict[str, "SubagentSpec"] | None = Field(default=None, description="Subagents")
29
+
30
+
31
+ class SubagentSpec(BaseModel):
32
+ """Subagent specification."""
33
+
34
+ path: Path = Field(description="Subagent file path")
35
+ description: str = Field(description="Subagent description")
36
+
37
+
38
+ class ResolvedAgentSpec(NamedTuple):
39
+ """Resolved agent specification."""
40
+
41
+ name: str
42
+ system_prompt_path: Path
43
+ system_prompt_args: dict[str, str]
44
+ tools: list[str]
45
+ exclude_tools: list[str]
46
+ subagents: dict[str, "SubagentSpec"]
47
+
48
+
49
+ def load_agent_spec(agent_file: Path) -> ResolvedAgentSpec:
50
+ """Load agent specification from file."""
51
+ agent_spec = _load_agent_spec(agent_file)
52
+ assert agent_spec.extend is None, "agent extension should be recursively resolved"
53
+ if agent_spec.name is None:
54
+ raise ValueError("Agent name is required")
55
+ if agent_spec.system_prompt_path is None:
56
+ raise ValueError("System prompt path is required")
57
+ if agent_spec.tools is None:
58
+ raise ValueError("Tools are required")
59
+ return ResolvedAgentSpec(
60
+ name=agent_spec.name,
61
+ system_prompt_path=agent_spec.system_prompt_path,
62
+ system_prompt_args=agent_spec.system_prompt_args,
63
+ tools=agent_spec.tools,
64
+ exclude_tools=agent_spec.exclude_tools or [],
65
+ subagents=agent_spec.subagents or {},
66
+ )
67
+
68
+
69
+ def _load_agent_spec(agent_file: Path) -> AgentSpec:
70
+ 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)
73
+
74
+ version = data.get("version", 1)
75
+ if version != 1:
76
+ raise ValueError(f"Unsupported agent spec version: {version}")
77
+
78
+ agent_spec = AgentSpec(**data.get("agent", {}))
79
+ if agent_spec.system_prompt_path is not None:
80
+ agent_spec.system_prompt_path = agent_file.parent / agent_spec.system_prompt_path
81
+ if agent_spec.subagents is not None:
82
+ for v in agent_spec.subagents.values():
83
+ v.path = agent_file.parent / v.path
84
+ if agent_spec.extend:
85
+ if agent_spec.extend == "default":
86
+ base_agent_file = DEFAULT_AGENT_FILE
87
+ else:
88
+ base_agent_file = agent_file.parent / agent_spec.extend
89
+ base_agent_spec = _load_agent_spec(base_agent_file)
90
+ if agent_spec.name is not None:
91
+ base_agent_spec.name = agent_spec.name
92
+ if agent_spec.system_prompt_path is not None:
93
+ base_agent_spec.system_prompt_path = agent_spec.system_prompt_path
94
+ for k, v in agent_spec.system_prompt_args.items():
95
+ # system prompt args should be merged instead of overwritten
96
+ base_agent_spec.system_prompt_args[k] = v
97
+ if agent_spec.tools is not None:
98
+ base_agent_spec.tools = agent_spec.tools
99
+ if agent_spec.exclude_tools is not None:
100
+ base_agent_spec.exclude_tools = agent_spec.exclude_tools
101
+ if agent_spec.subagents is not None:
102
+ base_agent_spec.subagents = agent_spec.subagents
103
+ agent_spec = base_agent_spec
104
+ return agent_spec
@@ -1,53 +1,28 @@
1
1
  import asyncio
2
- import contextlib
3
- import importlib.metadata
4
2
  import json
5
- import os
6
- import subprocess
7
3
  import sys
8
- import textwrap
9
- import warnings
10
- from datetime import datetime
11
4
  from pathlib import Path
12
- from typing import Any, Literal
13
5
 
14
6
  import click
15
- from pydantic import SecretStr
16
7
 
17
- from kimi_cli.agent import (
18
- DEFAULT_AGENT_FILE,
19
- AgentGlobals,
20
- BuiltinSystemPromptArgs,
21
- load_agent_with_mcp,
22
- load_agents_md,
23
- )
24
- from kimi_cli.config import (
25
- Config,
26
- ConfigError,
27
- LLMModel,
28
- LLMProvider,
29
- load_config,
30
- )
31
- from kimi_cli.metadata import Session, continue_session, new_session
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
11
+ from kimi_cli.constant import VERSION
12
+ from kimi_cli.metadata import continue_session, new_session
32
13
  from kimi_cli.share import get_share_dir
33
- from kimi_cli.soul.approval import Approval
34
- from kimi_cli.soul.context import Context
35
- from kimi_cli.soul.denwarenji import DenwaRenji
36
- from kimi_cli.soul.kimisoul import KimiSoul
37
- from kimi_cli.ui.acp import ACPServer
38
- from kimi_cli.ui.print import InputFormat, OutputFormat, PrintApp
39
- from kimi_cli.ui.shell import Reload, ShellApp
40
- from kimi_cli.utils.logging import StreamToLogger, logger
41
- from kimi_cli.utils.provider import augment_provider_with_env_vars, create_llm
14
+ from kimi_cli.ui.print import InputFormat, OutputFormat
15
+ from kimi_cli.utils.logging import logger
42
16
 
43
- __version__ = importlib.metadata.version("kimi-cli")
44
- USER_AGENT = f"KimiCLI/{__version__}"
45
17
 
46
- UIMode = Literal["shell", "print", "acp"]
18
+ class Reload(Exception):
19
+ """Reload configuration."""
20
+
21
+ pass
47
22
 
48
23
 
49
24
  @click.command(context_settings=dict(help_option_names=["-h", "--help"]))
50
- @click.version_option(__version__)
25
+ @click.version_option(VERSION)
51
26
  @click.option(
52
27
  "--verbose",
53
28
  is_flag=True,
@@ -238,7 +213,6 @@ def kimi(
238
213
  session=session,
239
214
  command=command,
240
215
  agent_file=agent_file,
241
- verbose=verbose,
242
216
  ui=ui,
243
217
  input_format=input_format,
244
218
  output_format=output_format,
@@ -253,132 +227,6 @@ def kimi(
253
227
  continue
254
228
 
255
229
 
256
- async def kimi_run(
257
- *,
258
- config: Config,
259
- model_name: str | None,
260
- work_dir: Path,
261
- session: Session,
262
- command: str | None = None,
263
- agent_file: Path = DEFAULT_AGENT_FILE,
264
- verbose: bool = True,
265
- ui: UIMode = "shell",
266
- input_format: InputFormat | None = None,
267
- output_format: OutputFormat | None = None,
268
- mcp_configs: list[dict[str, Any]] | None = None,
269
- yolo: bool = False,
270
- ) -> bool:
271
- """Run Kimi CLI."""
272
- echo = click.echo if verbose else lambda *args, **kwargs: None
273
-
274
- model: LLMModel | None = None
275
- provider: LLMProvider | None = None
276
-
277
- # try to use config file
278
- if not model_name and config.default_model:
279
- # no --model specified && default model is set in config
280
- model = config.models[config.default_model]
281
- provider = config.providers[model.provider]
282
- if model_name and model_name in config.models:
283
- # --model specified && model is set in config
284
- model = config.models[model_name]
285
- provider = config.providers[model.provider]
286
-
287
- if not model:
288
- model = LLMModel(provider="", model="", max_context_size=100_000)
289
- provider = LLMProvider(type="kimi", base_url="", api_key=SecretStr(""))
290
-
291
- # try overwrite with environment variables
292
- assert provider is not None
293
- assert model is not None
294
- augment_provider_with_env_vars(provider, model)
295
-
296
- if not provider.base_url or not model.model:
297
- llm = None
298
- else:
299
- echo(f"✓ Using LLM provider: {provider}")
300
- echo(f"✓ Using LLM model: {model}")
301
- stream = ui != "print" # use non-streaming mode only for print UI
302
- llm = create_llm(provider, model, stream=stream, session_id=session.id)
303
-
304
- # TODO: support Windows
305
- ls = subprocess.run(["ls", "-la"], capture_output=True, text=True)
306
- agents_md = load_agents_md(work_dir) or ""
307
- if agents_md:
308
- echo(f"✓ Loaded agents.md: {textwrap.shorten(agents_md, width=100)}")
309
-
310
- agent_globals = AgentGlobals(
311
- config=config,
312
- llm=llm,
313
- builtin_args=BuiltinSystemPromptArgs(
314
- KIMI_NOW=datetime.now().astimezone().isoformat(),
315
- KIMI_WORK_DIR=work_dir,
316
- KIMI_WORK_DIR_LS=ls.stdout,
317
- KIMI_AGENTS_MD=agents_md,
318
- ),
319
- denwa_renji=DenwaRenji(),
320
- session=session,
321
- approval=Approval(yolo=yolo),
322
- )
323
- try:
324
- agent = await load_agent_with_mcp(agent_file, agent_globals, mcp_configs or [])
325
- except ValueError as e:
326
- raise click.BadParameter(f"Failed to load agent: {e}") from e
327
- echo(f"✓ Loaded agent: {agent.name}")
328
- echo(f"✓ Loaded system prompt: {textwrap.shorten(agent.system_prompt, width=100)}")
329
- echo(f"✓ Loaded tools: {[tool.name for tool in agent.toolset.tools]}")
330
-
331
- if command is not None:
332
- command = command.strip()
333
- if not command:
334
- raise click.BadParameter("Command cannot be empty")
335
-
336
- context = Context(session.history_file)
337
- restored = await context.restore()
338
- if restored:
339
- echo(f"✓ Restored history from {session.history_file}")
340
-
341
- soul = KimiSoul(
342
- agent,
343
- agent_globals,
344
- context=context,
345
- loop_control=config.loop_control,
346
- )
347
-
348
- original_cwd = Path.cwd()
349
- os.chdir(work_dir)
350
-
351
- try:
352
- if ui == "shell":
353
- if command is None and not sys.stdin.isatty():
354
- command = sys.stdin.read().strip()
355
- echo(f"✓ Read command from stdin: {command}")
356
-
357
- app = ShellApp(
358
- soul,
359
- welcome_info={
360
- "Directory": str(work_dir),
361
- "Session": session.id,
362
- },
363
- )
364
- # to ignore possible warnings from dateparser
365
- warnings.filterwarnings("ignore", category=DeprecationWarning)
366
- with contextlib.redirect_stderr(StreamToLogger()):
367
- return await app.run(command)
368
- elif ui == "print":
369
- app = PrintApp(soul, input_format or "text", output_format or "text")
370
- return await app.run(command)
371
- elif ui == "acp":
372
- if command is not None:
373
- logger.warning("ACP server ignores command argument")
374
- app = ACPServer(soul)
375
- return await app.run()
376
- else:
377
- raise click.BadParameter(f"Invalid UI mode: {ui}")
378
- finally:
379
- os.chdir(original_cwd)
380
-
381
-
382
230
  def main():
383
231
  kimi()
384
232
 
@@ -0,0 +1,4 @@
1
+ import importlib.metadata
2
+
3
+ VERSION = importlib.metadata.version("kimi-cli")
4
+ USER_AGENT = f"KimiCLI/{VERSION}"
@@ -1,12 +1,19 @@
1
1
  import os
2
+ from typing import NamedTuple
2
3
 
3
- from kosong.chat_provider import ChaosChatProvider, Kimi, OpenAILegacy
4
- from kosong.chat_provider.chaos import ChaosConfig
4
+ from kosong.base.chat_provider import ChatProvider
5
+ from kosong.chat_provider.chaos import ChaosChatProvider, ChaosConfig
6
+ from kosong.chat_provider.kimi import Kimi
7
+ from kosong.chat_provider.openai_legacy import OpenAILegacy
5
8
  from pydantic import SecretStr
6
9
 
7
- import kimi_cli
8
10
  from kimi_cli.config import LLMModel, LLMProvider
9
- from kimi_cli.llm import LLM
11
+ from kimi_cli.constant import USER_AGENT
12
+
13
+
14
+ class LLM(NamedTuple):
15
+ chat_provider: ChatProvider
16
+ max_context_size: int
10
17
 
11
18
 
12
19
  def augment_provider_with_env_vars(provider: LLMProvider, model: LLMModel):
@@ -44,7 +51,7 @@ def create_llm(
44
51
  api_key=provider.api_key.get_secret_value(),
45
52
  stream=stream,
46
53
  default_headers={
47
- "User-Agent": kimi_cli.USER_AGENT,
54
+ "User-Agent": USER_AGENT,
48
55
  },
49
56
  )
50
57
  if session_id:
@@ -0,0 +1,4 @@
1
+ from pathlib import Path
2
+
3
+ INIT = (Path(__file__).parent / "init.md").read_text(encoding="utf-8")
4
+ COMPACT = (Path(__file__).parent / "compact.md").read_text(encoding="utf-8")