openpod 0.1.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.
Files changed (85) hide show
  1. openpod-0.1.0/.gitignore +25 -0
  2. openpod-0.1.0/CHANGELOG.md +202 -0
  3. openpod-0.1.0/CONTRIBUTING.md +43 -0
  4. openpod-0.1.0/LICENSE +21 -0
  5. openpod-0.1.0/PKG-INFO +241 -0
  6. openpod-0.1.0/README.md +197 -0
  7. openpod-0.1.0/pyproject.toml +90 -0
  8. openpod-0.1.0/src/openpod/__init__.py +48 -0
  9. openpod-0.1.0/src/openpod/align.py +249 -0
  10. openpod-0.1.0/src/openpod/asr.py +164 -0
  11. openpod-0.1.0/src/openpod/brand/banner.txt +4 -0
  12. openpod-0.1.0/src/openpod/brand/brand-tokens.json +69 -0
  13. openpod-0.1.0/src/openpod/brand/card-template.html +34 -0
  14. openpod-0.1.0/src/openpod/bridge.py +486 -0
  15. openpod-0.1.0/src/openpod/briefing.py +232 -0
  16. openpod-0.1.0/src/openpod/captions.py +360 -0
  17. openpod-0.1.0/src/openpod/card.py +99 -0
  18. openpod-0.1.0/src/openpod/catch.py +165 -0
  19. openpod-0.1.0/src/openpod/cli.py +878 -0
  20. openpod-0.1.0/src/openpod/clip.py +502 -0
  21. openpod-0.1.0/src/openpod/config.py +240 -0
  22. openpod-0.1.0/src/openpod/crosswalk.py +205 -0
  23. openpod-0.1.0/src/openpod/deeplink.py +169 -0
  24. openpod-0.1.0/src/openpod/doctor.py +121 -0
  25. openpod-0.1.0/src/openpod/errors.py +153 -0
  26. openpod-0.1.0/src/openpod/exports.py +59 -0
  27. openpod-0.1.0/src/openpod/follows.py +174 -0
  28. openpod-0.1.0/src/openpod/identity.py +132 -0
  29. openpod-0.1.0/src/openpod/imports.py +172 -0
  30. openpod-0.1.0/src/openpod/ingest/__init__.py +8 -0
  31. openpod-0.1.0/src/openpod/ingest/apple.py +183 -0
  32. openpod-0.1.0/src/openpod/ingest/resolve.py +250 -0
  33. openpod-0.1.0/src/openpod/ingest/rss.py +206 -0
  34. openpod-0.1.0/src/openpod/ingest/spotify.py +154 -0
  35. openpod-0.1.0/src/openpod/ingest/validate.py +134 -0
  36. openpod-0.1.0/src/openpod/ingest/youtube.py +188 -0
  37. openpod-0.1.0/src/openpod/library.py +284 -0
  38. openpod-0.1.0/src/openpod/mcp_server.py +569 -0
  39. openpod-0.1.0/src/openpod/media.py +119 -0
  40. openpod-0.1.0/src/openpod/models.py +400 -0
  41. openpod-0.1.0/src/openpod/persona.py +358 -0
  42. openpod-0.1.0/src/openpod/player_manifest.json +374 -0
  43. openpod-0.1.0/src/openpod/scan.py +179 -0
  44. openpod-0.1.0/src/openpod/search/__init__.py +31 -0
  45. openpod-0.1.0/src/openpod/search/embed.py +68 -0
  46. openpod-0.1.0/src/openpod/search/index.py +212 -0
  47. openpod-0.1.0/src/openpod/segments.py +314 -0
  48. openpod-0.1.0/src/openpod/skills/__init__.py +89 -0
  49. openpod-0.1.0/src/openpod/skills/bring-in-my-world/SKILL.md +41 -0
  50. openpod-0.1.0/src/openpod/skills/catch-me-up/SKILL.md +52 -0
  51. openpod-0.1.0/src/openpod/skills/chapter-it/SKILL.md +25 -0
  52. openpod-0.1.0/src/openpod/skills/cut-the-clip/SKILL.md +65 -0
  53. openpod-0.1.0/src/openpod/skills/find-the-moment/SKILL.md +40 -0
  54. openpod-0.1.0/src/openpod/skills/follow-this/SKILL.md +24 -0
  55. openpod-0.1.0/src/openpod/skills/make-it-shareable/SKILL.md +63 -0
  56. openpod-0.1.0/src/openpod/skills/remember-this/SKILL.md +61 -0
  57. openpod-0.1.0/src/openpod/skills/set-up-my-persona/SKILL.md +85 -0
  58. openpod-0.1.0/src/openpod/skills/sharpen-my-persona/SKILL.md +33 -0
  59. openpod-0.1.0/src/openpod/skills/whats-new/SKILL.md +40 -0
  60. openpod-0.1.0/src/openpod/sync.py +875 -0
  61. openpod-0.1.0/src/openpod/theme.py +189 -0
  62. openpod-0.1.0/src/openpod/transcript.py +197 -0
  63. openpod-0.1.0/tests/conftest.py +79 -0
  64. openpod-0.1.0/tests/test_align.py +93 -0
  65. openpod-0.1.0/tests/test_asr_fallback_gate.py +139 -0
  66. openpod-0.1.0/tests/test_brand_consistency.py +203 -0
  67. openpod-0.1.0/tests/test_bridge.py +286 -0
  68. openpod-0.1.0/tests/test_caption_track.py +143 -0
  69. openpod-0.1.0/tests/test_captions_settings_doctor.py +181 -0
  70. openpod-0.1.0/tests/test_catch_and_search.py +58 -0
  71. openpod-0.1.0/tests/test_clip_and_cli.py +40 -0
  72. openpod-0.1.0/tests/test_crosswalk.py +83 -0
  73. openpod-0.1.0/tests/test_deeplink.py +22 -0
  74. openpod-0.1.0/tests/test_identity.py +116 -0
  75. openpod-0.1.0/tests/test_imports_skills_notes.py +222 -0
  76. openpod-0.1.0/tests/test_library_persona_follows.py +69 -0
  77. openpod-0.1.0/tests/test_persona_layers.py +120 -0
  78. openpod-0.1.0/tests/test_resolve_kinds.py +205 -0
  79. openpod-0.1.0/tests/test_scan.py +76 -0
  80. openpod-0.1.0/tests/test_segments.py +286 -0
  81. openpod-0.1.0/tests/test_summary.py +94 -0
  82. openpod-0.1.0/tests/test_sync.py +538 -0
  83. openpod-0.1.0/tests/test_theme.py +166 -0
  84. openpod-0.1.0/tests/test_transcript.py +47 -0
  85. openpod-0.1.0/tests/test_validate_and_links.py +120 -0
