Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Only write entries that are worth mentioning to users.

- Core: Fix HTTP header values containing trailing whitespace/newlines on certain Linux systems (e.g. kernel 6.8.0-101) causing connection errors — strip whitespace from ASCII header values before sending
- Core: Fix OpenAI Responses provider sending implicit `reasoning.effort=null` which breaks Responses-compatible endpoints that require reasoning — reasoning parameters are now omitted unless explicitly set
- Vis: Add session download, import, export and delete — one-click ZIP download from session explorer and detail page, ZIP import into a dedicated `~/.kimi/imported_sessions/` directory with "Imported" filter toggle, `kimi export <session_id>` CLI command, and delete support for imported sessions with AlertDialog confirmation
- Core: Fix context compaction failing when conversation contains media parts (images, audio, video) — switch from blacklist filtering (exclude `ThinkPart`) to whitelist filtering (only keep `TextPart`) to prevent unsupported content types from being sent to the compaction API
- Web: Fix `@` file mention index not refreshing after switching sessions or when workspace files change — reset index on session switch, auto-refresh after 30s staleness, and support path-prefix search beyond the 500-file limit

Expand Down
1 change: 1 addition & 0 deletions docs/en/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This page documents the changes in each Kimi Code CLI release.

- Core: Fix HTTP header values containing trailing whitespace/newlines on certain Linux systems (e.g. kernel 6.8.0-101) causing connection errors — strip whitespace from ASCII header values before sending
- Core: Fix OpenAI Responses provider sending implicit `reasoning.effort=null` which breaks Responses-compatible endpoints that require reasoning — reasoning parameters are now omitted unless explicitly set
- Vis: Add session download, import, export and delete — one-click ZIP download from session explorer and detail page, ZIP import into a dedicated `~/.kimi/imported_sessions/` directory with "Imported" filter toggle, `kimi export <session_id>` CLI command, and delete support for imported sessions with AlertDialog confirmation
- Core: Fix context compaction failing when conversation contains media parts (images, audio, video) — switch from blacklist filtering (exclude `ThinkPart`) to whitelist filtering (only keep `TextPart`) to prevent unsupported content types from being sent to the compaction API
- Web: Fix `@` file mention index not refreshing after switching sessions or when workspace files change — reset index on session switch, auto-refresh after 30s staleness, and support path-prefix search beyond the 500-file limit

Expand Down
1 change: 1 addition & 0 deletions docs/zh/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Core:修复部分 Linux 系统(如内核版本 6.8.0-101)上 HTTP 请求头包含尾部空白/换行符导致连接错误的问题——发送前对 ASCII 请求头值执行空白裁剪
- Core:修复 OpenAI Responses provider 隐式发送 `reasoning.effort=null` 导致需要推理的 Responses 兼容端点报错的问题——现在仅在显式设置时才发送推理参数
- Vis:新增会话下载、导入、导出与删除功能——在会话浏览器和详情页支持一键 ZIP 下载,支持将 ZIP 文件导入到独立的 `~/.kimi/imported_sessions/` 目录并通过"Imported"筛选器切换查看,新增 `kimi export <session_id>` CLI 命令,支持删除导入的会话并提供 AlertDialog 二次确认
- Core:修复对话包含媒体内容(图片、音频、视频)时上下文压缩失败的问题——将过滤策略从黑名单(排除 `ThinkPart`)改为白名单(仅保留 `TextPart`),防止不支持的内容类型被发送到压缩 API
- Web:修复 `@` 文件提及索引在切换会话或工作区文件变更后不刷新的问题——切换会话时重置索引,30 秒过期自动刷新,输入路径前缀可查找超出 500 文件上限的文件

Expand Down
2 changes: 2 additions & 0 deletions src/kimi_cli/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from kimi_cli.constant import VERSION

from .export import cli as export_cli
from .info import cli as info_cli
from .mcp import cli as mcp_cli
from .vis import cli as vis_cli
Expand Down Expand Up @@ -802,6 +803,7 @@ def web_worker(session_id: str) -> None:
asyncio.run(run_worker(parsed_session_id))


cli.add_typer(export_cli, name="export")
cli.add_typer(mcp_cli, name="mcp")
cli.add_typer(vis_cli, name="vis")
cli.add_typer(web_cli, name="web")
Expand Down
74 changes: 74 additions & 0 deletions src/kimi_cli/cli/export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Export command for packaging session data."""

from __future__ import annotations

import io
import zipfile
from pathlib import Path
from typing import Annotated

import typer

cli = typer.Typer(help="Export session data.")


def _find_session_by_id(session_id: str) -> Path | None:
"""Find a session directory by session ID across all work directories."""
from kimi_cli.share import get_share_dir

sessions_root = get_share_dir() / "sessions"
if not sessions_root.exists():
return None

for work_dir_hash_dir in sessions_root.iterdir():
if not work_dir_hash_dir.is_dir():
continue
candidate = work_dir_hash_dir / session_id
if candidate.is_dir():
return candidate

return None
Comment on lines +15 to +30
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 CLI export command cannot find imported sessions

The _find_session_by_id function in export.py only searches get_share_dir() / "sessions" (line 19), but imported sessions are stored under get_share_dir() / "imported_sessions" (as defined in src/kimi_cli/vis/api/sessions.py:51). Any attempt to run kimi export <imported-session-id> will always fail with "session not found" for imported sessions, making the CLI export feature inconsistent with the web import feature introduced in the same PR.

Suggested change
def _find_session_by_id(session_id: str) -> Path | None:
"""Find a session directory by session ID across all work directories."""
from kimi_cli.share import get_share_dir
sessions_root = get_share_dir() / "sessions"
if not sessions_root.exists():
return None
for work_dir_hash_dir in sessions_root.iterdir():
if not work_dir_hash_dir.is_dir():
continue
candidate = work_dir_hash_dir / session_id
if candidate.is_dir():
return candidate
return None
def _find_session_by_id(session_id: str) -> Path | None:
"""Find a session directory by session ID across all work directories."""
from kimi_cli.share import get_share_dir
sessions_root = get_share_dir() / "sessions"
if sessions_root.exists():
for work_dir_hash_dir in sessions_root.iterdir():
if not work_dir_hash_dir.is_dir():
continue
candidate = work_dir_hash_dir / session_id
if candidate.is_dir():
return candidate
imported_root = get_share_dir() / "imported_sessions"
if imported_root.exists():
candidate = imported_root / session_id
if candidate.is_dir():
return candidate
return None
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.



@cli.callback(invoke_without_command=True)
def export(
session_id: Annotated[
str,
typer.Argument(help="Session ID to export."),
],
output: Annotated[
Path | None,
typer.Option(
"--output",
"-o",
help="Output ZIP file path. Default: session-{id}.zip in current directory.",
),
] = None,
) -> None:
"""Export a session as a ZIP archive."""
session_dir = _find_session_by_id(session_id)
if session_dir is None:
typer.echo(f"Error: session '{session_id}' not found.", err=True)
raise typer.Exit(code=1)

# Collect files
files = sorted(f for f in session_dir.iterdir() if f.is_file())
if not files:
typer.echo(f"Error: session '{session_id}' has no files.", err=True)
raise typer.Exit(code=1)

# Determine output path
if output is None:
output = Path.cwd() / f"session-{session_id}.zip"

# Create ZIP
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
for file_path in files:
zf.write(file_path, arcname=file_path.name)
buf.seek(0)

output.parent.mkdir(parents=True, exist_ok=True)
output.write_bytes(buf.getvalue())

typer.echo(str(output))
Loading
Loading