puenteo 0.4.0__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.
puenteo-0.4.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mano7onam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
puenteo-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: puenteo
3
+ Version: 0.4.0
4
+ Summary: Puenteo — the bridge between coding agents. List, search, and export Claude/Codex/Grok/Pi sessions to md/html/pdf/json/zip (Python library + CLI).
5
+ Author-email: mano7onam <mano7onamm@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/mano7onam/puenteo
8
+ Project-URL: Repository, https://github.com/mano7onam/puenteo
9
+ Project-URL: Issues, https://github.com/mano7onam/puenteo/issues
10
+ Project-URL: Changelog, https://github.com/mano7onam/puenteo/releases
11
+ Keywords: agents,bridge,puente,claude-code,codex,grok,cli,library,export,markdown,pdf,session,developer-tools
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Topic :: Utilities
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.9
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7; extra == "dev"
32
+ Requires-Dist: build; extra == "dev"
33
+ Requires-Dist: twine; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # puenteo
37
+
38
+ **The bridge between coding agents.**
39
+
40
+ Python **library + CLI** to discover, search, and export local sessions from Claude Code, Codex, Grok, and Pi — to Markdown, HTML, PDF, JSON, ZIP, CSV, XML, YAML.
41
+
42
+ *Puenteo* ← Spanish *puente* (bridge) + *puentear* (to bridge / jump across).
43
+
44
+ Zero runtime dependencies · Python ≥ 3.9
45
+
46
+ [![PyPI](https://img.shields.io/pypi/v/puenteo.svg)](https://pypi.org/project/puenteo/)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pip install puenteo
53
+ # or
54
+ uv add puenteo
55
+ # or
56
+ pipx install puenteo
57
+ ```
58
+
59
+ From source:
60
+
61
+ ```bash
62
+ git clone https://github.com/mano7onam/puenteo.git
63
+ cd puenteo
64
+ python3 -m venv .venv && .venv/bin/pip install -e .
65
+ ```
66
+
67
+ ## Library
68
+
69
+ ```python
70
+ import puenteo
71
+
72
+ print(puenteo.status())
73
+
74
+ for s in puenteo.list_sessions(limit=10):
75
+ print(s.provider, s.session_id[:8], s.title)
76
+
77
+ t = puenteo.load("019f7a24", include_tools=True)
78
+ hits = puenteo.search("gatekeeper dmg")
79
+ msgs = puenteo.pull("019f7a24", query="export", mode="query")
80
+
81
+ puenteo.export_session("019f7a24", fmt="md", output="chat.md")
82
+ puenteo.export_session("019f7a24", fmt="pdf", output="chat.pdf")
83
+ puenteo.export_session("019f7a24", fmt="all", output="./exports/")
84
+
85
+ data, media_type, filename = puenteo.export_bytes("019f7a24", fmt="html")
86
+ ```
87
+
88
+ ## CLI
89
+
90
+ ```bash
91
+ puenteo status
92
+ puenteo list -n 20 --json
93
+ puenteo list --provider claude,grok --cwd ~/dev/myapp
94
+ puenteo show <id> --last 40
95
+ puenteo search "topic" --json
96
+ puenteo pull <id> --query "topic" --mode query --max-chars 8000
97
+ puenteo export <id> -f md -o chat.md
98
+ puenteo export <id> -f pdf -o chat.pdf
99
+ puenteo export <id> -f html|json|zip|csv|xml|yaml -o …
100
+ puenteo export <id> -f all -o ./out/
101
+ ```
102
+
103
+ Short alias after install: `pto` (same as `puenteo`).
104
+
105
+ `<id>` = full uuid, **prefix**, path, or unique title substring.
106
+
107
+ ## Providers
108
+
109
+ | Provider | Store |
110
+ |----------|--------|
111
+ | Claude Code | `~/.claude/projects/**/*.jsonl` |
112
+ | Codex | `~/.codex/sessions/**/rollout-*.jsonl` |
113
+ | Grok | `~/.grok/sessions/**/chat_history.jsonl` |
114
+ | Pi | `~/.pi/agent/sessions/**/*.jsonl` |
115
+
116
+ ## Export formats
117
+
118
+ `md` · `txt` · `html` · `pdf` · `json` · `zip` · `csv` · `xml` · `yaml`
119
+
120
+ ## Agent skill
121
+
122
+ ```bash
123
+ ./scripts/install_skills.sh
124
+ ```
125
+
126
+ ## Used by
127
+
128
+ [Terminal Dashboard](https://github.com/mano7onam/terminal-dashboard) imports **puenteo** for chat export (shared parsers, no duplication).
129
+
130
+ ## License
131
+
132
+ MIT · [mano7onam/puenteo](https://github.com/mano7onam/puenteo)
@@ -0,0 +1,97 @@
1
+ # puenteo
2
+
3
+ **The bridge between coding agents.**
4
+
5
+ Python **library + CLI** to discover, search, and export local sessions from Claude Code, Codex, Grok, and Pi — to Markdown, HTML, PDF, JSON, ZIP, CSV, XML, YAML.
6
+
7
+ *Puenteo* ← Spanish *puente* (bridge) + *puentear* (to bridge / jump across).
8
+
9
+ Zero runtime dependencies · Python ≥ 3.9
10
+
11
+ [![PyPI](https://img.shields.io/pypi/v/puenteo.svg)](https://pypi.org/project/puenteo/)
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install puenteo
18
+ # or
19
+ uv add puenteo
20
+ # or
21
+ pipx install puenteo
22
+ ```
23
+
24
+ From source:
25
+
26
+ ```bash
27
+ git clone https://github.com/mano7onam/puenteo.git
28
+ cd puenteo
29
+ python3 -m venv .venv && .venv/bin/pip install -e .
30
+ ```
31
+
32
+ ## Library
33
+
34
+ ```python
35
+ import puenteo
36
+
37
+ print(puenteo.status())
38
+
39
+ for s in puenteo.list_sessions(limit=10):
40
+ print(s.provider, s.session_id[:8], s.title)
41
+
42
+ t = puenteo.load("019f7a24", include_tools=True)
43
+ hits = puenteo.search("gatekeeper dmg")
44
+ msgs = puenteo.pull("019f7a24", query="export", mode="query")
45
+
46
+ puenteo.export_session("019f7a24", fmt="md", output="chat.md")
47
+ puenteo.export_session("019f7a24", fmt="pdf", output="chat.pdf")
48
+ puenteo.export_session("019f7a24", fmt="all", output="./exports/")
49
+
50
+ data, media_type, filename = puenteo.export_bytes("019f7a24", fmt="html")
51
+ ```
52
+
53
+ ## CLI
54
+
55
+ ```bash
56
+ puenteo status
57
+ puenteo list -n 20 --json
58
+ puenteo list --provider claude,grok --cwd ~/dev/myapp
59
+ puenteo show <id> --last 40
60
+ puenteo search "topic" --json
61
+ puenteo pull <id> --query "topic" --mode query --max-chars 8000
62
+ puenteo export <id> -f md -o chat.md
63
+ puenteo export <id> -f pdf -o chat.pdf
64
+ puenteo export <id> -f html|json|zip|csv|xml|yaml -o …
65
+ puenteo export <id> -f all -o ./out/
66
+ ```
67
+
68
+ Short alias after install: `pto` (same as `puenteo`).
69
+
70
+ `<id>` = full uuid, **prefix**, path, or unique title substring.
71
+
72
+ ## Providers
73
+
74
+ | Provider | Store |
75
+ |----------|--------|
76
+ | Claude Code | `~/.claude/projects/**/*.jsonl` |
77
+ | Codex | `~/.codex/sessions/**/rollout-*.jsonl` |
78
+ | Grok | `~/.grok/sessions/**/chat_history.jsonl` |
79
+ | Pi | `~/.pi/agent/sessions/**/*.jsonl` |
80
+
81
+ ## Export formats
82
+
83
+ `md` · `txt` · `html` · `pdf` · `json` · `zip` · `csv` · `xml` · `yaml`
84
+
85
+ ## Agent skill
86
+
87
+ ```bash
88
+ ./scripts/install_skills.sh
89
+ ```
90
+
91
+ ## Used by
92
+
93
+ [Terminal Dashboard](https://github.com/mano7onam/terminal-dashboard) imports **puenteo** for chat export (shared parsers, no duplication).
94
+
95
+ ## License
96
+
97
+ MIT · [mano7onam/puenteo](https://github.com/mano7onam/puenteo)
@@ -0,0 +1,89 @@
1
+ """
2
+ Puenteo — the bridge between coding agents.
3
+
4
+ Install::
5
+
6
+ pip install puenteo
7
+ # CLI:
8
+ puenteo list
9
+ puenteo export <session_id> -f md -o chat.md
10
+
11
+ Library::
12
+
13
+ import puenteo
14
+
15
+ print(puenteo.status())
16
+ for s in puenteo.list_sessions(limit=10):
17
+ print(s.provider, s.session_id[:8], s.title)
18
+
19
+ puenteo.export_session("019f7a24", fmt="pdf", output="chat.pdf")
20
+ hits = puenteo.search("export markdown")
21
+ msgs = puenteo.pull("019f7a24", query="dmg", mode="query")
22
+
23
+ Providers: Claude Code, Codex, Grok, Pi.
24
+ Formats: md, txt, html, pdf, json, zip, csv, xml, yaml.
25
+ """
26
+
27
+ from .api import ( # noqa: F401
28
+ APP_NAME,
29
+ SUPPORTED_FORMATS,
30
+ Attachment,
31
+ Hit,
32
+ Message,
33
+ PROVIDER_NAMES,
34
+ RichMessage,
35
+ RichTranscript,
36
+ Session,
37
+ Transcript,
38
+ clipboard_text,
39
+ export_bytes,
40
+ export_session,
41
+ find_best_agent_transcript,
42
+ get_session,
43
+ list_sessions,
44
+ list_sources_for_cwd,
45
+ load,
46
+ load_rich_transcript,
47
+ load_transcript,
48
+ pull,
49
+ render,
50
+ resolve_session,
51
+ search,
52
+ search_all,
53
+ search_transcript,
54
+ smart_pull,
55
+ status,
56
+ )
57
+ from .version import __version__
58
+
59
+ __all__ = [
60
+ "APP_NAME",
61
+ "SUPPORTED_FORMATS",
62
+ "Attachment",
63
+ "Hit",
64
+ "Message",
65
+ "PROVIDER_NAMES",
66
+ "RichMessage",
67
+ "RichTranscript",
68
+ "Session",
69
+ "Transcript",
70
+ "__version__",
71
+ "clipboard_text",
72
+ "export_bytes",
73
+ "export_session",
74
+ "find_best_agent_transcript",
75
+ "get_session",
76
+ "list_sessions",
77
+ "list_sources_for_cwd",
78
+ "load",
79
+ "load_rich_transcript",
80
+ "load_transcript",
81
+ "pull",
82
+ "render",
83
+ "resolve_session",
84
+ "search",
85
+ "search_all",
86
+ "search_transcript",
87
+ "smart_pull",
88
+ "status",
89
+ ]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,286 @@
1
+ """
2
+ Public library API for Puenteo.
3
+
4
+ Prefer importing from the package root::
5
+
6
+ import puenteo
7
+
8
+ sessions = puenteo.list_sessions(limit=20)
9
+ path = puenteo.export_session(sessions[0].session_id, fmt="md", output="chat.md")
10
+ hits = puenteo.search("gatekeeper")
11
+ pack = puenteo.pull(sessions[0].session_id, query="dmg", mode="query")
12
+
13
+ CLI is the same package: ``puenteo`` / ``pto`` / ``python -m puenteo``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import os
19
+ from pathlib import Path
20
+ from typing import Any, Dict, List, Optional, Sequence, Union
21
+
22
+ from .exporters import SUPPORTED_FORMATS, clipboard_text, render
23
+ from .extract import smart_pull
24
+ from .models import Message, Session, Transcript
25
+ from .providers import PROVIDER_NAMES, list_sessions, load_transcript, resolve_session
26
+ from .rich import (
27
+ find_best_agent_transcript,
28
+ list_sources_for_cwd,
29
+ load_transcript as load_rich_transcript,
30
+ )
31
+ from .search import Hit, search_all, search_transcript
32
+ from .version import APP_NAME, __version__
33
+
34
+ # Re-export rich models for export consumers (Terminal Dashboard, etc.)
35
+ from .rich import Attachment, Message as RichMessage, Transcript as RichTranscript
36
+
37
+
38
+ def status() -> Dict[str, Any]:
39
+ """Discover which agent stores exist and how many sessions each has."""
40
+ from pathlib import Path as P
41
+
42
+ homes = {
43
+ "claude_code": P.home() / ".claude" / "projects",
44
+ "codex": P.home() / ".codex" / "sessions",
45
+ "grok": P.home() / ".grok" / "sessions",
46
+ "pi": P.home() / ".pi" / "agent" / "sessions",
47
+ }
48
+ providers: Dict[str, Any] = {}
49
+ for name, path in homes.items():
50
+ exists = path.is_dir()
51
+ count = 0
52
+ if exists:
53
+ try:
54
+ count = len(list_sessions(providers=[name], limit=500))
55
+ except Exception:
56
+ count = 0
57
+ providers[name] = {
58
+ "path": str(path),
59
+ "exists": exists,
60
+ "sessions": count,
61
+ }
62
+ return {
63
+ "name": APP_NAME,
64
+ "version": __version__,
65
+ "role": "library+cli",
66
+ "providers": providers,
67
+ "export_formats": list(SUPPORTED_FORMATS),
68
+ "provider_names": list(PROVIDER_NAMES),
69
+ }
70
+
71
+
72
+ def get_session(
73
+ ref: str,
74
+ *,
75
+ providers: Optional[Sequence[str]] = None,
76
+ cwd: Optional[str] = None,
77
+ ) -> Optional[Session]:
78
+ """Resolve a session by id prefix, path, or title substring."""
79
+ prov = list(providers) if providers else None
80
+ return resolve_session(ref, providers=prov, cwd=cwd)
81
+
82
+
83
+ def load(
84
+ ref: Union[str, Session],
85
+ *,
86
+ rich: bool = True,
87
+ include_tools: bool = False,
88
+ include_thinking: bool = False,
89
+ providers: Optional[Sequence[str]] = None,
90
+ cwd: Optional[str] = None,
91
+ ) -> Union[Transcript, RichTranscript]:
92
+ """
93
+ Load a session transcript.
94
+
95
+ - ``rich=True`` (default): full export model (tools, attachments) via ``rich`` loaders
96
+ - ``rich=False``: lightweight messages for search / pull
97
+ """
98
+ sess = ref if isinstance(ref, Session) else get_session(ref, providers=providers, cwd=cwd)
99
+ if not sess:
100
+ raise LookupError(f"Session not found: {ref!r}")
101
+ if rich:
102
+ return load_rich_transcript(
103
+ sess.provider,
104
+ sess.path,
105
+ include_tools=include_tools,
106
+ include_thinking=include_thinking,
107
+ )
108
+ return load_transcript(sess, include_tools=include_tools)
109
+
110
+
111
+ def search(
112
+ query: str,
113
+ *,
114
+ session: Optional[str] = None,
115
+ providers: Optional[Sequence[str]] = None,
116
+ cwd: Optional[str] = None,
117
+ limit: int = 20,
118
+ session_limit: int = 40,
119
+ ) -> List[Hit]:
120
+ """Search message text across sessions (or one session)."""
121
+ prov = list(providers) if providers else None
122
+ if session:
123
+ sess = get_session(session, providers=prov, cwd=cwd)
124
+ if not sess:
125
+ return []
126
+ tr = load_transcript(sess)
127
+ return search_transcript(tr, query, limit=limit)
128
+ return search_all(
129
+ query,
130
+ providers=prov,
131
+ cwd=cwd,
132
+ session_limit=session_limit,
133
+ hit_limit=limit,
134
+ )
135
+
136
+
137
+ def pull(
138
+ ref: Union[str, Session],
139
+ *,
140
+ query: Optional[str] = None,
141
+ mode: str = "auto",
142
+ last: int = 0,
143
+ max_chars: int = 12000,
144
+ max_messages: int = 30,
145
+ include_tools: bool = False,
146
+ providers: Optional[Sequence[str]] = None,
147
+ cwd: Optional[str] = None,
148
+ ) -> List[Message]:
149
+ """Build a compact message pack for another agent (same as ``puenteo pull``)."""
150
+ sess = ref if isinstance(ref, Session) else get_session(ref, providers=providers, cwd=cwd)
151
+ if not sess:
152
+ raise LookupError(f"Session not found: {ref!r}")
153
+ tr = load_transcript(sess, include_tools=include_tools)
154
+ return smart_pull(
155
+ tr,
156
+ query=query,
157
+ mode=mode,
158
+ last=last,
159
+ max_chars=max_chars,
160
+ max_messages=max_messages,
161
+ )
162
+
163
+
164
+ def export_session(
165
+ ref: Union[str, Session],
166
+ *,
167
+ fmt: str = "md",
168
+ output: Optional[Union[str, Path]] = None,
169
+ include_tools: bool = False,
170
+ include_thinking: bool = False,
171
+ providers: Optional[Sequence[str]] = None,
172
+ cwd: Optional[str] = None,
173
+ ) -> Path:
174
+ """
175
+ Export a full transcript to a file.
176
+
177
+ ``fmt``: md | txt | html | pdf | json | zip | csv | xml | yaml | all
178
+
179
+ Returns the primary written path (or directory when ``fmt=all``).
180
+ """
181
+ sess = ref if isinstance(ref, Session) else get_session(ref, providers=providers, cwd=cwd)
182
+ if not sess:
183
+ raise LookupError(f"Session not found: {ref!r}")
184
+
185
+ rich_tr = load_rich_transcript(
186
+ sess.provider,
187
+ sess.path,
188
+ include_tools=include_tools,
189
+ include_thinking=include_thinking,
190
+ )
191
+
192
+ fmt_arg = (fmt or "md").lower().strip()
193
+ formats = list(SUPPORTED_FORMATS) if fmt_arg == "all" else [fmt_arg]
194
+ out = Path(os.path.expanduser(str(output))).resolve() if output else None
195
+
196
+ written: List[Path] = []
197
+ for f in formats:
198
+ data, _media, filename = render(
199
+ rich_tr,
200
+ f,
201
+ include_tools=include_tools,
202
+ include_thinking=include_thinking,
203
+ )
204
+ if out is None:
205
+ path = Path.cwd() / filename
206
+ elif out.is_dir() or fmt_arg == "all" or (out.suffix == "" and len(formats) > 1):
207
+ out.mkdir(parents=True, exist_ok=True)
208
+ path = out / filename if out.is_dir() or out.suffix == "" else out
209
+ if out.suffix == "" and not out.is_dir():
210
+ # treat as directory path to create
211
+ out.mkdir(parents=True, exist_ok=True)
212
+ path = out / filename
213
+ else:
214
+ path = out
215
+ path.parent.mkdir(parents=True, exist_ok=True)
216
+
217
+ path.write_bytes(data)
218
+ written.append(path)
219
+
220
+ if not written:
221
+ raise RuntimeError("Nothing exported")
222
+ return written[0] if len(written) == 1 else (out if out and out.is_dir() else written[0].parent)
223
+
224
+
225
+ def export_bytes(
226
+ ref: Union[str, Session],
227
+ *,
228
+ fmt: str = "md",
229
+ include_tools: bool = False,
230
+ include_thinking: bool = False,
231
+ providers: Optional[Sequence[str]] = None,
232
+ cwd: Optional[str] = None,
233
+ ) -> tuple:
234
+ """
235
+ Export without writing a file.
236
+
237
+ Returns ``(data: bytes, media_type: str, filename: str)``.
238
+ """
239
+ sess = ref if isinstance(ref, Session) else get_session(ref, providers=providers, cwd=cwd)
240
+ if not sess:
241
+ raise LookupError(f"Session not found: {ref!r}")
242
+ rich_tr = load_rich_transcript(
243
+ sess.provider,
244
+ sess.path,
245
+ include_tools=include_tools,
246
+ include_thinking=include_thinking,
247
+ )
248
+ return render(
249
+ rich_tr,
250
+ fmt,
251
+ include_tools=include_tools,
252
+ include_thinking=include_thinking,
253
+ )
254
+
255
+
256
+ __all__ = [
257
+ "APP_NAME",
258
+ "SUPPORTED_FORMATS",
259
+ "Attachment",
260
+ "Hit",
261
+ "Message",
262
+ "PROVIDER_NAMES",
263
+ "RichMessage",
264
+ "RichTranscript",
265
+ "Session",
266
+ "Transcript",
267
+ "__version__",
268
+ "clipboard_text",
269
+ "export_bytes",
270
+ "export_session",
271
+ "find_best_agent_transcript",
272
+ "get_session",
273
+ "list_sessions",
274
+ "list_sources_for_cwd",
275
+ "load",
276
+ "load_rich_transcript",
277
+ "load_transcript",
278
+ "pull",
279
+ "render",
280
+ "resolve_session",
281
+ "search",
282
+ "search_all",
283
+ "search_transcript",
284
+ "smart_pull",
285
+ "status",
286
+ ]