recallnest 2.2.0 → 2.3.0

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 (249) hide show
  1. package/.env +4 -0
  2. package/CLAUDE.md +1 -1
  3. package/README.md +30 -5
  4. package/README_CN.md +30 -5
  5. package/config.json +55 -0
  6. package/connectors/examples/email-adapter.ts +73 -0
  7. package/connectors/examples/logs-adapter.ts +103 -0
  8. package/connectors/examples/rss-adapter.ts +95 -0
  9. package/logs/daily-recall.error.log +0 -0
  10. package/logs/daily-recall.log +60 -0
  11. package/logs/ingest-2026-04-11.log +110 -0
  12. package/logs/ingest-2026-04-12.log +90 -0
  13. package/logs/ingest-2026-04-13.log +87 -0
  14. package/logs/launchagent-stderr.log +0 -0
  15. package/logs/launchagent-stdout.log +0 -0
  16. package/package.json +1 -1
  17. package/src/cli.ts +30 -0
  18. package/src/connector-types.ts +57 -0
  19. package/src/conversation-importer.ts +25 -1
  20. package/src/data-checkup.ts +31 -1
  21. package/src/doctor.ts +29 -0
  22. package/src/ingest.ts +203 -0
  23. package/src/mcp-server.ts +2 -2
  24. package/src/obsidian-connector.ts +282 -0
  25. package/src/session-schema.ts +2 -2
  26. package/src/source-heartbeat.ts +108 -0
  27. package/.claude-plugin/CLAUDE.md +0 -29
  28. package/.claude-plugin/marketplace.json +0 -18
  29. package/.dockerignore +0 -11
  30. package/.github/workflows/ci.yml +0 -28
  31. package/.mcp.json +0 -9
  32. package/CHANGELOG.md +0 -51
  33. package/DEMO.md +0 -94
  34. package/Dockerfile +0 -20
  35. package/EVAL.md +0 -89
  36. package/FAILURES.md +0 -75
  37. package/OPERATIONS.md +0 -59
  38. package/ROADMAP.md +0 -245
  39. package/SKILL.md +0 -250
  40. package/assets/dashboard.png +0 -0
  41. package/assets/knowledge-graph.png +0 -0
  42. package/assets/recallnest-workbench.png +0 -0
  43. package/assets/screenshots/ui-full.png +0 -0
  44. package/assets/screenshots/ui-overview.png +0 -0
  45. package/assets/screenshots/ui-search.png +0 -0
  46. package/assets/ui/app.js +0 -1031
  47. package/assets/ui/index.html +0 -224
  48. package/assets/ui/styles.css +0 -781
  49. package/assets/wechat-qr.jpg +0 -0
  50. package/bun.lock +0 -337
  51. package/daily-reflections/2026-03-05.md +0 -15
  52. package/daily-reflections/2026-03-06.md +0 -15
  53. package/daily-reflections/2026-03-07.md +0 -7
  54. package/daily-reflections/2026-03-08.md +0 -34
  55. package/daily-reflections/2026-03-09.md +0 -35
  56. package/daily-reflections/2026-03-10.md +0 -36
  57. package/daily-reflections/2026-03-11.md +0 -35
  58. package/daily-reflections/2026-03-12.md +0 -36
  59. package/daily-reflections/2026-03-13.md +0 -35
  60. package/daily-reflections/2026-03-14.md +0 -35
  61. package/daily-reflections/2026-03-15.md +0 -35
  62. package/daily-reflections/2026-03-16.md +0 -41
  63. package/daily-reflections/2026-03-17.md +0 -10
  64. package/daily-reflections/2026-03-18.md +0 -10
  65. package/daily-reflections/2026-03-19.md +0 -10
  66. package/daily-reflections/2026-03-20.md +0 -10
  67. package/daily-reflections/2026-03-21.md +0 -10
  68. package/daily-reflections/2026-03-22.md +0 -10
  69. package/daily-reflections/2026-03-23.md +0 -10
  70. package/daily-reflections/2026-03-24.md +0 -10
  71. package/daily-reflections/2026-03-25.md +0 -32
  72. package/daily-reflections/2026-03-26.md +0 -37
  73. package/daily-reflections/2026-03-27.md +0 -35
  74. package/daily-reflections/2026-03-28.md +0 -35
  75. package/daily-reflections/2026-03-29.md +0 -35
  76. package/daily-reflections/2026-03-30.md +0 -36
  77. package/daily-reflections/2026-03-31.md +0 -35
  78. package/daily-reflections/2026-04-01.md +0 -35
  79. package/daily-reflections/2026-04-02.md +0 -35
  80. package/daily-reflections/2026-04-03.md +0 -37
  81. package/daily-reflections/2026-04-04.md +0 -37
  82. package/daily-reflections/2026-04-05.md +0 -35
  83. package/daily-reflections/2026-04-06.md +0 -36
  84. package/daily-reflections/2026-04-07.md +0 -35
  85. package/daily-reflections/2026-04-08.md +0 -35
  86. package/daily-reflections/2026-04-12.md +0 -35
  87. package/data/alias-map.example.json +0 -22
  88. package/docs/api-reference.md +0 -1291
  89. package/docs/architecture.md +0 -168
  90. package/docs/memory-boundary-contract.md +0 -226
  91. package/docs/memory-categories.md +0 -119
  92. package/docs/pipeline-audit-ultramemory.md +0 -177
  93. package/docs/plans/2026-03-08-query-expansion.md +0 -270
  94. package/docs/plans/2026-03-16-continuity-layer.md +0 -196
  95. package/docs/plans/2026-04-01-recall-boost.md +0 -100
  96. package/docs/plans/mcp-tiering-plan.md +0 -126
  97. package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
  98. package/docs/self-evolution.md +0 -198
  99. package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
  100. package/docs/superpowers/plans/2026-04-11-constructive-retrieval-emotion.md +0 -1195
  101. package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
  102. package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
  103. package/docs/superpowers/specs/2026-04-11-constructive-retrieval-emotion-design.md +0 -398
  104. package/eval/continuity/results-2026-03-16.md +0 -117
  105. package/eval/continuity/results-2026-03-19.md +0 -453
  106. package/eval/continuity/runbook.md +0 -110
  107. package/eval/continuity/seed-cases.md +0 -54
  108. package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
  109. package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
  110. package/eval/reports/2026-03-06-baseline.md +0 -82
  111. package/eval/reports/query-expansion.md +0 -138
  112. package/glama.json +0 -6
  113. package/lm +0 -19
  114. package/plan-philosophy-upgrades.md +0 -363
  115. package/plan-product-upgrades.md +0 -155
  116. package/plan-v1.2-distributable.md +0 -105
  117. package/plan.md +0 -140
  118. package/research-memory-evolution.md +0 -440
  119. package/research-rl-agent-memory.md +0 -330
  120. package/scripts/archive-distilled-junk.ts +0 -116
  121. package/scripts/backfill-anchors.ts +0 -130
  122. package/scripts/backfill-l0l1l2.ts +0 -98
  123. package/scripts/blind-scan.ts +0 -588
  124. package/scripts/cleanup-step1-exact-dups.ts +0 -117
  125. package/scripts/cleanup-step2-template-dups.ts +0 -113
  126. package/scripts/cleanup-step3-stale-content.ts +0 -149
  127. package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
  128. package/scripts/delete-distilled-junk.ts +0 -88
  129. package/scripts/diagnose-vector-search.ts +0 -132
  130. package/scripts/distill-facts.ts +0 -305
  131. package/scripts/health-check.ts +0 -343
  132. package/scripts/incremental-ingest.sh +0 -71
  133. package/scripts/migrate-language.ts +0 -75
  134. package/scripts/mlp-health-check.ts +0 -382
  135. package/scripts/pull-claude-desktop.ts +0 -515
  136. package/scripts/re-ingest-cc.sh +0 -66
  137. package/scripts/rebalance-memory-health.ts +0 -181
  138. package/scripts/run-desktop-ingest.sh +0 -11
  139. package/scripts/start-server.sh +0 -12
  140. package/scripts/verify-emotion-decay.ts +0 -148
  141. package/scripts/viz-dashboard.html +0 -423
  142. package/scripts/viz-export.ts +0 -703
  143. package/src/__tests__/access-tracker-novelty.test.ts +0 -228
  144. package/src/__tests__/activity-counter.test.ts +0 -96
  145. package/src/__tests__/admission-control.test.ts +0 -206
  146. package/src/__tests__/anchor-generator.test.ts +0 -67
  147. package/src/__tests__/auto-consolidation.test.ts +0 -168
  148. package/src/__tests__/auto-recall.test.ts +0 -131
  149. package/src/__tests__/babel-integration.test.ts +0 -139
  150. package/src/__tests__/batch-dedup.test.ts +0 -65
  151. package/src/__tests__/capture-engine.test.ts +0 -1144
  152. package/src/__tests__/capture-heuristic.test.ts +0 -171
  153. package/src/__tests__/cascade-forget.test.ts +0 -210
  154. package/src/__tests__/category-min-scores.test.ts +0 -33
  155. package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
  156. package/src/__tests__/circuit-breaker.test.ts +0 -99
  157. package/src/__tests__/cluster-consolidation.test.ts +0 -712
  158. package/src/__tests__/confidence-metadata.test.ts +0 -125
  159. package/src/__tests__/confidence-tracker.test.ts +0 -249
  160. package/src/__tests__/conflict-advisor.test.ts +0 -206
  161. package/src/__tests__/conflict-escalation.test.ts +0 -149
  162. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  163. package/src/__tests__/conflict-output.test.ts +0 -85
  164. package/src/__tests__/conflict-store.test.ts +0 -374
  165. package/src/__tests__/consolidation-engine.test.ts +0 -181
  166. package/src/__tests__/constructive-retrieval.test.ts +0 -509
  167. package/src/__tests__/context-collapse-renderer.test.ts +0 -214
  168. package/src/__tests__/context-composer-pins.test.ts +0 -64
  169. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  170. package/src/__tests__/context-reconstructor.test.ts +0 -65
  171. package/src/__tests__/context-renderer.test.ts +0 -91
  172. package/src/__tests__/continuity-eval.test.ts +0 -275
  173. package/src/__tests__/continuity-schema.test.ts +0 -97
  174. package/src/__tests__/conversation-importer.test.ts +0 -692
  175. package/src/__tests__/core-recall-eval.test.ts +0 -235
  176. package/src/__tests__/core-summary.test.ts +0 -109
  177. package/src/__tests__/data-checkup.test.ts +0 -225
  178. package/src/__tests__/decay-exemption.test.ts +0 -84
  179. package/src/__tests__/dedup-actions.test.ts +0 -58
  180. package/src/__tests__/dedup-guard.test.ts +0 -39
  181. package/src/__tests__/deterministic-id.test.ts +0 -39
  182. package/src/__tests__/distill-lock.test.ts +0 -176
  183. package/src/__tests__/doctor.test.ts +0 -303
  184. package/src/__tests__/dream-pipeline.test.ts +0 -232
  185. package/src/__tests__/drill-down.test.ts +0 -108
  186. package/src/__tests__/embedder.test.ts +0 -122
  187. package/src/__tests__/emotion-decay.test.ts +0 -46
  188. package/src/__tests__/emotion-detector.test.ts +0 -56
  189. package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
  190. package/src/__tests__/emotion-retrieval.test.ts +0 -56
  191. package/src/__tests__/entity-resolver.test.ts +0 -58
  192. package/src/__tests__/ethics-layer.test.ts +0 -476
  193. package/src/__tests__/event-segmenter.test.ts +0 -153
  194. package/src/__tests__/feature-flags-integration.test.ts +0 -54
  195. package/src/__tests__/frequency-tracker.test.ts +0 -113
  196. package/src/__tests__/graph-export.test.ts +0 -489
  197. package/src/__tests__/hotness-blend.test.ts +0 -76
  198. package/src/__tests__/ingest.test.ts +0 -360
  199. package/src/__tests__/interference-detector.test.ts +0 -203
  200. package/src/__tests__/kg-extractor.test.ts +0 -184
  201. package/src/__tests__/kg-store.test.ts +0 -198
  202. package/src/__tests__/llm-consolidation.test.ts +0 -226
  203. package/src/__tests__/mcp-efficiency.test.ts +0 -288
  204. package/src/__tests__/memory-boundaries.test.ts +0 -237
  205. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -243
  206. package/src/__tests__/memory-evolution.test.ts +0 -271
  207. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  208. package/src/__tests__/memory-lint.test.ts +0 -271
  209. package/src/__tests__/memory-output.test.ts +0 -215
  210. package/src/__tests__/multi-vector.test.ts +0 -242
  211. package/src/__tests__/narrative-architecture.test.ts +0 -456
  212. package/src/__tests__/pending-review.test.ts +0 -77
  213. package/src/__tests__/post-retrieval-filter.test.ts +0 -178
  214. package/src/__tests__/ppr-traversal.test.ts +0 -256
  215. package/src/__tests__/prediction-engine.test.ts +0 -349
  216. package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
  217. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  218. package/src/__tests__/preference-matcher.test.ts +0 -230
  219. package/src/__tests__/preference-slots.test.ts +0 -205
  220. package/src/__tests__/prospective-memory.test.ts +0 -283
  221. package/src/__tests__/query-entity-detector.test.ts +0 -171
  222. package/src/__tests__/query-expander-alias.test.ts +0 -89
  223. package/src/__tests__/query-expander.test.ts +0 -34
  224. package/src/__tests__/recall-governor.test.ts +0 -227
  225. package/src/__tests__/recall-mode.test.ts +0 -64
  226. package/src/__tests__/result-synthesizer.test.ts +0 -198
  227. package/src/__tests__/resume-context.test.ts +0 -4898
  228. package/src/__tests__/retrieval-trace.test.ts +0 -213
  229. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  230. package/src/__tests__/retroactive-boost.test.ts +0 -223
  231. package/src/__tests__/salience-scoring.test.ts +0 -106
  232. package/src/__tests__/scope-inventory.test.ts +0 -200
  233. package/src/__tests__/scope-policy.test.ts +0 -69
  234. package/src/__tests__/session-checkpoint.test.ts +0 -259
  235. package/src/__tests__/session-distiller.test.ts +0 -611
  236. package/src/__tests__/session-gc.test.ts +0 -215
  237. package/src/__tests__/skill-engine.test.ts +0 -546
  238. package/src/__tests__/skill-promotion.test.ts +0 -349
  239. package/src/__tests__/store-scope-audit.test.ts +0 -81
  240. package/src/__tests__/supersede-chain.test.ts +0 -154
  241. package/src/__tests__/temporal-parser.test.ts +0 -241
  242. package/src/__tests__/temporal-validity.test.ts +0 -160
  243. package/src/__tests__/term-registry.test.ts +0 -121
  244. package/src/__tests__/tool-output-compressor.test.ts +0 -133
  245. package/src/__tests__/topic-tag.test.ts +0 -113
  246. package/src/__tests__/trusted-memory.test.ts +0 -828
  247. package/src/__tests__/version-manager.test.ts +0 -213
  248. package/src/__tests__/workflow-observation.test.ts +0 -400
  249. package/src/__tests__/write-verifier.test.ts +0 -122
