Communitygithub.com

Twitter Skill

Habilidad para publicar texto e imágenes en X (Twitter) mediante el modo Chrome Computer Use y API oficial, con respaldo automático al navegador.

¿Qué es Twitter Skill?

Esta habilidad de agente de IA permite publicar y gestionar tweets en X (Twitter) combinando dos modos de ejecución: primero intenta usar la API oficial de X (OAuth 1.0a) para publicar texto de forma rápida y eficiente. Si la API falla por límites de tasa o cuota gratuita, automáticamente recurre al modo Chrome Computer Use (o CLI Bridge) para simular acciones del navegador. Soporta publicación de texto e imágenes (hasta 4), eliminación de tweets y exportación de tweets a un archivo YAML local. Está diseñada para ejecutarse en plataformas macOS (vía AppleScript), Linux y Windows, y es compatible con agentes como Claude Code, Codex o Cursor. La habilidad incluye directrices detalladas de automatización DOM para el editor Draft.js de X, asegurando una interacción robusta con la interfaz web cuando se usa el modo de respaldo del navegador.

Compatible con✓Claude Code~Codex CLI~Cursor✓Antigravity
npx skills add hankunpeng/hkp-skills

Preguntar en tu IA favorita

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

Documentación

¿Qué hace Twitter Skill?

Publica texto e imágenes en X (Twitter) mediante el modo Chrome Computer Use.

Directorio de Scripts

Importante: Todos los scripts se encuentran en el subdirectorio scripts/ de esta habilidad.

Instrucciones de Ejecución para el Agente:

  1. Determine la ruta del directorio de este archivo SKILL.md como {baseDir}
  2. Rutas de scripts:
    • Ayudante de portapapeles: {baseDir}/scripts/copy-to-clipboard.ts
    • Exportador/recopilador de tweets: {baseDir}/scripts/export-tweets.ts
    • Ayudante de API oficial de X: {baseDir}/scripts/x-api.ts
  3. Resuelva el runtime ${BUN_X}: si bun está instalado → bun; si npx está disponible → npx -y bun; si no, sugiera instalar bun
  4. Reemplace todas las ocurrencias de {baseDir} y ${BUN_X} en este documento con los valores reales

Modo de Ejecución

Esta habilidad sigue un Modelo de Ejecución Híbrido:

  1. API primero (recomendado para publicaciones de texto): Intente publicar el tweet usando el script oficial de la API de X (x-api.ts). Es rápido, estable y no utiliza recursos del navegador.
  2. Respaldo mediante navegador: Si la llamada a la API falla (por ejemplo, límite mensual de 1500 tweets en el nivel gratuito excedido, límite de tasa) o las credenciales de la API no están configuradas en subscription.yaml, recurra automáticamente al Modo Chrome Computer Use / CLI Bridge para simular acciones del navegador.

Requisitos previos

  • Para modo API: Configure sus credenciales de la API de X (tokens de contexto de usuario OAuth 1.0a) en su archivo local subscription.yaml (que está en .gitignore):
    x_api:
      api_key: "SU_API_KEY"
      api_key_secret: "SU_API_KEY_SECRET"
      access_token: "SU_ACCESS_TOKEN"
      access_token_secret: "SU_ACCESS_TOKEN_SECRET"
    
  • Para respaldo mediante navegador: Tener Google Chrome instalado, haber iniciado sesión en X (Twitter) en Chrome y permisos de accesibilidad de macOS otorgados si es necesario.

Flujo de Trabajo para Publicaciones Normales (Texto e Imágenes)

Al ejecutar una publicación:

  1. Inicie el turno del agente llamando a get_app_state (o herramienta equivalente) para Google Chrome.
  2. Abra o navegue en Google Chrome a https://x.com/compose/post.
  3. Localice el cuadro de entrada del compositor de tweets.
  4. Escriba el texto de la publicación en el compositor usando las entradas de teclado de Computer Use.
  5. Si hay imágenes para adjuntar (máximo 4): Para cada imagen: a. Ejecute el script ayudante de portapapeles para copiar la imagen al portapapeles:
    ${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts image /ruta/absoluta/a/la/imagen.png
    
    b. Pegue la imagen en el compositor usando el atajo de pegar (super+v en macOS, control+v en Windows/Linux). c. Espere 2-3 segundos hasta que X termine de subir el archivo multimedia.
  6. Seguridad de publicación: Nunca haga clic en Publicar, Post o cualquier botón equivalente para publicar el tweet sin obtener una confirmación final explícita del usuario en la conversación actual.
  7. Una vez que el usuario confirme, haga clic en el botón Post para publicar.
  8. Después de publicar, cierre el modal del compositor para que la interfaz no se quede atascada en el diálogo de composición. Use el botón de cerrar o Escape:
    • Selector DOM: [data-testid="app-bar-close"] o [aria-label="Close"]
    • Respaldo: despache un evento keydown de Escape
  9. Recarga automática del feed (opcional): Si el usuario tiene otras pestañas abiertas en su perfil (por ejemplo, x.com/[nombredeusuario]) o en el feed principal (x.com/home), recárguelas para que el nuevo tweet sea visible de inmediato.
var closeBtn = document.querySelector('[data-testid="app-bar-close"], [aria-label="Close"]');
if (closeBtn) { closeBtn.click(); }
else { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true })); }

