kimi-cli 0.44__py3-none-any.whl → 0.78__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 (137) hide show
  1. kimi_cli/CHANGELOG.md +349 -40
  2. kimi_cli/__init__.py +6 -0
  3. kimi_cli/acp/AGENTS.md +91 -0
  4. kimi_cli/acp/__init__.py +13 -0
  5. kimi_cli/acp/convert.py +111 -0
  6. kimi_cli/acp/kaos.py +270 -0
  7. kimi_cli/acp/mcp.py +46 -0
  8. kimi_cli/acp/server.py +335 -0
  9. kimi_cli/acp/session.py +445 -0
  10. kimi_cli/acp/tools.py +158 -0
  11. kimi_cli/acp/types.py +13 -0
  12. kimi_cli/agents/default/agent.yaml +4 -4
  13. kimi_cli/agents/default/sub.yaml +2 -1
  14. kimi_cli/agents/default/system.md +79 -21
  15. kimi_cli/agents/okabe/agent.yaml +17 -0
  16. kimi_cli/agentspec.py +53 -25
  17. kimi_cli/app.py +180 -52
  18. kimi_cli/cli/__init__.py +595 -0
  19. kimi_cli/cli/__main__.py +8 -0
  20. kimi_cli/cli/info.py +63 -0
  21. kimi_cli/cli/mcp.py +349 -0
  22. kimi_cli/config.py +153 -17
  23. kimi_cli/constant.py +3 -0
  24. kimi_cli/exception.py +23 -2
  25. kimi_cli/flow/__init__.py +117 -0
  26. kimi_cli/flow/d2.py +376 -0
  27. kimi_cli/flow/mermaid.py +218 -0
  28. kimi_cli/llm.py +129 -23
  29. kimi_cli/metadata.py +32 -7
  30. kimi_cli/platforms.py +262 -0
  31. kimi_cli/prompts/__init__.py +2 -0
  32. kimi_cli/prompts/compact.md +4 -5
  33. kimi_cli/session.py +223 -31
  34. kimi_cli/share.py +2 -0
  35. kimi_cli/skill.py +145 -0
  36. kimi_cli/skills/kimi-cli-help/SKILL.md +55 -0
  37. kimi_cli/skills/skill-creator/SKILL.md +351 -0
  38. kimi_cli/soul/__init__.py +51 -20
  39. kimi_cli/soul/agent.py +213 -85
  40. kimi_cli/soul/approval.py +86 -17
  41. kimi_cli/soul/compaction.py +64 -53
  42. kimi_cli/soul/context.py +38 -5
  43. kimi_cli/soul/denwarenji.py +2 -0
  44. kimi_cli/soul/kimisoul.py +442 -60
  45. kimi_cli/soul/message.py +54 -54
  46. kimi_cli/soul/slash.py +72 -0
  47. kimi_cli/soul/toolset.py +387 -6
  48. kimi_cli/toad.py +74 -0
  49. kimi_cli/tools/AGENTS.md +5 -0
  50. kimi_cli/tools/__init__.py +42 -34
  51. kimi_cli/tools/display.py +25 -0
  52. kimi_cli/tools/dmail/__init__.py +10 -10
  53. kimi_cli/tools/dmail/dmail.md +11 -9
  54. kimi_cli/tools/file/__init__.py +1 -3
  55. kimi_cli/tools/file/glob.py +20 -23
  56. kimi_cli/tools/file/grep.md +1 -1
  57. kimi_cli/tools/file/{grep.py → grep_local.py} +51 -23
  58. kimi_cli/tools/file/read.md +24 -6
  59. kimi_cli/tools/file/read.py +134 -50
  60. kimi_cli/tools/file/replace.md +1 -1
  61. kimi_cli/tools/file/replace.py +36 -29
  62. kimi_cli/tools/file/utils.py +282 -0
  63. kimi_cli/tools/file/write.py +43 -22
  64. kimi_cli/tools/multiagent/__init__.py +7 -0
  65. kimi_cli/tools/multiagent/create.md +11 -0
  66. kimi_cli/tools/multiagent/create.py +50 -0
  67. kimi_cli/tools/{task/__init__.py → multiagent/task.py} +48 -53
  68. kimi_cli/tools/shell/__init__.py +120 -0
  69. kimi_cli/tools/{bash → shell}/bash.md +1 -2
  70. kimi_cli/tools/shell/powershell.md +25 -0
  71. kimi_cli/tools/test.py +4 -4
  72. kimi_cli/tools/think/__init__.py +2 -2
  73. kimi_cli/tools/todo/__init__.py +14 -8
  74. kimi_cli/tools/utils.py +64 -24
  75. kimi_cli/tools/web/fetch.py +68 -13
  76. kimi_cli/tools/web/search.py +10 -12
  77. kimi_cli/ui/acp/__init__.py +65 -412
  78. kimi_cli/ui/print/__init__.py +37 -49
  79. kimi_cli/ui/print/visualize.py +179 -0
  80. kimi_cli/ui/shell/__init__.py +141 -84
  81. kimi_cli/ui/shell/console.py +2 -0
  82. kimi_cli/ui/shell/debug.py +28 -23
  83. kimi_cli/ui/shell/keyboard.py +5 -1
  84. kimi_cli/ui/shell/prompt.py +220 -194
  85. kimi_cli/ui/shell/replay.py +111 -46
  86. kimi_cli/ui/shell/setup.py +89 -82
  87. kimi_cli/ui/shell/slash.py +422 -0
  88. kimi_cli/ui/shell/update.py +4 -2
  89. kimi_cli/ui/shell/usage.py +271 -0
  90. kimi_cli/ui/shell/visualize.py +574 -72
  91. kimi_cli/ui/wire/__init__.py +267 -0
  92. kimi_cli/ui/wire/jsonrpc.py +142 -0
  93. kimi_cli/ui/wire/protocol.py +1 -0
  94. kimi_cli/utils/__init__.py +0 -0
  95. kimi_cli/utils/aiohttp.py +2 -0
  96. kimi_cli/utils/aioqueue.py +72 -0
  97. kimi_cli/utils/broadcast.py +37 -0
  98. kimi_cli/utils/changelog.py +12 -7
  99. kimi_cli/utils/clipboard.py +12 -0
  100. kimi_cli/utils/datetime.py +37 -0
  101. kimi_cli/utils/environment.py +58 -0
  102. kimi_cli/utils/envvar.py +12 -0
  103. kimi_cli/utils/frontmatter.py +44 -0
  104. kimi_cli/utils/logging.py +7 -6
  105. kimi_cli/utils/message.py +9 -14
  106. kimi_cli/utils/path.py +99 -9
  107. kimi_cli/utils/pyinstaller.py +6 -0
  108. kimi_cli/utils/rich/__init__.py +33 -0
  109. kimi_cli/utils/rich/columns.py +99 -0
  110. kimi_cli/utils/rich/markdown.py +961 -0
  111. kimi_cli/utils/rich/markdown_sample.md +108 -0
  112. kimi_cli/utils/rich/markdown_sample_short.md +2 -0
  113. kimi_cli/utils/signals.py +2 -0
  114. kimi_cli/utils/slashcmd.py +124 -0
  115. kimi_cli/utils/string.py +2 -0
  116. kimi_cli/utils/term.py +168 -0
  117. kimi_cli/utils/typing.py +20 -0
  118. kimi_cli/wire/__init__.py +98 -29
  119. kimi_cli/wire/serde.py +45 -0
  120. kimi_cli/wire/types.py +299 -0
  121. kimi_cli-0.78.dist-info/METADATA +200 -0
  122. kimi_cli-0.78.dist-info/RECORD +135 -0
  123. kimi_cli-0.78.dist-info/entry_points.txt +4 -0
  124. kimi_cli/cli.py +0 -250
  125. kimi_cli/soul/runtime.py +0 -96
  126. kimi_cli/tools/bash/__init__.py +0 -99
  127. kimi_cli/tools/file/patch.md +0 -8
  128. kimi_cli/tools/file/patch.py +0 -143
  129. kimi_cli/tools/mcp.py +0 -85
  130. kimi_cli/ui/shell/liveview.py +0 -386
  131. kimi_cli/ui/shell/metacmd.py +0 -262
  132. kimi_cli/wire/message.py +0 -91
  133. kimi_cli-0.44.dist-info/METADATA +0 -188
  134. kimi_cli-0.44.dist-info/RECORD +0 -89
  135. kimi_cli-0.44.dist-info/entry_points.txt +0 -3
  136. /kimi_cli/tools/{task → multiagent}/task.md +0 -0
  137. {kimi_cli-0.44.dist-info → kimi_cli-0.78.dist-info}/WHEEL +0 -0
