Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 22 additions & 134 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,144 +1,32 @@
# XActions Configuration
# Copy this file to .env and fill in your values
# XActions Environment Variables
# Copy this to .env and fill in your values
# NEVER commit .env to git

# ═══════════════════════════════════════════════════════════════════════════════
# Server Configuration
# ═══════════════════════════════════════════════════════════════════════════════
# X/Twitter Session Cookie
# Get this from: DevTools (F12) -> Application -> Cookies -> x.com -> auth_token
XACTIONS_SESSION_COOKIE=your_auth_token_here

NODE_ENV=development
PORT=3001
API_URL=http://localhost:3001
API_BASE_URL=http://localhost:3001
FRONTEND_URL=http://localhost:3000

# ═══════════════════════════════════════════════════════════════════════════════
# x402 Payment Configuration (AI Agent Monetization)
# ═══════════════════════════════════════════════════════════════════════════════

# ⚠️ REQUIRED for production - your wallet address to receive USDC payments
# This MUST be set to a valid Ethereum address (0x...) for x402 to work
# Server will fail to start in production if this is not configured
X402_PAY_TO_ADDRESS=0x4027FdaC1a5216e264A00a5928b8366aE59cE888

# Network: which blockchain to use for payments
# eip155:8453 = Base mainnet (REAL money - use for production)
# eip155:84532 = Base Sepolia testnet (TEST money - use for development)
# Defaults: testnet in development, mainnet in production
X402_NETWORK=eip155:84532

# Facilitator URL for payment verification and settlement
# Default works for most cases, only change if using custom facilitator
X402_FACILITATOR_URL=https://x402.org/facilitator

# Debug logging for x402 payments (optional)
X402_DEBUG=false

# Skip payment verification in development (NEVER use in production!)
# Uncomment ONLY for local testing without a wallet
# X402_SKIP_VERIFICATION=true

# ═══════════════════════════════════════════════════════════════════════════════
# Payment Webhook Notifications (Optional - for revenue tracking)
# ═══════════════════════════════════════════════════════════════════════════════

# Custom webhook URL - receives JSON POST for all payment events
# This is the recommended option - just point to your own endpoint
# Payload: { event, timestamp, data: { amount, operation, payer, network, txHash } }
X402_WEBHOOK_URL=https://your-server.com/webhooks/payments

# Webhook signature secret (optional but recommended for production)
# Signs payloads with HMAC-SHA256 for security
# Verify: X-Webhook-Signature header = HMAC-SHA256(timestamp.payload, secret)
# Generate with: openssl rand -hex 32
X402_WEBHOOK_SECRET=

# Optional: Discord/Slack webhooks (leave empty if not using)
# DISCORD_WEBHOOK_URL=
# SLACK_WEBHOOK_URL=

# ═══════════════════════════════════════════════════════════════════════════════
# Stripe Billing (Subscription Payments)
# ═══════════════════════════════════════════════════════════════════════════════

# Get your keys from https://dashboard.stripe.com/apikeys
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...

# Webhook signing secret — from Stripe Dashboard > Webhooks > Signing secret
# Endpoint URL: https://your-domain.com/webhooks/stripe
STRIPE_WEBHOOK_SECRET=whsec_...

# Price IDs — create these in Stripe Dashboard > Products
# Each should be a recurring monthly price
STRIPE_PRO_PRICE_ID=price_...
STRIPE_BUSINESS_PRICE_ID=price_...
STRIPE_ENTERPRISE_PRICE_ID=price_...

# ═══════════════════════════════════════════════════════════════════════════════
# MCP Server Configuration (for AI agents like Claude)
# ═══════════════════════════════════════════════════════════════════════════════

# MCP mode: local (free, uses Puppeteer) or remote (paid, uses x402 API)
# Mode: 'local' (default, uses Puppeteer) or 'remote' (cloud API)
XACTIONS_MODE=local

# API URL for remote mode
# Remote mode settings (only needed if XACTIONS_MODE=remote)
XACTIONS_API_URL=https://api.xactions.app

# Private key for x402 payments (remote mode only)
# ⚠️ NEVER commit this value! Use a separate wallet for payments.
# X402_PRIVATE_KEY=0xYourPrivateKey

# X/Twitter session cookie for authentication
XACTIONS_SESSION_COOKIE=

# ═══════════════════════════════════════════════════════════════════════════════
# Database (PostgreSQL)
# ═══════════════════════════════════════════════════════════════════════════════

DATABASE_URL="postgresql://user:password@localhost:5432/xactions?schema=public"

