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
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
from prompt_toolkit.shortcuts.choice_input import ChoiceInput
|
|
7
|
+
|
|
8
|
+
from kimi_cli.cli import Reload
|
|
9
|
+
from kimi_cli.config import load_config, save_config
|
|
10
|
+
from kimi_cli.exception import ConfigError
|
|
11
|
+
from kimi_cli.platforms import get_platform_name_for_provider, refresh_managed_models
|
|
12
|
+
from kimi_cli.session import Session
|
|
13
|
+
from kimi_cli.soul.kimisoul import KimiSoul
|
|
14
|
+
from kimi_cli.ui.shell.console import console
|
|
15
|
+
from kimi_cli.utils.changelog import CHANGELOG
|
|
16
|
+
from kimi_cli.utils.datetime import format_relative_time
|
|
17
|
+
from kimi_cli.utils.slashcmd import SlashCommand, SlashCommandRegistry
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from kimi_cli.ui.shell import Shell
|
|
21
|
+
|
|
22
|
+
type ShellSlashCmdFunc = Callable[[Shell, str], None | Awaitable[None]]
|
|
23
|
+
"""
|
|
24
|
+
A function that runs as a Shell-level slash command.
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
Reload: When the configuration should be reloaded.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
registry = SlashCommandRegistry[ShellSlashCmdFunc]()
|
|
32
|
+
shell_mode_registry = SlashCommandRegistry[ShellSlashCmdFunc]()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _ensure_kimi_soul(app: Shell) -> KimiSoul | None:
|
|
36
|
+
if not isinstance(app.soul, KimiSoul):
|
|
37
|
+
console.print("[red]KimiSoul required[/red]")
|
|
38
|
+
return None
|
|
39
|
+
return app.soul
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@registry.command(aliases=["quit"])
|
|
43
|
+
@shell_mode_registry.command(aliases=["quit"])
|
|
44
|
+
def exit(app: Shell, args: str):
|
|
45
|
+
"""Exit the application"""
|
|
46
|
+
# should be handled by `Shell`
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
SKILL_COMMAND_PREFIX = "skill:"
|
|
51
|
+
|
|
52
|
+
_KEYBOARD_SHORTCUTS = [
|
|
53
|
+
("Ctrl-X", "Toggle agent/shell mode"),
|
|
54
|
+
("Ctrl-J / Alt-Enter", "Insert newline"),
|
|
55
|
+
("Ctrl-V", "Paste (supports images)"),
|
|
56
|
+
("Ctrl-D", "Exit"),
|
|
57
|
+
("Ctrl-C", "Interrupt"),
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@registry.command(aliases=["h", "?"])
|
|
62
|
+
@shell_mode_registry.command(aliases=["h", "?"])
|
|
63
|
+
def help(app: Shell, args: str):
|
|
64
|
+
"""Show help information"""
|
|
65
|
+
from rich.console import Group, RenderableType
|
|
66
|
+
from rich.text import Text
|
|
67
|
+
|
|
68
|
+
from kimi_cli.utils.rich.columns import BulletColumns
|
|
69
|
+
|
|
70
|
+
def section(title: str, items: list[tuple[str, str]], color: str) -> BulletColumns:
|
|
71
|
+
lines: list[RenderableType] = [Text.from_markup(f"[bold]{title}:[/bold]")]
|
|
72
|
+
for name, desc in items:
|
|
73
|
+
lines.append(
|
|
74
|
+
BulletColumns(
|
|
75
|
+
Text.from_markup(f"[{color}]{name}[/{color}]: [grey50]{desc}[/grey50]"),
|
|
76
|
+
bullet_style=color,
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
return BulletColumns(Group(*lines))
|
|
80
|
+
|
|
81
|
+
renderables: list[RenderableType] = []
|
|
82
|
+
renderables.append(
|
|
83
|
+
BulletColumns(
|
|
84
|
+
Group(
|
|
85
|
+
Text.from_markup("[grey50]Help! I need somebody. Help! Not just anybody.[/grey50]"),
|
|
86
|
+
Text.from_markup("[grey50]Help! You know I need someone. Help![/grey50]"),
|
|
87
|
+
Text.from_markup("[grey50]\u2015 The Beatles, [italic]Help![/italic][/grey50]"),
|
|
88
|
+
),
|
|
89
|
+
bullet_style="grey50",
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
renderables.append(
|
|
93
|
+
BulletColumns(
|
|
94
|
+
Text(
|
|
95
|
+
"Sure, Kimi CLI is ready to help! "
|
|
96
|
+
"Just send me messages and I will help you get things done!"
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
commands: list[SlashCommand[Any]] = []
|
|
102
|
+
skills: list[SlashCommand[Any]] = []
|
|
103
|
+
for cmd in app.available_slash_commands.values():
|
|
104
|
+
if cmd.name.startswith(SKILL_COMMAND_PREFIX):
|
|
105
|
+
skills.append(cmd)
|
|
106
|
+
else:
|
|
107
|
+
commands.append(cmd)
|
|
108
|
+
|
|
109
|
+
renderables.append(
|
|
110
|
+
section(
|
|
111
|
+
"Slash commands",
|
|
112
|
+
[(c.slash_name(), c.description) for c in sorted(commands, key=lambda c: c.name)],
|
|
113
|
+
"blue",
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
if skills:
|
|
117
|
+
renderables.append(
|
|
118
|
+
section(
|
|
119
|
+
"Skills",
|
|
120
|
+
[(c.slash_name(), c.description) for c in sorted(skills, key=lambda c: c.name)],
|
|
121
|
+
"cyan",
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
renderables.append(section("Keyboard shortcuts", _KEYBOARD_SHORTCUTS, "yellow"))
|
|
125
|
+
|
|
126
|
+
with console.pager(styles=True):
|
|
127
|
+
console.print(Group(*renderables))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@registry.command
|
|
131
|
+
@shell_mode_registry.command
|
|
132
|
+
def version(app: Shell, args: str):
|
|
133
|
+
"""Show version information"""
|
|
134
|
+
from kimi_cli.constant import VERSION
|
|
135
|
+
|
|
136
|
+
console.print(f"kimi, version {VERSION}")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@registry.command
|
|
140
|
+
async def model(app: Shell, args: str):
|
|
141
|
+
"""Switch LLM model or thinking mode"""
|
|
142
|
+
from kimi_cli.llm import derive_model_capabilities
|
|
143
|
+
|
|
144
|
+
soul = _ensure_kimi_soul(app)
|
|
145
|
+
if soul is None:
|
|
146
|
+
return
|
|
147
|
+
config = soul.runtime.config
|
|
148
|
+
|
|
149
|
+
await refresh_managed_models(config)
|
|
150
|
+
|
|
151
|
+
if not config.models:
|
|
152
|
+
console.print('[yellow]No models configured, send "/setup" to configure.[/yellow]')
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
if not config.is_from_default_location:
|
|
156
|
+
console.print(
|
|
157
|
+
"[yellow]Model switching requires the default config file; "
|
|
158
|
+
"restart without --config/--config-file.[/yellow]"
|
|
159
|
+
)
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
# Find current model/thinking from runtime (may be overridden by --model/--thinking)
|
|
163
|
+
curr_model_cfg = soul.runtime.llm.model_config if soul.runtime.llm else None
|
|
164
|
+
curr_model_name: str | None = None
|
|
165
|
+
if curr_model_cfg is not None:
|
|
166
|
+
for name, model_cfg in config.models.items():
|
|
167
|
+
if model_cfg == curr_model_cfg:
|
|
168
|
+
curr_model_name = name
|
|
169
|
+
break
|
|
170
|
+
curr_thinking = soul.thinking
|
|
171
|
+
|
|
172
|
+
# Step 1: Select model
|
|
173
|
+
model_choices: list[tuple[str, str]] = []
|
|
174
|
+
for name in sorted(config.models):
|
|
175
|
+
model_cfg = config.models[name]
|
|
176
|
+
provider_label = get_platform_name_for_provider(model_cfg.provider) or model_cfg.provider
|
|
177
|
+
marker = " (current)" if name == curr_model_name else ""
|
|
178
|
+
label = f"{model_cfg.model} ({provider_label}){marker}"
|
|
179
|
+
model_choices.append((name, label))
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
selected_model_name = await ChoiceInput(
|
|
183
|
+
message="Select a model (↑↓ navigate, Enter select, Ctrl+C cancel):",
|
|
184
|
+
options=model_choices,
|
|
185
|
+
default=curr_model_name or model_choices[0][0],
|
|
186
|
+
).prompt_async()
|
|
187
|
+
except (EOFError, KeyboardInterrupt):
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
if not selected_model_name:
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
selected_model_cfg = config.models[selected_model_name]
|
|
194
|
+
selected_provider = config.providers.get(selected_model_cfg.provider)
|
|
195
|
+
if selected_provider is None:
|
|
196
|
+
console.print(f"[red]Provider not found: {selected_model_cfg.provider}[/red]")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
# Step 2: Determine thinking mode
|
|
200
|
+
capabilities = derive_model_capabilities(selected_model_cfg)
|
|
201
|
+
new_thinking: bool
|
|
202
|
+
|
|
203
|
+
if "always_thinking" in capabilities:
|
|
204
|
+
new_thinking = True
|
|
205
|
+
elif "thinking" in capabilities:
|
|
206
|
+
thinking_choices: list[tuple[str, str]] = [
|
|
207
|
+
("off", "off" + (" (current)" if not curr_thinking else "")),
|
|
208
|
+
("on", "on" + (" (current)" if curr_thinking else "")),
|
|
209
|
+
]
|
|
210
|
+
try:
|
|
211
|
+
thinking_selection = await ChoiceInput(
|
|
212
|
+
message="Enable thinking mode? (↑↓ navigate, Enter select, Ctrl+C cancel):",
|
|
213
|
+
options=thinking_choices,
|
|
214
|
+
default="on" if curr_thinking else "off",
|
|
215
|
+
).prompt_async()
|
|
216
|
+
except (EOFError, KeyboardInterrupt):
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
if not thinking_selection:
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
new_thinking = thinking_selection == "on"
|
|
223
|
+
else:
|
|
224
|
+
new_thinking = False
|
|
225
|
+
|
|
226
|
+
# Check if anything changed
|
|
227
|
+
model_changed = curr_model_name != selected_model_name
|
|
228
|
+
thinking_changed = curr_thinking != new_thinking
|
|
229
|
+
|
|
230
|
+
if not model_changed and not thinking_changed:
|
|
231
|
+
console.print(
|
|
232
|
+
f"[yellow]Already using {selected_model_name} "
|
|
233
|
+
f"with thinking {'on' if new_thinking else 'off'}.[/yellow]"
|
|
234
|
+
)
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
# Save and reload
|
|
238
|
+
prev_model = config.default_model
|
|
239
|
+
prev_thinking = config.default_thinking
|
|
240
|
+
config.default_model = selected_model_name
|
|
241
|
+
config.default_thinking = new_thinking
|
|
242
|
+
try:
|
|
243
|
+
config_for_save = load_config()
|
|
244
|
+
config_for_save.default_model = selected_model_name
|
|
245
|
+
config_for_save.default_thinking = new_thinking
|
|
246
|
+
save_config(config_for_save)
|
|
247
|
+
except (ConfigError, OSError) as exc:
|
|
248
|
+
config.default_model = prev_model
|
|
249
|
+
config.default_thinking = prev_thinking
|
|
250
|
+
console.print(f"[red]Failed to save config: {exc}[/red]")
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
console.print(
|
|
254
|
+
f"[green]Switched to {selected_model_name} "
|
|
255
|
+
f"with thinking {'on' if new_thinking else 'off'}. "
|
|
256
|
+
"Reloading...[/green]"
|
|
257
|
+
)
|
|
258
|
+
raise Reload(session_id=soul.runtime.session.id)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@registry.command(aliases=["release-notes"])
|
|
262
|
+
@shell_mode_registry.command(aliases=["release-notes"])
|
|
263
|
+
def changelog(app: Shell, args: str):
|
|
264
|
+
"""Show release notes"""
|
|
265
|
+
from rich.console import Group, RenderableType
|
|
266
|
+
from rich.text import Text
|
|
267
|
+
|
|
268
|
+
from kimi_cli.utils.rich.columns import BulletColumns
|
|
269
|
+
|
|
270
|
+
renderables: list[RenderableType] = []
|
|
271
|
+
for ver, entry in CHANGELOG.items():
|
|
272
|
+
title = f"[bold]{ver}[/bold]"
|
|
273
|
+
if entry.description:
|
|
274
|
+
title += f": {entry.description}"
|
|
275
|
+
|
|
276
|
+
lines: list[RenderableType] = [Text.from_markup(title)]
|
|
277
|
+
for item in entry.entries:
|
|
278
|
+
if item.lower().startswith("lib:"):
|
|
279
|
+
continue
|
|
280
|
+
lines.append(
|
|
281
|
+
BulletColumns(
|
|
282
|
+
Text.from_markup(f"[grey50]{item}[/grey50]"),
|
|
283
|
+
bullet_style="grey50",
|
|
284
|
+
),
|
|
285
|
+
)
|
|
286
|
+
renderables.append(BulletColumns(Group(*lines)))
|
|
287
|
+
|
|
288
|
+
with console.pager(styles=True):
|
|
289
|
+
console.print(Group(*renderables))
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@registry.command
|
|
293
|
+
@shell_mode_registry.command
|
|
294
|
+
def feedback(app: Shell, args: str):
|
|
295
|
+
"""Submit feedback to make Kimi CLI better"""
|
|
296
|
+
import webbrowser
|
|
297
|
+
|
|
298
|
+
ISSUE_URL = "https://github.com/MoonshotAI/kimi-cli/issues"
|
|
299
|
+
if webbrowser.open(ISSUE_URL):
|
|
300
|
+
return
|
|
301
|
+
console.print(f"Please submit feedback at [underline]{ISSUE_URL}[/underline].")
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@registry.command(aliases=["reset"])
|
|
305
|
+
async def clear(app: Shell, args: str):
|
|
306
|
+
"""Clear the context"""
|
|
307
|
+
soul = _ensure_kimi_soul(app)
|
|
308
|
+
if soul is None:
|
|
309
|
+
return
|
|
310
|
+
await soul.context.clear()
|
|
311
|
+
raise Reload()
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@registry.command(name="sessions", aliases=["resume"])
|
|
315
|
+
async def list_sessions(app: Shell, args: str):
|
|
316
|
+
"""List sessions and resume optionally"""
|
|
317
|
+
soul = _ensure_kimi_soul(app)
|
|
318
|
+
if soul is None:
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
work_dir = soul.runtime.session.work_dir
|
|
322
|
+
current_session = soul.runtime.session
|
|
323
|
+
current_session_id = current_session.id
|
|
324
|
+
sessions = [
|
|
325
|
+
session for session in await Session.list(work_dir) if session.id != current_session_id
|
|
326
|
+
]
|
|
327
|
+
|
|
328
|
+
await current_session.refresh()
|
|
329
|
+
sessions.insert(0, current_session)
|
|
330
|
+
|
|
331
|
+
choices: list[tuple[str, str]] = []
|
|
332
|
+
for session in sessions:
|
|
333
|
+
time_str = format_relative_time(session.updated_at)
|
|
334
|
+
marker = " (current)" if session.id == current_session_id else ""
|
|
335
|
+
label = f"{session.title}, {time_str}{marker}"
|
|
336
|
+
choices.append((session.id, label))
|
|
337
|
+
|
|
338
|
+
try:
|
|
339
|
+
selection = await ChoiceInput(
|
|
340
|
+
message="Select a session to switch to (↑↓ navigate, Enter select, Ctrl+C cancel):",
|
|
341
|
+
options=choices,
|
|
342
|
+
default=choices[0][0],
|
|
343
|
+
).prompt_async()
|
|
344
|
+
except (EOFError, KeyboardInterrupt):
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
if not selection:
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
if selection == current_session_id:
|
|
351
|
+
console.print("[yellow]You are already in this session.[/yellow]")
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
console.print(f"[green]Switching to session {selection}...[/green]")
|
|
355
|
+
raise Reload(session_id=selection)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@registry.command
|
|
359
|
+
async def mcp(app: Shell, args: str):
|
|
360
|
+
"""Show MCP servers and tools"""
|
|
361
|
+
from rich.console import Group, RenderableType
|
|
362
|
+
from rich.text import Text
|
|
363
|
+
|
|
364
|
+
from kimi_cli.soul.toolset import KimiToolset
|
|
365
|
+
from kimi_cli.utils.rich.columns import BulletColumns
|
|
366
|
+
|
|
367
|
+
soul = _ensure_kimi_soul(app)
|
|
368
|
+
if soul is None:
|
|
369
|
+
return
|
|
370
|
+
toolset = soul.agent.toolset
|
|
371
|
+
if not isinstance(toolset, KimiToolset):
|
|
372
|
+
console.print("[red]KimiToolset required[/red]")
|
|
373
|
+
return
|
|
374
|
+
|
|
375
|
+
servers = toolset.mcp_servers
|
|
376
|
+
|
|
377
|
+
if not servers:
|
|
378
|
+
console.print("[yellow]No MCP servers configured.[/yellow]")
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
n_conn = sum(1 for s in servers.values() if s.status == "connected")
|
|
382
|
+
n_tools = sum(len(s.tools) for s in servers.values())
|
|
383
|
+
console.print(
|
|
384
|
+
BulletColumns(
|
|
385
|
+
Text.from_markup(
|
|
386
|
+
f"[bold]MCP Servers:[/bold] {n_conn}/{len(servers)} connected, {n_tools} tools"
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
status_colors = {
|
|
392
|
+
"connected": "green",
|
|
393
|
+
"connecting": "cyan",
|
|
394
|
+
"pending": "yellow",
|
|
395
|
+
"failed": "red",
|
|
396
|
+
"unauthorized": "red",
|
|
397
|
+
}
|
|
398
|
+
for name, info in servers.items():
|
|
399
|
+
color = status_colors.get(info.status, "red")
|
|
400
|
+
server_text = f"[{color}]{name}[/{color}]"
|
|
401
|
+
if info.status == "unauthorized":
|
|
402
|
+
server_text += " [grey50](unauthorized - run: kimi mcp auth {name})[/grey50]"
|
|
403
|
+
elif info.status != "connected":
|
|
404
|
+
server_text += f" [grey50]({info.status})[/grey50]"
|
|
405
|
+
|
|
406
|
+
lines: list[RenderableType] = [Text.from_markup(server_text)]
|
|
407
|
+
for tool in info.tools:
|
|
408
|
+
lines.append(
|
|
409
|
+
BulletColumns(
|
|
410
|
+
Text.from_markup(f"[grey50]{tool.name}[/grey50]"),
|
|
411
|
+
bullet_style="grey50",
|
|
412
|
+
)
|
|
413
|
+
)
|
|
414
|
+
console.print(BulletColumns(Group(*lines), bullet_style=color))
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
from . import ( # noqa: E402
|
|
418
|
+
debug, # noqa: F401 # type: ignore[reportUnusedImport]
|
|
419
|
+
setup, # noqa: F401 # type: ignore[reportUnusedImport]
|
|
420
|
+
update, # noqa: F401 # type: ignore[reportUnusedImport]
|
|
421
|
+
usage, # noqa: F401 # type: ignore[reportUnusedImport]
|
|
422
|
+
)
|
kimi_cli/ui/shell/update.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import os
|
|
3
5
|
import platform
|
|
@@ -199,12 +201,12 @@ async def _do_update(*, print: bool, check_only: bool) -> UpdateResult:
|
|
|
199
201
|
|
|
200
202
|
|
|
201
203
|
# @meta_command
|
|
202
|
-
# async def update(app: "
|
|
204
|
+
# async def update(app: "Shell", args: list[str]):
|
|
203
205
|
# """Check for updates"""
|
|
204
206
|
# await do_update(print=True)
|
|
205
207
|
|
|
206
208
|
|
|
207
209
|
# @meta_command(name="check-update")
|
|
208
|
-
# async def check_update(app: "
|
|
210
|
+
# async def check_update(app: "Shell", args: list[str]):
|
|
209
211
|
# """Check for updates"""
|
|
210
212
|
# await do_update(print=True, check_only=True)
|