recallnest 2.2.0 → 2.2.1

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 (232) hide show
  1. package/.env +4 -0
  2. package/config.json +55 -0
  3. package/logs/daily-recall.error.log +0 -0
  4. package/logs/daily-recall.log +30 -0
  5. package/logs/ingest-2026-04-11.log +110 -0
  6. package/logs/ingest-2026-04-12.log +90 -0
  7. package/logs/launchagent-stderr.log +0 -0
  8. package/logs/launchagent-stdout.log +0 -0
  9. package/package.json +1 -1
  10. package/.claude-plugin/CLAUDE.md +0 -29
  11. package/.claude-plugin/marketplace.json +0 -18
  12. package/.dockerignore +0 -11
  13. package/.github/workflows/ci.yml +0 -28
  14. package/.mcp.json +0 -9
  15. package/CHANGELOG.md +0 -51
  16. package/DEMO.md +0 -94
  17. package/Dockerfile +0 -20
  18. package/EVAL.md +0 -89
  19. package/FAILURES.md +0 -75
  20. package/OPERATIONS.md +0 -59
  21. package/ROADMAP.md +0 -245
  22. package/SKILL.md +0 -250
  23. package/assets/dashboard.png +0 -0
  24. package/assets/knowledge-graph.png +0 -0
  25. package/assets/recallnest-workbench.png +0 -0
  26. package/assets/screenshots/ui-full.png +0 -0
  27. package/assets/screenshots/ui-overview.png +0 -0
  28. package/assets/screenshots/ui-search.png +0 -0
  29. package/assets/ui/app.js +0 -1031
  30. package/assets/ui/index.html +0 -224
  31. package/assets/ui/styles.css +0 -781
  32. package/assets/wechat-qr.jpg +0 -0
  33. package/bun.lock +0 -337
  34. package/daily-reflections/2026-03-05.md +0 -15
  35. package/daily-reflections/2026-03-06.md +0 -15
  36. package/daily-reflections/2026-03-07.md +0 -7
  37. package/daily-reflections/2026-03-08.md +0 -34
  38. package/daily-reflections/2026-03-09.md +0 -35
  39. package/daily-reflections/2026-03-10.md +0 -36
  40. package/daily-reflections/2026-03-11.md +0 -35
  41. package/daily-reflections/2026-03-12.md +0 -36
  42. package/daily-reflections/2026-03-13.md +0 -35
  43. package/daily-reflections/2026-03-14.md +0 -35
  44. package/daily-reflections/2026-03-15.md +0 -35
  45. package/daily-reflections/2026-03-16.md +0 -41
  46. package/daily-reflections/2026-03-17.md +0 -10
  47. package/daily-reflections/2026-03-18.md +0 -10
  48. package/daily-reflections/2026-03-19.md +0 -10
  49. package/daily-reflections/2026-03-20.md +0 -10
  50. package/daily-reflections/2026-03-21.md +0 -10
  51. package/daily-reflections/2026-03-22.md +0 -10
  52. package/daily-reflections/2026-03-23.md +0 -10
  53. package/daily-reflections/2026-03-24.md +0 -10
  54. package/daily-reflections/2026-03-25.md +0 -32
  55. package/daily-reflections/2026-03-26.md +0 -37
  56. package/daily-reflections/2026-03-27.md +0 -35
  57. package/daily-reflections/2026-03-28.md +0 -35
  58. package/daily-reflections/2026-03-29.md +0 -35
  59. package/daily-reflections/2026-03-30.md +0 -36
  60. package/daily-reflections/2026-03-31.md +0 -35
  61. package/daily-reflections/2026-04-01.md +0 -35
  62. package/daily-reflections/2026-04-02.md +0 -35
  63. package/daily-reflections/2026-04-03.md +0 -37
  64. package/daily-reflections/2026-04-04.md +0 -37
  65. package/daily-reflections/2026-04-05.md +0 -35
  66. package/daily-reflections/2026-04-06.md +0 -36
  67. package/daily-reflections/2026-04-07.md +0 -35
  68. package/daily-reflections/2026-04-08.md +0 -35
  69. package/daily-reflections/2026-04-12.md +0 -35
  70. package/data/alias-map.example.json +0 -22
  71. package/docs/api-reference.md +0 -1291
  72. package/docs/architecture.md +0 -168
  73. package/docs/memory-boundary-contract.md +0 -226
  74. package/docs/memory-categories.md +0 -119
  75. package/docs/pipeline-audit-ultramemory.md +0 -177
  76. package/docs/plans/2026-03-08-query-expansion.md +0 -270
  77. package/docs/plans/2026-03-16-continuity-layer.md +0 -196
  78. package/docs/plans/2026-04-01-recall-boost.md +0 -100
  79. package/docs/plans/mcp-tiering-plan.md +0 -126
  80. package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
  81. package/docs/self-evolution.md +0 -198
  82. package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
  83. package/docs/superpowers/plans/2026-04-11-constructive-retrieval-emotion.md +0 -1195
  84. package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
  85. package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
  86. package/docs/superpowers/specs/2026-04-11-constructive-retrieval-emotion-design.md +0 -398
  87. package/eval/continuity/results-2026-03-16.md +0 -117
  88. package/eval/continuity/results-2026-03-19.md +0 -453
  89. package/eval/continuity/runbook.md +0 -110
  90. package/eval/continuity/seed-cases.md +0 -54
  91. package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
  92. package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
  93. package/eval/reports/2026-03-06-baseline.md +0 -82
  94. package/eval/reports/query-expansion.md +0 -138
  95. package/glama.json +0 -6
  96. package/lm +0 -19
  97. package/plan-philosophy-upgrades.md +0 -363
  98. package/plan-product-upgrades.md +0 -155
  99. package/plan-v1.2-distributable.md +0 -105
  100. package/plan.md +0 -140
  101. package/research-memory-evolution.md +0 -440
  102. package/research-rl-agent-memory.md +0 -330
  103. package/scripts/archive-distilled-junk.ts +0 -116
  104. package/scripts/backfill-anchors.ts +0 -130
  105. package/scripts/backfill-l0l1l2.ts +0 -98
  106. package/scripts/blind-scan.ts +0 -588
  107. package/scripts/cleanup-step1-exact-dups.ts +0 -117
  108. package/scripts/cleanup-step2-template-dups.ts +0 -113
  109. package/scripts/cleanup-step3-stale-content.ts +0 -149
  110. package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
  111. package/scripts/delete-distilled-junk.ts +0 -88
  112. package/scripts/diagnose-vector-search.ts +0 -132
  113. package/scripts/distill-facts.ts +0 -305
  114. package/scripts/health-check.ts +0 -343
  115. package/scripts/incremental-ingest.sh +0 -71
  116. package/scripts/migrate-language.ts +0 -75
  117. package/scripts/mlp-health-check.ts +0 -382
  118. package/scripts/pull-claude-desktop.ts +0 -515
  119. package/scripts/re-ingest-cc.sh +0 -66
  120. package/scripts/rebalance-memory-health.ts +0 -181
  121. package/scripts/run-desktop-ingest.sh +0 -11
  122. package/scripts/start-server.sh +0 -12
  123. package/scripts/verify-emotion-decay.ts +0 -148
  124. package/scripts/viz-dashboard.html +0 -423
  125. package/scripts/viz-export.ts +0 -703
  126. package/src/__tests__/access-tracker-novelty.test.ts +0 -228
  127. package/src/__tests__/activity-counter.test.ts +0 -96
  128. package/src/__tests__/admission-control.test.ts +0 -206
  129. package/src/__tests__/anchor-generator.test.ts +0 -67
  130. package/src/__tests__/auto-consolidation.test.ts +0 -168
  131. package/src/__tests__/auto-recall.test.ts +0 -131
  132. package/src/__tests__/babel-integration.test.ts +0 -139
  133. package/src/__tests__/batch-dedup.test.ts +0 -65
  134. package/src/__tests__/capture-engine.test.ts +0 -1144
  135. package/src/__tests__/capture-heuristic.test.ts +0 -171
  136. package/src/__tests__/cascade-forget.test.ts +0 -210
  137. package/src/__tests__/category-min-scores.test.ts +0 -33
  138. package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
  139. package/src/__tests__/circuit-breaker.test.ts +0 -99
  140. package/src/__tests__/cluster-consolidation.test.ts +0 -712
  141. package/src/__tests__/confidence-metadata.test.ts +0 -125
  142. package/src/__tests__/confidence-tracker.test.ts +0 -249
  143. package/src/__tests__/conflict-advisor.test.ts +0 -206
  144. package/src/__tests__/conflict-escalation.test.ts +0 -149
  145. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  146. package/src/__tests__/conflict-output.test.ts +0 -85
  147. package/src/__tests__/conflict-store.test.ts +0 -374
  148. package/src/__tests__/consolidation-engine.test.ts +0 -181
  149. package/src/__tests__/constructive-retrieval.test.ts +0 -509
  150. package/src/__tests__/context-collapse-renderer.test.ts +0 -214
  151. package/src/__tests__/context-composer-pins.test.ts +0 -64
  152. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  153. package/src/__tests__/context-reconstructor.test.ts +0 -65
  154. package/src/__tests__/context-renderer.test.ts +0 -91
  155. package/src/__tests__/continuity-eval.test.ts +0 -275
  156. package/src/__tests__/continuity-schema.test.ts +0 -97
  157. package/src/__tests__/conversation-importer.test.ts +0 -692
  158. package/src/__tests__/core-recall-eval.test.ts +0 -235
  159. package/src/__tests__/core-summary.test.ts +0 -109
  160. package/src/__tests__/data-checkup.test.ts +0 -225
  161. package/src/__tests__/decay-exemption.test.ts +0 -84
  162. package/src/__tests__/dedup-actions.test.ts +0 -58
  163. package/src/__tests__/dedup-guard.test.ts +0 -39
  164. package/src/__tests__/deterministic-id.test.ts +0 -39
  165. package/src/__tests__/distill-lock.test.ts +0 -176
  166. package/src/__tests__/doctor.test.ts +0 -303
  167. package/src/__tests__/dream-pipeline.test.ts +0 -232
  168. package/src/__tests__/drill-down.test.ts +0 -108
  169. package/src/__tests__/embedder.test.ts +0 -122
  170. package/src/__tests__/emotion-decay.test.ts +0 -46
  171. package/src/__tests__/emotion-detector.test.ts +0 -56
  172. package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
  173. package/src/__tests__/emotion-retrieval.test.ts +0 -56
  174. package/src/__tests__/entity-resolver.test.ts +0 -58
  175. package/src/__tests__/ethics-layer.test.ts +0 -476
  176. package/src/__tests__/event-segmenter.test.ts +0 -153
  177. package/src/__tests__/feature-flags-integration.test.ts +0 -54
  178. package/src/__tests__/frequency-tracker.test.ts +0 -113
  179. package/src/__tests__/graph-export.test.ts +0 -489
  180. package/src/__tests__/hotness-blend.test.ts +0 -76
  181. package/src/__tests__/ingest.test.ts +0 -360
  182. package/src/__tests__/interference-detector.test.ts +0 -203
  183. package/src/__tests__/kg-extractor.test.ts +0 -184
  184. package/src/__tests__/kg-store.test.ts +0 -198
  185. package/src/__tests__/llm-consolidation.test.ts +0 -226
  186. package/src/__tests__/mcp-efficiency.test.ts +0 -288
  187. package/src/__tests__/memory-boundaries.test.ts +0 -237
  188. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -243
  189. package/src/__tests__/memory-evolution.test.ts +0 -271
  190. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  191. package/src/__tests__/memory-lint.test.ts +0 -271
  192. package/src/__tests__/memory-output.test.ts +0 -215
  193. package/src/__tests__/multi-vector.test.ts +0 -242
  194. package/src/__tests__/narrative-architecture.test.ts +0 -456
  195. package/src/__tests__/pending-review.test.ts +0 -77
  196. package/src/__tests__/post-retrieval-filter.test.ts +0 -178
  197. package/src/__tests__/ppr-traversal.test.ts +0 -256
  198. package/src/__tests__/prediction-engine.test.ts +0 -349
  199. package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
  200. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  201. package/src/__tests__/preference-matcher.test.ts +0 -230
  202. package/src/__tests__/preference-slots.test.ts +0 -205
  203. package/src/__tests__/prospective-memory.test.ts +0 -283
  204. package/src/__tests__/query-entity-detector.test.ts +0 -171
  205. package/src/__tests__/query-expander-alias.test.ts +0 -89
  206. package/src/__tests__/query-expander.test.ts +0 -34
  207. package/src/__tests__/recall-governor.test.ts +0 -227
  208. package/src/__tests__/recall-mode.test.ts +0 -64
  209. package/src/__tests__/result-synthesizer.test.ts +0 -198
  210. package/src/__tests__/resume-context.test.ts +0 -4898
  211. package/src/__tests__/retrieval-trace.test.ts +0 -213
  212. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  213. package/src/__tests__/retroactive-boost.test.ts +0 -223
  214. package/src/__tests__/salience-scoring.test.ts +0 -106
  215. package/src/__tests__/scope-inventory.test.ts +0 -200
  216. package/src/__tests__/scope-policy.test.ts +0 -69
  217. package/src/__tests__/session-checkpoint.test.ts +0 -259
  218. package/src/__tests__/session-distiller.test.ts +0 -611
  219. package/src/__tests__/session-gc.test.ts +0 -215
  220. package/src/__tests__/skill-engine.test.ts +0 -546
  221. package/src/__tests__/skill-promotion.test.ts +0 -349
  222. package/src/__tests__/store-scope-audit.test.ts +0 -81
  223. package/src/__tests__/supersede-chain.test.ts +0 -154
  224. package/src/__tests__/temporal-parser.test.ts +0 -241
  225. package/src/__tests__/temporal-validity.test.ts +0 -160
  226. package/src/__tests__/term-registry.test.ts +0 -121
  227. package/src/__tests__/tool-output-compressor.test.ts +0 -133
  228. package/src/__tests__/topic-tag.test.ts +0 -113
  229. package/src/__tests__/trusted-memory.test.ts +0 -828
  230. package/src/__tests__/version-manager.test.ts +0 -213
  231. package/src/__tests__/workflow-observation.test.ts +0 -400
  232. 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"