Communitygithub.com

Varnan-Tech/opendirectory

Toma la URL de un repositorio público de GitHub de un competidor, obtiene sus issues abiertos a través de la API REST de GitHub, filtra ruido localmente, agrupa los issues en 6 categorías de demanda, calcula una puntuación de demanda por issue y por clúster, y genera un informe de brechas de demanda clasificado con un resumen de mensajería GTM. Úsalo cuando te pidan escanear issues de un competidor, encontrar lo que los usuarios están pidiendo a gritos, convertir quejas de GitHub en posicionamiento de producto, identificar brechas de funciones de la competencia o generar mensajes a partir de la demanda real de los usuarios. Actívalo cuando un usuario diga "escanear issues del competidor", "qué están pidiendo los usuarios en el repo X", "encontrar brechas de demanda en Y", "convertir issues de GitHub en mensajes" o "qué debería construir basado en quejas de la competencia".

¿Qué es opendirectory?

Esta habilidad para agentes de IA (como Claude Code, Cursor o Codex) te permite analizar sistemáticamente los issues abiertos de un repositorio público de GitHub de un competidor para identificar brechas de demanda no satisfechas. El proceso comienza con la configuración de un token de GitHub (opcional para tasas limitadas), seguido de la captura de hasta 200 issues mediante la API REST de GitHub. Luego, se filtra el ruido localmente (eliminando bots, PRs, issues sin interacción y títulos con patrones predefinidos) y se calcula una puntuación de demanda basada en reacciones y comentarios. Los issues filtrados se clasifican en 6 categorías (brecha de funcionalidad, patrón de error, queja de UX, rendimiento, falta de integración, falta de documentación) y se agrupan en temas de clúster. Finalmente, se genera un informe de brechas de demanda clasificado por puntuación total y un breve resumen de mensajería GTM con titulares y frases de alcance basadas en citas literales de los issues. Esta herramienta es ideal para estrategas de producto, especialistas en GTM e ingenieros que buscan convertir quejas de usuarios en oportunidades de producto basadas en datos reales.

Compatible con✓Claude Code~Codex CLI~Cursor✓Gemini CLI
npx skills add https://github.com/Varnan-Tech/opendirectory/tree/main/skills/gh-issue-to-demand-signal

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Señal de Demanda de Issues de GitHub

Toma un repositorio público de GitHub de un competidor. Obtén sus issues abiertos. Filtra el ruido localmente. Agrúpalos en 6 categorías de demanda. Puntúa según el compromiso real. Genera un informe de brechas de demanda clasificado y un resumen de mensajería GTM.


Regla crítica: Cada título de issue en el resultado debe ser textual de la respuesta de la API de GitHub. Cada nombre de tema de clúster debe derivarse de los títulos de issues reales en ese clúster. Si después del filtrado de ruido quedan menos de 10 issues, detente e informa al usuario: el repositorio es demasiado pequeño para una agrupación confiable. No se permite contenido de issues inventado en ningún lugar.


Errores Comunes

El agente tenderá a...Por qué está mal
Enviar los 200 issues crudos a la IA sin filtrarLos issues de bots, PRs y el ruido sin compromiso inflan los conteos de clústeres y desperdician contexto. Filtra localmente primero.
Usar el conteo de comentarios como la señal principal de demandaLos comentarios incluyen respuestas de mantenedores, discusiones fuera de tema y spam. Las reacciones +1 son la señal de comprador más limpia.
Parafrasear los títulos de issues al resumir clústeresParafrasear pierde el lenguaje exacto del comprador, que es el punto central. Usa los títulos de issues textuales.
Continuar más allá del Paso 4 si quedan menos de 10 issues después del filtradoMenos de 10 issues significa que el repositorio es demasiado pequeño o se dio una URL incorrecta. Agrupar datos dispersos produce categorías sin sentido.
Incluir pull requests en el análisisEl endpoint de Issues de GitHub también devuelve PRs. Filtra verificando que la clave pull_request esté ausente en el objeto del issue.
Marcar un issue como demanda ignorada sin verificar los 3 criteriosLos tres deben ser ciertos: reacciones >= 10, antigüedad >= 180 días, sin etiqueta de planificado/en progreso/hoja de ruta. La falta de un criterio descalifica el issue.

