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.
- kimi_cli/CHANGELOG.md +349 -40
- kimi_cli/__init__.py +6 -0
- kimi_cli/acp/AGENTS.md +91 -0
- kimi_cli/acp/__init__.py +13 -0
- kimi_cli/acp/convert.py +111 -0
- kimi_cli/acp/kaos.py +270 -0
- kimi_cli/acp/mcp.py +46 -0
- kimi_cli/acp/server.py +335 -0
- kimi_cli/acp/session.py +445 -0
- kimi_cli/acp/tools.py +158 -0
- kimi_cli/acp/types.py +13 -0
- kimi_cli/agents/default/agent.yaml +4 -4
- kimi_cli/agents/default/sub.yaml +2 -1
- kimi_cli/agents/default/system.md +79 -21
- kimi_cli/agents/okabe/agent.yaml +17 -0
- kimi_cli/agentspec.py +53 -25
- kimi_cli/app.py +180 -52
- kimi_cli/cli/__init__.py +595 -0
- kimi_cli/cli/__main__.py +8 -0
- kimi_cli/cli/info.py +63 -0
- kimi_cli/cli/mcp.py +349 -0
- kimi_cli/config.py +153 -17
- kimi_cli/constant.py +3 -0
- kimi_cli/exception.py +23 -2
- kimi_cli/flow/__init__.py +117 -0
- kimi_cli/flow/d2.py +376 -0
- kimi_cli/flow/mermaid.py +218 -0
- kimi_cli/llm.py +129 -23
- kimi_cli/metadata.py +32 -7
- kimi_cli/platforms.py +262 -0
- kimi_cli/prompts/__init__.py +2 -0
- kimi_cli/prompts/compact.md +4 -5
- kimi_cli/session.py +223 -31
- kimi_cli/share.py +2 -0
- kimi_cli/skill.py +145 -0
- kimi_cli/skills/kimi-cli-help/SKILL.md +55 -0
- kimi_cli/skills/skill-creator/SKILL.md +351 -0
- kimi_cli/soul/__init__.py +51 -20
- kimi_cli/soul/agent.py +213 -85
- kimi_cli/soul/approval.py +86 -17
- kimi_cli/soul/compaction.py +64 -53
- kimi_cli/soul/context.py +38 -5
- kimi_cli/soul/denwarenji.py +2 -0
- kimi_cli/soul/kimisoul.py +442 -60
- kimi_cli/soul/message.py +54 -54
- kimi_cli/soul/slash.py +72 -0
- kimi_cli/soul/toolset.py +387 -6
- kimi_cli/toad.py +74 -0
- kimi_cli/tools/AGENTS.md +5 -0
- kimi_cli/tools/__init__.py +42 -34
- kimi_cli/tools/display.py +25 -0
- kimi_cli/tools/dmail/__init__.py +10 -10
- kimi_cli/tools/dmail/dmail.md +11 -9
- kimi_cli/tools/file/__init__.py +1 -3
- kimi_cli/tools/file/glob.py +20 -23
- kimi_cli/tools/file/grep.md +1 -1
- kimi_cli/tools/file/{grep.py → grep_local.py} +51 -23
- kimi_cli/tools/file/read.md +24 -6
- kimi_cli/tools/file/read.py +134 -50
- kimi_cli/tools/file/replace.md +1 -1
- kimi_cli/tools/file/replace.py +36 -29
- kimi_cli/tools/file/utils.py +282 -0
- kimi_cli/tools/file/write.py +43 -22
- kimi_cli/tools/multiagent/__init__.py +7 -0
- kimi_cli/tools/multiagent/create.md +11 -0
- kimi_cli/tools/multiagent/create.py +50 -0
- kimi_cli/tools/{task/__init__.py → multiagent/task.py} +48 -53
- kimi_cli/tools/shell/__init__.py +120 -0
- kimi_cli/tools/{bash → shell}/bash.md +1 -2
- kimi_cli/tools/shell/powershell.md +25 -0
- kimi_cli/tools/test.py +4 -4
- kimi_cli/tools/think/__init__.py +2 -2
- kimi_cli/tools/todo/__init__.py +14 -8
- kimi_cli/tools/utils.py +64 -24
- kimi_cli/tools/web/fetch.py +68 -13
- kimi_cli/tools/web/search.py +10 -12
- kimi_cli/ui/acp/__init__.py +65 -412
- kimi_cli/ui/print/__init__.py +37 -49
- kimi_cli/ui/print/visualize.py +179 -0
- kimi_cli/ui/shell/__init__.py +141 -84
- kimi_cli/ui/shell/console.py +2 -0
- kimi_cli/ui/shell/debug.py +28 -23
- kimi_cli/ui/shell/keyboard.py +5 -1
- kimi_cli/ui/shell/prompt.py +220 -194
- kimi_cli/ui/shell/replay.py +111 -46
- kimi_cli/ui/shell/setup.py +89 -82
- kimi_cli/ui/shell/slash.py +422 -0
- kimi_cli/ui/shell/update.py +4 -2
- kimi_cli/ui/shell/usage.py +271 -0
- kimi_cli/ui/shell/visualize.py +574 -72
- kimi_cli/ui/wire/__init__.py +267 -0
- kimi_cli/ui/wire/jsonrpc.py +142 -0
- kimi_cli/ui/wire/protocol.py +1 -0
- kimi_cli/utils/__init__.py +0 -0
- kimi_cli/utils/aiohttp.py +2 -0
- kimi_cli/utils/aioqueue.py +72 -0
- kimi_cli/utils/broadcast.py +37 -0
- kimi_cli/utils/changelog.py +12 -7
- kimi_cli/utils/clipboard.py +12 -0
- kimi_cli/utils/datetime.py +37 -0
- kimi_cli/utils/environment.py +58 -0
- kimi_cli/utils/envvar.py +12 -0
- kimi_cli/utils/frontmatter.py +44 -0
- kimi_cli/utils/logging.py +7 -6
- kimi_cli/utils/message.py +9 -14
- kimi_cli/utils/path.py +99 -9
- kimi_cli/utils/pyinstaller.py +6 -0
- kimi_cli/utils/rich/__init__.py +33 -0
- kimi_cli/utils/rich/columns.py +99 -0
- kimi_cli/utils/rich/markdown.py +961 -0
- kimi_cli/utils/rich/markdown_sample.md +108 -0
- kimi_cli/utils/rich/markdown_sample_short.md +2 -0
- kimi_cli/utils/signals.py +2 -0
- kimi_cli/utils/slashcmd.py +124 -0
- kimi_cli/utils/string.py +2 -0
- kimi_cli/utils/term.py +168 -0
- kimi_cli/utils/typing.py +20 -0
- kimi_cli/wire/__init__.py +98 -29
- kimi_cli/wire/serde.py +45 -0
- kimi_cli/wire/types.py +299 -0
- kimi_cli-0.78.dist-info/METADATA +200 -0
- kimi_cli-0.78.dist-info/RECORD +135 -0
- kimi_cli-0.78.dist-info/entry_points.txt +4 -0
- kimi_cli/cli.py +0 -250
- kimi_cli/soul/runtime.py +0 -96
- kimi_cli/tools/bash/__init__.py +0 -99
- kimi_cli/tools/file/patch.md +0 -8
- kimi_cli/tools/file/patch.py +0 -143
- kimi_cli/tools/mcp.py +0 -85
- kimi_cli/ui/shell/liveview.py +0 -386
- kimi_cli/ui/shell/metacmd.py +0 -262
- kimi_cli/wire/message.py +0 -91
- kimi_cli-0.44.dist-info/METADATA +0 -188
- kimi_cli-0.44.dist-info/RECORD +0 -89
- kimi_cli-0.44.dist-info/entry_points.txt +0 -3
- /kimi_cli/tools/{task → multiagent}/task.md +0 -0
- {kimi_cli-0.44.dist-info → kimi_cli-0.78.dist-info}/WHEEL +0 -0
kimi_cli/cli/__init__.py
ADDED
|
@@ -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())
|
kimi_cli/cli/__main__.py
ADDED
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)
|