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,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
+ )
@@ -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: "ShellApp", args: list[str]):
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: "ShellApp", args: list[str]):
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)