CommunityRedacción y edicióngithub.com

Registrador de Actualizaciones de Producto

Indica a la habilidad lo que tu producto ha lanzado. Escribe una entrada pulida y fechada en un `docs/changelog.md` vivo y produce un paquete de contenido listo para usar: hilo de tweets, publicación de LinkedIn, fragmento de correo electrónico y una frase corta.

¿Qué es Registrador de Actualizaciones de Producto?

Esta habilidad de agente de IA está diseñada para automatizar el registro de cambios (changelog) de productos y la creación de contenido promocional. Cuando le informas sobre las novedades de tu producto (a través de texto libre, commits de Git locales o Pull Requests de GitHub), la habilidad transforma esa información técnica en una entrada de changelog pulida y fechada, añadiéndola al principio del archivo `docs/changelog.md`. Simultáneamente, genera un paquete de contenido completo que incluye un hilo de tweets (ideal para X/Twitter), una publicación de LinkedIn, un fragmento de correo electrónico y una frase corta de resumen, todo siguiendo unas reglas de estilo predefinidas para asegurar consistencia y tono profesional. Es útil para equipos de desarrollo, startups o cualquier persona que gestione un producto de software y quiera mantener a sus usuarios y seguidores informados de forma eficiente, ahorrando tiempo y manteniendo un registro histórico vivo. Funciona en cualquier entorno de desarrollo que tenga acceso a Python y Git, y requiere un archivo `SKILL.md` con los pasos de ejecución.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Varnan-Tech/opendirectory/tree/main/skills/product-update-logger

Installed? Explore more Redacción y edición skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Preguntar en tu IA favorita

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

Documentación

product-update-logger

Indica a esta habilidad lo que tu producto ha lanzado. Escribe una entrada pulida de changelog en docs/changelog.md (un registro vivo, con la entrada más nueva primero) y produce simultáneamente un paquete de contenido: hilo de tweets, publicación de LinkedIn, fragmento de correo electrónico y una frase corta.

Fuentes de entrada: texto libre de tu mensaje, commits de git leídos automáticamente del repositorio local, o PRs de GitHub si proporcionas un repositorio. Cualquier combinación funciona.

Archivos de Referencia

Lee estos archivos antes de cada ejecución:

cat references/changelog-format.md
cat references/content-rules.md
cat references/noise-filter.md

Paso 1: Verificación de Configuración

echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-no configurado -- obtención de PRs de GitHub deshabilitada}"
echo "Git:          $(git rev-parse --is-inside-work-tree 2>/dev/null && echo 'repositorio detectado' || echo 'no es un repositorio git')"
echo "Changelog:    $(ls docs/changelog.md 2>/dev/null && echo 'existe' || echo 'se creará')"

Nota si git está disponible y si ya existe un changelog. Esto determina el formato de la etiqueta de versión.


Paso 2: Analizar la Entrada

Recopila de la conversación:

  • items -- descripción en texto libre de lo que se lanzó (separado por tuberías | si hay varios). Opcional si git está disponible.
  • since -- cuántos días hacia atrás mirar. Por defecto: 7. El usuario puede decir "últimas 2 semanas" (14) o "desde el último lanzamiento".
  • repo -- "propietario/repositorio" de GitHub para obtener PRs. Opcional.
  • version_label -- etiqueta personalizada como "v2.1.0" o "La Actualización de Velocidad". Opcional; por defecto es basada en la fecha.

Si el usuario no dijo nada sobre los items Y no hay repositorio git: Pregunta "¿Qué lanzaste? Enumera las características, correcciones o mejoras -- una por línea."

Si git está disponible y el usuario no dijo nada específico: Procede con la lectura automática de git en el Paso 3. Muestra al usuario lo que se encontró y confirma antes de transformar.

Escribe la entrada analizada:

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