Paso 1: Verificación de Configuración

echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-no configurado, se aplica límite de tasa no autenticado (60 solicitudes/hora)}"

Si GITHUB_TOKEN no está configurado: Continúa. Informa al usuario: "GITHUB_TOKEN no está configurado. El límite de tasa no autenticado es de 60 solicitudes/hora, suficiente para 2 descargas antes de alcanzar el límite. Para uso repetido, añade un token en github.com/settings/tokens (no se necesitan permisos para repositorios públicos)."


Paso 2: Recopilar Entrada

Necesitas:

Analiza propietario y repositorio de la entrada:

python3 << 'PYEOF'
import re, sys, os

raw = "REPO_INPUT_HERE"

# Normalizar a propietario/repositorio
if raw.startswith("http"):
    m = re.search(r"github\.com/([^/]+)/([^/?\s]+)", raw)
    if not m:
        print("ERROR: No se pudo analizar la URL de GitHub. Formato esperado: https://github.com/propietario/repositorio")
        sys.exit(1)
    owner, repo = m.group(1), m.group(2).rstrip("/")
elif "/" in raw:
    parts = raw.strip().split("/")
    owner, repo = parts[0], parts[1]
else:
    print("ERROR: La entrada debe ser una URL de GitHub o un slug propietario/repositorio (ej. vercel/next.js)")
    sys.exit(1)

print(f"Propietario: {owner}")
print(f"Repositorio: {repo}")

with open("/tmp/ghd-target.txt", "w") as f:
    f.write(f"{owner}/{repo}")
PYEOF

Si el análisis falla: Detente. Pregunta: "Proporciona el repositorio de GitHub como una URL (https://github.com/propietario/repositorio) o un slug propietario/repositorio (ej. vercel/next.js)."


Paso 3: Obtener Issues de la API REST de GitHub

Obtén hasta 200 issues (2 páginas de 100). Verifica el límite de tasa después de la primera descarga.

python3 << 'PYEOF'
import json, urllib.request, os, sys
from datetime import datetime, timezone

target = open("/tmp/ghd-target.txt").read().strip()
owner_repo = target
token = os.environ.get("GITHUB_TOKEN", "")

headers = {"Accept": "application/vnd.github+json", "User-Agent": "gh-issue-demand-signal/1.0"}
if token:
    headers["Authorization"] = f"Bearer {token}"

all_issues = []
rate_limit_hit = False

for page in [1, 2]:
    url = f"https://api.github.com/repos/{owner_repo}/issues?state=open&per_page=100&page={page}"
    req = urllib.request.Request(url, headers=headers)

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            # Verificar límite de tasa después de la primera página
            if page == 1:
                remaining = int(resp.headers.get("X-RateLimit-Remaining", 999))
                reset_ts = resp.headers.get("X-RateLimit-Reset", "")
                if remaining == 0:
                    reset_str = datetime.fromtimestamp(int(reset_ts), tz=timezone.utc).strftime("%H:%M UTC") if reset_ts else "desconocido"
                    print(f"ERROR: Límite de tasa de GitHub agotado. Se restablece a las {reset_str}.")
                    print("Añade GITHUB_TOKEN a tu archivo .env para obtener 5000 solicitudes/hora. Consulta github.com/settings/tokens (no se necesitan permisos).")
                    sys.exit(1)
                print(f"Límite de tasa restante: {remaining}")

                # Verificar 404/403
                status = resp.status
                if status == 404:
                    print(f"ERROR: Repositorio '{owner_repo}' no encontrado. Verifica la URL o el slug.")
                    sys.exit(1)

            page_data = json.loads(resp.read())
            if not page_data:
                print(f"Página {page}: vacía, deteniendo.")
                break
            all_issues.extend(page_data)
            print(f"Página {page}: {len(page_data)} issues obtenidos")
    except urllib.error.HTTPError as e:
        if e.code == 404:
            print(f"ERROR: Repositorio '{owner_repo}' no encontrado (404). Verifica la URL o el slug.")
        elif e.code == 403:
            print(f"ERROR: Acceso denegado (403). El repositorio puede ser privado o se alcanzó el límite de tasa.")
        else:
            print(f"ERROR: La API de GitHub devolvió HTTP {e.code}")
        sys.exit(1)
    except Exception as e:
        print(f"ERROR: Falló la descarga de la página {page}: {e}")
        sys.exit(1)

