+190
-100
| --- | ||
| name: x-search | ||
| description: "X/Twitter search via xAI Grok API. Use when user wants to search tweets, monitor topics, find viral posts, or run social listening. Costs 5 credits per search. Triggers on x search, tweet search, twitter search, social listening, revenue intel, viral tweets." | ||
| version: 1.0.0 | ||
| version: 2.0.0 | ||
| tags: | ||
@@ -11,135 +11,225 @@ - x-search | ||
| # x-search | ||
| # X Search | ||
| Search X/Twitter via xAI's Grok API. 5 credits per search action. Returns tweets with full text, author, engagement metrics, and citations. | ||
| > Drop this in `~/.claude/skills/x-search/SKILL.md` and Claude Code becomes your X/Twitter intelligence tool. | ||
| ## Quick Search | ||
| ## Bootstrap (ALWAYS Run First) | ||
| ```python | ||
| cd backend | ||
| source ../venv/bin/activate | ||
| ``` | ||
| Before any X search operation, run this bootstrap to ensure everything is set up: | ||
| ```python | ||
| import asyncio | ||
| from dotenv import load_dotenv | ||
| load_dotenv('.env') | ||
| ```bash | ||
| #!/bin/bash | ||
| set -e | ||
| from clients.xai import XAIClient, bill_xai_action | ||
| # 1. Check if atris CLI is installed | ||
| if ! command -v atris &> /dev/null; then | ||
| echo "Installing atris CLI..." | ||
| npm install -g atris | ||
| fi | ||
| # 1. Bill the user (5 credits) | ||
| bill = await bill_xai_action(USER_ID, "x_search") | ||
| if not bill["ok"]: | ||
| print(f"Insufficient credits: {bill['error']}") | ||
| # 2. Check if logged in to AtrisOS | ||
| if [ ! -f ~/.atris/credentials.json ]; then | ||
| echo "Not logged in to AtrisOS." | ||
| echo "" | ||
| echo "Option 1 (interactive): Run 'atris login' and follow prompts" | ||
| echo "Option 2 (non-interactive): Get token from https://atris.ai/auth/cli" | ||
| echo " Then run: atris login --token YOUR_TOKEN" | ||
| echo "" | ||
| exit 1 | ||
| fi | ||
| # 2. Search | ||
| client = XAIClient() | ||
| result = await client.search_x('"CRM is dead" OR "Salesforce is dead"', limit=10) | ||
| print(result["content"]) | ||
| print(f"Usage: {result['usage']}") | ||
| ``` | ||
| # 3. Extract token | ||
| if command -v node &> /dev/null; then | ||
| TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)") | ||
| elif command -v python3 &> /dev/null; then | ||
| TOKEN=$(python3 -c "import json,os; print(json.load(open(os.path.expanduser('~/.atris/credentials.json')))['token'])") | ||
| elif command -v jq &> /dev/null; then | ||
| TOKEN=$(jq -r '.token' ~/.atris/credentials.json) | ||
| else | ||
| echo "Error: Need node, python3, or jq to read credentials" | ||
| exit 1 | ||
| fi | ||
| ## Date-Constrained Search (last N days) | ||
| # 4. Quick auth check | ||
| STATUS=$(curl -s "https://api.atris.ai/api/me" \ | ||
| -H "Authorization: Bearer $TOKEN") | ||
| ```python | ||
| from xai_sdk.chat import user as xai_user | ||
| if echo "$STATUS" | grep -q "Token expired\|Not authenticated\|Unauthorized"; then | ||
| echo "Token expired. Please re-authenticate:" | ||
| echo " Run: atris login --force" | ||
| exit 1 | ||
| fi | ||
| client = XAIClient() | ||
| tools = client._get_tools() | ||
| echo "Ready. X Search is available (5 credits per search)." | ||
| export ATRIS_TOKEN="$TOKEN" | ||
| ``` | ||
| chat = client._get_client().chat.create( | ||
| model="grok-4-1-fast", | ||
| tools=[tools.x_search()], | ||
| ) | ||
| --- | ||
| chat.append(xai_user(f"""Search X/Twitter for: "your query here" | ||
| ## API Reference | ||
| IMPORTANT: Only return tweets posted within the last 7 days. | ||
| Return the 10 most relevant posts with: | ||
| - Full tweet text (quoted exactly) | ||
| - Author handle | ||
| - Exact date/time | ||
| - Engagement (likes, retweets, views) | ||
| Base: `https://api.atris.ai/api/x-search` | ||
| Prioritize posts with high engagement (likes > 5, views > 1000).""")) | ||
| All requests require: `-H "Authorization: Bearer $TOKEN"` | ||
| content = "" | ||
| for response, chunk in chat.stream(): | ||
| if chunk.content: | ||
| content += chunk.content | ||
| print(content) | ||
| ### Get Token (after bootstrap) | ||
| ```bash | ||
| TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)") | ||
| ``` | ||
| ## Revenue Intel Digest (multi-cluster) | ||
| ### Search X/Twitter | ||
| ```bash | ||
| curl -s -X POST "https://api.atris.ai/api/x-search/search" \ | ||
| -H "Authorization: Bearer $TOKEN" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "query": "\"CRM is dead\" OR \"Salesforce alternative\"", | ||
| "limit": 10 | ||
| }' | ||
| ``` | ||
| ```python | ||
| from atris.team.gtm.skills.revenue_intel import run_digest, format_for_slack | ||
| **With date filter** (last N days only): | ||
| ```bash | ||
| curl -s -X POST "https://api.atris.ai/api/x-search/search" \ | ||
| -H "Authorization: Bearer $TOKEN" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "query": "AI agents replacing SaaS", | ||
| "limit": 10, | ||
| "days_back": 7 | ||
| }' | ||
| ``` | ||
| # Runs 5 keyword clusters, drafts replies for top 5 items | ||
| # Cost: 5 credits per cluster = 25 credits total | ||
| result = await run_digest( | ||
| days_back=1, | ||
| user_id=USER_ID, | ||
| draft_count=5, | ||
| ) | ||
| **Response:** | ||
| ```json | ||
| { | ||
| "status": "success", | ||
| "credits_used": 5, | ||
| "credits_remaining": 995, | ||
| "data": { | ||
| "content": "1. @levelsio: AI agents are replacing...", | ||
| "citations": ["https://x.com/levelsio/status/..."], | ||
| "usage": {"prompt_tokens": 200, "completion_tokens": 800} | ||
| } | ||
| } | ||
| ``` | ||
| print(result["digest"]) # Ranked results by cluster | ||
| print(result["drafts"]) # Reply options A/B per item | ||
| print(result["credits_used"]) | ||
| ### Research a Person | ||
| ```bash | ||
| curl -s -X POST "https://api.atris.ai/api/x-search/research-person" \ | ||
| -H "Authorization: Bearer $TOKEN" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "name": "Leah Bonvissuto", | ||
| "handle": "leahbon", | ||
| "company": "Presentr", | ||
| "context": "Interested in revenue intelligence and AI for GTM" | ||
| }' | ||
| ``` | ||
| # Format for Slack delivery | ||
| slack_msg = await format_for_slack(result) | ||
| **Response:** | ||
| ```json | ||
| { | ||
| "status": "success", | ||
| "credits_used": 5, | ||
| "credits_remaining": 990, | ||
| "data": { | ||
| "content": "### 1. Profile\n**Name:** Leah Bonvissuto\n...", | ||
| "citations": ["https://x.com/..."], | ||
| "usage": {"prompt_tokens": 300, "completion_tokens": 1200} | ||
| } | ||
| } | ||
| ``` | ||
| ## Custom Keyword Clusters | ||
| --- | ||
| ```python | ||
| my_clusters = { | ||
| "competitors": '"Competitor Name" (review OR complaint OR switching OR alternative)', | ||
| "industry_pain": '"your industry" (frustrated OR broken OR replacing OR ripping out)', | ||
| "thought_leaders": '(founder OR CEO OR CTO) ("your topic" OR "related topic") min_faves:10', | ||
| } | ||
| ## Workflows | ||
| result = await run_digest(clusters=my_clusters, user_id=USER_ID, days_back=7) | ||
| ``` | ||
| ### "Search X for tweets about a topic" | ||
| 1. Run bootstrap | ||
| 2. Search: `POST /x-search/search` with `{query, limit}` | ||
| 3. Display results: tweet text, author, engagement, links | ||
| ### "Find tweets from the last week about X" | ||
| 1. Run bootstrap | ||
| 2. Search with date filter: `POST /x-search/search` with `{query, limit, days_back: 7}` | ||
| 3. Display results sorted by engagement | ||
| ### "Research a person before a meeting" | ||
| 1. Run bootstrap | ||
| 2. Research: `POST /x-search/research-person` with `{name, handle, company, context}` | ||
| 3. Display profile, background, talking points | ||
| ### "Monitor keyword clusters for revenue intel" | ||
| 1. Run bootstrap | ||
| 2. Run multiple searches across keyword clusters: | ||
| - `"CRM is dead" OR "Salesforce is dead" OR "HubSpot sucks"` | ||
| - `"revenue operations" (broken OR frustrated OR replacing)` | ||
| - `(founder OR CEO) "tech stack" (consolidating OR ripping out)` | ||
| 3. Each search costs 5 credits | ||
| 4. Combine results, rank by engagement, draft replies | ||
| ### "Find viral tweets in my industry" | ||
| 1. Run bootstrap | ||
| 2. Search with engagement filter: `POST /x-search/search` with query including `min_faves:50` | ||
| 3. Display top tweets sorted by likes/retweets | ||
| --- | ||
| ## Query Tips | ||
| | Goal | Query Example | | ||
| |------|--------------| | ||
| | Specific phrase | `"revenue operations"` | | ||
| | OR logic | `"CRM is dead" OR "Salesforce alternative"` | | ||
| | From a user | `from:levelsio` | | ||
| | High engagement | `"AI agents" min_faves:50` | | ||
| | Exclude retweets | `"your query" -is:retweet` | | ||
| | Multiple keywords | `(founder OR CEO) ("AI adoption" OR "AI native")` | | ||
| --- | ||
| ## Billing | ||
| 1. Every xAI action costs **5 credits** (flat) | ||
| 2. 1 credit = $0.01, so 1 search = $0.05 | ||
| 3. `bill_xai_action(user_id, action_name)` checks balance and deducts before the call | ||
| 4. Returns `{"ok": False, "error": "Insufficient credits"}` if balance < 5 | ||
| 5. Transaction logged as `txn_type="xai_action"` in `credit_transactions` | ||
| - Every search costs **5 credits** (flat) | ||
| - 1 credit = $0.01, so 1 search = $0.05 | ||
| - Research person also costs 5 credits | ||
| - Credits are deducted server-side before the search runs | ||
| - If insufficient credits, returns `402 Insufficient credits` | ||
| | Plan | Monthly Credits | Searches/Month | | ||
| |------|----------------|----------------| | ||
| | Free | 50 | 10 | | ||
| | Pro ($20/mo) | 1,000 | 200 | | ||
| | Max ($200/mo) | unlimited | unlimited | | ||
| | Credit pack ($10) | 1,000 | 200 | | ||
| --- | ||
| ## Cost Breakdown | ||
| ## Error Handling | ||
| | Component | Cost to us | What it does | | ||
| |-----------|-----------|--------------| | ||
| | xAI x_search tool | $0.005/call | Searches X, returns batch of tweets | | ||
| | grok-4-1-fast tokens | ~$0.003/call | Processes and formats results | | ||
| | Total per search | ~$0.008 | Raw cost | | ||
| | We charge | $0.05 (5 credits) | 84% margin | | ||
| | Error | Meaning | Solution | | ||
| |-------|---------|----------| | ||
| | `401 Not authenticated` | Invalid/expired token | Run `atris login` | | ||
| | `402 Insufficient credits` | Not enough credits | Purchase credits at atris.ai | | ||
| | `502 Search failed` | xAI API issue | Retry in a few seconds | | ||
| ## Key Files | ||
| --- | ||
| | File | What | | ||
| |------|------| | ||
| | `backend/clients/xai.py` | XAIClient + bill_xai_action() | | ||
| | `atris/team/gtm/skills/revenue_intel.py` | Multi-cluster digest pipeline | | ||
| | `backend/tools/core/reply_guy_tool.py` | Reply generation tool (bills 5 credits) | | ||
| | `backend/tools/integrations/research_tweet_tool.py` | Research + tweet tool (bills 5 credits) | | ||
| ## Quick Reference | ||
| ## Rules | ||
| ```bash | ||
| # Setup (one time) | ||
| npm install -g atris && atris login | ||
| 1. Always bill before searching — never search without `bill_xai_action()` | ||
| 2. Date filtering is prompt-based, not API-level — include explicit date cutoff in prompt | ||
| 3. Use `min_faves:N` in queries to filter low-engagement noise | ||
| 4. Never auto-post tweets — always require user approval | ||
| 5. Internal usage (no user_id) skips billing — platform absorbs cost | ||
| # Get token | ||
| TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)") | ||
| # Search tweets | ||
| curl -s -X POST "https://api.atris.ai/api/x-search/search" \ | ||
| -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ | ||
| -d '{"query": "AI agents", "limit": 10}' | ||
| # Search last 7 days only | ||
| curl -s -X POST "https://api.atris.ai/api/x-search/search" \ | ||
| -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ | ||
| -d '{"query": "AI agents", "limit": 10, "days_back": 7}' | ||
| # Research a person | ||
| curl -s -X POST "https://api.atris.ai/api/x-search/research-person" \ | ||
| -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ | ||
| -d '{"name": "John Doe", "handle": "johndoe", "company": "Acme"}' | ||
| ``` |
+1
-1
| { | ||
| "name": "atris", | ||
| "version": "2.3.7", | ||
| "version": "2.3.8", | ||
| "description": "atrisDev (atris dev) - CLI for AI coding agents. Works with Claude Code, Cursor, Windsurf. Make any codebase AI-navigable.", | ||
@@ -5,0 +5,0 @@ "main": "bin/atris.js", |
Network access
Supply chain riskThis module accesses the network.
Found 3 instances in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 3 instances in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
714257
0.32%