inp = {
    "items": "",          # LLENAR: texto libre separado por tuberías, o "" si no hay
    "since": 7,           # LLENAR: número entero de días
    "repo": "",           # LLENAR: "propietario/repositorio" o ""
    "version_label": ""   # LLENAR: "" significa automático (basado en fecha), o cadena personalizada
}

with open("/tmp/pul-input.json", "w") as f:
    json.dump(inp, f, indent=2)
print(f"Desde hace: {inp['since']} días")
print(f"Items de texto libre: {inp['items'] or 'ninguno (se usará git/GitHub)'}")
print(f"Repositorio GitHub: {inp['repo'] or 'ninguno'}")
print(f"Etiqueta de versión: {inp['version_label'] or 'automática (basada en fecha)'}")
PYEOF

Paso 3: Ejecutar el Script de Recopilación

ls scripts/gather.py 2>/dev/null && echo "script encontrado" || echo "ERROR: scripts/gather.py no encontrado"
GITHUB_TOKEN="${GITHUB_TOKEN:-}" python3 scripts/gather.py \
    --since "$(python3 -c "import json; print(json.load(open('/tmp/pul-input.json'))['since'])")" \
    --repo "$(python3 -c "import json; print(json.load(open('/tmp/pul-input.json'))['repo'])")" \
    --items "$(python3 -c "import json; print(json.load(open('/tmp/pul-input.json'))['items'])")" \
    --output /tmp/pul-raw.json

Verifica la salida:

python3 -c "
import json
with open('/tmp/pul-raw.json') as f:
    d = json.load(f)
print(f'Items encontrados:      {d[\"total_items\"]}')
print(f'Ruido filtrado:   {d[\"noise_filtered\"]}')
print(f'Git disponible:    {d[\"git_available\"]}')
print(f'GitHub disponible: {d[\"github_available\"]}')
print(f'Fuentes: git={sum(1 for i in d[\"items\"] if i[\"source\"]==\"git_commit\")}, '
      f'prs={sum(1 for i in d[\"items\"] if i[\"source\"]==\"github_pr\")}, '
      f'texto={sum(1 for i in d[\"items\"] if i[\"source\"]==\"free_text\")}')
print()
print('Items:')
for item in d['items']:
    print(f'  [{item[\"source\"]}] {item[\"subject\"]}')
"

Si total_items == 0: Detente. Dile al usuario: "No se encontraron items lanzados. Describe lo que lanzaste, apúntame a un repositorio git con commits recientes, o añade un repositorio de GitHub con repo: propietario/repositorio y un GITHUB_TOKEN."

Muestra la lista de items al usuario y pregunta: "Estos son los items que encontré. ¿Algo que añadir o eliminar antes de que escriba el changelog?"

Espera confirmación o ediciones. Si el usuario dice "todo bien", "adelante", o no hace cambios, continúa. Si el usuario añade o elimina items, actualiza /tmp/pul-raw.json en consecuencia antes del Paso 4.


Paso 4: Generar Entrada de Changelog

Imprime los items para contexto:

python3 -c "
import json
with open('/tmp/pul-raw.json') as f:
    d = json.load(f)
print(json.dumps(d['items'], indent=2))
print()
print(f'Formato de changelog existente: {d[\"existing_changelog\"][\"format\"]}')
print(f'Última etiqueta: {d[\"existing_changelog\"][\"last_label\"]}')
print(f'Hoy: {d[\"date\"]}')
"

Instrucciones para la IA: Transforma cada item en bruto de lenguaje técnico a un lenguaje orientado al beneficio para el usuario. Sigue references/changelog-format.md para las reglas de transformación y ejemplos.

Reglas:

  • NO inventes resultados o métricas. "40% más rápido" debe provenir de los datos de origen. Si no hay número en el commit o PR, no añadas ninguno.
  • Usa tiempo pasado: "Añadido", "Corregido", "Mejorado" -- no "Añade", "Corrige"
  • Asigna exactamente una categoría a cada item: Nuevo, Mejorado, Corregido o Interno
  • Interno: Incluir solo si es relevante para desarrolladores (cambios de API, cambios disruptivos). Omite secciones vacías.
  • Omite cualquier cosa que corresponda a: cambios de pruebas, cambios de CI, commits solo de documentación

