chatstrata 0.2.0__tar.gz → 0.2.2__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 (72) hide show
  1. {chatstrata-0.2.0 → chatstrata-0.2.2}/CHANGELOG.md +8 -0
  2. {chatstrata-0.2.0 → chatstrata-0.2.2}/PKG-INFO +50 -16
  3. {chatstrata-0.2.0 → chatstrata-0.2.2}/README.md +48 -14
  4. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/__init__.py +1 -1
  5. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/claude_code/adapter.py +33 -3
  6. {chatstrata-0.2.0 → chatstrata-0.2.2}/pyproject.toml +2 -2
  7. {chatstrata-0.2.0 → chatstrata-0.2.2}/.gitignore +0 -0
  8. {chatstrata-0.2.0 → chatstrata-0.2.2}/CONTRIBUTING.md +0 -0
  9. {chatstrata-0.2.0 → chatstrata-0.2.2}/LICENSE +0 -0
  10. {chatstrata-0.2.0 → chatstrata-0.2.2}/SECURITY.md +0 -0
  11. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/analysis/__init__.py +0 -0
  12. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/analysis/cli.py +0 -0
  13. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/analysis/queries/activity.sql +0 -0
  14. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/analysis/queries/conversations.sql +0 -0
  15. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/analysis/queries/example_queries.sql +0 -0
  16. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/analysis/queries/models.sql +0 -0
  17. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/analysis/queries/projects.sql +0 -0
  18. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/analysis/queries/tools.sql +0 -0
  19. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/cli.py +0 -0
  20. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/core/__init__.py +0 -0
  21. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/core/db.py +0 -0
  22. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/core/ingest.py +0 -0
  23. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/core/migrations/0001_initial.sql +0 -0
  24. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/core/migrations/0002_fts_index.sql +0 -0
  25. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/core/migrations/0003_conversation_mtime.sql +0 -0
  26. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/core/migrations/__init__.py +0 -0
  27. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/core/models.py +0 -0
  28. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/core/search.py +0 -0
  29. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/embed/__init__.py +0 -0
  30. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/embed/base.py +0 -0
  31. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/embed/cli.py +0 -0
  32. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/embed/local_provider.py +0 -0
  33. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/embed/search.py +0 -0
  34. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/mcp/README.md +0 -0
  35. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/mcp/__init__.py +0 -0
  36. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/mcp/safety.py +0 -0
  37. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/mcp/server.py +0 -0
  38. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/redact/__init__.py +0 -0
  39. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/redact/base.py +0 -0
  40. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/redact/cli.py +0 -0
  41. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/redact/presidio_engine.py +0 -0
  42. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/redact/recognizers/__init__.py +0 -0
  43. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/redact/recognizers/api_keys.py +0 -0
  44. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/redact/recognizers/connection_strings.py +0 -0
  45. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/redact/recognizers/paths.py +0 -0
  46. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/redact/recognizers/tokens.py +0 -0
  47. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/schedule/__init__.py +0 -0
  48. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/schedule/cli.py +0 -0
  49. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/schedule/launchd.py +0 -0
  50. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/__init__.py +0 -0
  51. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/base.py +0 -0
  52. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/claude_code/__init__.py +0 -0
  53. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/claude_code/manifest.yaml +0 -0
  54. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/claude_export/__init__.py +0 -0
  55. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/claude_export/adapter.py +0 -0
  56. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/claude_export/manifest.yaml +0 -0
  57. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/codex_cli/__init__.py +0 -0
  58. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/codex_cli/adapter.py +0 -0
  59. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/codex_cli/manifest.yaml +0 -0
  60. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/opencode/__init__.py +0 -0
  61. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/opencode/adapter.py +0 -0
  62. {chatstrata-0.2.0 → chatstrata-0.2.2}/chatstrata/sources/opencode/manifest.yaml +0 -0
  63. {chatstrata-0.2.0 → chatstrata-0.2.2}/docs/adapter-guide.md +0 -0
  64. {chatstrata-0.2.0 → chatstrata-0.2.2}/docs/adr/0001-use-duckdb.md +0 -0
  65. {chatstrata-0.2.0 → chatstrata-0.2.2}/docs/adr/0002-store-raw-alongside-normalized.md +0 -0
  66. {chatstrata-0.2.0 → chatstrata-0.2.2}/docs/adr/0003-adapter-pattern-entry-points.md +0 -0
  67. {chatstrata-0.2.0 → chatstrata-0.2.2}/docs/images/chatstrata.png +0 -0
  68. {chatstrata-0.2.0 → chatstrata-0.2.2}/docs/redaction.md +0 -0
  69. {chatstrata-0.2.0 → chatstrata-0.2.2}/docs/release.md +0 -0
  70. {chatstrata-0.2.0 → chatstrata-0.2.2}/docs/schema.md +0 -0
  71. {chatstrata-0.2.0 → chatstrata-0.2.2}/docs/sources/claude_export.md +0 -0
  72. {chatstrata-0.2.0 → chatstrata-0.2.2}/docs/sources/codex_cli.md +0 -0
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.2 - 2026-07-29
4
+
5
+ - 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'`.
6
+
7
+ ## 0.2.1 - 2026-07-22
8
+
9
+ - Fix `claude_code` conversation `project` paths: derive the project from the lossless `cwd` recorded in each transcript instead of decoding the session folder name. The folder-name encoding collapses `/`, `_`, `-`, and `.` all into `-`, so decoding it produced wrong, non-existent paths (e.g. `bstaq_git/pepstaq` became `bstaq/git`). New ingests are now correct automatically; `codex_cli` and `opencode` were unaffected.
10
+
3
11
  ## 0.2.0 - 2026-06-16
