- get_transcript recognises YouTube by URL: youtube_transcript() runs even
when media_info() returns {} (no yt-dlp / blocked / offline); a page
scrape is never reported as a transcript for a media URL.
- Episode identity: duration + date only ADMIT candidates. Confirmation =
title similarity >= 0.6 OR show-notes overlap (>= 6 shared distinctive
tokens, Jaccard >= 0.15). Unconfirmed best candidate is returned under
'candidate' with found=False and a confirm-with-user note.
- tests/test_transcript_resolver.py: 17 unit tests, network stubbed —
export list, 403/200-error bodies, both reviewer traps (yt-dlp missing;
same channel/day/length different title), candidate surfacing, incident
retitled-upload confirmation.
Incident link still resolves: identity=description_overlap shared=126.
Co-authored-by: Starchild <noreply@iamstarchild.com>
- __all__ now exports get_transcript / podcast_transcript /
resolve_podcast_episode / media_info / captions_from_media_info;
verified through core.skill_tools loader (P1-1).
- Cross-platform identity: channel = YouTube URL declared in the show's RSS
(else search hit whose channel name EQUALS show name minus stopwords);
episode = duration +-5% AND date +-3d, title tie-break only. Date alone
can no longer select a same-day video from another show (P1-2).
- RSS transcript download requires 2xx, >500 chars, no error-page body;
otherwise falls through instead of reporting found (P1-3).
- Provider 1 is now yt-dlp extract_info(download=False): captions and
metadata for 1800+ sites with zero media bytes, replacing the
YouTube-only ScrapeCreators call (kept as fallback).
Verified: incident Apple link -> found via RSS-declared @a16z channel,
1.2% duration delta, same day, 55k chars; YouTube link 1.9s; identity
traps (same-day other-length, The a16z Show vs The Cooking Show, 403
body, 200 access-denied body) all rejected.
Co-authored-by: Starchild <noreply@iamstarchild.com>
One dispatcher, one return shape {found, kind, source, text, url, title,
note}. podcast_transcript becomes one branch. Verified: YouTube 55k chars
12.6s; Apple link via podcast chain 55k 30s; unknown Snipd URL ->
found=False with note, no download.
Co-authored-by: Starchild <noreply@iamstarchild.com>
Chain: link -> show + episode title + release date (iTunes lookup /
Spotify og tags) -> RSS <podcast:transcript> -> the show's own YouTube
upload of the episode (same channel; title similarity >= 0.6 OR published
within +-2 days) -> captions via youtube_transcript. found=False means
ask the user before any download.
Verified on the 2026-09-17 incident link (The a16z Show / The AI-Native
CRM): 55k chars in 14 s, zero audio. Also carries 2.7.x route text.
Co-authored-by: Starchild <noreply@iamstarchild.com>
Only two of 105 entries carried it, so official-index scoring was
inconsistent, and the list was hand-copied from exports.__all__ and would
drift. Official discovery uses name + description; installed skills still
expose their real tool list.
Co-authored-by: Starchild <noreply@iamstarchild.com>
The tools field feeds starchild-clawd#1698 (search_skills scores official
index entries by tool names, so web-crawler's youtube_transcript ranks for
'youtube' queries).
Co-authored-by: Starchild <noreply@iamstarchild.com>
Incident 2026-09-17 (machine e823d11b053068): user gave an Apple Podcasts link and
asked for the transcript. The URL rule only covered YouTube, so the agent guessed
four hosts, then escalated to 'download the audio and transcribe it' — an
escalation the old text permitted ('Only fall back to downloading when the
transcript path fails'). When the user pasted a JS-rendered transcript page,
web_fetch returned empty and the agent did not retry with scrape_markdown.
- video-analysis: URL rule rewritten as a 4-level route (podcast → video →
JS-page retry → stop & ask). Local download is user-authorised only, not a
fallback.
- web-crawler: new 'Podcast / interview transcript route' section covering
Apple Podcasts / Spotify / episode-name inputs and the empty_extraction retry.
Co-authored-by: Starchild <noreply@iamstarchild.com>
Vendored upstream READMEs now point at their upstream repos / docs sites instead of
files that do not exist in the packaged skill; internal refs corrected.
Co-authored-by: Starchild <noreply@iamstarchild.com>
Keep research separate from action routing, avoid repeated design approvals, and document secure hosted credentials and current npm PATH handling.
Co-authored-by: Starchild <noreply@iamstarchild.com>
From a real trace (agent told the user to run agent-shell when
mcp__browser__page_snapshot briefly failed "not found" after a bridge
reconnect, then misidentified the page):
- New "Stop" section: "not found in registry" right after (re)connect
is transient registration lag — retry next turn or check web_status;
NEVER conclude the browser is gone or mention starchild agent-shell
(unrelated laptop channel).
- New section: "what page is this?" needs only tabs_list (title + url +
activeTabId), no snapshot — works for chrome:// pages too. Snapshot
is for content, not identity.
Review P2: reserve() ran first, so a rejected parameter (0 paid calls) still
burned the single auto-fix. Now: read-only peek for default base/keep → full
local validation → atomic reserve() (re-reads latest state; default base
follows the approved pointer if it moved) → paid call → record().
Regression test: 3 invalid calls leave budget 0/1; corrected call proceeds.
Co-authored-by: Starchild <noreply@iamstarchild.com>
Review P1 #1: edit() held a tx snapshot across the paid call and wrote it back
whole → an approve() during rendering was reverted. Now every mutation is
read-modify-write under a per-tx fcntl lock; reserve() consumes the auto-fix
budget and captures parent BEFORE the request, record() re-reads the latest
state AFTER and appends one revision. Concurrent edits append independently.
Review P1 #2: reject(approved_rev) left the pointer on the rejected image.
Now rolls back to the nearest still-approved ancestor or the original base;
current_base() also refuses non-approved pointers.
Review P2: qa verdict is validated (pass is bool, issues well-formed, pass
consistent with issue severity); invalid → verdict=None, undeterminable=True.
Tests: 11 passed (3 new regression tests reproduce the reviewer's scenarios).
Co-authored-by: Starchild <noreply@iamstarchild.com>
Document that composio-gateway.flycast must use plain HTTP/curl only —
never proxied_get/post, HTTP_PROXY, or curl -x — so caller identity is
not rewritten.
Web path uses frontend_action(action_type=composio_connect) instead of
markdown connect_url links; keep POST /api/connect as non-Web fallback.
Bump skill version to 1.4.0.
Recommend pnpm over npm to minimize peak memory consumption in container
environments, aligning with system PROTOCOL and low-memory guidelines.
Co-authored-by: Starchild <noreply@iamstarchild.com>
Two review findings:
1. The guard was invoked as 'python3 scripts/export.py', which only resolves
when the cwd happens to be the skill directory. Run from a workspace or
repo root it fails with 'file not found' and aborts the export. The step
now resolves the script against the directory of the loaded SKILL.md.
2. The skill advertises backup / hand-off use, but the guard ran before
packaging and so blocked those flows entirely inside Starchild. The check
moved to Step 3 (relay upload): building a bundle works everywhere, only
the relay handoff is refused from inside Starchild. Description and the
refusal message state this, and the flag is now --check-relay
(--check-env kept as an alias).
Co-authored-by: Starchild <noreply@iamstarchild.com>
SKILL.md carried an inlined copy of the check that had already drifted from
scripts/export.py (the inline version omitted the FLY_APP_NAME probe), so the
flow agents actually execute was weaker than the shipped script. SKILL.md now
invokes 'python3 scripts/export.py --check-env' and aborts on non-zero exit;
the script rejects unknown arguments so the guard cannot be silently skipped.
Co-authored-by: Starchild <noreply@iamstarchild.com>