CLI Bridge (Sin Herramientas de Computer Use)

Cuando el entorno carece de herramientas de teclado/ratón de Computer Use, use métodos específicos de la plataforma para abrir Chrome e inyectar JavaScript en la página.

macOS (AppleScript)

Abrir página de composición:

open -a "Google Chrome" "https://x.com/compose/post"

Ejecutar JavaScript en Chrome — escriba el JS en un archivo temporal primero (evita problemas de escape de shell) y luego ejecute mediante AppleScript:

cat > /tmp/tweet.js << 'EOF'
(function() { /* su código */ })();
EOF

osascript -e '
tell application "Google Chrome"
    activate
    set js to read "/tmp/tweet.js"
    set result to execute front window''''s active tab javascript js
    return result
end tell'

Patrón: Siempre escriba el payload JS en un archivo temporal primero. NO intente osascript -e en línea con JS incrustado — los conflictos de citas y escapes causarán errores de análisis.

Cerrar compositor después de publicar (de lo contrario el modal permanece abierto):

var closeBtn = document.querySelector('[data-testid="app-bar-close"], [aria-label="Close"]');
if (closeBtn) { closeBtn.click(); }
else { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true })); }

Recargar pestañas de perfil/feed para reflejar la nueva publicación inmediatamente (opcional):

osascript -e '
tell application "Google Chrome"
    tell window 1
        set tabList to every tab whose URL contains "x.com/hankunpeng" or URL contains "x.com/home"
        repeat with t in tabList
            reload t
        end repeat
    end tell
end tell'

Linux

google-chrome "https://x.com/compose/post"
# Use chrome-remote-interface o un cliente CDP similar para ejecutar JS

Windows

Start-Process "chrome" "https://x.com/compose/post"
# Use CDP mediante --remote-debugging-port o automatización con PowerShell

Directrices de Automatización JavaScript/DOM (Respaldo)

Si los eventos directos de teclado/ratón de Computer Use no están disponibles, o si está ejecutando en modo de scripting del navegador/CDP, siga estas directrices para interactuar con el editor React/Draft.js de X:

  1. Detección de Múltiples Editores: Las páginas de X a menudo contienen múltiples áreas de texto de composición (por ejemplo, compositor en línea en el feed principal y el diálogo modal activo). Siempre consulte todos los editores:

    var els = document.querySelectorAll('[data-testid="tweetTextarea_0"]');
    var el = els.length > 1 ? els[els.length - 1] : els[0];
    

    Siempre apunte al compositor modal activo (generalmente el último elemento de la lista).

  2. Vinculación de Selección y Enfoque: Antes de insertar texto, DEBE hacer clic en el elemento para activar la vinculación de selección de Draft.js, luego enfocar:

    el.click();
    el.focus();
    
  3. Preservar la Estructura del Editor:

    • NO use el.innerHTML = '' o document.execCommand('delete') en un compositor vacío. Borrar los nodos DOM destruye la estructura interna de wrapper de Draft.js (por ejemplo, el span public-DraftStyleDefault-block), lo que bloquea el componente React y deja el botón Publicar permanentemente deshabilitado.
    • Simplemente use document.execCommand('insertText', false, text) directamente en el editor vacío enfocado.
  4. Activar Actualizaciones de Estado de React: Después de la inserción de texto, despache un evento input con propagación para notificar a React:

    el.dispatchEvent(new Event('input', { bubbles: true }));
    
  5. Localizar el Botón de Publicación Correcto: Los testids de los botones (tweetButtonInline y tweetButton) pueden intercambiarse según el contexto. Siempre escanee el botón visible y habilitado:

    var btns = document.querySelectorAll('[data-testid="tweetButtonInline"], [data-testid="tweetButton"]');
    var activeBtn = Array.from(btns).find(function(btn) {
        var isVisible = btn.offsetWidth > 0 && btn.offsetHeight > 0;
        var isDisabled = btn.disabled || btn.getAttribute('aria-disabled') === 'true';
        return isVisible && !isDisabled;
    });
     if (activeBtn) activeBtn.click();
    

Flujo de Trabajo para Eliminar Tweets

Al ejecutar una eliminación:

  1. Abra o navegue en Google Chrome a la página de perfil del usuario (https://x.com/[nombredeusuario]) o a la URL directa del tweet (https://x.com/[nombredeusuario]/status/[tweetId]).
  2. Busque el contenedor <article> del tweet objetivo que contenga el texto a eliminar.
  3. Haga clic en el botón del menú de opciones del tweet:
    • Selector DOM: [data-testid="caret"]
  4. Espere 1-2 segundos, luego haga clic en el elemento del menú "Eliminar":
    • Selector DOM: Un elemento [role="menuitem"] cuyo texto contenga "Delete" o "删除".
  5. Espere 1-2 segundos, luego haga clic en el botón de confirmación de eliminación en la hoja de diálogo:
    • Selector DOM: [data-testid="confirmationSheetConfirm"] (o respaldo a cualquier botón del diálogo con texto "Delete" o "删除").

Ejemplo de CLI Bridge (macOS)

Use el mismo patrón de archivo temporal + AppleScript que para publicar. Reemplace TWEET_TEXT_HERE con el contenido del tweet objetivo.

Paso 1 — Encontrar tweet y hacer clic en caret:

cat > /tmp/del-1.js << 'EOF'
(function() {
  var articles = document.querySelectorAll('article');
  var target = null;
  var needle = 'TWEET_TEXT_HERE';
  for (var i = 0; i < articles.length; i++) {
    var textEl = articles[i].querySelector('[data-testid="tweetText"]');
    if (textEl && textEl.textContent.trim().indexOf(needle) !== -1) {
      target = articles[i];
      break;
    }
  }
  if (!target) return 'ERROR: tweet no encontrado';
  var caret = target.querySelector('[data-testid="caret"]');
  if (!caret) return 'ERROR: caret no encontrado';
  caret.click();
  return 'OK: caret clickeado';
})();
EOF

osascript -e '
tell application "Google Chrome"
    set js to read "/tmp/del-1.js"
    set result to execute front window''''s active tab javascript js
    return result