print(f"Total de issues crudos obtenidos: {len(all_issues)}")
json.dump(all_issues, open("/tmp/ghd-raw-issues.json", "w"), indent=2)
PYEOF

Si GitHub devuelve 404: Detente. Informa al usuario: "Repositorio no encontrado. Verifica la URL o el slug e inténtalo de nuevo. Los repositorios privados no son accesibles sin autenticación y permiso explícito de repositorio."

Si GitHub devuelve 403 con el encabezado de límite de tasa: Detente. Muestra la hora de restablecimiento e indica al usuario que añada GITHUB_TOKEN.


Paso 4: Preprocesar Localmente -- Filtrar, Puntuar, Detectar Demanda Ignorada

Sin llamada a la API. Python puro. Ejecuta antes de enviar cualquier cosa a la IA.

python3 << 'PYEOF'
import json, re
from datetime import datetime, timezone

raw = json.load(open("/tmp/ghd-raw-issues.json"))
target = open("/tmp/ghd-target.txt").read().strip()
now = datetime.now(tz=timezone.utc)

noise_patterns = re.compile(
    r"^(chore|deps|bump|renovate|dependabot|release|ci|build|revert)[\s:\[]",
    re.IGNORECASE
)
pr_title_patterns = re.compile(
    r"^(feat|fix|refactor|docs|test|style|perf|chore)(\(.+\))?:",
    re.IGNORECASE
)

filtered = []
noise_count = 0
noise_reasons = {}

for issue in raw:
    # Omitir pull requests (el endpoint de Issues de GitHub también devuelve PRs)
    if "pull_request" in issue:
        noise_count += 1
        noise_reasons["pull_request"] = noise_reasons.get("pull_request", 0) + 1
        continue

    title = issue.get("title", "")
    reactions = issue.get("reactions", {}).get("+1", 0)
    comments = issue.get("comments", 0)
    user_type = (issue.get("user") or {}).get("type", "User")

    # Omitir issues creados por bots
    if user_type == "Bot":
        noise_count += 1
        noise_reasons["bot_author"] = noise_reasons.get("bot_author", 0) + 1
        continue

    # Omitir títulos con patrón de bot
    if noise_patterns.match(title):
        noise_count += 1
        noise_reasons["bot_title"] = noise_reasons.get("bot_title", 0) + 1
        continue

    # Omitir títulos de PR como issue
    if pr_title_patterns.match(title):
        noise_count += 1
        noise_reasons["pr_as_issue"] = noise_reasons.get("pr_as_issue", 0) + 1
        continue

    # Omitir issues sin señal
    if reactions == 0 and comments == 0:
        noise_count += 1
        noise_reasons["zero_signal"] = noise_reasons.get("zero_signal", 0) + 1
        continue

    # Calcular puntuación de demanda
    demand_score = (reactions * 2) + (comments * 0.5)

    # Detectar demanda ignorada
    created_at = issue.get("created_at", "")
    if created_at:
        created = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
        age_days = (now - created).days
    else:
        age_days = 0

    labels = [l.get("name", "").lower() for l in issue.get("labels", [])]
    has_planned_label = any(
        kw in label for label in labels
        for kw in ["in-progress", "planned", "roadmap", "wip", "in progress"]
    )

    ignored_demand = (
        reactions >= 10 and
        age_days >= 180 and
        not has_planned_label
    )

    filtered.append({
        "number": issue["number"],
        "title": title,
        "url": issue.get("html_url", f"https://github.com/{target}/issues/{issue['number']}"),
        "reactions_plus1": reactions,
        "comments": comments,
        "demand_score": demand_score,
        "age_days": age_days,
        "labels": labels,
        "ignored_demand": ignored_demand,
        "body_snippet": (issue.get("body") or "")[:300]
    })

