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