Install
$ agentstack add skill-hankunpeng-skills-twitter ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Twitter Skill
Posts text and images to X (Twitter) via Chrome Computer Use Mode.
Script Directory
Important: All scripts are located in the scripts/ subdirectory of this skill.
Agent Execution Instructions:
- Determine this SKILL.md file's directory path as
{baseDir} - Script paths:
- Setup / Initializer:
{baseDir}/scripts/setup.ts - Clipboard helper:
{baseDir}/scripts/copy-to-clipboard.ts - Tweet exporter/scraper:
{baseDir}/scripts/export-tweets.ts - Official X API helper:
{baseDir}/scripts/x-api.ts
- Resolve
${BUN_X}runtime: ifbuninstalled →bun; ifnpxavailable →npx -y bun; else suggest installing bun - Replace all
{baseDir}and${BUN_X}in this document with actual values - Initial Setup: Run the setup script to automatically initialize configuration folders and template files:
``bash ${BUN_X} {baseDir}/scripts/setup.ts ``
Execution Mode
This skill follows a Hybrid Execution Model:
- API First (Recommended for text posts): Attempt to post the tweet using the official X API script (
x-api.ts). This is fast, stable, and uses no browser resources. - Browser Fallback: If the API call fails (e.g., monthly 1500-tweet Free tier quota exceeded, rate limit) or API credentials are not configured in
~/.config/skills/twitter.yaml, fall back automatically to Chrome Computer Use Mode / CLI Bridge to simulate browser actions.
Prerequisites
- For API Mode: Configure your X API credentials and state in your global
~/.config/skills/twitter.yamlfile:
```yaml xapi: apikey: "YOURAPIKEY" apikeysecret: "YOURAPIKEYSECRET" accesstoken: "YOURACCESSTOKEN" accesstokensecret: "YOURACCESSTOKEN_SECRET"
state: useapi: true lastreset_month: "2026-06" ```
#### X Developer Portal Setup Guide:
- Go to the X Developer Portal.
- Select your App under Projects & Apps.
- Under User authentication settings, click Set up (or Edit):
- App permissions: Select Read and write.
- Type of App: Select Web App, Automated App or Bot.
- Callback URI / Redirect URL: Enter
https://127.0.0.1(required placeholder). - Website URL: Enter your project URL, e.g.
https://github.com/hankunpeng/skills(required placeholder). - Save the settings.
- Go to the Keys and Tokens tab:
- Under Consumer Keys, copy or regenerate the API Key and API Key Secret.
- Under Access Token and Secret, click Regenerate to obtain the Access Token and Access Token Secret (Note: tokens must be regenerated after changing permissions to activate write access).
- Copy these 4 credentials and paste them into
~/.config/skills/twitter.yaml.
- For Browser Fallback: Google Chrome installed, logged into X (Twitter) in Chrome, and macOS accessibility permissions granted if required.
Regular Posts Workflow (Text & Images)
When executing a post:
- Start the agent turn by calling
get_app_state(or equivalent tool) forGoogle Chrome. - Open or navigate Google Chrome to
https://x.com/compose/post. - Locate the tweet composer input box.
- Type the post text into the composer using Computer Use keyboard inputs.
- If there are any images to attach (max 4):
For each image: a. Run the clipboard helper script to copy the image to the clipboard: ``bash ${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts image /absolute/path/to/image.png ` b. Paste the image into the composer using the paste shortcut (super+v on macOS, control+v` on Windows/Linux). c. Wait 2-3 seconds until X finishes uploading the media.
- Publish Safety: Never click
Publish,Post, or any equivalent button to publish the tweet without getting explicit final confirmation from the user in the current conversation. - Once the user confirms, click the
Postbutton to publish. - After publishing, close the composer modal so the UI doesn't stay stuck on the compose dialog. Use the close button or Escape:
- DOM Selector:
[data-testid="app-bar-close"]or[aria-label="Close"] - Fallback: dispatch an
Escapekeydown event
- Auto-Reload Feed (Optional): If the user has other tabs open to their profile (e.g.,
x.com/[username]) or home feed (x.com/home), reload them so the new tweet is visible immediately.
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 (No Computer Use Tools)
When the environment lacks Computer Use keyboard/mouse tools, use platform-specific methods to open Chrome and inject JavaScript into the page.
macOS (AppleScript)
Open compose page:
open -a "Google Chrome" "https://x.com/compose/post"
Execute JavaScript in Chrome — write JS to a temp file first (avoids shell escaping issues), then run via AppleScript:
cat > /tmp/tweet.js 1 ? els[els.length - 1] : els[0];
```
Always target the active modal composer (usually the last element in the list).
2. **Binding Selection & Focus**:
Before inserting text, you MUST click the element to trigger Draft.js selection binding, then focus:
```javascript
el.click();
el.focus();
```
3. **Preserving Editor Structure**:
- **Do NOT** use `el.innerHTML = ''` or `document.execCommand('delete')` on an empty composer. Wiping the DOM nodes destroys Draft.js's internal wrapper structure (e.g., `public-DraftStyleDefault-block` span), which crashes the React component and leaves the Post button permanently disabled.
- Simply use `document.execCommand('insertText', false, text)` directly into the empty focused editor.
4. **Triggering React State Updates**:
After text insertion, dispatch a bubbled `input` event to notify React:
```javascript
el.dispatchEvent(new Event('input', { bubbles: true }));
```
5. **Locating the Correct Post Button**:
The button testids (`tweetButtonInline` and `tweetButton`) might be swapped depending on the context. Always scan for the visible, enabled button:
```javascript
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();
```
## Delete Tweet Workflow
When executing a deletion:
1. Open or navigate Google Chrome to the user's profile page (`https://x.com/[username]`) or the direct tweet URL (`https://x.com/[username]/status/[tweetId]`).
2. Search for the target tweet `` container containing the text to delete.
3. Click the options menu button on the tweet:
- **DOM Selector**: `[data-testid="caret"]`
4. Wait 1-2 seconds, then click the "Delete" menu item:
- **DOM Selector**: A `[role="menuitem"]` element whose text contains "Delete" or "删除".
5. Wait 1-2 seconds, then click the confirmation delete button in the dialog sheet:
- **DOM Selector**: `[data-testid="confirmationSheetConfirm"]` (or fallback to any dialog button with text "Delete" or "删除").
### CLI Bridge Example (macOS)
Use the same temp-file + AppleScript pattern as posting. Replace `TWEET_TEXT_HERE` with the target tweet content.
**Step 1 — Find tweet and click caret:**
```bash
cat > /tmp/del-1.js /tmp/del-2.js /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: confirm button not found';
confirmBtn.click();
return 'OK: confirm clicked';
})();
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'
Export Tweets Workflow
To export/save all or filtered tweets from your profile page:
- Run the exporter script:
``bash ${BUN_X} {baseDir}/scripts/export-tweets.ts [startDate] [endDate] ``
- Optional Date Filters: You can pass
startDate(e.g.2026-06-01) andendDate(e.g.2026-06-30) to filter the output by date range. If omitted, all scraped tweets are exported. - Output File: The tweets will be saved in
/Users/alex/twitter/twitter.yamlwhere the tweet URL is the key, and the tweet text content is the value.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: hankunpeng
- Source: hankunpeng/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.