Add X integration skill (#52)
This commit is contained in:
413
.claude/skills/x-integration/SKILL.md
Normal file
413
.claude/skills/x-integration/SKILL.md
Normal file
@@ -0,0 +1,413 @@
|
||||
---
|
||||
name: x-integration
|
||||
description: X (Twitter) integration for NanoClaw. 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:** NanoClaw 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:
|
||||
|
||||
1. **NanoClaw is installed and running** - WhatsApp connected, service active
|
||||
2. **Dependencies installed**:
|
||||
```bash
|
||||
npm ls playwright dotenv-cli || npm install playwright dotenv-cli
|
||||
```
|
||||
3. **CHROME_PATH configured** in `.env` (if Chrome is not at default location):
|
||||
```bash
|
||||
# Find your Chrome path
|
||||
mdfind "kMDItemCFBundleIdentifier == 'com.google.Chrome'" 2>/dev/null | head -1
|
||||
# Add to .env
|
||||
CHROME_PATH=/path/to/Google Chrome.app/Contents/MacOS/Google Chrome
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Setup authentication (interactive)
|
||||
npx dotenv -e .env -- npx tsx .claude/skills/x-integration/scripts/setup.ts
|
||||
# Verify: data/x-auth.json should exist after successful login
|
||||
|
||||
# 2. Rebuild container to include skill
|
||||
./container/build.sh
|
||||
# Verify: Output shows "COPY .claude/skills/x-integration/agent.ts"
|
||||
|
||||
# 3. Rebuild host and restart service
|
||||
npm run build
|
||||
launchctl kickstart -k gui/$(id -u)/com.nanoclaw
|
||||
# Verify: launchctl list | grep nanoclaw shows PID and exit code 0
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `CHROME_PATH` | `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` | Chrome executable path |
|
||||
| `NANOCLAW_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):
|
||||
|
||||
```bash
|
||||
# .env
|
||||
CHROME_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
|
||||
```
|
||||
|
||||
### Configuration File
|
||||
|
||||
Edit `lib/config.ts` to modify defaults:
|
||||
|
||||
```typescript
|
||||
export const config = {
|
||||
// Browser viewport
|
||||
viewport: { width: 1280, height: 800 },
|
||||
|
||||
// Timeouts (milliseconds)
|
||||
timeouts: {
|
||||
navigation: 30000, // Page navigation
|
||||
elementWait: 5000, // Wait for element
|
||||
afterClick: 1000, // Delay after click
|
||||
afterFill: 1000, // Delay after form fill
|
||||
afterSubmit: 3000, // Delay after submit
|
||||
pageLoad: 3000, // Initial page load
|
||||
},
|
||||
|
||||
// Tweet limits
|
||||
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/nanoclaw.log` | Service logs (contains X operation logs) | Ignored |
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Container (Linux VM) │
|
||||
│ └── agent.ts → MCP tool definitions (x_post, etc.) │
|
||||
│ └── Writes IPC request to /workspace/ipc/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 # Container-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 NanoClaw, 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';`):
|
||||
```typescript
|
||||
import { handleXIpc } from '../.claude/skills/x-integration/host.js';
|
||||
```
|
||||
|
||||
Modify `processTaskIpc` function's switch statement default case:
|
||||
```typescript
|
||||
// Find:
|
||||
default:
|
||||
logger.warn({ type: data.type }, 'Unknown IPC task type');
|
||||
|
||||
// Replace with:
|
||||
default:
|
||||
const handled = await handleXIpc(data, sourceGroup, isMain, DATA_DIR);
|
||||
if (!handled) {
|
||||
logger.warn({ type: data.type }, 'Unknown IPC task type');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**2. Container side: `container/agent-runner/src/ipc-mcp.ts`**
|
||||
|
||||
Add import after `cron-parser` import:
|
||||
```typescript
|
||||
// @ts-ignore - Copied during Docker build from .claude/skills/x-integration/
|
||||
import { createXTools } from './skills/x-integration/agent.js';
|
||||
```
|
||||
|
||||
Add to the end of tools array (before the closing `]`):
|
||||
```typescript
|
||||
...createXTools({ groupFolder, isMain })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**3. Build script: `container/build.sh`**
|
||||
|
||||
Change build context from `container/` to project root (required to access `.claude/skills/`):
|
||||
```bash
|
||||
# Find:
|
||||
container build -t "${IMAGE_NAME}:${TAG}" .
|
||||
|
||||
# Replace with:
|
||||
cd "$SCRIPT_DIR/.."
|
||||
container build -t "${IMAGE_NAME}:${TAG}" -f container/Dockerfile .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**4. Dockerfile: `container/Dockerfile`**
|
||||
|
||||
First, update the build context paths (required to access `.claude/skills/` from project root):
|
||||
```dockerfile
|
||||
# Find:
|
||||
COPY agent-runner/package*.json ./
|
||||
...
|
||||
COPY agent-runner/ ./
|
||||
|
||||
# Replace with:
|
||||
COPY container/agent-runner/package*.json ./
|
||||
...
|
||||
COPY container/agent-runner/ ./
|
||||
```
|
||||
|
||||
Then add COPY line after `COPY container/agent-runner/ ./` and before `RUN npm run build`:
|
||||
```dockerfile
|
||||
# Copy skill MCP tools
|
||||
COPY .claude/skills/x-integration/agent.ts ./src/skills/x-integration/
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
All paths below are relative to project root (`NANOCLAW_ROOT`).
|
||||
|
||||
### 1. Check Chrome Path
|
||||
|
||||
```bash
|
||||
# Check if Chrome exists at configured 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
|
||||
|
||||
```bash
|
||||
npx dotenv -e .env -- npx tsx .claude/skills/x-integration/scripts/setup.ts
|
||||
```
|
||||
|
||||
This opens Chrome for manual X login. Session saved to `data/x-browser-profile/`.
|
||||
|
||||
**Verify success:**
|
||||
```bash
|
||||
cat data/x-auth.json # Should show {"authenticated": true, ...}
|
||||
```
|
||||
|
||||
### 3. Rebuild Container
|
||||
|
||||
```bash
|
||||
./container/build.sh
|
||||
```
|
||||
|
||||
**Verify success:**
|
||||
```bash
|
||||
./container/build.sh 2>&1 | grep -i "agent.ts" # Should show COPY line
|
||||
```
|
||||
|
||||
### 4. Restart Service
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
launchctl kickstart -k gui/$(id -u)/com.nanoclaw
|
||||
```
|
||||
|
||||
**Verify success:**
|
||||
```bash
|
||||
launchctl list | grep nanoclaw # Should show PID and exit code 0 or -
|
||||
```
|
||||
|
||||
## Usage via WhatsApp
|
||||
|
||||
Replace `@Assistant` with your configured trigger name (`ASSISTANT_NAME` in `.env`):
|
||||
|
||||
```
|
||||
@Assistant post a tweet: Hello world!
|
||||
|
||||
@Assistant like this tweet https://x.com/user/status/123
|
||||
|
||||
@Assistant reply to https://x.com/user/status/123 with: Great post!
|
||||
|
||||
@Assistant retweet https://x.com/user/status/123
|
||||
|
||||
@Assistant 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
|
||||
|
||||
```bash
|
||||
# Check if auth file exists and is valid
|
||||
cat data/x-auth.json 2>/dev/null && echo "Auth configured" || echo "Auth not configured"
|
||||
|
||||
# Check if browser profile exists
|
||||
ls -la data/x-browser-profile/ 2>/dev/null | head -5
|
||||
```
|
||||
|
||||
### Re-authenticate (if expired)
|
||||
|
||||
```bash
|
||||
npx dotenv -e .env -- npx tsx .claude/skills/x-integration/scripts/setup.ts
|
||||
```
|
||||
|
||||
### Test Post (will actually post)
|
||||
|
||||
```bash
|
||||
echo '{"content":"Test tweet - please ignore"}' | npx dotenv -e .env -- npx tsx .claude/skills/x-integration/scripts/post.ts
|
||||
```
|
||||
|
||||
### Test Like
|
||||
|
||||
```bash
|
||||
echo '{"tweetUrl":"https://x.com/user/status/123"}' | npx dotenv -e .env -- npx tsx .claude/skills/x-integration/scripts/like.ts
|
||||
```
|
||||
|
||||
Or export `CHROME_PATH` manually before running:
|
||||
|
||||
```bash
|
||||
export CHROME_PATH="/path/to/chrome"
|
||||
echo '{"content":"Test"}' | npx tsx .claude/skills/x-integration/scripts/post.ts
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Authentication Expired
|
||||
|
||||
```bash
|
||||
npx dotenv -e .env -- npx tsx .claude/skills/x-integration/scripts/setup.ts
|
||||
launchctl kickstart -k gui/$(id -u)/com.nanoclaw
|
||||
```
|
||||
|
||||
### Browser Lock Files
|
||||
|
||||
If Chrome fails to launch:
|
||||
|
||||
```bash
|
||||
rm -f data/x-browser-profile/SingletonLock
|
||||
rm -f data/x-browser-profile/SingletonSocket
|
||||
rm -f data/x-browser-profile/SingletonCookie
|
||||
```
|
||||
|
||||
### Check Logs
|
||||
|
||||
```bash
|
||||
# Host logs (relative to project root)
|
||||
grep -i "x_post\|x_like\|x_reply\|handleXIpc" logs/nanoclaw.log | tail -20
|
||||
|
||||
# Script errors
|
||||
grep -i "error\|failed" logs/nanoclaw.log | tail -20
|
||||
```
|
||||
|
||||
### Script Timeout
|
||||
|
||||
Default timeout is 2 minutes (120s). Increase in `host.ts`:
|
||||
|
||||
```typescript
|
||||
const timer = setTimeout(() => {
|
||||
proc.kill('SIGTERM');
|
||||
resolve({ success: false, message: 'Script timed out (120s)' });
|
||||
}, 120000); // ← Increase this value
|
||||
```
|
||||
|
||||
### 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"]` |
|
||||
|
||||
### Container Build Issues
|
||||
|
||||
If MCP tools not found in container:
|
||||
|
||||
```bash
|
||||
# Verify build copies skill
|
||||
./container/build.sh 2>&1 | grep -i skill
|
||||
|
||||
# Check container has the file
|
||||
container run nanoclaw-agent ls -la /app/src/skills/
|
||||
```
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user