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

@dropugc/discover

v0.5.0

Published

DropUGC Discover — TikTok slideshow & video format research engine, driven by your own Claude via MCP. Cross-platform companion (Windows/macOS/Linux).

Readme

slideshow-intel

Motor de research de cuentas TikTok slideshow-first, operado desde Claude Code (u otro cliente MCP) o desde la CLI. Herramienta personal; réplica del núcleo de Scroll Show (ver docs/LOGICA_SCROLLSHOW.md, docs/SPEC_MVP.md).

  • Node 22 + TypeScript. patchright (fork drop-in de Playwright que cierra la fuga de CDP Runtime.enable) controlando tu Chrome real con un perfil dedicado (%LOCALAPPDATA%\slideshow-intel\chrome-profile), siempre con ventana. Sin stealth JS: en un navegador real la coherencia gana al spoofing (ver docs/GUIA_INDETECTABILIDAD.md).
  • Coherencia por país: por defecto hereda el locale/timezone del SO del usuario (coherente con su IP/región, para cualquier país). Override con SLI_LOCALE / SLI_TZ solo si hace falta.
  • Intercepta los XHR de TikTok (/api/search/, /api/post/item_list/, /api/user/detail/…), calcula métricas (views/follower, slideshow %, cadence, consistency 0-10…), guarda todo en SQLite (node:sqlite, sin deps nativas) y descarga slides + metadata.json.
  • MCP server Streamable HTTP en 127.0.0.1:43121/mcp con Bearer token (%LOCALAPPDATA%\slideshow-intel\mcp-token.txt).

Instalación (usuario final) — Windows / macOS / Linux

Requiere Node ≥ 22.13 y Google Chrome instalado. Cross-platform vía npm (sin instalador por-OS, sin firma/notarización):

npm i -g @dropugc/discover        # o:  npx @dropugc/discover setup
sli setup                       # registra el MCP en Claude Code + imprime los próximos pasos
sli login                       # inicia sesión en TikTok una vez (Chrome dedicado)
sli link <token>                # (opcional) conecta a DropUGC → Discover para ver los resultados en la web
sli sync [runId] [--all]        # (opcional) re-envía un run pasado a DropUGC (los runs se sincronizan solos al terminar o detenerse)

Luego, en Claude Code: "búscame cuentas ganadoras de slideshows/videos en mi nicho". El daemon arranca solo. SLI_CHROME=<ruta> si Chrome no está en la ruta estándar.

Uso rápido (desarrollo)

npm install
npm run sli -- doctor            # Chrome, data dir, sesión TikTok, librería
npm run sli -- login             # abre el Chrome dedicado en tiktok.com; inicia sesión ahí una vez
npm run sli -- discover "glow up tips" "jawline exercises" -t 5 -f slideshow   # o -f video / -f any
npm run sli -- analyze hyginmaxxing --days 15
npm run sli -- download -a hyginmaxxing -c 3 --sort views
npm run sli -- download -l https://www.tiktok.com/@user/photo/123456
npm run sli -- library search glow --sort views_per_follower
npm run sli -- runs

Arquitectura: daemon único