# Ordenar por puntuación de demanda descendente
filtered.sort(key=lambda x: x["demand_score"], reverse=True)

print(f"Issues crudos: {len(raw)}")
print(f"Ruido filtrado: {noise_count} ({', '.join(f'{k}: {v}' for k, v in noise_reasons.items())})")
print(f"Issues para análisis: {len(filtered)}")

if len(filtered) < 10:
    print(f"ERROR: Solo quedan {len(filtered)} issues después del filtrado. Este repositorio tiene muy pocos issues con compromiso para una agrupación confiable.")
    print("Prueba con un repositorio más grande o con más participación de la comunidad.")
    import sys; sys.exit(1)

# Resumen de demanda ignorada
ignored = [i for i in filtered if i["ignored_demand"]]
print(f"Issues de demanda ignorada: {len(ignored)}")

json.dump(filtered, open("/tmp/ghd-filtered-issues.json", "w"), indent=2)
print("Preprocesamiento completado.")
PYEOF

Si quedan menos de 10 issues después del filtrado: Detente. Informa al usuario exactamente cuántos issues se encontraron y filtraron, y por qué el repositorio es demasiado pequeño para una agrupación confiable de demanda.


Paso 5: Agrupar Issues

Imprime los issues filtrados para su análisis:

python3 << 'PYEOF'
import json
filtered = json.load(open("/tmp/ghd-filtered-issues.json"))
target = open("/tmp/ghd-target.txt").read().strip()

issue_list = filtered[:150]
print(f"Repositorio: {target}")
print(f"Issues a agrupar: {len(issue_list)}")
print()
for i in issue_list:
    labels_str = f" [{', '.join(i['labels'][:3])}]" if i['labels'] else ""
    print(f"#{i['number']} [puntuación:{round(i['demand_score'],1)} reacciones:{i['reactions_plus1']}] {i['title']}{labels_str}")
PYEOF

Clasifica cada issue impreso anteriormente en una de estas 6 categorías: feature_gap, bug_pattern, ux_complaint, performance, integration_missing, docs_missing

Reglas:

  • Clasifica cada issue exactamente en una categoría
  • Extrae una declaración de dolor de 1 oración usando el lenguaje exacto del usuario del título -- no parafrasees
  • Identifica 5-8 temas de clúster: frases cortas (3-6 palabras) que capturen los patrones de queja dominantes en todos los issues
  • Sin guiones largos. Sin lenguaje de marketing.

Escribe tu análisis en /tmp/ghd-clusters.json con esta estructura exacta:

{
  "classified_issues": [
    {"number": 123, "category": "feature_gap", "pain_statement": "Los usuarios necesitan X que aún no existe"}
  ],
  "cluster_themes": [
    {"theme_name": "Faltan opciones de exportación", "category": "feature_gap", "issue_numbers": [123, 456, 789]}
  ],
  "category_counts": {"feature_gap": 5, "bug_pattern": 3, "ux_complaint": 4, "performance": 2, "integration_missing": 6, "docs_missing": 1}
}

Después de escribir el archivo, confirma con:

python3 -c "
import json
d = json.load(open('/tmp/ghd-clusters.json'))
print(f'Clasificados: {len(d[\"classified_issues\"])} issues, {len(d[\"cluster_themes\"])} temas')
print('Categorías:', d['category_counts'])
"

Paso 6: Resumen de Mensajería

Calcula la puntuación total de demanda por clúster e imprime los 3 principales:

python3 << 'PYEOF'
import json

filtered = json.load(open("/tmp/ghd-filtered-issues.json"))
clusters = json.load(open("/tmp/ghd-clusters.json"))
target = open("/tmp/ghd-target.txt").read().strip()

demand_by_issue = {i["number"]: i["demand_score"] for i in filtered}
issue_titles = {i["number"]: i["title"] for i in filtered}
issue_reactions = {i["number"]: i["reactions_plus1"] for i in filtered}

