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.
Files changed (89) hide show
  1. agent_decoder-0.3.2.dist-info/METADATA +387 -0
  2. agent_decoder-0.3.2.dist-info/RECORD +89 -0
  3. agent_decoder-0.3.2.dist-info/WHEEL +4 -0
  4. agent_decoder-0.3.2.dist-info/entry_points.txt +2 -0
  5. agent_decoder-0.3.2.dist-info/licenses/LICENSE +111 -0
  6. clau_decode/__init__.py +8 -0
  7. clau_decode/__main__.py +11 -0
  8. clau_decode/_auth_env.py +118 -0
  9. clau_decode/_pty_preexec.py +42 -0
  10. clau_decode/analytics/__init__.py +0 -0
  11. clau_decode/analytics/aggregator.py +55 -0
  12. clau_decode/analytics/cost.py +76 -0
  13. clau_decode/analytics/extractor.py +32 -0
  14. clau_decode/analytics/fast.py +289 -0
  15. clau_decode/analytics/models.py +49 -0
  16. clau_decode/analytics/pricing.py +162 -0
  17. clau_decode/analytics/prompt.py +34 -0
  18. clau_decode/analytics/service.py +23 -0
  19. clau_decode/analytics/stats.py +117 -0
  20. clau_decode/analytics/tips.py +176 -0
  21. clau_decode/btw_capture.py +290 -0
  22. clau_decode/cli.py +504 -0
  23. clau_decode/config.py +144 -0
  24. clau_decode/db.py +2054 -0
  25. clau_decode/driver_manager.py +387 -0
  26. clau_decode/drivers/__init__.py +27 -0
  27. clau_decode/drivers/base.py +170 -0
  28. clau_decode/drivers/registry.py +113 -0
  29. clau_decode/drivers/tmux_driver.py +488 -0
  30. clau_decode/editor.py +182 -0
  31. clau_decode/events_bus.py +69 -0
  32. clau_decode/locks.py +388 -0
  33. clau_decode/migrate.py +1313 -0
  34. clau_decode/models.py +285 -0
  35. clau_decode/parser.py +466 -0
  36. clau_decode/providers/__init__.py +30 -0
  37. clau_decode/providers/base.py +132 -0
  38. clau_decode/providers/claude.py +81 -0
  39. clau_decode/providers/codex.py +441 -0
  40. clau_decode/providers/registry.py +89 -0
  41. clau_decode/pty_native.py +39 -0
  42. clau_decode/pty_runner.py +2406 -0
  43. clau_decode/pty_screen_state.py +65 -0
  44. clau_decode/recap_runner.py +230 -0
  45. clau_decode/reporter.py +305 -0
  46. clau_decode/scanner.py +125 -0
  47. clau_decode/server.py +2841 -0
  48. clau_decode/static/android-chrome-192x192.png +0 -0
  49. clau_decode/static/android-chrome-512x512.png +0 -0
  50. clau_decode/static/app-icon.png +0 -0
  51. clau_decode/static/apple-touch-icon.png +0 -0
  52. clau_decode/static/assets/ChatView-BBVXuITs.js +79 -0
  53. clau_decode/static/assets/ChatView-BrP-ENHg.css +1 -0
  54. clau_decode/static/assets/Dashboard-3CYO4HQw.js +1 -0
  55. clau_decode/static/assets/FileViewer-BwbDc3ky.js +1 -0
  56. clau_decode/static/assets/HelpPopup-CPtECjYw.js +1 -0
  57. clau_decode/static/assets/ScrollContainer-BT_Dzxah.js +1 -0
  58. clau_decode/static/assets/SearchOverlay-DqI7aZaC.js +1 -0
  59. clau_decode/static/assets/SessionItem-DlyKg80u.js +1 -0
  60. clau_decode/static/assets/SettingsModal-CZBfMqx7.js +1 -0
  61. clau_decode/static/assets/ShortcutsPopup-W7CPdCzW.js +1 -0
  62. clau_decode/static/assets/Sidebar-BBswhEua.js +1 -0
  63. clau_decode/static/assets/client-DTSXvAb8.js +1 -0
  64. clau_decode/static/assets/fira-code-DGosTW8U.woff2 +0 -0
  65. clau_decode/static/assets/formatRelative-j86webkr.js +1 -0
  66. clau_decode/static/assets/hljs-B3O74O7Z.js +35 -0
  67. clau_decode/static/assets/index-BHdCOIkq.css +10 -0
  68. clau_decode/static/assets/index-DfKltZ6z.js +5 -0
  69. clau_decode/static/assets/ioskeley-mono-C7VR2-sL.woff2 +0 -0
  70. clau_decode/static/assets/jetbrains-mono-V6pRDFza.woff2 +0 -0
  71. clau_decode/static/assets/julia-mono-Cbvba6sW.woff2 +0 -0
  72. clau_decode/static/assets/libertinus-mono-Coz17E-O.woff2 +0 -0
  73. clau_decode/static/assets/monaspace-argon-RS-2TGNg.woff2 +0 -0
  74. clau_decode/static/assets/nativePtyFonts-BaXKtUsn.js +1 -0
  75. clau_decode/static/assets/react-Bi_azaFt.js +9 -0
  76. clau_decode/static/assets/renderSnippet-CqpQQugH.js +1 -0
  77. clau_decode/static/assets/rolldown-runtime-S-ySWqyJ.js +1 -0
  78. clau_decode/static/assets/shared-DdVGprvF.js +1 -0
  79. clau_decode/static/assets/source-code-pro-DEXWLoHG.woff2 +0 -0
  80. clau_decode/static/assets/spline-sans-mono-mUpA6Mve.woff2 +0 -0
  81. clau_decode/static/assets/ui-BcGPjpYn.js +1 -0
  82. clau_decode/static/assets/xanh-mono-Dsg19vWS.woff2 +0 -0
  83. clau_decode/static/favicon-16x16.png +0 -0
  84. clau_decode/static/favicon-32x32.png +0 -0
  85. clau_decode/static/favicon.ico +0 -0
  86. clau_decode/static/index.html +24 -0
  87. clau_decode/static/site.webmanifest +1 -0
  88. clau_decode/update_check.py +51 -0
  89. 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