trove-cli 0.0.0-stage → 0.3.2

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.
Files changed (210) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -2
  3. package/dist/adapters/file-source.d.ts +31 -0
  4. package/dist/adapters/file-source.js +97 -0
  5. package/dist/adapters/file-source.js.map +1 -0
  6. package/dist/adapters/index.d.ts +34 -0
  7. package/dist/adapters/index.js +146 -0
  8. package/dist/adapters/index.js.map +1 -0
  9. package/dist/adapters/opencode.d.ts +30 -0
  10. package/dist/adapters/opencode.js +135 -0
  11. package/dist/adapters/opencode.js.map +1 -0
  12. package/dist/cli.d.ts +58 -0
  13. package/dist/cli.js +974 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config.d.ts +81 -0
  16. package/dist/config.js +99 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/core/archive.d.ts +27 -0
  19. package/dist/core/archive.js +101 -0
  20. package/dist/core/archive.js.map +1 -0
  21. package/dist/core/chunker.d.ts +28 -0
  22. package/dist/core/chunker.js +67 -0
  23. package/dist/core/chunker.js.map +1 -0
  24. package/dist/core/clean.d.ts +36 -0
  25. package/dist/core/clean.js +182 -0
  26. package/dist/core/clean.js.map +1 -0
  27. package/dist/core/collect.d.ts +39 -0
  28. package/dist/core/collect.js +194 -0
  29. package/dist/core/collect.js.map +1 -0
  30. package/dist/core/concurrency.d.ts +2 -0
  31. package/dist/core/concurrency.js +16 -0
  32. package/dist/core/concurrency.js.map +1 -0
  33. package/dist/core/cursor.d.ts +31 -0
  34. package/dist/core/cursor.js +31 -0
  35. package/dist/core/cursor.js.map +1 -0
  36. package/dist/core/events/claude.d.ts +7 -0
  37. package/dist/core/events/claude.js +287 -0
  38. package/dist/core/events/claude.js.map +1 -0
  39. package/dist/core/events/codex.d.ts +6 -0
  40. package/dist/core/events/codex.js +149 -0
  41. package/dist/core/events/codex.js.map +1 -0
  42. package/dist/core/events/doubao.d.ts +9 -0
  43. package/dist/core/events/doubao.js +151 -0
  44. package/dist/core/events/doubao.js.map +1 -0
  45. package/dist/core/events/index.d.ts +6 -0
  46. package/dist/core/events/index.js +20 -0
  47. package/dist/core/events/index.js.map +1 -0
  48. package/dist/core/events/opencode.d.ts +7 -0
  49. package/dist/core/events/opencode.js +189 -0
  50. package/dist/core/events/opencode.js.map +1 -0
  51. package/dist/core/events/schema.d.ts +83 -0
  52. package/dist/core/events/schema.js +35 -0
  53. package/dist/core/events/schema.js.map +1 -0
  54. package/dist/core/layout.d.ts +26 -0
  55. package/dist/core/layout.js +122 -0
  56. package/dist/core/layout.js.map +1 -0
  57. package/dist/core/lessons/analyze.d.ts +40 -0
  58. package/dist/core/lessons/analyze.js +107 -0
  59. package/dist/core/lessons/analyze.js.map +1 -0
  60. package/dist/core/lessons/audit.d.ts +51 -0
  61. package/dist/core/lessons/audit.js +57 -0
  62. package/dist/core/lessons/audit.js.map +1 -0
  63. package/dist/core/lessons/compaction.d.ts +5 -0
  64. package/dist/core/lessons/compaction.js +58 -0
  65. package/dist/core/lessons/compaction.js.map +1 -0
  66. package/dist/core/lessons/config.d.ts +15 -0
  67. package/dist/core/lessons/config.js +31 -0
  68. package/dist/core/lessons/config.js.map +1 -0
  69. package/dist/core/lessons/existing.d.ts +4 -0
  70. package/dist/core/lessons/existing.js +22 -0
  71. package/dist/core/lessons/existing.js.map +1 -0
  72. package/dist/core/lessons/extract.d.ts +21 -0
  73. package/dist/core/lessons/extract.js +106 -0
  74. package/dist/core/lessons/extract.js.map +1 -0
  75. package/dist/core/lessons/frontmatter.d.ts +29 -0
  76. package/dist/core/lessons/frontmatter.js +87 -0
  77. package/dist/core/lessons/frontmatter.js.map +1 -0
  78. package/dist/core/lessons/graph.d.ts +256 -0
  79. package/dist/core/lessons/graph.js +86 -0
  80. package/dist/core/lessons/graph.js.map +1 -0
  81. package/dist/core/lessons/jev.d.ts +36 -0
  82. package/dist/core/lessons/jev.js +100 -0
  83. package/dist/core/lessons/jev.js.map +1 -0
  84. package/dist/core/lessons/layout.d.ts +2 -0
  85. package/dist/core/lessons/layout.js +56 -0
  86. package/dist/core/lessons/layout.js.map +1 -0
  87. package/dist/core/lessons/learn.d.ts +38 -0
  88. package/dist/core/lessons/learn.js +190 -0
  89. package/dist/core/lessons/learn.js.map +1 -0
  90. package/dist/core/lessons/llm.d.ts +42 -0
  91. package/dist/core/lessons/llm.js +106 -0
  92. package/dist/core/lessons/llm.js.map +1 -0
  93. package/dist/core/lessons/memo.d.ts +19 -0
  94. package/dist/core/lessons/memo.js +170 -0
  95. package/dist/core/lessons/memo.js.map +1 -0
  96. package/dist/core/lessons/merge.d.ts +39 -0
  97. package/dist/core/lessons/merge.js +79 -0
  98. package/dist/core/lessons/merge.js.map +1 -0
  99. package/dist/core/lessons/queue.d.ts +6 -0
  100. package/dist/core/lessons/queue.js +52 -0
  101. package/dist/core/lessons/queue.js.map +1 -0
  102. package/dist/core/lessons/recent.d.ts +21 -0
  103. package/dist/core/lessons/recent.js +111 -0
  104. package/dist/core/lessons/recent.js.map +1 -0
  105. package/dist/core/lessons/schema.d.ts +26 -0
  106. package/dist/core/lessons/schema.js +64 -0
  107. package/dist/core/lessons/schema.js.map +1 -0
  108. package/dist/core/lessons/select.d.ts +19 -0
  109. package/dist/core/lessons/select.js +55 -0
  110. package/dist/core/lessons/select.js.map +1 -0
  111. package/dist/core/lessons/sidecar.d.ts +23 -0
  112. package/dist/core/lessons/sidecar.js +171 -0
  113. package/dist/core/lessons/sidecar.js.map +1 -0
  114. package/dist/core/lessons/storage.d.ts +21 -0
  115. package/dist/core/lessons/storage.js +65 -0
  116. package/dist/core/lessons/storage.js.map +1 -0
  117. package/dist/core/lessons/usage.d.ts +31 -0
  118. package/dist/core/lessons/usage.js +91 -0
  119. package/dist/core/lessons/usage.js.map +1 -0
  120. package/dist/core/lessons/write.d.ts +18 -0
  121. package/dist/core/lessons/write.js +131 -0
  122. package/dist/core/lessons/write.js.map +1 -0
  123. package/dist/core/library/build.d.ts +22 -0
  124. package/dist/core/library/build.js +163 -0
  125. package/dist/core/library/build.js.map +1 -0
  126. package/dist/core/library/group.d.ts +14 -0
  127. package/dist/core/library/group.js +129 -0
  128. package/dist/core/library/group.js.map +1 -0
  129. package/dist/core/library/index-file.d.ts +7 -0
  130. package/dist/core/library/index-file.js +250 -0
  131. package/dist/core/library/index-file.js.map +1 -0
  132. package/dist/core/library/schema.d.ts +78 -0
  133. package/dist/core/library/schema.js +37 -0
  134. package/dist/core/library/schema.js.map +1 -0
  135. package/dist/core/library/skills.d.ts +26 -0
  136. package/dist/core/library/skills.js +168 -0
  137. package/dist/core/library/skills.js.map +1 -0
  138. package/dist/core/library/sync.d.ts +48 -0
  139. package/dist/core/library/sync.js +271 -0
  140. package/dist/core/library/sync.js.map +1 -0
  141. package/dist/core/lock.d.ts +6 -0
  142. package/dist/core/lock.js +56 -0
  143. package/dist/core/lock.js.map +1 -0
  144. package/dist/core/pull.d.ts +23 -0
  145. package/dist/core/pull.js +147 -0
  146. package/dist/core/pull.js.map +1 -0
  147. package/dist/core/push.d.ts +29 -0
  148. package/dist/core/push.js +170 -0
  149. package/dist/core/push.js.map +1 -0
  150. package/dist/core/remote-layout.d.ts +67 -0
  151. package/dist/core/remote-layout.js +94 -0
  152. package/dist/core/remote-layout.js.map +1 -0
  153. package/dist/core/sessions.d.ts +29 -0
  154. package/dist/core/sessions.js +96 -0
  155. package/dist/core/sessions.js.map +1 -0
  156. package/dist/core/state.d.ts +94 -0
  157. package/dist/core/state.js +62 -0
  158. package/dist/core/state.js.map +1 -0
  159. package/dist/core/sync.d.ts +48 -0
  160. package/dist/core/sync.js +140 -0
  161. package/dist/core/sync.js.map +1 -0
  162. package/dist/core/time-window.d.ts +8 -0
  163. package/dist/core/time-window.js +36 -0
  164. package/dist/core/time-window.js.map +1 -0
  165. package/dist/daemon.d.ts +73 -0
  166. package/dist/daemon.js +272 -0
  167. package/dist/daemon.js.map +1 -0
  168. package/dist/format.d.ts +4 -0
  169. package/dist/format.js +28 -0
  170. package/dist/format.js.map +1 -0
  171. package/dist/main.d.ts +2 -0
  172. package/dist/main.js +9 -0
  173. package/dist/main.js.map +1 -0
  174. package/dist/paths.d.ts +19 -0
  175. package/dist/paths.js +19 -0
  176. package/dist/paths.js.map +1 -0
  177. package/dist/progress-console.d.ts +12 -0
  178. package/dist/progress-console.js +42 -0
  179. package/dist/progress-console.js.map +1 -0
  180. package/dist/progress.d.ts +7 -0
  181. package/dist/progress.js +6 -0
  182. package/dist/progress.js.map +1 -0
  183. package/dist/remote/create-remote.d.ts +20 -0
  184. package/dist/remote/create-remote.js +86 -0
  185. package/dist/remote/create-remote.js.map +1 -0
  186. package/dist/remote/prompt.d.ts +17 -0
  187. package/dist/remote/prompt.js +111 -0
  188. package/dist/remote/prompt.js.map +1 -0
  189. package/dist/remote/s3-sigv4.d.ts +28 -0
  190. package/dist/remote/s3-sigv4.js +100 -0
  191. package/dist/remote/s3-sigv4.js.map +1 -0
  192. package/dist/remote/s3-store.d.ts +75 -0
  193. package/dist/remote/s3-store.js +438 -0
  194. package/dist/remote/s3-store.js.map +1 -0
  195. package/dist/remote/s3-vendors.d.ts +31 -0
  196. package/dist/remote/s3-vendors.js +47 -0
  197. package/dist/remote/s3-vendors.js.map +1 -0
  198. package/dist/remote/store.d.ts +44 -0
  199. package/dist/remote/store.js +2 -0
  200. package/dist/remote/store.js.map +1 -0
  201. package/dist/remote/webdav-store.d.ts +41 -0
  202. package/dist/remote/webdav-store.js +285 -0
  203. package/dist/remote/webdav-store.js.map +1 -0
  204. package/dist/sqlite.d.ts +3 -0
  205. package/dist/sqlite.js +12 -0
  206. package/dist/sqlite.js.map +1 -0
  207. package/dist/status.d.ts +82 -0
  208. package/dist/status.js +162 -0
  209. package/dist/status.js.map +1 -0
  210. package/package.json +43 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 1o57
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, subject 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.
package/README.md CHANGED
@@ -1,3 +1,203 @@
1
- # Temporary Holding Version
1
+ # trove
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ AI 会话资产归档工具:把本机 Claude Code / Codex / OpenCode / 豆包的会话**无损**归档到 WebDAV 或 S3 兼容对象存储,并支持多设备增量同步。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm i -g trove-cli # 提供 trove 命令
9
+ npx trove-cli --help # 免安装试用
10
+ ```
11
+
12
+ npm 上 unscoped 的 `trove` 是别人的包(仍在发布中),所以本项目发布为 `trove-cli`,命令行还是 `trove`。
13
+
14
+ ## 用法
15
+
16
+ ```bash
17
+ # 首次配置(设备名、远端后端、凭证)
18
+ # 默认会拉起 daemon 并立即跑首轮同步;只写配置用 --no-daemon
19
+ # 交互式:trove init 会先问远端类型(WebDAV / S3),再按类型问对应字段
20
+ trove init
21
+ trove init --url https://dav.example.com/ --username u --password p
22
+ trove init --url https://dav.example.com/ --username u --password p --no-daemon
23
+
24
+ # S3 兼容对象存储(阿里云 OSS / 腾讯云 COS)
25
+ trove init --type s3 --provider aliyun --region cn-hangzhou \
26
+ --bucket my-bucket --access-key-id AKID --secret-access-key SK --no-daemon
27
+ trove init --type s3 --provider tencent --region ap-guangzhou \
28
+ --bucket my-bucket-1250000000 --access-key-id AKID --secret-access-key SK --no-daemon
29
+ # 私有 S3 兼容部署(MinIO 等):自定义 endpoint + path-style 寻址
30
+ trove init --type s3 --endpoint http://127.0.0.1:9000 --force-path-style \
31
+ --region us-east-1 --bucket my-bucket --access-key-id AKID --secret-access-key SK --no-daemon
32
+
33
+ # 同步:默认全量;日常建议限定时间窗
34
+ trove sync --since 7d
35
+ trove sync --since all # 首次全量归档
36
+ trove sync --pull-only # 只拉取其他设备
37
+ trove sync --push-only # 只推送本机
38
+ trove sync --no-raw # 只同步 library(raw 只归档到本地)
39
+ trove sync --raw # 强制同步 raw(覆盖配置)
40
+ trove config set sync.raw false # 默认只同步 library,可被 --raw 覆盖
41
+ trove sync --quiet # 不显示逐项进度
42
+
43
+ # 概览与检索
44
+ trove status # 本地 / 远端 / daemon / 待同步
45
+ trove sessions # 列出 library 会话(最近 50 个)
46
+ trove sessions --grep PATH # 在标题/摘要/目录/模型/正文里搜
47
+ trove sessions --harness opencode --json
48
+ trove skills # 汇总后的技能(最新版本)
49
+ trove skills show review # 某技能的全部版本与来源
50
+ trove library reindex # 从设备分片重建统一索引
51
+ trove usage # trove 自身 LLM 消耗的 token 用量
52
+ trove usage --since 7d --json # 限定时间窗,机器可读
53
+
54
+ # 常驻服务:间隔/时间窗默认取 config.daemon,显式参数优先
55
+ trove config set daemon.intervalSeconds 300
56
+ trove config set daemon.since 7d
57
+ trove config set daemon.startOnInit false # init 不再自动启动
58
+ trove daemon start # 等价于每 300s、时间窗 7d
59
+ trove daemon start --interval 60 --since 24h
60
+ trove daemon restart # 停止后按同样参数重新拉起
61
+ trove daemon status
62
+ trove daemon stop
63
+ ```
64
+
65
+ `trove status` 输出四段:
66
+
67
+ - **本地**:各工具已归档的会话数与体积
68
+ - **远端**:5 秒超时探测,显示可达性、设备数与各设备上次在线时间
69
+ - **daemon**:运行状态、间隔、时间窗、上次/下次同步与最近一轮结果
70
+ - **待同步**:raw 层按工具分会话/文件数;library 按「会话 / 技能 / 索引」分类计数(只给计数,不逐条列)。
71
+ 会话数按 `sessionKey` 去重,同一会话的多个文件算 1 个;Claude 子代理文件没有会话归并规则,会被算作独立会话。
72
+ `config.sync.raw=false` 时只显示「raw 同步已关闭」与 library 待同步。
73
+
74
+ ## 支持的数据源
75
+
76
+ | 工具 | 位置 | 归档内容 |
77
+ |---|---|---|
78
+ | Claude Code | `~/.claude/projects/**/*.jsonl`、`~/.claude/transcripts/**/*.jsonl` | 两套互不重叠的会话原始 JSONL + skills/plans 等小资产 |
79
+ | Codex | `~/.codex/sessions/**/*.jsonl`、`archived_sessions/**` | rollout 原始 JSONL + skills |
80
+ | OpenCode | `~/.local/share/opencode/opencode.db` | `session_v2`/`session_message` 与旧表 `session`/`message`/`part` 的并集,逐会话导出确定性 JSONL(两套序列化都保留,保留全部列) |
81
+ | 豆包(办公版) | `~/Library/Application Support/DoubaoWork/.../agent_mode/workspace` | `.sessions/<id>` 的 board/memory/agents 轨迹 + `conversation/<id>` |
82
+
83
+ 凭证类文件(`settings.json`、`auth.json`、`config.toml`、`*.key` 等)在适配器层硬排除。
84
+
85
+ ## 同步模型(Beacon poll 骨架)
86
+
87
+ - **游标**:`{size, mtimeMs, prefixHash, generation}`。size+mtime 未变直接跳过;文件增长只搬新增字节;size 回退/同尺寸改写/前缀哈希不符 → 换世代全量重建。
88
+ - **无损**:本地 vault 是源文件的原始字节副本;OpenCode 是数据库全字段导出(新旧表并集,实测 782 会话 / 153,406 行 / 990MB)。WebDAV 远端按 4MiB 分块(大请求不稳),S3 一个文件一个对象,拉取时都校验 sha256。
89
+ - **增量**:WebDAV 远端只传新增块,块路径扁平到 `<device>/chunks/<harness>/`,避免每文件多次 MKCOL;S3 按文件覆盖写,大文件走 Multipart Upload。清单并入设备索引(`<device>/index.json`)。实测二次全量扫描 2450 项仅 14s、上传 2 项。
90
+ - **只增不删**:远端和本地 vault 默认不删除;源文件被删后归档仍保留。唯一的删除入口是 `trove clean`(显式确认,只删索引不再引用且超过 24 小时的旧对象)。
91
+ - **时间窗**:`--since 7d|24h|2026-10-01|all` 按会话最近变更时间过滤,避免首次全量过大。
92
+ - **进度**:collect / pull / push 三阶段,TTY 单行刷新,管道按秒节流。
93
+ - **daemon**:单实例 pid 锁、跨进程 sync 锁、SIGTERM 协作中止、状态写 `~/.trove/daemon.status.json`、日志轮转。间隔/时间窗/init 是否自动启动由 `config.daemon.{intervalSeconds,since,startOnInit}` 决定(默认 `300 / 7d / true`),`daemon start --interval/--since` 显式参数优先于配置。`daemon restart` 先优雅停止再按同样参数拉起;旧进程没停干净时不会起第二个实例。
94
+
95
+ ## library(个人资产层)
96
+
97
+ `raw/` 是设备事实,`library/` 是合并后的个人资产,随同步一起走:
98
+
99
+ ```
100
+ ~/.trove/vault/
101
+ ├── raw/<device>/<harness>/… 原始字节(设备 × 工具)
102
+ └── library/ 个人资产(同步)
103
+ ├── index.json 统一索引(派生物,可重建)
104
+ ├── index/<device>.json 每设备索引分片(只由本设备写)
105
+ ├── sessions/<harness>/<session>/<device>.jsonl
106
+ ├── skills/<name>/versions/<hash>/… 内容寻址,保留各版本
107
+ ├── skills/<name>/meta/<device>.json 来源与版本
108
+ └── lessons/ 学习沉淀(同步)
109
+ ```
110
+
111
+ - **归组**:按 `(harness, sessionId)` 归并同一会话的多份 raw——Claude 主文件 + `subagents/*.jsonl`、豆包 `.sessions` + `conversation`。
112
+ - **冲突**:每台设备只写自己的文件和分片,远端不存在并发覆盖。同一会话内容不同时,索引选 canonical(事件超集;豆包按事件 id 求并集),其余进 `variants`,文件永远保留。
113
+ - **索引**:`index/<device>.json` 是权威,`index.json` 由本地合并生成;索引冲突只需重建,不会丢数据。
114
+ - **技能**:按目录内容 hash 去重,来源由 `SKILL.md` frontmatter 与 `.git/config` origin 判定(`self/github/skillsh/unknown`);最新版本按 semver → updatedAt → hash 选择,历史版本保留。
115
+ - **lessons**:按主题沉淀经验,内容用 `(harness, session, seq)` 引用会话。与 sessions/skills 一起同步;没有设备分片,多设备各自 learn 时同路径文件按最后写入覆盖,拉取只在本地缺失时恢复,不会覆盖本机刚生成的内容。
116
+
117
+ 事件 schema 仍是 `session/message/reasoning/tool_call/tool_result/meta`,带 `raw:{path,line}` 回指原始文件。
118
+
119
+ ## 同步模型补充
120
+
121
+ - library **始终同步**(约 500 MB):推送本设备的会话文件、技能、lessons 与索引分片,拉取其他设备的分片与文件。
122
+ - raw 是否上传/拉取按 **命令行 > `config.sync.raw` > 默认 true** 决定;关闭时只同步 library,raw 只归档到本地。
123
+ - 配置示例:`trove config set sync.raw false`;`trove sync --raw` 可临时覆盖。
124
+ - 索引分片按设备隔离,拉取时落盘后本地重建统一索引。
125
+
126
+ ## LLM 用量统计
127
+
128
+ trove 自己调用 LLM 的场景(手动 `trove learn`、daemon 每轮同步后的经验提取)会把每次调用的 token 用量追加到 `~/.trove/usage.jsonl`:时间、任务(如 `learn:summarize`)、模型、输入/输出/合计 tokens。`trove usage` 按任务与模型汇总,`--since` 限定时间窗,`--json` 输出机器可读结果。
129
+
130
+ 该台账只统计 trove 自身调用 LLM 的消耗,不包含被归档会话里的 usage 字段;Jev 打分走 TypeSafe API,响应不含 token 用量,因此不计入。台账只留本机,不参与同步。
131
+
132
+ ## 本地布局
133
+
134
+ ```
135
+ ~/.trove/
136
+ ├── config.json 设备身份与远端凭证(600)
137
+ ├── state.json 游标 + 推送/拉取水位 + library 记录(原子写)
138
+ ├── usage.jsonl trove 自身 LLM 调用的 token 用量(只追加,不同步)
139
+ ├── daemon.pid / daemon.status.json
140
+ ├── vault/raw/<device>/<harness>/... 原始字节归档
141
+ ├── vault/library/ 个人资产(会话/技能/索引/lessons)
142
+ ├── tmp/ OpenCode 导出与 library 重建临时文件
143
+ └── logs/daemon.log
144
+ ```
145
+
146
+ 远端布局(索引与设备元信息一致;raw 文件按后端区分):
147
+
148
+ ```
149
+ raw/<device>/index.json 索引(含每文件块清单与哈希)
150
+ raw/<device>/meta.json 设备名 / lastSeen
151
+ raw/<device>/chunks/<harness>/<key>.<gen>.<off>-<len>.bin WebDAV:不可变块
152
+ raw/<device>/files/<harness>/<relPath> S3:完整文件(控制台可直接浏览)
153
+ library/index/<device>.json 每设备索引分片
154
+ library/sessions/…、library/skills/… 个人资产
155
+ ```
156
+
157
+ WebDAV 服务端没有原生分片上传,只能按 4 MiB 块存;对象存储一个会话文件就是一个对象,
158
+ 路径即原始相对路径,桶里能直接打开会话原文。索引项的 `layout` 字段区分两种布局,老索引缺省按分块解释。
159
+
160
+ ## 远端后端
161
+
162
+ | 后端 | 配置方式 | 说明 |
163
+ |---|---|---|
164
+ | WebDAV | `trove init --url … --username … --password …` | 默认后端,兼容自建与第三方网盘 |
165
+ | 阿里云 OSS | `--type s3 --provider aliyun --region cn-hangzhou --bucket …` | S3 兼容,走 virtual-hosted 寻址 |
166
+ | 腾讯云 COS | `--type s3 --provider tencent --region ap-guangzhou --bucket …` | S3 兼容;Bucket 名需带 `-<appid>` 后缀 |
167
+
168
+ 交互式配置(`trove init` / `trove config`)会先问远端类型,再按类型问对应字段 —— 选 S3 时依次问厂商、地域、Bucket、AccessKey,不选就沿用现有配置。
169
+
170
+ **换后端与历史数据迁移**:索引、设备元信息与 library 布局在两种后端下一致,raw 文件布局按后端区分。`state.json` 里的 `remoteId` 记录了推送水位属于哪个远端;检测到后端变了,旧水位自动作废,历史数据会整份重推(不会"以为推过了"而静默丢数据)。首次切到新后端时跑一次 `trove sync --since all` 即可。S3 切到单对象布局后,旧的 `chunks/` 对象不会自动删除,需要时手工清理。
171
+
172
+ **清理远端旧对象**:`trove clean` 扫描当前远端,删除不再被任何设备索引引用、且最后修改时间早于 24 小时的对象(旧布局分块、已无引用的会话/技能文件)。
173
+
174
+ ```bash
175
+ trove clean --dry-run # 只列出
176
+ trove clean # 列出后交互确认,默认否
177
+ trove clean --yes # 直接删除
178
+ ```
179
+
180
+ 安全边界:设备索引缺失时整个设备跳过;library 任一分片读取失败则整个 library 跳过;`lessons/**`、`merged.*.jsonl`、技能 `meta/*.json` 一律保留;24 小时内上传的对象、时间缺失的对象不删。切换远端后的第一次 `trove sync` 结束时(TTY 环境)会询问一次是否清理,默认不清理;daemon 永不触发清理。
181
+
182
+ ## 开发
183
+
184
+ ```bash
185
+ pnpm test # 单元 + 真实 WebDAV 服务集成 + 真实本机会话扫描
186
+ pnpm typecheck
187
+ pnpm build
188
+ ```
189
+
190
+ ## 发版
191
+
192
+ 合并到 `main` 即自动发版(`.github/workflows/release.yml`):
193
+
194
+ 1. PR 里改好 `package.json` 的 `version`,合并进 `main`
195
+ 2. Actions 装依赖 → 构建 → 跑测试 → 发布到 npm → 打 tag `v<version>` 并生成 GitHub Release(附带 npm 包 tarball)
196
+
197
+ 一次性配置(npm 侧):npmjs.com → 包的设置 → Trusted Publisher → GitHub Actions,填 `blankPen` / `trove` / `release.yml`,并允许 `npm publish`。之后 CI 用 OIDC 直接发布,不需要长期 token。
198
+
199
+ 仍用 token 的话:仓库 Settings → Secrets and variables → Actions → 新建 `NPM_TOKEN`(granular access token,Read and write(publish and stage)+ 勾选 Bypass 2FA)。只用 stage only 的 token 只会把版本暂存,需要人工 `npm stage approve <stage-id> --otp <code>` 才会真正上线。
200
+
201
+ 重复触发是幂等的:npm 上已有该版本就跳过发布,GitHub Release 已存在就跳过建 Release,两样都做完就整体跳过。
202
+
203
+ npm 计划 2027 年 1 月起停用 bypass 2FA token 的直接发布,届时改走 trusted publishing(OIDC)即可,工作流不用动。
@@ -0,0 +1,31 @@
1
+ /** 一个文件源(某个 agent 的原始落盘目录)。 */
2
+ export interface FileSourceConfig {
3
+ id: string;
4
+ displayName: string;
5
+ root: string;
6
+ /** 白名单 glob,相对 root;命中才归档(设计原则 2) */
7
+ patterns: string[];
8
+ /** 硬排除 glob,相对 root;优先于白名单 */
9
+ exclusions: string[];
10
+ /** 整棵跳过的目录 glob(性能剪枝,如 plugins、plugins/**) */
11
+ pruneDirs?: string[];
12
+ /** 会话 id 推导;默认一个文件就是一个会话 */
13
+ sessionKeyOf?: (relPath: string) => string;
14
+ /** 哪些文件算"会话"(用于计数);默认等于 patterns */
15
+ sessionPatterns?: string[];
16
+ }
17
+ export interface DiscoveredFile {
18
+ /** root 内相对路径,统一 '/' */
19
+ relPath: string;
20
+ absPath: string;
21
+ sessionKey: string;
22
+ size: number;
23
+ mtimeMs: number;
24
+ /** 所属会话的最近变更时间(同会话多文件取最大) */
25
+ sessionMtimeMs: number;
26
+ }
27
+ /**
28
+ * 发现一个源里应归档的文件。只要目录遍历 + 路径匹配,不读文件内容,
29
+ * 源文件零改写。白名单 ∩ 排除,排除优先。
30
+ */
31
+ export declare function discoverFiles(config: FileSourceConfig): Promise<DiscoveredFile[]>;
@@ -0,0 +1,97 @@
1
+ import { readdir, stat } from 'node:fs/promises';
2
+ import { join, relative, sep } from 'node:path';
3
+ import { minimatch } from 'minimatch';
4
+ function toPosix(p) {
5
+ return p.split(sep).join('/');
6
+ }
7
+ function matchesAny(path, globs) {
8
+ return globs.some((g) => minimatch(path, g, { dot: true }));
9
+ }
10
+ function isPruned(relDir, pruneDirs) {
11
+ if (!relDir)
12
+ return false;
13
+ return pruneDirs.some((p) => minimatch(relDir, p, { dot: true }) || minimatch(relDir, `${p}/**`, { dot: true }));
14
+ }
15
+ /** 递归收集目录下所有常规文件与"指向文件"的符号链接;不读内容。 */
16
+ async function walk(root, pruneDirs) {
17
+ const out = [];
18
+ const stack = [''];
19
+ while (stack.length) {
20
+ const relDir = stack.pop();
21
+ const absDir = relDir ? join(root, relDir) : root;
22
+ let entries;
23
+ try {
24
+ entries = await readdir(absDir, { withFileTypes: true });
25
+ }
26
+ catch (err) {
27
+ if (err.code === 'ENOENT')
28
+ continue;
29
+ throw err;
30
+ }
31
+ for (const e of entries) {
32
+ const relPath = toPosix(relDir ? `${relDir}/${e.name}` : e.name);
33
+ const absPath = join(absDir, e.name);
34
+ if (e.isDirectory()) {
35
+ if (!isPruned(relPath, pruneDirs))
36
+ stack.push(relPath);
37
+ continue;
38
+ }
39
+ if (e.isSymbolicLink()) {
40
+ try {
41
+ const st = await stat(absPath);
42
+ if (!st.isFile())
43
+ continue;
44
+ }
45
+ catch {
46
+ continue; // 悬空链接
47
+ }
48
+ }
49
+ else if (!e.isFile()) {
50
+ continue;
51
+ }
52
+ out.push({ relPath, absPath });
53
+ }
54
+ }
55
+ return out;
56
+ }
57
+ /**
58
+ * 发现一个源里应归档的文件。只要目录遍历 + 路径匹配,不读文件内容,
59
+ * 源文件零改写。白名单 ∩ 排除,排除优先。
60
+ */
61
+ export async function discoverFiles(config) {
62
+ const pruneDirs = config.pruneDirs ?? [];
63
+ const found = await walk(config.root, pruneDirs);
64
+ const out = [];
65
+ for (const f of found) {
66
+ if (matchesAny(f.relPath, config.exclusions))
67
+ continue;
68
+ if (!matchesAny(f.relPath, config.patterns))
69
+ continue;
70
+ let st;
71
+ try {
72
+ st = await stat(f.absPath);
73
+ }
74
+ catch (err) {
75
+ if (err.code === 'ENOENT')
76
+ continue;
77
+ throw err;
78
+ }
79
+ const sessionKey = config.sessionKeyOf ? config.sessionKeyOf(f.relPath) : f.relPath;
80
+ out.push({
81
+ ...f,
82
+ sessionKey,
83
+ size: st.size,
84
+ mtimeMs: Math.round(st.mtimeMs),
85
+ sessionMtimeMs: Math.round(st.mtimeMs),
86
+ });
87
+ }
88
+ // 同一会话多文件:会话时间取最大,供 --since 判定(目录里旧附件不因自身 mtime 被漏掉)
89
+ const sessionMax = new Map();
90
+ for (const f of out) {
91
+ sessionMax.set(f.sessionKey, Math.max(sessionMax.get(f.sessionKey) ?? 0, f.mtimeMs));
92
+ }
93
+ for (const f of out)
94
+ f.sessionMtimeMs = sessionMax.get(f.sessionKey);
95
+ return out;
96
+ }
97
+ //# sourceMappingURL=file-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-source.js","sourceRoot":"","sources":["../../src/adapters/file-source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { FileSourceConfig } from './file-source.js';
2
+ export type HarnessId = 'claude-code' | 'codex' | 'opencode' | 'doubao';
3
+ export interface FileAdapter {
4
+ kind: 'files';
5
+ id: Exclude<HarnessId, 'opencode'>;
6
+ displayName: string;
7
+ source: FileSourceConfig;
8
+ }
9
+ export interface OpenCodeAdapter {
10
+ kind: 'opencode';
11
+ id: 'opencode';
12
+ displayName: string;
13
+ dbPath: string;
14
+ }
15
+ export type Adapter = FileAdapter | OpenCodeAdapter;
16
+ export interface AdapterPaths {
17
+ home?: string;
18
+ claudeRoot?: string;
19
+ codexRoot?: string;
20
+ opencodeDb?: string;
21
+ doubaoRoot?: string;
22
+ }
23
+ export declare function claudeCodeAdapter(root: string): FileAdapter;
24
+ export declare function codexAdapter(root: string): FileAdapter;
25
+ export declare function doubaoAdapter(root: string): FileAdapter;
26
+ export declare function opencodeAdapter(dbPath: string): OpenCodeAdapter;
27
+ /** 这个归档项是否属于"会话"(skills/plans 等资产不算会话)。 */
28
+ export declare function isSessionPath(adapter: Adapter, relPath: string): boolean;
29
+ /**
30
+ * 按本机路径生成四个 adapter;调用方按 config.sources 过滤启用项。
31
+ * 允许用环境变量覆盖根目录,便于测试与迁移:
32
+ * TROVE_CLAUDE_ROOT / TROVE_CODEX_ROOT / TROVE_OPENCODE_DB / TROVE_DOUBAO_ROOT
33
+ */
34
+ export declare function createAdapters(opts?: AdapterPaths, env?: NodeJS.ProcessEnv): Adapter[];
@@ -0,0 +1,146 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import { minimatch } from 'minimatch';
4
+ /** 豆包 agent mode 工作区(办公版),会话内容在 .sessions/<id> 与 conversation/<id>。 */
5
+ function defaultDoubaoRoot(home) {
6
+ return join(home, 'Library/Application Support/DoubaoWork/Default/.doubaowork/agent_mode/workspace');
7
+ }
8
+ export function claudeCodeAdapter(root) {
9
+ return {
10
+ kind: 'files',
11
+ id: 'claude-code',
12
+ displayName: 'Claude Code',
13
+ source: {
14
+ id: 'claude-code',
15
+ displayName: 'Claude Code',
16
+ root,
17
+ // 两套互不重叠的会话存储:projects/*.jsonl 与 transcripts/*.jsonl
18
+ patterns: [
19
+ 'projects/**/*.jsonl',
20
+ 'transcripts/**/*.jsonl',
21
+ 'history.jsonl',
22
+ 'skills/**',
23
+ 'CLAUDE.md',
24
+ 'RTK.md',
25
+ 'plans/**',
26
+ 'tasks/**',
27
+ // file-history 是编辑器快照(1797 个小文件),不是会话正文;默认不搬
28
+ ],
29
+ exclusions: ['.credentials.json', 'settings.json', 'settings.local.json'],
30
+ sessionPatterns: ['projects/**/*.jsonl', 'transcripts/**/*.jsonl'],
31
+ // 大而无价值的目录:整棵剪掉,不参与遍历
32
+ pruneDirs: [
33
+ 'plugins',
34
+ 'stats',
35
+ 'stats2',
36
+ 'stats3',
37
+ 'stats4',
38
+ 'bash-snapshots',
39
+ 'downloads',
40
+ 'telemetry',
41
+ 'paste-cache',
42
+ 'backups',
43
+ 'shell-snapshots',
44
+ 'sessions',
45
+ 'cache',
46
+ 'jobs',
47
+ 'bin',
48
+ 'ide',
49
+ 'debug',
50
+ 'session-models',
51
+ 'token-offsets',
52
+ 'hooks',
53
+ 'daemon',
54
+ ],
55
+ // 会话 id = 文件名 UUID(与 group.claudeSessionOf 同口径),供 learn 入队与统一索引对齐
56
+ sessionKeyOf: (rel) => {
57
+ if (rel.startsWith('transcripts/') && rel.endsWith('.jsonl')) {
58
+ return rel.slice('transcripts/'.length).replace(/\.jsonl$/, '');
59
+ }
60
+ if (rel.startsWith('projects/') && rel.endsWith('.jsonl')) {
61
+ const parts = rel.split('/');
62
+ const sub = parts.indexOf('subagents');
63
+ return (sub > 0 ? parts[sub - 1] : parts[parts.length - 1]).replace(/\.jsonl$/, '');
64
+ }
65
+ return rel;
66
+ },
67
+ },
68
+ };
69
+ }
70
+ export function codexAdapter(root) {
71
+ return {
72
+ kind: 'files',
73
+ id: 'codex',
74
+ displayName: 'Codex',
75
+ source: {
76
+ id: 'codex',
77
+ displayName: 'Codex',
78
+ root,
79
+ patterns: [
80
+ 'sessions/**/*.jsonl',
81
+ 'archived_sessions/**/*.jsonl',
82
+ 'history.jsonl',
83
+ 'skills/**',
84
+ ],
85
+ // auth.json / config.toml 可能含 API key;SQLite 是运行态缓存,均不归档
86
+ exclusions: ['auth.json', 'config.toml', 'config.toml.bak', '*.sqlite', '*.sqlite-wal', '*.sqlite-shm'],
87
+ sessionPatterns: ['sessions/**/*.jsonl', 'archived_sessions/**/*.jsonl'],
88
+ pruneDirs: ['.tmp', 'tmp', 'log', 'logs', 'shell_snapshots', 'thread-writer-locks'],
89
+ // shortcut: 未配 sessionKeyOf,codex 会话 id 是内容派生(session_meta.payload.id)、文件名推不出,
90
+ // 因此 changedSessions 会落 relPath 形 key 并在 learn 里 fail(failed.json 优雅降级);待有 codex 数据时按 group.codexSessionOf 读内容对齐
91
+ },
92
+ };
93
+ }
94
+ export function doubaoAdapter(root) {
95
+ return {
96
+ kind: 'files',
97
+ id: 'doubao',
98
+ displayName: '豆包',
99
+ source: {
100
+ id: 'doubao',
101
+ displayName: '豆包',
102
+ root,
103
+ // 只收会话内容:board/memory/assignment/trajectory/socket 与后台任务输出。
104
+ // scratch、sandbox、skills 是生成物/技能库,不属于会话正文(体积也占九成)。
105
+ patterns: [
106
+ '.sessions/*/board.md',
107
+ '.sessions/*/memory/**',
108
+ '.sessions/*/agents/*/system/**',
109
+ '.sessions/*/agents/*/socket/**',
110
+ 'conversation/**',
111
+ ],
112
+ exclusions: ['**/*.key', '**/*.pem', '**/.env', '**/auth.json', '**/credentials*'],
113
+ sessionPatterns: ['.sessions/**', 'conversation/**'],
114
+ pruneDirs: ['.skills', '.user_skills', 'experience', '**/scratch', '**/sandbox', '**/node_modules'],
115
+ sessionKeyOf: (rel) => {
116
+ const m = /^\.sessions\/([^/]+)\//.exec(rel) ?? /^conversation\/([^/]+)\//.exec(rel);
117
+ return m ? m[1] : rel;
118
+ },
119
+ },
120
+ };
121
+ }
122
+ export function opencodeAdapter(dbPath) {
123
+ return { kind: 'opencode', id: 'opencode', displayName: 'OpenCode', dbPath };
124
+ }
125
+ /** 这个归档项是否属于"会话"(skills/plans 等资产不算会话)。 */
126
+ export function isSessionPath(adapter, relPath) {
127
+ if (adapter.kind === 'opencode')
128
+ return true;
129
+ const globs = adapter.source.sessionPatterns ?? adapter.source.patterns;
130
+ return globs.some((g) => minimatch(relPath, g, { dot: true }));
131
+ }
132
+ /**
133
+ * 按本机路径生成四个 adapter;调用方按 config.sources 过滤启用项。
134
+ * 允许用环境变量覆盖根目录,便于测试与迁移:
135
+ * TROVE_CLAUDE_ROOT / TROVE_CODEX_ROOT / TROVE_OPENCODE_DB / TROVE_DOUBAO_ROOT
136
+ */
137
+ export function createAdapters(opts = {}, env = process.env) {
138
+ const home = opts.home ?? homedir();
139
+ return [
140
+ claudeCodeAdapter(opts.claudeRoot ?? env.TROVE_CLAUDE_ROOT ?? join(home, '.claude')),
141
+ codexAdapter(opts.codexRoot ?? env.TROVE_CODEX_ROOT ?? join(home, '.codex')),
142
+ opencodeAdapter(opts.opencodeDb ?? env.TROVE_OPENCODE_DB ?? join(home, '.local/share/opencode/opencode.db')),
143
+ doubaoAdapter(opts.doubaoRoot ?? env.TROVE_DOUBAO_ROOT ?? defaultDoubaoRoot(home)),
144
+ ];
145
+ }
146
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * OpenCode 适配器(设计:SQLite 全表对比,Beacon 游标档位 3)。
3
+ *
4
+ * OpenCode 1.x 同时存在两套表:
5
+ * - 新:session_v2 + session_message(消息内容含 parts,data 为完整 JSON)
6
+ * - 旧:session + message + part
7
+ * 新表只覆盖了旧表的一部分,因此导出取并集,任何一行的字段都原样保留。
8
+ * 只读打开源库;归档内容是每会话一份确定性 JSONL。
9
+ */
10
+ export interface OpenCodeSession {
11
+ id: string;
12
+ updatedAt: number;
13
+ }
14
+ /** 导出格式版本:导出逻辑变化时递增,collect 会据此强制重导(旧归档自动换世代)。 */
15
+ export declare const OPENCODE_EXPORT_VERSION = 2;
16
+ export declare class OpenCodeStore {
17
+ private db;
18
+ constructor(dbPath: string);
19
+ close(): void;
20
+ /** 列出全部会话(新表 + 仅存在于旧表的会话),updatedAt 取会话/消息/part 的最大值。 */
21
+ listSessions(cutoff?: number): OpenCodeSession[];
22
+ /**
23
+ * 导出单个会话为确定性 JSONL:每个数据库行一行,保留全部列。
24
+ * 行格式:{"v":1,"table":"session_v2","row":{...}}
25
+ */
26
+ exportSession(sessionId: string): string;
27
+ }
28
+ /** 便捷函数(测试/单次导出)。 */
29
+ export declare function listSessions(dbPath: string, cutoff?: number): OpenCodeSession[];
30
+ export declare function exportSession(dbPath: string, sessionId: string): string;