@@ -1,34 +1,33 @@
1
- You are Kimi CLI. You are an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and efficiently, adhering strictly to the following instructions and utilizing your available tools.
1
+ You are Kimi CLI, an interactive general AI agent running on a user's computer.
2
+
3
+ Your primary goal is to answer questions and/or finish tasks safely and efficiently, adhering strictly to the following system instructions and the user's requirements, leveraging the available tools flexibly.
2
4
 
3
5
  ${ROLE_ADDITIONAL}
4
6
 
5
7
  # Prompt and Tool Use
6
8
 
7
- The user's requests are provided in natural language within `user` messages, which may contain code snippets, logs, file paths, or specific requirements. ALWAYS follow the user's requests, always stay on track. Do not do anything that is not asked.
9
+ The user's messages may contain questions and/or task descriptions in natural language, code snippets, logs, file paths, or other forms of information. Read them, understand them and do what the user requested. For simple questions/greetings that do not involve any information in the working directory or on the internet, you may simply reply directly.
8
10
 
9
- When handling the user's request, you can call available tools to accomplish the task. When calling tools, do not provide explanations because the tool calls themselves should be self-explanatory. You MUST follow the description of each tool and its parameters when calling tools.
11
+ When handling the user's request, you may call available tools to accomplish the task. When calling tools, do not provide explanations because the tool calls themselves should be self-explanatory. You MUST follow the description of each tool and its parameters when calling tools.
10
12
 
11
13
  You have the capability to output any number of tool calls in a single response. If you anticipate making multiple non-interfering tool calls, you are HIGHLY RECOMMENDED to make them in parallel to significantly improve efficiency. This is very important to your performance.
12
14
 
13
- The results of the tool calls will be returned to you in a `tool` message. In some cases, non-plain-text content might be sent as a `user` message following the `tool` message. You must decide on your next action based on the tool call results, which could be one of the following: 1. Continue working on the task, 2. Inform the user that the task is completed or has failed, or 3. Ask the user for more information.
15
+ The results of the tool calls will be returned to you in a tool message. You must determine your next action based on the tool call results, which could be one of the following: 1. Continue working on the task, 2. Inform the user that the task is completed or has failed, or 3. Ask the user for more information.
14
16
 