# ═══════════════════════════════════════════════════════════════════════════════
# Authentication & Security
# ═══════════════════════════════════════════════════════════════════════════════

JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
SESSION_SECRET=your-session-secret-key

# Admin API key for accessing admin endpoints (payment stats, webhook management)
# Generate a secure random string: openssl rand -hex 32
ADMIN_API_KEY=
# Optional: x420 micropayments (remote mode)
X402_PRIVATE_KEY=
X402_NETWORK=base-sepolia

# CORS allowed origins (comma-separated)
CORS_ORIGINS=http://localhost:3000,http://localhost:3001,https://xactions.app
# Optional: AI-generated content via XActions
OPENROUTER_API_KEY=

# ═══════════════════════════════════════════════════════════════════════════════
# Twitter/X OAuth 2.0
# ═══════════════════════════════════════════════════════════════════════════════
# Database (Docker Compose defaults)
POSTGRES_PASSWORD=xactions_dev_password
DATABASE_URL=postgresql://xactions:xactions_dev_password@localhost:5432/xactions?schema=public

TWITTER_CLIENT_ID=your_twitter_client_id
TWITTER_CLIENT_SECRET=your_twitter_client_secret
TWITTER_BEARER_TOKEN=your_twitter_bearer_token

# ═══════════════════════════════════════════════════════════════════════════════
# Redis (for job queue)
# ═══════════════════════════════════════════════════════════════════════════════

REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=

# ═══════════════════════════════════════════════════════════════════════════════
# Puppeteer Configuration (for local mode)
# ═══════════════════════════════════════════════════════════════════════════════

PUPPETEER_HEADLESS=true
PUPPETEER_NO_SANDBOX=true
# PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

# ═══════════════════════════════════════════════════════════════════════════════
# Logging & Monitoring
# ═══════════════════════════════════════════════════════════════════════════════

LOG_LEVEL=info
# SENTRY_DSN=https://xxx@sentry.io/xxx
# API Server
PORT=3001
JWT_SECRET=change_this_to_a_random_string
SESSION_SECRET=change_this_too

# Hermes Integration
HERMES_CONTENT_DIR=~/.hermes/cron/output
32 changes: 30 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ extension/ → Browser extension (Chrome/Edge)

## Skills

31 skills in `skills/*/SKILL.md`. Read the relevant SKILL.md when a user's request matches a category.
32 skills in `skills/*/SKILL.md`. Read the relevant SKILL.md when a user's request matches a category.

- **Hermes integration** — `skills/hermes-integration/SKILL.md` — content pipeline from Hermes to X/Twitter

- **Unfollow management** — mass unfollow, non-follower cleanup
- **Analytics & insights** — engagement, hashtags, competitors, best times
Expand Down Expand Up @@ -104,9 +106,35 @@ Common resource hogs: `tsgo --noEmit` (~500% CPU), vitest workers (15x ~100% CPU
- Do not reuse foreground shell sessions — stale sessions block future operations
- If a terminal appears unresponsive, kill it and create a new one

## Hermes Integration

This repo is integrated with Hermes Agent for AI-powered content generation.

### Content Pipeline

1. Hermes generates security content (vulnerability threads, exploit analysis)
2. All content is deslopped (no AI patterns, em dashes, hedging)
3. Hermes sends content to the user via Telegram
4. **User posts manually** — no automation, no API keys needed

### Key Files

| File | Purpose |
|------|---------|
| `skills/hermes-integration/SKILL.md` | Full integration guide |
| `scripts/hermes-poster.js` | Format content for manual posting |
| `.env.example` | Environment variable template (for future automation) |

### Environment

- `.env` is in `.gitignore` — never commit secrets
- Content directory: `~/.hermes/cron/output/`

## Mandatory Rules

1. **Never mock, stub, or fake anything.** Real implementations only.
2. **TypeScript strict mode** — no `any`, no `@ts-ignore`.
3. **Always kill terminals** after commands complete.
4. **Always commit and push** as `nirholas`.
4. **Always commit and push** as `preciousnwankwo`.
5. **Never commit `.env` files, session cookies, or API keys.**
6. **Never auto-post content. The user posts all content manually.**
12 changes: 12 additions & 0 deletions config/hermes-mcp-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"mcpServers": {
"xactions": {
"command": "npx",
"args": ["-y", "xactions-mcp"],
"env": {
"XACTIONS_SESSION_COOKIE": "${XACTIONS_SESSION_COOKIE}",
"XACTIONS_MODE": "local"
}
}
}
}
64 changes: 64 additions & 0 deletions docs/hermes-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Hermes Agent Integration