Determina la etiqueta de versión:

  • Si el usuario especificó una: úsala exactamente.
  • Si existing_changelog.format == "semver": incrementa según los cambios (parche solo para correcciones, menor para cualquier característica nueva).
  • Por defecto: Semana del [Mes Día, Año] usando la fecha de hoy.

Escribe la entrada en /tmp/pul-entry.json:

{
  "label": "Semana del 23 de abril de 2026",
  "date": "2026-04-23",
  "new": [
    {"title": "Modo oscuro", "description": "Actívalo en Ajustes > Apariencia. Funciona en todas las vistas."}
  ],
  "improved": [
    {"title": "Tiempo de respuesta de la API", "description": "Un 40% más rápido de media. El panel ahora carga en menos de 1 segundo."}
  ],
  "fixed": [
    {"title": "Exportación CSV", "description": "Las exportaciones ya no omiten la última fila."}
  ],
  "under_the_hood": []
}

Verifica la entrada:

python3 -c "
import json
with open('/tmp/pul-entry.json') as f:
    e = json.load(f)
print(f'Etiqueta: {e[\"label\"]}')
total = 0
for cat in ['new', 'improved', 'fixed', 'under_the_hood']:
    items = e.get(cat, [])
    if items:
        print(f'{cat.replace(\"_\", \" \").title()} ({len(items)}):')
        for item in items:
            print(f'  - {item[\"title\"]}: {item[\"description\"]}')
        total += len(items)
print(f'Total: {total} items')
"

Paso 5: Generar Paquete de Contenido

Usando la entrada del changelog del Paso 4, genera las cuatro piezas de contenido. Sigue references/content-rules.md estrictamente.

Frase corta (máx 20 palabras): Una oración que cubra los 1-2 items más importantes. Lenguaje sencillo, sin jerga.

Hilo de tweets (3-5 tweets):

  • Tweet 1: Gancho -- "Lanzamos [N] cosas esta semana." o adelanta con la característica más importante.
  • Tweets 2-N: Un item por tweet, 1-2 oraciones como máximo.
  • Último tweet: "Changelog: [enlace]" o "La semana que viene más." (opcional)
  • Cada tweet estrictamente con menos de 280 caracteres.
  • Sin hashtags. Sin guiones largos. Voz activa.

Publicación de LinkedIn:

  • Sin markdown (los asteriscos se renderizan literales en LinkedIn)
  • Sin hashtags
  • Voz de fundador: "Lanzamos", no "Estamos emocionados de anunciar"
  • Párrafos cortos (1-2 oraciones cada uno), líneas en blanco entre ellos
  • Cerrar con una pregunta u observación, no con una llamada a la acción
  • 150-400 palabras en total

Fragmento de correo electrónico:

  • Asunto: "Lo que lanzamos esta semana: [item más importante] + [1 más]"
  • Cuerpo: 50-100 palabras. "Esto es lo que lanzamos esta semana:" luego viñetas.

Escribe en /tmp/pul-content.json:

