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.

Files changed (76) hide show
  1. kimi_cli/CHANGELOG.md +304 -0
  2. kimi_cli/__init__.py +374 -0
  3. kimi_cli/agent.py +261 -0
  4. kimi_cli/agents/koder/README.md +3 -0
  5. kimi_cli/agents/koder/agent.yaml +24 -0
  6. kimi_cli/agents/koder/sub.yaml +11 -0
  7. kimi_cli/agents/koder/system.md +72 -0
  8. kimi_cli/config.py +138 -0
  9. kimi_cli/llm.py +8 -0
  10. kimi_cli/metadata.py +117 -0
  11. kimi_cli/prompts/metacmds/__init__.py +4 -0
  12. kimi_cli/prompts/metacmds/compact.md +74 -0
  13. kimi_cli/prompts/metacmds/init.md +21 -0
  14. kimi_cli/py.typed +0 -0
  15. kimi_cli/share.py +8 -0
  16. kimi_cli/soul/__init__.py +59 -0
  17. kimi_cli/soul/approval.py +69 -0
  18. kimi_cli/soul/context.py +142 -0
  19. kimi_cli/soul/denwarenji.py +37 -0
  20. kimi_cli/soul/kimisoul.py +248 -0
  21. kimi_cli/soul/message.py +76 -0
  22. kimi_cli/soul/toolset.py +25 -0
  23. kimi_cli/soul/wire.py +101 -0
  24. kimi_cli/tools/__init__.py +85 -0
  25. kimi_cli/tools/bash/__init__.py +97 -0
  26. kimi_cli/tools/bash/bash.md +31 -0
  27. kimi_cli/tools/dmail/__init__.py +38 -0
  28. kimi_cli/tools/dmail/dmail.md +15 -0
  29. kimi_cli/tools/file/__init__.py +21 -0
  30. kimi_cli/tools/file/glob.md +17 -0
  31. kimi_cli/tools/file/glob.py +149 -0
  32. kimi_cli/tools/file/grep.md +5 -0
  33. kimi_cli/tools/file/grep.py +285 -0
  34. kimi_cli/tools/file/patch.md +8 -0
  35. kimi_cli/tools/file/patch.py +131 -0
  36. kimi_cli/tools/file/read.md +14 -0
  37. kimi_cli/tools/file/read.py +139 -0
  38. kimi_cli/tools/file/replace.md +7 -0
  39. kimi_cli/tools/file/replace.py +132 -0
  40. kimi_cli/tools/file/write.md +5 -0
  41. kimi_cli/tools/file/write.py +107 -0
  42. kimi_cli/tools/mcp.py +85 -0
  43. kimi_cli/tools/task/__init__.py +156 -0
  44. kimi_cli/tools/task/task.md +26 -0
  45. kimi_cli/tools/test.py +55 -0
  46. kimi_cli/tools/think/__init__.py +21 -0
  47. kimi_cli/tools/think/think.md +1 -0
  48. kimi_cli/tools/todo/__init__.py +27 -0
  49. kimi_cli/tools/todo/set_todo_list.md +15 -0
  50. kimi_cli/tools/utils.py +150 -0
  51. kimi_cli/tools/web/__init__.py +4 -0
  52. kimi_cli/tools/web/fetch.md +1 -0
  53. kimi_cli/tools/web/fetch.py +94 -0
  54. kimi_cli/tools/web/search.md +1 -0
  55. kimi_cli/tools/web/search.py +126 -0
  56. kimi_cli/ui/__init__.py +68 -0
  57. kimi_cli/ui/acp/__init__.py +441 -0
  58. kimi_cli/ui/print/__init__.py +176 -0
  59. kimi_cli/ui/shell/__init__.py +326 -0
  60. kimi_cli/ui/shell/console.py +3 -0
  61. kimi_cli/ui/shell/liveview.py +158 -0
  62. kimi_cli/ui/shell/metacmd.py +309 -0
  63. kimi_cli/ui/shell/prompt.py +574 -0
  64. kimi_cli/ui/shell/setup.py +192 -0
  65. kimi_cli/ui/shell/update.py +204 -0
  66. kimi_cli/utils/changelog.py +101 -0
  67. kimi_cli/utils/logging.py +18 -0
  68. kimi_cli/utils/message.py +8 -0
  69. kimi_cli/utils/path.py +23 -0
  70. kimi_cli/utils/provider.py +64 -0
  71. kimi_cli/utils/pyinstaller.py +24 -0
  72. kimi_cli/utils/string.py +12 -0
  73. kimi_cli-0.35.dist-info/METADATA +24 -0
  74. kimi_cli-0.35.dist-info/RECORD +76 -0
  75. kimi_cli-0.35.dist-info/WHEEL +4 -0
  76. 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
+ )