15
- The system may, where appropriate, insert hints or information wrapped in `<system>` and `</system>` tags within `user` or `tool` messages. This information is relevant to the current task or tool calls, may or may not be important to you. Take this info into consideration when determining your next action.
17
+ The system may, where appropriate, insert hints or information wrapped in `<system>` and `</system>` tags within user or tool messages. This information is relevant to the current task or tool calls, may or may not be important to you. Take this info into consideration when determining your next action.
16
18
 
17
19
  When responding to the user, you MUST use the SAME language as the user, unless explicitly instructed to do otherwise.
18
20
 
19
- # General Coding Guidelines
20
-
21
- Always think carefully. Be patient and thorough. Do not give up too early.
22
-
23
- ALWAYS, keep it stupidly simple. Do not overcomplicate things.
21
+ # General Guidelines for Coding
24
22
 
25
23
  When building something from scratch, you should:
26
24
 
27
25
  - Understand the user's requirements.
26
+ - Clarify with the user if there is anything unclear.
28
27
  - Design the architecture and make a plan for the implementation.
29
28
  - Write the code in a modular and maintainable way.
30
29
 
31
- When working on existing codebase, you should:
30
+ When working on an existing codebase, you should:
32
31
 
33
32
  - Understand the codebase and the user's requirements. Identify the ultimate goal and the most important criteria to achieve the goal.
34
33
  - For a bug fix, you typically need to check error logs or failed tests, scan over the codebase to find the root cause, and figure out a fix. If user mentioned any failed tests, you should make sure they pass after the changes.
@@ -37,15 +36,32 @@ When working on existing codebase, you should:
37
36
  - Make MINIMAL changes to achieve the goal. This is very important to your performance.
38
37
  - Follow the coding style of existing code in the project.
39
38
 
39
+ DO NOT run `git commit`, `git push`, `git reset`, `git rebase` and/or do any other git mutations unless explicitly asked to do so. Ask for confirmation each time when you need to do git mutations, even if the user has confirmed in earlier conversations.
40
+
41
+ # General Guidelines for Research and Data Processing
42
+
43
+ The user may ask you to research on certain topics, or process certain multimedia files or folders. You must understand the user's requirements thoroughly, ask for clarification before you start if needed.
44
+
45
+ Make plans before you do deep or wide research tasks, to ensure you are always on track. Search on the Internet if possible, with carefully-designed search queries to improve efficiency and accuracy.
46
+
47
+ Operate on the user's computer carefully:
48
+
49
+ - When working on images, videos, PDFs, docs, spreadsheets, presentations, or other multimedia files, you may need to use proper shell commands or Python tools to process them. Detect if there are already such tools in the environment. If you have to install them, you MUST ensure that any third-party packages are installed in a virtual environment.
50
+ - Avoid installing or deleting anything to/from outside of the current working directory. If you have to do so, ask the user for confirmation.
51
+
40
52
  # Working Environment
41
53
 
42
54
  ## Operating System
43
55
 
44
- The operating environment is not in a sandbox. Any action especially mutation you do will immediately affect the user's system. So you MUST be extremely cautious. Unless being explicitly instructed to do so, you should never access (read/write/execute) files outside of the working directory.
56
+ The operating environment is not in a sandbox. Any actions you do will immediately affect the user's system. So you MUST be extremely cautious. Unless being explicitly instructed to do so, you should never access (read/write/execute) files outside of the working directory.
57
+
58
+ ## Date and Time
59
+
60
+ The current date and time in ISO format is `${KIMI_NOW}`. This is only a reference for you when searching the web, or checking file modification time, etc. If you need the exact time, use Shell tool with proper command.
45
61
 
46
62
  ## Working Directory
47
63
 
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.
64
+ 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 MUST use absolute paths for these parameters.
49
65
 
50
66
  The directory listing of current working directory is:
51
67
 
@@ -55,18 +71,60 @@ ${KIMI_WORK_DIR_LS}
55
71
 
56
72
  Use this as your basic understanding of the project structure.
57
73
 
58
- ## Date and Time
59
-
60
- The current date and time in ISO format is `${KIMI_NOW}`. This is only a reference for you when searching the web, or checking file modification time, etc. If you need the exact time, use Bash tool with proper command.
61
-
62
74
  # Project Information
63
75
 
64
- Markdown files named `AGENTS.md` usually contain the background, structure, coding styles, user preferences and other relevant information about the project. You should use this information to understand the project and the user's preferences. `AGENTS.md` files may exist at different locations in the project, but typically there is one in the project root. The following content between two `---`s is the content of the root-level `AGENTS.md` file.
76
+ Markdown files named `AGENTS.md` usually contain the background, structure, coding styles, user preferences and other relevant information about the project. You should use this information to understand the project and the user's preferences. `AGENTS.md` files may exist at different locations in the project, but typically there is one in the project root.
65
77
 
