hive.x-automation
Read before automating X / Twitter with browser_* tools. Verified flows for post, reply, delete, search-and-engage, plus the Draft.js compose quirks that silently disable the send button. Includes the daily-reply and job-market-reply playbooks.
Install / Use
npx skills add aden-hive/hive --skill x-automationInstalls into whichever agent you are using.
SKILL.md
Installable skill definition
Quality Score
Category
AutomationSupported Platforms
Tags
Our assessment of hive.x-automation
hive.x-automation scores 97/100 on our quality scale, 114th of 1,335 Automation skills we index (top 9%).
Its SKILL.md is 17 KB long, well organised into 36 sections with 8 code examples: a thorough specification that gives an agent plenty to work with.
With 11,072 GitHub stars, it is one of the more widely adopted skills in the catalogue.
Maintenance, license and trust
- The repository was last updated 12 days ago, so hive.x-automation is actively maintained.
- It is released under the Apache-2.0 license, a permissive license that allows use, modification and commercial use with attribution.
- Its trust signals score 100/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.
Safety scan
No issues foundOur scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands.
Automated pattern scan on 2026-09-26. It catches known dangerous patterns, not every risk — read a skill before letting an agent act on it.
hive.x-automation compared with similar skills
All 4 of these similar skills score higher than hive.x-automation; compare them before choosing.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| hive.x-automation (this skill)by aden-hive | 97 | 11.1k | 12d ago | SKILL.md |
| Agent-Reachby Panniantong | 100 | 85.5k | 10d ago | CLAUDE.md |
| rufloby ruvnet | 100 | 73.3k | 1d ago | CLAUDE.md |
| Scraplingby D4Vinci | 100 | 83.7k | today | MCP Server |
| algorithmic-artby anthropics | 100 | 177.9k | 3d ago | SKILL.md |
Frequently asked questions
- How do I install hive.x-automation?
- Run
npx skills add aden-hive/hive --skill hive.x-automation. The install tabs above show the steps for each supported agent. - Which AI agents does hive.x-automation work with?
- It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
- Is hive.x-automation safe to use?
- Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. It is Apache-2.0-licensed and scores 100/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
- Is hive.x-automation still maintained?
- The repository was last updated 12 days ago, so hive.x-automation is actively maintained.
Skill content
View source on GitHubname: hive.x-automation description: Read before automating X / Twitter with browser_* tools. Verified flows for post, reply, delete, search-and-engage, plus the Draft.js compose quirks that silently disable the send button. Includes the daily-reply and job-market-reply playbooks. Requires hive.browser-automation for the underlying screenshot + coordinate workflow. Verified 2026-04-11. metadata: author: hive type: default-skill version: "1.0" verified: 2026-04-11 requires_skill: hive.browser-automation
X / Twitter Automation
X uses Draft.js (the original Facebook rich-text editor) for the compose text area, which was the original canary for all the rich-text editor quirks the browser-automation skill now documents. Most of the site is otherwise stable — data-testid attributes have held up for years, the SPA is reasonably honest about what it renders, and shadow DOM is minimal. The hard parts are the composer, rate limiting, and the occasional anti-bot challenge.
Always activate browser-automation first. This skill assumes you already know about CSS-px coordinates, click-first typing, and Input.insertText. The guidance below is X-specific.
Timing expectations
browser_navigate(wait_until="load")returns in 1.3–1.6 s on a warm cache.- After navigation,
sleep(2–3)for SPA hydration before querying selectors. - Compose modal slide-in: ~1.5 s after clicking reply / compose.
- First 1–2 characters typed into the compose editor may be dropped — see "Draft.js quirks" below.
Verified selectors (2026-04-11)
| Target | Selector |
|---|---|
| Home nav link | a[data-testid='AppTabBar_Home_Link'] |
| Explore nav link | a[data-testid='AppTabBar_Explore_Link'] |
| Notifications | a[data-testid='AppTabBar_Notifications_Link'] |
| Main search input | input[data-testid='SearchBox_Search_Input'] |
| Compose text area | [data-testid='tweetTextarea_0'] (Draft.js contenteditable) |
| Post / Tweet submit button | [data-testid='tweetButton'] |
| Reply button (on feed / tweet detail) | [data-testid='reply'] |
| Like button | [data-testid='like'] |
| Retweet / repost button | [data-testid='retweet'] |
| Caret (⋯) menu on a post | [data-testid='caret'] |
| Confirmation sheet confirm button | [data-testid='confirmationSheetConfirm'] |
| Tweet article wrapper | article[data-testid='tweet'] |
| Close modal / composer | [aria-label='Close'] or press Escape |
All of these are light-DOM data-testid attributes — wait_for_selector and browser_type(selector=...) work on them directly, no shadow piercing needed.
Post new tweet flow
browser_navigate("https://x.com/home", wait_until="load")
sleep(3)
# Open the compose UI (click the post-new-tweet nav or use shortcut N)
browser_press("n") # keyboard shortcut — opens compose modal
sleep(1.5)
# Click the textarea to make sure Draft.js is in edit mode
ta_rect = browser_get_rect("[data-testid='tweetTextarea_0']")
browser_click_coordinate(ta_rect.cx, ta_rect.cy)
sleep(0.5)
# Type — browser_type handles Draft.js correctly now via Input.insertText
browser_type("[data-testid='tweetTextarea_0']", tweet_text)
sleep(1.0) # let Draft.js commit state
# Verify the Post button is enabled — never click blindly, Draft.js sometimes
# doesn't register the input even with a prior click.
state = browser_evaluate("""
(function(){
const btn = document.querySelector('[data-testid="tweetButton"]');
if (!btn) return {found: false};
return {
found: true,
disabled: btn.disabled || btn.getAttribute('aria-disabled') === 'true',
};
})();
""")
if state['found'] and not state['disabled']:
browser_click("[data-testid='tweetButton']")
sleep(2)
browser_press("Escape") # close any leftover modal
Posting a tweet WITH an image
Critical: NEVER click the photo button. On x.com/compose/post the media button is a styled <button> that triggers Chrome's native OS file picker when clicked — that dialog is unreachable via CDP and will wedge the automation. Instead, set the file directly on the hidden <input type='file'> element using browser_upload:
# 1. Open the compose modal as usual
browser_press("n")
sleep(1.5)
browser_click_coordinate(ta_rect.cx, ta_rect.cy)
sleep(0.5)
browser_type("[data-testid='tweetTextarea_0']", tweet_text)
# 2. Find the hidden file input X uses for media uploads.
# X's input is marked with data-testid='fileInput' and accepts
# image/*,video/*. It's hidden (display:none) but still mounted.
inputs = browser_evaluate("""
(function(){
return Array.from(document.querySelectorAll('input[type="file"]'))
.map(el => ({
testid: el.getAttribute('data-testid') || '',
accept: el.accept || '',
multiple: el.multiple,
}));
})();
""")
# Expect to see: [{testid: 'fileInput', accept: 'image/jpeg,...', multiple: true}]
# 3. Set the file WITHOUT opening any dialog
browser_upload(
selector="input[data-testid='fileInput']",
file_paths=["/absolute/path/to/photo.png"],
)
sleep(2) # X takes ~1-2s to show the preview thumbnail
# 4. Verify the preview rendered before posting — if not, the upload
# didn't land and Post button will fail.
preview = browser_evaluate("""
(function(){
// X renders uploaded media as an <img> with data-testid='attachments'
// (or similar) inside the composer.
const att = document.querySelector('[data-testid="attachments"] img');
return { hasPreview: !!att };
})();
""")
if not preview['hasPreview']:
raise Exception("Upload didn't render in composer — do NOT click Post")
# 5. Now click Post as usual
browser_click("[data-testid='tweetButton']")
sleep(3) # media upload + post takes longer than text-only
browser_press("Escape")
If you don't already have the image file on disk, write it first: write_file("/tmp/x_upload.png", base64_bytes) or copy from a known location. browser_upload requires an absolute file path — relative paths and ~ expansion are not supported.
Reply to a post flow
The reply flow is the same shape as posting, with a few scroll / find-and-click steps before.
browser_navigate("https://x.com/home", wait_until="load")
sleep(3)
# Load content by scrolling — X lazy-loads feed items
browser_scroll(direction="down", amount=2000)
sleep(1.5)
# Find replyable tweets — reply buttons, in visual/feed order
candidates = browser_evaluate("""
(function(){
const tweets = document.querySelectorAll('article[data-testid="tweet"]');
const out = [];
tweets.forEach((t, i) => {
const reply = t.querySelector('[data-testid="reply"]');
if (!reply) return;
const r = reply.getBoundingClientRect();
if (r.width <= 0 || r.y < 0 || r.y > window.innerHeight) return;
const text = (t.textContent || '').slice(0, 120);
out.push({
index: i,
preview: text,
cx: r.x + r.width/2,
cy: r.y + r.height/2,
});
});
return out;
})();
""")
# For each unreplied candidate...
for c in candidates:
if already_replied(c['preview']):
continue # see dedup pattern below
# Click reply
browser_click_coordinate(c['cx'], c['cy'])
sleep(1.5) # composer slide-in
# Click the textarea to focus Draft.js
ta = browser_get_rect("[data-testid='tweetTextarea_0']")
browser_click_coordinate(ta.cx, ta.cy)
sleep(0.5)
# Type the reply
browser_type("[data-testid='tweetTextarea_0']", reply_text)
sleep(1.5) # Draft.js state commit takes a beat
# Verify button enabled
state = browser_evaluate("""
(function(){
const b = document.querySelector('[data-testid="tweetButton"]');
return b ? {d: b.disabled || b.getAttribute('aria-disabled') === 'true'} : {d: true};
})();
""")
if state['d']:
# Recovery: click the textarea again + one extra character toggles React state
browser_click_coordinate(ta.cx, ta.cy)
browser_press("End")
browser_press(" ")
browser_press("Backspace")
sleep(0.5)
else:
browser_click("[data-testid='tweetButton']")
sleep(2)
# Mark the task done in progress.db — see hive.colony-progress-tracker
# Close the composer (press Escape or click the Close button)
browser_press("Escape")
sleep(random.uniform(10, 20)) # human cadence — see rate limits
Search-and-engage flow
For "daily reply to live posts matching query X" — e.g. job-market replies.
query = "job market"
url = f"https://x.com/search?q={urllib.parse.quote(query)}&src=typed_query&f=live"
browser_navigate(url, wait_until="load")
sleep(3)
browser_scroll("down", 2000)
sleep(1.5)
# Same replyable-tweets probe as above, then same reply-to-tweet loop
Delete a post flow
browser_navigate("https://x.com/<your_username>/with_replies", wait_until="load")
sleep(3)
# Find the target article (by text match or index)
target_caret = browser_evaluate("""
(function(target_text){
const tweets = document.querySelectorAll('article[data-testid="tweet"]');
for (const t of tweets){
if (!(t.textContent || '').includes(target_text)) continue;
const caret = t.querySelector('[data-testid="caret"]');
if (!caret) continue;
const r = caret.getBoundingClientRect();
return {cx: r.x + r.width/2, cy: r.y + r.height/2};
}
return null;
})();
""", target_text)
browser_click_coordinate(target_caret['cx'], target_caret['cy'])
sleep(0.8) # menu animation
# The Delete menuitem doesn't have a stable data-testid — find by text
delete_rect = browser_evaluate("""
(function(){
const items = document.querySelectorAll('[role="menuitem"]');
for (const el of items){
if ((el.textContent || '').trim() === 'Delete'){
const r = el.getBoundingClientRect();
return {cx: r.x + r.width/2, cy: r.y + r.height/2};
}
}
return null;
})();
""")
browser_click_coordinate(delete_rect['cx'], delete_rect['cy'])
sleep(0.8)
# Confirmation sheet — this one DOES have a stable testid
browser_click("[data-testid='confirmationSheetConfirm']")
sleep(1.5)
Draft.js quirks
X's compose editor is the canonical test case for every rich-text-editor bug the GCU bridge has ever had. What you need to know:
-
Click the textarea first. Mandatory. Without a native click-sourced focus event, Draft.js's editor state never enters edit mode, and the Post button stays disabled regardless of how much text you type.
browser_typenow does this click automatically. -
browser_typeuses CDPInput.insertTextby default, which Draft.js accepts cleanly. The older approach — per-characterInput.dispatchKeyEventwithdelay_ms=20— also works, but insertText is more reliable and faster. Only passdelay_ms > 0(which falls back to per-char dispatch) if you're specifically testing the keystroke timing path. -
First 1–2 characters may be eaten on the per-char dispatch path (not on insertText). If you see
"estin"instead of"testin", prepend a throwaway character or use insertText. -
Verify
tweetButton'sdisabledstate before clicking. Draft.js's internal state can disagree with the DOM text — verify framework state via a targetedbrowser_evaluateonaria-disabled. -
If the button stays disabled after typing, use the recovery dance: click the textarea again, press
End, press a space, pressBackspace. This forces React to recomputehasRealContentand usually flips the button on. -
URL previews take a beat to render. If your tweet ends with a URL, wait 2–3 s after typing so the link-card preview loads before you post — otherwise the tweet publishes without the card.
Rate limits and safety
| Action | Limit | |---|---| | Tweets per hour | ~50 before throttling | | Replies per session | 5–10 per run, randomized 10–20 s delays | | DMs per day | Varies by account age; 50–100 for established accounts |
Truncated for display — read the full file on GitHub.
Related Skills
Agent-Reach
85.5kGive your AI agent eyes to see the entire internet. Read & search Twitter, Reddit, YouTube, GitHub, Bilibili, XiaoHongShu — one CLI, zero API fees.
ruflo
73.3k🌊 The original agent harness. Deploy intelligent multi-player swarms, coordinate autonomous workflows, and build conversational AI systems. Features adaptive memory, self-learning intelligence, federation, vector RAG integration, and native Claude Code / Codex / Hermes and many more Integrated
Scrapling
83.7k🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ and follow here for daily tips and tricks: https://x.com/Scrapling_dev
algorithmic-art
177.9kCreating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems.
Languages
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
