thread-archive 0.0.2__py3-none-any.whl
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.
- thread_archive/__init__.py +34 -0
- thread_archive/_api.py +2235 -0
- thread_archive/_config.py +126 -0
- thread_archive/_importers/__init__.py +81 -0
- thread_archive/_importers/_continuation.py +136 -0
- thread_archive/_importers/_cursor.py +103 -0
- thread_archive/_importers/_events.py +244 -0
- thread_archive/_importers/_line_stream.py +193 -0
- thread_archive/_importers/_read.py +82 -0
- thread_archive/_importers/_result.py +17 -0
- thread_archive/_importers/_sidecar.py +113 -0
- thread_archive/_importers/_state.py +240 -0
- thread_archive/_importers/_titles.py +179 -0
- thread_archive/_importers/antigravity.py +254 -0
- thread_archive/_importers/claude_code.py +293 -0
- thread_archive/_importers/claude_science.py +330 -0
- thread_archive/_importers/cloth.py +20 -0
- thread_archive/_importers/codex.py +324 -0
- thread_archive/_importers/cowork.py +107 -0
- thread_archive/_importers/cursor.py +432 -0
- thread_archive/_importers/exports.py +389 -0
- thread_archive/_importers/grok.py +600 -0
- thread_archive/_importers/opencode.py +538 -0
- thread_archive/_knowledge/__init__.py +67 -0
- thread_archive/_knowledge/_claims.py +116 -0
- thread_archive/_knowledge/_community.py +75 -0
- thread_archive/_knowledge/graph.py +208 -0
- thread_archive/_knowledge/materialize.py +212 -0
- thread_archive/_knowledge/write.py +438 -0
- thread_archive/_launchd.py +167 -0
- thread_archive/_mcp/__init__.py +1 -0
- thread_archive/_mcp/librarian.py +151 -0
- thread_archive/_mcp/server.py +307 -0
- thread_archive/_retrieval/__init__.py +280 -0
- thread_archive/_retrieval/_classify.py +80 -0
- thread_archive/_retrieval/_codex.py +157 -0
- thread_archive/_retrieval/_context.py +123 -0
- thread_archive/_retrieval/_extract.py +184 -0
- thread_archive/_retrieval/embed.py +186 -0
- thread_archive/_retrieval/format.py +149 -0
- thread_archive/_retrieval/fts.py +538 -0
- thread_archive/_retrieval/rank.py +228 -0
- thread_archive/_retrieval/read.py +908 -0
- thread_archive/_retrieval/rerank.py +143 -0
- thread_archive/_retrieval/vectors.py +611 -0
- thread_archive/_scripts/__init__.py +1 -0
- thread_archive/_scripts/backfill_recompute.py +328 -0
- thread_archive/_scripts/backfill_reconcile.py +296 -0
- thread_archive/_scripts/denamespace_dedup_keys.py +120 -0
- thread_archive/_scripts/recover_dropped_events.py +190 -0
- thread_archive/_scripts/repair_grok_tool_names.py +118 -0
- thread_archive/_scripts/repair_grok_tool_names_backup_20260704T155625Z.json +275 -0
- thread_archive/_scripts/repair_grok_tool_names_plan_20260704.json +435 -0
- thread_archive/_setup/__init__.py +12 -0
- thread_archive/_setup/clients.py +89 -0
- thread_archive/_setup/wizard.py +474 -0
- thread_archive/_store/__init__.py +45 -0
- thread_archive/_store/_base.py +173 -0
- thread_archive/_store/_defaults.py +57 -0
- thread_archive/_store/_types.py +38 -0
- thread_archive/_store/models.py +370 -0
- thread_archive/_store/schema.py +84 -0
- thread_archive/_thread_import/__init__.py +40 -0
- thread_archive/_thread_import/api.py +78 -0
- thread_archive/_thread_import/event_builder.py +810 -0
- thread_archive/_thread_import/exporters/__init__.py +9 -0
- thread_archive/_thread_import/exporters/_cursor_kv_mixin.py +166 -0
- thread_archive/_thread_import/exporters/_cursor_parse_mixin.py +149 -0
- thread_archive/_thread_import/exporters/cursor.py +582 -0
- thread_archive/_thread_import/exporters/cursor_parse.py +145 -0
- thread_archive/_thread_import/parsers/__init__.py +191 -0
- thread_archive/_thread_import/parsers/base.py +698 -0
- thread_archive/_thread_import/parsers/chatgpt.py +722 -0
- thread_archive/_thread_import/parsers/chatgpt_content.py +332 -0
- thread_archive/_thread_import/parsers/claude.py +443 -0
- thread_archive/_thread_import/parsers/claude_code.py +1035 -0
- thread_archive/_thread_import/parsers/claude_code_blocks.py +415 -0
- thread_archive/_thread_import/parsers/claude_code_ide.py +122 -0
- thread_archive/_thread_import/parsers/claude_code_sessions.py +90 -0
- thread_archive/_thread_import/parsers/config/__init__.py +26 -0
- thread_archive/_thread_import/parsers/config/base.py +220 -0
- thread_archive/_thread_import/parsers/cursor.py +538 -0
- thread_archive/_thread_import/parsers/cursor_blocks.py +365 -0
- thread_archive/_thread_import/parsers/pipeline/__init__.py +29 -0
- thread_archive/_thread_import/parsers/pipeline/base.py +251 -0
- thread_archive/_thread_import/parsers/pipeline/interfaces.py +191 -0
- thread_archive/_thread_import/parsers/transformers/__init__.py +22 -0
- thread_archive/_thread_import/parsers/transformers/active_path.py +87 -0
- thread_archive/_thread_import/parsers/transformers/coalescing.py +389 -0
- thread_archive/_thread_import/parsers/transformers/ide_context.py +158 -0
- thread_archive/_thread_import/parsers/transformers/thinking_merge.py +68 -0
- thread_archive/_thread_import/parsers/types/__init__.py +56 -0
- thread_archive/_thread_import/parsers/types/chatgpt.py +99 -0
- thread_archive/_thread_import/parsers/types/claude.py +120 -0
- thread_archive/_thread_import/parsers/types/claude_code.py +139 -0
- thread_archive/_thread_import/parsers/types/cursor.py +109 -0
- thread_archive/_thread_import/parsers/validators/__init__.py +83 -0
- thread_archive/_thread_import/parsers/validators/base.py +168 -0
- thread_archive/_thread_import/parsers/validators/content.py +80 -0
- thread_archive/_thread_import/parsers/validators/referential.py +57 -0
- thread_archive/_thread_import/parsers/validators/thinking.py +143 -0
- thread_archive/_thread_import/parsers/validators/types.py +87 -0
- thread_archive/_thread_import/schemas/__init__.py +231 -0
- thread_archive/_thread_import/schemas/chatgpt/v1.json +197 -0
- thread_archive/_thread_import/schemas/chatgpt/v2.json +203 -0
- thread_archive/_thread_import/schemas/claude/v1.json +188 -0
- thread_archive/_thread_import/schemas/claude_code/v1.json +183 -0
- thread_archive/_thread_import/schemas/cursor/v1.json +143 -0
- thread_archive/_thread_import/schemas/cursor/v2.json +200 -0
- thread_archive/_thread_import/timestamps.py +57 -0
- thread_archive/_thread_import/tool_names.py +48 -0
- thread_archive/_truth/__init__.py +40 -0
- thread_archive/_truth/jsonl_log.py +2340 -0
- thread_archive/_truth/repair.py +322 -0
- thread_archive/_watcher/__init__.py +57 -0
- thread_archive/_watcher/base.py +65 -0
- thread_archive/_watcher/daemon.py +289 -0
- thread_archive/_watcher/export_drop.py +203 -0
- thread_archive/_watcher/exthost.py +316 -0
- thread_archive/_watcher/lazy.py +117 -0
- thread_archive/_watcher/sources.py +616 -0
- thread_archive/_web/__init__.py +15 -0
- thread_archive/_web/server.py +299 -0
- thread_archive/_web/static/assets/index-DECSH1Mz.css +10 -0
- thread_archive/_web/static/assets/index-Djq0FK0y.js +101 -0
- thread_archive/_web/static/index.html +13 -0
- thread_archive/cli.py +746 -0
- thread_archive-0.0.2.dist-info/METADATA +296 -0
- thread_archive-0.0.2.dist-info/RECORD +132 -0
- thread_archive-0.0.2.dist-info/WHEEL +4 -0
- thread_archive-0.0.2.dist-info/entry_points.txt +6 -0
- thread_archive-0.0.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "cursor_export_v1",
|
|
4
|
+
"title": "Cursor Export v1",
|
|
5
|
+
"description": "Cursor IDE chat export format (from cursor_export tool)",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"provider": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"const": "cursor"
|
|
11
|
+
},
|
|
12
|
+
"export_version": {
|
|
13
|
+
"type": "string"
|
|
14
|
+
},
|
|
15
|
+
"export_date": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "ISO timestamp"
|
|
18
|
+
},
|
|
19
|
+
"conversations": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": {
|
|
22
|
+
"$ref": "#/definitions/conversation"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"definitions": {
|
|
27
|
+
"conversation": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"required": ["id"],
|
|
30
|
+
"properties": {
|
|
31
|
+
"id": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Conversation ID"
|
|
34
|
+
},
|
|
35
|
+
"title": {
|
|
36
|
+
"type": ["string", "null"],
|
|
37
|
+
"description": "Chat title"
|
|
38
|
+
},
|
|
39
|
+
"workspace_hash": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Hash identifying the workspace"
|
|
42
|
+
},
|
|
43
|
+
"workspace_name": {
|
|
44
|
+
"type": ["string", "null"],
|
|
45
|
+
"description": "Human-readable workspace name"
|
|
46
|
+
},
|
|
47
|
+
"workspace_uri": {
|
|
48
|
+
"type": ["string", "null"],
|
|
49
|
+
"description": "Path to the workspace"
|
|
50
|
+
},
|
|
51
|
+
"messages": {
|
|
52
|
+
"type": "array",
|
|
53
|
+
"items": {
|
|
54
|
+
"$ref": "#/definitions/message"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"bubbles": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": {
|
|
60
|
+
"$ref": "#/definitions/bubble"
|
|
61
|
+
},
|
|
62
|
+
"description": "Alternative message format (raw Cursor format)"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"message": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"properties": {
|
|
69
|
+
"id": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"description": "Message ID"
|
|
72
|
+
},
|
|
73
|
+
"role": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"enum": ["user", "assistant", "system", "tool"],
|
|
76
|
+
"description": "Message author role"
|
|
77
|
+
},
|
|
78
|
+
"content": {
|
|
79
|
+
"type": ["string", "null"],
|
|
80
|
+
"description": "Message text content"
|
|
81
|
+
},
|
|
82
|
+
"content_blocks": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"description": "Pre-parsed content blocks"
|
|
85
|
+
},
|
|
86
|
+
"created_at": {
|
|
87
|
+
"type": ["string", "null"],
|
|
88
|
+
"description": "ISO timestamp"
|
|
89
|
+
},
|
|
90
|
+
"tool_calls": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"description": "Tool/function calls made"
|
|
93
|
+
},
|
|
94
|
+
"tool_results": {
|
|
95
|
+
"type": "array",
|
|
96
|
+
"description": "Results from tool calls"
|
|
97
|
+
},
|
|
98
|
+
"model": {
|
|
99
|
+
"type": ["string", "null"],
|
|
100
|
+
"description": "Model used for response"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"bubble": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"description": "Raw Cursor bubble format",
|
|
107
|
+
"properties": {
|
|
108
|
+
"type": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"enum": ["user", "ai"],
|
|
111
|
+
"description": "Bubble type"
|
|
112
|
+
},
|
|
113
|
+
"text": {
|
|
114
|
+
"type": ["string", "null"],
|
|
115
|
+
"description": "Message text"
|
|
116
|
+
},
|
|
117
|
+
"rawText": {
|
|
118
|
+
"type": ["string", "null"],
|
|
119
|
+
"description": "Raw text before processing"
|
|
120
|
+
},
|
|
121
|
+
"codeBlocks": {
|
|
122
|
+
"type": "array",
|
|
123
|
+
"description": "Code blocks in the message"
|
|
124
|
+
},
|
|
125
|
+
"selections": {
|
|
126
|
+
"type": "array",
|
|
127
|
+
"description": "Code selections referenced"
|
|
128
|
+
},
|
|
129
|
+
"messageId": {
|
|
130
|
+
"type": ["string", "integer"],
|
|
131
|
+
"description": "Message identifier"
|
|
132
|
+
},
|
|
133
|
+
"relevantFiles": {
|
|
134
|
+
"type": "array",
|
|
135
|
+
"description": "Files relevant to this message"
|
|
136
|
+
},
|
|
137
|
+
"capabilityStatuses": {
|
|
138
|
+
"type": "array"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "cursor_export_v2",
|
|
4
|
+
"title": "Cursor Export v2",
|
|
5
|
+
"description": "Cursor IDE chat export format with toolFormerData (newer Cursor versions)",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"provider": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"const": "cursor"
|
|
11
|
+
},
|
|
12
|
+
"export_version": {
|
|
13
|
+
"type": "string"
|
|
14
|
+
},
|
|
15
|
+
"export_date": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "ISO timestamp"
|
|
18
|
+
},
|
|
19
|
+
"conversations": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": {
|
|
22
|
+
"$ref": "#/definitions/conversation"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"definitions": {
|
|
27
|
+
"conversation": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"required": ["id"],
|
|
30
|
+
"properties": {
|
|
31
|
+
"id": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Conversation ID"
|
|
34
|
+
},
|
|
35
|
+
"title": {
|
|
36
|
+
"type": ["string", "null"],
|
|
37
|
+
"description": "Chat title"
|
|
38
|
+
},
|
|
39
|
+
"workspace_hash": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Hash identifying the workspace"
|
|
42
|
+
},
|
|
43
|
+
"workspace_name": {
|
|
44
|
+
"type": ["string", "null"],
|
|
45
|
+
"description": "Human-readable workspace name"
|
|
46
|
+
},
|
|
47
|
+
"workspace_uri": {
|
|
48
|
+
"type": ["string", "null"],
|
|
49
|
+
"description": "Path to the workspace"
|
|
50
|
+
},
|
|
51
|
+
"messages": {
|
|
52
|
+
"type": "array",
|
|
53
|
+
"items": {
|
|
54
|
+
"$ref": "#/definitions/message"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"bubbles": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": {
|
|
60
|
+
"$ref": "#/definitions/bubble"
|
|
61
|
+
},
|
|
62
|
+
"description": "Alternative message format (raw Cursor format)"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"message": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"properties": {
|
|
69
|
+
"id": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"description": "Message ID"
|
|
72
|
+
},
|
|
73
|
+
"role": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"enum": ["user", "assistant", "system", "tool"],
|
|
76
|
+
"description": "Message author role"
|
|
77
|
+
},
|
|
78
|
+
"content": {
|
|
79
|
+
"type": ["string", "null"],
|
|
80
|
+
"description": "Message text content"
|
|
81
|
+
},
|
|
82
|
+
"content_blocks": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"description": "Pre-parsed content blocks"
|
|
85
|
+
},
|
|
86
|
+
"created_at": {
|
|
87
|
+
"type": ["string", "null"],
|
|
88
|
+
"description": "ISO timestamp"
|
|
89
|
+
},
|
|
90
|
+
"tool_calls": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"description": "Tool/function calls made"
|
|
93
|
+
},
|
|
94
|
+
"tool_results": {
|
|
95
|
+
"type": "array",
|
|
96
|
+
"description": "Results from tool calls"
|
|
97
|
+
},
|
|
98
|
+
"model": {
|
|
99
|
+
"type": ["string", "null"],
|
|
100
|
+
"description": "Model used for response"
|
|
101
|
+
},
|
|
102
|
+
"tool_call": {
|
|
103
|
+
"$ref": "#/definitions/toolCall",
|
|
104
|
+
"description": "v2: Structured tool call from toolFormerData"
|
|
105
|
+
},
|
|
106
|
+
"thinking": {
|
|
107
|
+
"$ref": "#/definitions/thinking",
|
|
108
|
+
"description": "v2: Thinking/reasoning content"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"bubble": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"description": "Raw Cursor bubble format with v2 toolFormerData",
|
|
115
|
+
"properties": {
|
|
116
|
+
"type": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"enum": ["user", "ai"],
|
|
119
|
+
"description": "Bubble type"
|
|
120
|
+
},
|
|
121
|
+
"text": {
|
|
122
|
+
"type": ["string", "null"],
|
|
123
|
+
"description": "Message text"
|
|
124
|
+
},
|
|
125
|
+
"rawText": {
|
|
126
|
+
"type": ["string", "null"],
|
|
127
|
+
"description": "Raw text before processing"
|
|
128
|
+
},
|
|
129
|
+
"codeBlocks": {
|
|
130
|
+
"type": "array",
|
|
131
|
+
"description": "Code blocks in the message"
|
|
132
|
+
},
|
|
133
|
+
"selections": {
|
|
134
|
+
"type": "array",
|
|
135
|
+
"description": "Code selections referenced"
|
|
136
|
+
},
|
|
137
|
+
"messageId": {
|
|
138
|
+
"type": ["string", "integer"],
|
|
139
|
+
"description": "Message identifier"
|
|
140
|
+
},
|
|
141
|
+
"relevantFiles": {
|
|
142
|
+
"type": "array",
|
|
143
|
+
"description": "Files relevant to this message"
|
|
144
|
+
},
|
|
145
|
+
"capabilityStatuses": {
|
|
146
|
+
"type": "array"
|
|
147
|
+
},
|
|
148
|
+
"toolFormerData": {
|
|
149
|
+
"$ref": "#/definitions/toolFormerData",
|
|
150
|
+
"description": "v2: Tool calling data from newer Cursor versions"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"toolFormerData": {
|
|
155
|
+
"type": "object",
|
|
156
|
+
"description": "Tool calling data structure (v2 indicator)",
|
|
157
|
+
"properties": {
|
|
158
|
+
"toolCalls": {
|
|
159
|
+
"type": "array",
|
|
160
|
+
"items": {
|
|
161
|
+
"$ref": "#/definitions/toolCall"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"toolResults": {
|
|
165
|
+
"type": "array"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
"toolCall": {
|
|
170
|
+
"type": "object",
|
|
171
|
+
"properties": {
|
|
172
|
+
"name": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"description": "Tool name"
|
|
175
|
+
},
|
|
176
|
+
"arguments": {
|
|
177
|
+
"type": ["string", "object"],
|
|
178
|
+
"description": "Tool arguments (JSON string or object)"
|
|
179
|
+
},
|
|
180
|
+
"id": {
|
|
181
|
+
"type": "string",
|
|
182
|
+
"description": "Tool call ID"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"thinking": {
|
|
187
|
+
"type": "object",
|
|
188
|
+
"description": "Thinking/reasoning block",
|
|
189
|
+
"properties": {
|
|
190
|
+
"text": {
|
|
191
|
+
"type": "string"
|
|
192
|
+
},
|
|
193
|
+
"thinking_duration_ms": {
|
|
194
|
+
"type": "number",
|
|
195
|
+
"description": "v2 indicator: duration of thinking"
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Unified timestamp parsing for thread import.
|
|
2
|
+
|
|
3
|
+
Handles ISO 8601 strings (with or without Z suffix), unix epoch
|
|
4
|
+
(seconds or milliseconds), and None/empty values.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from typing import Optional, Union
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def parse_timestamp(ts: Optional[Union[str, int, float]], *, default: Optional[datetime] = None) -> Optional[datetime]:
|
|
12
|
+
"""Parse a timestamp value into a datetime.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
ts: ISO string, unix epoch (seconds or milliseconds), or None
|
|
16
|
+
default: fallback value when ts is None/empty/unparseable.
|
|
17
|
+
If not provided, returns None on failure.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Parsed datetime, or default if parsing fails.
|
|
21
|
+
"""
|
|
22
|
+
if ts is None or ts == "":
|
|
23
|
+
return default
|
|
24
|
+
|
|
25
|
+
# ISO string
|
|
26
|
+
if isinstance(ts, str):
|
|
27
|
+
try:
|
|
28
|
+
if ts.endswith("Z"):
|
|
29
|
+
ts = ts[:-1] + "+00:00"
|
|
30
|
+
dt = datetime.fromisoformat(ts)
|
|
31
|
+
except (ValueError, TypeError):
|
|
32
|
+
return default
|
|
33
|
+
# An offset-less ISO string parses to a naive datetime; treat it as UTC
|
|
34
|
+
# so callers never mix naive and aware datetimes (comparing the two
|
|
35
|
+
# raises TypeError in the event-ordering path). Explicit offsets are kept.
|
|
36
|
+
if dt.tzinfo is None:
|
|
37
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
38
|
+
return dt
|
|
39
|
+
|
|
40
|
+
# Unix timestamp (seconds or milliseconds). Epoch is an absolute instant —
|
|
41
|
+
# build an aware UTC datetime, not a naive-local one (the host's local zone
|
|
42
|
+
# would silently skew the time by the UTC offset).
|
|
43
|
+
if isinstance(ts, (int, float)):
|
|
44
|
+
try:
|
|
45
|
+
if ts > 1_000_000_000_000:
|
|
46
|
+
ts = ts / 1000
|
|
47
|
+
return datetime.fromtimestamp(ts, tz=timezone.utc)
|
|
48
|
+
except (ValueError, OSError):
|
|
49
|
+
return default
|
|
50
|
+
|
|
51
|
+
return default
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def parse_timestamp_iso(ts: Optional[Union[str, int, float]]) -> Optional[str]:
|
|
55
|
+
"""Parse a timestamp and return as ISO string, or None."""
|
|
56
|
+
dt = parse_timestamp(ts)
|
|
57
|
+
return dt.isoformat() if dt else None
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Canonical tool name mapping from provider-specific names to Thread's standard names.
|
|
2
|
+
|
|
3
|
+
Thread uses Claude Code's tool names as the canonical standard (Edit, Write, Read, Bash, etc.)
|
|
4
|
+
since those are the de facto standard in the database. Provider-specific names (edit_file_v2,
|
|
5
|
+
run_terminal_cmd, etc.) are mapped to these canonical names during import.
|
|
6
|
+
|
|
7
|
+
The original provider name is preserved as `provider_tool_name` on content blocks and events.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Provider tool name → Thread canonical name
|
|
11
|
+
TOOL_NAME_MAP: dict[str, str] = {
|
|
12
|
+
# Cursor file tools
|
|
13
|
+
"edit_file_v2": "Edit",
|
|
14
|
+
"edit_file": "Edit",
|
|
15
|
+
"write_file": "Write",
|
|
16
|
+
"read_file": "Read",
|
|
17
|
+
"read_file_v2": "Read",
|
|
18
|
+
# Cursor terminal tools
|
|
19
|
+
"run_terminal_cmd": "Bash",
|
|
20
|
+
"run_terminal_command_v2": "Bash",
|
|
21
|
+
# Cursor search/navigation tools
|
|
22
|
+
"list_dir": "Glob",
|
|
23
|
+
"list_dir_v2": "Glob",
|
|
24
|
+
"search_files": "Grep",
|
|
25
|
+
"codebase_search": "Grep",
|
|
26
|
+
"grep_search": "Grep",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Canonical tool names that operate on files — normalized events MUST have input.file_path
|
|
30
|
+
FILE_TOOLS: frozenset[str] = frozenset({"Edit", "Write", "Read"})
|
|
31
|
+
|
|
32
|
+
# Provider-specific field name that holds the file path in the tool's input dict.
|
|
33
|
+
# None means the input doesn't contain the path at all (must recover from code_blocks).
|
|
34
|
+
PROVIDER_PATH_FIELDS: dict[str, str | None] = {
|
|
35
|
+
"read_file_v2": "path",
|
|
36
|
+
"read_file": "path",
|
|
37
|
+
"edit_file": None,
|
|
38
|
+
"edit_file_v2": None,
|
|
39
|
+
"write_file": "filePath",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def normalize_tool_name(name: str) -> str:
|
|
44
|
+
"""Map a provider tool name to Thread's canonical name.
|
|
45
|
+
|
|
46
|
+
Returns the original name unchanged if no mapping exists.
|
|
47
|
+
"""
|
|
48
|
+
return TOOL_NAME_MAP.get(name, name)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""JSONL truth-log: durable source of truth, with reindex as the recovery primitive.
|
|
2
|
+
|
|
3
|
+
JSONL is truth; ``index.db`` is a rebuildable projection. See :mod:`.jsonl_log`.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .jsonl_log import (
|
|
9
|
+
append_event_row,
|
|
10
|
+
append_kg_event,
|
|
11
|
+
checkpoint,
|
|
12
|
+
log_dir,
|
|
13
|
+
rebuild_truth_from_store,
|
|
14
|
+
record_thread,
|
|
15
|
+
reindex,
|
|
16
|
+
reset_handles,
|
|
17
|
+
scan_truth_counts,
|
|
18
|
+
shared_ingest_lock,
|
|
19
|
+
try_shared_ingest_lock,
|
|
20
|
+
unstage_thread,
|
|
21
|
+
write_events,
|
|
22
|
+
)
|
|
23
|
+
from .repair import repair_truth
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"write_events",
|
|
27
|
+
"append_event_row",
|
|
28
|
+
"append_kg_event",
|
|
29
|
+
"record_thread",
|
|
30
|
+
"unstage_thread",
|
|
31
|
+
"checkpoint",
|
|
32
|
+
"reindex",
|
|
33
|
+
"rebuild_truth_from_store",
|
|
34
|
+
"log_dir",
|
|
35
|
+
"repair_truth",
|
|
36
|
+
"reset_handles",
|
|
37
|
+
"scan_truth_counts",
|
|
38
|
+
"shared_ingest_lock",
|
|
39
|
+
"try_shared_ingest_lock",
|
|
40
|
+
]
|