Un solo proceso (el daemon) es dueño del Chrome. Claude Code y la CLI son clientes: le mandan el trabajo por HTTP en vez de abrir su propio navegador, así que nunca chocan por el perfil.

  • El daemon sirve /mcp (para Claude) y /api/* (para la CLI) en 127.0.0.1:43121.
  • Los comandos que tocan el navegador (discover, analyze, download, login, doctor) levantan el daemon solos si no está corriendo, y hacen streaming de los logs.
  • Los que no tocan el navegador (library, runs) leen la SQLite directo (seguro con WAL).
  • Instancia única: si ya hay un daemon, un segundo mcp serve no arranca.

Desde Claude Code

npm run sli -- mcp install        # una vez: registra el daemon en Claude Code (scope user)
# el daemon arranca solo cuando Claude o la CLI lo necesitan; para dejarlo fijo en primer plano:
npm run sli -- mcp serve

Luego, en cualquier sesión de Claude Code: "tengo una app de X, búscame 5 cuentas de slideshows con ≥100k views en 30 días y dime qué formato usan". Tools: app_status, start_discovery, analyze_account, start_download, job_status, wait_job, job_results, resume_job, stop_job, list_runs, sync_run, save_insight, search_accounts, get_account, judge_candidates.

Discovery con objetivo (0.2.0): relevancia + checkpoints de revisión

El score de discovery era 100 % rendimiento (views/follower, save rate, momentum…) sin noción de lo que buscas: un creator de lifestyle con un post patrocinado que TikTok asoció al keyword "ganaba". Desde 0.2.0 el motor es objective-aware en cada punto donde gasta presupuesto:

  • start_discovery(objective, include_terms, exclude_terms, visual_cues, min_on_topic_pct=20, review=true). Claude escribe una rúbrica una vez (frases multi-palabra: "ai photo", "photo edit", "epik" — nunca un genérico suelto como "ai"); el daemon la aplica determinísticamente.
  • Gate del candidato (antes de gastar ~24 s midiendo): con el caption/hashtags/bio del hit de búsqueda, los autores claramente off-topic no se miden (skipped_off_topic en el resultado).
  • Relevancia de la cuenta medida: on_topic_pct (% de posts recientes que matchean) y on_topic_views_pct → veredicto automático on_topic | adjacent | off_topic. adjacent = pocos posts on-topic pero cargan las views (creator con posts patrocinados que sí ganan en el nicho); off_topic → rechazada.
  • Solo los on_topic siembran sonidos/hashtags/sugeridas y el PageRank de la librería (adiós al compounding de basura). Los keywords se buscan intercalados (1 cada 2 mediciones) hasta agotarlos.
  • Checkpoint de revisión (review=true, cada 4 PASS + al final): wait_job devuelve state: "review" con una tarjeta por candidata (bio, top captions, hashtags, ≤3 covers como imágenes). Claude responde judge_candidates(job_id, batch_id, verdicts); off_topic des-pasa la cuenta, la saca de los seeds, retira los arms que sembró y libera el slot (el target vuelve a N-1 y el presupuesto de medición se refunda con el coste medio de un ganador) → el run sigue buscando. Sin respuesta en review_timeout_s (180 s) sigue con los veredictos automáticos. La CLI (sli discover --objective --include --exclude) no tiene reviewer: solo veredictos automáticos.
  • Los frames de video NO se analizan en discovery (caro, detectable, rara vez cambia el veredicto que ya dan cover + caption + bio); eso sigue en start_download para los finalistas.

0.3 — Claude en cada decisión (protocolo de decisión): el job ya no se limita a un checkpoint final. Cede en cada punto donde se gasta presupuesto y wait_job devuelve state: "decision" con un brief: triage (tras cada búsqueda: autores con caption/hashtags/bio + cover para los dudosos → triage_candidates: probe|skip + new_queries/retire_queries), verdict (tras sondas baratas de ~8 s: bio, captions recientes marcadas [on-topic], 2 covers reducidas, métricas aproximadas → judge_probes: measure|reject + relevancia) y review (raro: PASS con relevancia solo automática → judge_candidates). Solo lo aprobado recibe la medición profunda (~23 s). Las decisiones van en cola (una pendiente a la vez) y no bloquean las manos: el daemon sigue sondeando lo ya aprobado mientras Claude decide; sin respuesta en review_timeout_s deciden las heurísticas. peek_covers(usernames) sirve covers bajo demanda cuando el texto no basta. Los skips/verdicts de Claude se recuerdan por colección. Ver docs/ALGORITMO_IDEAL.md.

0.3 fase 2 — LEARN / EXPAND / BUDGET con Claude (fuentes gateadas al nacer, recompensa diferida):

  • Recompensa de cada fuente (query / hashtag / sonido): el pull ya no se premia por "autores nuevos" sino por lo que esos autores llegan a ganar después: probe aprobado (+0.1), verdict measure (+0.3), PASS (+0.6; se devuelve si se des-pasa). El bandit UCB reparte las búsquedas entre las fuentes vivas con esa recompensa; cada candidata recuerda qué arm la sacó. job_results muestra por fuente surfaced → probed → measured → passed.
  • learn (tras cada 2 PASS, o antes si la frontera se queda corta): una tarjeta por ganador (métricas, captions, 2 covers, sus sonidos y hashtags con cuántas cuentas del run los comparten y su lift — evidencia, no veredicto, y las cuentas sugeridas del perfil) → approve_expansion: why_it_wins por ganador (queda en resultados y dashboard), follow_sounds / follow_hashtags (se vuelven arms del bandit) y follow_suggested (true | lista → pasan a la frontera y se sondean). Nada se sigue sin aprobación; ganador omitido o timeout → heurística de lift (la de 0.2.0, solo on_topic). Además add_include / add_exclude evolucionan la rúbrica en caliente (versión +1; los gates siguientes usan la nueva) y new_queries / retire_queries.
  • budget (cada 4 búsquedas, y al agotarse el presupuesto con PASS < target — antes el run moría en silencio): tabla de rendimiento por fuente + presupuesto restante + eventos recientes → steer_budget: continue | expand (+extra_searches, default 4; las sondas y mediciones crecen en proporción; con new_queries) | narrow (retire_queries) | stop (termina y puntúa). Máximo 5 decisiones de budget por run; las notas quedan en steering del resultado.
  • Fuentes: las queries nuevas de Claude y los arms que aprueba en learn nacen "sin pull" (el bandit los busca pronto, una vez); el gemelo photo de cada keyword nace con prior bajo (compite por UCB) salvo en runs format: slideshow, donde sigue siendo primario. retire_queries retira ambas pestañas. Tras stop no se abren más decisiones: las sondas pendientes se cierran como "stopped before verdict" y no se expande nada.
  • Memoria por colección con desenlace: además del veredicto (on/adjacent/off) se recuerda si la cuenta pasó en algún run de la colección (ganador conocido → se mide sin volver a preguntar) o fue rechazada y por qué (se muestra en el triage como "rejected last time (razón) — probe only if it may have grown"; sin cliente no se vuelve a sondear). Triage admite ignore (on-topic pero no vale la pena ahora: no se recuerda nada, a diferencia de skip = off-topic). El brief de triage muestra como máximo 24 autores (primero los dudosos por prior; una página de sonido lista ~100 usuarios al azar): el resto se resuelve sin preguntar (los seguros ya están en la frontera; los dudosos se descartan). retire_queries también acepta sound|<id>, ht|<tag> o #tag para retirar una fuente de expansión.
  • CLI / sin cliente: no hay learn ni budget; expansión por lift como siempre.

Afinos de tiempo (0.3.0): una visita tiene un suelo anti-detección (nav + 2.5–4 s + 2–3.5 s por scroll) que no se toca; lo que se recorta es el TRABAJO: (1) toda medición para de hacer scroll en cuanto la ventana de métricas está cubierta (≥3 posts más antiguos que su inicio) — la sonda salta su scroll y la profunda baja de ~23 s a 5–10 s o a 0 s (si la primera página de la sonda ya cubría la ventana no se re-visita: se reutilizan sus posts, sonidos y sugeridas); (2) ganadores conocidos de la colección usan la medición de la librería hasta 7 días (24 h para el resto) → 0 s; (3) duraciones en los logs (probe — … (7.1 s, covers the window), revisited — 2 scroll(s) (11.2 s)). Medido con scripts/mcp-autopilot.mjs (reviewer de latencia 0): target 5 en nicho nuevo = 7.7 min, 14/18 profundas gratis; el coste dominante pasa a ser la sonda (~7 s × N) — reducir N es el trabajo del triage de Claude y de la memoria. postsPerWeek se calcula sobre el tramo realmente visto (≤90 d), no sobre 90 días fijos.

0.3 fase 3 — cerebro del nicho + calibración + desatendido: todo lo que Claude decide se vuelve memoria de la colección (collection de start_discovery / --collection), persistida en SQLite (collection_brain, collection_sources, collection_calibration, run_accounts.why_it_wins):

  • Cerebro: objetivo + rúbrica tal como Claude la dejó (v0 al crearla, +1 por cada evolución; la numeración continúa entre runs), fuentes (queries, sonidos, hashtags) con rendimiento ACUMULADO surfaced→probed→measured→passed, quién las creó (user/claude/heuristic/brain) y cuáles retiró Claude, ganadores conocidos con su why_it_wins, cuentas juzgadas, lista de "dudosos". Tool collection_brain(collection?) (sin argumento lista las colecciones) y CLI sli brain [collection].

  • FRAME: un run de una colección conocida arranca desde su cerebro: la rúbrica que pasa Claude se une a la del cerebro (o se usa la del cerebro si no pasa ninguna), el objetivo por defecto es el del cerebro, las queries/arms productivas vuelven como arms con su rendimiento histórico como prior (compiten, no saltan la cola), las retiradas no vuelven solas (si el user las pasa explícitamente como keyword, se reactivan y se avisa), los ganadores conocidos se miden sin preguntar. El prompt /discover-niche llama a collection_brain antes de start_discovery.

  • Calibración: el daemon cuenta, por colección, cuándo cada regla automática coincide con Claude — gate (rúbrica en triage vs probe/skip), auto_verdict (relevancia automática vs la de Claude en verdict/review) y auto_probe (measure/reject automático vs Claude). Se muestra en los briefs ("el gate acierta 78 % aquí, n=40") y en el cerebro.

  • Desatendido (CLI / review=false): usa el cerebro (rúbrica, queries productivas, memoria de cuentas). Una regla actúa sola solo si está calibrada ≥80 % con n≥10 (sin votos aún, actúa — no hay nada mejor); si está calibrada por debajo, lo que ella descartaría queda como "undecided" (no se sonda / se rechaza marcado, sin memoria) y sale en result.pending y en el cerebro para que el próximo run con Claude lo resuelva.

  • wait_job(job_id, timeout_s≤120) hace long-poll: Claude no necesita dormir ni sondear desde una shell.

  • start_discovery(..., collection: "Pushup apps") agrupa el run en una colección (nicho/proyecto) del dashboard de DropUGC. Los runs de una colección comparten cuentas, top posts e insights.

  • save_insight(collection, title, body_md): Claude guarda su análisis ("por qué gana el formato") como nota markdown en la colección — la investigación sobrevive al chat.

Sync a DropUGC: cada run de discovery se envía solo al terminar o al detenerse (stop_job / sli stop): el run detenido se puntúa igual con lo medido, se guarda en la librería (runs.result) y se sincroniza. El payload lleva, por cuenta ganadora, sus top 5 posts (views, caption, sonido, link) y un thumbnail chico del cover subido a DropUGC (best-effort; el mp4/frames nunca salen de tu máquina). Si el push falla (sin internet, DropUGC caído) queda en el outbox (runs.synced_at IS NULL) y el daemon lo reintenta al arrancar y cada 10 min (SLI_SYNC_RETRY_MIN); job_results muestra el estado del sync. sync_run / sli sync [runId] [--all] re-envían runs pasados (idempotente). Runs que quedaron running por un daemon muerto se marcan error al arrancar. Nota: discovery mide a propósito más allá del target (buffer ×1.5 para la selección diversa) — llegar al target no es motivo para detenerlo.

Captcha / verificación

Si TikTok muestra una verificación en la ventana de Chrome, el job pasa a paused; resuélvela a mano y se reanuda solo (o resume_job).

Variables

SLI_HOME (data dir), SLI_CHROME (ruta a chrome.exe), SLI_LOCALE (default en-US), SLI_MCP_PORT (default 43121).

Estructura

src/browser    launch de Chrome, sesión, captcha, pacing
src/tiktok     parsers de JSON, captura XHR, perfil, búsqueda
src/metrics    fórmulas
src/library    SQLite
src/jobs       job manager (running/paused/done, partial en disco)
src/core       discovery / analyze / download / engine
src/mcp        servidor MCP
src/cli        comandos
tests/         unit tests (npm test)