chatstrata 0.2.2__tar.gz → 0.2.3__tar.gz

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 (75) hide show
  1. {chatstrata-0.2.2 → chatstrata-0.2.3}/CHANGELOG.md +5 -0
  2. {chatstrata-0.2.2 → chatstrata-0.2.3}/PKG-INFO +2 -2
  3. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/codex_cli/adapter.py +14 -2
  4. chatstrata-0.2.3/chatstrata/sources/omp/__init__.py +3 -0
  5. chatstrata-0.2.3/chatstrata/sources/omp/adapter.py +323 -0
  6. chatstrata-0.2.3/chatstrata/sources/omp/manifest.yaml +12 -0
  7. {chatstrata-0.2.2 → chatstrata-0.2.3}/pyproject.toml +2 -1
  8. {chatstrata-0.2.2 → chatstrata-0.2.3}/.gitignore +0 -0
  9. {chatstrata-0.2.2 → chatstrata-0.2.3}/CONTRIBUTING.md +0 -0
  10. {chatstrata-0.2.2 → chatstrata-0.2.3}/LICENSE +0 -0
  11. {chatstrata-0.2.2 → chatstrata-0.2.3}/README.md +0 -0
  12. {chatstrata-0.2.2 → chatstrata-0.2.3}/SECURITY.md +0 -0
  13. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/__init__.py +0 -0
  14. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/analysis/__init__.py +0 -0
  15. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/analysis/cli.py +0 -0
  16. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/analysis/queries/activity.sql +0 -0
  17. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/analysis/queries/conversations.sql +0 -0
  18. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/analysis/queries/example_queries.sql +0 -0
  19. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/analysis/queries/models.sql +0 -0
  20. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/analysis/queries/projects.sql +0 -0
  21. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/analysis/queries/tools.sql +0 -0
  22. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/cli.py +0 -0
  23. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/core/__init__.py +0 -0
  24. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/core/db.py +0 -0
  25. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/core/ingest.py +0 -0
  26. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/core/migrations/0001_initial.sql +0 -0
  27. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/core/migrations/0002_fts_index.sql +0 -0
  28. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/core/migrations/0003_conversation_mtime.sql +0 -0
  29. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/core/migrations/__init__.py +0 -0
  30. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/core/models.py +0 -0
  31. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/core/search.py +0 -0
  32. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/embed/__init__.py +0 -0
  33. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/embed/base.py +0 -0
  34. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/embed/cli.py +0 -0
  35. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/embed/local_provider.py +0 -0
  36. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/embed/search.py +0 -0
  37. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/mcp/README.md +0 -0
  38. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/mcp/__init__.py +0 -0
  39. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/mcp/safety.py +0 -0
  40. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/mcp/server.py +0 -0
  41. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/redact/__init__.py +0 -0
  42. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/redact/base.py +0 -0
  43. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/redact/cli.py +0 -0
  44. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/redact/presidio_engine.py +0 -0
  45. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/redact/recognizers/__init__.py +0 -0
  46. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/redact/recognizers/api_keys.py +0 -0
  47. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/redact/recognizers/connection_strings.py +0 -0
  48. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/redact/recognizers/paths.py +0 -0
  49. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/redact/recognizers/tokens.py +0 -0
  50. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/schedule/__init__.py +0 -0
  51. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/schedule/cli.py +0 -0
  52. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/schedule/launchd.py +0 -0
  53. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/__init__.py +0 -0
  54. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/base.py +0 -0
  55. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/claude_code/__init__.py +0 -0
  56. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/claude_code/adapter.py +0 -0
  57. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/claude_code/manifest.yaml +0 -0
  58. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/claude_export/__init__.py +0 -0
  59. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/claude_export/adapter.py +0 -0
  60. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/claude_export/manifest.yaml +0 -0
  61. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/codex_cli/__init__.py +0 -0
  62. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/codex_cli/manifest.yaml +0 -0
  63. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/opencode/__init__.py +0 -0
  64. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/opencode/adapter.py +0 -0
  65. {chatstrata-0.2.2 → chatstrata-0.2.3}/chatstrata/sources/opencode/manifest.yaml +0 -0
  66. {chatstrata-0.2.2 → chatstrata-0.2.3}/docs/adapter-guide.md +0 -0
  67. {chatstrata-0.2.2 → chatstrata-0.2.3}/docs/adr/0001-use-duckdb.md +0 -0
  68. {chatstrata-0.2.2 → chatstrata-0.2.3}/docs/adr/0002-store-raw-alongside-normalized.md +0 -0
  69. {chatstrata-0.2.2 → chatstrata-0.2.3}/docs/adr/0003-adapter-pattern-entry-points.md +0 -0
  70. {chatstrata-0.2.2 → chatstrata-0.2.3}/docs/images/chatstrata.png +0 -0
  71. {chatstrata-0.2.2 → chatstrata-0.2.3}/docs/redaction.md +0 -0
  72. {chatstrata-0.2.2 → chatstrata-0.2.3}/docs/release.md +0 -0
  73. {chatstrata-0.2.2 → chatstrata-0.2.3}/docs/schema.md +0 -0
  74. {chatstrata-0.2.2 → chatstrata-0.2.3}/docs/sources/claude_export.md +0 -0
  75. {chatstrata-0.2.2 → chatstrata-0.2.3}/docs/sources/codex_cli.md +0 -0
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3 - 2026-09-12
4
+
5
+ - Add Oh My Pi (`omp`) source adapter: ingests session transcripts from `~/.omp/agent/sessions/`, including tool calls/results, thinking blocks, extension (`custom_message`) turns, titles, and project paths from the session header.
6
+ - Fix `codex_cli` adapter: newer Codex rollouts encode tool-result `output` fields as content arrays instead of plain strings; these now parse correctly.
7
+
3
8
  ## 0.2.2 - 2026-07-29