{
  "one_liner": "Modo oscuro, API más rápida y un error de exportación corregido.",
  "tweet_thread": [
    "Lanzamos 3 cosas esta semana.",
    "El modo oscuro ya está disponible. Actívalo en Ajustes > Apariencia. Funciona en todas partes.",
    "El tiempo de respuesta de la API ahora es un 40% más rápido. El panel carga en menos de un segundo.",
    "Corregido: las exportaciones CSV omitían la última fila. Eso ya no ocurre.",
    "Changelog: [enlace]"
  ],
  "linkedin_post": "Lanzamos 3 actualizaciones esta semana.\n\nEl modo oscuro ya está disponible. Actívalo en Ajustes, bajo Apariencia. Funciona en todas las vistas.\n\nEl tiempo de respuesta de la API es un 40% más rápido de media. El panel ahora carga en menos de un segundo para la mayoría de los usuarios.\n\nTambién corregimos un error en el que las exportaciones CSV omitían silenciosamente la última fila. Si te pasó y dejaste de exportar, vale la pena intentarlo de nuevo.\n\n¿Qué característica has estado esperando?",
  "email_snippet": {
    "subject": "Lo que lanzamos esta semana: modo oscuro + API más rápida",
    "body": "Esto es lo que lanzamos esta semana:\n\n- Modo oscuro: actívalo en Ajustes > Apariencia\n- Tiempo de respuesta de la API: 40% más rápido, el panel carga en menos de 1 segundo\n- Corregido: las exportaciones CSV ya no omiten la última fila\n\nChangelog completo a continuación."
  }
}

Paso 6: Autoverificación

python3 -c "
import json, re

with open('/tmp/pul-raw.json') as f:
    raw = json.load(f)
with open('/tmp/pul-entry.json') as f:
    entry = json.load(f)
with open('/tmp/pul-content.json') as f:
    content = json.load(f)

full_text = json.dumps(entry) + json.dumps(content)
fails = 0

# Verificación 1: Sin guiones largos
if chr(8212) in full_text:
    print('FALLO: se encontró guión largo -- reemplázalo con guión normal')
    fails += 1
else:
    print('APROBADO: sin guiones largos')

# Verificación 2: Palabras prohibidas
banned = ['poderoso', 'robusto', 'sin fisuras', 'innovador', 'revolucionario',
          'optimizar', 'aprovechar', 'transformar', 'revolucionar', 'emocionados de anunciar',
          'contentos de anunciar', 'estamos encantados', 'de vanguardia', 'de primera clase',
          'de clase mundial', 'desbloquear', 'encantador']
found = [w for w in banned if w.lower() in full_text.lower()]
if found:
    print(f'FALLO: palabras prohibidas encontradas: {found}')
    fails += 1
else:
    print('APROBADO: sin palabras prohibidas')

# Verificación 3: Longitud de tweets
thread = content.get('tweet_thread', [])
long_tweets = [(i+1, len(t)) for i, t in enumerate(thread) if len(t) > 280]
if long_tweets:
    print(f'FALLO: tweets de más de 280 caracteres: {long_tweets}')
    fails += 1
else:
    print(f'APROBADO: todos los {len(thread)} tweets tienen menos de 280 caracteres')

# Verificación 4: LinkedIn sin hashtags
li = content.get('linkedin_post', '')
if re.search(r'#[A-Za-z]', li):
    print('FALLO: hashtags encontrados en la publicación de LinkedIn')
    fails += 1
else:
    print('APROBADO: sin hashtags en LinkedIn')

# Verificación 5: Sin markdown en LinkedIn
if '**' in li or '__' in li:
    print('FALLO: formato markdown en LinkedIn (se renderiza como asteriscos literales)')
    fails += 1
else:
    print('APROBADO: sin markdown en LinkedIn')

# Verificación 6: Recuento de palabras de la frase corta
one_liner = content.get('one_liner', '')
word_count = len(one_liner.split())
if word_count > 20:
    print(f'FALLO: la frase corta tiene {word_count} palabras (máximo 20)')
    fails += 1
else:
    print(f'APROBADO: la frase corta tiene {word_count} palabras')

# Verificación 7: Recuento de items
entry_items = (len(entry.get('new', [])) + len(entry.get('improved', [])) +
               len(entry.get('fixed', [])) + len(entry.get('under_the_hood', [])))
raw_total = raw['total_items']
print(f'INFO: {entry_items} items de changelog de {raw_total} items en bruto')

print()
print(f'Resultado: {\"APROBADO\" if fails == 0 else f\"FALLO ({fails} problema(s))\"}')
"