XActions integrates with [Hermes Agent](https://hermes-agent.nousresearch.com/) for AI-powered Twitter content generation.

## Overview

1. **Hermes generates content** — security threads, vulnerability breakdowns, exploit analysis
2. **Deslop pass** — all output is stripped of AI patterns (no em dashes, hedging, AI vocabulary)
3. **User posts manually** — Hermes sends content to the user via Telegram. The user copies and posts it.

No X API keys. No session cookies. No browser automation. Just content generation.

## Quick Start

### 1. Generate Content

Hermes uses the security content pool at `~/.hermes/scripts/security-content-generator.py` to generate threads. Content is automatically deslopped.

### 2. Receive Content

Hermes sends content to the user via Telegram, 3 times daily:
- Morning (9:00 UTC)
- Afternoon (14:00 UTC)
- Evening (22:37 UTC)

### 3. Post Manually

The user copies the content and posts it to X/Twitter manually. No automation.

## Content Pipeline

```
Security Content Pool (26+ threads, auto-refreshed weekly)
Hermes picks topic (no repeats)
Deslop transformation
Hermes sends to user (Telegram)
User copies and posts manually
Log sent thread (no duplicates)
```

## Content Types

- **Single tweet** — one vulnerability insight, under 280 chars
- **Thread** — 3-8 tweets breaking down an exploit, with hook + value + CTA

## Safety

- **No auto-posting** — the user posts all content manually
- **No duplicate content** — sent threads are logged and never repeated
- **No secrets in repo** — `.env` is in `.gitignore`
- **No X API keys needed** — no automation means no keys

## Files

| File | Purpose |
|------|---------|
| `skills/hermes-integration/SKILL.md` | Full integration guide |
| `scripts/hermes-poster.js` | Format content for manual posting |
| `.env.example` | Environment template (for future automation) |
95 changes: 95 additions & 0 deletions scripts/hermes-poster.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env node
// Hermes Content Poster — Format deslopped content for manual posting
// Usage: node hermes-poster.js [content-file]
// Reads a content file, splits it into tweet-sized chunks, outputs copy-paste ready text

import fs from 'node:fs';
import path from 'node:path';

const CONTENT_DIR = process.env.HERMES_CONTENT_DIR || `${process.env.HOME}/.hermes/cron/output`;

function parseContent(raw) {
const lines = raw.trim().split('\n');
const title = lines[0].replace(/[:\-–—].*$/, '').trim();
const body = lines.slice(1).join('\n').trim();
const tweets = splitIntoTweets(body);
return { title, body, tweets };
}

function splitIntoTweets(text) {
const maxLen = 280;
const tweets = [];
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];

let current = '';
for (const sentence of sentences) {
if ((current + ' ' + sentence).trim().length <= maxLen) {
current = (current + ' ' + sentence).trim();
} else {
if (current) tweets.push(current);
if (sentence.length <= maxLen) {
current = sentence.trim();
} else {
const words = sentence.split(' ');
current = '';
for (const word of words) {
if ((current + ' ' + word).length <= maxLen) {
current = (current + ' ' + word).trim();
} else {
if (current) tweets.push(current);
current = word;
}
}
}
}
}
if (current) tweets.push(current);

if (tweets.length > 1) {
return tweets.map((t, i) => `${i + 1}/${tweets.length} ${t}`);
}
return tweets;
}

function main() {
const inputFile = process.argv[2];
if (!inputFile) {
const files = fs.readdirSync(CONTENT_DIR)
.filter(f => f.startsWith('security-thread_') && f.endsWith('.txt'))
.sort().reverse().slice(0, 10);

if (files.length === 0) {
console.log('No content files found.');
process.exit(1);
}

console.log('Available content files:');
files.forEach((f, i) => console.log(` ${i + 1}. ${f}`));
console.log('\nUsage: node hermes-poster.js <filename>');
process.exit(0);
}

const filePath = path.isAbsolute(inputFile) ? inputFile : path.join(CONTENT_DIR, inputFile);
if (!fs.existsSync(filePath)) {
console.error(`File not found: ${filePath}`);
process.exit(1);
}

const raw = fs.readFileSync(filePath, 'utf-8');
const { title, tweets } = parseContent(raw);

console.log(`\n${'='.repeat(60)}`);
console.log(`Title: ${title}`);
console.log(`Tweets: ${tweets.length}`);
console.log(`${'='.repeat(60)}\n`);

tweets.forEach((t, i) => {
console.log(`--- Tweet ${i + 1} (${t.length} chars) ---`);
console.log(t);
console.log();
});

console.log('Copy each tweet above and post to X/Twitter manually.');
}

main();
Loading