feat: Add AIsa unified API skill - Twitter, Search, Scholar, Financial, LLM
This commit is contained in:
parent
da71eaebd2
commit
4336368265
210
skills/aisa/SKILL.md
Normal file
210
skills/aisa/SKILL.md
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
---
|
||||||
|
name: openclaw-starter-kit
|
||||||
|
description: "OpenClaw Starter Kit - The definitive starting point for building autonomous agents. Powered by AIsa unified API: Twitter/X (read & write), web search, academic papers, news, and LLM routing with one API key."
|
||||||
|
homepage: https://openclaw.ai
|
||||||
|
metadata: {"openclaw":{"emoji":"🦞","requires":{"bins":["curl","python3"],"env":["AISA_API_KEY"]},"primaryEnv":"AISA_API_KEY"}}
|
||||||
|
---
|
||||||
|
|
||||||
|
# OpenClaw Starter Kit 🦞
|
||||||
|
|
||||||
|
**The definitive starting point for autonomous agents. Powered by AIsa.**
|
||||||
|
|
||||||
|
One API key. All the data sources your agent needs.
|
||||||
|
|
||||||
|
## 🔥 What Can You Do?
|
||||||
|
|
||||||
|
### Morning Briefing (Scheduled)
|
||||||
|
```
|
||||||
|
"Send me a daily briefing at 8am with:
|
||||||
|
- My portfolio performance (NVDA, TSLA, BTC)
|
||||||
|
- Twitter trends in AI
|
||||||
|
- Top news in my industry"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Competitor Intelligence
|
||||||
|
```
|
||||||
|
"Monitor @OpenAI - alert me on new tweets, news mentions, and paper releases"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Investment Research
|
||||||
|
```
|
||||||
|
"Full analysis on NVDA: price trends, insider trades, analyst estimates,
|
||||||
|
SEC filings, and Twitter sentiment"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Startup Validation
|
||||||
|
```
|
||||||
|
"Research the market for AI writing tools - find competitors,
|
||||||
|
Twitter discussions, and academic papers on the topic"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Crypto Whale Alerts
|
||||||
|
```
|
||||||
|
"Track large BTC movements and correlate with Twitter activity"
|
||||||
|
```
|
||||||
|
|
||||||
|
## AIsa vs bird
|
||||||
|
|
||||||
|
| Feature | AIsa ⚡ | bird 🐦 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| Auth method | API Key (simple) | Browser cookies (complex) |
|
||||||
|
| Read Twitter | ✅ | ✅ |
|
||||||
|
| Post/Like/Retweet | ✅ (via login) | ✅ |
|
||||||
|
| Web Search | ✅ | ❌ |
|
||||||
|
| Scholar Search | ✅ | ❌ |
|
||||||
|
| News/Financial | ✅ | ❌ |
|
||||||
|
| LLM Routing | ✅ | ❌ |
|
||||||
|
| Server-friendly | ✅ | ❌ |
|
||||||
|
| Cost | Pay-per-use | Free |
|
||||||
|
|
||||||
|
**Use AIsa when**: Server environment, need search/scholar APIs, prefer simple API key setup.
|
||||||
|
**Use bird when**: Local machine with browser, need free access, complex Twitter interactions.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export AISA_API_KEY="your-key"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Capabilities
|
||||||
|
|
||||||
|
### Twitter/X Data (Read)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get user info
|
||||||
|
curl "https://api.aisa.one/apis/v1/twitter/user/info?userName=elonmusk" \
|
||||||
|
-H "Authorization: Bearer $AISA_API_KEY"
|
||||||
|
|
||||||
|
# Advanced tweet search
|
||||||
|
curl "https://api.aisa.one/apis/v1/twitter/tweet/advanced_search?query=AI+agents&queryType=Latest" \
|
||||||
|
-H "Authorization: Bearer $AISA_API_KEY"
|
||||||
|
|
||||||
|
# Get trending topics (worldwide)
|
||||||
|
curl "https://api.aisa.one/apis/v1/twitter/trends?woeid=1" \
|
||||||
|
-H "Authorization: Bearer $AISA_API_KEY"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Twitter/X Post (Write)
|
||||||
|
|
||||||
|
> ⚠️ **Warning**: Posting requires account login. Use responsibly to avoid rate limits or account suspension.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Step 1: Login first (async, check status after)
|
||||||
|
curl -X POST "https://api.aisa.one/apis/v1/twitter/user_login_v3" \
|
||||||
|
-H "Authorization: Bearer $AISA_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"user_name":"myaccount","email":"me@example.com","password":"xxx","proxy":"http://user:pass@ip:port"}'
|
||||||
|
|
||||||
|
# Step 2: Send tweet
|
||||||
|
curl -X POST "https://api.aisa.one/apis/v1/twitter/send_tweet_v3" \
|
||||||
|
-H "Authorization: Bearer $AISA_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"user_name":"myaccount","text":"Hello from OpenClaw!"}'
|
||||||
|
|
||||||
|
# Like / Retweet
|
||||||
|
curl -X POST "https://api.aisa.one/apis/v1/twitter/like_tweet_v3" \
|
||||||
|
-H "Authorization: Bearer $AISA_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"user_name":"myaccount","tweet_id":"1234567890"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Search (Web + Academic)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Web search
|
||||||
|
curl -X POST "https://api.aisa.one/apis/v1/scholar/search/web?query=AI+frameworks&max_num_results=10" \
|
||||||
|
-H "Authorization: Bearer $AISA_API_KEY"
|
||||||
|
|
||||||
|
# Academic/scholar search
|
||||||
|
curl -X POST "https://api.aisa.one/apis/v1/scholar/search/scholar?query=transformer+models&max_num_results=10" \
|
||||||
|
-H "Authorization: Bearer $AISA_API_KEY"
|
||||||
|
|
||||||
|
# Smart search (web + academic combined)
|
||||||
|
curl -X POST "https://api.aisa.one/apis/v1/scholar/search/smart?query=machine+learning&max_num_results=10" \
|
||||||
|
-H "Authorization: Bearer $AISA_API_KEY"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Financial News
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Company news by ticker
|
||||||
|
curl "https://api.aisa.one/apis/v1/financial/news?ticker=AAPL&limit=10" \
|
||||||
|
-H "Authorization: Bearer $AISA_API_KEY"
|
||||||
|
```
|
||||||
|
|
||||||
|
### LLM Routing (OpenAI Compatible)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://api.aisa.one/v1/chat/completions" \
|
||||||
|
-H "Authorization: Bearer $AISA_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"model": "gpt-4", "messages": [{"role": "user", "content": "Hello"}]}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported models: GPT-4, Claude-3, Gemini, Qwen, Deepseek, Grok, and more.
|
||||||
|
|
||||||
|
## Python Client
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Twitter Read
|
||||||
|
python3 {baseDir}/scripts/aisa_client.py twitter user-info --username elonmusk
|
||||||
|
python3 {baseDir}/scripts/aisa_client.py twitter search --query "AI agents"
|
||||||
|
python3 {baseDir}/scripts/aisa_client.py twitter trends --woeid 1
|
||||||
|
|
||||||
|
# Twitter Write (requires login first)
|
||||||
|
python3 {baseDir}/scripts/aisa_client.py twitter login --username myaccount --email me@example.com --password xxx --proxy "http://user:pass@ip:port"
|
||||||
|
python3 {baseDir}/scripts/aisa_client.py twitter post --username myaccount --text "Hello!"
|
||||||
|
python3 {baseDir}/scripts/aisa_client.py twitter like --username myaccount --tweet-id 1234567890
|
||||||
|
|
||||||
|
# Search
|
||||||
|
python3 {baseDir}/scripts/aisa_client.py search web --query "latest AI news"
|
||||||
|
python3 {baseDir}/scripts/aisa_client.py search scholar --query "LLM research"
|
||||||
|
python3 {baseDir}/scripts/aisa_client.py search smart --query "machine learning"
|
||||||
|
|
||||||
|
# News
|
||||||
|
python3 {baseDir}/scripts/aisa_client.py news --ticker AAPL
|
||||||
|
|
||||||
|
# LLM
|
||||||
|
python3 {baseDir}/scripts/aisa_client.py llm complete --model gpt-4 --prompt "Explain quantum computing"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pricing
|
||||||
|
|
||||||
|
| API | Cost |
|
||||||
|
|-----|------|
|
||||||
|
| Twitter query | ~$0.0004 |
|
||||||
|
| Twitter post/like | ~$0.001 |
|
||||||
|
| Web search | ~$0.001 |
|
||||||
|
| Scholar search | ~$0.002 |
|
||||||
|
| News | ~$0.001 |
|
||||||
|
| LLM | Token-based |
|
||||||
|
|
||||||
|
Every response includes `usage.cost` and `usage.credits_remaining`.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Errors return JSON with `error` field:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Invalid API key",
|
||||||
|
"code": 401
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Common error codes:
|
||||||
|
- `401` - Invalid or missing API key
|
||||||
|
- `402` - Insufficient credits
|
||||||
|
- `429` - Rate limit exceeded
|
||||||
|
- `500` - Server error
|
||||||
|
|
||||||
|
## Get Started
|
||||||
|
|
||||||
|
1. Sign up at [aisa.one](https://aisa.one)
|
||||||
|
2. Get your API key
|
||||||
|
3. Add credits (pay-as-you-go)
|
||||||
|
4. Set environment variable: `export AISA_API_KEY="your-key"`
|
||||||
|
|
||||||
|
## Full API Reference
|
||||||
|
|
||||||
|
See [references/api-reference.md](references/api-reference.md) for complete endpoint documentation.
|
||||||
198
skills/aisa/references/api-reference.md
Normal file
198
skills/aisa/references/api-reference.md
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
# OpenClaw Starter Kit - API Reference
|
||||||
|
|
||||||
|
**Powered by AIsa**
|
||||||
|
|
||||||
|
Complete API documentation based on [aisa.mintlify.app](https://aisa.mintlify.app/api-reference/introduction).
|
||||||
|
|
||||||
|
## Base URL
|
||||||
|
|
||||||
|
```
|
||||||
|
https://api.aisa.one/apis/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
All requests require a Bearer token:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Bearer YOUR_AISA_API_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Twitter/X APIs
|
||||||
|
|
||||||
|
### GET /twitter/user/info
|
||||||
|
|
||||||
|
Get user information by username.
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| userName | string | Yes | Twitter username (without @) |
|
||||||
|
|
||||||
|
### GET /twitter/tweet/advanced_search
|
||||||
|
|
||||||
|
Advanced search for tweets.
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| query | string | Yes | Search query |
|
||||||
|
| queryType | string | Yes | "Latest" or "Top" |
|
||||||
|
| cursor | string | No | Pagination cursor |
|
||||||
|
|
||||||
|
### GET /twitter/user/user_last_tweet
|
||||||
|
|
||||||
|
Get user's recent tweets.
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| userName | string | Yes | Twitter username |
|
||||||
|
|
||||||
|
### GET /twitter/tweet/tweetById
|
||||||
|
|
||||||
|
Get tweets by IDs.
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| tweet_ids | string | Yes | Comma-separated tweet IDs |
|
||||||
|
|
||||||
|
### GET /twitter/trends
|
||||||
|
|
||||||
|
Get trending topics by WOEID.
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| woeid | integer | Yes | WOEID (1 = worldwide) |
|
||||||
|
| count | integer | No | Number of trends (default 30) |
|
||||||
|
|
||||||
|
### GET /twitter/user/search_user
|
||||||
|
|
||||||
|
Search for users by keyword.
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| keyword | string | Yes | Search keyword |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Search APIs
|
||||||
|
|
||||||
|
### POST /scholar/search/web
|
||||||
|
|
||||||
|
Web search with structured results.
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| query | string | Yes | Search query |
|
||||||
|
| max_num_results | integer | No | Max results (1-100, default 10) |
|
||||||
|
| as_ylo | integer | No | Year lower bound |
|
||||||
|
| as_yhi | integer | No | Year upper bound |
|
||||||
|
|
||||||
|
### POST /scholar/search/scholar
|
||||||
|
|
||||||
|
Academic paper search.
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| query | string | Yes | Search query |
|
||||||
|
| max_num_results | integer | No | Max results (1-100, default 10) |
|
||||||
|
| as_ylo | integer | No | Year lower bound |
|
||||||
|
| as_yhi | integer | No | Year upper bound |
|
||||||
|
|
||||||
|
### POST /scholar/search/smart
|
||||||
|
|
||||||
|
Intelligent search combining web and academic results.
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| query | string | Yes | Search query |
|
||||||
|
| max_num_results | integer | No | Max results |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tavily APIs
|
||||||
|
|
||||||
|
### POST /tavily/search
|
||||||
|
|
||||||
|
Tavily search integration.
|
||||||
|
|
||||||
|
### POST /tavily/extract
|
||||||
|
|
||||||
|
Extract content from URLs.
|
||||||
|
|
||||||
|
### POST /tavily/crawl
|
||||||
|
|
||||||
|
Crawl web pages.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Financial APIs
|
||||||
|
|
||||||
|
### GET /financial/news/company
|
||||||
|
|
||||||
|
Company news by ticker.
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| ticker | string | Yes | Stock ticker (e.g., AAPL) |
|
||||||
|
| limit | integer | No | Number of articles |
|
||||||
|
|
||||||
|
### Other Financial Endpoints
|
||||||
|
|
||||||
|
- `/financial/stock/prices` - Historical stock prices
|
||||||
|
- `/financial/financial_statements/*` - Income, balance, cash flow
|
||||||
|
- `/financial/company/facts` - Company facts by CIK
|
||||||
|
- `/financial/search/stock` - Stock screener
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## LLM APIs (OpenAI Compatible)
|
||||||
|
|
||||||
|
Base URL for LLM: `https://api.aisa.one/v1`
|
||||||
|
|
||||||
|
### POST /v1/chat/completions
|
||||||
|
|
||||||
|
OpenAI-compatible chat completions.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "gpt-4",
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": "You are helpful."},
|
||||||
|
{"role": "user", "content": "Hello!"}
|
||||||
|
],
|
||||||
|
"max_tokens": 1000,
|
||||||
|
"temperature": 0.7
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Supported Models:**
|
||||||
|
|
||||||
|
| Provider | Models |
|
||||||
|
|----------|--------|
|
||||||
|
| OpenAI | gpt-4, gpt-4-turbo, gpt-3.5-turbo |
|
||||||
|
| Anthropic | claude-3-opus, claude-3-sonnet, claude-3-haiku |
|
||||||
|
| Google | gemini-pro, gemini-ultra |
|
||||||
|
| Alibaba | qwen-* |
|
||||||
|
| Deepseek | deepseek-* |
|
||||||
|
| xAI | grok-* |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "error message",
|
||||||
|
"code": 400,
|
||||||
|
"details": "additional info"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Full Documentation
|
||||||
|
|
||||||
|
For complete API documentation including all endpoints:
|
||||||
|
- [AIsa API Reference](https://aisa.mintlify.app/api-reference/introduction)
|
||||||
|
- [Documentation Index](https://aisa.mintlify.app/llms.txt)
|
||||||
456
skills/aisa/scripts/aisa_client.py
Normal file
456
skills/aisa/scripts/aisa_client.py
Normal file
@ -0,0 +1,456 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
OpenClaw Starter Kit - AIsa API Client
|
||||||
|
Powered by AIsa (https://aisa.one)
|
||||||
|
|
||||||
|
Unified API access for autonomous agents.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python aisa_client.py twitter user-info --username <username>
|
||||||
|
python aisa_client.py twitter tweets --username <username> [--count <n>]
|
||||||
|
python aisa_client.py twitter search --query <query> [--count <n>]
|
||||||
|
python aisa_client.py twitter detail --tweet-id <id>
|
||||||
|
python aisa_client.py twitter trends
|
||||||
|
python aisa_client.py search web --query <query> [--count <n>]
|
||||||
|
python aisa_client.py search scholar --query <query> [--count <n>]
|
||||||
|
python aisa_client.py news --query <query> [--count <n>]
|
||||||
|
python aisa_client.py llm complete --model <model> --prompt <prompt>
|
||||||
|
python aisa_client.py llm chat --model <model> --messages <json>
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.error
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class AIsaClient:
|
||||||
|
"""OpenClaw Starter Kit - AIsa API Client for unified access to AI-native data sources."""
|
||||||
|
|
||||||
|
BASE_URL = "https://api.aisa.one/apis/v1"
|
||||||
|
LLM_BASE_URL = "https://api.aisa.one/v1"
|
||||||
|
|
||||||
|
def __init__(self, api_key: Optional[str] = None):
|
||||||
|
"""Initialize the client with an API key."""
|
||||||
|
self.api_key = api_key or os.environ.get("AISA_API_KEY")
|
||||||
|
if not self.api_key:
|
||||||
|
raise ValueError(
|
||||||
|
"AISA_API_KEY is required. Set it via environment variable or pass to constructor."
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request(
|
||||||
|
self,
|
||||||
|
method: str,
|
||||||
|
endpoint: str,
|
||||||
|
params: Optional[Dict[str, Any]] = None,
|
||||||
|
data: Optional[Dict[str, Any]] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Make an HTTP request to the AIsa API."""
|
||||||
|
url = f"{self.BASE_URL}{endpoint}"
|
||||||
|
|
||||||
|
if params:
|
||||||
|
query_string = urllib.parse.urlencode(
|
||||||
|
{k: v for k, v in params.items() if v is not None}
|
||||||
|
)
|
||||||
|
url = f"{url}?{query_string}"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "OpenClaw-Starter-Kit/1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
request_data = None
|
||||||
|
if data:
|
||||||
|
request_data = json.dumps(data).encode("utf-8")
|
||||||
|
|
||||||
|
# For POST requests without body, send empty JSON
|
||||||
|
if method == "POST" and request_data is None:
|
||||||
|
request_data = b"{}"
|
||||||
|
|
||||||
|
req = urllib.request.Request(url, data=request_data, headers=headers, method=method)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=60) as response:
|
||||||
|
return json.loads(response.read().decode("utf-8"))
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
error_body = e.read().decode("utf-8")
|
||||||
|
try:
|
||||||
|
return json.loads(error_body)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return {"success": False, "error": {"code": str(e.code), "message": error_body}}
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
return {"success": False, "error": {"code": "NETWORK_ERROR", "message": str(e.reason)}}
|
||||||
|
|
||||||
|
# ==================== Twitter APIs ====================
|
||||||
|
|
||||||
|
def twitter_user_info(self, username: str) -> Dict[str, Any]:
|
||||||
|
"""Get Twitter user information by username."""
|
||||||
|
return self._request("GET", "/twitter/user/info", params={"userName": username})
|
||||||
|
|
||||||
|
def twitter_user_tweets(self, username: str, count: int = 20) -> Dict[str, Any]:
|
||||||
|
"""Get tweets from a specific user."""
|
||||||
|
return self._request("GET", "/twitter/user/user_last_tweet", params={"userName": username})
|
||||||
|
|
||||||
|
def twitter_search(self, query: str, query_type: str = "Latest") -> Dict[str, Any]:
|
||||||
|
"""Search for tweets matching a query (Advanced Search)."""
|
||||||
|
return self._request("GET", "/twitter/tweet/advanced_search", params={
|
||||||
|
"query": query,
|
||||||
|
"queryType": query_type # "Latest" or "Top"
|
||||||
|
})
|
||||||
|
|
||||||
|
def twitter_tweet_detail(self, tweet_ids: str) -> Dict[str, Any]:
|
||||||
|
"""Get detailed information about tweets by IDs (comma-separated)."""
|
||||||
|
return self._request("GET", "/twitter/tweet/tweetById", params={"tweet_ids": tweet_ids})
|
||||||
|
|
||||||
|
def twitter_trends(self, woeid: int = 1) -> Dict[str, Any]:
|
||||||
|
"""Get current Twitter trending topics by WOEID (1 = worldwide)."""
|
||||||
|
return self._request("GET", "/twitter/trends", params={"woeid": woeid})
|
||||||
|
|
||||||
|
def twitter_user_search(self, keyword: str) -> Dict[str, Any]:
|
||||||
|
"""Search for Twitter users by keyword."""
|
||||||
|
return self._request("GET", "/twitter/user/search_user", params={"keyword": keyword})
|
||||||
|
|
||||||
|
# ==================== Twitter Post APIs (V3 - requires login) ====================
|
||||||
|
|
||||||
|
def twitter_login(self, username: str, email: str, password: str, proxy: str, totp_code: str = None) -> Dict[str, Any]:
|
||||||
|
"""Login to Twitter account (V3). Required before posting."""
|
||||||
|
data = {
|
||||||
|
"user_name": username,
|
||||||
|
"email": email,
|
||||||
|
"password": password,
|
||||||
|
"proxy": proxy
|
||||||
|
}
|
||||||
|
if totp_code:
|
||||||
|
data["totp_code"] = totp_code
|
||||||
|
return self._request("POST", "/twitter/user_login_v3", data=data)
|
||||||
|
|
||||||
|
def twitter_get_account(self, username: str) -> Dict[str, Any]:
|
||||||
|
"""Get logged-in account details (check login status)."""
|
||||||
|
return self._request("GET", "/twitter/get_my_x_account_detail_v3", params={"user_name": username})
|
||||||
|
|
||||||
|
def twitter_send_tweet(self, username: str, text: str, media_base64: str = None, media_type: str = None, community_id: str = None) -> Dict[str, Any]:
|
||||||
|
"""Send a tweet (requires prior login via twitter_login)."""
|
||||||
|
data = {
|
||||||
|
"user_name": username,
|
||||||
|
"text": text
|
||||||
|
}
|
||||||
|
if media_base64:
|
||||||
|
data["media_data_base64"] = media_base64
|
||||||
|
if media_type:
|
||||||
|
data["media_type"] = media_type
|
||||||
|
if community_id:
|
||||||
|
data["community_id"] = community_id
|
||||||
|
return self._request("POST", "/twitter/send_tweet_v3", data=data)
|
||||||
|
|
||||||
|
def twitter_like(self, username: str, tweet_id: str) -> Dict[str, Any]:
|
||||||
|
"""Like a tweet (requires prior login)."""
|
||||||
|
return self._request("POST", "/twitter/like_tweet_v3", data={
|
||||||
|
"user_name": username,
|
||||||
|
"tweet_id": tweet_id
|
||||||
|
})
|
||||||
|
|
||||||
|
def twitter_retweet(self, username: str, tweet_id: str) -> Dict[str, Any]:
|
||||||
|
"""Retweet a tweet (requires prior login)."""
|
||||||
|
return self._request("POST", "/twitter/retweet_v3", data={
|
||||||
|
"user_name": username,
|
||||||
|
"tweet_id": tweet_id
|
||||||
|
})
|
||||||
|
|
||||||
|
# ==================== Search APIs ====================
|
||||||
|
|
||||||
|
def search_web(self, query: str, max_results: int = 10) -> Dict[str, Any]:
|
||||||
|
"""Perform a web search (POST method)."""
|
||||||
|
return self._request("POST", "/scholar/search/web", params={
|
||||||
|
"query": query,
|
||||||
|
"max_num_results": max_results
|
||||||
|
})
|
||||||
|
|
||||||
|
def search_scholar(self, query: str, max_results: int = 10, year_from: int = None, year_to: int = None) -> Dict[str, Any]:
|
||||||
|
"""Search academic papers and scholarly content (POST method)."""
|
||||||
|
params = {
|
||||||
|
"query": query,
|
||||||
|
"max_num_results": max_results
|
||||||
|
}
|
||||||
|
if year_from:
|
||||||
|
params["as_ylo"] = year_from
|
||||||
|
if year_to:
|
||||||
|
params["as_yhi"] = year_to
|
||||||
|
return self._request("POST", "/scholar/search/scholar", params=params)
|
||||||
|
|
||||||
|
def search_smart(self, query: str, max_results: int = 10) -> Dict[str, Any]:
|
||||||
|
"""Perform intelligent search combining web and academic results."""
|
||||||
|
return self._request("POST", "/scholar/search/smart", params={
|
||||||
|
"query": query,
|
||||||
|
"max_num_results": max_results
|
||||||
|
})
|
||||||
|
|
||||||
|
# ==================== News API ====================
|
||||||
|
|
||||||
|
def news(self, ticker: str, count: int = 10) -> Dict[str, Any]:
|
||||||
|
"""Get company news by stock ticker."""
|
||||||
|
return self._request("GET", "/financial/news", params={"ticker": ticker, "limit": count})
|
||||||
|
|
||||||
|
# ==================== LLM APIs ====================
|
||||||
|
|
||||||
|
def _llm_request(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Make an HTTP request to the AIsa LLM API (different base URL)."""
|
||||||
|
url = f"{self.LLM_BASE_URL}{endpoint}"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "OpenClaw-Starter-Kit/1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
request_data = json.dumps(data).encode("utf-8")
|
||||||
|
req = urllib.request.Request(url, data=request_data, headers=headers, method="POST")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=120) as response:
|
||||||
|
return json.loads(response.read().decode("utf-8"))
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
error_body = e.read().decode("utf-8")
|
||||||
|
try:
|
||||||
|
return json.loads(error_body)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return {"success": False, "error": {"code": str(e.code), "message": error_body}}
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
return {"success": False, "error": {"code": "NETWORK_ERROR", "message": str(e.reason)}}
|
||||||
|
|
||||||
|
def llm_complete(self, model: str, prompt: str, **kwargs) -> Dict[str, Any]:
|
||||||
|
"""Generate a completion using the specified LLM model."""
|
||||||
|
data = {
|
||||||
|
"model": model,
|
||||||
|
"messages": [{"role": "user", "content": prompt}],
|
||||||
|
**kwargs
|
||||||
|
}
|
||||||
|
return self._llm_request("/chat/completions", data)
|
||||||
|
|
||||||
|
def llm_chat(self, model: str, messages: list, **kwargs) -> Dict[str, Any]:
|
||||||
|
"""Perform a chat completion with message history."""
|
||||||
|
data = {
|
||||||
|
"model": model,
|
||||||
|
"messages": messages,
|
||||||
|
**kwargs
|
||||||
|
}
|
||||||
|
return self._llm_request("/chat/completions", data)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main CLI entry point."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="OpenClaw Starter Kit - Unified API access for autonomous agents (Powered by AIsa)",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
Examples:
|
||||||
|
%(prog)s twitter user-info --username elonmusk
|
||||||
|
%(prog)s twitter search --query "AI agents" --count 10
|
||||||
|
%(prog)s search web --query "latest AI news"
|
||||||
|
%(prog)s search scholar --query "transformer architecture"
|
||||||
|
%(prog)s llm complete --model gpt-4 --prompt "Explain quantum computing"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
subparsers = parser.add_subparsers(dest="command", help="API category")
|
||||||
|
|
||||||
|
# Twitter commands
|
||||||
|
twitter_parser = subparsers.add_parser("twitter", help="Twitter/X API operations")
|
||||||
|
twitter_sub = twitter_parser.add_subparsers(dest="action", help="Twitter action")
|
||||||
|
|
||||||
|
# twitter user-info
|
||||||
|
user_info = twitter_sub.add_parser("user-info", help="Get user information")
|
||||||
|
user_info.add_argument("--username", "-u", required=True, help="Twitter username")
|
||||||
|
|
||||||
|
# twitter tweets
|
||||||
|
tweets = twitter_sub.add_parser("tweets", help="Get user's last tweets")
|
||||||
|
tweets.add_argument("--username", "-u", required=True, help="Twitter username")
|
||||||
|
|
||||||
|
# twitter search
|
||||||
|
search = twitter_sub.add_parser("search", help="Advanced tweet search")
|
||||||
|
search.add_argument("--query", "-q", required=True, help="Search query")
|
||||||
|
search.add_argument("--type", "-t", choices=["Latest", "Top"], default="Latest", help="Query type")
|
||||||
|
|
||||||
|
# twitter detail
|
||||||
|
detail = twitter_sub.add_parser("detail", help="Get tweets by IDs")
|
||||||
|
detail.add_argument("--tweet-ids", "-t", required=True, help="Tweet IDs (comma-separated)")
|
||||||
|
|
||||||
|
# twitter trends
|
||||||
|
trends = twitter_sub.add_parser("trends", help="Get trending topics")
|
||||||
|
trends.add_argument("--woeid", "-w", type=int, default=1, help="WOEID (1=worldwide)")
|
||||||
|
|
||||||
|
# twitter user-search
|
||||||
|
user_search = twitter_sub.add_parser("user-search", help="Search for users")
|
||||||
|
user_search.add_argument("--keyword", "-k", required=True, help="Search keyword")
|
||||||
|
|
||||||
|
# twitter login (V3)
|
||||||
|
login = twitter_sub.add_parser("login", help="Login to Twitter account (required for posting)")
|
||||||
|
login.add_argument("--username", "-u", required=True, help="Twitter username")
|
||||||
|
login.add_argument("--email", "-e", required=True, help="Account email")
|
||||||
|
login.add_argument("--password", "-p", required=True, help="Account password")
|
||||||
|
login.add_argument("--proxy", required=True, help="Proxy URL (http://user:pass@ip:port)")
|
||||||
|
login.add_argument("--totp", help="TOTP 2FA secret (recommended)")
|
||||||
|
|
||||||
|
# twitter account (check login status)
|
||||||
|
account = twitter_sub.add_parser("account", help="Check logged-in account status")
|
||||||
|
account.add_argument("--username", "-u", required=True, help="Twitter username")
|
||||||
|
|
||||||
|
# twitter post (send tweet)
|
||||||
|
post = twitter_sub.add_parser("post", help="Send a tweet (requires login)")
|
||||||
|
post.add_argument("--username", "-u", required=True, help="Twitter username")
|
||||||
|
post.add_argument("--text", "-t", required=True, help="Tweet text content")
|
||||||
|
post.add_argument("--media", help="Base64 encoded media data")
|
||||||
|
post.add_argument("--media-type", choices=["image/jpeg", "image/png", "image/gif", "video/mp4"], help="Media MIME type")
|
||||||
|
post.add_argument("--community", help="Community ID to post to")
|
||||||
|
|
||||||
|
# twitter like
|
||||||
|
like = twitter_sub.add_parser("like", help="Like a tweet (requires login)")
|
||||||
|
like.add_argument("--username", "-u", required=True, help="Twitter username")
|
||||||
|
like.add_argument("--tweet-id", "-t", required=True, help="Tweet ID to like")
|
||||||
|
|
||||||
|
# twitter retweet
|
||||||
|
retweet = twitter_sub.add_parser("retweet", help="Retweet a tweet (requires login)")
|
||||||
|
retweet.add_argument("--username", "-u", required=True, help="Twitter username")
|
||||||
|
retweet.add_argument("--tweet-id", "-t", required=True, help="Tweet ID to retweet")
|
||||||
|
|
||||||
|
# Search commands
|
||||||
|
search_parser = subparsers.add_parser("search", help="Search API operations")
|
||||||
|
search_sub = search_parser.add_subparsers(dest="action", help="Search type")
|
||||||
|
|
||||||
|
# search web
|
||||||
|
web_search = search_sub.add_parser("web", help="Web search")
|
||||||
|
web_search.add_argument("--query", "-q", required=True, help="Search query")
|
||||||
|
web_search.add_argument("--count", "-c", type=int, default=10, help="Max results (up to 100)")
|
||||||
|
|
||||||
|
# search scholar
|
||||||
|
scholar_search = search_sub.add_parser("scholar", help="Academic paper search")
|
||||||
|
scholar_search.add_argument("--query", "-q", required=True, help="Search query")
|
||||||
|
scholar_search.add_argument("--count", "-c", type=int, default=10, help="Max results (up to 100)")
|
||||||
|
scholar_search.add_argument("--year-from", type=int, help="Publication year lower bound")
|
||||||
|
scholar_search.add_argument("--year-to", type=int, help="Publication year upper bound")
|
||||||
|
|
||||||
|
# search smart
|
||||||
|
smart_search = search_sub.add_parser("smart", help="Smart search (web + academic)")
|
||||||
|
smart_search.add_argument("--query", "-q", required=True, help="Search query")
|
||||||
|
smart_search.add_argument("--count", "-c", type=int, default=10, help="Max results")
|
||||||
|
|
||||||
|
# News commands
|
||||||
|
news_parser = subparsers.add_parser("news", help="Company news by ticker")
|
||||||
|
news_parser.add_argument("--ticker", "-t", required=True, help="Stock ticker (e.g., AAPL)")
|
||||||
|
news_parser.add_argument("--count", "-c", type=int, default=10, help="Number of results")
|
||||||
|
|
||||||
|
# LLM commands
|
||||||
|
llm_parser = subparsers.add_parser("llm", help="LLM API operations")
|
||||||
|
llm_sub = llm_parser.add_subparsers(dest="action", help="LLM action")
|
||||||
|
|
||||||
|
# llm complete
|
||||||
|
complete = llm_sub.add_parser("complete", help="Generate completion")
|
||||||
|
complete.add_argument("--model", "-m", required=True, help="Model name (e.g., gpt-4, claude-3)")
|
||||||
|
complete.add_argument("--prompt", "-p", required=True, help="Prompt text")
|
||||||
|
complete.add_argument("--max-tokens", type=int, help="Maximum tokens to generate")
|
||||||
|
complete.add_argument("--temperature", type=float, help="Sampling temperature")
|
||||||
|
|
||||||
|
# llm chat
|
||||||
|
chat = llm_sub.add_parser("chat", help="Chat completion")
|
||||||
|
chat.add_argument("--model", "-m", required=True, help="Model name")
|
||||||
|
chat.add_argument("--messages", required=True, help="JSON array of messages")
|
||||||
|
chat.add_argument("--max-tokens", type=int, help="Maximum tokens to generate")
|
||||||
|
chat.add_argument("--temperature", type=float, help="Sampling temperature")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.command:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
client = AIsaClient()
|
||||||
|
except ValueError as e:
|
||||||
|
print(json.dumps({"success": False, "error": {"code": "AUTH_ERROR", "message": str(e)}}))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
result = None
|
||||||
|
|
||||||
|
# Execute the appropriate command
|
||||||
|
if args.command == "twitter":
|
||||||
|
if args.action == "user-info":
|
||||||
|
result = client.twitter_user_info(args.username)
|
||||||
|
elif args.action == "tweets":
|
||||||
|
result = client.twitter_user_tweets(args.username)
|
||||||
|
elif args.action == "search":
|
||||||
|
result = client.twitter_search(args.query, args.type)
|
||||||
|
elif args.action == "detail":
|
||||||
|
result = client.twitter_tweet_detail(args.tweet_ids)
|
||||||
|
elif args.action == "trends":
|
||||||
|
result = client.twitter_trends(args.woeid)
|
||||||
|
elif args.action == "user-search":
|
||||||
|
result = client.twitter_user_search(args.keyword)
|
||||||
|
# V3 APIs (require login)
|
||||||
|
elif args.action == "login":
|
||||||
|
result = client.twitter_login(args.username, args.email, args.password, args.proxy, args.totp)
|
||||||
|
elif args.action == "account":
|
||||||
|
result = client.twitter_get_account(args.username)
|
||||||
|
elif args.action == "post":
|
||||||
|
result = client.twitter_send_tweet(args.username, args.text, args.media, args.media_type, args.community)
|
||||||
|
elif args.action == "like":
|
||||||
|
result = client.twitter_like(args.username, args.tweet_id)
|
||||||
|
elif args.action == "retweet":
|
||||||
|
result = client.twitter_retweet(args.username, args.tweet_id)
|
||||||
|
else:
|
||||||
|
twitter_parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
elif args.command == "search":
|
||||||
|
if args.action == "web":
|
||||||
|
result = client.search_web(args.query, args.count)
|
||||||
|
elif args.action == "scholar":
|
||||||
|
year_from = getattr(args, 'year_from', None)
|
||||||
|
year_to = getattr(args, 'year_to', None)
|
||||||
|
result = client.search_scholar(args.query, args.count, year_from, year_to)
|
||||||
|
elif args.action == "smart":
|
||||||
|
result = client.search_smart(args.query, args.count)
|
||||||
|
else:
|
||||||
|
search_parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
elif args.command == "news":
|
||||||
|
result = client.news(args.ticker, args.count)
|
||||||
|
|
||||||
|
elif args.command == "llm":
|
||||||
|
kwargs = {}
|
||||||
|
if hasattr(args, "max_tokens") and args.max_tokens:
|
||||||
|
kwargs["max_tokens"] = args.max_tokens
|
||||||
|
if hasattr(args, "temperature") and args.temperature is not None:
|
||||||
|
kwargs["temperature"] = args.temperature
|
||||||
|
|
||||||
|
if args.action == "complete":
|
||||||
|
result = client.llm_complete(args.model, args.prompt, **kwargs)
|
||||||
|
elif args.action == "chat":
|
||||||
|
try:
|
||||||
|
messages = json.loads(args.messages)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(json.dumps({"success": False, "error": {"code": "INVALID_JSON", "message": "Invalid JSON in --messages"}}))
|
||||||
|
sys.exit(1)
|
||||||
|
result = client.llm_chat(args.model, messages, **kwargs)
|
||||||
|
else:
|
||||||
|
llm_parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Output result
|
||||||
|
if result:
|
||||||
|
# Handle encoding for Windows console
|
||||||
|
output = json.dumps(result, indent=2, ensure_ascii=False)
|
||||||
|
try:
|
||||||
|
print(output)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
# Fallback to ASCII-safe output
|
||||||
|
print(json.dumps(result, indent=2, ensure_ascii=True))
|
||||||
|
sys.exit(0 if result.get("success", True) else 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user