4
9
 
5
10
  - Cap the `mcp` dependency at `>=1.12.0,<2`. `mcp` 2.0.0 removed the `mcp.server.fastmcp` module that `chatstrata/mcp/server.py` imports, so the previously unbounded pin resolved 2.0.0 and crashed `chatstrata-mcp` on startup with `ModuleNotFoundError: No module named 'mcp.server.fastmcp'`.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: chatstrata
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A personal, queryable archive of your AI conversations across providers
5
5
  Project-URL: Homepage, https://github.com/brandonbosch/chatstrata
6
6
  Project-URL: Repository, https://github.com/brandonbosch/chatstrata
@@ -93,6 +93,18 @@ def _text_from_content(content: list[dict[str, Any]]) -> str | None:
93
93
  return "\n".join(parts) if parts else None
94
94
 
95
95
 
96
+ def _text_from_value(value: Any) -> str | None:
97
+ """Normalize text fields that newer Codex rollouts encode as content arrays."""
98
+ if isinstance(value, str):
99
+ return value
100
+ if isinstance(value, list):
101
+ return _text_from_content(value)
102
+ if isinstance(value, dict):
103
+ text = value.get("text")
104
+ return text if isinstance(text, str) else None
105
+ return None
106
+
107
+
96
108
  def _blocks_from_response_item(
97
109
  payload: dict[str, Any],
98
110
  ) -> tuple[Role | None, list[ContentBlock]]:
@@ -139,7 +151,7 @@ def _blocks_from_response_item(
139
151
  ContentBlock(
140
152
  type=BlockType.TOOL_RESULT,
141
153
  tool_use_id=payload.get("call_id"),
142
- text=payload.get("output"),
154
+ text=_text_from_value(payload.get("output")),
143
155
  )
144
156
  ]
145
157
 
@@ -172,7 +184,7 @@ def _blocks_from_response_item(
172
184
  ContentBlock(
173
185
  type=BlockType.TOOL_RESULT,
174
186
  tool_use_id=payload.get("call_id"),
175
- text=payload.get("output"),
187
+ text=_text_from_value(payload.get("output")),
176
188
  )
177
189
  ]
178
190
 