Si alguna verificación falla: Corrige el problema en el archivo temporal relevante antes de proceder al Paso 7. Vuelve a ejecutar la verificación después de corregir.


Paso 7: Añadir al Changelog y Guardar Contenido

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

with open('/tmp/pul-entry.json') as f:
    entry = json.load(f)
with open('/tmp/pul-content.json') as f:
    content = json.load(f)

# Construir la nueva sección del changelog
lines = [f"## {entry['label']}", ""]

CAT_HEADERS = {
    "new": "### Nuevo",
    "improved": "### Mejorado",
    "fixed": "### Corregido",
    "under_the_hood": "### Interno",
}

for cat, header in CAT_HEADERS.items():
    items = entry.get(cat, [])
    if items:
        lines.append(header)
        for item in items:
            lines.append(f"- **{item['title']}** -- {item['description']}")
        lines.append("")

lines.append("---")
lines.append("")
new_section = "\n".join(lines)

# Anteponer a docs/changelog.md
os.makedirs("docs", exist_ok=True)
changelog_path = "docs/changelog.md"

if os.path.exists(changelog_path):
    existing = open(changelog_path).read()
    # Insertar después del encabezado de nivel superior (si lo hay) o al principio
    if existing.startswith("# "):
        end_of_heading = existing.index("\n") + 1
        updated = existing[:end_of_heading] + "\n" + new_section + existing[end_of_heading:]
    else:
        updated = new_section + existing
else:
    updated = "# Changelog\n\n" + new_section

with open(changelog_path, "w") as f:
    f.write(updated)

print(f"Changelog actualizado: {changelog_path}")

# Guardar paquete de contenido
date = entry['date']
content_dir = "docs/product-updates"
os.makedirs(content_dir, exist_ok=True)
content_path = f"{content_dir}/{date}-content.md"

content_lines = [
    f"# Paquete de Contenido: {entry['label']}",
    "",
    "## Frase corta",
    content.get('one_liner', ''),
    "",
    "## Hilo de tweets",
    "",
]
thread = content.get('tweet_thread', [])
for i, tweet in enumerate(thread, 1):
    content_lines.append(f"[{i}/{len(thread)}] {tweet}")
    content_lines.append("")

content_lines += [
    "## Publicación de LinkedIn",
    "",
    content.get('linkedin_post', ''),
    "",
    "## Fragmento de correo electrónico",
    "",
    f"Asunto: {content.get('email_snippet', {}).get('subject', '')}",
    "",
    content.get('email_snippet', {}).get('body', ''),
    "",
]

with open(content_path, "w") as f:
    f.write("\n".join(content_lines))

print(f"Paquete de contenido: {content_path}")
PYEOF

Paso 8: Limpiar y Presentar

rm -f /tmp/pul-input.json /tmp/pul-raw.json /tmp/pul-entry.json /tmp/pul-content.json
echo "Listo."

Presenta al usuario en este orden:

1. Entrada de changelog (formato markdown, no JSON en bruto):

## Semana del 23 de abril de 2026

### Nuevo
- **Modo oscuro** -- Actívalo en Ajustes > Apariencia. Funciona en todas las vistas.

### Mejorado
- **Tiempo de respuesta de la API** -- Un 40% más rápido de media. El panel ahora carga en menos de 1 segundo.

### Corregido
- **Exportación CSV** -- Las exportaciones ya no omiten la última fila.

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

Takes a competitor's public GitHub repo URL, fetches their open issues via the GitHub REST API, filters noise locally, clusters issues into 6 demand categories, computes a demand score per issue and per cluster, and outputs a ranked demand gap report with a GTM messaging brief. Use when asked to scan a competitor's GitHub issues, find what their users are begging for, turn GitHub complaints into product positioning, identify competitor feature gaps, or generate messaging from real user demand. Trigger when a user says "scan competitor issues", "what are users asking for on X repo", "find demand gaps in Y", "turn GitHub issues into messaging", or "what should I build based on competitor complaints".

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

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