@@ -0,0 +1,25 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv*/
9
+ venv/
10
+
11
+ # Tooling
12
+ .pytest_cache/
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .coverage
16
+ htmlcov/
17
+
18
+ # OS
19
+ .DS_Store
20
+
21
+ # OpenPod local libraries created while developing/testing
22
+ .openpod/
23
+
24
+ # Never commit internal strategy docs into the public repo
25
+ /Docs/
@@ -0,0 +1,202 @@
1
+ # Changelog
2
+
3
+ All notable changes to OpenPod are documented here. Format loosely follows
4
+ [Keep a Changelog](https://keepachangelog.com/); versions follow SemVer.
5
+
6
+ ## [0.1.0] — 2026-07-16
7
+
8
+ First Stage 1 alpha: local-pure, pull-only.
9
+
10
+ ### Added
11
+ - **Two-layer persona: who you are vs. what this folder is for.** Global
12
+ (`~/.openpod/persona.md`): role, language & style, standing
13
+ interests/filters, what you want from long-form — true in every
14
+ workspace, the layer a cloud tier would sync. Workspace
15
+ (`.openpod/persona.md`): current projects, local amplify/filter topics,
16
+ and the machine-derived blocks (derived from *this* library). The MCP
17
+ `persona` tool returns both layers labeled — identity/language lean
18
+ global, relevance decisions lean workspace — and routes writes by kind.
19
+ Existing single-file personas get a **split proposal**: sections are
20
+ classified (Role → global, Current projects → local, unknowns → ask) and
21
+ offered as one multiple-choice confirmation — the user picks, never
22
+ writes prose — then `persona_split` / `openpod persona split --to-global`
23
+ moves exactly the confirmed sections. Machine-owned blocks are never
24
+ offered and never move. CLI: `persona init|show --global`, `persona
25
+ split`. House rule codified in the skill: users choose from options
26
+ mined from evidence; system files never demand elaboration.
27
+ - **`summary.md` — the cross-session memory artifact**: a durable, local
28
+ distillation of an episode, written by the agent (or user) after actually
29
+ engaging with it, and loaded by future sessions as context. The contract
30
+ is deliberately minimal: OpenPod owns placement and identity frontmatter
31
+ (entry_id, episode_key — sync-ready for the cloud tier, updated_at,
32
+ revision count); the body — structure, language, depth, relevance — is
33
+ entirely the author's. `write_summary(append=True)` lets sessions add
34
+ dated takes without losing prior ones. Surfaces: MCP `save_summary` +
35
+ `recall_summaries` (the "load what past sessions learned" call),
36
+ summary included in `get_briefing`, CLI `openpod summary` (read / write
37
+ / list), and the **Remember This** skill — guidance (personalize from
38
+ the session, exclude what didn't matter, cite moments, budget tokens),
39
+ explicitly defaults-not-mandates.
40
+ - **No more silent Whisper fallback** — a cost-based gate keeps interaction
41
+ fast: every ASR run is *announced* (why captions failed, the wall-clock
42
+ estimate, and that it's local compute — no tokens or API cost — recorded
43
+ in `transcript.notes`), but the user is only *asked* when it's worth
44
+ asking. Caption failures are classified: permanent (no captions exist →
45
+ ASR just runs, announced) vs transient (429/IP throttling → a short
46
+ transcription under `asr.auto_threshold_seconds` (default 180s) runs with
47
+ a notice, a long one raises structured `captions_unavailable` with both
48
+ options priced — wait and re-catch for free captions, or `asr="now"` /
49
+ `--asr-now`). `--no-asr` never transcribes. Throttled-then-ASR
50
+ transcripts carry "a later re-catch may upgrade this to platform
51
+ captions". RSS enclosure ASR announces itself the same way. Caption
52
+ language follows `locale.preferred_language` (a Hebrew interview fetches
53
+ Hebrew captions first, then fallback, then English).
54
+ - **Caption data contract + export package** (naval-clipper design port —
55
+ contracts, not renderers):
56
+ - Clips carry a **structured caption track in their `.json`**: phrases
57
+ chunked by the karaoke rules (≤5 words / sentence / `‖` force-break —
58
+ the marker translated Hebrew keeps so nothing regroups it), language +
59
+ `rtl`, and honest `timing` (`approximate` for rolling YouTube cues,
60
+ `exact` only with word-level).
61
+ - **Word-level track** (`--word-level` on `clip` and `captions`, asr
62
+ extra): whisper on just the clip window/file → `*.words.json` — the
63
+ karaoke-highlight layer.
64
+ - **Burn gate**: burning refuses unverified translations and any
65
+ `captions_file` that fails the coverage check (dropped speech named in
66
+ the note); RTL burns warn to eyeball `verify.png` for missing glyphs.
67
+ - **Export package**: the working folder now also gets `deeplink.txt`,
68
+ `label.json` (label + optional `hook`), `*.words.json`, and `verify.png`
69
+ (start/mid/end frames of the burned derivative, tiled).
70
+ - **Speakers**: `{title}`/`{role}` template aliases and a workspace
71
+ `speakers.yaml` (keyed by show) as fallback — teach OpenPod a show's
72
+ hosts once.
73
+ - **`clip.style` setting** (default `karaoke`) reserved for renderers.
74
+ - **New skill: Make it Shareable** — the export pipeline end-to-end
75
+ (doctor first, captions as data, line-locked translation, verified burn,
76
+ export-folder hygiene); `cut-the-clip` hands off to it, and
77
+ `sharpen-my-persona` may offer `locale.preferred_language` when the
78
+ library skews non-English.
79
+ - **The two-artifact clip contract** (QA session 2026-07-09): the library
80
+ clip is the clean master and is never mutated; presentation — captions,
81
+ name-plate labels, social encodes — are **export derivatives**:
82
+ - **Settings schema v0** (`settings.yaml`, documented defaults merged at
83
+ read time): `locale.preferred_language`/`fallback_language`,
84
+ `clip.captions` (`off|soft|burn`), `clip.caption_language`,
85
+ `clip.burn_in` (default **false**), `clip.label`, `clip.label_template`,
86
+ `clip.export_dir`, `clip.keep_clean_master`. Unknown keys are rejected
87
+ with the documented list. CLI `openpod settings`, MCP `settings` tool.
88
+ - **Caption primitive** (`captions.py`): SRT/VTT sidecars generated from
89
+ `transcript.window(start, end)` — full coverage by construction — plus a
90
+ **coverage verify** (`openpod captions --verify`, MCP `captions`) that
91
+ catches dropped lines in edited/translated caption files before any
92
+ burn-in. Translation is flagged (`translation_needed`), never guessed.
93
+ - **Clip presentation flags**: `--captions off|soft|burn`, `--lang`,
94
+ `--label` / `--label-from-meta`, `--out DIR` (and `clip.export_dir`):
95
+ derivatives and copies land in the working folder; burned files land
96
+ *only* there. Labels render from structured `source.speakers`
97
+ (name/role), never from agent memory.
98
+ - **`openpod doctor`**: ffmpeg capability report (libass/drawtext probed
99
+ before any burn is promised), settings status, and library hygiene —
100
+ foreign files (venvs, scripts, previews) under `library/` are flagged
101
+ and quarantined to `library/_scratch/` with `--fix`.
102
+ - **cut-the-clip skill v2**: teaches the two-artifact contract — never
103
+ burn the master, captions from transcript with verify, labels from
104
+ meta, working files never in the vault.
105
+ - **Deterministic cross-platform resolution** — episode identity is product
106
+ code now, not per-session agent improvisation:
107
+ - **Apple Podcasts ingest** (`ingest/apple.py`): `id{show}` + `i={episode}`
108
+ + country parsed from the URL, resolved via the keyless iTunes Lookup API
109
+ to the RSS feed and the episode's guid — the exact episode, never
110
+ "newest in feed".
111
+ - **Spotify ingest** (`ingest/spotify.py`): oEmbed title → iTunes episode
112
+ search → guid-anchored feed match. No HTML scraping, no open-ended web
113
+ search; Spotify-exclusives fail with a structured, actionable error.
114
+ - **Identity crosswalk** (`crosswalk.py`, `EpisodeIdentity`): per-episode
115
+ records under `.openpod/crosswalk/` mapping YouTube/Spotify/Apple/RSS ids
116
+ with per-field provenance, plus a show-level table. Resolved once, cached
117
+ forever; schema is user-free so it can later be served globally.
118
+ - **Confirmation gate**: fuzzy matches below confidence 0.8 raise/return a
119
+ structured `needs_confirmation` candidate instead of silently ingesting;
120
+ `catch(..., confirmed=true)` records the user's confirmation. Confidence
121
+ is corroborated (title + duration + date), never a single signal.
122
+ - **No silent defaults**: unclassifiable links are `unknown` and return a
123
+ structured `unresolved_link` error (with what was tried), instead of
124
+ falling through into the RSS parser.
125
+ - **Origin/source decoupling + capability-aware links** — `catch` persists
126
+ what the user pasted (`origin`) separately from the transcript source;
127
+ `build_link()` renders the output on the user's platform (explicit arg →
128
+ `preferred_playback_app` setting → origin) from a per-app capability table
129
+ and reports honestly when it degrades ("Apple opens the episode, not the
130
+ moment"). New MCP tools: `playback_link`, `set_preferred_app`.
131
+ - **Cross-source timestamp alignment** (`align.py`) — piecewise-constant
132
+ offset maps recovered by probe-and-bisect (≈10–20 fifteen-second ASR
133
+ probes per episode, O(log n) per ad break), cached on the crosswalk record.
134
+ - **Content-type validation** (`ingest/validate.py`) — HEAD + magic-byte
135
+ sniffing with a defined support matrix; tracking redirectors resolve to
136
+ their terminal URL once; unsupported content raises `unsupported_format`
137
+ instead of being piped into Whisper on luck.
138
+ - **Shared media cache + video-preserving clips** — one download per episode
139
+ under `.openpod/media/`; `clip` keeps video by default when the source has
140
+ it (`--video` / `--audio-only` on the CLI, `video=` on MCP) and reports
141
+ `has_video` + a capability note *before* delivering a mismatched artifact.
142
+ - **Follow-time show resolution** — following an Apple show URL resolves and
143
+ stores its RSS feed once, recorded in the crosswalk show table.
144
+
145
+ - **`catch`** — ingest a podcast/RSS/YouTube link (or local file) into a local
146
+ library: timed transcript, extracted key ideas, navigable TOC, and a briefing
147
+ scaffold for the agent to complete.
148
+ - **`search`** — cross-episode local search over the whole library (SQLite FTS5
149
+ keyword retrieval + a dependency-free local embedding re-rank).
150
+ - **`export_timestamps`** — emit timed segments + deep-links as JSON or Markdown.
151
+ - **`clip`** — sentence-snapped, local clip extraction via ffmpeg, plus a
152
+ shareable deep-link card. Local files only; no re-hosting.
153
+ - **`follow` / `digest`** — local follow list (podcast RSS + YouTube channels)
154
+ and a "what's new" digest polled locally. Digest items carry
155
+ `in_rotation`: follows the user has never caught are flagged as the
156
+ **discovery pool** — the What's New skill surfaces interest-matching
157
+ episodes from unfamiliar shows with a cheap trial (briefing or the one
158
+ matching beat) instead of burying them under familiar names. Persona
159
+ filters apply to topics, never to sources.
160
+ - **`persona`** — a local, user-owned `persona.md` that evolves from the
161
+ library. Machine-owned blocks (`## Derived from my library`, `## Imported
162
+ interests (opt-in)`) are regenerated in place under a marker contract that
163
+ provably never touches the human-authored sections.
164
+ - **`import`** — opt-in OPML import: stages the raw export verbatim in
165
+ `.openpod/imports/`, merges subscriptions into `follows.yaml` de-duplicated
166
+ and tagged with `source:` provenance, and refreshes the persona's
167
+ imported-interests block. Snapshot, not sync; no OAuth, no cloud APIs.
168
+ - **`note`** — append to an episode's user-owned `notes.md` (also exposed to
169
+ agents as `append_note`, append-on-request only).
170
+ - **`persona scan`** — a fast, bounded, read-only workspace evidence sweep
171
+ (project folders by recency, markdown doc titles, `CLAUDE.md` headings,
172
+ library themes, follows) powering a **guess-and-confirm interview**: the
173
+ agent presents multi-select options mined from the evidence and the user
174
+ picks instead of typing. Extra roots are opt-in only. The trust-question
175
+ was retired (follows/OPML answer it); all interview questions are now
176
+ multi-select guesses with a free-text escape hatch.
177
+ - **Skills catalog** — nine versioned `SKILL.md` bundles shipped in the package
178
+ (Catch Me Up, Set Up My Persona, Bring In My World, Sharpen My Persona, Find
179
+ the Moment, Cut the Clip, What's New, Chapter It, Follow This); `openpod
180
+ skills` lists them and the MCP server exposes them as prompts.
181
+ - **MCP server** (`openpod-mcp`) exposing the primitives to AI agents, plus
182
+ `list_entries`, `append_note`, and `import_opml`; every tool result carries
183
+ the entry id and the path(s) touched.
184
+ - **CLI UX contract** — mutating commands print the path(s) they wrote;
185
+ read/produce commands take `--json` shaped identically to the MCP tools;
186
+ errors name the fix; `catch` on a persona-less workspace suggests "Set Up My
187
+ Persona".
188
+ - Transcript parsers for WebVTT, SubRip, YouTube `json3`, and cue lists.
189
+ - Deep-link construction for YouTube, Spotify, and open podcast enclosures.
190
+ - **The anchor ladder** — every idea, search hit, and TOC entry carries up to
191
+ three labeled deep-links, because different readers want different
192
+ landings: the creator's **chapter** ("take me to the topic"), the detected
193
+ **beat** where the idea starts being articulated ("play the argument from
194
+ its start"), and the exact **moment** ("show me where they said it").
195
+ Beats come from creator chapters when the source ships them (YouTube
196
+ chapter markers, or timestamp lists parsed from the description) — with
197
+ over-long chapters sub-segmented by local lexical-cohesion topic detection
198
+ — and from pure topic detection otherwise. No link ever lands mid-sentence
199
+ without a labeled alternative. Beats persist in `meta.json`; hits carry
200
+ `chapter_*`, `segment_*`, and `deeplink` fields; `ideas.md` renders the
201
+ ladder with a one-line legend.
202
+ - MIT license; offline test suite; CI across Python 3.10–3.13.
@@ -0,0 +1,43 @@
1
+ # Contributing to OpenPod
2
+
3
+ Thanks for helping! OpenPod is a local-first tool with a few hard invariants —
4
+ please keep them in mind, they're the whole point of the project.
5
+
6
+ ## The invariants (non-negotiable)
7
+
8
+ 1. **Local-pure.** No code path may upload the user's audio, transcripts,
9
+ library, or persona to any server. If a feature needs a server, it's Stage 2
10
+ and doesn't belong here.
11
+ 2. **Pull, not push.** No background processes, no always-on monitoring.
12
+ 3. **Artifacts, not telemetry.** Persist what the user explicitly produces.
13
+ Never capture behavioral signal (play/skip/replay).
14
+ 4. **Navigate, don't re-host.** Sharing is deep-links to the moment in the
15
+ native player, never republished audio/video.
16
+
17
+ If a change touches these, open an issue first.
18
+
19
+ ## Dev setup
20
+
21
+ ```bash
22
+ python -m venv .venv && . .venv/bin/activate
23
+ pip install -e '.[dev]'
24
+ pytest
25
+ ```
26
+
27
+ The test suite must stay **fully offline** — no network calls, no model
28
+ downloads. Use fixtures (see `tests/conftest.py`). Heavy/network dependencies
29
+ (`yt-dlp`, `youtube-transcript-api`, `faster-whisper`, `mcp`) are optional
30
+ extras and must be imported lazily so the core installs and tests without them.
31
+
32
+ ## Style
33
+
34
+ - Standard library first; add a dependency only when it clearly earns its place.
35
+ - Keep the public surface small (`catch`, `clip`, `export_timestamps`, `search`,
36
+ plus `follow`/`persona`).
37
+ - New optional integrations go behind an extra in `pyproject.toml` and a lazy
38
+ import with a clear "install `openpod[...]`" error message.
39
+
40
+ ## License
41
+
42
+ By contributing you agree your contributions are licensed under
43
+ **MIT**, the project's license.
openpod-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yoav Dori
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.
openpod-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: openpod
3
+ Version: 0.1.0
4
+ Summary: Local-first CLI + MCP server that turns long-form audio/video into personalized, cited, navigable briefings — everything runs on your machine.
5
+ Project-URL: Homepage, https://github.com/openpodhq/openpod
6
+ Project-URL: Repository, https://github.com/openpodhq/openpod
7
+ Author: OpenPod contributors
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ai-agent,cli,local-first,mcp,podcast,transcript,youtube
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Multimedia :: Sound/Audio
17
+ Classifier: Topic :: Text Processing
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: feedparser>=6.0
20
+ Requires-Dist: pyyaml>=6.0
21
+ Provides-Extra: all
22
+ Requires-Dist: faster-whisper>=1.0; extra == 'all'
23
+ Requires-Dist: mcp>=1.2; extra == 'all'
24
+ Requires-Dist: numpy>=1.24; extra == 'all'
25
+ Requires-Dist: playwright>=1.40; extra == 'all'
26
+ Requires-Dist: youtube-transcript-api>=0.6; extra == 'all'
27
+ Requires-Dist: yt-dlp>=2024.1; extra == 'all'
28
+ Provides-Extra: asr
29
+ Requires-Dist: faster-whisper>=1.0; extra == 'asr'
30
+ Provides-Extra: bridge
31
+ Provides-Extra: card-png
32
+ Requires-Dist: playwright>=1.40; extra == 'card-png'
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=8.0; extra == 'dev'
35
+ Provides-Extra: embeddings
36
+ Requires-Dist: numpy>=1.24; extra == 'embeddings'
37
+ Provides-Extra: mcp
38
+ Requires-Dist: mcp>=1.2; extra == 'mcp'
39
+ Provides-Extra: sync
40
+ Provides-Extra: youtube
41
+ Requires-Dist: youtube-transcript-api>=0.6; extra == 'youtube'
42
+ Requires-Dist: yt-dlp>=2024.1; extra == 'youtube'
43
+ Description-Content-Type: text/markdown
44
+
45
+ <p align="center">
46
+ <picture>
47
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/openpodhq/openpod/main/assets/brand/wordmark-blue-dark-636.png">
48
+ <img src="https://raw.githubusercontent.com/openpodhq/openpod/main/assets/brand/wordmark-blue-636.png" alt="OpenPod" width="212">
49
+ </picture>
50
+ </p>
51
+
52
+ <p align="center">
53
+ <img src="https://raw.githubusercontent.com/openpodhq/openpod/main/assets/demo.gif" alt="An AI agent pulls the minutes that matter from hundreds of hours across the shows you follow — every hit timestamped to verify" width="720">
54
+ </p>
55
+
56
+ # OpenPod
57
+
58
+ **Pull the ten minutes that matter — to you.**
59
+
60
+ [![PyPI](https://img.shields.io/pypi/v/openpod)](https://pypi.org/project/openpod/) [![Python](https://img.shields.io/pypi/pyversions/openpod)](https://pypi.org/project/openpod/) [![CI](https://github.com/openpodhq/openpod/actions/workflows/ci.yml/badge.svg)](https://github.com/openpodhq/openpod/actions) [![License](https://img.shields.io/badge/license-MIT-black)](LICENSE) [![Telemetry](https://img.shields.io/badge/telemetry-0-black)](#nothing-leaves-your-machine) [![MCP](https://img.shields.io/badge/MCP-server-black)](https://modelcontextprotocol.io)
61
+
62
+ Hundreds of hours pile up across the shows you follow. OpenPod pulls out the minutes you're chasing — cited, timestamped to verify, extracted on your own machine — as the starting point your AI agent works from.
63
+
64
+ OpenPod is a local-first agent toolkit — primitives plus packaged skills — that your agent drives over MCP or the CLI. It connects to nothing external; the only corpus is your library on disk.
65
+
66
+ ```bash
67
+ pip install openpod
68
+ openpod catch "https://example.com/podcast/feed.xml"
69
+ ```
70
+
71
+ **Catch your first episode** — one command, about a minute. You get a timestamped transcript, the key ideas, and jump-to-moment links, all as plain files in a folder you own.
72
+
73
+ `0 servers · 0 accounts · 0 telemetry · $0 to run · test suite runs fully offline`
74
+
75
+ > **Status:** Stage 1 alpha. Local-pure, pull-only. It does what this page shows — nothing more yet.
76
+
77
+ ---
78
+
79
+ ## The problem
80
+
81
+ You subscribe to twelve shows. You're forty episodes behind, every one is three hours long, and the one segment you actually needed last week — you can't find it, because nobody remembers a timestamp. So the backlog grows, the guilt compounds, and "I'll listen on the weekend" becomes a lie you tell yourself on Mondays.
82
+
83
+ Cloud summarizers answer this by asking you to upload what you listen to — and to trust their summary with no way to check it. OpenPod doesn't summarize your listening on someone else's computer. It gives **your** agent the ability to listen, cite, and link — locally.
84
+
85
+ ## What you get
86
+
87
+ Point OpenPod (or your agent) at an RSS feed or a YouTube link. In return:
88
+
89
+ - **A briefing you can verify.** Every claim carries a timestamp and a deep link. Don't trust the summary? Click and hear the source in the native player. OpenPod never re-hosts anyone's audio — it navigates you to the moment.
90
+ - **A library that compounds.** Everything you catch lands in `.openpod/` as plain files — transcripts, briefings, ideas, notes, clips. Searchable across everything you've ever caught, keyword and semantic, all local.
91
+ - **Personalized by your own agent.** OpenPod reads your local `persona.md`, so your AI foregrounds the decisions, tools, and numbers *you* care about and drops the rest — the same episode briefs differently for you than for the next person, and no cloud model ever sees who you are.
92
+
93
+ ## Nothing leaves your machine
94
+
95
+ There is no OpenPod server, no account, no telemetry — there is nothing to opt out of, because nothing phones home. The only corpus is your own library on disk. Publisher transcripts and captions are used when they exist; otherwise audio is transcribed locally with Whisper. Your agent brings the intelligence; OpenPod supplies extraction, structure, citations, deep links, and local search. That's the whole trade — and it's why running it costs nothing.
96
+
97
+ ## Your agent can drive all of it
98
+
99
+ OpenPod's toolkit is exposed as an [MCP](https://modelcontextprotocol.io) server, so Claude Code, Cowork, Codex — any MCP client — can catch, search, clip, and brief by name:
100
+
101
+ ```bash
102
+ openpod-mcp
103
+ ```
104
+
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "openpod": { "command": "openpod-mcp", "env": { "OPENPOD_HOME": "/path/to/your/workspace" } }
109
+ }
110
+ }
111
+ ```
112
+
113
+ Say *"catch me up on this episode"* and the agent runs `catch`, reads your local persona file, and writes a cited briefing back into your library — then hands you the path. Mid-conversation, no dashboard, no copy-paste.
114
+
115
+ ## The four primitives
116
+
117
+ | Tool | Does | Writes |
118
+ |---|---|---|
119
+ | `catch <link>` | Ingest → transcribe → structure → brief | `transcript.json`, `ideas.md`, `briefing.md` |
120
+ | `search <query>` | Retrieve across your local library | ranked cues with deep links |
121
+ | `export_timestamps <entry>` | Navigable TOC with deep links | JSON / Markdown |
122
+ | `clip <entry> <start> <end>` | Sentence-snapped local cut + shareable deep-link card | media in `clips/`, card |
123
+
124
+ Every mutating command prints the path it wrote — the output of an action is the address of the artifact it produced. Every read command takes `--json`, shaped identically to the matching MCP tool.
125
+
126
+ ## Skills — the features, by name
127
+
128
+ Primitives are the API; **skills are the product.** Each is a versioned instruction bundle (`openpod skills` lists them; the MCP server exposes them as prompts) that tells an agent how to compose the primitives and report back:
129
+
130
+ | Skill | You ask | It writes |
131
+ |---|---|---|
132
+ | **Catch Me Up** | "Brief me on this episode" | `briefing.md`, `ideas.md` |
133
+ | **Find the Moment** | "Where did they talk about X?" | deep-linked hits |
134
+ | **What's New** | "What dropped in my feeds?" | digest |
135
+ | **Cut the Clip** | "Pull the shareable minute" | media + share card |
136
+ | **Chapter It** | "Give me a navigable TOC" | timestamps (md/json) |
137
+ | **Follow This** | "Keep me on this show" | `follows.yaml` entry |
138
+ | **Set Up My Persona** | "Learn who I am" | `persona.md` — yours, local |
139
+ | **Sharpen My Persona** | "Update what you know about me" | persona Derived block |
140
+ | **Bring In My World** | "Import my subscriptions" | `imports/`, `follows.yaml` |
141
+
142
+ ## More things it does
143
+
144
+ ```bash
145
+ # Already have a transcript? Skip the network entirely.
146
+ openpod catch "https://example.com/ep1" --kind podcast --transcript ./ep1.vtt
147
+
148
+ # Search everything you've ever caught (keyword + local semantic)
149
+ openpod search "what did they say about raft consensus"
150
+
151
+ # Follow feeds locally; see what's new on your schedule — nothing polls in the background
152
+ openpod follow "https://example.com/feed.xml"
153
+ openpod digest
154
+
155
+ # Cut a local, sentence-snapped clip (needs ffmpeg) + a shareable deep-link card
156
+ openpod clip "test-pod/episode-one-consensus" 320 385
157
+
158
+ # Import subscriptions from a file you export (OPML) — point-in-time, opt-in
159
+ openpod import ~/Downloads/overcast.opml
160
+
161
+ # Annotate; notes feed future personalization
162
+ openpod note "test-pod/episode-one-consensus" "the Raft segment matters for our design"
163
+ ```
164
+
165
+ Everything lands in a plain, user-owned tree:
166
+
167
+ ```
168
+ .openpod/
169
+ persona.md # who you are (evolving, local)
170
+ follows.yaml # subscribed RSS + YouTube channels
171
+ library/<show>/<episode>/
172
+ transcript.json briefing.md ideas.md notes.md clips/
173
+ index/ # local search index (SQLite FTS + embeddings)
174
+ ```
175
+
176
+ Want it on two machines? Put the tree in git or Dropbox yourself. OpenPod won't sync it for you.
177
+
178
+ ## Install options
179
+
180
+ ```bash
181
+ pip install openpod # core: RSS ingest, local search, deep links, clip
182
+ pip install 'openpod[youtube]' # + YouTube captions & audio
183
+ pip install 'openpod[asr]' # + local Whisper transcription
184
+ pip install 'openpod[mcp]' # + the MCP server
185
+ pip install 'openpod[all]' # everything
186
+ ```
187
+
188
+ `ffmpeg` on your `PATH` is required for `clip`.
189
+
190
+ ## Use it as a library
191
+
192
+ ```python
193
+ from openpod import catch, search
194
+
195
+ result = catch("https://example.com/ep1", transcript_path="ep1.vtt", kind="podcast")
196
+ print(result.entry_id, len(result.transcript), result.ideas)
197
+
198
+ for hit in search("consensus algorithms"):
199
+ print(hit.show, hit.start, hit.deeplink)
200
+ ```
201
+
202
+ ## Trust & posture FAQ
203
+
204
+ **Does OpenPod download or re-host other people's content?**
205
+ It navigates, it doesn't republish. Deep links open the moment in YouTube, Spotify, or the open podcast enclosure. Podcast RSS — content published for open distribution — is the primary surface; YouTube support is an optional module that uses official captions, or local transcription of audio you can already access. OpenPod never touches DRM, paywalls, or age gates.
206
+
207
+ **What data do you collect about me?**
208
+ None. There is no endpoint to send it to.
209
+
210
+ **How is transcription chosen?**
211
+ Publisher-provided timed transcripts and captions first — free, fast, good for navigation (±2–4s cue timing). Otherwise local Whisper (`asr` extra). Nothing is uploaded either way. `clip` snaps cuts to cue boundaries so clips land on sentences.
212
+
213
+ **Why MIT?**
214
+ [MIT](LICENSE): maximally permissive and frictionless — install it at work without tripping a license policy, embed it, build on it, no obligations. OpenPod isn't protected by its license; it's protected by being local-pure and simple. No copyleft, no lock-in.
215
+
216
+ **Can I hack on it without heavy models or network?**
217
+ Yes — the test suite runs fully offline against fixtures.
218
+
219
+ Built by Yoav — building OpenPod in public.
220
+
221
+ ## Development
222
+
223
+ ```bash
224
+ git clone https://github.com/openpodhq/openpod
225
+ cd openpod
226
+ python -m venv .venv && . .venv/bin/activate
227
+ pip install -e '.[dev]'
228
+ pytest
229
+ ```
230
+
231
+ Contributions that package or connect OpenPod travel furthest: Homebrew formula, Docker image, MCP client recipes, note-app bridges. Open an issue first; response is fast.
232
+
233
+ ---
234
+
235
+ **Long-form is a lot. OpenPod pulls the ten minutes that matter — to you — with a link straight to every one.**
236
+
237
+ ```bash
238
+ pip install openpod
239
+ ```
240
+
241
+ If it saved you an hour this week, [star the repo](../../stargazers) — stars are how the next person finds it.