¿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:
- Determine la ruta del directorio de este archivo SKILL.md como
{baseDir} - 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
- Ayudante de portapapeles:
- Resuelva el runtime
${BUN_X}: sibunestá instalado →bun; sinpxestá disponible →npx -y bun; si no, sugiera instalar bun - 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:
- 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. - 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:
- Inicie el turno del agente llamando a
get_app_state(o herramienta equivalente) paraGoogle Chrome. - Abra o navegue en Google Chrome a
https://x.com/compose/post. - Localice el cuadro de entrada del compositor de tweets.
- Escriba el texto de la publicación en el compositor usando las entradas de teclado de Computer Use.
- 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:
b. Pegue la imagen en el compositor usando el atajo de pegar (${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts image /ruta/absoluta/a/la/imagen.pngsuper+ven macOS,control+ven Windows/Linux). c. Espere 2-3 segundos hasta que X termine de subir el archivo multimedia. - Seguridad de publicación: Nunca haga clic en
Publicar,Posto cualquier botón equivalente para publicar el tweet sin obtener una confirmación final explícita del usuario en la conversación actual. - Una vez que el usuario confirme, haga clic en el botón
Postpara publicar. - 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
- Selector DOM:
- 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:
-
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).
-
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(); -
Preservar la Estructura del Editor:
- NO use
el.innerHTML = ''odocument.execCommand('delete')en un compositor vacío. Borrar los nodos DOM destruye la estructura interna de wrapper de Draft.js (por ejemplo, el spanpublic-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.
- NO use
-
Activar Actualizaciones de Estado de React: Después de la inserción de texto, despache un evento
inputcon propagación para notificar a React:el.dispatchEvent(new Event('input', { bubbles: true })); -
Localizar el Botón de Publicación Correcto: Los testids de los botones (
tweetButtonInlineytweetButton) 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:
- 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]). - Busque el contenedor
<article>del tweet objetivo que contenga el texto a eliminar. - Haga clic en el botón del menú de opciones del tweet:
- Selector DOM:
[data-testid="caret"]
- Selector DOM:
- Espere 1-2 segundos, luego haga clic en el elemento del menú "Eliminar":
- Selector DOM: Un elemento
[role="menuitem"]cuyo texto contenga "Delete" o "删除".
- Selector DOM: Un elemento
- 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 "删除").
- Selector DOM:
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:
- 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) yfechaFin(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.yamldonde la URL del tweet es la clave y el contenido del texto del tweet es el valor.
- Filtros de fecha opcionales: Puede pasar