4
12
 
5
13
  - Add `chatstrata schedule` command group for automatic background sync.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chatstrata
3
- Version: 0.2.0
3
+ Version: 0.2.2
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
@@ -34,7 +34,7 @@ Provides-Extra: embeddings
34
34
  Requires-Dist: sentence-transformers>=3.0; extra == 'embeddings'
35
35
  Requires-Dist: torch>=2.0; extra == 'embeddings'
36
36
  Provides-Extra: mcp
37
- Requires-Dist: mcp[cli]>=1.12.0; extra == 'mcp'
37
+ Requires-Dist: mcp[cli]<2,>=1.12.0; extra == 'mcp'
38
38
  Provides-Extra: redact
39
39
  Requires-Dist: presidio-analyzer>=2.2; extra == 'redact'
40
40
  Requires-Dist: presidio-anonymizer>=2.2; extra == 'redact'
@@ -75,30 +75,64 @@ Concretely, with chatstrata you can:
75
75
 
76
76
  ## Status
77
77
 
78
- **Early alpha.** v0 includes adapters for Claude Code, claude.ai exports, Codex
79
- CLI, and OpenCode. The architecture is built so that adding more sources
80
- (ChatGPT exports, Cursor, etc.) is the work of one adapter — see
81
- [docs/adapter-guide.md](docs/adapter-guide.md).
78
+ **Alpha.** Published on [PyPI](https://pypi.org/project/chatstrata/). Includes
79
+ adapters for Claude Code, claude.ai exports, Codex CLI, and OpenCode, plus
80
+ scheduled background sync so your archive stays current without manual runs. The
81
+ architecture is built so that adding more sources (ChatGPT exports, Cursor, etc.)
82
+ is the work of one adapter — see [docs/adapter-guide.md](docs/adapter-guide.md).
82
83
 
83
84
  ## Quickstart
84
85
 
85
- Requires Python 3.10+. DuckDB is installed as a Python dependency; you do not
86
- need to install a separate DuckDB server or CLI.
86
+ Requires Python 3.10+. We recommend [uv](https://docs.astral.sh/uv/) — it's the
87
+ fastest way to get started (no virtualenv wrangling, no dependency conflicts).
88
+
89
+ ### 1. Install
87
90
 
88
91
  ```bash
89
92
  uv tool install "chatstrata[embeddings]"
90
- # or: pipx install "chatstrata[embeddings]"
93
+ ```
91
94
 
92
- # Create the local DuckDB archive and show detected sources
93
- chatstrata init
95
+ <details>
96
+ <summary>Alternative: pipx</summary>
94
97
 
95
- # Auto-detect local app transcripts, ingest them, and generate embeddings
96
- chatstrata ingest --auto
98
+ ```bash
99
+ pipx install "chatstrata[embeddings]"
100
+ ```
101
+ </details>
97
102
 
98
- # See what's there
99
- chatstrata stats
103
+ ### 2. Initialize and ingest
104
+
105
+ ```bash
106
+ chatstrata init # create the local DuckDB archive
107
+ chatstrata ingest --auto # auto-detect transcripts and generate embeddings
108
+ ```
109
+
110
+ ### 3. Set up background sync
111
+
112
+ Never think about syncing again — chatstrata can keep your archive up to date
113
+ automatically in the background:
100
114
 
101
- # Run a query
115
+ ```bash
116
+ chatstrata schedule install # sync every 15 minutes (default)
117
+ chatstrata schedule install --interval 1h # or pick your own interval
118
+ chatstrata schedule status # check that it's running
119
+ ```
120
+
121
+ On macOS this installs a launchd agent that runs `chatstrata ingest --auto`
122
+ on the configured interval. Logs go to `~/Library/Logs/chatstrata/`. To stop
123
+ background sync:
124
+
125
+ ```bash
126
+ chatstrata schedule uninstall
127
+ ```
128
+
129
+ > **Linux:** systemd scheduling is not yet implemented. Use cron as a workaround:
130
+ > `*/15 * * * * chatstrata ingest --auto`
131
+
132
+ ### 4. Query your archive
133
+
134
+ ```bash
135
+ chatstrata stats
102
136
  chatstrata query "SELECT model, COUNT(*) FROM messages GROUP BY model"
103
137
  ```
104
138
 
@@ -32,30 +32,64 @@ Concretely, with chatstrata you can:
32
32
 
33
33
  ## Status
34
34
 
35
- **Early alpha.** v0 includes adapters for Claude Code, claude.ai exports, Codex
36
- CLI, and OpenCode. The architecture is built so that adding more sources
37
- (ChatGPT exports, Cursor, etc.) is the work of one adapter — see
38
- [docs/adapter-guide.md](docs/adapter-guide.md).
35
+ **Alpha.** Published on [PyPI](https://pypi.org/project/chatstrata/). Includes
36
+ adapters for Claude Code, claude.ai exports, Codex CLI, and OpenCode, plus
37
+ scheduled background sync so your archive stays current without manual runs. The
38
+ architecture is built so that adding more sources (ChatGPT exports, Cursor, etc.)
39
+ is the work of one adapter — see [docs/adapter-guide.md](docs/adapter-guide.md).
39
40
 
40
41
  ## Quickstart
41
42
 
42
- Requires Python 3.10+. DuckDB is installed as a Python dependency; you do not
43
- need to install a separate DuckDB server or CLI.
43
+ Requires Python 3.10+. We recommend [uv](https://docs.astral.sh/uv/) — it's the
44
+ fastest way to get started (no virtualenv wrangling, no dependency conflicts).
45
+
46
+ ### 1. Install
44
47
 
45
48
  ```bash
46
49
  uv tool install "chatstrata[embeddings]"
47
- # or: pipx install "chatstrata[embeddings]"
50
+ ```
48
51
 
49
- # Create the local DuckDB archive and show detected sources
50
- chatstrata init
52
+ <details>
53
+ <summary>Alternative: pipx</summary>
51
54
 
52
- # Auto-detect local app transcripts, ingest them, and generate embeddings
53
- chatstrata ingest --auto
55
+ ```bash
56
+ pipx install "chatstrata[embeddings]"
57
+ ```
58
+ </details>
54
59
 
55
- # See what's there
56
- chatstrata stats
60
+ ### 2. Initialize and ingest
61
+
62
+ ```bash
63
+ chatstrata init # create the local DuckDB archive
64
+ chatstrata ingest --auto # auto-detect transcripts and generate embeddings
65
+ ```
66
+
67
+ ### 3. Set up background sync
68
+
69
+ Never think about syncing again — chatstrata can keep your archive up to date
70
+ automatically in the background:
57
71
 
58
- # Run a query
72
+ ```bash
73
+ chatstrata schedule install # sync every 15 minutes (default)
74
+ chatstrata schedule install --interval 1h # or pick your own interval
75
+ chatstrata schedule status # check that it's running
76
+ ```
77
+
78
+ On macOS this installs a launchd agent that runs `chatstrata ingest --auto`
79
+ on the configured interval. Logs go to `~/Library/Logs/chatstrata/`. To stop
80
+ background sync:
81
+
82
+ ```bash
83
+ chatstrata schedule uninstall
84
+ ```
85
+
86
+ > **Linux:** systemd scheduling is not yet implemented. Use cron as a workaround:
87
+ > `*/15 * * * * chatstrata ingest --auto`
88
+
89
+ ### 4. Query your archive
90
+
91
+ ```bash
92
+ chatstrata stats
59
93
  chatstrata query "SELECT model, COUNT(*) FROM messages GROUP BY model"
60
94
  ```
61
95
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  from chatstrata.core.migrations import LATEST_VERSION as SCHEMA_VERSION
4
4
 
5
- __version__ = "0.2.0"
5
+ __version__ = "0.2.1"
6
6
 
7
7
  __all__ = ["SCHEMA_VERSION", "__version__"]
@@ -49,16 +49,39 @@ def _parse_timestamp(s: Any) -> datetime | None:
49
49
 
50
50
 
51
51
  def _decode_project_dir(dir_name: str) -> str:
52
- """Claude Code encodes the project cwd by replacing '/' with '-'.
52
+ """Decode Claude Code's encoded project-folder name back into a path.
53
53
 
54
54
  e.g. '-Users-alice-code-myproj' -> '/Users/alice/code/myproj'
55
- This is lossy but a reasonable best effort.
55
+
56
+ This decode is *lossy and irreversible*: Claude Code collapses '/', '_',
57
+ '-', and '.' in the cwd all into '-' when naming the folder, so a '-' in
58
+ the folder name could have been any of those characters. As a result many
59
+ distinct cwds map to the same decoded path (e.g. both '.../my_proj' and
60
+ '.../my/proj' decode identically). Prefer the lossless cwd recorded inside
61
+ the transcript (see `_project_from_events`); this decode is only a
62
+ last-resort fallback for transcripts that never recorded a cwd.
56
63
  """
57
64
  if dir_name.startswith("-"):
58
65
  return "/" + dir_name[1:].replace("-", "/")
59
66
  return dir_name
60
67
 
61
68
 
69
+ def _project_from_events(events: list[dict[str, Any]]) -> str | None:
70
+ """Return the real working directory recorded in the transcript.
71
+
72
+ Every Claude Code event carries the session cwd, e.g.
73
+ ``"cwd": "/Users/alice/code/my_project"``. This is the lossless source of
74
+ truth for the project path -- unlike the encoded folder name, which cannot
75
+ be reversed (see `_decode_project_dir`). Use the first event that records a
76
+ non-empty cwd; the cwd is constant across a session.
77
+ """
78
+ for ev in events:
79
+ cwd = ev.get("cwd")
80
+ if isinstance(cwd, str) and cwd:
81
+ return cwd
82
+ return None
83
+
84
+
62
85
  def _role_from_event(event: dict[str, Any]) -> Role | None:
63
86
  t = event.get("type")
64
87
  if t == "user":
@@ -153,6 +176,8 @@ class ClaudeCodeAdapter:
153
176
  return
154
177
  for jsonl in sorted(root.glob("*/*.jsonl")):
155
178
  session_id = jsonl.stem
179
+ # Lossy fallback only: parse() prefers the lossless cwd from the
180
+ # transcript and uses this decoded folder name just as a backstop.
156
181
  project = _decode_project_dir(jsonl.parent.name)
157
182
  yield ConversationHandle(
158
183
  source_native_id=session_id,
@@ -228,7 +253,12 @@ class ClaudeCodeAdapter:
228
253
  )
229
254
  )
230
255
 
231
- project = handle.metadata.get("project") if handle.metadata else None
256
+ # Prefer the lossless cwd recorded in the transcript over the lossy
257
+ # folder-name decode carried on the handle. Fall back to the decoded
258
+ # folder name only for transcripts that never recorded a cwd.
259
+ project = _project_from_events(events)
260
+ if project is None and handle.metadata:
261
+ project = handle.metadata.get("project")
232
262
  # If no summary, fall back to the first user text block as a title.
233
263
  if title is None:
234
264
  for m in messages:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "chatstrata"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "A personal, queryable archive of your AI conversations across providers"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -45,7 +45,7 @@ redact = [
45
45
  "spacy>=3.7",
46
46
  ]
47
47
  mcp = [
48
- "mcp[cli]>=1.12.0",
48
+ "mcp[cli]>=1.12.0,<2",
49
49
  ]
50
50
  dev = [
51
51
  "pytest>=8.0",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes