npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rashed.parvez/ytscript

v0.2.1

Published

Fetch YouTube transcripts from the terminal. No login, no API key, no subscription.

Readme

ytscript

YouTube transcripts in your terminal. No login, no API key, no subscription, no proxy.

Output is shaped for pasting into Claude Code, llm, or any other AI CLI.

yts                                      # open the app
yts "https://youtu.be/dQw4w9WgXcQ"       # print the transcript

Install

npm install -g @rashed.parvez/ytscript

You need Node 20 or newer. There is one runtime dependency, effect. The app adds two more, @opentui/core and @effect/platform-bun, and both are optional, so the plain output path never loads them.

Two commands are installed: ytscript and the shorter yts.

Feed it to an AI

Piping is the fastest route. The transcript goes to stdout, and nothing else does, so pipes stay clean.

yts "https://youtu.be/VIDEO" | claude -p "Summarise the key arguments"
yts "https://youtu.be/VIDEO" -p "Extract every action item" | claude
yts "https://youtu.be/VIDEO" -f ts | llm "When do they discuss pricing?"

If you would rather paste by hand, put it straight on the clipboard:

yts "https://youtu.be/VIDEO" -c
yts "https://youtu.be/VIDEO" -c -p "Summarise this talk in five bullets"

For a model with a small context window, split it up first:

yts "https://youtu.be/VIDEO" --chunk 8000

Every part is labelled --- part 1 of 3 ---, so you can paste them in order.

The app

Run yts with no arguments and it opens a full-screen app: a field to paste a URL into, and a reader for the result.

yts
                ▀█▀ █▀█ ▄▀█ █▄ █ █▀▀ █▀▀ █▀█ █ █▀█ ▀█▀
                 █  █▀▄ █▀█ █ ▀█ ▄▄█ █▄▄ █▀▄ █ █▀▀  █
               YouTube transcripts without an account.

   ╭─ paste a youtube url ────────────────────────────────────────╮
   │ https://youtu.be/...                                         │
   ╰──────────────────────────────────────────────────────────────╯

  enter  fetch      ^c  quit

In the reader: / searches, y copies what is on screen, t toggles timestamps, a opens the ask-ai panel, esc goes back for another URL, q quits. Searching narrows to matching paragraphs, and y then copies only those. The header keeps a live token count so you know the size of what you are about to paste.

yts <url> -i skips the home screen and goes straight to the reader.

Try it without a network call

yts demo

demo returns a built-in sample transcript. Everything works on it without a single request to YouTube. Formats, search, the reader, the ask-ai panel, all of it. Handy when you are rate limited, offline, or changing the app itself. It works inside the app too, so you can type demo into the URL field.

Ask AI

Press a in the reader and a panel slides in from the right.

╭─ transcript ───────────────╮╭─ ask ai · claude ────────────────╮
│ [0:02]                     ││ s summarise     k key points     │
│ Right, let's get started.  ││ a action items  c chapters       │
│ I want to talk about why   ││ i ask…   m model   o scope: all  │
│ most internal tools die    ││                                  │
│ within a year.             ││ > Summarise this transcript      │
│                            ││                                  │
│ [0:14]                     ││ A talk on why internal tools get │
│ The first reason is that   ││ abandoned within a year, and how │
│ they solve a problem       ││ to build ones that survive.      │
╰────────────────────────────╯╰──────────────────────────────────╯
  tab  transcript    y  copy answer    esc  close

It finds whatever AI CLI you already have and shells out to it. Searched in order: claude, codex, qwen, gemini, opencode, llm. Press m to cycle between the ones you have installed.

No API keys are stored or asked for. The tool runs the CLI exactly as you have already configured and authenticated it, which is the same principle as the rest of ytscript.

Four presets are one keypress each: s summarise, k key points, a action items, c chapters with timestamps. Press i to type any question instead.

o toggles scope. By default you send the whole transcript. Switch it to screen and only the paragraphs matching your current / search go to the model, which costs less and keeps the answer focused. Either way the panel shows you the token count before anything is sent.

y copies the answer.

Answers stream in as they are generated where the CLI supports it. Claude does, via --output-format=stream-json; the others print once they finish.

The app needs Bun

OpenTUI's renderer is native code bound through bun:ffi. There is no Node equivalent yet, so under Node it fails with "OpenTUI native FFI is not available for this runtime yet", regardless of what its engines field claims.

yts handles this for you: if it is running under Node and finds bun on your PATH, it re-runs itself under Bun. You only need to care if Bun is not installed, in which case the app tells you so and everything else keeps working.

Only the app is affected. yts <url> and every flag below run on plain Node.

Why the default output looks the way it does

The default format is plain prose. No cue numbers, no timestamps, no --> arrows.

Subtitle files waste tokens. A typical SRT spends roughly a third of its characters on timing metadata that a language model does not need. Stripping it leaves more of your context window for the actual content.

Paragraph breaks land where the speaker paused for two and a half seconds or more. That gives a model real structure to work with instead of one undifferentiated wall of text.

Each transcript is topped with the title, channel, duration, and source URL. A model reasons better when it knows what it is reading. Use --raw to drop that header.

Options

-l, --lang <code>       Caption language, e.g. en, es, ja
-t, --translate <code>  Machine-translate into this language
-f, --format <name>     text | ts | md | srt | vtt | json
-c, --copy              Copy to clipboard instead of printing
-o, --out <file>        Write to a file
-p, --prompt <text>     Prepend an instruction line
-i, --interactive       Open this URL straight in the app
    --chunk <tokens>    Split into parts of roughly this many tokens
    --wrap <cols>       Hard-wrap prose at this width
    --raw               Omit the title and channel header
    --retry             Wait out YouTube's rate limiting instead of failing
    --langs             List available caption languages
    --stats             Print character and token counts to stderr

Formats

text is prose, and the default. ts adds a [12:34] stamp per paragraph, which is what you want when you plan to ask the model where something was said. md turns those stamps into links that jump to the right second. srt and vtt are standard subtitle files. json gives you raw cues with start and duration in seconds.

Counts go to stderr, never stdout, so --stats is safe inside a pipe.

--langs marks the video's own default track with *. That matters on heavily translated videos, where the track list is alphabetical and the first entry is often not the original language.

How it works

Most transcript tools broke when YouTube started gating captions behind a Proof-of-Origin token. The WEB client now stamps caption URLs with exp=xpo,xpe, and those URLs return HTTP 200 with an empty body unless a real browser has run BotGuard first.

That gate is why competing tools ask you to log in, and why they charge. Serving those requests means either an authenticated session or residential proxy bandwidth, and both cost money. YouTube's own get_transcript endpoint refuses anonymous callers outright with FAILED_PRECONDITION.

This tool sidesteps all of it. The ANDROID_VR and IOS clients hand back caption URLs without the exp gate. No token, no browser, no session. Requests run from your own machine and your own IP, so there is nothing to meter and nobody to bill.

src/Youtube.ts tries those clients in order and keeps the first that returns captions. A failure on one client does not abort the chain.

Rate limiting

YouTube throttles the caption endpoint separately from the metadata endpoint, and more aggressively. Fetching many transcripts back to back will earn a 429 on captions while --langs still works fine.

--retry waits it out with an exponential backoff across four attempts, roughly 75 seconds total. A heavier throttle can outlast that, in which case the answer is simply to wait longer.

If it stops working

YouTube could close the ungated path. If that happens you will see:

YouTube returned an empty caption body. The ungated client path may have changed.

That message means the empty-200 signature is back. The fix is confined to the CLIENTS array at the top of src/Youtube.ts; adding a new client entry is all it takes.

Limits

Videos with captions disabled cannot be transcribed. There is no speech recognition here, so there is nothing to fall back on.

Auto-generated captions have no punctuation or sentence casing. That is YouTube's output, not a formatting bug. Models handle it fine.

--translate uses YouTube's own machine translation. Quality varies by language pair.

Development

bun test           # unit tests
bun run typecheck  # tsc --noEmit
bun run build      # emit dist/
bun run dev <url>  # run from source

Errors are a tagged union (InvalidUrl, VideoUnavailable, NoCaptions, LanguageNotFound, RateLimited, CaptionsGated, NetworkError), so the type checker enforces that every failure has a message in explain.

Tests are offline and cover URL parsing, track selection, paragraph grouping, chunking, and every output format. Network behaviour is deliberately not mocked, because the thing most likely to break is YouTube itself, and a mock would happily keep passing while it did.

License

MIT