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.
Files changed (132) hide show
  1. thread_archive/__init__.py +34 -0
  2. thread_archive/_api.py +2235 -0
  3. thread_archive/_config.py +126 -0
  4. thread_archive/_importers/__init__.py +81 -0
  5. thread_archive/_importers/_continuation.py +136 -0
  6. thread_archive/_importers/_cursor.py +103 -0
  7. thread_archive/_importers/_events.py +244 -0
  8. thread_archive/_importers/_line_stream.py +193 -0
  9. thread_archive/_importers/_read.py +82 -0
  10. thread_archive/_importers/_result.py +17 -0
  11. thread_archive/_importers/_sidecar.py +113 -0
  12. thread_archive/_importers/_state.py +240 -0
  13. thread_archive/_importers/_titles.py +179 -0
  14. thread_archive/_importers/antigravity.py +254 -0
  15. thread_archive/_importers/claude_code.py +293 -0
  16. thread_archive/_importers/claude_science.py +330 -0
  17. thread_archive/_importers/cloth.py +20 -0
  18. thread_archive/_importers/codex.py +324 -0
  19. thread_archive/_importers/cowork.py +107 -0
  20. thread_archive/_importers/cursor.py +432 -0
  21. thread_archive/_importers/exports.py +389 -0
  22. thread_archive/_importers/grok.py +600 -0
  23. thread_archive/_importers/opencode.py +538 -0
  24. thread_archive/_knowledge/__init__.py +67 -0
  25. thread_archive/_knowledge/_claims.py +116 -0
  26. thread_archive/_knowledge/_community.py +75 -0
  27. thread_archive/_knowledge/graph.py +208 -0
  28. thread_archive/_knowledge/materialize.py +212 -0
  29. thread_archive/_knowledge/write.py +438 -0
  30. thread_archive/_launchd.py +167 -0
  31. thread_archive/_mcp/__init__.py +1 -0
  32. thread_archive/_mcp/librarian.py +151 -0
  33. thread_archive/_mcp/server.py +307 -0
  34. thread_archive/_retrieval/__init__.py +280 -0
  35. thread_archive/_retrieval/_classify.py +80 -0
  36. thread_archive/_retrieval/_codex.py +157 -0
  37. thread_archive/_retrieval/_context.py +123 -0
  38. thread_archive/_retrieval/_extract.py +184 -0
  39. thread_archive/_retrieval/embed.py +186 -0
  40. thread_archive/_retrieval/format.py +149 -0
  41. thread_archive/_retrieval/fts.py +538 -0
  42. thread_archive/_retrieval/rank.py +228 -0
  43. thread_archive/_retrieval/read.py +908 -0
  44. thread_archive/_retrieval/rerank.py +143 -0
  45. thread_archive/_retrieval/vectors.py +611 -0
  46. thread_archive/_scripts/__init__.py +1 -0
  47. thread_archive/_scripts/backfill_recompute.py +328 -0
  48. thread_archive/_scripts/backfill_reconcile.py +296 -0
  49. thread_archive/_scripts/denamespace_dedup_keys.py +120 -0
  50. thread_archive/_scripts/recover_dropped_events.py +190 -0
  51. thread_archive/_scripts/repair_grok_tool_names.py +118 -0
  52. thread_archive/_scripts/repair_grok_tool_names_backup_20260704T155625Z.json +275 -0
  53. thread_archive/_scripts/repair_grok_tool_names_plan_20260704.json +435 -0
  54. thread_archive/_setup/__init__.py +12 -0
  55. thread_archive/_setup/clients.py +89 -0
  56. thread_archive/_setup/wizard.py +474 -0
  57. thread_archive/_store/__init__.py +45 -0
  58. thread_archive/_store/_base.py +173 -0
  59. thread_archive/_store/_defaults.py +57 -0
  60. thread_archive/_store/_types.py +38 -0
  61. thread_archive/_store/models.py +370 -0
  62. thread_archive/_store/schema.py +84 -0
  63. thread_archive/_thread_import/__init__.py +40 -0
  64. thread_archive/_thread_import/api.py +78 -0
  65. thread_archive/_thread_import/event_builder.py +810 -0
  66. thread_archive/_thread_import/exporters/__init__.py +9 -0
  67. thread_archive/_thread_import/exporters/_cursor_kv_mixin.py +166 -0
  68. thread_archive/_thread_import/exporters/_cursor_parse_mixin.py +149 -0
  69. thread_archive/_thread_import/exporters/cursor.py +582 -0
  70. thread_archive/_thread_import/exporters/cursor_parse.py +145 -0
  71. thread_archive/_thread_import/parsers/__init__.py +191 -0
  72. thread_archive/_thread_import/parsers/base.py +698 -0
  73. thread_archive/_thread_import/parsers/chatgpt.py +722 -0
  74. thread_archive/_thread_import/parsers/chatgpt_content.py +332 -0
  75. thread_archive/_thread_import/parsers/claude.py +443 -0
  76. thread_archive/_thread_import/parsers/claude_code.py +1035 -0
  77. thread_archive/_thread_import/parsers/claude_code_blocks.py +415 -0
  78. thread_archive/_thread_import/parsers/claude_code_ide.py +122 -0
  79. thread_archive/_thread_import/parsers/claude_code_sessions.py +90 -0
  80. thread_archive/_thread_import/parsers/config/__init__.py +26 -0
  81. thread_archive/_thread_import/parsers/config/base.py +220 -0
  82. thread_archive/_thread_import/parsers/cursor.py +538 -0
  83. thread_archive/_thread_import/parsers/cursor_blocks.py +365 -0
  84. thread_archive/_thread_import/parsers/pipeline/__init__.py +29 -0
  85. thread_archive/_thread_import/parsers/pipeline/base.py +251 -0
  86. thread_archive/_thread_import/parsers/pipeline/interfaces.py +191 -0
  87. thread_archive/_thread_import/parsers/transformers/__init__.py +22 -0
  88. thread_archive/_thread_import/parsers/transformers/active_path.py +87 -0
  89. thread_archive/_thread_import/parsers/transformers/coalescing.py +389 -0
  90. thread_archive/_thread_import/parsers/transformers/ide_context.py +158 -0
  91. thread_archive/_thread_import/parsers/transformers/thinking_merge.py +68 -0
  92. thread_archive/_thread_import/parsers/types/__init__.py +56 -0
  93. thread_archive/_thread_import/parsers/types/chatgpt.py +99 -0
  94. thread_archive/_thread_import/parsers/types/claude.py +120 -0
  95. thread_archive/_thread_import/parsers/types/claude_code.py +139 -0
  96. thread_archive/_thread_import/parsers/types/cursor.py +109 -0
  97. thread_archive/_thread_import/parsers/validators/__init__.py +83 -0
  98. thread_archive/_thread_import/parsers/validators/base.py +168 -0
  99. thread_archive/_thread_import/parsers/validators/content.py +80 -0
  100. thread_archive/_thread_import/parsers/validators/referential.py +57 -0
  101. thread_archive/_thread_import/parsers/validators/thinking.py +143 -0
  102. thread_archive/_thread_import/parsers/validators/types.py +87 -0
  103. thread_archive/_thread_import/schemas/__init__.py +231 -0
  104. thread_archive/_thread_import/schemas/chatgpt/v1.json +197 -0
  105. thread_archive/_thread_import/schemas/chatgpt/v2.json +203 -0
  106. thread_archive/_thread_import/schemas/claude/v1.json +188 -0
  107. thread_archive/_thread_import/schemas/claude_code/v1.json +183 -0
  108. thread_archive/_thread_import/schemas/cursor/v1.json +143 -0
  109. thread_archive/_thread_import/schemas/cursor/v2.json +200 -0
  110. thread_archive/_thread_import/timestamps.py +57 -0
  111. thread_archive/_thread_import/tool_names.py +48 -0
  112. thread_archive/_truth/__init__.py +40 -0
  113. thread_archive/_truth/jsonl_log.py +2340 -0
  114. thread_archive/_truth/repair.py +322 -0
  115. thread_archive/_watcher/__init__.py +57 -0
  116. thread_archive/_watcher/base.py +65 -0
  117. thread_archive/_watcher/daemon.py +289 -0
  118. thread_archive/_watcher/export_drop.py +203 -0
  119. thread_archive/_watcher/exthost.py +316 -0
  120. thread_archive/_watcher/lazy.py +117 -0
  121. thread_archive/_watcher/sources.py +616 -0
  122. thread_archive/_web/__init__.py +15 -0
  123. thread_archive/_web/server.py +299 -0
  124. thread_archive/_web/static/assets/index-DECSH1Mz.css +10 -0
  125. thread_archive/_web/static/assets/index-Djq0FK0y.js +101 -0
  126. thread_archive/_web/static/index.html +13 -0
  127. thread_archive/cli.py +746 -0
  128. thread_archive-0.0.2.dist-info/METADATA +296 -0
  129. thread_archive-0.0.2.dist-info/RECORD +132 -0
  130. thread_archive-0.0.2.dist-info/WHEEL +4 -0
  131. thread_archive-0.0.2.dist-info/entry_points.txt +6 -0
  132. thread_archive-0.0.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,145 @@
