| name | x-integration |
| description | X (Twitter) integration for Gandalf. Post tweets, like, reply, retweet, and quote. Use for setup, testing, or troubleshooting X functionality. Triggers on "setup x", "x integration", "twitter", "post tweet", "tweet". |
X (Twitter) Integration
Browser automation for X interactions via WhatsApp.
Compatibility: Gandalf v1.0.0. Directory structure may change in future versions.
Features
| Action | Tool | Description |
|---|
| Post | x_post | Publish new tweets |
| Like | x_like | Like any tweet |
| Reply | x_reply | Reply to tweets |
| Retweet | x_retweet | Retweet without comment |
| Quote | x_quote | Quote tweet with comment |
Prerequisites
Before using this skill, ensure:
- Gandalf is installed and running - WhatsApp connected, service active
- Dependencies installed:
bun pm ls playwright dotenv-cli || bun add playwright dotenv-cli
- CHROME_PATH configured in
.env (if Chrome is not at default location):
mdfind "kMDItemCFBundleIdentifier == 'com.google.Chrome'" 2>/dev/null | head -1
CHROME_PATH=/path/to/Google Chrome.app/Contents/MacOS/Google Chrome
Quick Start
bunx dotenv -e .env -- bunx tsx .claude/skills/x-integration/scripts/setup.ts
bun run build
launchctl kickstart -k gui/$(id -u)/com.gandalf
Configuration
Environment Variables
| Variable | Default | Description |
|---|
CHROME_PATH | /Applications/Google Chrome.app/Contents/MacOS/Google Chrome | Chrome executable path |
GANDALF_ROOT | process.cwd() | Project root directory |
LOG_LEVEL | info | Logging level (debug, info, warn, error) |
Set in .env file (loaded via dotenv-cli at runtime):
CHROME_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
Configuration File
Edit lib/config.ts to modify defaults:
export const config = {
viewport: { width: 1280, height: 800 },
timeouts: {
navigation: 30000,
elementWait: 5000,
afterClick: 1000,
afterFill: 1000,
afterSubmit: 3000,
pageLoad: 3000,
},
limits: {
tweetMaxLength: 280,
},
};
Data Directories
Paths relative to project root:
| Path | Purpose | Git |
|---|
data/x-browser-profile/ | Chrome profile with X session | Ignored |
data/x-auth.json | Auth state marker | Ignored |
logs/gandalf.log | Service logs (contains X operation logs) | Ignored |
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Agent Subprocess │
│ └── agent.ts → MCP tool definitions (x_post, etc.) │
│ └── Writes IPC request to data/ipc/{group}/tasks/ │
└──────────────────────┬──────────────────────────────────────┘
│ IPC (file system)
▼
┌─────────────────────────────────────────────────────────────┐
│ Host (macOS) │
│ └── src/index.ts → processTaskIpc() │
│ └── host.ts → handleXIpc() │
│ └── spawn subprocess → scripts/*.ts │
│ └── Playwright → Chrome → X Website │
└─────────────────────────────────────────────────────────────┘
Why This Design?
- API is expensive - X official API requires paid subscription ($100+/month) for posting
- Bot browsers get blocked - X detects and bans headless browsers and common automation fingerprints
- Must use user's real browser - Reuses the user's actual Chrome on Host with real browser fingerprint to avoid detection
- One-time authorization - User logs in manually once, session persists in Chrome profile for future use
File Structure
.claude/skills/x-integration/
├── SKILL.md # This documentation
├── host.ts # Host-side IPC handler
├── agent.ts # Agent-side MCP tool definitions
├── lib/
│ ├── config.ts # Centralized configuration
│ └── browser.ts # Playwright utilities
└── scripts/
├── setup.ts # Interactive login
├── post.ts # Post tweet
├── like.ts # Like tweet
├── reply.ts # Reply to tweet
├── retweet.ts # Retweet
└── quote.ts # Quote tweet
Integration Points
To integrate this skill into Gandalf, make the following modifications:
1. Host side: src/index.ts
Add import after other local imports (look for import { loadJson, saveJson, acquirePidLock } from './utils.js';):
import { handleXIpc } from '../.claude/skills/x-integration/host.js';
Modify processTaskIpc function's switch statement default case:
default:
logger.warn({ type: data.type }, 'Unknown IPC task type');
default:
const handled = await handleXIpc(data, sourceGroup, isMain, DATA_DIR);
if (!handled) {
logger.warn({ type: data.type }, 'Unknown IPC task type');
}
2. Agent side: src/ipc-mcp.ts
Add import after existing imports:
import { createXTools } from '../.claude/skills/x-integration/agent.js';
Add to the end of tools array (before the closing ]):
...createXTools({ groupFolder, isMain, ipcDir })
Setup
All paths below are relative to project root (GANDALF_ROOT).
1. Check Chrome Path
cat .env | grep CHROME_PATH
ls -la "$(grep CHROME_PATH .env | cut -d= -f2)" 2>/dev/null || \
echo "Chrome not found - update CHROME_PATH in .env"
2. Run Authentication
bunx dotenv -e .env -- bunx tsx .claude/skills/x-integration/scripts/setup.ts
This opens Chrome for manual X login. Session saved to data/x-browser-profile/.
Verify success:
cat data/x-auth.json
3. Restart Service
bun run build
launchctl kickstart -k gui/$(id -u)/com.gandalf
Verify success:
launchctl list | grep gandalf
Usage via WhatsApp
No trigger prefix is required. Send plain messages like:
post a tweet: Hello world!
like this tweet https://x.com/user/status/123
reply to https://x.com/user/status/123 with: Great post!
retweet https://x.com/user/status/123
quote https://x.com/user/status/123 with comment: Interesting
Note: Only the main group can use X tools. Other groups will receive an error.
Testing
Scripts require environment variables from .env. Use dotenv-cli to load them:
Check Authentication Status
cat data/x-auth.json 2>/dev/null && echo "Auth configured" || echo "Auth not configured"
ls -la data/x-browser-profile/ 2>/dev/null | head -5
Re-authenticate (if expired)
bunx dotenv -e .env -- bunx tsx .claude/skills/x-integration/scripts/setup.ts
Test Post (will actually post)
echo '{"content":"Test tweet - please ignore"}' | bunx dotenv -e .env -- bunx tsx .claude/skills/x-integration/scripts/post.ts
Test Like
echo '{"tweetUrl":"https://x.com/user/status/123"}' | bunx dotenv -e .env -- bunx tsx .claude/skills/x-integration/scripts/like.ts
Or export CHROME_PATH manually before running:
export CHROME_PATH="/path/to/chrome"
echo '{"content":"Test"}' | bunx tsx .claude/skills/x-integration/scripts/post.ts
Troubleshooting
Authentication Expired
bunx dotenv -e .env -- bunx tsx .claude/skills/x-integration/scripts/setup.ts
launchctl kickstart -k gui/$(id -u)/com.gandalf
Browser Lock Files
If Chrome fails to launch:
rm -f data/x-browser-profile/SingletonLock
rm -f data/x-browser-profile/SingletonSocket
rm -f data/x-browser-profile/SingletonCookie
Check Logs
grep -i "x_post\|x_like\|x_reply\|handleXIpc" logs/gandalf.log | tail -20
grep -i "error\|failed" logs/gandalf.log | tail -20
Script Timeout
Default timeout is 2 minutes (120s). Increase in host.ts:
const timer = setTimeout(() => {
proc.kill('SIGTERM');
resolve({ success: false, message: 'Script timed out (120s)' });
}, 120000);
X UI Selector Changes
If X updates their UI, selectors in scripts may break. Current selectors:
| Element | Selector |
|---|
| Tweet input | [data-testid="tweetTextarea_0"] |
| Post button | [data-testid="tweetButtonInline"] |
| Reply button | [data-testid="reply"] |
| Like | [data-testid="like"] |
| Unlike | [data-testid="unlike"] |
| Retweet | [data-testid="retweet"] |
| Unretweet | [data-testid="unretweet"] |
| Confirm retweet | [data-testid="retweetConfirm"] |
| Modal dialog | [role="dialog"][aria-modal="true"] |
| Modal submit | [data-testid="tweetButton"] |
Security
data/x-browser-profile/ - Contains X session cookies (in .gitignore)
data/x-auth.json - Auth state marker (in .gitignore)
- Only main group can use X tools (enforced in
agent.ts and host.ts)
- Scripts run as subprocesses with limited environment