end tell'

Paso 2 — Hacer clic en el elemento de menú Eliminar (esperar 1-2s después del paso 1):

cat > /tmp/del-2.js << 'EOF'
(function() {
  var items = document.querySelectorAll('[role="menuitem"]');
  for (var i = 0; i < items.length; i++) {
    var txt = items[i].textContent.trim();
    if (txt === 'Delete' || txt === '删除') {
      // Hacer clic en el menuitem en sí, o si contiene un elemento clickeable anidado
      var clickTarget = items[i].querySelector('[role="menuitem"]') || items[i];
      clickTarget.click();
      return 'OK: eliminar clickeado (índice ' + i + ')';
    }
  }
  // Respaldo: hacer clic en el primer menuitem (siempre "Eliminar" en el menú desplegable)
  var first = document.querySelectorAll('[role="menuitem"]')[0];
  if (first) { first.click(); return 'OK: primer menuitem clickeado'; }
  return 'ERROR: sin menuitems';
})();
EOF

osascript -e '
tell application "Google Chrome"
    set js to read "/tmp/del-2.js"
    set result to execute front window''''s active tab javascript js
    return result
end tell'

Paso 3 — Confirmar eliminación (esperar 1-2s después del paso 2):

cat > /tmp/del-3.js << 'EOF'
(function() {
  var confirmBtn = document.querySelector('[data-testid="confirmationSheetConfirm"]');
  if (!confirmBtn) {
    var buttons = document.querySelectorAll('[role="button"], button');
    for (var i = 0; i < buttons.length; i++) {
      var txt = buttons[i].textContent.trim();
      if (txt === 'Delete' || txt === '删除') {
        confirmBtn = buttons[i];
        break;
      }
    }
  }
  if (!confirmBtn) return 'ERROR: botón de confirmación no encontrado';
  confirmBtn.click();
  return 'OK: confirmación clickeada';
})();
EOF

osascript -e '
tell application "Google Chrome"
    set js to read "/tmp/del-3.js"
    set result to execute front window''''s active tab javascript js
    return result
end tell'

Flujo de Trabajo para Exportar Tweets

Para exportar/guardar todos los tweets o tweets filtrados de su página de perfil:

  1. Ejecute el script exportador:
    ${BUN_X} {baseDir}/scripts/export-tweets.ts [fechaInicio] [fechaFin]
    
    • Filtros de fecha opcionales: Puede pasar fechaInicio (por ejemplo, 2026-06-01) y fechaFin (por ejemplo, 2026-06-30) para filtrar la salida por rango de fechas. Si se omiten, se exportan todos los tweets recopilados.
    • Archivo de salida: Los tweets se guardarán en /Users/alex/twitter/twitter.yaml donde la URL del tweet es la clave y el contenido del texto del tweet es el valor.

Skills relacionados