66
- `${KIMI_WORK_DIR}/AGENTS.md`:
78
+ > Why `AGENTS.md`?
79
+ >
80
+ > `README.md` files are for humans: quick starts, project descriptions, and contribution guidelines. `AGENTS.md` complements this by containing the extra, sometimes detailed context coding agents need: build steps, tests, and conventions that might clutter a README or aren’t relevant to human contributors.
81
+ >
82
+ > We intentionally kept it separate to:
83
+ >
84
+ > - Give agents a clear, predictable place for instructions.
85
+ > - Keep `README`s concise and focused on human contributors.
86
+ > - Provide precise, agent-focused guidance that complements existing `README` and docs.
67
87
 
68
- ---
88
+ The project level `${KIMI_WORK_DIR}/AGENTS.md`:
69
89
 
90
+ `````````
70
91
  ${KIMI_AGENTS_MD}
92
+ `````````
93
+
94
+ If the above `AGENTS.md` is empty or insufficient, you may check `README`/`README.md` files or `AGENTS.md` files in subdirectories for more information about specific parts of the project.
95
+
96
+ If you modified any files/styles/structures/configurations/workflows/... mentioned in `AGENTS.md` files, you MUST update the corresponding `AGENTS.md` files to keep them up-to-date.
97
+
98
+ # Skills
99
+
100
+ Skills are reusable, composable capabilities that enhance your abilities. Each skill is a self-contained directory with a `SKILL.md` file that contains instructions, examples, and/or reference material.
101
+
102
+ ## What are skills?
103
+
104
+ Skills are modular extensions that provide:
105
+
106
+ - Specialized knowledge: Domain-specific expertise (e.g., PDF processing, data analysis)
107
+ - Workflow patterns: Best practices for common tasks
108
+ - Tool integrations: Pre-configured tool chains for specific operations
109
+ - Reference material: Documentation, templates, and examples
110
+
111
+ ## Available skills
112
+
113
+ ${KIMI_SKILLS}
114
+
115
+ ## How to use skills
116
+
117
+ Identify the skills that are likely to be useful for the tasks you are currently working on, read the `SKILL.md` file for detailed instructions, guidelines, scripts and more.
118
+
119
+ Only read skill details when needed to conserve the context window.
120
+
121
+ # Ultimate Reminders
122
+
123
+ At any time, you should be HELPFUL and POLITE, CONCISE and ACCURATE, PATIENT and THOROUGH.
71
124
 
72
- ---
125
+ - Never diverge from the requirements and the goals of the task you work on. Stay on track.
126
+ - Never give the user more than what they want.
127
+ - Try your best to avoid any hallucination. Do fact checking before providing any factual information.
128
+ - Think twice before you act.
129
+ - Do not give up too early.
130
+ - ALWAYS, keep it stupidly simple. Do not overcomplicate things.
@@ -0,0 +1,17 @@
1
+ version: 1
2
+ agent:
3
+ extend: default
4
+ tools:
5
+ - "kimi_cli.tools.multiagent:Task"
6
+ # - "kimi_cli.tools.multiagent:CreateSubagent"
7
+ - "kimi_cli.tools.dmail:SendDMail"
8
+ # - "kimi_cli.tools.think:Think"
9
+ - "kimi_cli.tools.todo:SetTodoList"
10
+ - "kimi_cli.tools.shell:Shell"
11
+ - "kimi_cli.tools.file:ReadFile"
12
+ - "kimi_cli.tools.file:Glob"
13
+ - "kimi_cli.tools.file:Grep"
14
+ - "kimi_cli.tools.file:WriteFile"
15
+ - "kimi_cli.tools.file:StrReplaceFile"
16
+ - "kimi_cli.tools.web:SearchWeb"
17
+ - "kimi_cli.tools.web:FetchURL"
kimi_cli/agentspec.py CHANGED
@@ -1,3 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
1
4
  from pathlib import Path
2
5
  from typing import Any, NamedTuple
3
6
 
@@ -6,28 +9,43 @@ from pydantic import BaseModel, Field
6
9
 
7
10
  from kimi_cli.exception import AgentSpecError
8
11
 
12
+ DEFAULT_AGENT_SPEC_VERSION = "1"
13
+ SUPPORTED_AGENT_SPEC_VERSIONS = (DEFAULT_AGENT_SPEC_VERSION,)
14
+
9
15
 
10
16
  def get_agents_dir() -> Path:
11
17
  return Path(__file__).parent / "agents"
12
18
 
13
19
 
14
20
  DEFAULT_AGENT_FILE = get_agents_dir() / "default" / "agent.yaml"
21
+ OKABE_AGENT_FILE = get_agents_dir() / "okabe" / "agent.yaml"
22
+
23
+
24
+ class Inherit(NamedTuple):
25
+ """Marker class for inheritance in agent spec."""
26
+
27
+
28
+ inherit = Inherit()
15
29
 
16
30
 
17
31
  class AgentSpec(BaseModel):
18
32
  """Agent specification."""
19
33
 
20
34
  extend: str | None = Field(default=None, description="Agent file to extend")