@@ -0,0 +1,3 @@
1
+ from chatstrata.sources.omp.adapter import OmpAdapter
2
+
3
+ __all__ = ["OmpAdapter"]
@@ -0,0 +1,323 @@
1
+ """Oh My Pi (omp) adapter.
2
+
3
+ Oh My Pi writes one JSONL file per session to:
4
+ ~/.omp/agent/sessions/<encoded-cwd>/<timestamp>_<session-id>.jsonl
5
+
6
+ Each file physically begins with a fixed-width 256-byte title slot
7
+ ({"type": "title", ...} padded with spaces), followed by a session header
8
+ ({"type": "session", ...}) and then append-only SessionEntry values.
9
+
10
+ Conversation content lives in "message" entries whose persisted roles are
11
+ camelCase: "user", "assistant", "toolResult" (plus rarer coding-agent roles
12
+ like "bashExecution" we skip). Tool calls are content blocks of type
13
+ "toolCall" inside assistant messages; results are separate "toolResult"
14
+ messages carrying toolCallId/toolName. Extensions may add "custom_message"
15
+ entries that participate in the transcript.
16
+
17
+ Entries form a tree via id/parentId (branching, compaction); this adapter
18
+ flattens in file order, preserving ids so lineage stays queryable.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ from collections.abc import Iterable
25
+ from datetime import datetime
26
+ from pathlib import Path
27
+ from typing import Any
28
+
29
+ from chatstrata.core.models import (
30
+ BlockType,
31
+ ContentBlock,
32
+ ConversationHandle,
33
+ ParsedConversation,
34
+ ParsedMessage,
35
+ Role,
36
+ )
37
+
38
+ DEFAULT_OMP_DIR = Path("~/.omp/agent/sessions").expanduser()
39
+
40
+ _ROLE_MAP: dict[str, Role] = {
41
+ "user": Role.USER,
42
+ "assistant": Role.ASSISTANT,
43
+ "toolResult": Role.TOOL,
44
+ }
45
+
46
+ # Roles persisted by omp that carry no conversational value for the archive
47
+ # (tool-feedback bookkeeping, standalone shell runs, legacy hook payloads).
48
+ _SKIPPED_ROLES = {"developer", "hookMessage", "bashExecution", "pythonExecution", "fileMention"}
49
+
50
+
51
+ def _parse_timestamp(s: Any) -> datetime | None:
52
+ """omp timestamps are ISO 8601 strings, usually with 'Z' suffix."""
53
+ if not isinstance(s, str):
54
+ return None
55
+ try:
56
+ return datetime.fromisoformat(s.replace("Z", "+00:00"))
57
+ except ValueError:
58
+ return None
59
+
60
+
61
+ def _text_from_content(content: Any) -> str | None:
62
+ """Extract concatenated text from a content string or block list."""
63
+ if isinstance(content, str):
64
+ return content
65
+ if not isinstance(content, list):
66
+ return None
67
+ parts = [
68
+ block.get("text", "")
69
+ for block in content
70
+ if isinstance(block, dict) and block.get("type") == "text" and isinstance(block.get("text"), str)
71
+ ]
72
+ return "\n".join(parts) if parts else None
73
+
74
+
75
+ def _blocks_from_content(content: Any) -> list[ContentBlock]:
76
+ """Convert an omp message content value into ContentBlocks.
77
+
78
+ Content may be a plain string or a list of typed blocks: "text",
79
+ "thinking", "toolCall" (assistant-side), and "image".
80
+ """
81
+ if content is None:
82
+ return []
83
+ if isinstance(content, str):
84
+ return [ContentBlock(type=BlockType.TEXT, text=content)] if content else []
85
+
86
+ blocks: list[ContentBlock] = []
87
+ for item in content:
88
+ if not isinstance(item, dict):
89
+ continue
90
+ ctype = item.get("type")
91
+ if ctype == "text":
92
+ text = item.get("text")
93
+ if isinstance(text, str) and text:
94
+ blocks.append(ContentBlock(type=BlockType.TEXT, text=text))
95
+ elif ctype == "thinking":
96
+ text = item.get("thinking")
97
+ if isinstance(text, str) and text:
98
+ blocks.append(ContentBlock(type=BlockType.THINKING, text=text))
99
+ elif ctype == "toolCall":
100
+ arguments = item.get("arguments")
101
+ if isinstance(arguments, str):
102
+ try:
103
+ arguments = json.loads(arguments)
104
+ except json.JSONDecodeError:
105
+ arguments = {"raw": arguments}
106
+ blocks.append(
107
+ ContentBlock(
108
+ type=BlockType.TOOL_USE,
109
+ tool_name=item.get("name"),
110
+ tool_use_id=item.get("id"),
111
+ payload={"arguments": arguments},
112
+ )
113
+ )
114
+ elif ctype == "image":
115
+ payload = {k: v for k, v in item.items() if k != "type"}
116
+ blocks.append(ContentBlock(type=BlockType.IMAGE, payload=payload))
117
+ return blocks
118
+
119
+
120
+ def _blocks_from_tool_result(message: dict[str, Any]) -> list[ContentBlock]:
121
+ """Convert a toolResult message into a single TOOL_RESULT block."""
122
+ text = _text_from_content(message.get("content"))
123
+ details = message.get("details")
124
+ payload: dict[str, Any] = {}
125
+ if isinstance(details, dict):
126
+ payload["details"] = details
127
+ if message.get("isError"):
128
+ payload["isError"] = True
129
+ return [
130
+ ContentBlock(
131
+ type=BlockType.TOOL_RESULT,
132
+ tool_name=message.get("toolName"),
133
+ tool_use_id=message.get("toolCallId"),
134
+ text=text,
135
+ payload=payload,
136
+ )
137
+ ]
138
+
139
+
140
+ def _decode_project_dir(dir_name: str) -> str:
141
+ """Best-effort decode of omp's encoded cwd bucket name back into a path.
142
+
143
+ Buckets under home are spelled "-<relative>" (e.g. "-git-chatstrata");
144
+ absolute fallback buckets are "--<encoded>--". parse() prefers the
145
+ lossless cwd from the session header; this is only a backstop.
146
+ """
147
+ if dir_name.startswith("--") and dir_name.endswith("--"):
148
+ return "/" + dir_name[2:-2].replace("-", "/")
149
+ if dir_name.startswith("-"):
150
+ return str(Path.home() / dir_name[1:].replace("-", "/"))
151
+ return dir_name
152
+
153
+
154
+ class OmpAdapter:
155
+ """Adapter for Oh My Pi session transcripts."""
156
+
157
+ name = "omp"
158
+ display_name = "Oh My Pi"
159
+ version = "0.1.0"
160
+ schema_version = 1
161
+
162
+ def discover(self, config: dict | None = None) -> Iterable[ConversationHandle]:
163
+ """Walk ~/.omp/agent/sessions/**/*.jsonl and yield a handle per file."""
164
+ root = Path((config or {}).get("path") or DEFAULT_OMP_DIR).expanduser()
165
+ if not root.exists():
166
+ return
167
+ for jsonl in sorted(root.glob("*/*.jsonl")):
168
+ stem = jsonl.stem
169
+ # Files are <timestamp>_<session-id>.jsonl; the id is the suffix
170
+ # after the first underscore in the timestamp portion.
171
+ session_id = stem.split("_", 1)[1] if "_" in stem else stem
172
+ project = _decode_project_dir(jsonl.parent.name)
173
+ yield ConversationHandle(
174
+ source_native_id=session_id,
175
+ path=jsonl,
176
+ metadata={"project": project},
177
+ )
178
+
179
+ def parse(self, handle: ConversationHandle) -> ParsedConversation:
180
+ if handle.path is None:
181
+ raise ValueError("OmpAdapter requires a path on the handle")
182
+ path = handle.path
183
+
184
+ events: list[dict[str, Any]] = []
185
+ with path.open("r", encoding="utf-8") as f:
186
+ for line in f:
187
+ line = line.strip()
188
+ if not line:
189
+ continue
190
+ try:
191
+ events.append(json.loads(line))
192
+ except json.JSONDecodeError:
193
+ # Skip malformed lines; one bad line should not kill an
194
+ # entire session import.
195
+ continue
196
+
197
+ messages: list[ParsedMessage] = []
198
+ title: str | None = None
199
+ project: str | None = None
200
+ started_at: datetime | None = None
201
+ ended_at: datetime | None = None
202
+
203
+ for ev in events:
204
+ etype = ev.get("type")
205
+
206
+ if etype == "title":
207
+ # Fixed-width title slot; folded into the header below when
208
+ # the header lacks a title.
209
+ if title is None:
210
+ slot_title = ev.get("title")
211
+ if isinstance(slot_title, str) and slot_title.strip():
212
+ title = slot_title.strip()
213
+ continue
214
+
215
+ if etype == "session":
216
+ header_title = ev.get("title")
217
+ if isinstance(header_title, str) and header_title.strip():
218
+ title = header_title.strip()
219
+ cwd = ev.get("cwd")
220
+ if isinstance(cwd, str):
221
+ project = cwd
222
+ ts = _parse_timestamp(ev.get("timestamp"))
223
+ if ts:
224
+ started_at = ts
225
+ continue
226
+
227
+ if etype == "message":
228
+ message = ev.get("message") or {}
229
+ role_name = message.get("role")
230
+ if not isinstance(role_name, str) or role_name in _SKIPPED_ROLES:
231
+ continue
232
+
233
+ if role_name == "toolResult":
234
+ blocks = _blocks_from_tool_result(message)
235
+ else:
236
+ blocks = _blocks_from_content(message.get("content"))
237
+
238
+ if not blocks:
239
+ continue
240
+
241
+ role = _ROLE_MAP[role_name]
242
+ ts = _parse_timestamp(ev.get("timestamp"))
243
+ if ts:
244
+ if started_at is None or ts < started_at:
245
+ started_at = ts
246
+ if ended_at is None or ts > ended_at:
247
+ ended_at = ts
248
+
249
+ metadata: dict[str, Any] = {}
250
+ attribution = message.get("attribution")
251
+ if isinstance(attribution, str):
252
+ metadata["attribution"] = attribution
253
+
254
+ messages.append(
255
+ ParsedMessage(
256
+ source_native_id=ev.get("id"),
257
+ parent_source_native_id=ev.get("parentId"),
258
+ role=role,
259
+ model=message.get("model"),
260
+ created_at=ts,
261
+ blocks=blocks,
262
+ metadata=metadata,
263
+ )
264
+ )
265
+ continue
266
+
267
+ if etype == "custom_message":
268
+ # Extension messages that participate in the transcript.
269
+ attribution = ev.get("attribution")
270
+ role = Role.USER if attribution == "user" else Role.ASSISTANT
271
+ text = _text_from_content(ev.get("content"))
272
+ if not text:
273
+ continue
274
+ ts = _parse_timestamp(ev.get("timestamp"))
275
+ if ts:
276
+ if started_at is None or ts < started_at:
277
+ started_at = ts
278
+ if ended_at is None or ts > ended_at:
279
+ ended_at = ts
280
+ messages.append(
281
+ ParsedMessage(
282
+ source_native_id=ev.get("id"),
283
+ parent_source_native_id=ev.get("parentId"),
284
+ role=role,
285
+ created_at=ts,
286
+ blocks=[ContentBlock(type=BlockType.TEXT, text=text)],
287
+ metadata=(
288
+ {"attribution": attribution}
289
+ if isinstance(attribution, str)
290
+ else {}
291
+ ),
292
+ )
293
+ )
294
+ continue
295
+
296
+ # Other entry types (compaction, model_change, custom, ...) affect
297
+ # replay state, not transcript content; skip them.
298
+
299
+ if project is None and handle.metadata:
300
+ project = handle.metadata.get("project")
301
+
302
+ # Fall back to the first user text block as a title.
303
+ if title is None:
304
+ for m in messages:
305
+ if m.role == Role.USER:
306
+ for b in m.blocks:
307
+ if b.type == BlockType.TEXT and b.text:
308
+ title = b.text.strip().splitlines()[0][:200]
309
+ break
310
+ if title:
311
+ break
312
+
313
+ return ParsedConversation(
314
+ source_native_id=handle.source_native_id,
315
+ title=title,
316
+ project=project,
317
+ started_at=started_at,
318
+ ended_at=ended_at,
319
+ messages=messages,
320
+ raw_path=str(path),
321
+ metadata={"event_count": len(events)},
322
+ raw_events=events,
323
+ )
@@ -0,0 +1,12 @@
1
+ name: omp
2
+ display_name: Oh My Pi
3
+ version: "0.1.0"
4
+ schema_version: 1
5
+ description: |
6
+ Ingests Oh My Pi (omp) session transcripts from ~/.omp/agent/sessions/.
7
+ Each session is a JSONL file beginning with a fixed-width title slot and a
8
+ session header, followed by append-only entries (message, custom_message,
9
+ compaction, model_change, ...). Messages use camelCase roles (user,
10
+ assistant, toolResult) with toolCall blocks inside assistant content.
11
+ data_source: local_filesystem
12
+ default_path: ~/.omp/agent/sessions
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "chatstrata"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "A personal, queryable archive of your AI conversations across providers"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -62,6 +62,7 @@ claude_code = "chatstrata.sources.claude_code.adapter:ClaudeCodeAdapter"
62
62
  claude_export = "chatstrata.sources.claude_export.adapter:ClaudeExportAdapter"
63
63
  codex_cli = "chatstrata.sources.codex_cli.adapter:CodexCliAdapter"
64
64
  opencode = "chatstrata.sources.opencode.adapter:OpenCodeAdapter"
65
+ omp = "chatstrata.sources.omp.adapter:OmpAdapter"
65
66
 
66
67
  [build-system]
67
68
  requires = ["hatchling"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes