kimi-cli 0.39__py3-none-any.whl → 0.41__py3-none-any.whl

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 (52) hide show
  1. kimi_cli/CHANGELOG.md +23 -0
  2. kimi_cli/__init__.py +18 -280
  3. kimi_cli/agents/koder/system.md +1 -1
  4. kimi_cli/agentspec.py +104 -0
  5. kimi_cli/cli.py +235 -0
  6. kimi_cli/constant.py +4 -0
  7. kimi_cli/llm.py +69 -0
  8. kimi_cli/prompts/__init__.py +2 -2
  9. kimi_cli/soul/__init__.py +102 -6
  10. kimi_cli/soul/agent.py +157 -0
  11. kimi_cli/soul/approval.py +2 -4
  12. kimi_cli/soul/compaction.py +10 -10
  13. kimi_cli/soul/context.py +5 -0
  14. kimi_cli/soul/globals.py +92 -0
  15. kimi_cli/soul/kimisoul.py +26 -30
  16. kimi_cli/soul/message.py +5 -5
  17. kimi_cli/tools/bash/__init__.py +2 -2
  18. kimi_cli/tools/dmail/__init__.py +1 -1
  19. kimi_cli/tools/file/glob.md +1 -1
  20. kimi_cli/tools/file/glob.py +2 -2
  21. kimi_cli/tools/file/grep.py +3 -2
  22. kimi_cli/tools/file/patch.py +2 -2
  23. kimi_cli/tools/file/read.py +1 -1
  24. kimi_cli/tools/file/replace.py +2 -2
  25. kimi_cli/tools/file/write.py +2 -2
  26. kimi_cli/tools/task/__init__.py +23 -22
  27. kimi_cli/tools/task/task.md +1 -1
  28. kimi_cli/tools/todo/__init__.py +1 -1
  29. kimi_cli/tools/utils.py +1 -1
  30. kimi_cli/tools/web/fetch.py +2 -1
  31. kimi_cli/tools/web/search.py +4 -4
  32. kimi_cli/ui/__init__.py +0 -69
  33. kimi_cli/ui/acp/__init__.py +8 -9
  34. kimi_cli/ui/print/__init__.py +17 -35
  35. kimi_cli/ui/shell/__init__.py +9 -15
  36. kimi_cli/ui/shell/liveview.py +13 -4
  37. kimi_cli/ui/shell/metacmd.py +3 -3
  38. kimi_cli/ui/shell/setup.py +7 -6
  39. kimi_cli/ui/shell/update.py +4 -3
  40. kimi_cli/ui/shell/visualize.py +18 -9
  41. kimi_cli/utils/aiohttp.py +10 -0
  42. kimi_cli/utils/changelog.py +3 -1
  43. kimi_cli/wire/__init__.py +57 -0
  44. kimi_cli/{soul/wire.py → wire/message.py} +4 -39
  45. {kimi_cli-0.39.dist-info → kimi_cli-0.41.dist-info}/METADATA +35 -2
  46. kimi_cli-0.41.dist-info/RECORD +85 -0
  47. kimi_cli-0.41.dist-info/entry_points.txt +3 -0
  48. kimi_cli/agent.py +0 -261
  49. kimi_cli/utils/provider.py +0 -72
  50. kimi_cli-0.39.dist-info/RECORD +0 -80
  51. kimi_cli-0.39.dist-info/entry_points.txt +0 -3
  52. {kimi_cli-0.39.dist-info → kimi_cli-0.41.dist-info}/WHEEL +0 -0
kimi_cli/CHANGELOG.md CHANGED
@@ -9,6 +9,29 @@ 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
+
24
+ ## [0.40] - 2025-10-24
25
+
26
+ ### Added
27
+
28
+ - Support `ESC` key to interrupt the agent loop
29
+
30
+ ### Fixed
31
+
32
+ - Fix SSL certificate verification error in some rare cases
33
+ - Fix possible decoding error in Bash tool
34
+
12
35
  ## [0.39] - 2025-10-24
13
36
 
14
37
  ### Fixed
kimi_cli/__init__.py CHANGED
@@ -1,258 +1,28 @@
1
- import asyncio
2
1
  import contextlib
3
- import importlib.metadata
4
- import json
5
2
  import os
6
- import subprocess
7
- import sys
8
- import textwrap
9
3
  import warnings
10
- from datetime import datetime
11
4
  from pathlib import Path
12
5
  from typing import Any, Literal
13
6
 
14
7
  import click
15
8
  from pydantic import SecretStr
16
9
 
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
32
- from kimi_cli.share import get_share_dir
33
- from kimi_cli.soul.approval import Approval
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
34
15
  from kimi_cli.soul.context import Context
35
- from kimi_cli.soul.denwarenji import DenwaRenji
16
+ from kimi_cli.soul.globals import AgentGlobals
36
17
  from kimi_cli.soul.kimisoul import KimiSoul
37
18
  from kimi_cli.ui.acp import ACPServer
38
19
  from kimi_cli.ui.print import InputFormat, OutputFormat, PrintApp