21
- name: str | None = Field(default=None, description="Agent name") # required
22
- system_prompt_path: Path | None = Field(
23
- default=None, description="System prompt path"
35
+ name: str | Inherit = Field(default=inherit, description="Agent name") # required
36
+ system_prompt_path: Path | Inherit = Field(
37
+ default=inherit, description="System prompt path"
24
38
  ) # required
25
39
  system_prompt_args: dict[str, str] = Field(
26
40
  default_factory=dict, description="System prompt arguments"
27
41
  )
28
- tools: list[str] | None = Field(default=None, description="Tools") # required
29
- exclude_tools: list[str] | None = Field(default=None, description="Tools to exclude")
30
- subagents: dict[str, "SubagentSpec"] | None = Field(default=None, description="Subagents")
42
+ tools: list[str] | None | Inherit = Field(default=inherit, description="Tools") # required
43
+ exclude_tools: list[str] | None | Inherit = Field(
44
+ default=inherit, description="Tools to exclude"
45
+ )
46
+ subagents: dict[str, SubagentSpec] | None | Inherit = Field(
47
+ default=inherit, description="Subagents"
48
+ )
31
49
 
32
50
 
33
51
  class SubagentSpec(BaseModel):
@@ -37,7 +55,8 @@ class SubagentSpec(BaseModel):
37
55
  description: str = Field(description="Subagent description")
38
56
 
39
57
 
40
- class ResolvedAgentSpec(NamedTuple):
58
+ @dataclass(frozen=True, slots=True, kw_only=True)
59
+ class ResolvedAgentSpec:
41
60
  """Resolved agent specification."""
42
61
 
43
62
  name: str
@@ -45,7 +64,7 @@ class ResolvedAgentSpec(NamedTuple):
45
64
  system_prompt_args: dict[str, str]
46
65
  tools: list[str]
47
66
  exclude_tools: list[str]
48
- subagents: dict[str, "SubagentSpec"]
67
+ subagents: dict[str, SubagentSpec]
49
68
 
50
69
 
51
70
  def load_agent_spec(agent_file: Path) -> ResolvedAgentSpec:
@@ -58,58 +77,67 @@ def load_agent_spec(agent_file: Path) -> ResolvedAgentSpec:
58
77
  """
59
78
  agent_spec = _load_agent_spec(agent_file)
60
79
  assert agent_spec.extend is None, "agent extension should be recursively resolved"
61
- if agent_spec.name is None:
80
+ if isinstance(agent_spec.name, Inherit):
62
81
  raise AgentSpecError("Agent name is required")
63
- if agent_spec.system_prompt_path is None:
82
+ if isinstance(agent_spec.system_prompt_path, Inherit):
64
83
  raise AgentSpecError("System prompt path is required")
65
- if agent_spec.tools is None:
84
+ if isinstance(agent_spec.tools, Inherit):
66
85
  raise AgentSpecError("Tools are required")
86
+ if isinstance(agent_spec.exclude_tools, Inherit):
87
+ agent_spec.exclude_tools = []
88
+ if isinstance(agent_spec.subagents, Inherit):
89
+ agent_spec.subagents = {}
67
90
  return ResolvedAgentSpec(
68
91
  name=agent_spec.name,
69
92
  system_prompt_path=agent_spec.system_prompt_path,
70
93
  system_prompt_args=agent_spec.system_prompt_args,
71
- tools=agent_spec.tools,
94
+ tools=agent_spec.tools or [],
72
95
  exclude_tools=agent_spec.exclude_tools or [],
73
96
  subagents=agent_spec.subagents or {},
74
97
  )
75
98
 
76
99
 
77
100
  def _load_agent_spec(agent_file: Path) -> AgentSpec:
78
- assert agent_file.is_file(), "expect agent file to exist"
101
+ if not agent_file.exists():
102
+ raise AgentSpecError(f"Agent spec file not found: {agent_file}")
103
+ if not agent_file.is_file():
104
+ raise AgentSpecError(f"Agent spec path is not a file: {agent_file}")
79
105
  try:
80
106
  with open(agent_file, encoding="utf-8") as f:
81
107
  data: dict[str, Any] = yaml.safe_load(f)
82
108
  except yaml.YAMLError as e:
83
109
  raise AgentSpecError(f"Invalid YAML in agent spec file: {e}") from e
84
110
 
85
- version = data.get("version", 1)
86
- if version != 1:
111
+ version = str(data.get("version", DEFAULT_AGENT_SPEC_VERSION))
112
+ if version not in SUPPORTED_AGENT_SPEC_VERSIONS:
87
113
  raise AgentSpecError(f"Unsupported agent spec version: {version}")
88
114
 
89
115
  agent_spec = AgentSpec(**data.get("agent", {}))
90
- if agent_spec.system_prompt_path is not None:
91
- agent_spec.system_prompt_path = agent_file.parent / agent_spec.system_prompt_path
92
- if agent_spec.subagents is not None:
116
+ if isinstance(agent_spec.system_prompt_path, Path):
117
+ agent_spec.system_prompt_path = (
118
+ agent_file.parent / agent_spec.system_prompt_path
119
+ ).absolute()
120
+ if isinstance(agent_spec.subagents, dict):
93
121
  for v in agent_spec.subagents.values():
94
- v.path = agent_file.parent / v.path
122
+ v.path = (agent_file.parent / v.path).absolute()
95
123
  if agent_spec.extend:
96
124
  if agent_spec.extend == "default":
97
125
  base_agent_file = DEFAULT_AGENT_FILE
98
126
  else:
99
- base_agent_file = agent_file.parent / agent_spec.extend
127
+ base_agent_file = (agent_file.parent / agent_spec.extend).absolute()
100
128
  base_agent_spec = _load_agent_spec(base_agent_file)
101
- if agent_spec.name is not None:
129
+ if not isinstance(agent_spec.name, Inherit):
102
130
  base_agent_spec.name = agent_spec.name
103
- if agent_spec.system_prompt_path is not None:
131
+ if not isinstance(agent_spec.system_prompt_path, Inherit):
104
132
  base_agent_spec.system_prompt_path = agent_spec.system_prompt_path
105
133
  for k, v in agent_spec.system_prompt_args.items():
106
134
  # system prompt args should be merged instead of overwritten
107
135
  base_agent_spec.system_prompt_args[k] = v
108
- if agent_spec.tools is not None:
136
+ if not isinstance(agent_spec.tools, Inherit):
109
137
  base_agent_spec.tools = agent_spec.tools
110
- if agent_spec.exclude_tools is not None:
138
+ if not isinstance(agent_spec.exclude_tools, Inherit):
111
139
  base_agent_spec.exclude_tools = agent_spec.exclude_tools
112
- if agent_spec.subagents is not None:
140
+ if not isinstance(agent_spec.subagents, Inherit):
113
141
  base_agent_spec.subagents = agent_spec.subagents
114
142
  agent_spec = base_agent_spec
115
143
  return agent_spec
kimi_cli/app.py CHANGED
@@ -1,22 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
1
4
  import contextlib
2
- import os
3
5
  import warnings
4
- from collections.abc import Generator
6
+ from collections.abc import AsyncGenerator
5
7
  from pathlib import Path
6
- from typing import Any
8
+ from typing import TYPE_CHECKING, Any
7
9
 
10
+ import kaos
11
+ from kaos.path import KaosPath
8
12
  from pydantic import SecretStr
9
13
 
10
14
  from kimi_cli.agentspec import DEFAULT_AGENT_FILE
11
15
  from kimi_cli.cli import InputFormat, OutputFormat
12
- from kimi_cli.config import LLMModel, LLMProvider, load_config
16
+ from kimi_cli.config import Config, LLMModel, LLMProvider, load_config
17
+ from kimi_cli.flow import PromptFlow
13
18
  from kimi_cli.llm import augment_provider_with_env_vars, create_llm
14
19
  from kimi_cli.session import Session
15
- from kimi_cli.soul.agent import load_agent
20
+ from kimi_cli.share import get_share_dir
21
+ from kimi_cli.soul import run_soul
22
+ from kimi_cli.soul.agent import Runtime, load_agent
16
23
  from kimi_cli.soul.context import Context
17
24
  from kimi_cli.soul.kimisoul import KimiSoul
18
- from kimi_cli.soul.runtime import Runtime
25
+ from kimi_cli.utils.aioqueue import QueueShutDown
19
26
  from kimi_cli.utils.logging import StreamToLogger, logger
27
+ from kimi_cli.utils.path import shorten_home
28
+ from kimi_cli.wire import Wire, WireUISide
29
+ from kimi_cli.wire.types import ContentPart, WireMessage
30
+
31
+ if TYPE_CHECKING:
32
+ from fastmcp.mcp_config import MCPConfig
33
+
34
+
35
+ def enable_logging(debug: bool = False) -> None:
36
+ logger.remove() # Remove default stderr handler
37
+ logger.enable("kimi_cli")
38
+ if debug:
39
+ logger.enable("kosong")
40
+ logger.add(
41
+ get_share_dir() / "logs" / "kimi.log",
42
+ # FIXME: configure level for different modules
43
+ level="TRACE" if debug else "INFO",
44
+ rotation="06:00",
45
+ retention="10 days",
46
+ )
20
47
 
21
48
 
22
49
  class KimiCLI:
@@ -24,30 +51,60 @@ class KimiCLI:
24
51
  async def create(
25
52
  session: Session,
26
53
  *,
27
- yolo: bool = False,
28
- stream: bool = True, # TODO: remove this when we have a correct print mode impl
29
- mcp_configs: list[dict[str, Any]] | None = None,
30
- config_file: Path | None = None,
54
+ # Basic configuration
55
+ config: Config | Path | None = None,
31
56
  model_name: str | None = None,
57
+ thinking: bool | None = None,
58
+ # Run mode
59
+ yolo: bool = False,
60
+ # Extensions
32
61
  agent_file: Path | None = None,
33
- ) -> "KimiCLI":
62
+ mcp_configs: list[MCPConfig] | list[dict[str, Any]] | None = None,
63
+ skills_dir: Path | None = None,
64
+ # Loop control
65
+ max_steps_per_turn: int | None = None,
66
+ max_retries_per_step: int | None = None,
67
+ max_ralph_iterations: int | None = None,
68
+ flow: PromptFlow | None = None,
69
+ ) -> KimiCLI:
34
70
  """
35
71
  Create a KimiCLI instance.
36
72
 
37
73
  Args:
38
74
  session (Session): A session created by `Session.create` or `Session.continue_`.
39
- yolo (bool, optional): Approve all actions without confirmation. Defaults to False.
40
- stream (bool, optional): Use stream mode when calling LLM API. Defaults to True.
41
- config_file (Path | None, optional): Path to the configuration file. Defaults to None.
75
+ config (Config | Path | None, optional): Configuration to use, or path to config file.
76
+ Defaults to None.
42
77
  model_name (str | None, optional): Name of the model to use. Defaults to None.
78
+ thinking (bool | None, optional): Whether to enable thinking mode. Defaults to None.
79
+ yolo (bool, optional): Approve all actions without confirmation. Defaults to False.
43
80
  agent_file (Path | None, optional): Path to the agent file. Defaults to None.
81
+ mcp_configs (list[MCPConfig | dict[str, Any]] | None, optional): MCP configs to load
82
+ MCP tools from. Defaults to None.
83
+ skills_dir (Path | None, optional): Path to the skills directory. Defaults to None.
84
+ max_steps_per_turn (int | None, optional): Maximum number of steps in one turn.
85
+ Defaults to None.
86
+ max_retries_per_step (int | None, optional): Maximum number of retries in one step.
87
+ Defaults to None.
88
+ max_ralph_iterations (int | None, optional): Extra iterations after the first turn in
89
+ Ralph mode. Defaults to None.
90
+ flow (PromptFlow | None, optional): Prompt flow to execute. Defaults to None.
44
91
 
45
92
  Raises:
46
93
  FileNotFoundError: When the agent file is not found.
47
- ConfigError(KimiCLIException): When the configuration is invalid.
48
- AgentSpecError(KimiCLIException): When the agent specification is invalid.
94
+ ConfigError(KimiCLIException, ValueError): When the configuration is invalid.
95
+ AgentSpecError(KimiCLIException, ValueError): When the agent specification is invalid.
96
+ InvalidToolError(KimiCLIException, ValueError): When any tool cannot be loaded.
97
+ MCPConfigError(KimiCLIException, ValueError): When any MCP configuration is invalid.
98
+ MCPRuntimeError(KimiCLIException, RuntimeError): When any MCP server cannot be
99
+ connected.
49
100
  """
50
- config = load_config(config_file)
101
+ config = config if isinstance(config, Config) else load_config(config)
102
+ if max_steps_per_turn is not None:
103
+ config.loop_control.max_steps_per_turn = max_steps_per_turn
104
+ if max_retries_per_step is not None:
105
+ config.loop_control.max_retries_per_step = max_retries_per_step
106
+ if max_ralph_iterations is not None:
107
+ config.loop_control.max_ralph_iterations = max_ralph_iterations
51
108
  logger.info("Loaded config: {config}", config=config)
52
109
 
53
110
  model: LLMModel | None = None
@@ -72,27 +129,25 @@ class KimiCLI:
72
129
  assert model is not None
73
130
  env_overrides = augment_provider_with_env_vars(provider, model)
74
131
 
75
- if not provider.base_url or not model.model:
76
- llm = None
77
- else:
132
+ # determine thinking mode
133
+ thinking = config.default_thinking if thinking is None else thinking
134
+
135
+ llm = create_llm(provider, model, thinking=thinking, session_id=session.id)
136
+ if llm is not None:
78
137
  logger.info("Using LLM provider: {provider}", provider=provider)
79
138
  logger.info("Using LLM model: {model}", model=model)
80
- llm = create_llm(provider, model, stream=stream, session_id=session.id)
139
+ logger.info("Thinking mode: {thinking}", thinking=thinking)
81
140
 
82
- runtime = await Runtime.create(config, llm, session, yolo)
141
+ runtime = await Runtime.create(config, llm, session, yolo, skills_dir)
83
142
 
84
143
  if agent_file is None:
85
144
  agent_file = DEFAULT_AGENT_FILE
86
145
  agent = await load_agent(agent_file, runtime, mcp_configs=mcp_configs or [])
87
146
 
88
- context = Context(session.history_file)
147
+ context = Context(session.context_file)
89
148
  await context.restore()
90
149
 
91
- soul = KimiSoul(
92
- agent,
93
- runtime,
94
- context=context,
95
- )
150
+ soul = KimiSoul(agent, context=context, flow=flow)
96
151
  return KimiCLI(soul, runtime, env_overrides)
97
152
 
98
153
  def __init__(
@@ -115,23 +170,75 @@ class KimiCLI:
115
170
  """Get the Session instance."""
116
171
  return self._runtime.session
117
172
 
118
- @contextlib.contextmanager
119
- def _app_env(self) -> Generator[None]:
120
- original_cwd = Path.cwd()
121
- os.chdir(self._runtime.session.work_dir)
173
+ @contextlib.asynccontextmanager
174
+ async def _env(self) -> AsyncGenerator[None]:
175
+ original_cwd = KaosPath.cwd()
176
+ await kaos.chdir(self._runtime.session.work_dir)
122
177
  try:
123
178
  # to ignore possible warnings from dateparser
124
179
  warnings.filterwarnings("ignore", category=DeprecationWarning)
125
180
  with contextlib.redirect_stderr(StreamToLogger()):
126
181
  yield
127
182
  finally:
128
- os.chdir(original_cwd)
183
+ await kaos.chdir(original_cwd)
184
+
185
+ async def run(
186
+ self,
187
+ user_input: str | list[ContentPart],
188
+ cancel_event: asyncio.Event,
189
+ merge_wire_messages: bool = False,
190
+ ) -> AsyncGenerator[WireMessage]:
191
+ """
192
+ Run the Kimi CLI instance without any UI and yield Wire messages directly.
193
+
194
+ Args:
195
+ user_input (str | list[ContentPart]): The user input to the agent.
196
+ cancel_event (asyncio.Event): An event to cancel the run.
197
+ merge_wire_messages (bool): Whether to merge Wire messages as much as possible.
129
198
 
130
- async def run_shell_mode(self, command: str | None = None) -> bool:
131
- from kimi_cli.ui.shell import ShellApp, WelcomeInfoItem
199
+ Yields:
200
+ WireMessage: The Wire messages from the `KimiSoul`.
201
+
202
+ Raises:
203
+ LLMNotSet: When the LLM is not set.
204
+ LLMNotSupported: When the LLM does not have required capabilities.
205
+ ChatProviderError: When the LLM provider returns an error.
206
+ MaxStepsReached: When the maximum number of steps is reached.
207
+ RunCancelled: When the run is cancelled by the cancel event.
208
+ """
209
+ async with self._env():
210
+ wire_future = asyncio.Future[WireUISide]()
211
+ stop_ui_loop = asyncio.Event()
212
+
213
+ async def _ui_loop_fn(wire: Wire) -> None:
214
+ wire_future.set_result(wire.ui_side(merge=merge_wire_messages))
215
+ await stop_ui_loop.wait()
216
+
217
+ soul_task = asyncio.create_task(
218
+ run_soul(self.soul, user_input, _ui_loop_fn, cancel_event)
219
+ )
220
+
221
+ try:
222
+ wire_ui = await wire_future
223
+ while True:
224
+ msg = await wire_ui.receive()
225
+ yield msg
226
+ except QueueShutDown:
227
+ pass
228
+ finally:
229
+ # stop consuming Wire messages
230
+ stop_ui_loop.set()
231
+ # wait for the soul task to finish, or raise
232
+ await soul_task
233
+
234
+ async def run_shell(self, command: str | None = None) -> bool:
235
+ """Run the Kimi CLI instance with shell UI."""
236
+ from kimi_cli.ui.shell import Shell, WelcomeInfoItem
132
237
 
133
238
  welcome_info = [
134
- WelcomeInfoItem(name="Directory", value=str(self._runtime.session.work_dir)),
239
+ WelcomeInfoItem(
240
+ name="Directory", value=str(shorten_home(self._runtime.session.work_dir))
241
+ ),
135
242
  WelcomeInfoItem(name="Session", value=self._runtime.session.id),
136
243
  ]
137
244
  if base_url := self._env_overrides.get("KIMI_BASE_URL"):
@@ -142,6 +249,14 @@ class KimiCLI:
142
249
  level=WelcomeInfoItem.Level.WARN,
143
250
  )
144
251
  )
252
+ if self._env_overrides.get("KIMI_API_KEY"):
253
+ welcome_info.append(
254
+ WelcomeInfoItem(
255
+ name="API Key",
256
+ value="****** (from KIMI_API_KEY)",
257
+ level=WelcomeInfoItem.Level.WARN,
258
+ )
259
+ )
145
260
  if not self._runtime.llm:
146
261
  welcome_info.append(
147
262
  WelcomeInfoItem(
@@ -154,7 +269,7 @@ class KimiCLI:
154
269
  welcome_info.append(
155
270
  WelcomeInfoItem(
156
271
  name="Model",
157
- value=f"{self._soul.model} (from KIMI_MODEL_NAME)",
272
+ value=f"{self._soul.model_name} (from KIMI_MODEL_NAME)",
158
273
  level=WelcomeInfoItem.Level.WARN,
159
274
  )
160
275
  )
@@ -162,34 +277,47 @@ class KimiCLI:
162
277
  welcome_info.append(
163
278
  WelcomeInfoItem(
164
279
  name="Model",
165
- value=self._soul.model,
280
+ value=self._soul.model_name,
166
281
  level=WelcomeInfoItem.Level.INFO,
167
282
  )
168
283
  )
169
- with self._app_env():
170
- app = ShellApp(self._soul, welcome_info=welcome_info)
171
- return await app.run(command)
284
+ async with self._env():
285
+ shell = Shell(self._soul, welcome_info=welcome_info)
286
+ return await shell.run(command)
172
287
 
173
- async def run_print_mode(
288
+ async def run_print(
174
289
  self,
175
290
  input_format: InputFormat,
176
291
  output_format: OutputFormat,
177
292
  command: str | None = None,
293
+ *,
294
+ final_only: bool = False,
178
295
  ) -> bool:
179
- from kimi_cli.ui.print import PrintApp
296
+ """Run the Kimi CLI instance with print UI."""
297
+ from kimi_cli.ui.print import Print
180
298
 
181
- with self._app_env():
182
- app = PrintApp(
299
+ async with self._env():
300
+ print_ = Print(
183
301
  self._soul,
184
302
  input_format,
185
303
  output_format,
186
- self._runtime.session.history_file,
304
+ self._runtime.session.context_file,
305
+ final_only=final_only,
187
306
  )
188
- return await app.run(command)
307
+ return await print_.run(command)
308
+
309
+ async def run_acp(self) -> None:
310
+ """Run the Kimi CLI instance as ACP server."""
311
+ from kimi_cli.ui.acp import ACP
312
+
313
+ async with self._env():
314
+ acp = ACP(self._soul)
315
+ await acp.run()
189
316
 
190
- async def run_acp_server(self) -> bool:
191
- from kimi_cli.ui.acp import ACPServer
317
+ async def run_wire_stdio(self) -> None:
318
+ """Run the Kimi CLI instance as Wire server over stdio."""
319
+ from kimi_cli.ui.wire import WireOverStdio
192
320
 
193
- with self._app_env():
194
- app = ACPServer(self._soul)
195
- return await app.run()
321
+ async with self._env():
322
+ server = WireOverStdio(self._soul)
323
+ await server.serve()