agent-decoder 0.3.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.
- agent_decoder-0.3.2.dist-info/METADATA +387 -0
- agent_decoder-0.3.2.dist-info/RECORD +89 -0
- agent_decoder-0.3.2.dist-info/WHEEL +4 -0
- agent_decoder-0.3.2.dist-info/entry_points.txt +2 -0
- agent_decoder-0.3.2.dist-info/licenses/LICENSE +111 -0
- clau_decode/__init__.py +8 -0
- clau_decode/__main__.py +11 -0
- clau_decode/_auth_env.py +118 -0
- clau_decode/_pty_preexec.py +42 -0
- clau_decode/analytics/__init__.py +0 -0
- clau_decode/analytics/aggregator.py +55 -0
- clau_decode/analytics/cost.py +76 -0
- clau_decode/analytics/extractor.py +32 -0
- clau_decode/analytics/fast.py +289 -0
- clau_decode/analytics/models.py +49 -0
- clau_decode/analytics/pricing.py +162 -0
- clau_decode/analytics/prompt.py +34 -0
- clau_decode/analytics/service.py +23 -0
- clau_decode/analytics/stats.py +117 -0
- clau_decode/analytics/tips.py +176 -0
- clau_decode/btw_capture.py +290 -0
- clau_decode/cli.py +504 -0
- clau_decode/config.py +144 -0
- clau_decode/db.py +2054 -0
- clau_decode/driver_manager.py +387 -0
- clau_decode/drivers/__init__.py +27 -0
- clau_decode/drivers/base.py +170 -0
- clau_decode/drivers/registry.py +113 -0
- clau_decode/drivers/tmux_driver.py +488 -0
- clau_decode/editor.py +182 -0
- clau_decode/events_bus.py +69 -0
- clau_decode/locks.py +388 -0
- clau_decode/migrate.py +1313 -0
- clau_decode/models.py +285 -0
- clau_decode/parser.py +466 -0
- clau_decode/providers/__init__.py +30 -0
- clau_decode/providers/base.py +132 -0
- clau_decode/providers/claude.py +81 -0
- clau_decode/providers/codex.py +441 -0
- clau_decode/providers/registry.py +89 -0
- clau_decode/pty_native.py +39 -0
- clau_decode/pty_runner.py +2406 -0
- clau_decode/pty_screen_state.py +65 -0
- clau_decode/recap_runner.py +230 -0
- clau_decode/reporter.py +305 -0
- clau_decode/scanner.py +125 -0
- clau_decode/server.py +2841 -0
- clau_decode/static/android-chrome-192x192.png +0 -0
- clau_decode/static/android-chrome-512x512.png +0 -0
- clau_decode/static/app-icon.png +0 -0
- clau_decode/static/apple-touch-icon.png +0 -0
- clau_decode/static/assets/ChatView-BBVXuITs.js +79 -0
- clau_decode/static/assets/ChatView-BrP-ENHg.css +1 -0
- clau_decode/static/assets/Dashboard-3CYO4HQw.js +1 -0
- clau_decode/static/assets/FileViewer-BwbDc3ky.js +1 -0
- clau_decode/static/assets/HelpPopup-CPtECjYw.js +1 -0
- clau_decode/static/assets/ScrollContainer-BT_Dzxah.js +1 -0
- clau_decode/static/assets/SearchOverlay-DqI7aZaC.js +1 -0
- clau_decode/static/assets/SessionItem-DlyKg80u.js +1 -0
- clau_decode/static/assets/SettingsModal-CZBfMqx7.js +1 -0
- clau_decode/static/assets/ShortcutsPopup-W7CPdCzW.js +1 -0
- clau_decode/static/assets/Sidebar-BBswhEua.js +1 -0
- clau_decode/static/assets/client-DTSXvAb8.js +1 -0
- clau_decode/static/assets/fira-code-DGosTW8U.woff2 +0 -0
- clau_decode/static/assets/formatRelative-j86webkr.js +1 -0
- clau_decode/static/assets/hljs-B3O74O7Z.js +35 -0
- clau_decode/static/assets/index-BHdCOIkq.css +10 -0
- clau_decode/static/assets/index-DfKltZ6z.js +5 -0
- clau_decode/static/assets/ioskeley-mono-C7VR2-sL.woff2 +0 -0
- clau_decode/static/assets/jetbrains-mono-V6pRDFza.woff2 +0 -0
- clau_decode/static/assets/julia-mono-Cbvba6sW.woff2 +0 -0
- clau_decode/static/assets/libertinus-mono-Coz17E-O.woff2 +0 -0
- clau_decode/static/assets/monaspace-argon-RS-2TGNg.woff2 +0 -0
- clau_decode/static/assets/nativePtyFonts-BaXKtUsn.js +1 -0
- clau_decode/static/assets/react-Bi_azaFt.js +9 -0
- clau_decode/static/assets/renderSnippet-CqpQQugH.js +1 -0
- clau_decode/static/assets/rolldown-runtime-S-ySWqyJ.js +1 -0
- clau_decode/static/assets/shared-DdVGprvF.js +1 -0
- clau_decode/static/assets/source-code-pro-DEXWLoHG.woff2 +0 -0
- clau_decode/static/assets/spline-sans-mono-mUpA6Mve.woff2 +0 -0
- clau_decode/static/assets/ui-BcGPjpYn.js +1 -0
- clau_decode/static/assets/xanh-mono-Dsg19vWS.woff2 +0 -0
- clau_decode/static/favicon-16x16.png +0 -0
- clau_decode/static/favicon-32x32.png +0 -0
- clau_decode/static/favicon.ico +0 -0
- clau_decode/static/index.html +24 -0
- clau_decode/static/site.webmanifest +1 -0
- clau_decode/update_check.py +51 -0
- clau_decode/watcher.py +63 -0
clau_decode/scanner.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""File system scanner — discovers JSONL session files under configured root paths.
|
|
2
|
+
|
|
3
|
+
Contract (for Agent 2 to implement):
|
|
4
|
+
scan_paths(root_paths: list[Path]) -> AsyncIterator[tuple[Project, Path]]
|
|
5
|
+
- For each root path, walk projects/ subdirectory
|
|
6
|
+
- Each immediate child of projects/ is a project directory (mangled path name)
|
|
7
|
+
- Each *.jsonl file inside a project dir is a session
|
|
8
|
+
- Yield (project, session_file_path) tuples
|
|
9
|
+
- Also yield sessions from root_path/history.jsonl as a special "_history" project
|
|
10
|
+
|
|
11
|
+
build_project_from_dir(dir_name: str, data_source: str) -> Project
|
|
12
|
+
- Convert the mangled directory name into a Project object
|
|
13
|
+
- Use _unmangle_project_id from parser to get a display name
|
|
14
|
+
|
|
15
|
+
resolve_path(mangled: str) -> str | None
|
|
16
|
+
- Try to resolve the mangled path back to a real filesystem path
|
|
17
|
+
- Return None if the path doesn't exist
|
|
18
|
+
|
|
19
|
+
SOLID notes:
|
|
20
|
+
- Single Responsibility: scanning only; no parsing, no DB writes
|
|
21
|
+
- Callers (server.py) compose scanner + parser + db; this module doesn't know about them
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import hashlib
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import AsyncIterator
|
|
29
|
+
|
|
30
|
+
from .models import Project
|
|
31
|
+
from .parser import _unmangle_project_id
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def resolve_path(mangled: str) -> str | None:
|
|
35
|
+
"""Attempt to reconstruct and verify the original filesystem path.
|
|
36
|
+
|
|
37
|
+
Strips the leading dash then converts remaining dashes to slashes,
|
|
38
|
+
treating double-dashes as literal hyphens (matching _unmangle_project_id).
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
mangled: A directory name like '-Volumes-ExternalDrive-Work-foo' or
|
|
42
|
+
'-Users-alice-my--project'.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The absolute path string if it exists on disk, otherwise None.
|
|
46
|
+
"""
|
|
47
|
+
unmangled = _unmangle_project_id(mangled)
|
|
48
|
+
path = Path("/" + unmangled)
|
|
49
|
+
if path.exists():
|
|
50
|
+
return str(path)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def build_project_from_dir(dir_name: str, data_source: str) -> Project:
|
|
55
|
+
"""Create a Project from a mangled project directory name.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
dir_name: The raw directory name as it appears on disk
|
|
59
|
+
(e.g. '-Users-alice-project-foo').
|
|
60
|
+
data_source: The configured root path this project was found under
|
|
61
|
+
(e.g. '~/.claude').
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
A Project with a stable 16-hex-char id, a human-readable display_name
|
|
65
|
+
showing the last two path components, and a resolved_path if the
|
|
66
|
+
underlying directory exists.
|
|
67
|
+
"""
|
|
68
|
+
project_id = hashlib.sha256(f"{dir_name}\0{data_source}".encode()).hexdigest()[:16]
|
|
69
|
+
|
|
70
|
+
unmangled = _unmangle_project_id(dir_name)
|
|
71
|
+
parts = [p for p in unmangled.split("/") if p]
|
|
72
|
+
if len(parts) >= 2:
|
|
73
|
+
display_name = "/".join(parts[-2:])
|
|
74
|
+
elif parts:
|
|
75
|
+
display_name = parts[0]
|
|
76
|
+
else:
|
|
77
|
+
display_name = dir_name
|
|
78
|
+
|
|
79
|
+
resolved = resolve_path(dir_name)
|
|
80
|
+
|
|
81
|
+
return Project(
|
|
82
|
+
id=project_id,
|
|
83
|
+
display_name=display_name,
|
|
84
|
+
raw_path=dir_name,
|
|
85
|
+
resolved_path=resolved,
|
|
86
|
+
data_source=data_source,
|
|
87
|
+
session_count=0,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def scan_paths(root_paths: list[Path]) -> AsyncIterator[tuple[Project, Path]]:
|
|
92
|
+
"""Yield (project, session_path) for every session file found under root_paths.
|
|
93
|
+
|
|
94
|
+
For each root path:
|
|
95
|
+
- Skips paths that don't exist on disk.
|
|
96
|
+
- Looks for a ``projects/`` subdirectory.
|
|
97
|
+
- Iterates over every immediate child directory inside ``projects/``.
|
|
98
|
+
- Within each project directory, yields one tuple per ``*.jsonl`` file.
|
|
99
|
+
|
|
100
|
+
This is an async generator so it can be consumed with ``async for``.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
root_paths: List of root directories to scan (e.g. ``[Path("~/.claude")]``).
|
|
104
|
+
|
|
105
|
+
Yields:
|
|
106
|
+
Tuples of (Project, Path) where Path points to a ``.jsonl`` session file.
|
|
107
|
+
"""
|
|
108
|
+
for root_path in root_paths:
|
|
109
|
+
if not root_path.exists():
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
projects_dir = root_path / "projects"
|
|
113
|
+
if not projects_dir.is_dir():
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
for project_dir in sorted(projects_dir.iterdir()):
|
|
117
|
+
if not project_dir.is_dir():
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
project = build_project_from_dir(project_dir.name, str(root_path))
|
|
121
|
+
|
|
122
|
+
for jsonl_file in sorted(
|
|
123
|
+
f for f in project_dir.glob("*.jsonl") if ".bak." not in f.name
|
|
124
|
+
):
|
|
125
|
+
yield project, jsonl_file
|