@@ -1,515 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * pull-claude-desktop.ts
4
- *
5
- * 从 claude.ai 拉取 Claude Desktop / 网页版对话历史,
6
- * 转成 CC transcript .jsonl 格式,供 `lm ingest --source desktop` 消费。
7
- *
8
- * 用法:
9
- * CLAUDE_SESSION_KEY="sk-ant-..." bun scripts/pull-claude-desktop.ts
10
- * CLAUDE_SESSION_KEY="sk-ant-..." bun scripts/pull-claude-desktop.ts --limit 10
11
- * CLAUDE_SESSION_KEY="sk-ant-..." bun scripts/pull-claude-desktop.ts --since 2025-01-01
12
- */
13
-
14
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
- import { join } from "node:path";
16
- import { parseArgs } from "node:util";
17
-
18
- // ============================================================================
19
- // Config
20
- // ============================================================================
21
-
22
- const BASE_URL = "https://claude.ai";
23
- const PAGE_SIZE = 50;
24
- const THROTTLE_MS = 200;
25
- const MAX_RETRIES = 3;
26
- const OUTPUT_DIR = join(import.meta.dir, "..", "data", "desktop-import");
27
- const SYNC_STATE_PATH = join(OUTPUT_DIR, ".sync-state.json");
28
-
29
- // ============================================================================
30
- // Types
31
- // ============================================================================
32
-
33
- interface SyncState {
34
- lastSyncedAt: string;
35
- conversations: Record<
36
- string,
37
- {
38
- messageCount: number;
39
- updatedAt: string;
40
- pulledAt: string;
41
- name: string;
42
- }
43
- >;
44
- }
45
-
46
- interface ConversationSummary {
47
- uuid: string;
48
- name: string;
49
- created_at: string;
50
- updated_at: string;
51
- }
52
-
53
- interface ChatMessage {
54
- uuid: string;
55
- text: string;
56
- sender: "human" | "assistant";
57
- index: number;
58
- created_at: string;
59
- parent_message_uuid: string;
60
- content: Array<{
61
- type: "text" | "thinking" | "tool_use" | "tool_result";
62
- text?: string;
63
- name?: string;
64
- input?: unknown;
65
- }>;
66
- attachments?: Array<{
67
- file_name: string;
68
- file_type?: string;
69
- file_size?: number;
70
- }>;
71
- }
72
-
73
- interface Conversation {
74
- uuid: string;
75
- name: string;
76
- created_at: string;
77
- updated_at: string;
78
- chat_messages: ChatMessage[];
79
- }
80
-
81
- // ============================================================================
82
- // HTTP Client
83
- // ============================================================================
84
-
85
- class ClaudeApiClient {
86
- private sessionKey: string;
87
- private orgId: string | null = null;
88
- private throttleMs = THROTTLE_MS;
89
-
90
- constructor(sessionKey: string) {
91
- this.sessionKey = sessionKey;
92
- }
93
-
94
- private async request(path: string): Promise<any> {
95
- let lastError: Error | null = null;
96
-
97
- for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
98
- try {
99
- const res = await fetch(`${BASE_URL}${path}`, {
100
- headers: {
101
- Cookie: `sessionKey=${this.sessionKey}`,
102
- "Content-Type": "application/json",
103
- Accept: "application/json",
104
- },
105
- });
106
-
107
- if (res.status === 401 || res.status === 403) {
108
- throw new Error(
109
- `认证失败 (${res.status})。sessionKey 可能已过期,请从浏览器重新获取。`,
110
- );
111
- }
112
-
113
- if (res.status === 429) {
114
- this.throttleMs = Math.min(this.throttleMs * 2, 5000);
115
- console.log(` ⚠️ 限流,等待 ${this.throttleMs}ms...`);
116
- await sleep(this.throttleMs);
117
- continue;
118
- }
119
-
120
- if (res.status >= 500) {
121
- const waitMs = Math.pow(2, attempt) * 1000;
122
- console.log(
123
- ` ⚠️ 服务器错误 ${res.status},${waitMs}ms 后重试...`,
124
- );
125
- await sleep(waitMs);
126
- continue;
127
- }
128
-
129
- if (!res.ok) {
130
- throw new Error(`HTTP ${res.status}: ${await res.text()}`);
131
- }
132
-
133
- // Reset throttle on success
134
- this.throttleMs = THROTTLE_MS;
135
- return await res.json();
136
- } catch (err: any) {
137
- if (
138
- err.message?.includes("认证失败") ||
139
- err.message?.startsWith("HTTP ")
140
- ) {
141
- throw err;
142
- }
143
- lastError = err;
144
- const waitMs = Math.pow(2, attempt) * 1000;
145
- console.log(` ⚠️ 网络错误: ${err.message},${waitMs}ms 后重试...`);
146
- await sleep(waitMs);
147
- }
148
- }
149
-
150
- throw lastError || new Error("请求失败,已重试 3 次");
151
- }
152
-
153
- async getOrgId(): Promise<string> {
154
- if (this.orgId) return this.orgId;
155
- const orgs = await this.request("/api/organizations");
156
- if (!Array.isArray(orgs) || orgs.length === 0) {
157
- throw new Error("获取 organization 失败。请确认 sessionKey 有效。");
158
- }
159
- this.orgId = orgs[0].uuid;
160
- return this.orgId!;
161
- }
162
-
163
- async listConversations(
164
- offset: number = 0,
165
- ): Promise<ConversationSummary[]> {
166
- const orgId = await this.getOrgId();
167
- return await this.request(
168
- `/api/organizations/${orgId}/chat_conversations?limit=${PAGE_SIZE}&offset=${offset}`,
169
- );
170
- }
171
-
172
- async getConversation(chatId: string): Promise<Conversation> {
173
- const orgId = await this.getOrgId();
174
- return await this.request(
175
- `/api/organizations/${orgId}/chat_conversations/${chatId}?tree=True&rendering_mode=messages&render_all_tools=true`,
176
- );
177
- }
178
-
179
- async throttle(): Promise<void> {
180
- await sleep(this.throttleMs);
181
- }
182
- }
183
-
184
- // ============================================================================
185
- // Branch Extraction
186
- // ============================================================================
187
-
188
- function extractLatestBranch(messages: ChatMessage[]): ChatMessage[] {
189
- if (messages.length === 0) return [];
190
-
191
- const byUuid = new Map<string, ChatMessage>();
192
- for (const msg of messages) {
193
- byUuid.set(msg.uuid, msg);
194
- }
195
-
196
- // Find all parent UUIDs
197
- const parentUuids = new Set<string>();
198
- for (const msg of messages) {
199
- if (msg.parent_message_uuid) {
200
- parentUuids.add(msg.parent_message_uuid);
201
- }
202
- }
203
-
204
- // Leaves = messages that are not anyone's parent
205
- const leaves = messages.filter((m) => !parentUuids.has(m.uuid));
206
- if (leaves.length === 0) return messages;
207
-
208
- // Pick the leaf with the latest timestamp
209
- const latestLeaf = leaves.reduce((a, b) =>
210
- new Date(a.created_at).getTime() >= new Date(b.created_at).getTime()
211
- ? a
212
- : b,
213
- );
214
-
215
- // Walk backwards from leaf to root
216
- const branch: ChatMessage[] = [];
217
- let current: ChatMessage | undefined = latestLeaf;
218
- const visited = new Set<string>();
219
-
220
- while (current && !visited.has(current.uuid)) {
221
- visited.add(current.uuid);
222
- branch.push(current);
223
- current = current.parent_message_uuid
224
- ? byUuid.get(current.parent_message_uuid)
225
- : undefined;
226
- }
227
-
228
- branch.reverse();
229
- return branch;
230
- }
231
-
232
- // ============================================================================
233
- // Format Conversion
234
- // ============================================================================
235
-
236
- function messageToText(msg: ChatMessage): string {
237
- const parts: string[] = [];
238
-
239
- if (Array.isArray(msg.content) && msg.content.length > 0) {
240
- for (const block of msg.content) {
241
- if (block.type === "text" && block.text) {
242
- parts.push(block.text);
243
- }
244
- // Skip thinking, tool_use, tool_result — noise for memory
245
- }
246
- } else if (msg.text) {
247
- // Fallback: use raw text field
248
- parts.push(msg.text);
249
- }
250
-
251
- // Mark attachments
252
- if (Array.isArray(msg.attachments)) {
253
- for (const att of msg.attachments) {
254
- parts.push(`[Attachment: ${att.file_name}]`);
255
- }
256
- }
257
-
258
- return parts.join("\n").trim();
259
- }
260
-
261
- function convertToCCTranscript(
262
- conversation: Conversation,
263
- ): string[] {
264
- const branch = extractLatestBranch(conversation.chat_messages || []);
265
- const lines: string[] = [];
266
-
267
- for (const msg of branch) {
268
- const text = messageToText(msg);
269
- if (!text || text.length < 5) continue;
270
-
271
- const type = msg.sender === "human" ? "user" : "assistant";
272
- const entry = {
273
- type,
274
- message: {
275
- role: type,
276
- content: text,
277
- },
278
- sessionId: conversation.uuid,
279
- timestamp: msg.created_at,
280
- uuid: msg.uuid,
281
- parentUuid: msg.parent_message_uuid || undefined,
282
- };
283
-
284
- lines.push(JSON.stringify(entry));
285
- }
286
-
287
- return lines;
288
- }
289
-
290
- // ============================================================================
291
- // Sync State
292
- // ============================================================================
293
-
294
- function loadSyncState(): SyncState {
295
- if (existsSync(SYNC_STATE_PATH)) {
296
- try {
297
- return JSON.parse(readFileSync(SYNC_STATE_PATH, "utf-8"));
298
- } catch {
299
- // Corrupted, start fresh
300
- }
301
- }
302
- return { lastSyncedAt: "", conversations: {} };
303
- }
304
-
305
- function saveSyncState(state: SyncState): void {
306
- writeFileSync(SYNC_STATE_PATH, JSON.stringify(state, null, 2));
307
- }
308
-
309
- function needsPull(
310
- state: SyncState,
311
- conv: ConversationSummary,
312
- ): boolean {
313
- const existing = state.conversations[conv.uuid];
314
- if (!existing) return true;
315
- // Re-pull if conversation was updated since last pull
316
- return conv.updated_at !== existing.updatedAt;
317
- }
318
-
319
- // ============================================================================
320
- // Main
321
- // ============================================================================
322
-
323
- function sleep(ms: number): Promise<void> {
324
- return new Promise((resolve) => setTimeout(resolve, ms));
325
- }
326
-
327
- async function main() {
328
- const { values } = parseArgs({
329
- args: process.argv.slice(2),
330
- options: {
331
- "session-key": { type: "string" },
332
- limit: { type: "string" },
333
- since: { type: "string" },
334
- verbose: { type: "boolean", default: false },
335
- },
336
- strict: false,
337
- });
338
-
339
- const sessionKey =
340
- values["session-key"] ||
341
- process.env.CLAUDE_SESSION_KEY ||
342
- process.env.CLAUDE_DESKTOP_SESSION_KEY;
343
-
344
- if (!sessionKey) {
345
- console.error(
346
- "❌ 需要 sessionKey。用法:\n" +
347
- " CLAUDE_SESSION_KEY='...' bun scripts/pull-claude-desktop.ts\n\n" +
348
- "获取方式:浏览器登录 claude.ai → DevTools → Application → Cookies → sessionKey",
349
- );
350
- process.exit(1);
351
- }
352
-
353
- const limit = values.limit ? parseInt(values.limit, 10) : Infinity;
354
- const sinceDate = values.since ? new Date(values.since) : null;
355
- const verbose = values.verbose ?? false;
356
-
357
- // Ensure output directory
358
- mkdirSync(OUTPUT_DIR, { recursive: true });
359
-
360
- const client = new ClaudeApiClient(sessionKey);
361
- const state = loadSyncState();
362
-
363
- // Step 1: Verify auth
364
- console.log("🔑 验证 sessionKey...");
365
- try {
366
- const orgId = await client.getOrgId();
367
- console.log(` ✅ 认证成功 (org: ${orgId.slice(0, 8)}...)`);
368
- } catch (err: any) {
369
- console.error(` ❌ ${err.message}`);
370
- process.exit(1);
371
- }
372
-
373
- // Step 2: List all conversations (paginated)
374
- console.log("\n📋 获取对话列表...");
375
- const allConversations: ConversationSummary[] = [];
376
- let offset = 0;
377
- let pageCount = 0;
378
-
379
- while (true) {
380
- const page = await client.listConversations(offset);
381
- if (!Array.isArray(page) || page.length === 0) break;
382
-
383
- // Filter by --since if specified
384
- for (const conv of page) {
385
- if (sinceDate && new Date(conv.updated_at) < sinceDate) {
386
- // API returns newest first; once we hit older, stop
387
- console.log(
388
- ` ⏭️ 跳过 ${conv.updated_at} 之前的对话 (--since ${values.since})`,
389
- );
390
- break;
391
- }
392
- allConversations.push(conv);
393
- }
394
-
395
- pageCount++;
396
- process.stdout.write(
397
- ` 已扫描 ${allConversations.length} 条对话 (第 ${pageCount} 页)...\r`,
398
- );
399
-
400
- // Stop if we hit the since boundary or got a short page
401
- if (sinceDate) {
402
- const lastOnPage = page[page.length - 1];
403
- if (new Date(lastOnPage.updated_at) < sinceDate) break;
404
- }
405
- if (page.length < PAGE_SIZE) break;
406
-
407
- offset += PAGE_SIZE;
408
- await client.throttle();
409
- }
410
-
411
- console.log(` ✅ 共发现 ${allConversations.length} 条对话`);
412
-
413
- // Step 3: Filter — skip already-synced
414
- const toPull = allConversations.filter((c) => needsPull(state, c));
415
- const toSkip = allConversations.length - toPull.length;
416
-
417
- if (toSkip > 0) {
418
- console.log(` ⏭️ ${toSkip} 条已同步,跳过`);
419
- }
420
-
421
- // Apply --limit
422
- const batch = toPull.slice(0, limit);
423
- if (batch.length === 0) {
424
- console.log("\n✅ 没有需要拉取的新对话。");
425
- return;
426
- }
427
-
428
- console.log(`\n🔄 开始拉取 ${batch.length} 条对话...\n`);
429
-
430
- // Step 4: Pull each conversation
431
- let pulled = 0;
432
- let skipped = 0;
433
- let failed = 0;
434
- const errors: string[] = [];
435
-
436
- for (let i = 0; i < batch.length; i++) {
437
- const conv = batch[i];
438
- const label = conv.name?.slice(0, 40) || conv.uuid.slice(0, 8);
439
-
440
- try {
441
- process.stdout.write(
442
- ` [${i + 1}/${batch.length}] ${label}...`,
443
- );
444
-
445
- const full = await client.getConversation(conv.uuid);
446
- const lines = convertToCCTranscript(full);
447
-
448
- if (lines.length === 0) {
449
- console.log(` ⏭️ 空对话`);
450
- skipped++;
451
- } else {
452
- const outPath = join(OUTPUT_DIR, `${conv.uuid}.jsonl`);
453
- writeFileSync(outPath, lines.join("\n") + "\n");
454
- console.log(` ✅ ${lines.length} 条消息`);
455
- pulled++;
456
- }
457
-
458
- // Update sync state
459
- state.conversations[conv.uuid] = {
460
- messageCount: lines.length,
461
- updatedAt: conv.updated_at,
462
- pulledAt: new Date().toISOString(),
463
- name: conv.name || "",
464
- };
465
-
466
- // Save state periodically (every 10 conversations)
467
- if ((i + 1) % 10 === 0) {
468
- state.lastSyncedAt = new Date().toISOString();
469
- saveSyncState(state);
470
- }
471
-
472
- await client.throttle();
473
- } catch (err: any) {
474
- console.log(` ❌ ${err.message}`);
475
- errors.push(`${label}: ${err.message}`);
476
- failed++;
477
-
478
- // Don't abort on single conversation failure
479
- await client.throttle();
480
- }
481
- }
482
-
483
- // Final save
484
- state.lastSyncedAt = new Date().toISOString();
485
- saveSyncState(state);
486
-
487
- // Step 5: Summary
488
- console.log("\n📊 拉取汇总:");
489
- console.log(` 总对话数: ${allConversations.length}`);
490
- console.log(` 已同步跳过: ${toSkip}`);
491
- console.log(` 本次拉取: ${pulled}`);
492
- console.log(` 空对话跳过: ${skipped}`);
493
- console.log(` 失败: ${failed}`);
494
- console.log(` 输出目录: ${OUTPUT_DIR}`);
495
-
496
- if (errors.length > 0) {
497
- console.log("\n⚠️ 失败详情:");
498
- for (const e of errors.slice(0, 10)) {
499
- console.log(` - ${e}`);
500
- }
501
- }
502
-
503
- if (pulled > 0) {
504
- console.log(
505
- "\n🎯 下一步:运行以下命令将对话导入 RecallNest:",
506
- );
507
- console.log(" bun run src/cli.ts ingest --source desktop");
508
- }
509
- }
510
-
511
- main().catch((err) => {
512
- console.error(`\n❌ 致命错误: ${err.message}`);
513
- if (err.stack) console.error(err.stack);
514
- process.exit(1);
515
- });
@@ -1,66 +0,0 @@
1
- #!/bin/bash
2
- # re-ingest-cc.sh — 全量重建 RecallNest 索引
3
- #
4
- # 背景:旧 dedup 阈值 (hard=0.80, soft=0.68) 导致 72% 内容被误杀,
5
- # 且旧数据中可能有噪音。最干净的做法是清空后用新阈值从头导入。
6
- #
7
- # 用法:
8
- # cd ~/recallnest && bash scripts/re-ingest-cc.sh
9
- #
10
- # 步骤:
11
- # 1. 备份 LanceDB + ingested-files.json + distill-progress.json
12
- # 2. reset(清空 LanceDB)
13
- # 3. 清空 ingest 和 distill 进度记录
14
- # 4. 全量 ingest(所有源)
15
- # 5. 完成后提示手动跑 distill-facts
16
- #
17
- # 预计耗时:3-5 小时(后台跑)
18
- # 预计 API 成本:~$3-5
19
-
20
- set -euo pipefail
21
- cd "$(dirname "$0")/.."
22
-
23
- TIMESTAMP=$(date +%Y%m%d-%H%M%S)
24
- BACKUP_DIR="data/backup-$TIMESTAMP"
25
- LOG_FILE="logs/re-ingest-$TIMESTAMP.log"
26
-
27
- echo "=== RecallNest 全量重建 ===" | tee "$LOG_FILE"
28
- echo "开始时间: $(date)" | tee -a "$LOG_FILE"
29
-
30
- # Step 1: 备份
31
- echo "[1/4] 备份现有数据..." | tee -a "$LOG_FILE"
32
- mkdir -p "$BACKUP_DIR"
33
- cp data/ingested-files.json "$BACKUP_DIR/" 2>/dev/null || true
34
- cp data/distill-progress.json "$BACKUP_DIR/" 2>/dev/null || true
35
- # LanceDB 太大不整体备份,只记录 stats
36
- echo " LanceDB 当前大小: $(du -sh data/lancedb 2>/dev/null | cut -f1)" | tee -a "$LOG_FILE"
37
- echo " 备份到: $BACKUP_DIR/" | tee -a "$LOG_FILE"
38
-
39
- # Step 2: 清空 LanceDB
40
- echo "[2/4] 清空 LanceDB 索引..." | tee -a "$LOG_FILE"
41
- bun run src/cli.ts reset --yes 2>&1 | tee -a "$LOG_FILE"
42
-
43
- # Step 3: 清空进度记录
44
- echo "[3/4] 清空 ingest/distill 进度记录..." | tee -a "$LOG_FILE"
45
- # Reset ingested-files.json — 让 ingest 认为所有文件都没处理过
46
- echo '{"files":{}}' > data/ingested-files.json
47
- # Reset distill progress
48
- echo '{"startedAt":"","lastUpdated":"","completedScopes":[],"stats":{}}' > data/distill-progress.json
49
- echo " 进度已重置" | tee -a "$LOG_FILE"
50
-
51
- # Step 4: 全量 ingest
52
- echo "[4/4] 开始全量导入(这一步需要 3-5 小时)..." | tee -a "$LOG_FILE"
53
- echo " 日志实时写入: $LOG_FILE" | tee -a "$LOG_FILE"
54
- echo " 查看进度: tail -f $LOG_FILE" | tee -a "$LOG_FILE"
55
- echo "" | tee -a "$LOG_FILE"
56
-
57
- bun run src/cli.ts ingest 2>&1 | tee -a "$LOG_FILE"
58
-
59
- echo "" | tee -a "$LOG_FILE"
60
- echo "=== 全量导入完成 ===" | tee -a "$LOG_FILE"
61
- echo "结束时间: $(date)" | tee -a "$LOG_FILE"
62
- echo "" | tee -a "$LOG_FILE"
63
- echo "下一步:" | tee -a "$LOG_FILE"
64
- echo " 1. 检查日志确认入库率(目标 50-65%,旧阈值是 27%)" | tee -a "$LOG_FILE"
65
- echo " 2. 跑 distill: bun scripts/distill-facts.ts" | tee -a "$LOG_FILE"
66
- echo " 3. 旧数据备份在: $BACKUP_DIR/" | tee -a "$LOG_FILE"