agent-dump 0.1.0__tar.gz
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.
- agent_dump-0.1.0/.gitignore +42 -0
- agent_dump-0.1.0/.python-version +1 -0
- agent_dump-0.1.0/AGENTS.md +237 -0
- agent_dump-0.1.0/LICENSE +21 -0
- agent_dump-0.1.0/Makefile +12 -0
- agent_dump-0.1.0/PKG-INFO +165 -0
- agent_dump-0.1.0/README.md +141 -0
- agent_dump-0.1.0/pyproject.toml +63 -0
- agent_dump-0.1.0/ruff.toml +41 -0
- agent_dump-0.1.0/src/agent_dump/__init__.py +9 -0
- agent_dump-0.1.0/src/agent_dump/__main__.py +8 -0
- agent_dump-0.1.0/src/agent_dump/cli.py +91 -0
- agent_dump-0.1.0/src/agent_dump/db.py +74 -0
- agent_dump-0.1.0/src/agent_dump/exporter.py +126 -0
- agent_dump-0.1.0/src/agent_dump/selector.py +75 -0
- agent_dump-0.1.0/uv.lock +145 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# python
|
|
2
|
+
.venv
|
|
3
|
+
.ruff_cache
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*$py.class
|
|
7
|
+
*.so
|
|
8
|
+
.Python
|
|
9
|
+
build/
|
|
10
|
+
develop-eggs/
|
|
11
|
+
dist/
|
|
12
|
+
downloads/
|
|
13
|
+
eggs/
|
|
14
|
+
.eggs/
|
|
15
|
+
lib/
|
|
16
|
+
lib64/
|
|
17
|
+
parts/
|
|
18
|
+
sdist/
|
|
19
|
+
var/
|
|
20
|
+
wheels/
|
|
21
|
+
*.egg-info/
|
|
22
|
+
.installed.cfg
|
|
23
|
+
*.egg
|
|
24
|
+
|
|
25
|
+
# pyright
|
|
26
|
+
.pyright/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.vscode/
|
|
30
|
+
.idea/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
*~
|
|
34
|
+
.DS_Store
|
|
35
|
+
|
|
36
|
+
# data
|
|
37
|
+
/data
|
|
38
|
+
/sessions
|
|
39
|
+
|
|
40
|
+
# uv
|
|
41
|
+
*.lock
|
|
42
|
+
!uv.lock
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
> 本文档用于帮助 AI Agents 快速理解本项目结构和开发规范
|
|
4
|
+
|
|
5
|
+
## 项目概述
|
|
6
|
+
|
|
7
|
+
**agent-dump** 是一个 AI 编码助手会话导出工具,支持从 OpenCode 等 AI 编码工具中导出会话数据为 JSON 格式。
|
|
8
|
+
|
|
9
|
+
- **语言**: Python 3.14+
|
|
10
|
+
- **包管理**: uv
|
|
11
|
+
- **代码规范**: Ruff
|
|
12
|
+
- **构建工具**: Hatchling
|
|
13
|
+
|
|
14
|
+
## 项目结构
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
agent-dump/
|
|
18
|
+
├── src/agent_dump/ # 主包目录
|
|
19
|
+
│ ├── __init__.py # 包初始化,公开 API
|
|
20
|
+
│ ├── __main__.py # python -m agent_dump 入口
|
|
21
|
+
│ ├── cli.py # 命令行接口和参数解析
|
|
22
|
+
│ ├── db.py # 数据库连接和查询操作
|
|
23
|
+
│ ├── exporter.py # JSON 导出逻辑
|
|
24
|
+
│ └── selector.py # 交互式会话选择
|
|
25
|
+
├── tests/ # 测试目录(待填充)
|
|
26
|
+
├── data/opencode/ # 本地数据库
|
|
27
|
+
├── sessions/ # 导出目录
|
|
28
|
+
├── pyproject.toml # 项目配置
|
|
29
|
+
├── ruff.toml # 代码风格配置
|
|
30
|
+
└── Makefile # 自动化命令
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 核心模块
|
|
34
|
+
|
|
35
|
+
### cli.py
|
|
36
|
+
命令行入口模块,处理参数解析和主流程控制。
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from agent_dump.cli import main
|
|
40
|
+
# 主函数入口
|
|
41
|
+
main()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**关键函数:**
|
|
45
|
+
- `main()` - 主入口,处理参数解析和工作流调度
|
|
46
|
+
|
|
47
|
+
### db.py
|
|
48
|
+
数据库操作模块,处理 SQLite 数据库连接和查询。
|
|
49
|
+
|
|
50
|
+
**关键函数:**
|
|
51
|
+
- `find_db_path() -> Path` - 自动查找数据库路径
|
|
52
|
+
- `get_recent_sessions(db_path, days=7) -> List[Dict]` - 获取最近 N 天的会话
|
|
53
|
+
|
|
54
|
+
### exporter.py
|
|
55
|
+
导出逻辑模块,处理会话数据的 JSON 序列化。
|
|
56
|
+
|
|
57
|
+
**关键函数:**
|
|
58
|
+
- `export_session(db_path, session, output_dir) -> Path` - 导出单个会话
|
|
59
|
+
- `export_sessions(db_path, sessions, output_dir) -> List[Path]` - 批量导出
|
|
60
|
+
|
|
61
|
+
### selector.py
|
|
62
|
+
交互式选择模块,提供终端和简单两种选择模式。
|
|
63
|
+
|
|
64
|
+
**关键函数:**
|
|
65
|
+
- `select_sessions_interactive(sessions) -> List[Dict]` - 交互式选择(questionary)
|
|
66
|
+
- `select_sessions_simple(sessions) -> List[Dict]` - 简单选择(stdin)
|
|
67
|
+
- `is_terminal() -> bool` - 检查是否在终端环境
|
|
68
|
+
|
|
69
|
+
## 使用方式
|
|
70
|
+
|
|
71
|
+
### 命令行
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# 开发时
|
|
75
|
+
uv run agent-dump # 交互式导出
|
|
76
|
+
uv run agent-dump --days 3 # 导出最近 3 天
|
|
77
|
+
uv run agent-dump --list # 仅列出会话
|
|
78
|
+
uv run agent-dump --export id1,id2 # 指定 ID 导出
|
|
79
|
+
|
|
80
|
+
# 模块方式
|
|
81
|
+
uv run python -m agent_dump
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 作为库使用
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from agent_dump import find_db_path, get_recent_sessions, export_session
|
|
88
|
+
from pathlib import Path
|
|
89
|
+
|
|
90
|
+
# 查找数据库
|
|
91
|
+
db_path = find_db_path()
|
|
92
|
+
|
|
93
|
+
# 获取最近 7 天的会话
|
|
94
|
+
sessions = get_recent_sessions(db_path, days=7)
|
|
95
|
+
|
|
96
|
+
# 导出第一个会话
|
|
97
|
+
output_dir = Path("./my-sessions")
|
|
98
|
+
export_session(db_path, sessions[0], output_dir)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 开发规范
|
|
102
|
+
|
|
103
|
+
### 代码风格
|
|
104
|
+
- 使用 Ruff 进行代码检查和格式化
|
|
105
|
+
- 配置位于 `ruff.toml`
|
|
106
|
+
- 单行最大长度 100
|
|
107
|
+
- 使用双引号
|
|
108
|
+
|
|
109
|
+
### 命令
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# 代码检查
|
|
113
|
+
make lint # ruff check
|
|
114
|
+
make lint.fix # ruff check --fix
|
|
115
|
+
make lint.fmt # ruff format
|
|
116
|
+
|
|
117
|
+
# 类型检查(待配置)
|
|
118
|
+
make check # ty check
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 添加依赖
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# 生产依赖
|
|
125
|
+
uv add package-name
|
|
126
|
+
|
|
127
|
+
# 开发依赖
|
|
128
|
+
uv add --dev package-name
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 扩展指南
|
|
132
|
+
|
|
133
|
+
### 支持新的 AI 工具
|
|
134
|
+
|
|
135
|
+
要支持新的 AI 编码工具(如 Claude Code、Copilot Chat):
|
|
136
|
+
|
|
137
|
+
1. **在 `db.py` 中添加新工具的数据库查找路径**
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
def find_db_path():
|
|
141
|
+
paths = [
|
|
142
|
+
# OpenCode
|
|
143
|
+
os.path.expanduser("~/.local/share/opencode/opencode.db"),
|
|
144
|
+
# 新工具
|
|
145
|
+
os.path.expanduser("~/.config/newtool/database.db"),
|
|
146
|
+
]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
2. **在 `cli.py` 中添加新的 agent 选项**
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
parser.add_argument(
|
|
153
|
+
"--agent",
|
|
154
|
+
type=str,
|
|
155
|
+
default="opencode",
|
|
156
|
+
choices=["opencode", "newtool"], # 添加新选项
|
|
157
|
+
help="Agent tool name",
|
|
158
|
+
)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
3. **如有需要,创建新的数据解析模块**
|
|
162
|
+
|
|
163
|
+
如果新工具的数据库结构与 OpenCode 不同,建议:
|
|
164
|
+
- 创建 `parsers/` 子目录
|
|
165
|
+
- 为每个工具创建解析器类
|
|
166
|
+
- 在 `exporter.py` 中根据 agent 类型选择解析器
|
|
167
|
+
|
|
168
|
+
### 添加新的导出格式
|
|
169
|
+
|
|
170
|
+
1. 在 `exporter.py` 中创建新的导出函数
|
|
171
|
+
2. 在 `cli.py` 中添加格式选项
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# exporter.py
|
|
175
|
+
def export_session_markdown(db_path, session, output_dir) -> Path:
|
|
176
|
+
"""导出为 Markdown 格式"""
|
|
177
|
+
# 实现导出逻辑
|
|
178
|
+
pass
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## 数据库结构
|
|
182
|
+
|
|
183
|
+
### OpenCode 数据库表
|
|
184
|
+
|
|
185
|
+
```sql
|
|
186
|
+
-- session 表
|
|
187
|
+
CREATE TABLE session (
|
|
188
|
+
id TEXT PRIMARY KEY,
|
|
189
|
+
title TEXT,
|
|
190
|
+
time_created INTEGER, -- 毫秒时间戳
|
|
191
|
+
time_updated INTEGER,
|
|
192
|
+
slug TEXT,
|
|
193
|
+
directory TEXT,
|
|
194
|
+
version INTEGER,
|
|
195
|
+
summary_files TEXT
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
-- message 表
|
|
199
|
+
CREATE TABLE message (
|
|
200
|
+
id TEXT PRIMARY KEY,
|
|
201
|
+
session_id TEXT,
|
|
202
|
+
time_created INTEGER,
|
|
203
|
+
data TEXT -- JSON 格式
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
-- part 表
|
|
207
|
+
CREATE TABLE part (
|
|
208
|
+
id TEXT PRIMARY KEY,
|
|
209
|
+
message_id TEXT,
|
|
210
|
+
time_created INTEGER,
|
|
211
|
+
data TEXT -- JSON 格式
|
|
212
|
+
);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## 注意事项
|
|
216
|
+
|
|
217
|
+
1. **数据库路径**: `find_db_path()` 会尝试多个路径,新工具需要添加对应路径
|
|
218
|
+
2. **时间戳**: 数据库存储的是毫秒时间戳,Python datetime 需要转换
|
|
219
|
+
3. **JSON 数据**: message 和 part 表中的 data 字段是 JSON 字符串
|
|
220
|
+
4. **交互模式**: `selector.py` 会根据是否在终端自动选择合适的模式
|
|
221
|
+
|
|
222
|
+
## 发布
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# 构建包
|
|
226
|
+
uv build
|
|
227
|
+
|
|
228
|
+
# 发布到 PyPI
|
|
229
|
+
uv publish
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## 相关文件
|
|
233
|
+
|
|
234
|
+
- `pyproject.toml` - 项目配置和依赖
|
|
235
|
+
- `ruff.toml` - 代码风格配置
|
|
236
|
+
- `README.md` - 用户文档(中文)
|
|
237
|
+
- `Makefile` - 自动化命令
|
agent_dump-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 XingKaiXin contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, standard to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-dump
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI Coding Assistant Session Export Tool
|
|
5
|
+
Project-URL: Homepage, https://github.com/xingkaixin/agent-dump
|
|
6
|
+
Project-URL: Repository, https://github.com/xingkaixin/agent-dump
|
|
7
|
+
Project-URL: Issues, https://github.com/xingkaixin/agent-dump/issues
|
|
8
|
+
Author-email: XingKaiXin <xingkaixin@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,chat,cli,export,opencode
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Python: >=3.14
|
|
21
|
+
Requires-Dist: prompt-toolkit>=3.0.0
|
|
22
|
+
Requires-Dist: questionary>=2.1.1
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Agent Dump
|
|
26
|
+
|
|
27
|
+
AI 编码助手会话导出工具 - 支持从多种 AI 编码工具的会话数据导出会话为 JSON 格式。
|
|
28
|
+
|
|
29
|
+
## 支持的 AI 工具
|
|
30
|
+
|
|
31
|
+
- **OpenCode** - 开源 AI 编程助手
|
|
32
|
+
- **Claude Code** - Anthropic 的 AI 编码工具 *(计划中)*
|
|
33
|
+
- **Code X** - GitHub Copilot Chat *(计划中)*
|
|
34
|
+
- **更多工具** - 欢迎提交 PR 支持其他 AI 编码工具
|
|
35
|
+
|
|
36
|
+
## 功能特性
|
|
37
|
+
|
|
38
|
+
- **交互式选择**: 使用 questionary 提供友好的命令行交互界面
|
|
39
|
+
- **批量导出**: 支持导出最近 N 天的所有会话
|
|
40
|
+
- **指定导出**: 通过会话 ID 导出特定会话
|
|
41
|
+
- **会话列表**: 仅列出会话而不导出
|
|
42
|
+
- **统计数据**: 导出包含 tokens 使用量、成本等统计信息
|
|
43
|
+
- **消息详情**: 完整保留会话消息、工具调用等详细信息
|
|
44
|
+
|
|
45
|
+
## 安装
|
|
46
|
+
|
|
47
|
+
### 方式一:使用 uv tool 安装(推荐)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# 从 PyPI 安装(发布后可使用)
|
|
51
|
+
uv tool install agent-dump
|
|
52
|
+
|
|
53
|
+
# 从 GitHub 直接安装
|
|
54
|
+
uv tool install git+https://github.com/xingkaixin/agent-dump
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 方式二:使用 uvx 直接运行(无需安装)
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# 从 PyPI 运行(发布后可使用)
|
|
61
|
+
uvx agent-dump --help
|
|
62
|
+
|
|
63
|
+
# 从 GitHub 直接运行
|
|
64
|
+
uvx --from git+https://github.com/xingkaixin/agent-dump agent-dump --help
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 方式三:本地开发
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# 克隆仓库
|
|
71
|
+
git clone https://github.com/xingkaixin/agent-dump.git
|
|
72
|
+
cd agent-dump
|
|
73
|
+
|
|
74
|
+
# 使用 uv 安装依赖
|
|
75
|
+
uv sync
|
|
76
|
+
|
|
77
|
+
# 本地安装测试
|
|
78
|
+
uv tool install . --force
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 使用方法
|
|
82
|
+
|
|
83
|
+
### 交互式导出(默认)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# 方式一:使用命令行入口
|
|
87
|
+
uv run agent-dump
|
|
88
|
+
|
|
89
|
+
# 方式二:使用模块运行
|
|
90
|
+
uv run python -m agent_dump
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
运行后会显示最近 7 天的会话列表,使用空格选择/取消,回车确认导出。
|
|
94
|
+
|
|
95
|
+
### 命令行参数
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
uv run agent-dump --days 3 # 导出最近 3 天的会话
|
|
99
|
+
uv run agent-dump --agent claude # 指定 Agent 工具名称
|
|
100
|
+
uv run agent-dump --output ./my-sessions # 指定输出目录
|
|
101
|
+
uv run agent-dump --list # 仅列出会话
|
|
102
|
+
uv run agent-dump --export ses_abc,ses_xyz # 导出指定 ID 的会话
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 完整参数说明
|
|
106
|
+
|
|
107
|
+
| 参数 | 说明 | 默认值 |
|
|
108
|
+
|------|------|--------|
|
|
109
|
+
| `--days` | 查询最近 N 天的会话 | 7 |
|
|
110
|
+
| `--agent` | Agent 工具名称 | opencode |
|
|
111
|
+
| `--output` | 输出目录 | ./sessions |
|
|
112
|
+
| `--export` | 导出指定会话 ID(逗号分隔) | - |
|
|
113
|
+
| `--list` | 仅列出会话,不导出 | - |
|
|
114
|
+
|
|
115
|
+
## 项目结构
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
.
|
|
119
|
+
├── src/
|
|
120
|
+
│ └── agent_dump/ # 主包目录
|
|
121
|
+
│ ├── __init__.py # 包初始化
|
|
122
|
+
│ ├── __main__.py # python -m agent_dump 入口
|
|
123
|
+
│ ├── cli.py # 命令行接口
|
|
124
|
+
│ ├── db.py # 数据库操作
|
|
125
|
+
│ ├── exporter.py # 导出逻辑
|
|
126
|
+
│ └── selector.py # 交互式选择
|
|
127
|
+
├── tests/ # 测试目录
|
|
128
|
+
├── pyproject.toml # 项目配置
|
|
129
|
+
├── Makefile # 自动化命令
|
|
130
|
+
├── ruff.toml # 代码风格配置
|
|
131
|
+
├── data/ # 数据库目录
|
|
132
|
+
│ └── opencode/
|
|
133
|
+
│ └── opencode.db
|
|
134
|
+
└── sessions/ # 导出目录
|
|
135
|
+
└── {agent-name}/ # 按工具分类的导出文件
|
|
136
|
+
└── ses_xxx.json
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 开发
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# 代码检查
|
|
143
|
+
make lint
|
|
144
|
+
|
|
145
|
+
# 自动修复
|
|
146
|
+
make lint.fix
|
|
147
|
+
|
|
148
|
+
# 代码格式化
|
|
149
|
+
make lint.fmt
|
|
150
|
+
|
|
151
|
+
# 类型检查
|
|
152
|
+
make check
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 依赖
|
|
156
|
+
|
|
157
|
+
- Python >= 3.14
|
|
158
|
+
- prompt-toolkit >= 3.0.0
|
|
159
|
+
- questionary >= 2.1.1
|
|
160
|
+
- ruff >= 0.15.2 (开发)
|
|
161
|
+
- ty >= 0.0.18 (开发)
|
|
162
|
+
|
|
163
|
+
## 许可证
|
|
164
|
+
|
|
165
|
+
MIT
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Agent Dump
|
|
2
|
+
|
|
3
|
+
AI 编码助手会话导出工具 - 支持从多种 AI 编码工具的会话数据导出会话为 JSON 格式。
|
|
4
|
+
|
|
5
|
+
## 支持的 AI 工具
|
|
6
|
+
|
|
7
|
+
- **OpenCode** - 开源 AI 编程助手
|
|
8
|
+
- **Claude Code** - Anthropic 的 AI 编码工具 *(计划中)*
|
|
9
|
+
- **Code X** - GitHub Copilot Chat *(计划中)*
|
|
10
|
+
- **更多工具** - 欢迎提交 PR 支持其他 AI 编码工具
|
|
11
|
+
|
|
12
|
+
## 功能特性
|
|
13
|
+
|
|
14
|
+
- **交互式选择**: 使用 questionary 提供友好的命令行交互界面
|
|
15
|
+
- **批量导出**: 支持导出最近 N 天的所有会话
|
|
16
|
+
- **指定导出**: 通过会话 ID 导出特定会话
|
|
17
|
+
- **会话列表**: 仅列出会话而不导出
|
|
18
|
+
- **统计数据**: 导出包含 tokens 使用量、成本等统计信息
|
|
19
|
+
- **消息详情**: 完整保留会话消息、工具调用等详细信息
|
|
20
|
+
|
|
21
|
+
## 安装
|
|
22
|
+
|
|
23
|
+
### 方式一:使用 uv tool 安装(推荐)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 从 PyPI 安装(发布后可使用)
|
|
27
|
+
uv tool install agent-dump
|
|
28
|
+
|
|
29
|
+
# 从 GitHub 直接安装
|
|
30
|
+
uv tool install git+https://github.com/xingkaixin/agent-dump
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 方式二:使用 uvx 直接运行(无需安装)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# 从 PyPI 运行(发布后可使用)
|
|
37
|
+
uvx agent-dump --help
|
|
38
|
+
|
|
39
|
+
# 从 GitHub 直接运行
|
|
40
|
+
uvx --from git+https://github.com/xingkaixin/agent-dump agent-dump --help
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 方式三:本地开发
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 克隆仓库
|
|
47
|
+
git clone https://github.com/xingkaixin/agent-dump.git
|
|
48
|
+
cd agent-dump
|
|
49
|
+
|
|
50
|
+
# 使用 uv 安装依赖
|
|
51
|
+
uv sync
|
|
52
|
+
|
|
53
|
+
# 本地安装测试
|
|
54
|
+
uv tool install . --force
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 使用方法
|
|
58
|
+
|
|
59
|
+
### 交互式导出(默认)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# 方式一:使用命令行入口
|
|
63
|
+
uv run agent-dump
|
|
64
|
+
|
|
65
|
+
# 方式二:使用模块运行
|
|
66
|
+
uv run python -m agent_dump
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
运行后会显示最近 7 天的会话列表,使用空格选择/取消,回车确认导出。
|
|
70
|
+
|
|
71
|
+
### 命令行参数
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
uv run agent-dump --days 3 # 导出最近 3 天的会话
|
|
75
|
+
uv run agent-dump --agent claude # 指定 Agent 工具名称
|
|
76
|
+
uv run agent-dump --output ./my-sessions # 指定输出目录
|
|
77
|
+
uv run agent-dump --list # 仅列出会话
|
|
78
|
+
uv run agent-dump --export ses_abc,ses_xyz # 导出指定 ID 的会话
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 完整参数说明
|
|
82
|
+
|
|
83
|
+
| 参数 | 说明 | 默认值 |
|
|
84
|
+
|------|------|--------|
|
|
85
|
+
| `--days` | 查询最近 N 天的会话 | 7 |
|
|
86
|
+
| `--agent` | Agent 工具名称 | opencode |
|
|
87
|
+
| `--output` | 输出目录 | ./sessions |
|
|
88
|
+
| `--export` | 导出指定会话 ID(逗号分隔) | - |
|
|
89
|
+
| `--list` | 仅列出会话,不导出 | - |
|
|
90
|
+
|
|
91
|
+
## 项目结构
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
.
|
|
95
|
+
├── src/
|
|
96
|
+
│ └── agent_dump/ # 主包目录
|
|
97
|
+
│ ├── __init__.py # 包初始化
|
|
98
|
+
│ ├── __main__.py # python -m agent_dump 入口
|
|
99
|
+
│ ├── cli.py # 命令行接口
|
|
100
|
+
│ ├── db.py # 数据库操作
|
|
101
|
+
│ ├── exporter.py # 导出逻辑
|
|
102
|
+
│ └── selector.py # 交互式选择
|
|
103
|
+
├── tests/ # 测试目录
|
|
104
|
+
├── pyproject.toml # 项目配置
|
|
105
|
+
├── Makefile # 自动化命令
|
|
106
|
+
├── ruff.toml # 代码风格配置
|
|
107
|
+
├── data/ # 数据库目录
|
|
108
|
+
│ └── opencode/
|
|
109
|
+
│ └── opencode.db
|
|
110
|
+
└── sessions/ # 导出目录
|
|
111
|
+
└── {agent-name}/ # 按工具分类的导出文件
|
|
112
|
+
└── ses_xxx.json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 开发
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# 代码检查
|
|
119
|
+
make lint
|
|
120
|
+
|
|
121
|
+
# 自动修复
|
|
122
|
+
make lint.fix
|
|
123
|
+
|
|
124
|
+
# 代码格式化
|
|
125
|
+
make lint.fmt
|
|
126
|
+
|
|
127
|
+
# 类型检查
|
|
128
|
+
make check
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 依赖
|
|
132
|
+
|
|
133
|
+
- Python >= 3.14
|
|
134
|
+
- prompt-toolkit >= 3.0.0
|
|
135
|
+
- questionary >= 2.1.1
|
|
136
|
+
- ruff >= 0.15.2 (开发)
|
|
137
|
+
- ty >= 0.0.18 (开发)
|
|
138
|
+
|
|
139
|
+
## 许可证
|
|
140
|
+
|
|
141
|
+
MIT
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "agent-dump"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "AI Coding Assistant Session Export Tool"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.14"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "XingKaiXin", email = "xingkaixin@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
keywords = ["ai", "chat", "export", "opencode", "cli"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.14",
|
|
20
|
+
"Topic :: Utilities",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"prompt-toolkit>=3.0.0",
|
|
24
|
+
"questionary>=2.1.1",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/xingkaixin/agent-dump"
|
|
29
|
+
Repository = "https://github.com/xingkaixin/agent-dump"
|
|
30
|
+
Issues = "https://github.com/xingkaixin/agent-dump/issues"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
agent-dump = "agent_dump.cli:main"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"pyright>=1.1.408",
|
|
38
|
+
"ruff>=0.15.2",
|
|
39
|
+
"ty>=0.0.18",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["hatchling"]
|
|
44
|
+
build-backend = "hatchling.build"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
packages = ["src/agent_dump"]
|
|
48
|
+
|
|
49
|
+
[tool.pyright]
|
|
50
|
+
pythonVersion = "3.14"
|
|
51
|
+
include = ["src"]
|
|
52
|
+
exclude = [
|
|
53
|
+
"**/node_modules",
|
|
54
|
+
"**/__pycache__",
|
|
55
|
+
".git",
|
|
56
|
+
".venv",
|
|
57
|
+
]
|
|
58
|
+
defineConstant = { DEBUG = true }
|
|
59
|
+
venvPath = "."
|
|
60
|
+
venv = ".venv"
|
|
61
|
+
typeCheckingMode = "standard"
|
|
62
|
+
reportMissingImports = true
|
|
63
|
+
reportMissingTypeStubs = false
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# ruff.toml
|
|
2
|
+
line-length = 120
|
|
3
|
+
target-version = "py314"
|
|
4
|
+
exclude = [
|
|
5
|
+
".git",
|
|
6
|
+
".venv",
|
|
7
|
+
"__pycache__",
|
|
8
|
+
"tests",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[lint]
|
|
12
|
+
select = [
|
|
13
|
+
"E", # pycodestyle
|
|
14
|
+
"F", # pyflakes
|
|
15
|
+
"I", # isort
|
|
16
|
+
"UP", # pyupgrade
|
|
17
|
+
"B", # bugbear
|
|
18
|
+
"A", # builtins
|
|
19
|
+
"S", # bandit(安全检查,轻量但非常有用)
|
|
20
|
+
"C4", # flake8-comprehensions(提升可读性)
|
|
21
|
+
"SIM", # flake8-simplify(简化代码)
|
|
22
|
+
]
|
|
23
|
+
ignore = [
|
|
24
|
+
"E501", # line-length
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# --- Import Sorting ---
|
|
28
|
+
[lint.isort]
|
|
29
|
+
known-first-party = ["agent-dump"]
|
|
30
|
+
combine-as-imports = true
|
|
31
|
+
force-sort-within-sections = true
|
|
32
|
+
|
|
33
|
+
[format]
|
|
34
|
+
quote-style = "double"
|
|
35
|
+
indent-style = "space"
|
|
36
|
+
line-ending = "lf"
|
|
37
|
+
|
|
38
|
+
# --- 可选:对特定目录放宽规则 ---
|
|
39
|
+
[lint.per-file-ignores]
|
|
40
|
+
"tests/*" = ["S101"] # 测试里允许 assert
|
|
41
|
+
"scripts/*" = ["SIM"] # 脚本可以更随意
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Dump - AI Coding Assistant Session Export Tool
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
__all__ = ["find_db_path", "get_recent_sessions", "export_session", "export_sessions"]
|
|
7
|
+
|
|
8
|
+
from agent_dump.db import find_db_path, get_recent_sessions
|
|
9
|
+
from agent_dump.exporter import export_session, export_sessions
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for agent-dump
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from agent_dump.db import find_db_path, get_recent_sessions
|
|
9
|
+
from agent_dump.exporter import export_sessions
|
|
10
|
+
from agent_dump.selector import select_sessions_interactive
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
"""Main entry point"""
|
|
15
|
+
parser = argparse.ArgumentParser(description="Export agent sessions to JSON")
|
|
16
|
+
parser.add_argument("--days", type=int, default=7, help="Number of days to look back (default: 7)")
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--agent",
|
|
19
|
+
type=str,
|
|
20
|
+
default="opencode",
|
|
21
|
+
help="Agent tool name (default: opencode)",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--output",
|
|
25
|
+
type=str,
|
|
26
|
+
default="./sessions",
|
|
27
|
+
help="Output base directory (default: ./sessions)",
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--export",
|
|
31
|
+
type=str,
|
|
32
|
+
metavar="IDS",
|
|
33
|
+
help="Export specific session IDs (comma-separated)",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument("--list", action="store_true", help="List sessions without exporting")
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
|
|
38
|
+
print(f"🔍 {args.agent.title()} Session Exporter\n")
|
|
39
|
+
|
|
40
|
+
# Find database
|
|
41
|
+
try:
|
|
42
|
+
db_path = find_db_path()
|
|
43
|
+
print(f"📁 Database: {db_path}\n")
|
|
44
|
+
except FileNotFoundError as e:
|
|
45
|
+
print(f"❌ Error: {e}")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Get recent sessions
|
|
49
|
+
print(f"📊 Loading sessions from the last {args.days} days...")
|
|
50
|
+
sessions = get_recent_sessions(db_path, days=args.days)
|
|
51
|
+
print(f"✓ Found {len(sessions)} sessions\n")
|
|
52
|
+
|
|
53
|
+
if not sessions:
|
|
54
|
+
print("No sessions found.")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
# List mode
|
|
58
|
+
if args.list:
|
|
59
|
+
print("Available sessions:")
|
|
60
|
+
print("-" * 80)
|
|
61
|
+
for i, session in enumerate(sessions, 1):
|
|
62
|
+
print(f"{i}. {session['title']}")
|
|
63
|
+
print(f" Time: {session['created_formatted']}")
|
|
64
|
+
print(f" ID: {session['id']}")
|
|
65
|
+
print()
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
# Export specific IDs
|
|
69
|
+
if args.export:
|
|
70
|
+
target_ids = [sid.strip() for sid in args.export.split(",")]
|
|
71
|
+
selected = [s for s in sessions if s["id"] in target_ids]
|
|
72
|
+
if not selected:
|
|
73
|
+
print(f"❌ No sessions found with IDs: {args.export}")
|
|
74
|
+
return
|
|
75
|
+
print(f"✓ Selected {len(selected)} session(s) by ID\n")
|
|
76
|
+
else:
|
|
77
|
+
# Interactive selection
|
|
78
|
+
selected = select_sessions_interactive(sessions)
|
|
79
|
+
if not selected:
|
|
80
|
+
print("\n⚠️ No sessions selected. Exiting.")
|
|
81
|
+
return
|
|
82
|
+
print(f"\n✓ Selected {len(selected)} session(s)\n")
|
|
83
|
+
|
|
84
|
+
# Export
|
|
85
|
+
output_dir = Path(args.output) / args.agent
|
|
86
|
+
exported = export_sessions(db_path, selected, output_dir)
|
|
87
|
+
print(f"\n✅ Successfully exported {len(exported)} session(s) to {output_dir}/")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database operations for agent session export
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sqlite3
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def find_db_path() -> Path:
|
|
13
|
+
"""Find the OpenCode database path"""
|
|
14
|
+
paths = [
|
|
15
|
+
os.path.expanduser("data/opencode/opencode.db"),
|
|
16
|
+
os.path.expanduser("~/.local/share/opencode/opencode.db"),
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
for path in paths:
|
|
20
|
+
if os.path.exists(path):
|
|
21
|
+
return Path(path)
|
|
22
|
+
|
|
23
|
+
raise FileNotFoundError("Could not find opencode.db database")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_recent_sessions(db_path: Path, days: int = 7) -> list[dict[str, Any]]:
|
|
27
|
+
"""Get sessions from the last N days"""
|
|
28
|
+
conn = sqlite3.connect(db_path)
|
|
29
|
+
conn.row_factory = sqlite3.Row
|
|
30
|
+
cursor = conn.cursor()
|
|
31
|
+
|
|
32
|
+
# Calculate timestamp for N days ago (milliseconds)
|
|
33
|
+
cutoff_time = int((datetime.now() - timedelta(days=days)).timestamp() * 1000)
|
|
34
|
+
|
|
35
|
+
# Query sessions with basic info
|
|
36
|
+
cursor.execute(
|
|
37
|
+
"""
|
|
38
|
+
SELECT
|
|
39
|
+
s.id,
|
|
40
|
+
s.title,
|
|
41
|
+
s.time_created,
|
|
42
|
+
s.time_updated,
|
|
43
|
+
s.slug,
|
|
44
|
+
s.directory,
|
|
45
|
+
s.version,
|
|
46
|
+
s.summary_files
|
|
47
|
+
FROM session s
|
|
48
|
+
WHERE s.time_created >= ?
|
|
49
|
+
ORDER BY s.time_created DESC
|
|
50
|
+
""",
|
|
51
|
+
(cutoff_time,),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
sessions = []
|
|
55
|
+
for row in cursor.fetchall():
|
|
56
|
+
# Convert timestamp to readable format
|
|
57
|
+
created_dt = datetime.fromtimestamp(row["time_created"] / 1000)
|
|
58
|
+
|
|
59
|
+
sessions.append(
|
|
60
|
+
{
|
|
61
|
+
"id": row["id"],
|
|
62
|
+
"title": row["title"],
|
|
63
|
+
"time_created": row["time_created"],
|
|
64
|
+
"time_updated": row["time_updated"],
|
|
65
|
+
"created_formatted": created_dt.strftime("%Y-%m-%d %H:%M:%S"),
|
|
66
|
+
"slug": row["slug"],
|
|
67
|
+
"directory": row["directory"],
|
|
68
|
+
"version": row["version"],
|
|
69
|
+
"summary_files": row["summary_files"],
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
conn.close()
|
|
74
|
+
return sessions
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session export functionality
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import sqlite3
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def export_session(db_path: Path, session: dict[str, Any], output_dir: Path) -> Path:
|
|
12
|
+
"""Export a single session to JSON"""
|
|
13
|
+
conn = sqlite3.connect(db_path)
|
|
14
|
+
conn.row_factory = sqlite3.Row
|
|
15
|
+
cursor = conn.cursor()
|
|
16
|
+
|
|
17
|
+
# Build session data
|
|
18
|
+
session_data = {
|
|
19
|
+
"id": session["id"],
|
|
20
|
+
"title": session["title"],
|
|
21
|
+
"slug": session["slug"],
|
|
22
|
+
"directory": session["directory"],
|
|
23
|
+
"version": session["version"],
|
|
24
|
+
"time_created": session["time_created"],
|
|
25
|
+
"time_updated": session["time_updated"],
|
|
26
|
+
"summary_files": session["summary_files"],
|
|
27
|
+
"stats": {
|
|
28
|
+
"total_cost": 0,
|
|
29
|
+
"total_input_tokens": 0,
|
|
30
|
+
"total_output_tokens": 0,
|
|
31
|
+
"message_count": 0,
|
|
32
|
+
},
|
|
33
|
+
"messages": [],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Get messages for this session
|
|
37
|
+
cursor.execute(
|
|
38
|
+
"""
|
|
39
|
+
SELECT * FROM message
|
|
40
|
+
WHERE session_id = ?
|
|
41
|
+
ORDER BY time_created ASC
|
|
42
|
+
""",
|
|
43
|
+
(session["id"],),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
for msg_row in cursor.fetchall():
|
|
47
|
+
msg_data = json.loads(msg_row["data"])
|
|
48
|
+
|
|
49
|
+
message = {
|
|
50
|
+
"id": msg_row["id"],
|
|
51
|
+
"role": msg_data.get("role", "unknown"),
|
|
52
|
+
"agent": msg_data.get("agent"),
|
|
53
|
+
"mode": msg_data.get("mode"),
|
|
54
|
+
"model": msg_data.get("modelID"),
|
|
55
|
+
"provider": msg_data.get("providerID"),
|
|
56
|
+
"time_created": msg_row["time_created"],
|
|
57
|
+
"time_completed": msg_data.get("time", {}).get("completed"),
|
|
58
|
+
"tokens": msg_data.get("tokens", {}),
|
|
59
|
+
"cost": msg_data.get("cost", 0),
|
|
60
|
+
"parts": [],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Update session stats
|
|
64
|
+
session_data["stats"]["message_count"] += 1
|
|
65
|
+
if message["cost"]:
|
|
66
|
+
session_data["stats"]["total_cost"] += message["cost"]
|
|
67
|
+
tokens = message["tokens"] or {}
|
|
68
|
+
session_data["stats"]["total_input_tokens"] += tokens.get("input", 0)
|
|
69
|
+
session_data["stats"]["total_output_tokens"] += tokens.get("output", 0)
|
|
70
|
+
|
|
71
|
+
# Get parts for this message
|
|
72
|
+
cursor.execute(
|
|
73
|
+
"""
|
|
74
|
+
SELECT * FROM part
|
|
75
|
+
WHERE message_id = ?
|
|
76
|
+
ORDER BY time_created ASC
|
|
77
|
+
""",
|
|
78
|
+
(msg_row["id"],),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
for part_row in cursor.fetchall():
|
|
82
|
+
part_data = json.loads(part_row["data"])
|
|
83
|
+
part = {
|
|
84
|
+
"type": part_data.get("type"),
|
|
85
|
+
"time_created": part_row["time_created"],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if part["type"] == "text" or part["type"] == "reasoning":
|
|
89
|
+
part["text"] = part_data.get("text", "")
|
|
90
|
+
elif part["type"] == "tool":
|
|
91
|
+
part["tool"] = part_data.get("tool")
|
|
92
|
+
part["callID"] = part_data.get("callID")
|
|
93
|
+
part["title"] = part_data.get("title", "")
|
|
94
|
+
part["state"] = part_data.get("state", {})
|
|
95
|
+
elif part["type"] in ["step-start", "step-finish"]:
|
|
96
|
+
part["reason"] = part_data.get("reason")
|
|
97
|
+
part["tokens"] = part_data.get("tokens")
|
|
98
|
+
part["cost"] = part_data.get("cost")
|
|
99
|
+
|
|
100
|
+
message["parts"].append(part)
|
|
101
|
+
|
|
102
|
+
session_data["messages"].append(message)
|
|
103
|
+
|
|
104
|
+
conn.close()
|
|
105
|
+
|
|
106
|
+
# Save to file
|
|
107
|
+
output_path = output_dir / f"{session['id']}.json"
|
|
108
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
109
|
+
json.dump(session_data, f, ensure_ascii=False, indent=2)
|
|
110
|
+
|
|
111
|
+
return output_path
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def export_sessions(db_path: Path, sessions: list[dict[str, Any]], output_dir: Path) -> list[Path]:
|
|
115
|
+
"""Export multiple sessions"""
|
|
116
|
+
output_dir = Path(output_dir)
|
|
117
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
|
|
119
|
+
print("📤 Exporting sessions...")
|
|
120
|
+
exported = []
|
|
121
|
+
for session in sessions:
|
|
122
|
+
output_path = export_session(db_path, session, output_dir)
|
|
123
|
+
exported.append(output_path)
|
|
124
|
+
print(f" ✓ {session['title'][:50]}... → {output_path.name}")
|
|
125
|
+
|
|
126
|
+
return exported
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session selection utilities
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import questionary
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_terminal() -> bool:
|
|
12
|
+
"""Check if running in a terminal"""
|
|
13
|
+
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def select_sessions_interactive(sessions: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
17
|
+
"""Let user select sessions interactively"""
|
|
18
|
+
if not sessions:
|
|
19
|
+
print("No sessions found in the specified time range.")
|
|
20
|
+
return []
|
|
21
|
+
|
|
22
|
+
# If not in terminal, use simple selection
|
|
23
|
+
if not is_terminal():
|
|
24
|
+
return select_sessions_simple(sessions)
|
|
25
|
+
|
|
26
|
+
# Prepare choices for questionary
|
|
27
|
+
choices = []
|
|
28
|
+
for session in sessions:
|
|
29
|
+
# Format: Title (Date) - ID
|
|
30
|
+
label = f"{session['title'][:60]}{'...' if len(session['title']) > 60 else ''}"
|
|
31
|
+
description = f"{session['created_formatted']} | {session['id']}"
|
|
32
|
+
|
|
33
|
+
choices.append(questionary.Choice(title=label, value=session, description=description))
|
|
34
|
+
|
|
35
|
+
# Show interactive checkbox
|
|
36
|
+
selected = questionary.checkbox(
|
|
37
|
+
"选择要导出的会话 (空格选择/取消, 回车确认):",
|
|
38
|
+
choices=choices,
|
|
39
|
+
instruction="\n使用 ↑↓ 移动, 空格 选择/取消, 回车 确认导出",
|
|
40
|
+
).ask()
|
|
41
|
+
|
|
42
|
+
return selected or []
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def select_sessions_simple(sessions: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
46
|
+
"""Simple selection for non-terminal environments"""
|
|
47
|
+
print("Available sessions:")
|
|
48
|
+
print("-" * 80)
|
|
49
|
+
for i, session in enumerate(sessions, 1):
|
|
50
|
+
print(f"{i}. {session['title'][:60]}")
|
|
51
|
+
print(f" {session['created_formatted']} | {session['id']}")
|
|
52
|
+
print()
|
|
53
|
+
|
|
54
|
+
print("Enter session numbers to export (comma-separated, e.g., '1,3,5' or 'all'):")
|
|
55
|
+
try:
|
|
56
|
+
selection = input("> ").strip()
|
|
57
|
+
except EOFError:
|
|
58
|
+
print("\n⚠️ No input provided. Exiting.")
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
if selection.lower() == "all":
|
|
62
|
+
return sessions
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
indices = [int(x.strip()) - 1 for x in selection.split(",")]
|
|
66
|
+
selected = []
|
|
67
|
+
for idx in indices:
|
|
68
|
+
if 0 <= idx < len(sessions):
|
|
69
|
+
selected.append(sessions[idx])
|
|
70
|
+
else:
|
|
71
|
+
print(f"⚠️ Invalid selection: {idx + 1}")
|
|
72
|
+
return selected
|
|
73
|
+
except ValueError:
|
|
74
|
+
print("⚠️ Invalid input. Please enter numbers separated by commas.")
|
|
75
|
+
return []
|
agent_dump-0.1.0/uv.lock
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.14"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "agent-dump"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
source = { editable = "." }
|
|
9
|
+
dependencies = [
|
|
10
|
+
{ name = "prompt-toolkit" },
|
|
11
|
+
{ name = "questionary" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[package.dev-dependencies]
|
|
15
|
+
dev = [
|
|
16
|
+
{ name = "pyright" },
|
|
17
|
+
{ name = "ruff" },
|
|
18
|
+
{ name = "ty" },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[package.metadata]
|
|
22
|
+
requires-dist = [
|
|
23
|
+
{ name = "prompt-toolkit", specifier = ">=3.0.0" },
|
|
24
|
+
{ name = "questionary", specifier = ">=2.1.1" },
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[package.metadata.requires-dev]
|
|
28
|
+
dev = [
|
|
29
|
+
{ name = "pyright", specifier = ">=1.1.408" },
|
|
30
|
+
{ name = "ruff", specifier = ">=0.15.2" },
|
|
31
|
+
{ name = "ty", specifier = ">=0.0.18" },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[[package]]
|
|
35
|
+
name = "nodeenv"
|
|
36
|
+
version = "1.10.0"
|
|
37
|
+
source = { registry = "https://pypi.org/simple" }
|
|
38
|
+
sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
|
|
39
|
+
wheels = [
|
|
40
|
+
{ url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[[package]]
|
|
44
|
+
name = "prompt-toolkit"
|
|
45
|
+
version = "3.0.52"
|
|
46
|
+
source = { registry = "https://pypi.org/simple" }
|
|
47
|
+
dependencies = [
|
|
48
|
+
{ name = "wcwidth" },
|
|
49
|
+
]
|
|
50
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
|
|
51
|
+
wheels = [
|
|
52
|
+
{ url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[[package]]
|
|
56
|
+
name = "pyright"
|
|
57
|
+
version = "1.1.408"
|
|
58
|
+
source = { registry = "https://pypi.org/simple" }
|
|
59
|
+
dependencies = [
|
|
60
|
+
{ name = "nodeenv" },
|
|
61
|
+
{ name = "typing-extensions" },
|
|
62
|
+
]
|
|
63
|
+
sdist = { url = "https://files.pythonhosted.org/packages/74/b2/5db700e52554b8f025faa9c3c624c59f1f6c8841ba81ab97641b54322f16/pyright-1.1.408.tar.gz", hash = "sha256:f28f2321f96852fa50b5829ea492f6adb0e6954568d1caa3f3af3a5f555eb684", size = 4400578, upload-time = "2026-01-08T08:07:38.795Z" }
|
|
64
|
+
wheels = [
|
|
65
|
+
{ url = "https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl", hash = "sha256:090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1", size = 6399144, upload-time = "2026-01-08T08:07:37.082Z" },
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[[package]]
|
|
69
|
+
name = "questionary"
|
|
70
|
+
version = "2.1.1"
|
|
71
|
+
source = { registry = "https://pypi.org/simple" }
|
|
72
|
+
dependencies = [
|
|
73
|
+
{ name = "prompt-toolkit" },
|
|
74
|
+
]
|
|
75
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d", size = 25845, upload-time = "2025-08-28T19:00:20.851Z" }
|
|
76
|
+
wheels = [
|
|
77
|
+
{ url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" },
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[[package]]
|
|
81
|
+
name = "ruff"
|
|
82
|
+
version = "0.15.2"
|
|
83
|
+
source = { registry = "https://pypi.org/simple" }
|
|
84
|
+
sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" }
|
|
85
|
+
wheels = [
|
|
86
|
+
{ url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" },
|
|
87
|
+
{ url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" },
|
|
88
|
+
{ url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" },
|
|
89
|
+
{ url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" },
|
|
90
|
+
{ url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" },
|
|
91
|
+
{ url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" },
|
|
92
|
+
{ url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" },
|
|
93
|
+
{ url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" },
|
|
94
|
+
{ url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" },
|
|
95
|
+
{ url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" },
|
|
96
|
+
{ url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" },
|
|
97
|
+
{ url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" },
|
|
98
|
+
{ url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" },
|
|
99
|
+
{ url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" },
|
|
100
|
+
{ url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" },
|
|
101
|
+
{ url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" },
|
|
102
|
+
{ url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" },
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
[[package]]
|
|
106
|
+
name = "ty"
|
|
107
|
+
version = "0.0.18"
|
|
108
|
+
source = { registry = "https://pypi.org/simple" }
|
|
109
|
+
sdist = { url = "https://files.pythonhosted.org/packages/74/15/9682700d8d60fdca7afa4febc83a2354b29cdcd56e66e19c92b521db3b39/ty-0.0.18.tar.gz", hash = "sha256:04ab7c3db5dcbcdac6ce62e48940d3a0124f377c05499d3f3e004e264ae94b83", size = 5214774, upload-time = "2026-02-20T21:51:31.173Z" }
|
|
110
|
+
wheels = [
|
|
111
|
+
{ url = "https://files.pythonhosted.org/packages/ae/d8/920460d4c22ea68fcdeb0b2fb53ea2aeb9c6d7875bde9278d84f2ac767b6/ty-0.0.18-py3-none-linux_armv6l.whl", hash = "sha256:4e5e91b0a79857316ef893c5068afc4b9872f9d257627d9bc8ac4d2715750d88", size = 10280825, upload-time = "2026-02-20T21:51:25.03Z" },
|
|
112
|
+
{ url = "https://files.pythonhosted.org/packages/83/56/62587de582d3d20d78fcdddd0594a73822ac5a399a12ef512085eb7a4de6/ty-0.0.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee0e578b3f8416e2d5416da9553b78fd33857868aa1384cb7fefeceee5ff102d", size = 10118324, upload-time = "2026-02-20T21:51:22.27Z" },
|
|
113
|
+
{ url = "https://files.pythonhosted.org/packages/2f/2d/dbdace8d432a0755a7417f659bfd5b8a4261938ecbdfd7b42f4c454f5aa9/ty-0.0.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3f7a0487d36b939546a91d141f7fc3dbea32fab4982f618d5b04dc9d5b6da21e", size = 9605861, upload-time = "2026-02-20T21:51:16.066Z" },
|
|
114
|
+
{ url = "https://files.pythonhosted.org/packages/6b/d9/de11c0280f778d5fc571393aada7fe9b8bc1dd6a738f2e2c45702b8b3150/ty-0.0.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5e2fa8d45f57ca487a470e4bf66319c09b561150e98ae2a6b1a97ef04c1a4eb", size = 10092701, upload-time = "2026-02-20T21:51:26.862Z" },
|
|
115
|
+
{ url = "https://files.pythonhosted.org/packages/0f/94/068d4d591d791041732171e7b63c37a54494b2e7d28e88d2167eaa9ad875/ty-0.0.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d75652e9e937f7044b1aca16091193e7ef11dac1c7ec952b7fb8292b7ba1f5f2", size = 10109203, upload-time = "2026-02-20T21:51:11.59Z" },
|
|
116
|
+
{ url = "https://files.pythonhosted.org/packages/34/e4/526a4aa56dc0ca2569aaa16880a1ab105c3b416dd70e87e25a05688999f3/ty-0.0.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:563c868edceb8f6ddd5e91113c17d3676b028f0ed380bdb3829b06d9beb90e58", size = 10614200, upload-time = "2026-02-20T21:51:20.298Z" },
|
|
117
|
+
{ url = "https://files.pythonhosted.org/packages/fd/3d/b68ab20a34122a395880922587fbfc3adf090d22e0fb546d4d20fe8c2621/ty-0.0.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:502e2a1f948bec563a0454fc25b074bf5cf041744adba8794d024277e151d3b0", size = 11153232, upload-time = "2026-02-20T21:51:14.121Z" },
|
|
118
|
+
{ url = "https://files.pythonhosted.org/packages/68/ea/678243c042343fcda7e6af36036c18676c355878dcdcd517639586d2cf9e/ty-0.0.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc881dea97021a3aa29134a476937fd8054775c4177d01b94db27fcfb7aab65b", size = 10832934, upload-time = "2026-02-20T21:51:32.92Z" },
|
|
119
|
+
{ url = "https://files.pythonhosted.org/packages/d8/bd/7f8d647cef8b7b346c0163230a37e903c7461c7248574840b977045c77df/ty-0.0.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:421fcc3bc64cab56f48edb863c7c1c43649ec4d78ff71a1acb5366ad723b6021", size = 10700888, upload-time = "2026-02-20T21:51:09.673Z" },
|
|
120
|
+
{ url = "https://files.pythonhosted.org/packages/6e/06/cb3620dc48c5d335ba7876edfef636b2f4498eff4a262ff90033b9e88408/ty-0.0.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0fe5038a7136a0e638a2fb1ad06e3d3c4045314c6ba165c9c303b9aeb4623d6c", size = 10078965, upload-time = "2026-02-20T21:51:07.678Z" },
|
|
121
|
+
{ url = "https://files.pythonhosted.org/packages/60/27/c77a5a84533fa3b685d592de7b4b108eb1f38851c40fac4e79cc56ec7350/ty-0.0.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d123600a52372677613a719bbb780adeb9b68f47fb5f25acb09171de390e0035", size = 10134659, upload-time = "2026-02-20T21:51:18.311Z" },
|
|
122
|
+
{ url = "https://files.pythonhosted.org/packages/43/6e/60af6b88c73469e628ba5253a296da6984e0aa746206f3034c31f1a04ed1/ty-0.0.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb4bc11d32a1bf96a829bf6b9696545a30a196ac77bbc07cc8d3dfee35e03723", size = 10297494, upload-time = "2026-02-20T21:51:39.631Z" },
|
|
123
|
+
{ url = "https://files.pythonhosted.org/packages/33/90/612dc0b68224c723faed6adac2bd3f930a750685db76dfe17e6b9e534a83/ty-0.0.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dda2efbf374ba4cd704053d04e32f2f784e85c2ddc2400006b0f96f5f7e4b667", size = 10791944, upload-time = "2026-02-20T21:51:37.13Z" },
|
|
124
|
+
{ url = "https://files.pythonhosted.org/packages/0d/da/f4ada0fd08a9e4138fe3fd2bcd3797753593f423f19b1634a814b9b2a401/ty-0.0.18-py3-none-win32.whl", hash = "sha256:c5768607c94977dacddc2f459ace6a11a408a0f57888dd59abb62d28d4fee4f7", size = 9677964, upload-time = "2026-02-20T21:51:42.039Z" },
|
|
125
|
+
{ url = "https://files.pythonhosted.org/packages/5e/fa/090ed9746e5c59fc26d8f5f96dc8441825171f1f47752f1778dad690b08b/ty-0.0.18-py3-none-win_amd64.whl", hash = "sha256:b78d0fa1103d36fc2fce92f2092adace52a74654ab7884d54cdaec8eb5016a4d", size = 10636576, upload-time = "2026-02-20T21:51:29.159Z" },
|
|
126
|
+
{ url = "https://files.pythonhosted.org/packages/92/4f/5dd60904c8105cda4d0be34d3a446c180933c76b84ae0742e58f02133713/ty-0.0.18-py3-none-win_arm64.whl", hash = "sha256:01770c3c82137c6b216aa3251478f0b197e181054ee92243772de553d3586398", size = 10095449, upload-time = "2026-02-20T21:51:34.914Z" },
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
[[package]]
|
|
130
|
+
name = "typing-extensions"
|
|
131
|
+
version = "4.15.0"
|
|
132
|
+
source = { registry = "https://pypi.org/simple" }
|
|
133
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
|
134
|
+
wheels = [
|
|
135
|
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
[[package]]
|
|
139
|
+
name = "wcwidth"
|
|
140
|
+
version = "0.6.0"
|
|
141
|
+
source = { registry = "https://pypi.org/simple" }
|
|
142
|
+
sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" }
|
|
143
|
+
wheels = [
|
|
144
|
+
{ url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" },
|
|
145
|
+
]
|