-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: add --sessions/--list-sessions CLI options & fix CJK shorten #1376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
762fb81
be6d284
39e11e1
ea8fe9f
8b65aca
5767b5b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -56,8 +56,10 @@ The working directory determines the root directory for file operations. Relativ | |||||||||||||||||||
| |--------|-------|-------------| | ||||||||||||||||||||
| | `--continue` | `-C` | Continue the previous session in the current working directory | | ||||||||||||||||||||
| | `--session ID` | `-S` | Resume session with specified ID, creates new session if not exists | | ||||||||||||||||||||
| | `--sessions` | | Interactively select a session from the current working directory | | ||||||||||||||||||||
| | `--list-sessions` | | List all sessions in the current working directory and exit | | ||||||||||||||||||||
|
|
||||||||||||||||||||
| `--continue` and `--session` are mutually exclusive. | ||||||||||||||||||||
| `--continue`, `--session`, `--sessions`, and `--list-sessions` are mutually exclusive. | ||||||||||||||||||||
|
Comment on lines
+59
to
+62
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Documentation references The English reference docs document the flag as
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||
|
|
||||||||||||||||||||
| ## Input and commands | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -56,8 +56,10 @@ kimi [OPTIONS] COMMAND [ARGS] | |||||||||||||||||||
| |------|------|------| | ||||||||||||||||||||
| | `--continue` | `-C` | 继续当前工作目录的上一个会话 | | ||||||||||||||||||||
| | `--session ID` | `-S` | 恢复指定 ID 的会话,若不存在则创建新会话 | | ||||||||||||||||||||
| | `--sessions` | | 交互式选择当前工作目录的会话 | | ||||||||||||||||||||
| | `--list-sessions` | | 列出当前工作目录的所有会话并退出 | | ||||||||||||||||||||
|
|
||||||||||||||||||||
| `--continue` 和 `--session` 互斥。 | ||||||||||||||||||||
| `--continue`、`--session`、`--sessions` 和 `--list-sessions` 互斥。 | ||||||||||||||||||||
|
Comment on lines
+59
to
+62
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Chinese docs reference Same documentation/code mismatch as the English docs: the Chinese reference docs use
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||
|
|
||||||||||||||||||||
| ## 输入与命令 | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,12 @@ def __init__(self, session_id: str | None = None): | |
| OutputFormat = Literal["text", "stream-json"] | ||
|
|
||
|
|
||
| def _strip_session_id_suffix(title: str, session_id: str) -> str: | ||
| """Remove the trailing `` (session_id)`` that `Session.refresh` appends.""" | ||
| suffix = f" ({session_id})" | ||
| return title.rsplit(suffix, 1)[0] if title.endswith(suffix) else title | ||
|
|
||
|
|
||
| def _version_callback(value: bool) -> None: | ||
| if value: | ||
| from kimi_cli.constant import get_version | ||
|
|
@@ -119,6 +125,20 @@ def kimi( | |
| help="Continue the previous session for the working directory. Default: no.", | ||
| ), | ||
| ] = False, | ||
| sessions: Annotated[ | ||
| bool, | ||
| typer.Option( | ||
| "--pick-session", | ||
| help="Interactively select a session to resume for the working directory.", | ||
| ), | ||
| ] = False, | ||
| list_sessions: Annotated[ | ||
| bool, | ||
| typer.Option( | ||
| "--list-sessions", | ||
| help="List all sessions for the working directory and exit.", | ||
| ), | ||
| ] = False, | ||
| config_string: Annotated[ | ||
| str | None, | ||
| typer.Option( | ||
|
|
@@ -385,6 +405,8 @@ def _emit_fatal_error(message: str) -> None: | |
| { | ||
| "--continue": continue_, | ||
| "--session": session_id is not None, | ||
| "--pick-session": sessions, | ||
| "--list-sessions": list_sessions, | ||
| }, | ||
| { | ||
| "--config": config_string is not None, | ||
|
|
@@ -434,6 +456,11 @@ def _emit_fatal_error(message: str) -> None: | |
| "Final-message-only output is only supported for print UI", | ||
| param_hint="--final-message-only", | ||
| ) | ||
| if sessions and ui != "shell": | ||
| raise typer.BadParameter( | ||
| "--pick-session is only supported for shell UI", | ||
| param_hint="--pick-session", | ||
| ) | ||
|
|
||
| config: Config | Path | None = None | ||
| if config_string is not None: | ||
|
|
@@ -472,6 +499,32 @@ def _emit_fatal_error(message: str) -> None: | |
|
|
||
| work_dir = KaosPath.unsafe_from_local_path(local_work_dir) if local_work_dir else KaosPath.cwd() | ||
|
|
||
| if list_sessions: | ||
| from rich.console import Console | ||
| from rich.table import Table | ||
|
|
||
| from kimi_cli.utils.datetime import format_relative_time | ||
|
|
||
| async def _list(): | ||
| return await Session.list(work_dir) | ||
|
|
||
| all_sessions = asyncio.run(_list()) | ||
| console = Console() | ||
| if not all_sessions: | ||
| console.print("[yellow]No sessions found for the working directory.[/yellow]") | ||
| raise typer.Exit(0) | ||
|
|
||
| table = Table(show_header=True, show_edge=False) | ||
| table.add_column("ID") | ||
| table.add_column("Title") | ||
| table.add_column("Updated") | ||
| for s in all_sessions: | ||
| name = _strip_session_id_suffix(s.title, s.id) | ||
| table.add_row(s.id, name, format_relative_time(s.updated_at)) | ||
|
Comment on lines
+521
to
+523
|
||
|
|
||
| console.print(table) | ||
| raise typer.Exit(0) | ||
|
|
||
| async def _run(session_id: str | None) -> tuple[Session, bool]: | ||
| """ | ||
| Create/load session and run the CLI instance. | ||
|
|
@@ -634,6 +687,43 @@ async def _reload_loop(session_id: str | None) -> bool: | |
| await _post_run(last_session, succeeded) | ||
| return False | ||
|
|
||
| if sessions: | ||
| from prompt_toolkit.shortcuts.choice_input import ChoiceInput | ||
| from rich.console import Console | ||
|
|
||
| from kimi_cli.utils.datetime import format_relative_time | ||
|
|
||
| async def _pick_session() -> str: | ||
| all_sessions = await Session.list(work_dir) | ||
| if not all_sessions: | ||
| Console().print("[yellow]No sessions found for the working directory.[/yellow]") | ||
| raise typer.Exit(0) | ||
|
|
||
| choices: list[tuple[str, str]] = [] | ||
| for s in all_sessions: | ||
| time_str = format_relative_time(s.updated_at) | ||
| short_id = s.id[:8] | ||
| name = _strip_session_id_suffix(s.title, s.id) | ||
| label = f"{name} ({short_id}), {time_str}" | ||
| choices.append((s.id, label)) | ||
|
|
||
| try: | ||
| selection = await ChoiceInput( | ||
| message="Select a session to resume" | ||
| " (↑↓ navigate, Enter select, Ctrl+C cancel):", | ||
| options=choices, | ||
| default=choices[0][0], | ||
| ).prompt_async() | ||
| except (EOFError, KeyboardInterrupt): | ||
| raise typer.Exit(0) from None | ||
|
|
||
| if not selection: | ||
| raise typer.Exit(0) | ||
|
|
||
| return selection | ||
|
|
||
| session_id = asyncio.run(_pick_session()) | ||
|
|
||
| try: | ||
| switch_to_web = asyncio.run(_reload_loop(session_id)) | ||
| except (typer.BadParameter, typer.Exit): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,25 @@ | |
| _NEWLINE_RE = re.compile(r"[\r\n]+") | ||
|
|
||
|
|
||
| def shorten(text: str, *, width: int, placeholder: str = "…") -> str: | ||
| """Shorten text to at most *width* characters. | ||
|
|
||
| Normalises whitespace, then truncates — preferring a word boundary | ||
| when one exists near the cut point, but falling back to a hard cut | ||
| so that CJK text without spaces won't collapse to just the placeholder. | ||
| """ | ||
| text = " ".join(text.split()) | ||
| if len(text) <= width: | ||
| return text | ||
| cut = width - len(placeholder) | ||
| if cut <= 0: | ||
| return text[:width] | ||
| space = text.rfind(" ", 0, cut + 1) | ||
| if space > 0: | ||
| cut = space | ||
| return text[:cut].rstrip() + placeholder | ||
|
Comment on lines
+10
to
+26
|
||
|
|
||
|
|
||
| def shorten_middle(text: str, width: int, remove_newline: bool = True) -> str: | ||
| """Shorten the text by inserting ellipsis in the middle.""" | ||
| if len(text) <= width: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 CHANGELOG references
--sessionsbut actual CLI flag is--pick-sessionThe CHANGELOG entry at line 106 says
--sessionsbut the actual Typer option is--pick-session(src/kimi_cli/cli/__init__.py:131). The same incorrect name propagates to both the English and Chinese changelog docs (docs/en/release-notes/changelog.md:99,docs/zh/release-notes/changelog.md:99).Was this helpful? React with 👍 or 👎 to provide feedback.