enriched_themes = []
for theme in clusters.get("cluster_themes", []):
    issue_nums = theme.get("issue_numbers", [])
    total_demand = sum(demand_by_issue.get(n, 0) for n in issue_nums)
    top_issues = sorted(issue_nums, key=lambda n: demand_by_issue.get(n, 0), reverse=True)[:3]
    enriched_themes.append({
        "theme_name": theme["theme_name"],
        "category": theme["category"],
        "issue_count": len(issue_nums),
        "total_demand_score": round(total_demand, 1),
        "top_issues": [
            {"number": n, "title": issue_titles.get(n, ""), "reactions": issue_reactions.get(n, 0)}
            for n in top_issues
        ]
    })

enriched_themes.sort(key=lambda x: x["total_demand_score"], reverse=True)
json.dump(enriched_themes, open("/tmp/ghd-enriched-themes.json", "w"), indent=2)

print(f"3 principales clústeres para el resumen de mensajería (repositorio: {target}):")
for t in enriched_themes[:3]:
    print(f"\n  {t['theme_name']} ({t['category']}) -- demanda total: {t['total_demand_score']}")
    for ti in t["top_issues"]:
        print(f"    #{ti['number']}: \"{ti['title']}\" ({ti['reactions']} reacciones)")
PYEOF

Genera un resumen de mensajería GTM a partir de los 3 principales clústeres impresos anteriormente.

Reglas:

  • Cada ángulo de posicionamiento debe citar el clúster específico del que proviene
  • Cada gancho de alcance debe citar un título de issue textual entre comillas
  • Los titulares deben incluir un número o un dolor específico nombrado -- sin declaraciones genéricas
  • Sin guiones largos. Sin palabras prohibidas: potente, robusto, perfecto, innovador, revolucionario, optimizar, aprovechar, transformar

Escribe tu resumen en /tmp/ghd-brief.json con esta estructura exacta:

...

[truncado para traducción]

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

Varnan-Tech/opendirectory

Use when the user asks to generate a blog cover image, thumbnail, or article header. Automatically uses modern typography, brand logos, and Google Search grounding to create beautiful 16:9 images with Gemini 3.1 Flash Image Preview.

Varnan-Tech/opendirectory

World-class brand strategist and naming expert. Uses an interrogation-led discovery phase to extract your brand's DNA, then applies scientific naming frameworks (Phonosemantics) and automated multi-TLD domain checking.

Varnan-Tech/opendirectory

Use when the user asks to generate or update a project's CLAUDE or AGENTS context file from a codebase scan. Writes a focused file under 100 lines containing only the non-obvious build commands, conventions, and gotchas Claude Code needs.

Varnan-Tech/opendirectory

Use when the user wants to verify cold emails, enrich a lead list, or autonomously guess email addresses from a CSV using ValidEmail.co or the open-source Reacher engine.

Varnan-Tech/opendirectory

Competitive intelligence orchestrator tracking companies across 8+ platforms (GitHub, Twitter, Reddit, HN, PH, YC Jobs) with heat scores and AI briefings.

Varnan-Tech/opendirectory

Give it your product URL or description. It finds your top 5 competitors, runs three-track PR research across all of them (editorial, podcasts, communities), identifies which channels appear most frequently, looks up the journalist or host for each, and returns a tiered outreach list with story angles and ready-to-send cold pitch drafts tailored to your product. Use when asked to find PR opportunities, discover where competitors got featured, build a media outreach list, find which journalists cover my space, or get pitch templates for press coverage.

Varnan-Tech/opendirectory

Generate high-converting, deep-dive growth case studies in MDX format. Use this skill when asked to write a case study or blog post about a company's growth, tech stack, or product-led strategy. It handles the full pipeline (researching the company via Tavily, generating a 16:9 cover image, quality checking the draft, uploading assets to cloud storage, and pushing directly to the target repository).

Varnan-Tech/opendirectory

Scans your project for outdated npm, pip, Cargo, Go, or Ruby packages. Runs a CVE security audit. Fetches changelogs, summarizes breaking changes with Gemini, and opens one PR per risk group (patch, minor, major). Includes Diagnosis Mode for install conflicts. Use when asked to update dependencies, check for outdated packages, open dependency PRs, scan for package updates, audit for CVEs, or flag breaking changes in upgrades. Trigger when a user says "check for outdated packages", "update my dependencies", "open PRs for dependency updates", "scan for CVEs", or "which packages need upgrading".

