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
@@ -0,0 +1,595 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Annotated, Literal
8
+
9
+ import typer
10
+
11
+ from kimi_cli.constant import VERSION
12
+
13
+ from .info import cli as info_cli
14
+ from .mcp import cli as mcp_cli
15
+
16
+
17
+ class Reload(Exception):
18
+ """Reload configuration."""
19
+
20
+ def __init__(self, session_id: str | None = None):
21
+ super().__init__("reload")
22
+ self.session_id = session_id
23
+
24
+
25
+ cli = typer.Typer(
26
+ add_completion=False,
27
+ context_settings={"help_option_names": ["-h", "--help"]},
28
+ help="Kimi, your next CLI agent.",
29
+ )
30
+
31
+ UIMode = Literal["shell", "print", "acp", "wire"]
32
+ InputFormat = Literal["text", "stream-json"]
33
+ OutputFormat = Literal["text", "stream-json"]
34
+
35
+
36
+ def _version_callback(value: bool) -> None:
37
+ if value:
38
+ typer.echo(f"kimi, version {VERSION}")
39
+ raise typer.Exit()
40
+
41
+
42
+ @cli.callback(invoke_without_command=True)
43
+ def kimi(
44
+ ctx: typer.Context,
45
+ # Meta
46
+ version: Annotated[
47
+ bool,
48
+ typer.Option(
49
+ "--version",
50
+ "-V",
51
+ help="Show version and exit.",
52
+ callback=_version_callback,
53
+ is_eager=True,
54
+ ),
55
+ ] = False,
56
+ verbose: Annotated[
57
+ bool,
58
+ typer.Option(
59
+ "--verbose",
60
+ help="Print verbose information. Default: no.",
61
+ ),
62
+ ] = False,
63
+ debug: Annotated[
64
+ bool,
65
+ typer.Option(
66
+ "--debug",
67
+ help="Log debug information. Default: no.",
68
+ ),
69
+ ] = False,
70
+ # Basic configuration
71
+ local_work_dir: Annotated[
72
+ Path | None,
73
+ typer.Option(
74
+ "--work-dir",
75
+ "-w",
76
+ exists=True,
77
+ file_okay=False,
78
+ dir_okay=True,
79
+ readable=True,
80
+ writable=True,
81
+ help="Working directory for the agent. Default: current directory.",
82
+ ),
83
+ ] = None,
84
+ session_id: Annotated[
85
+ str | None,
86
+ typer.Option(
87
+ "--session",
88
+ "-S",
89
+ help="Session ID to resume for the working directory. Default: new session.",
90
+ ),
91
+ ] = None,
92
+ continue_: Annotated[
93
+ bool,
94
+ typer.Option(
95
+ "--continue",
96
+ "-C",
97
+ help="Continue the previous session for the working directory. Default: no.",
98
+ ),
99
+ ] = False,
100
+ config_string: Annotated[
101
+ str | None,
102
+ typer.Option(
103
+ "--config",
104
+ help="Config TOML/JSON string to load. Default: none.",
105
+ ),
106
+ ] = None,
107
+ config_file: Annotated[
108
+ Path | None,
109
+ typer.Option(
110
+ "--config-file",
111
+ exists=True,
112
+ file_okay=True,
113
+ dir_okay=False,
114
+ readable=True,
115
+ help="Config TOML/JSON file to load. Default: ~/.kimi/config.toml.",
116
+ ),
117
+ ] = None,
118
+ model_name: Annotated[
119
+ str | None,
120
+ typer.Option(
121
+ "--model",
122
+ "-m",
123
+ help="LLM model to use. Default: default model set in config file.",
124
+ ),
125
+ ] = None,
126
+ thinking: Annotated[
127
+ bool | None,
128
+ typer.Option(
129
+ "--thinking/--no-thinking",
130
+ help="Enable thinking mode. Default: default thinking mode set in config file.",
131
+ ),
132
+ ] = None,
133
+ # Run mode
134
+ yolo: Annotated[
135
+ bool,
136
+ typer.Option(
137
+ "--yolo",
138
+ "--yes",
139
+ "-y",
140
+ "--auto-approve",
141
+ help="Automatically approve all actions. Default: no.",
142
+ ),
143
+ ] = False,
144
+ prompt: Annotated[
145
+ str | None,
146
+ typer.Option(
147
+ "--prompt",
148
+ "-p",
149
+ "--command",
150
+ "-c",
151
+ help="User prompt to the agent. Default: prompt interactively.",
152
+ ),
153
+ ] = None,
154
+ prompt_flow: Annotated[
155
+ Path | None,
156
+ typer.Option(
157
+ "--prompt-flow",
158
+ exists=True,
159
+ file_okay=True,
160
+ dir_okay=False,
161
+ readable=True,
162
+ help="D2 (.d2) or Mermaid (.mmd) flowchart file to run as a prompt flow.",
163
+ ),
164
+ ] = None,
165
+ print_mode: Annotated[
166
+ bool,
167
+ typer.Option(
168
+ "--print",
169
+ help=(
170
+ "Run in print mode (non-interactive). Note: print mode implicitly adds `--yolo`."
171
+ ),
172
+ ),
173
+ ] = False,
174
+ acp_mode: Annotated[
175
+ bool,
176
+ typer.Option(
177
+ "--acp",
178
+ help="(Deprecated, use `kimi acp` instead) Run as ACP server.",
179
+ ),
180
+ ] = False,
181
+ wire_mode: Annotated[
182
+ bool,
183
+ typer.Option(
184
+ "--wire",
185
+ help="Run as Wire server (experimental).",
186
+ ),
187
+ ] = False,
188
+ input_format: Annotated[
189
+ InputFormat | None,
190
+ typer.Option(
191
+ "--input-format",
192
+ help=(
193
+ "Input format to use. Must be used with `--print` "
194
+ "and the input must be piped in via stdin. "
195
+ "Default: text."
196
+ ),
197
+ ),
198
+ ] = None,
199
+ output_format: Annotated[
200
+ OutputFormat | None,
201
+ typer.Option(
202
+ "--output-format",
203
+ help="Output format to use. Must be used with `--print`. Default: text.",
204
+ ),
205
+ ] = None,
206
+ final_message_only: Annotated[
207
+ bool,
208
+ typer.Option(
209
+ "--final-message-only",
210
+ help="Only print the final assistant message (print UI).",
211
+ ),
212
+ ] = False,
213
+ quiet: Annotated[
214
+ bool,
215
+ typer.Option(
216
+ "--quiet",
217
+ help="Alias for `--print --output-format text --final-message-only`.",
218
+ ),
219
+ ] = False,
220
+ # Customization
221
+ agent: Annotated[
222
+ Literal["default", "okabe"] | None,
223
+ typer.Option(
224
+ "--agent",
225
+ help="Builtin agent specification to use. Default: builtin default agent.",
226
+ ),
227
+ ] = None,
228
+ agent_file: Annotated[
229
+ Path | None,
230
+ typer.Option(
231
+ "--agent-file",
232
+ exists=True,
233
+ file_okay=True,
234
+ dir_okay=False,
235
+ readable=True,
236
+ help="Custom agent specification file. Default: builtin default agent.",
237
+ ),
238
+ ] = None,
239
+ mcp_config_file: Annotated[
240
+ list[Path] | None,
241
+ typer.Option(
242
+ "--mcp-config-file",
243
+ exists=True,
244
+ file_okay=True,
245
+ dir_okay=False,
246
+ readable=True,
247
+ help=(
248
+ "MCP config file to load. Add this option multiple times to specify multiple MCP "
249
+ "configs. Default: none."
250
+ ),
251
+ ),
252
+ ] = None,
253
+ mcp_config: Annotated[
254
+ list[str] | None,
255
+ typer.Option(
256
+ "--mcp-config",
257
+ help=(
258
+ "MCP config JSON to load. Add this option multiple times to specify multiple MCP "
259
+ "configs. Default: none."
260
+ ),
261
+ ),
262
+ ] = None,
263
+ skills_dir: Annotated[
264
+ Path | None,
265
+ typer.Option(
266
+ "--skills-dir",
267
+ exists=True,
268
+ file_okay=False,
269
+ dir_okay=True,
270
+ readable=True,
271
+ help="Path to the skills directory. Default: ~/.kimi/skills",
272
+ ),
273
+ ] = None,
274
+ # Loop control
275
+ max_steps_per_turn: Annotated[
276
+ int | None,
277
+ typer.Option(
278
+ "--max-steps-per-turn",
279
+ min=1,
280
+ help="Maximum number of steps in one turn. Default: from config.",
281
+ ),
282
+ ] = None,
283
+ max_retries_per_step: Annotated[
284
+ int | None,
285
+ typer.Option(
286
+ "--max-retries-per-step",
287
+ min=1,
288
+ help="Maximum number of retries in one step. Default: from config.",
289
+ ),
290
+ ] = None,
291
+ max_ralph_iterations: Annotated[
292
+ int | None,
293
+ typer.Option(
294
+ "--max-ralph-iterations",
295
+ min=-1,
296
+ help=(
297
+ "Extra iterations after the first turn in Ralph mode. Use -1 for unlimited. "
298
+ "Default: from config."
299
+ ),
300
+ ),
301
+ ] = None,
302
+ ):
303
+ """Kimi, your next CLI agent."""
304
+ if ctx.invoked_subcommand is not None:
305
+ return # skip rest if a subcommand is invoked
306
+
307
+ del version # handled in the callback
308
+
309
+ from kaos.path import KaosPath
310
+
311
+ from kimi_cli.agentspec import DEFAULT_AGENT_FILE, OKABE_AGENT_FILE
312
+ from kimi_cli.app import KimiCLI, enable_logging
313
+ from kimi_cli.config import Config, load_config_from_string
314
+ from kimi_cli.exception import ConfigError
315
+ from kimi_cli.metadata import load_metadata, save_metadata
316
+ from kimi_cli.session import Session
317
+ from kimi_cli.utils.logging import logger
318
+
319
+ from .mcp import get_global_mcp_config_file
320
+
321
+ enable_logging(debug)
322
+
323
+ if session_id is not None:
324
+ session_id = session_id.strip()
325
+ if not session_id:
326
+ raise typer.BadParameter("Session ID cannot be empty", param_hint="--session")
327
+
328
+ if quiet:
329
+ if acp_mode or wire_mode:
330
+ raise typer.BadParameter(
331
+ "Quiet mode cannot be combined with ACP or Wire UI",
332
+ param_hint="--quiet",
333
+ )
334
+ if output_format not in (None, "text"):
335
+ raise typer.BadParameter(
336
+ "Quiet mode implies `--output-format text`",
337
+ param_hint="--quiet",
338
+ )
339
+ print_mode = True
340
+ output_format = "text"
341
+ final_message_only = True
342
+
343
+ conflict_option_sets = [
344
+ {
345
+ "--print": print_mode,
346
+ "--acp": acp_mode,
347
+ "--wire": wire_mode,
348
+ },
349
+ {
350
+ "--agent": agent is not None,
351
+ "--agent-file": agent_file is not None,
352
+ },
353
+ {
354
+ "--continue": continue_,
355
+ "--session": session_id is not None,
356
+ },
357
+ {
358
+ "--config": config_string is not None,
359
+ "--config-file": config_file is not None,
360
+ },
361
+ ]
362
+ for option_set in conflict_option_sets:
363
+ active_options = [flag for flag, active in option_set.items() if active]
364
+ if len(active_options) > 1:
365
+ raise typer.BadParameter(
366
+ f"Cannot combine {', '.join(active_options)}.",
367
+ param_hint=active_options[0],
368
+ )
369
+
370
+ if agent is not None:
371
+ match agent:
372
+ case "default":
373
+ agent_file = DEFAULT_AGENT_FILE
374
+ case "okabe":
375
+ agent_file = OKABE_AGENT_FILE
376
+
377
+ ui: UIMode = "shell"
378
+ if print_mode:
379
+ ui = "print"
380
+ elif acp_mode:
381
+ ui = "acp"
382
+ elif wire_mode:
383
+ ui = "wire"
384
+
385
+ if prompt is not None:
386
+ prompt = prompt.strip()
387
+ if not prompt:
388
+ raise typer.BadParameter("Prompt cannot be empty", param_hint="--prompt")
389
+
390
+ flow = None
391
+ if prompt_flow is not None:
392
+ from kimi_cli.flow import PromptFlowError
393
+ from kimi_cli.flow.d2 import parse_d2_flowchart
394
+ from kimi_cli.flow.mermaid import parse_mermaid_flowchart
395
+
396
+ if max_ralph_iterations is not None and max_ralph_iterations != 0:
397
+ raise typer.BadParameter(
398
+ "Prompt flow cannot be used with Ralph mode",
399
+ param_hint="--prompt-flow",
400
+ )
401
+ try:
402
+ flow_text = prompt_flow.read_text(encoding="utf-8")
403
+ except OSError as e:
404
+ raise typer.BadParameter(
405
+ f"Failed to read prompt flow file: {e}", param_hint="--prompt-flow"
406
+ ) from e
407
+ suffix = prompt_flow.suffix.lower()
408
+ if suffix in {".mmd", ".mermaid"}:
409
+ parser = parse_mermaid_flowchart
410
+ elif suffix == ".d2":
411
+ parser = parse_d2_flowchart
412
+ else:
413
+ raise typer.BadParameter(
414
+ "Unsupported prompt flow extension; use .mmd or .d2",
415
+ param_hint="--prompt-flow",
416
+ )
417
+ try:
418
+ flow = parser(flow_text)
419
+ except PromptFlowError as e:
420
+ raise typer.BadParameter(str(e), param_hint="--prompt-flow") from e
421
+
422
+ if input_format is not None and ui != "print":
423
+ raise typer.BadParameter(
424
+ "Input format is only supported for print UI",
425
+ param_hint="--input-format",
426
+ )
427
+ if output_format is not None and ui != "print":
428
+ raise typer.BadParameter(
429
+ "Output format is only supported for print UI",
430
+ param_hint="--output-format",
431
+ )
432
+ if final_message_only and ui != "print":
433
+ raise typer.BadParameter(
434
+ "Final-message-only output is only supported for print UI",
435
+ param_hint="--final-message-only",
436
+ )
437
+
438
+ config: Config | Path | None = None
439
+ if config_string is not None:
440
+ config_string = config_string.strip()
441
+ if not config_string:
442
+ raise typer.BadParameter("Config cannot be empty", param_hint="--config")
443
+ try:
444
+ config = load_config_from_string(config_string)
445
+ except ConfigError as e:
446
+ raise typer.BadParameter(str(e), param_hint="--config") from e
447
+ elif config_file is not None:
448
+ config = config_file
449
+
450
+ file_configs = list(mcp_config_file or [])
451
+ raw_mcp_config = list(mcp_config or [])
452
+
453
+ # Use default MCP config file if no MCP config is provided
454
+ if not file_configs:
455
+ default_mcp_file = get_global_mcp_config_file()
456
+ if default_mcp_file.exists():
457
+ file_configs.append(default_mcp_file)
458
+
459
+ try:
460
+ mcp_configs = [json.loads(conf.read_text(encoding="utf-8")) for conf in file_configs]
461
+ except json.JSONDecodeError as e:
462
+ raise typer.BadParameter(f"Invalid JSON: {e}", param_hint="--mcp-config-file") from e
463
+
464
+ try:
465
+ mcp_configs += [json.loads(conf) for conf in raw_mcp_config]
466
+ except json.JSONDecodeError as e:
467
+ raise typer.BadParameter(f"Invalid JSON: {e}", param_hint="--mcp-config") from e
468
+
469
+ work_dir = KaosPath.unsafe_from_local_path(local_work_dir) if local_work_dir else KaosPath.cwd()
470
+
471
+ async def _run(session_id: str | None) -> bool:
472
+ if session_id is not None:
473
+ session = await Session.find(work_dir, session_id)
474
+ if session is None:
475
+ logger.info(
476
+ "Session {session_id} not found, creating new session", session_id=session_id
477
+ )
478
+ session = await Session.create(work_dir, session_id)
479
+ logger.info("Switching to session: {session_id}", session_id=session.id)
480
+ elif continue_:
481
+ session = await Session.continue_(work_dir)
482
+ if session is None:
483
+ raise typer.BadParameter(
484
+ "No previous session found for the working directory",
485
+ param_hint="--continue",
486
+ )
487
+ logger.info("Continuing previous session: {session_id}", session_id=session.id)
488
+ else:
489
+ session = await Session.create(work_dir)
490
+ logger.info("Created new session: {session_id}", session_id=session.id)
491
+
492
+ instance = await KimiCLI.create(
493
+ session,
494
+ config=config,
495
+ model_name=model_name,
496
+ thinking=thinking,
497
+ yolo=yolo or (ui == "print"), # print mode implies yolo
498
+ agent_file=agent_file,
499
+ mcp_configs=mcp_configs,
500
+ skills_dir=skills_dir,
501
+ max_steps_per_turn=max_steps_per_turn,
502
+ max_retries_per_step=max_retries_per_step,
503
+ max_ralph_iterations=max_ralph_iterations,
504
+ flow=flow,
505
+ )
506
+ match ui:
507
+ case "shell":
508
+ succeeded = await instance.run_shell(prompt)
509
+ case "print":
510
+ succeeded = await instance.run_print(
511
+ input_format or "text",
512
+ output_format or "text",
513
+ prompt,
514
+ final_only=final_message_only,
515
+ )
516
+ case "acp":
517
+ if prompt is not None:
518
+ logger.warning("ACP server ignores prompt argument")
519
+ await instance.run_acp()
520
+ succeeded = True
521
+ case "wire":
522
+ if prompt is not None:
523
+ logger.warning("Wire server ignores prompt argument")
524
+ await instance.run_wire_stdio()
525
+ succeeded = True
526
+
527
+ if succeeded:
528
+ metadata = load_metadata()
529
+
530
+ # Update work_dir metadata with last session
531
+ work_dir_meta = metadata.get_work_dir_meta(session.work_dir)
532
+
533
+ if work_dir_meta is None:
534
+ logger.warning(
535
+ "Work dir metadata missing when marking last session, recreating: {work_dir}",
536
+ work_dir=session.work_dir,
537
+ )
538
+ work_dir_meta = metadata.new_work_dir_meta(session.work_dir)
539
+
540
+ if session.is_empty():
541
+ logger.info(
542
+ "Session {session_id} has empty context, removing it",
543
+ session_id=session.id,
544
+ )
545
+ await session.delete()
546
+ if work_dir_meta.last_session_id == session.id:
547
+ work_dir_meta.last_session_id = None
548
+ else:
549
+ work_dir_meta.last_session_id = session.id
550
+
551
+ save_metadata(metadata)
552
+
553
+ return succeeded
554
+
555
+ while True:
556
+ try:
557
+ succeeded = asyncio.run(_run(session_id))
558
+ session_id = None
559
+ if not succeeded:
560
+ raise typer.Exit(code=1)
561
+ break
562
+ except Reload as e:
563
+ session_id = e.session_id
564
+ continue
565
+
566
+
567
+ cli.add_typer(info_cli, name="info")
568
+
569
+
570
+ @cli.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
571
+ def term(
572
+ ctx: typer.Context,
573
+ ) -> None:
574
+ """Run Toad TUI backed by Kimi CLI ACP server."""
575
+ from kimi_cli.toad import run_term
576
+
577
+ run_term(ctx)
578
+
579
+
580
+ @cli.command()
581
+ def acp():
582
+ """Run Kimi CLI ACP server."""
583
+ from kimi_cli.acp import acp_main
584
+
585
+ acp_main()
586
+
587
+
588
+ cli.add_typer(mcp_cli, name="mcp")
589
+
590
+
591
+ if __name__ == "__main__":
592
+ if "kimi_cli.cli" not in sys.modules:
593
+ sys.modules["kimi_cli.cli"] = sys.modules[__name__]
594
+
595
+ sys.exit(cli())
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+ from kimi_cli.cli import cli
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(cli())
kimi_cli/cli/info.py ADDED
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import platform
5
+ from typing import Annotated, TypedDict
6
+
7
+ import typer
8
+
9
+ from kimi_cli.constant import VERSION
10
+
11
+
12
+ class InfoData(TypedDict):
13
+ kimi_cli_version: str
14
+ agent_spec_versions: list[str]
15
+ wire_protocol_version: str
16
+ python_version: str
17
+
18
+
19
+ def _collect_info() -> InfoData:
20
+ from kimi_cli.agentspec import SUPPORTED_AGENT_SPEC_VERSIONS
21
+ from kimi_cli.ui.wire.protocol import WIRE_PROTOCOL_VERSION
22
+
23
+ return {
24
+ "kimi_cli_version": VERSION,
25
+ "agent_spec_versions": [str(version) for version in SUPPORTED_AGENT_SPEC_VERSIONS],
26
+ "wire_protocol_version": WIRE_PROTOCOL_VERSION,
27
+ "python_version": platform.python_version(),
28
+ }
29
+
30
+
31
+ def _emit_info(json_output: bool) -> None:
32
+ info = _collect_info()
33
+ if json_output:
34
+ typer.echo(json.dumps(info, ensure_ascii=False))
35
+ return
36
+
37
+ agent_versions_text = ", ".join(str(version) for version in info["agent_spec_versions"])
38
+
39
+ lines = [
40
+ f"kimi-cli version: {info['kimi_cli_version']}",
41
+ f"agent spec versions: {agent_versions_text}",
42
+ f"wire protocol: {info['wire_protocol_version']}",
43
+ f"python version: {info['python_version']}",
44
+ ]
45
+ for line in lines:
46
+ typer.echo(line)
47
+
48
+
49
+ cli = typer.Typer(help="Show version and protocol information.")
50
+
51
+
52
+ @cli.callback(invoke_without_command=True)
53
+ def info(
54
+ json_output: Annotated[
55
+ bool,
56
+ typer.Option(
57
+ "--json",
58
+ help="Output information as JSON.",
59
+ ),
60
+ ] = False,
61
+ ):
62
+ """Show version and protocol information."""
63
+ _emit_info(json_output)