1
+ """Pure per-message-shape parsing helpers for the Cursor exporter.
2
+
3
+ Stateless functions that keep ``CursorExporter`` thin. The emitted dict/list
4
+ shapes are an import contract and must stay byte-for-byte stable.
5
+
6
+ No filesystem, no SQLite, no instance state — pure transforms over the raw
7
+ composer/bubble/message blobs that Cursor stores. ``CursorExporter``'s pinned
8
+ methods delegate here.
9
+ """
10
+
11
+ from typing import Any, Dict, List, Optional
12
+
13
+
14
+ def kv_role(bubble: Dict[str, Any], header: Dict[str, Any]) -> str:
15
+ """Map a Cursor bubble/header ``type`` (1/2) onto a canonical role."""
16
+ bubble_type = bubble.get("type") or header.get("type")
17
+ if bubble_type == 1:
18
+ return "user"
19
+ if bubble_type == 2:
20
+ return "assistant"
21
+ return "unknown"
22
+
23
+
24
+ def build_kv_message(
25
+ bubble_id: str,
26
+ bubble: Dict[str, Any],
27
+ header: Dict[str, Any],
28
+ idx: int,
29
+ ) -> Dict[str, Any]:
30
+ """Build one conversation message from a composer header + its bubble."""
31
+ message = {
32
+ "id": bubble_id,
33
+ "role": kv_role(bubble, header),
34
+ "content": bubble.get("text", ""),
35
+ "created_at": bubble.get("createdAt"),
36
+ "index": idx,
37
+ }
38
+
39
+ tool_data = bubble.get("toolFormerData")
40
+ if tool_data and isinstance(tool_data, dict):
41
+ message["tool_call"] = {
42
+ "name": tool_data.get("name", "unknown"),
43
+ "tool_id": tool_data.get("tool"),
44
+ "call_id": tool_data.get("toolCallId"),
45
+ "status": tool_data.get("status"),
46
+ "args": tool_data.get("rawArgs"),
47
+ "params": tool_data.get("params"),
48
+ "result": tool_data.get("result"),
49
+ "user_decision": tool_data.get("userDecision"),
50
+ }
51
+
52
+ code_blocks = bubble.get("codeBlocks", [])
53
+ if code_blocks:
54
+ message["code_blocks"] = code_blocks
55
+
56
+ thinking = bubble.get("thinking")
57
+ if thinking and isinstance(thinking, dict):
58
+ thinking_text = thinking.get("text", "")
59
+ if thinking_text:
60
+ message["thinking"] = thinking_text
61
+ message["thinking_duration_ms"] = bubble.get("thinkingDurationMs")
62
+
63
+ if bubble.get("serverBubbleId"):
64
+ message["server_bubble_id"] = bubble["serverBubbleId"]
65
+
66
+ return message
67
+
68
+
69
+ def build_kv_metadata(composer: Dict[str, Any]) -> Dict[str, Any]:
70
+ """Extract the conversation metadata block from a composer record."""
71
+ return {
72
+ "mode": composer.get("unifiedMode"),
73
+ "status": composer.get("status"),
74
+ "is_agentic": composer.get("isAgentic"),
75
+ "context_tokens": composer.get("contextTokensUsed"),
76
+ "lines_added": composer.get("totalLinesAdded"),
77
+ "lines_removed": composer.get("totalLinesRemoved"),
78
+ "branch": composer.get("createdOnBranch"),
79
+ }
80
+
81
+
82
+ def normalize_role(msg: Dict[str, Any]) -> Optional[str]:
83
+ """Map Cursor's role/type/sender aliases onto canonical role names."""
84
+ role = msg.get("role") or msg.get("type") or msg.get("sender")
85
+ if role:
86
+ role = role.lower()
87
+ if role in ("human", "user"):
88
+ role = "user"
89
+ elif role in ("assistant", "ai", "bot"):
90
+ role = "assistant"
91
+ elif role in ("system",):
92
+ role = "system"
93
+ elif role in ("tool", "function"):
94
+ role = "tool"
95
+ return role
96
+
97
+
98
+ def content_blocks_from_content(raw_content: Any) -> List[Dict[str, Any]]:
99
+ """Build text content blocks from a message's raw ``content`` field."""
100
+ content_blocks: List[Dict[str, Any]] = []
101
+ if isinstance(raw_content, list):
102
+ for block in raw_content:
103
+ if isinstance(block, dict):
104
+ content_blocks.append(block)
105
+ elif isinstance(block, str):
106
+ content_blocks.append({"type": "text", "text": block})
107
+ elif isinstance(raw_content, str):
108
+ content_blocks.append({"type": "text", "text": raw_content})
109
+ return content_blocks
110
+
111
+
112
+ def parse_message(msg: Any, index: int = 0) -> Optional[Dict[str, Any]]:
113
+ """Parse a single message from Cursor's format."""
114
+ if not isinstance(msg, dict):
115
+ return None
116
+
117
+ role = normalize_role(msg)
118
+ content = msg.get("content") or msg.get("text") or msg.get("message") or ""
119
+ content_blocks = content_blocks_from_content(msg.get("content"))
120
+
121
+ tool_calls = msg.get("tool_calls") or msg.get("toolCalls") or msg.get("function_call")
122
+ tool_results = msg.get("tool_results") or msg.get("toolResults")
123
+ thinking = msg.get("thinking") or msg.get("reasoning")
124
+
125
+ parsed = {
126
+ "id": msg.get("id") or msg.get("messageId") or f"msg_{index}",
127
+ "role": role or "unknown",
128
+ "content": content if isinstance(content, str) else "",
129
+ "content_blocks": content_blocks,
130
+ "created_at": msg.get("createdAt") or msg.get("created_at") or msg.get("timestamp"),
131
+ "index": index,
132
+ "raw_data": msg,
133
+ }
134
+
135
+ if tool_calls:
136
+ parsed["tool_calls"] = tool_calls
137
+ if tool_results:
138
+ parsed["tool_results"] = tool_results
139
+ if thinking:
140
+ parsed["thinking"] = thinking
141
+
142
+ if msg.get("model"):
143
+ parsed["model"] = msg["model"]
144
+
145
+ return parsed
@@ -0,0 +1,191 @@
1
+ """
2
+ Provider-specific parsers for chat export files.
3
+
4
+ Each parser transforms a provider's export format into a standardized
5
+ message format with unified content_blocks schema.
6
+
7
+ ## Architecture
8
+
9
+ The parser system uses a pipeline pattern with clear boundaries:
10
+
11
+ 1. **Types** (`types/`): TypedDict schemas for provider input formats
12
+ 2. **Config** (`config/`): Provider-specific configuration (ProviderConfig)
13
+ 3. **Pipeline** (`pipeline/`): Composable parse -> transform -> normalize -> validate
14
+ 4. **Validators** (`validators/`): Pluggable validation rules
15
+ 5. **Transformers** (`transformers/`): Message transformations (coalescing, etc.)
16
+
17
+ ## Content Block Types
18
+
19
+ - text: Plain text content
20
+ - thinking: Model thinking/reasoning
21
+ - tool_use: Tool/function call
22
+ - tool_result: Tool/function result
23
+ - image: Image content
24
+ - file: File attachment
25
+ - code: Code execution
26
+ - system_context: System/user context
27
+ - ide_context: IDE state (opened files, selections)
28
+ - file_snapshot: File state snapshots
29
+ - context_summary: Conversation summaries
30
+ - parse_error: Preserved malformed data
31
+
32
+ ## Field Mapping
33
+
34
+ - Each parser uses explicit FieldMapping declarations
35
+ - Maps provider JSON fields to our semantic model
36
+ - Documents what each field MEANS, not just where it comes from
37
+ """
38
+
39
+ from typing import Type
40
+
41
+ # Base types and infrastructure
42
+ from .base import (
43
+ CodeBlock,
44
+ ContentBlock,
45
+ FieldMapping,
46
+ FileBlock,
47
+ ImageBlock,
48
+ ImportResult,
49
+ NormalizedMessage,
50
+ ProviderParser,
51
+ SemanticCheck,
52
+ SystemContextBlock,
53
+ TextBlock,
54
+ ThinkingBlock,
55
+ ToolResultBlock,
56
+ ToolUseBlock,
57
+ ValidationContext,
58
+ ValidationSeverity,
59
+ )
60
+
61
+ # Existing parsers (legacy interface, still functional)
62
+ from .chatgpt import ChatGPTParser
63
+ from .claude import ClaudeParser
64
+ from .claude_code import ClaudeCodeParser
65
+
66
+ # Provider configurations
67
+ from .config import (
68
+ CHATGPT_CONFIG,
69
+ CLAUDE_CODE_CONFIG,
70
+ CLAUDE_CONFIG,
71
+ CURSOR_CONFIG,
72
+ ProviderConfig,
73
+ ThinkingExpectation,
74
+ get_provider_config,
75
+ )
76
+ from .cursor import CursorParser
77
+
78
+ # Pipeline infrastructure
79
+ from .pipeline import (
80
+ Normalizer,
81
+ Parser,
82
+ ParserPipeline,
83
+ RawMessage,
84
+ Transformer,
85
+ Validator,
86
+ )
87
+
88
+ # Transformers
89
+ from .transformers import (
90
+ ActivePathTransformer,
91
+ IDEContextTransformer,
92
+ ThinkingMergeTransformer,
93
+ ToolCoalescingTransformer,
94
+ )
95
+
96
+ # Validators
97
+ from .validators import (
98
+ BaseValidator,
99
+ ContentValidator,
100
+ ReferentialIntegrityValidator,
101
+ ThinkingBlockValidator,
102
+ TypeValidator,
103
+ )
104
+
105
+ # Registry of parser classes (not instances - we create instances with config)
106
+ PARSER_CLASSES: dict[str, Type[ProviderParser]] = {
107
+ "chatgpt": ChatGPTParser,
108
+ "claude": ClaudeParser,
109
+ "claude-code": ClaudeCodeParser,
110
+ "cursor": CursorParser,
111
+ # Add more providers here:
112
+ # 'gemini': GeminiParser,
113
+ # 'copilot': CopilotParser,
114
+ }
115
+
116
+ # Available provider names
117
+ PROVIDERS = list(PARSER_CLASSES.keys())
118
+
119
+
120
+ def get_parser(provider: str, strict: bool = False) -> ProviderParser:
121
+ """
122
+ Get a parser instance for a provider.
123
+
124
+ Args:
125
+ provider: Provider name (chatgpt, claude, cursor)
126
+ strict: If True, validation warnings become errors
127
+
128
+ Returns:
129
+ Parser instance configured with the given strictness level
130
+ """
131
+ if provider not in PARSER_CLASSES:
132
+ raise ValueError(f"Unknown provider '{provider}'. Available: {PROVIDERS}")
133
+ return PARSER_CLASSES[provider](strict=strict)
134
+
135
+
136
+ __all__ = [
137
+ # Base types
138
+ "ProviderParser",
139
+ "NormalizedMessage",
140
+ "ContentBlock",
141
+ "ImportResult",
142
+ # Field mapping infrastructure
143
+ "FieldMapping",
144
+ "SemanticCheck",
145
+ "ValidationContext",
146
+ "ValidationSeverity",
147
+ # Block types
148
+ "TextBlock",
149
+ "ThinkingBlock",
150
+ "ToolUseBlock",
151
+ "ToolResultBlock",
152
+ "ImageBlock",
153
+ "FileBlock",
154
+ "CodeBlock",
155
+ "SystemContextBlock",
156
+ # Provider configurations
157
+ "ProviderConfig",
158
+ "ThinkingExpectation",
159
+ "CHATGPT_CONFIG",
160
+ "CLAUDE_CONFIG",
161
+ "CLAUDE_CODE_CONFIG",
162
+ "CURSOR_CONFIG",
163
+ "get_provider_config",
164
+ # Pipeline infrastructure
165
+ "RawMessage",
166
+ "Parser",
167
+ "Transformer",
168
+ "Normalizer",
169
+ "Validator",
170
+ "ParserPipeline",
171
+ # Validators
172
+ "BaseValidator",
173
+ "ThinkingBlockValidator",
174
+ "ReferentialIntegrityValidator",
175
+ "TypeValidator",
176
+ "ContentValidator",
177
+ # Transformers
178
+ "ToolCoalescingTransformer",
179
+ "ActivePathTransformer",
180
+ "ThinkingMergeTransformer",
181
+ "IDEContextTransformer",
182
+ # Parsers (legacy interface)
183
+ "ChatGPTParser",
184
+ "ClaudeParser",
185
+ "ClaudeCodeParser",
186
+ "CursorParser",
187
+ "PARSER_CLASSES",
188
+ "PROVIDERS",
189
+ "get_parser",
190
+ ]
191
+