kimi-cli 0.35__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.
- kimi_cli/CHANGELOG.md +304 -0
- kimi_cli/__init__.py +374 -0
- kimi_cli/agent.py +261 -0
- kimi_cli/agents/koder/README.md +3 -0
- kimi_cli/agents/koder/agent.yaml +24 -0
- kimi_cli/agents/koder/sub.yaml +11 -0
- kimi_cli/agents/koder/system.md +72 -0
- kimi_cli/config.py +138 -0
- kimi_cli/llm.py +8 -0
- kimi_cli/metadata.py +117 -0
- kimi_cli/prompts/metacmds/__init__.py +4 -0
- kimi_cli/prompts/metacmds/compact.md +74 -0
- kimi_cli/prompts/metacmds/init.md +21 -0
- kimi_cli/py.typed +0 -0
- kimi_cli/share.py +8 -0
- kimi_cli/soul/__init__.py +59 -0
- kimi_cli/soul/approval.py +69 -0
- kimi_cli/soul/context.py +142 -0
- kimi_cli/soul/denwarenji.py +37 -0
- kimi_cli/soul/kimisoul.py +248 -0
- kimi_cli/soul/message.py +76 -0
- kimi_cli/soul/toolset.py +25 -0
- kimi_cli/soul/wire.py +101 -0
- kimi_cli/tools/__init__.py +85 -0
- kimi_cli/tools/bash/__init__.py +97 -0
- kimi_cli/tools/bash/bash.md +31 -0
- kimi_cli/tools/dmail/__init__.py +38 -0
- kimi_cli/tools/dmail/dmail.md +15 -0
- kimi_cli/tools/file/__init__.py +21 -0
- kimi_cli/tools/file/glob.md +17 -0
- kimi_cli/tools/file/glob.py +149 -0
- kimi_cli/tools/file/grep.md +5 -0
- kimi_cli/tools/file/grep.py +285 -0
- kimi_cli/tools/file/patch.md +8 -0
- kimi_cli/tools/file/patch.py +131 -0
- kimi_cli/tools/file/read.md +14 -0
- kimi_cli/tools/file/read.py +139 -0
- kimi_cli/tools/file/replace.md +7 -0
- kimi_cli/tools/file/replace.py +132 -0
- kimi_cli/tools/file/write.md +5 -0
- kimi_cli/tools/file/write.py +107 -0
- kimi_cli/tools/mcp.py +85 -0
- kimi_cli/tools/task/__init__.py +156 -0
- kimi_cli/tools/task/task.md +26 -0
- kimi_cli/tools/test.py +55 -0
- kimi_cli/tools/think/__init__.py +21 -0
- kimi_cli/tools/think/think.md +1 -0
- kimi_cli/tools/todo/__init__.py +27 -0
- kimi_cli/tools/todo/set_todo_list.md +15 -0
- kimi_cli/tools/utils.py +150 -0
- kimi_cli/tools/web/__init__.py +4 -0
- kimi_cli/tools/web/fetch.md +1 -0
- kimi_cli/tools/web/fetch.py +94 -0
- kimi_cli/tools/web/search.md +1 -0
- kimi_cli/tools/web/search.py +126 -0
- kimi_cli/ui/__init__.py +68 -0
- kimi_cli/ui/acp/__init__.py +441 -0
- kimi_cli/ui/print/__init__.py +176 -0
- kimi_cli/ui/shell/__init__.py +326 -0
- kimi_cli/ui/shell/console.py +3 -0
- kimi_cli/ui/shell/liveview.py +158 -0
- kimi_cli/ui/shell/metacmd.py +309 -0
- kimi_cli/ui/shell/prompt.py +574 -0
- kimi_cli/ui/shell/setup.py +192 -0
- kimi_cli/ui/shell/update.py +204 -0
- kimi_cli/utils/changelog.py +101 -0
- kimi_cli/utils/logging.py +18 -0
- kimi_cli/utils/message.py +8 -0
- kimi_cli/utils/path.py +23 -0
- kimi_cli/utils/provider.py +64 -0
- kimi_cli/utils/pyinstaller.py +24 -0
- kimi_cli/utils/string.py +12 -0
- kimi_cli-0.35.dist-info/METADATA +24 -0
- kimi_cli-0.35.dist-info/RECORD +76 -0
- kimi_cli-0.35.dist-info/WHEEL +4 -0
- kimi_cli-0.35.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
import webbrowser
|
|
3
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from string import Template
|
|
6
|
+
from typing import TYPE_CHECKING, NamedTuple, overload
|
|
7
|
+
|
|
8
|
+
from kosong.base import generate
|
|
9
|
+
from kosong.base.message import ContentPart, Message, TextPart
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
|
|
12
|
+
import kimi_cli.prompts.metacmds as prompts
|
|
13
|
+
from kimi_cli.agent import load_agents_md
|
|
14
|
+
from kimi_cli.soul import LLMNotSet
|
|
15
|
+
from kimi_cli.soul.context import Context
|
|
16
|
+
from kimi_cli.soul.kimisoul import KimiSoul
|
|
17
|
+
from kimi_cli.soul.message import system
|
|
18
|
+
from kimi_cli.ui.shell.console import console
|
|
19
|
+
from kimi_cli.utils.changelog import CHANGELOG, format_release_notes
|
|
20
|
+
from kimi_cli.utils.logging import logger
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from kimi_cli.ui.shell import ShellApp
|
|
24
|
+
|
|
25
|
+
type MetaCmdFunc = Callable[["ShellApp", list[str]], None | Awaitable[None]]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MetaCommand(NamedTuple):
|
|
29
|
+
name: str
|
|
30
|
+
description: str
|
|
31
|
+
func: MetaCmdFunc
|
|
32
|
+
aliases: list[str]
|
|
33
|
+
kimi_soul_only: bool
|
|
34
|
+
# TODO: actually kimi_soul_only meta commands should be defined in KimiSoul
|
|
35
|
+
|
|
36
|
+
def slash_name(self):
|
|
37
|
+
"""/name (aliases)"""
|
|
38
|
+
if self.aliases:
|
|
39
|
+
return f"/{self.name} ({', '.join(self.aliases)})"
|
|
40
|
+
return f"/{self.name}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# primary name -> MetaCommand
|
|
44
|
+
_meta_commands: dict[str, MetaCommand] = {}
|
|
45
|
+
# primary name or alias -> MetaCommand
|
|
46
|
+
_meta_command_aliases: dict[str, MetaCommand] = {}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_meta_command(name: str) -> MetaCommand | None:
|
|
50
|
+
return _meta_command_aliases.get(name)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_meta_commands() -> list[MetaCommand]:
|
|
54
|
+
"""Get all unique primary meta commands (without duplicating aliases)."""
|
|
55
|
+
return list(_meta_commands.values())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@overload
|
|
59
|
+
def meta_command(func: MetaCmdFunc, /) -> MetaCmdFunc: ...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@overload
|
|
63
|
+
def meta_command(
|
|
64
|
+
*,
|
|
65
|
+
name: str | None = None,
|
|
66
|
+
aliases: Sequence[str] | None = None,
|
|
67
|
+
kimi_soul_only: bool = False,
|
|
68
|
+
) -> Callable[[MetaCmdFunc], MetaCmdFunc]: ...
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def meta_command(
|
|
72
|
+
func: MetaCmdFunc | None = None,
|
|
73
|
+
*,
|
|
74
|
+
name: str | None = None,
|
|
75
|
+
aliases: Sequence[str] | None = None,
|
|
76
|
+
kimi_soul_only: bool = False,
|
|
77
|
+
) -> (
|
|
78
|
+
MetaCmdFunc
|
|
79
|
+
| Callable[
|
|
80
|
+
[MetaCmdFunc],
|
|
81
|
+
MetaCmdFunc,
|
|
82
|
+
]
|
|
83
|
+
):
|
|
84
|
+
"""Decorator to register a meta command with optional custom name and aliases.
|
|
85
|
+
|
|
86
|
+
Usage examples:
|
|
87
|
+
@meta_command
|
|
88
|
+
def help(app: App, args: list[str]): ...
|
|
89
|
+
|
|
90
|
+
@meta_command(name="run")
|
|
91
|
+
def start(app: App, args: list[str]): ...
|
|
92
|
+
|
|
93
|
+
@meta_command(aliases=["h", "?", "assist"])
|
|
94
|
+
def help(app: App, args: list[str]): ...
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def _register(f: MetaCmdFunc):
|
|
98
|
+
primary = name or f.__name__
|
|
99
|
+
alias_list = list(aliases) if aliases else []
|
|
100
|
+
|
|
101
|
+
# Create the primary command with aliases
|
|
102
|
+
cmd = MetaCommand(
|
|
103
|
+
name=primary,
|
|
104
|
+
description=(f.__doc__ or "").strip(),
|
|
105
|
+
func=f,
|
|
106
|
+
aliases=alias_list,
|
|
107
|
+
kimi_soul_only=kimi_soul_only,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Register primary command
|
|
111
|
+
_meta_commands[primary] = cmd
|
|
112
|
+
_meta_command_aliases[primary] = cmd
|
|
113
|
+
|
|
114
|
+
# Register aliases pointing to the same command
|
|
115
|
+
for alias in alias_list:
|
|
116
|
+
_meta_command_aliases[alias] = cmd
|
|
117
|
+
|
|
118
|
+
return f
|
|
119
|
+
|
|
120
|
+
if func is not None:
|
|
121
|
+
return _register(func)
|
|
122
|
+
return _register
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@meta_command(aliases=["quit"])
|
|
126
|
+
def exit(app: "ShellApp", args: list[str]):
|
|
127
|
+
"""Exit the application"""
|
|
128
|
+
# should be handled by `ShellApp`
|
|
129
|
+
raise NotImplementedError
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
_HELP_MESSAGE_FMT = """
|
|
133
|
+
[grey50]▌ Help! I need somebody. Help! Not just anybody.[/grey50]
|
|
134
|
+
[grey50]▌ Help! You know I need someone. Help![/grey50]
|
|
135
|
+
[grey50]▌ ― The Beatles, [italic]Help![/italic][/grey50]
|
|
136
|
+
|
|
137
|
+
Sure, Kimi CLI is ready to help!
|
|
138
|
+
Just send me messages and I will help you get things done!
|
|
139
|
+
|
|
140
|
+
Meta commands are also available:
|
|
141
|
+
|
|
142
|
+
[grey50]{meta_commands_md}[/grey50]
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@meta_command(aliases=["h", "?"])
|
|
147
|
+
def help(app: "ShellApp", args: list[str]):
|
|
148
|
+
"""Show help information"""
|
|
149
|
+
console.print(
|
|
150
|
+
Panel(
|
|
151
|
+
_HELP_MESSAGE_FMT.format(
|
|
152
|
+
meta_commands_md="\n".join(
|
|
153
|
+
f" • {command.slash_name()}: {command.description}"
|
|
154
|
+
for command in get_meta_commands()
|
|
155
|
+
)
|
|
156
|
+
).strip(),
|
|
157
|
+
title="Kimi CLI Help",
|
|
158
|
+
border_style="wheat4",
|
|
159
|
+
expand=False,
|
|
160
|
+
padding=(1, 2),
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@meta_command
|
|
166
|
+
def version(app: "ShellApp", args: list[str]):
|
|
167
|
+
"""Show version information"""
|
|
168
|
+
from kimi_cli import __version__
|
|
169
|
+
|
|
170
|
+
console.print(f"kimi, version {__version__}")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@meta_command(name="release-notes")
|
|
174
|
+
def release_notes(app: "ShellApp", args: list[str]):
|
|
175
|
+
"""Show release notes"""
|
|
176
|
+
text = format_release_notes(CHANGELOG)
|
|
177
|
+
with console.pager(styles=True):
|
|
178
|
+
console.print(Panel.fit(text, border_style="wheat4", title="Release Notes"))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@meta_command
|
|
182
|
+
def feedback(app: "ShellApp", args: list[str]):
|
|
183
|
+
"""Submit feedback to make Kimi CLI better"""
|
|
184
|
+
|
|
185
|
+
ISSUE_URL = "https://github.com/MoonshotAI/kimi-cli/issues"
|
|
186
|
+
if webbrowser.open(ISSUE_URL):
|
|
187
|
+
return
|
|
188
|
+
console.print(f"Please submit feedback at [underline]{ISSUE_URL}[/underline].")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@meta_command
|
|
192
|
+
async def init(app: "ShellApp", args: list[str]):
|
|
193
|
+
"""Analyze the codebase and generate an `AGENTS.md` file"""
|
|
194
|
+
soul_bak = app.soul
|
|
195
|
+
if not isinstance(soul_bak, KimiSoul):
|
|
196
|
+
console.print("[red]Failed to analyze the codebase.[/red]")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
200
|
+
logger.info("Running `/init`")
|
|
201
|
+
console.print("Analyzing the codebase...")
|
|
202
|
+
tmp_context = Context(file_backend=Path(temp_dir) / "context.jsonl")
|
|
203
|
+
app.soul = KimiSoul(
|
|
204
|
+
soul_bak._agent,
|
|
205
|
+
soul_bak._agent_globals,
|
|
206
|
+
context=tmp_context,
|
|
207
|
+
loop_control=soul_bak._loop_control,
|
|
208
|
+
)
|
|
209
|
+
ok = await app._run(prompts.INIT)
|
|
210
|
+
|
|
211
|
+
if ok:
|
|
212
|
+
console.print(
|
|
213
|
+
"Codebase analyzed successfully! "
|
|
214
|
+
"An [underline]AGENTS.md[/underline] file has been created."
|
|
215
|
+
)
|
|
216
|
+
else:
|
|
217
|
+
console.print("[red]Failed to analyze the codebase.[/red]")
|
|
218
|
+
|
|
219
|
+
app.soul = soul_bak
|
|
220
|
+
agents_md = load_agents_md(soul_bak._agent_globals.builtin_args.KIMI_WORK_DIR)
|
|
221
|
+
system_message = system(
|
|
222
|
+
"The user just ran `/init` meta command. "
|
|
223
|
+
"The system has analyzed the codebase and generated an `AGENTS.md` file. "
|
|
224
|
+
f"Latest AGENTS.md file content:\n{agents_md}"
|
|
225
|
+
)
|
|
226
|
+
await app.soul._context.append_message(Message(role="user", content=[system_message]))
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@meta_command(aliases=["reset"], kimi_soul_only=True)
|
|
230
|
+
async def clear(app: "ShellApp", args: list[str]):
|
|
231
|
+
"""Clear the context"""
|
|
232
|
+
assert isinstance(app.soul, KimiSoul)
|
|
233
|
+
|
|
234
|
+
if app.soul._context.n_checkpoints == 0:
|
|
235
|
+
console.print("[yellow]Context is empty.[/yellow]")
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
await app.soul._context.revert_to(0)
|
|
239
|
+
console.print("[green]✓[/green] Context has been cleared.")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@meta_command
|
|
243
|
+
async def compact(app: "ShellApp", args: list[str]):
|
|
244
|
+
"""Compact the context"""
|
|
245
|
+
assert isinstance(app.soul, KimiSoul)
|
|
246
|
+
|
|
247
|
+
logger.info("Running `/compact`")
|
|
248
|
+
|
|
249
|
+
if app.soul._agent_globals.llm is None:
|
|
250
|
+
raise LLMNotSet()
|
|
251
|
+
|
|
252
|
+
# Get current context history
|
|
253
|
+
current_history = list(app.soul._context.history)
|
|
254
|
+
if len(current_history) <= 1:
|
|
255
|
+
console.print("[yellow]Context is too short to compact.[/yellow]")
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
# Convert history to string for the compact prompt
|
|
259
|
+
history_text = "\n\n".join(
|
|
260
|
+
f"## Message {i + 1}\nRole: {msg.role}\nContent: {msg.content}"
|
|
261
|
+
for i, msg in enumerate(current_history)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Build the compact prompt using string template
|
|
265
|
+
compact_template = Template(prompts.COMPACT)
|
|
266
|
+
compact_prompt = compact_template.substitute(CONTEXT=history_text)
|
|
267
|
+
|
|
268
|
+
# Create input message for compaction
|
|
269
|
+
compact_message = Message(role="user", content=compact_prompt)
|
|
270
|
+
|
|
271
|
+
# Call generate to get the compacted context
|
|
272
|
+
try:
|
|
273
|
+
with console.status("[cyan]Compacting...[/cyan]"):
|
|
274
|
+
compacted_msg, usage = await generate(
|
|
275
|
+
chat_provider=app.soul._agent_globals.llm.chat_provider,
|
|
276
|
+
system_prompt="You are a helpful assistant that compacts conversation context.",
|
|
277
|
+
tools=[],
|
|
278
|
+
history=[compact_message],
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Clear the context and add the compacted message as the first message
|
|
282
|
+
await app.soul._context.revert_to(0)
|
|
283
|
+
content: list[ContentPart] = (
|
|
284
|
+
[TextPart(text=compacted_msg.content)]
|
|
285
|
+
if isinstance(compacted_msg.content, str)
|
|
286
|
+
else compacted_msg.content
|
|
287
|
+
)
|
|
288
|
+
content.insert(
|
|
289
|
+
0, system("Previous context has been compacted. Here is the compaction output:")
|
|
290
|
+
)
|
|
291
|
+
await app.soul._context.append_message(Message(role="assistant", content=content))
|
|
292
|
+
|
|
293
|
+
console.print("[green]✓[/green] Context has been compacted.")
|
|
294
|
+
if usage:
|
|
295
|
+
logger.info(
|
|
296
|
+
"Compaction used {input} input tokens and {output} output tokens",
|
|
297
|
+
input=usage.input,
|
|
298
|
+
output=usage.output,
|
|
299
|
+
)
|
|
300
|
+
except Exception as e:
|
|
301
|
+
logger.error("Failed to compact context: {error}", error=e)
|
|
302
|
+
console.print(f"[red]Failed to compact the context: {e}[/red]")
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
from . import ( # noqa: E402
|
|
307
|
+
setup, # noqa: F401
|
|
308
|
+
update, # noqa: F401
|
|
309
|
+
)
|