Varnan-Tech/opendirectory

Generates and updates README.md and API reference docs by reading your codebase's functions, routes, types, schemas, and architecture. Uses graphify to build a knowledge graph first, then writes accurate docs from it. Use when asked to write docs, generate a README, document an API, update stale docs, create an API reference from code, add an architecture section, or document a project in any language. Trigger when a user says their docs are missing, outdated, or wants to document their codebase without writing it manually.

Varnan-Tech/opendirectory

Evaluates expired domain candidates against a target niche, scores them by topical relevance, historical activity level, and history cleanliness, then outputs a ranked shortlist with explainable reasoning and risk flags.

Varnan-Tech/opendirectory

Brutally honest developer-experience audit for a GitHub repo. Scores 10 DX dimensions (time-to-first-success, README clarity, visual proof, install, quick-start, docs, examples, community, trust, marketing), writes a shareable roast in the requested tone (brutal/honest/kind), produces a prioritized action plan ranked by impact × effort, and sketches an ideal README. Trigger when user says "roast my repo", "audit my README", "dx audit", "developer experience review", "score my GitHub project", "before launch checklist", or "make my repo shareable".

Varnan-Tech/opendirectory

Drafts and designs a complete HTML email newsletter from a topic or content brief. Outputs paste-ready HTML for Loops, Mailchimp, Beehiiv, Resend, or any standard email platform. Includes subject line options and plain-text fallback. Trigger when a user says "write a newsletter", "draft an email newsletter", "create an HTML email", "design an email for my subscribers", or "write a newsletter about [topic]".

Varnan-Tech/opendirectory

Takes a GitHub PR URL or the current branch and writes a plain-English explanation of what it does and why, then posts it as a PR comment. Use when asked to explain a PR, summarize a pull request, write a plain-English description of a PR, add a summary comment to a PR, or understand what a PR changes. Trigger when a user says "explain this PR", "summarize this pull request", "what does this PR do", "add a comment explaining the PR", or shares a GitHub PR URL and asks what it does.

Varnan-Tech/opendirectory

Audit how often LLMs recommend your brand vs competitors and generate a GEO action plan.

Varnan-Tech/opendirectory

Find recurring confusion in your repo's GitHub Discussions, rank it by urgency, and draft the actual docs fixes and content angles — with verbatim community quotes and source links as evidence.

Varnan-Tech/opendirectory

SEO keyword research workflow for blog generation using Google Trends data. Use when writing blog posts, planning content calendars, or optimizing articles for search engines. Finds breakout keywords, builds content structure, and generates SEO-optimized blog outlines targeting tech and developer audiences.

Varnan-Tech/opendirectory

Generates a professionally designed case study PDF for B2B SaaS sales and marketing. Supports 7 page layouts, 9 style presets, 1-4 page output. Trigger when user says "create a case study", "write a customer story", "make a case study PDF", "design a success story", "turn this customer win into a PDF".

Varnan-Tech/opendirectory

Generates data visualization charts (bar, line, area, pie, doughnut, scatter, radar, treemap) as PNG using Apache ECharts v6. 1080×1080px default, 5 style presets, highlight annotations. Trigger when user says "create a chart", "visualize data", "make a bar chart", "line graph", "pie chart", "data visualization", "chart this data", "plot", "graph", or "visualize these numbers".

Varnan-Tech/opendirectory

Creates professionally designed B2B SaaS e-books in HTML + CSS, exported as print-ready PDF. 3–10 pages, 9 style presets, 11 page layout types. Trigger when user says "create an ebook", "design a lead magnet", "make a PDF guide", "build a gated content piece", "write a B2B ebook", "design a white paper", "create a nurture asset", or "make a PDF report".

Varnan-Tech/opendirectory

Fetches low-star App Store and Google Play reviews, clusters them into broken-promise patterns, and generates a ranked copy brief with positioning opportunities.

Skills relacionados