39
- from kimi_cli.ui.shell import Reload, ShellApp
20
+ from kimi_cli.ui.shell import ShellApp
40
21
  from kimi_cli.utils.logging import StreamToLogger, logger
41
- from kimi_cli.utils.provider import augment_provider_with_env_vars, create_llm
42
-
43
- __version__ = importlib.metadata.version("kimi-cli")
44
- USER_AGENT = f"KimiCLI/{__version__}"
45
22
 
46
23
  UIMode = Literal["shell", "print", "acp"]
47
24
 
48
25
 
49
- @click.command(context_settings=dict(help_option_names=["-h", "--help"]))
50
- @click.version_option(__version__)
51
- @click.option(
52
- "--verbose",
53
- is_flag=True,
54
- default=False,
55
- help="Print verbose information. Default: no.",
56
- )
57
- @click.option(
58
- "--debug",
59
- is_flag=True,
60
- default=False,
61
- help="Log debug information. Default: no.",
62
- )
63
- @click.option(
64
- "--agent-file",
65
- type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
66
- default=DEFAULT_AGENT_FILE,
67
- help="Custom agent specification file. Default: builtin Kimi Koder.",
68
- )
69
- @click.option(
70
- "--model",
71
- "-m",
72
- "model_name",
73
- type=str,
74
- default=None,
75
- help="LLM model to use. Default: default model set in config file.",
76
- )
77
- @click.option(
78
- "--work-dir",
79
- "-w",
80
- type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
81
- default=Path.cwd(),
82
- help="Working directory for the agent. Default: current directory.",
83
- )
84
- @click.option(
85
- "--continue",
86
- "-C",
87
- "continue_",
88
- is_flag=True,
89
- default=False,
90
- help="Continue the previous session for the working directory. Default: no.",
91
- )
92
- @click.option(
93
- "--command",
94
- "-c",
95
- "--query",
96
- "-q",
97
- "command",
98
- type=str,
99
- default=None,
100
- help="User query to the agent. Default: prompt interactively.",
101
- )
102
- @click.option(
103
- "--ui",
104
- "ui",
105
- type=click.Choice(["shell", "print", "acp"]),
106
- default="shell",
107
- help="UI mode to use. Default: shell.",
108
- )
109
- @click.option(
110
- "--print",
111
- "ui",
112
- flag_value="print",
113
- help="Run in print mode. Shortcut for `--ui print`.",
114
- )
115
- @click.option(
116
- "--acp",
117
- "ui",
118
- flag_value="acp",
119
- help="Start ACP server. Shortcut for `--ui acp`.",
120
- )
121
- @click.option(
122
- "--input-format",
123
- type=click.Choice(["text", "stream-json"]),
124
- default=None,
125
- help=(
126
- "Input format to use. Must be used with `--print` "
127
- "and the input must be piped in via stdin. "
128
- "Default: text."
129
- ),
130
- )
131
- @click.option(
132
- "--output-format",
133
- type=click.Choice(["text", "stream-json"]),
134
- default=None,
135
- help="Output format to use. Must be used with `--print`. Default: text.",
136
- )
137
- @click.option(
138
- "--mcp-config-file",
139
- type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
140
- multiple=True,
141
- help=(
142
- "MCP config file to load. Add this option multiple times to specify multiple MCP configs. "
143
- "Default: none."
144
- ),
145
- )
146
- @click.option(
147
- "--mcp-config",
148
- type=str,
149
- multiple=True,
150
- help=(
151
- "MCP config JSON to load. Add this option multiple times to specify multiple MCP configs. "
152
- "Default: none."
153
- ),
154
- )
155
- @click.option(
156
- "--yolo",
157
- "--yes",
158
- "-y",
159
- "--auto-approve",
160
- "yolo",
161
- is_flag=True,
162
- default=False,
163
- help="Automatically approve all actions. Default: no.",
164
- )
165
- def kimi(
166
- verbose: bool,
167
- debug: bool,
168
- agent_file: Path,
169
- model_name: str | None,
170
- work_dir: Path,
171
- continue_: bool,
172
- command: str | None,
173
- ui: UIMode,
174
- input_format: InputFormat | None,
175
- output_format: OutputFormat | None,
176
- mcp_config_file: list[Path],
177
- mcp_config: list[str],
178
- yolo: bool,
179
- ):
180
- """Kimi, your next CLI agent."""
181
- echo = click.echo if verbose else lambda *args, **kwargs: None
182
-
183
- logger.add(
184
- get_share_dir() / "logs" / "kimi.log",
185
- level="DEBUG" if debug else "INFO",
186
- rotation="06:00",
187
- retention="10 days",
188
- )
189
-
190
- work_dir = work_dir.absolute()
191
-
192
- if continue_:
193
- session = continue_session(work_dir)
194
- if session is None:
195
- raise click.BadOptionUsage(
196
- "--continue", "No previous session found for the working directory"
197
- )
198
- echo(f"✓ Continuing previous session: {session.id}")
199
- else:
200
- session = new_session(work_dir)
201
- echo(f"✓ Created new session: {session.id}")
202
- echo(f"✓ Session history file: {session.history_file}")
203
-
204
- if input_format is not None and ui != "print":
205
- raise click.BadOptionUsage(
206
- "--input-format",
207
- "Input format is only supported for print UI",
208
- )
209
- if output_format is not None and ui != "print":
210
- raise click.BadOptionUsage(
211
- "--output-format",
212
- "Output format is only supported for print UI",
213
- )
214
-
215
- try:
216
- mcp_configs = [json.loads(conf.read_text()) for conf in mcp_config_file]
217
- except json.JSONDecodeError as e:
218
- raise click.BadOptionUsage("--mcp-config-file", f"Invalid JSON: {e}") from e
219
-
220
- try:
221
- mcp_configs += [json.loads(conf) for conf in mcp_config]
222
- except json.JSONDecodeError as e:
223
- raise click.BadOptionUsage("--mcp-config", f"Invalid JSON: {e}") from e
224
-
225
- while True:
226
- try:
227
- try:
228
- config = load_config()
229
- except ConfigError as e:
230
- raise click.ClickException(f"Failed to load config: {e}") from e
231
- echo(f"✓ Loaded config: {config}")
232
-
233
- succeeded = asyncio.run(
234
- kimi_run(
235
- config=config,
236
- model_name=model_name,
237
- work_dir=work_dir,
238
- session=session,
239
- command=command,
240
- agent_file=agent_file,
241
- verbose=verbose,
242
- ui=ui,
243
- input_format=input_format,
244
- output_format=output_format,
245
- mcp_configs=mcp_configs,
246
- yolo=yolo,
247
- )
248
- )
249
- if not succeeded:
250
- sys.exit(1)
251
- break
252
- except Reload:
253
- continue
254
-
255
-
256
26
  async def kimi_run(
257
27
  *,
258
28
  config: Config,
@@ -261,7 +31,6 @@ async def kimi_run(
261
31
  session: Session,
262
32
  command: str | None = None,
263
33
  agent_file: Path = DEFAULT_AGENT_FILE,
264
- verbose: bool = True,
265
34
  ui: UIMode = "shell",
266
35
  input_format: InputFormat | None = None,
267
36
  output_format: OutputFormat | None = None,
@@ -269,8 +38,6 @@ async def kimi_run(
269
38
  yolo: bool = False,
270
39
  ) -> bool:
271
40
  """Run Kimi CLI."""
272
- echo = click.echo if verbose else lambda *args, **kwargs: None
273
-
274
41
  model: LLMModel | None = None
275
42
  provider: LLMProvider | None = None
276
43
 
@@ -296,37 +63,16 @@ async def kimi_run(
296
63
  if not provider.base_url or not model.model:
297
64
  llm = None
298
65
  else:
299
- echo(f"Using LLM provider: {provider}")
300
- echo(f"Using LLM model: {model}")
66
+ logger.info("Using LLM provider: {provider}", provider=provider)
67
+ logger.info("Using LLM model: {model}", model=model)
301
68
  stream = ui != "print" # use non-streaming mode only for print UI
302
69
  llm = create_llm(provider, model, stream=stream, session_id=session.id)
303
70
 
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
- )
71
+ agent_globals = await AgentGlobals.create(config, llm, session, yolo)
323
72
  try:
324
73
  agent = await load_agent_with_mcp(agent_file, agent_globals, mcp_configs or [])
325
74
  except ValueError as e:
326
75
  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
76
 
331
77
  if command is not None:
332
78
  command = command.strip()
@@ -334,9 +80,7 @@ async def kimi_run(
334
80
  raise click.BadParameter("Command cannot be empty")
335
81
 
336
82
  context = Context(session.history_file)
337
- restored = await context.restore()
338
- if restored:
339
- echo(f"✓ Restored history from {session.history_file}")
83
+ await context.restore()
340
84
 
341
85
  soul = KimiSoul(
342
86
  agent,
@@ -350,10 +94,6 @@ async def kimi_run(
350
94
 
351
95
  try:
352
96
  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
97
  app = ShellApp(
358
98
  soul,
359
99
  welcome_info={
@@ -366,7 +106,13 @@ async def kimi_run(
366
106
  with contextlib.redirect_stderr(StreamToLogger()):
367
107
  return await app.run(command)
368
108
  elif ui == "print":
369
- app = PrintApp(soul, input_format or "text", output_format or "text")
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
+ )
370
116
  return await app.run(command)
371
117
  elif ui == "acp":
372
118
  if command is not None:
@@ -377,11 +123,3 @@ async def kimi_run(
377
123
  raise click.BadParameter(f"Invalid UI mode: {ui}")
378
124
  finally:
379
125
  os.chdir(original_cwd)
380
-
381
-
382
- def main():
383
- kimi()
384
-
385
-
386
- if __name__ == "__main__":
387
- main()
@@ -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}
kimi_cli/agentspec.py ADDED
@@ -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