@xquik/tweetclaw 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -8
- package/openclaw.plugin.json +9 -4
- package/package.json +8 -2
- package/skills/tweetclaw/SKILL.md +75 -11
- package/src/api-spec.ts +242 -10
- package/src/index.ts +34 -15
- package/src/mpp.ts +44 -0
- package/src/request.ts +1 -1
- package/src/tools/tweetclaw.ts +4 -2
- package/src/types.ts +3 -1
package/README.md
CHANGED
|
@@ -12,14 +12,26 @@ openclaw plugins install @xquik/tweetclaw
|
|
|
12
12
|
|
|
13
13
|
## Configure
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
2. Set it in OpenClaw:
|
|
15
|
+
### Option A: API key (full access, 97 endpoints)
|
|
17
16
|
|
|
18
17
|
```bash
|
|
19
18
|
openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY'
|
|
20
19
|
```
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
Get a key at [dashboard.xquik.com/en/account?tab=x-accounts](https://dashboard.xquik.com/en/account?tab=x-accounts).
|
|
22
|
+
|
|
23
|
+
### Option B: MPP pay-per-use (no account needed, 7 read-only endpoints)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm i mppx viem
|
|
27
|
+
openclaw config set plugins.entries.tweetclaw.config.tempoPrivateKey '0xYOUR_TEMPO_KEY'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
MPP (Machine Payments Protocol) lets agents pay per API call via Tempo (USDC). No account, no API key, no subscription. Get a Tempo wallet at [tempo.xyz](https://tempo.xyz).
|
|
31
|
+
|
|
32
|
+
MPP-eligible endpoints: tweet lookup ($0.0003), tweet search ($0.0003/tweet), user lookup ($0.00036), follower check ($0.002), article lookup ($0.002), media download ($0.0003/media), trends ($0.0009).
|
|
33
|
+
|
|
34
|
+
### Optional settings
|
|
23
35
|
|
|
24
36
|
```bash
|
|
25
37
|
openclaw config set plugins.entries.tweetclaw.config.pollingEnabled true
|
|
@@ -85,36 +97,45 @@ You: "Monitor @elonmusk for new tweets and follower changes"
|
|
|
85
97
|
|
|
86
98
|
## API Coverage
|
|
87
99
|
|
|
88
|
-
|
|
100
|
+
97 endpoints across these categories:
|
|
89
101
|
|
|
90
102
|
| Category | Examples |
|
|
91
103
|
|----------|---------|
|
|
92
104
|
| **Write Actions** | Post tweets, reply, like, retweet, follow, unfollow, DM, update profile & avatar |
|
|
93
105
|
| **Media** | Upload media via URL, download tweet media, get gallery links |
|
|
94
|
-
| **Twitter** | Search tweets, look up users, check follow relationships |
|
|
106
|
+
| **Twitter** | Search tweets, look up users, check follow relationships, get articles |
|
|
95
107
|
| **Composition** | Compose, refine, score tweets; manage drafts; analyze writing styles |
|
|
96
108
|
| **Extraction** | Run extraction jobs (reply-extractor, community-explorer, etc.) |
|
|
97
109
|
| **Draws** | Run giveaway draws on tweets, export results |
|
|
98
110
|
| **Monitoring** | Create monitors, view events, manage webhooks |
|
|
111
|
+
| **Automations** | Create flows, add steps, test runs, inbound webhooks |
|
|
99
112
|
| **Account** | Manage API keys, subscription, connected X accounts |
|
|
100
113
|
| **Trends** | X trending topics, curated radar from multiple sources |
|
|
114
|
+
| **Support** | Create tickets, reply, track status |
|
|
101
115
|
|
|
102
116
|
## Pricing
|
|
103
117
|
|
|
104
|
-
**
|
|
118
|
+
**MPP pay-per-use** (no account needed):
|
|
119
|
+
- 7 read-only X-API endpoints via Tempo (USDC)
|
|
120
|
+
- Tweet/user/article lookup, search, follower check, media download, trends
|
|
121
|
+
|
|
122
|
+
**Free tier** (API key, no subscription needed):
|
|
105
123
|
- Tweet composition, style analysis, drafts
|
|
106
124
|
- Curated trending radar
|
|
107
125
|
- Account management, API keys
|
|
108
126
|
- Integrations management
|
|
127
|
+
- Flow automations (create, test, inbound webhooks)
|
|
128
|
+
- Support tickets
|
|
109
129
|
|
|
110
130
|
**Subscription ($20/month)** for full access:
|
|
111
131
|
- Write actions (post, reply, like, retweet, follow, DM, update profile)
|
|
112
|
-
- Tweet search, user lookup, media download
|
|
132
|
+
- Tweet search, user lookup, article lookup, media download
|
|
113
133
|
- Extractions, giveaway draws
|
|
114
134
|
- Account monitors, events, webhooks
|
|
115
135
|
- X trending topics
|
|
136
|
+
- Flow activation (free: 2 flows, subscriber: 10)
|
|
116
137
|
|
|
117
|
-
When a paid endpoint returns 402, TweetClaw
|
|
138
|
+
When a paid endpoint returns 402, TweetClaw provides a checkout URL (API key mode) or auto-pays via Tempo (MPP mode).
|
|
118
139
|
|
|
119
140
|
## License
|
|
120
141
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "tweetclaw",
|
|
3
3
|
"name": "TweetClaw",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"description": "Post tweets, reply, like, retweet, follow, DM from your chat - full X/Twitter automation powered by Xquik",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
9
9
|
"properties": {
|
|
10
|
-
"apiKey": { "type": "string", "minLength": 1, "description": "Xquik API key (get one at xquik.com/account-
|
|
10
|
+
"apiKey": { "type": "string", "minLength": 1, "description": "Xquik API key (get one at dashboard.xquik.com/en/account?tab=x-accounts). Required for full access." },
|
|
11
|
+
"tempoPrivateKey": { "type": "string", "minLength": 1, "description": "Tempo wallet private key for MPP pay-per-use mode. No account needed. Only 7 read-only X-API endpoints." },
|
|
11
12
|
"baseUrl": { "type": "string", "default": "https://xquik.com" },
|
|
12
13
|
"pollingInterval": { "type": "number", "default": 60, "description": "Event polling interval in seconds" },
|
|
13
14
|
"pollingEnabled": { "type": "boolean", "default": true }
|
|
14
15
|
},
|
|
15
|
-
"
|
|
16
|
+
"anyOf": [
|
|
17
|
+
{ "required": ["apiKey"] },
|
|
18
|
+
{ "required": ["tempoPrivateKey"] }
|
|
19
|
+
]
|
|
16
20
|
},
|
|
17
21
|
"uiHints": {
|
|
18
|
-
"apiKey": { "label": "Xquik API Key", "sensitive": true, "placeholder": "xq_...", "help": "Generate at xquik.com/account-
|
|
22
|
+
"apiKey": { "label": "Xquik API Key", "sensitive": true, "placeholder": "xq_...", "help": "Full access to all 97 endpoints. Generate at dashboard.xquik.com/en/account?tab=x-accounts." },
|
|
23
|
+
"tempoPrivateKey": { "label": "Tempo Private Key (MPP)", "sensitive": true, "placeholder": "0x...", "help": "Pay-per-use mode via Machine Payments Protocol. No account needed. 7 read-only X-API endpoints only. Requires mppx and viem packages." },
|
|
19
24
|
"baseUrl": { "label": "API Base URL", "placeholder": "https://xquik.com", "advanced": true, "help": "Only change if using a self-hosted Xquik instance." },
|
|
20
25
|
"pollingInterval": { "label": "Event Poll Interval (seconds)", "advanced": true, "help": "How often to check for new monitor events. Default: 60 seconds." },
|
|
21
26
|
"pollingEnabled": { "label": "Enable Event Notifications", "advanced": true, "help": "Deliver monitor alerts and extraction completions to your chat." }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xquik/tweetclaw",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Post tweets, reply, like, retweet, follow, DM & more from OpenClaw - full X/Twitter automation via Xquik",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -45,7 +45,13 @@
|
|
|
45
45
|
"check:all": "npm run typecheck && npm run lint && npm run cpd && npm run knip && npm run check-suppressions && npm run check-em-dash && npm run test:coverage"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"
|
|
48
|
+
"mppx": ">=0.1.0",
|
|
49
|
+
"openclaw": ">=2026.2.0",
|
|
50
|
+
"viem": ">=2.0.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"mppx": { "optional": true },
|
|
54
|
+
"viem": { "optional": true }
|
|
49
55
|
},
|
|
50
56
|
"devDependencies": {
|
|
51
57
|
"@eslint/js": "^10.0.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tweetclaw
|
|
3
|
-
description: "OpenClaw plugin for X/Twitter automation. Post tweets, reply, like, retweet, follow, DM, search, extract data, run giveaways, monitor accounts via Xquik.
|
|
3
|
+
description: "OpenClaw plugin for X/Twitter automation. Post tweets, reply, like, retweet, follow, DM, search, extract data, run giveaways, monitor accounts, automate flows via Xquik. 97 endpoints, 2 tools (explore + tweetclaw), 2 commands (/xstatus, /xtrends), background event poller."
|
|
4
4
|
homepage: https://xquik.com
|
|
5
5
|
read_when:
|
|
6
6
|
- Posting, replying, liking, retweeting, or following on X/Twitter
|
|
@@ -11,7 +11,7 @@ read_when:
|
|
|
11
11
|
- Extracting bulk data from X/Twitter (followers, replies, communities)
|
|
12
12
|
- Downloading tweet media or uploading images
|
|
13
13
|
- Sending DMs or updating X/Twitter profile
|
|
14
|
-
metadata: {"openclaw":{"emoji":"🐦","primaryEnv":"XQUIK_API_KEY","requires":{"env":["XQUIK_API_KEY"]}}}
|
|
14
|
+
metadata: {"openclaw":{"emoji":"🐦","primaryEnv":"XQUIK_API_KEY","requires":{"env":["XQUIK_API_KEY"]},"tags":["twitter","x","automation","social-media","tweets","scraping","giveaway","monitoring","rest-api"]}}
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
# TweetClaw
|
|
@@ -22,17 +22,52 @@ OpenClaw plugin for X/Twitter automation powered by Xquik. Install via:
|
|
|
22
22
|
openclaw plugins install @xquik/tweetclaw
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
## When to Use
|
|
26
|
+
|
|
27
|
+
Use TweetClaw when the user wants to:
|
|
28
|
+
|
|
29
|
+
- Post tweets, reply to tweets, or delete tweets
|
|
30
|
+
- Like, retweet, or follow/unfollow users
|
|
31
|
+
- Send DMs on X/Twitter
|
|
32
|
+
- Update their X profile, avatar, or banner
|
|
33
|
+
- Upload media and tweet with images
|
|
34
|
+
- Search tweets or look up user profiles
|
|
35
|
+
- Extract bulk data (followers, replies, communities, spaces)
|
|
36
|
+
- Run giveaway draws from tweet replies
|
|
37
|
+
- Monitor X accounts for new activity
|
|
38
|
+
- Compose algorithm-optimized tweets
|
|
39
|
+
- Analyze a user's writing style
|
|
40
|
+
- Check trending topics on X
|
|
41
|
+
- Download tweet media (images, videos, GIFs)
|
|
42
|
+
- Set up Telegram alerts for monitor events
|
|
43
|
+
- Create and manage automation flows (triggers, steps, test runs)
|
|
44
|
+
- Open and manage support tickets
|
|
45
|
+
- Read X Articles (long-form posts)
|
|
46
|
+
|
|
47
|
+
Do NOT use TweetClaw for browsing X in a browser, analytics dashboards, scheduling future posts, or managing X ads.
|
|
48
|
+
|
|
25
49
|
## Configuration
|
|
26
50
|
|
|
27
|
-
|
|
51
|
+
### API key mode (full access)
|
|
28
52
|
|
|
29
53
|
```bash
|
|
30
54
|
openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY'
|
|
31
55
|
```
|
|
32
56
|
|
|
57
|
+
Get a key at [dashboard.xquik.com/en/account?tab=x-accounts](https://dashboard.xquik.com/en/account?tab=x-accounts).
|
|
58
|
+
|
|
59
|
+
### MPP mode (no account, pay-per-use via Tempo/USDC)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm i mppx viem
|
|
63
|
+
openclaw config set plugins.entries.tweetclaw.config.tempoPrivateKey '0xYOUR_KEY'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
MPP gives agents access to 7 read-only X-API endpoints without any account or subscription. The mppx SDK handles HTTP 402 payment challenges automatically.
|
|
67
|
+
|
|
33
68
|
## Tools
|
|
34
69
|
|
|
35
|
-
TweetClaw registers 2 tools that cover the entire Xquik API (
|
|
70
|
+
TweetClaw registers 2 tools that cover the entire Xquik API (97 endpoints):
|
|
36
71
|
|
|
37
72
|
### `explore` (free, no network)
|
|
38
73
|
|
|
@@ -177,6 +212,27 @@ You: "What's trending on X right now?"
|
|
|
177
212
|
Agent uses tweetclaw -> returns curated trending topics from 7 sources
|
|
178
213
|
```
|
|
179
214
|
|
|
215
|
+
### Create an automation flow (free)
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
You: "Create an automation that sends a DM when I get a new follower"
|
|
219
|
+
Agent uses tweetclaw -> creates flow with monitor_event trigger, adds send_dm step, tests it
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Read an X Article
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
You: "Get the full article from this tweet: https://x.com/user/status/123"
|
|
226
|
+
Agent uses tweetclaw -> calls /api/v1/x/articles/:tweetId, returns title, body, images
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Open a support ticket (free)
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
You: "Open a support ticket about my monitor not working"
|
|
233
|
+
Agent uses tweetclaw -> creates ticket with subject and description
|
|
234
|
+
```
|
|
235
|
+
|
|
180
236
|
## API Categories
|
|
181
237
|
|
|
182
238
|
| Category | Examples | Free |
|
|
@@ -189,20 +245,28 @@ Agent uses tweetclaw -> returns curated trending topics from 7 sources
|
|
|
189
245
|
| Extraction | Reply/follower/community extraction (20 tools) | No |
|
|
190
246
|
| Draws | Giveaway draws, export results | No |
|
|
191
247
|
| Monitoring | Create monitors, view events, webhooks | No |
|
|
248
|
+
| Automations | Create flows, add steps, test runs, inbound webhooks | Yes |
|
|
192
249
|
| Account | API keys, subscription, connected X accounts | Yes |
|
|
193
250
|
| Trends | X trending topics, curated radar from 7 sources | Mixed |
|
|
251
|
+
| Support | Create tickets, reply, track status | Yes |
|
|
194
252
|
|
|
195
253
|
## Pricing
|
|
196
254
|
|
|
197
|
-
|
|
255
|
+
MPP pay-per-use (no account): 7 read-only X-API endpoints via Tempo (USDC) - tweet lookup ($0.0003), tweet search ($0.0003/tweet), user lookup ($0.00036), follower check ($0.002), article ($0.002), media download ($0.0003/media), trends ($0.0009).
|
|
256
|
+
|
|
257
|
+
Free tier (API key, no subscription): tweet composition, style analysis, drafts, curated radar, account management, integrations, automations (create/test), support tickets.
|
|
198
258
|
|
|
199
|
-
Subscription ($20/month): write actions, search, media, extractions, draws, monitors, X trending.
|
|
259
|
+
Subscription ($20/month): write actions, search, article lookup, media, extractions, draws, monitors, X trending, flow activation (2 free, 10 subscriber).
|
|
200
260
|
|
|
201
261
|
When a paid endpoint returns 402, TweetClaw provides a checkout URL.
|
|
202
262
|
|
|
203
|
-
##
|
|
263
|
+
## Tips
|
|
204
264
|
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
265
|
+
- Use `explore` first to discover endpoints before calling `tweetclaw` - saves tokens and avoids guessing
|
|
266
|
+
- Free endpoints (compose, styles, radar, drafts) work without a subscription - always try them first
|
|
267
|
+
- Never combine free and paid API calls in the same `Promise.all` - a 402 on one call kills all results
|
|
268
|
+
- For write actions (post, like, follow, DM), always pass the `account` parameter with the X username
|
|
269
|
+
- Follow/unfollow/DM require a numeric user ID - look up the user first via `/api/v1/x/users/:username`
|
|
270
|
+
- On 402 errors, call `POST /api/v1/subscribe` to get a checkout URL instead of giving up
|
|
271
|
+
- Use `/xstatus` to quickly check subscription and usage without invoking the AI agent
|
|
272
|
+
- The compose workflow (compose/refine/score) is free and helps draft high-engagement tweets
|
package/src/api-spec.ts
CHANGED
|
@@ -21,13 +21,16 @@ const EXTRACTION_SEARCH_PARAMS: readonly EndpointParameter[] = [
|
|
|
21
21
|
{ description: 'Language code filter, e.g. en, tr (tweet_search_extractor)', in: 'body', name: 'language', required: false, type: 'string' },
|
|
22
22
|
{ description: 'Start date YYYY-MM-DD (tweet_search_extractor)', in: 'body', name: 'sinceDate', required: false, type: 'string' },
|
|
23
23
|
{ description: 'End date YYYY-MM-DD (tweet_search_extractor)', in: 'body', name: 'untilDate', required: false, type: 'string' },
|
|
24
|
-
{ description: 'Filter by media type: images, videos,
|
|
24
|
+
{ description: 'Filter by media type: images, videos, gifs, media (tweet_search_extractor)', in: 'body', name: 'mediaType', required: false, type: 'string' },
|
|
25
25
|
{ description: 'Minimum likes threshold (tweet_search_extractor)', in: 'body', name: 'minFaves', required: false, type: 'number' },
|
|
26
26
|
{ description: 'Minimum retweets threshold (tweet_search_extractor)', in: 'body', name: 'minRetweets', required: false, type: 'number' },
|
|
27
27
|
{ description: 'Minimum replies threshold (tweet_search_extractor)', in: 'body', name: 'minReplies', required: false, type: 'number' },
|
|
28
28
|
{ description: 'Only verified authors (tweet_search_extractor)', in: 'body', name: 'verifiedOnly', required: false, type: 'boolean' },
|
|
29
|
-
{ description: '
|
|
30
|
-
{ description: '
|
|
29
|
+
{ description: 'Control reply inclusion (tweet_search_extractor): include, exclude, only', in: 'body', name: 'replies', required: false, type: 'string' },
|
|
30
|
+
{ description: 'Control retweet inclusion (tweet_search_extractor): include, exclude, only', in: 'body', name: 'retweets', required: false, type: 'string' },
|
|
31
|
+
{ description: 'Exact phrase match (tweet_search_extractor)', in: 'body', name: 'exactPhrase', required: false, type: 'string' },
|
|
32
|
+
{ description: 'Comma-separated words to exclude (tweet_search_extractor)', in: 'body', name: 'excludeWords', required: false, type: 'string' },
|
|
33
|
+
{ description: 'Raw X search operators (tweet_search_extractor)', in: 'body', name: 'advancedQuery', required: false, type: 'string' },
|
|
31
34
|
];
|
|
32
35
|
|
|
33
36
|
const PARAM_STYLE_USERNAME: EndpointParameter =
|
|
@@ -84,8 +87,16 @@ const PARAM_MEDIA_URL: EndpointParameter =
|
|
|
84
87
|
{ description: 'URL to download media from (alternative to file, HTTPS only)', in: 'body', name: 'url', required: false, type: 'string' };
|
|
85
88
|
|
|
86
89
|
const RESPONSE_COMMUNITY_ACTION = '{ communityId, communityName, success: true }';
|
|
90
|
+
const CATEGORY_AUTOMATIONS = 'automations';
|
|
91
|
+
const CATEGORY_SUPPORT = 'support';
|
|
87
92
|
const CATEGORY_X_WRITE = 'x-write';
|
|
88
93
|
|
|
94
|
+
const PARAM_AUTOMATION_SLUG: EndpointParameter =
|
|
95
|
+
{ description: 'Flow slug', in: 'path', name: 'slug', required: true, type: 'string' };
|
|
96
|
+
|
|
97
|
+
const PARAM_TICKET_ID: EndpointParameter =
|
|
98
|
+
{ description: 'Ticket public ID', in: 'path', name: 'id', required: true, type: 'string' };
|
|
99
|
+
|
|
89
100
|
const API_SPEC: readonly EndpointInfo[] = [
|
|
90
101
|
// --- Account ---
|
|
91
102
|
{
|
|
@@ -305,13 +316,14 @@ const API_SPEC: readonly EndpointInfo[] = [
|
|
|
305
316
|
method: 'GET',
|
|
306
317
|
parameters: [
|
|
307
318
|
{ description: 'Filter by category (general, tech, dev, etc.)', in: 'query', name: 'category', required: false, type: 'string' },
|
|
308
|
-
{ description: '
|
|
309
|
-
{ description: 'Lookback window in hours', in: 'query', name: 'hours', required: false, type: 'number' },
|
|
310
|
-
{ description: 'Region filter (
|
|
311
|
-
{ description: 'Source filter (
|
|
319
|
+
{ description: 'Max items to return (1-100, default 50)', in: 'query', name: 'limit', required: false, type: 'number' },
|
|
320
|
+
{ description: 'Lookback window in hours (1-72, default 6)', in: 'query', name: 'hours', required: false, type: 'number' },
|
|
321
|
+
{ description: 'Region filter (US, GB, TR, ES, DE, FR, JP, IN, BR, CA, MX, global)', in: 'query', name: 'region', required: false, type: 'string' },
|
|
322
|
+
{ description: 'Source filter (github, google_trends, hacker_news, polymarket, reddit, trustmrr, wikipedia)', in: 'query', name: 'source', required: false, type: 'string' },
|
|
323
|
+
{ description: DESCRIPTION_PAGINATION_CURSOR, in: 'query', name: 'after', required: false, type: 'string' },
|
|
312
324
|
],
|
|
313
325
|
path: '/api/v1/radar',
|
|
314
|
-
responseShape: '{ items: [{ title, url?, score, category, source, region, publishedAt }],
|
|
326
|
+
responseShape: '{ items: [{ title, url?, score, category, source, region, publishedAt }], hasMore, nextCursor? }',
|
|
315
327
|
summary: 'Get trending topics from curated radar sources',
|
|
316
328
|
},
|
|
317
329
|
|
|
@@ -374,10 +386,13 @@ const API_SPEC: readonly EndpointInfo[] = [
|
|
|
374
386
|
free: false,
|
|
375
387
|
method: 'POST',
|
|
376
388
|
parameters: [
|
|
377
|
-
{ description: 'Extraction tool type (
|
|
389
|
+
{ description: 'Extraction tool type (reply_extractor, community_extractor, etc.)', in: 'body', name: 'toolType', required: true, type: 'string' },
|
|
378
390
|
{ description: 'Target X username', in: 'body', name: 'targetUsername', required: false, type: 'string' },
|
|
379
391
|
{ description: 'Target tweet ID', in: 'body', name: 'targetTweetId', required: false, type: 'string' },
|
|
380
|
-
{ description: 'Search query for
|
|
392
|
+
{ description: 'Search query for search tools', in: 'body', name: 'searchQuery', required: false, type: 'string' },
|
|
393
|
+
{ description: 'Community ID for community tools', in: 'body', name: 'targetCommunityId', required: false, type: 'string' },
|
|
394
|
+
{ description: 'List ID for list tools', in: 'body', name: 'targetListId', required: false, type: 'string' },
|
|
395
|
+
{ description: 'Space ID for space_explorer', in: 'body', name: 'targetSpaceId', required: false, type: 'string' },
|
|
381
396
|
{ description: 'Max results to return', in: 'body', name: 'resultsLimit', required: false, type: 'number' },
|
|
382
397
|
...EXTRACTION_SEARCH_PARAMS,
|
|
383
398
|
],
|
|
@@ -393,6 +408,10 @@ const API_SPEC: readonly EndpointInfo[] = [
|
|
|
393
408
|
{ description: 'Extraction tool type', in: 'body', name: 'toolType', required: true, type: 'string' },
|
|
394
409
|
{ description: 'Target X username', in: 'body', name: 'targetUsername', required: false, type: 'string' },
|
|
395
410
|
{ description: 'Target tweet ID', in: 'body', name: 'targetTweetId', required: false, type: 'string' },
|
|
411
|
+
{ description: 'Search query for search tools', in: 'body', name: 'searchQuery', required: false, type: 'string' },
|
|
412
|
+
{ description: 'Community ID for community tools', in: 'body', name: 'targetCommunityId', required: false, type: 'string' },
|
|
413
|
+
{ description: 'List ID for list tools', in: 'body', name: 'targetListId', required: false, type: 'string' },
|
|
414
|
+
{ description: 'Space ID for space_explorer', in: 'body', name: 'targetSpaceId', required: false, type: 'string' },
|
|
396
415
|
{ description: 'Max results to return', in: 'body', name: 'resultsLimit', required: false, type: 'number' },
|
|
397
416
|
...EXTRACTION_SEARCH_PARAMS,
|
|
398
417
|
],
|
|
@@ -569,6 +588,7 @@ const API_SPEC: readonly EndpointInfo[] = [
|
|
|
569
588
|
parameters: [
|
|
570
589
|
{ description: 'Tweet ID to look up', in: 'path', name: 'tweetId', required: true, type: 'string' },
|
|
571
590
|
],
|
|
591
|
+
mpp: { intent: 'charge', price: '$0.0003/call' },
|
|
572
592
|
path: '/api/v1/x/tweets/:tweetId',
|
|
573
593
|
responseShape: '{ tweet: { id, text, likeCount, retweetCount, replyCount, viewCount, ... }, author? }',
|
|
574
594
|
summary: 'Look up a single tweet with engagement metrics',
|
|
@@ -581,6 +601,7 @@ const API_SPEC: readonly EndpointInfo[] = [
|
|
|
581
601
|
{ description: 'Search query (X search syntax)', in: 'query', name: 'q', required: true, type: 'string' },
|
|
582
602
|
{ description: 'Max tweets to return (default 20, max 200)', in: 'query', name: 'limit', required: false, type: 'number' },
|
|
583
603
|
],
|
|
604
|
+
mpp: { intent: 'session', price: '$0.0003/tweet' },
|
|
584
605
|
path: '/api/v1/x/tweets/search',
|
|
585
606
|
responseShape: '{ tweets: [{ id, text, author?, likeCount?, retweetCount?, media? }], total }',
|
|
586
607
|
summary: 'Search tweets by query with optional limit for pagination',
|
|
@@ -592,6 +613,7 @@ const API_SPEC: readonly EndpointInfo[] = [
|
|
|
592
613
|
parameters: [
|
|
593
614
|
{ description: 'X username to look up', in: 'path', name: 'username', required: true, type: 'string' },
|
|
594
615
|
],
|
|
616
|
+
mpp: { intent: 'charge', price: '$0.00036/call' },
|
|
595
617
|
path: '/api/v1/x/users/:username',
|
|
596
618
|
responseShape: '{ id, username, name, followers?, following?, verified?, description? }',
|
|
597
619
|
summary: 'Get X user profile by username',
|
|
@@ -604,10 +626,23 @@ const API_SPEC: readonly EndpointInfo[] = [
|
|
|
604
626
|
{ description: 'Source username', in: 'query', name: 'source', required: true, type: 'string' },
|
|
605
627
|
{ description: 'Target username', in: 'query', name: 'target', required: true, type: 'string' },
|
|
606
628
|
],
|
|
629
|
+
mpp: { intent: 'charge', price: '$0.002/call' },
|
|
607
630
|
path: '/api/v1/x/followers/check',
|
|
608
631
|
responseShape: '{ isFollowing, isFollowedBy, sourceUsername, targetUsername }',
|
|
609
632
|
summary: 'Check follow relationship between two users',
|
|
610
633
|
},
|
|
634
|
+
{
|
|
635
|
+
category: 'twitter',
|
|
636
|
+
free: false,
|
|
637
|
+
method: 'GET',
|
|
638
|
+
parameters: [
|
|
639
|
+
{ description: 'Tweet ID of the X Article', in: 'path', name: 'tweetId', required: true, type: 'string' },
|
|
640
|
+
],
|
|
641
|
+
mpp: { intent: 'charge', price: '$0.002/call' },
|
|
642
|
+
path: '/api/v1/x/articles/:tweetId',
|
|
643
|
+
responseShape: '{ article: { title, previewText, coverImageUrl, contents, createdAt, likeCount, replyCount, quoteCount, viewCount }, author? }',
|
|
644
|
+
summary: 'Get full content of an X Article (long-form post) by tweet ID',
|
|
645
|
+
},
|
|
611
646
|
|
|
612
647
|
// --- Media ---
|
|
613
648
|
{
|
|
@@ -618,6 +653,7 @@ const API_SPEC: readonly EndpointInfo[] = [
|
|
|
618
653
|
{ description: 'Tweet URL or ID (single tweet)', in: 'body', name: 'tweetInput', required: false, type: 'string' },
|
|
619
654
|
{ description: 'Array of tweet URLs or IDs (bulk, max 50)', in: 'body', name: 'tweetIds', required: false, type: 'string[]' },
|
|
620
655
|
],
|
|
656
|
+
mpp: { intent: 'session', price: '$0.0003/media' },
|
|
621
657
|
path: '/api/v1/x/media/download',
|
|
622
658
|
responseShape: 'Single: { tweetId, galleryUrl, cacheHit }. Bulk: { galleryUrl, totalTweets, totalMedia }',
|
|
623
659
|
summary: 'Download media from tweets. Single tweetInput or bulk tweetIds. Returns gallery URL.',
|
|
@@ -632,6 +668,7 @@ const API_SPEC: readonly EndpointInfo[] = [
|
|
|
632
668
|
{ description: 'WOEID location ID (1 for worldwide)', in: 'query', name: 'woeid', required: false, type: 'number' },
|
|
633
669
|
{ description: 'Max number of trends', in: 'query', name: 'count', required: false, type: 'number' },
|
|
634
670
|
],
|
|
671
|
+
mpp: { intent: 'charge', price: '$0.0009/call' },
|
|
635
672
|
path: '/api/v1/trends',
|
|
636
673
|
responseShape: '{ trends: [{ name, query?, description?, rank? }], total, woeid }',
|
|
637
674
|
summary: 'Get current trending topics on X',
|
|
@@ -1019,6 +1056,201 @@ const API_SPEC: readonly EndpointInfo[] = [
|
|
|
1019
1056
|
responseShape: RESPONSE_COMMUNITY_ACTION,
|
|
1020
1057
|
summary: 'Leave community',
|
|
1021
1058
|
},
|
|
1059
|
+
|
|
1060
|
+
// --- Automations ---
|
|
1061
|
+
{
|
|
1062
|
+
category: CATEGORY_AUTOMATIONS,
|
|
1063
|
+
free: true,
|
|
1064
|
+
method: 'GET',
|
|
1065
|
+
path: '/api/v1/automations',
|
|
1066
|
+
responseShape: '{ items: [{ id, name, slug, triggerType, triggerConfig, isActive, runCount, lastRunAt, createdAt, updatedAt }] }',
|
|
1067
|
+
summary: 'List all automation flows',
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
category: CATEGORY_AUTOMATIONS,
|
|
1071
|
+
free: true,
|
|
1072
|
+
method: 'POST',
|
|
1073
|
+
parameters: [
|
|
1074
|
+
{ description: 'Flow name', in: 'body', name: 'name', required: true, type: 'string' },
|
|
1075
|
+
{ description: 'Trigger type: monitor_event, schedule, search, webhook_inbound', in: 'body', name: 'triggerType', required: true, type: 'string' },
|
|
1076
|
+
{ description: 'Trigger-specific configuration', in: 'body', name: 'triggerConfig', required: true, type: 'object' },
|
|
1077
|
+
{ description: 'Template slug to scaffold from', in: 'body', name: 'templateSlug', required: false, type: 'string' },
|
|
1078
|
+
],
|
|
1079
|
+
path: '/api/v1/automations',
|
|
1080
|
+
responseShape: '{ id, name, slug, triggerType, triggerConfig, isActive, createdAt, updatedAt }',
|
|
1081
|
+
summary: 'Create a new automation flow',
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
category: CATEGORY_AUTOMATIONS,
|
|
1085
|
+
free: true,
|
|
1086
|
+
method: 'GET',
|
|
1087
|
+
parameters: [PARAM_AUTOMATION_SLUG],
|
|
1088
|
+
path: '/api/v1/automations/:slug',
|
|
1089
|
+
responseShape: '{ id, name, slug, triggerType, triggerConfig, isActive, steps, recentRuns, createdAt, updatedAt }',
|
|
1090
|
+
summary: 'Get flow details with steps and recent runs',
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
category: CATEGORY_AUTOMATIONS,
|
|
1094
|
+
free: true,
|
|
1095
|
+
method: 'PATCH',
|
|
1096
|
+
parameters: [
|
|
1097
|
+
PARAM_AUTOMATION_SLUG,
|
|
1098
|
+
{ description: 'Current updatedAt for optimistic concurrency', in: 'body', name: 'expectedUpdatedAt', required: true, type: 'string' },
|
|
1099
|
+
{ description: 'Updated flow name', in: 'body', name: 'name', required: false, type: 'string' },
|
|
1100
|
+
{ description: 'Updated trigger type', in: 'body', name: 'triggerType', required: false, type: 'string' },
|
|
1101
|
+
{ description: 'Updated trigger config', in: 'body', name: 'triggerConfig', required: false, type: 'object' },
|
|
1102
|
+
{ description: 'Activate or deactivate', in: 'body', name: 'isActive', required: false, type: 'boolean' },
|
|
1103
|
+
],
|
|
1104
|
+
path: '/api/v1/automations/:slug',
|
|
1105
|
+
responseShape: '{ id, name, slug, triggerType, triggerConfig, isActive, createdAt, updatedAt }',
|
|
1106
|
+
summary: 'Update flow name, trigger, or active status',
|
|
1107
|
+
},
|
|
1108
|
+
{
|
|
1109
|
+
category: CATEGORY_AUTOMATIONS,
|
|
1110
|
+
free: true,
|
|
1111
|
+
method: 'DELETE',
|
|
1112
|
+
parameters: [PARAM_AUTOMATION_SLUG],
|
|
1113
|
+
path: '/api/v1/automations/:slug',
|
|
1114
|
+
responseShape: RESPONSE_SUCCESS,
|
|
1115
|
+
summary: 'Delete a flow and all its steps',
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
category: CATEGORY_AUTOMATIONS,
|
|
1119
|
+
free: true,
|
|
1120
|
+
method: 'POST',
|
|
1121
|
+
parameters: [
|
|
1122
|
+
PARAM_AUTOMATION_SLUG,
|
|
1123
|
+
{ description: 'Step type: action, condition, extraction', in: 'body', name: 'stepType', required: true, type: 'string' },
|
|
1124
|
+
{ description: 'Branch: main, if_true, if_false', in: 'body', name: 'branch', required: true, type: 'string' },
|
|
1125
|
+
{ description: 'Step-specific configuration', in: 'body', name: 'config', required: true, type: 'object' },
|
|
1126
|
+
{ description: 'Order position in branch', in: 'body', name: 'position', required: false, type: 'number' },
|
|
1127
|
+
{ description: 'Parent step ID for condition branches', in: 'body', name: 'parentStepId', required: false, type: 'string' },
|
|
1128
|
+
{ description: 'Action type: create_tweet, follow, like, reply_tweet, retweet, send_dm, send_email, send_telegram, unfollow', in: 'body', name: 'actionType', required: false, type: 'string' },
|
|
1129
|
+
{ description: 'Extraction tool type', in: 'body', name: 'extractionType', required: false, type: 'string' },
|
|
1130
|
+
{ description: 'Variable name for extraction output', in: 'body', name: 'outputName', required: false, type: 'string' },
|
|
1131
|
+
],
|
|
1132
|
+
path: '/api/v1/automations/:slug/steps',
|
|
1133
|
+
responseShape: '{ id, flowId, stepType, actionType, extractionType, branch, config, position, createdAt }',
|
|
1134
|
+
summary: 'Add an action, condition, or extraction step to a flow',
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
category: CATEGORY_AUTOMATIONS,
|
|
1138
|
+
free: true,
|
|
1139
|
+
method: 'PATCH',
|
|
1140
|
+
parameters: [
|
|
1141
|
+
PARAM_AUTOMATION_SLUG,
|
|
1142
|
+
{ description: 'Step ID to update', in: 'body', name: 'stepId', required: true, type: 'string' },
|
|
1143
|
+
{ description: 'Updated step config', in: 'body', name: 'config', required: false, type: 'object' },
|
|
1144
|
+
{ description: 'Updated step type', in: 'body', name: 'stepType', required: false, type: 'string' },
|
|
1145
|
+
{ description: 'Updated branch', in: 'body', name: 'branch', required: false, type: 'string' },
|
|
1146
|
+
{ description: 'Updated position', in: 'body', name: 'position', required: false, type: 'number' },
|
|
1147
|
+
{ description: 'Updated action type', in: 'body', name: 'actionType', required: false, type: 'string' },
|
|
1148
|
+
{ description: 'Updated extraction type', in: 'body', name: 'extractionType', required: false, type: 'string' },
|
|
1149
|
+
{ description: 'Updated output variable name', in: 'body', name: 'outputName', required: false, type: 'string' },
|
|
1150
|
+
],
|
|
1151
|
+
path: '/api/v1/automations/:slug/steps',
|
|
1152
|
+
responseShape: '{ id, flowId, stepType, actionType, extractionType, branch, config, position, createdAt }',
|
|
1153
|
+
summary: 'Update a step configuration or position',
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
category: CATEGORY_AUTOMATIONS,
|
|
1157
|
+
free: true,
|
|
1158
|
+
method: 'DELETE',
|
|
1159
|
+
parameters: [
|
|
1160
|
+
PARAM_AUTOMATION_SLUG,
|
|
1161
|
+
{ description: 'Step ID to delete', in: 'body', name: 'stepId', required: true, type: 'string' },
|
|
1162
|
+
],
|
|
1163
|
+
path: '/api/v1/automations/:slug/steps',
|
|
1164
|
+
responseShape: RESPONSE_SUCCESS,
|
|
1165
|
+
summary: 'Remove a step from a flow',
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
category: CATEGORY_AUTOMATIONS,
|
|
1169
|
+
free: true,
|
|
1170
|
+
method: 'PATCH',
|
|
1171
|
+
parameters: [
|
|
1172
|
+
PARAM_AUTOMATION_SLUG,
|
|
1173
|
+
{ description: 'Array of { stepId, positionX, positionY } (max 10)', in: 'body', name: 'positions', required: true, type: 'array' },
|
|
1174
|
+
],
|
|
1175
|
+
path: '/api/v1/automations/:slug/steps/positions',
|
|
1176
|
+
responseShape: RESPONSE_SUCCESS,
|
|
1177
|
+
summary: 'Batch update canvas positions for flow steps',
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
category: CATEGORY_AUTOMATIONS,
|
|
1181
|
+
free: true,
|
|
1182
|
+
method: 'POST',
|
|
1183
|
+
parameters: [PARAM_AUTOMATION_SLUG],
|
|
1184
|
+
path: '/api/v1/automations/:slug/test',
|
|
1185
|
+
responseShape: '{ status, result, runId, error? }',
|
|
1186
|
+
summary: 'Test run a flow with synthetic trigger data',
|
|
1187
|
+
},
|
|
1188
|
+
{
|
|
1189
|
+
category: CATEGORY_AUTOMATIONS,
|
|
1190
|
+
free: true,
|
|
1191
|
+
method: 'POST',
|
|
1192
|
+
parameters: [
|
|
1193
|
+
{ description: 'Inbound webhook token', in: 'path', name: 'token', required: true, type: 'string' },
|
|
1194
|
+
],
|
|
1195
|
+
path: '/api/v1/webhooks/inbound/:token',
|
|
1196
|
+
responseShape: '{ accepted: true, flowId }',
|
|
1197
|
+
summary: 'Trigger a flow via inbound webhook (no auth required, token acts as auth)',
|
|
1198
|
+
},
|
|
1199
|
+
|
|
1200
|
+
// --- Support ---
|
|
1201
|
+
{
|
|
1202
|
+
category: CATEGORY_SUPPORT,
|
|
1203
|
+
free: true,
|
|
1204
|
+
method: 'POST',
|
|
1205
|
+
parameters: [
|
|
1206
|
+
{ description: 'Ticket subject (1-500 chars)', in: 'body', name: 'subject', required: true, type: 'string' },
|
|
1207
|
+
{ description: 'Initial message (1-10000 chars)', in: 'body', name: 'body', required: true, type: 'string' },
|
|
1208
|
+
],
|
|
1209
|
+
path: '/api/v1/support/tickets',
|
|
1210
|
+
responseShape: '{ publicId }',
|
|
1211
|
+
summary: 'Open a new support ticket',
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
category: CATEGORY_SUPPORT,
|
|
1215
|
+
free: true,
|
|
1216
|
+
method: 'GET',
|
|
1217
|
+
path: '/api/v1/support/tickets',
|
|
1218
|
+
responseShape: '{ tickets: [{ publicId, subject, status, messageCount, createdAt, updatedAt }] }',
|
|
1219
|
+
summary: 'List your support tickets',
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
category: CATEGORY_SUPPORT,
|
|
1223
|
+
free: true,
|
|
1224
|
+
method: 'GET',
|
|
1225
|
+
parameters: [PARAM_TICKET_ID],
|
|
1226
|
+
path: '/api/v1/support/tickets/:id',
|
|
1227
|
+
responseShape: '{ publicId, subject, status, messages: [{ body, sender, createdAt }], createdAt, updatedAt }',
|
|
1228
|
+
summary: 'Get a ticket with message history',
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
category: CATEGORY_SUPPORT,
|
|
1232
|
+
free: true,
|
|
1233
|
+
method: 'PATCH',
|
|
1234
|
+
parameters: [
|
|
1235
|
+
PARAM_TICKET_ID,
|
|
1236
|
+
{ description: 'New status: open, resolved, closed', in: 'body', name: 'status', required: true, type: 'string' },
|
|
1237
|
+
],
|
|
1238
|
+
path: '/api/v1/support/tickets/:id',
|
|
1239
|
+
responseShape: '{ publicId, status }',
|
|
1240
|
+
summary: 'Update ticket status',
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
category: CATEGORY_SUPPORT,
|
|
1244
|
+
free: true,
|
|
1245
|
+
method: 'POST',
|
|
1246
|
+
parameters: [
|
|
1247
|
+
PARAM_TICKET_ID,
|
|
1248
|
+
{ description: 'Message content (1-10000 chars)', in: 'body', name: 'body', required: true, type: 'string' },
|
|
1249
|
+
],
|
|
1250
|
+
path: '/api/v1/support/tickets/:id/messages',
|
|
1251
|
+
responseShape: '{ publicId }',
|
|
1252
|
+
summary: 'Reply to a support ticket',
|
|
1253
|
+
},
|
|
1022
1254
|
] as const;
|
|
1023
1255
|
|
|
1024
1256
|
export { API_SPEC };
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { handleXStatus } from './commands/xstatus.js';
|
|
2
2
|
import { handleXTrends } from './commands/xtrends.js';
|
|
3
|
+
import { initMpp } from './mpp.js';
|
|
3
4
|
import { createProxiedRequest } from './request.js';
|
|
4
5
|
import { createEventPoller } from './services/event-poller.js';
|
|
5
6
|
import { handleExplore, SEARCH_DESCRIPTION } from './tools/explore.js';
|
|
@@ -16,7 +17,8 @@ function isPollerEvent(value: unknown): value is PollerEvent {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function isPluginConfig(value: unknown): value is PluginConfig {
|
|
19
|
-
|
|
20
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
21
|
+
return 'apiKey' in value || 'tempoPrivateKey' in value;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
const DEFAULT_POLLING_INTERVAL_SECONDS = 60;
|
|
@@ -74,13 +76,28 @@ export default function register(api: OpenClawApi, fetchFunction?: FetchFunction
|
|
|
74
76
|
const config: unknown = api.pluginConfig;
|
|
75
77
|
if (!isPluginConfig(config)) {
|
|
76
78
|
api.logger.warn(
|
|
77
|
-
"TweetClaw: No API key configured. Run: openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY'",
|
|
79
|
+
"TweetClaw: No API key or Tempo wallet configured. Run: openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY' or set tempoPrivateKey for MPP pay-per-use",
|
|
78
80
|
);
|
|
79
81
|
return;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
const { apiKey, baseUrl = 'https://xquik.com' } = config;
|
|
83
|
-
const
|
|
84
|
+
const { apiKey, baseUrl = 'https://xquik.com', tempoPrivateKey } = config;
|
|
85
|
+
const isMppMode = apiKey === undefined && tempoPrivateKey !== undefined;
|
|
86
|
+
const credential = apiKey ?? '';
|
|
87
|
+
|
|
88
|
+
if (isMppMode) {
|
|
89
|
+
void (async (): Promise<void> => {
|
|
90
|
+
try {
|
|
91
|
+
await initMpp(tempoPrivateKey);
|
|
92
|
+
api.logger.info('TweetClaw: MPP initialized - Tempo wallet ready');
|
|
93
|
+
} catch (error: unknown) {
|
|
94
|
+
api.logger.error(`TweetClaw: MPP init failed - ${error instanceof Error ? error.message : String(error)}`);
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
api.logger.info('TweetClaw: MPP mode - pay-per-use via Tempo (7 X-API endpoints, no subscription needed)');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const request = createProxiedRequest(baseUrl, credential, fetchFunction);
|
|
84
101
|
|
|
85
102
|
// --- Tools (2-tool approach, execute inside tool object) ---
|
|
86
103
|
api.registerTool(
|
|
@@ -96,7 +113,7 @@ export default function register(api: OpenClawApi, fetchFunction?: FetchFunction
|
|
|
96
113
|
api.registerTool(
|
|
97
114
|
{
|
|
98
115
|
description: EXECUTE_DESCRIPTION,
|
|
99
|
-
execute: async (_toolCallId, { code }) => handleTweetclaw({ apiKey, baseUrl, code, fetchFunction }),
|
|
116
|
+
execute: async (_toolCallId, { code }) => handleTweetclaw({ apiKey: credential, baseUrl, code, fetchFunction }),
|
|
100
117
|
name: 'tweetclaw',
|
|
101
118
|
parameters: CODE_PARAMETER,
|
|
102
119
|
},
|
|
@@ -104,14 +121,16 @@ export default function register(api: OpenClawApi, fetchFunction?: FetchFunction
|
|
|
104
121
|
);
|
|
105
122
|
|
|
106
123
|
// --- Commands (instant, no LLM) ---
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
124
|
+
if (!isMppMode) {
|
|
125
|
+
api.registerCommand({
|
|
126
|
+
description: 'Show Xquik account status & usage',
|
|
127
|
+
handler: async () => {
|
|
128
|
+
const text = await handleXStatus(request);
|
|
129
|
+
return { text };
|
|
130
|
+
},
|
|
131
|
+
name: 'xstatus',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
115
134
|
|
|
116
135
|
api.registerCommand({
|
|
117
136
|
acceptsArgs: true,
|
|
@@ -123,9 +142,9 @@ export default function register(api: OpenClawApi, fetchFunction?: FetchFunction
|
|
|
123
142
|
name: 'xtrends',
|
|
124
143
|
});
|
|
125
144
|
|
|
126
|
-
// --- Background event poller ---
|
|
145
|
+
// --- Background event poller (requires API key, not available in MPP mode) ---
|
|
127
146
|
const { pollingEnabled, pollingInterval } = config;
|
|
128
|
-
if (pollingEnabled !== false) {
|
|
147
|
+
if (!isMppMode && pollingEnabled !== false) {
|
|
129
148
|
const poller = createEventPoller({
|
|
130
149
|
intervalSeconds: pollingInterval ?? DEFAULT_POLLING_INTERVAL_SECONDS,
|
|
131
150
|
onEvents: (events) => {
|
package/src/mpp.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { resolveAsyncFunctionConstructor } from './tools/sandbox.js';
|
|
2
|
+
|
|
3
|
+
type ModuleLoader = (name: string) => Promise<Record<string, unknown>>;
|
|
4
|
+
|
|
5
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
6
|
+
return typeof value === 'object' && value !== null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isCallable(value: unknown): value is (...args: readonly unknown[]) => unknown {
|
|
10
|
+
return typeof value === 'function';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function createModuleLoader(): ModuleLoader {
|
|
14
|
+
const loader = resolveAsyncFunctionConstructor();
|
|
15
|
+
const dynamicImport = new loader('n', 'return import(n)');
|
|
16
|
+
return async (name: string): Promise<Record<string, unknown>> => {
|
|
17
|
+
const mod: unknown = await dynamicImport(name);
|
|
18
|
+
if (!isRecord(mod)) {
|
|
19
|
+
throw new Error(`Failed to load ${name}`);
|
|
20
|
+
}
|
|
21
|
+
return mod;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function initMpp(tempoPrivateKey: string, loadModule?: ModuleLoader): Promise<void> {
|
|
26
|
+
const load = loadModule ?? createModuleLoader();
|
|
27
|
+
const mppxMod = await load('mppx/client').catch((): never => {
|
|
28
|
+
throw new Error('MPP requires mppx package. Run: npm i mppx viem');
|
|
29
|
+
});
|
|
30
|
+
const viemMod = await load('viem/accounts').catch((): never => {
|
|
31
|
+
throw new Error('MPP requires viem package. Run: npm i mppx viem');
|
|
32
|
+
});
|
|
33
|
+
if (!isCallable(viemMod.privateKeyToAccount)) throw new Error('viem missing privateKeyToAccount');
|
|
34
|
+
if (!isCallable(mppxMod.tempo)) throw new Error('mppx missing tempo');
|
|
35
|
+
if (!isRecord(mppxMod.Mppx)) throw new Error('mppx missing Mppx');
|
|
36
|
+
const createMethod: unknown = mppxMod.Mppx.create;
|
|
37
|
+
if (!isCallable(createMethod)) throw new Error('mppx Mppx.create is not a function');
|
|
38
|
+
const account: unknown = viemMod.privateKeyToAccount(tempoPrivateKey);
|
|
39
|
+
const method: unknown = mppxMod.tempo({ account });
|
|
40
|
+
createMethod({ methods: [method] });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { createModuleLoader, initMpp, isCallable, isRecord };
|
|
44
|
+
export type { ModuleLoader };
|
package/src/request.ts
CHANGED
|
@@ -16,7 +16,7 @@ function buildAuthHeader(credential: string): Record<string, string> {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function buildFetchHeaders(credential: string, hasBody: boolean): Record<string, string> {
|
|
19
|
-
const auth = buildAuthHeader(credential);
|
|
19
|
+
const auth = credential === '' ? {} : buildAuthHeader(credential);
|
|
20
20
|
if (hasBody) {
|
|
21
21
|
return { ...auth, [CONTENT_TYPE_HEADER]: 'application/json' };
|
|
22
22
|
}
|
package/src/tools/tweetclaw.ts
CHANGED
|
@@ -296,10 +296,12 @@ async () => {
|
|
|
296
296
|
\`\`\`
|
|
297
297
|
|
|
298
298
|
## Cost
|
|
299
|
-
- Free: /api/v1/compose, /api/v1/styles (cached lookup/save/delete/compare), /api/v1/drafts, /api/v1/radar, /api/v1/subscribe, /api/v1/account, /api/v1/api-keys, /api/v1/bot/*, /api/v1/integrations/*, /api/v1/x/accounts
|
|
300
|
-
-
|
|
299
|
+
- Free: /api/v1/compose, /api/v1/styles (cached lookup/save/delete/compare), /api/v1/drafts, /api/v1/radar, /api/v1/subscribe, /api/v1/account, /api/v1/api-keys, /api/v1/bot/*, /api/v1/integrations/*, /api/v1/x/accounts, /api/v1/automations/*, /api/v1/support/*
|
|
300
|
+
- MPP pay-per-use (no account/subscription needed, 7 endpoints): GET /api/v1/x/tweets/:id ($0.0003/call), GET /api/v1/x/tweets/search ($0.0003/tweet), GET /api/v1/x/users/:username ($0.00036/call), GET /api/v1/x/followers/check ($0.002/call), GET /api/v1/x/articles/:tweetId ($0.002/call), POST /api/v1/x/media/download ($0.0003/media), GET /api/v1/trends ($0.0009/call)
|
|
301
|
+
- Subscription required: /api/v1/styles (X API refresh when cache >7 days), /api/v1/x/profile, /api/v1/x/communities, /api/v1/x/dm, /api/v1/extractions, /api/v1/draws, /api/v1/monitors, /api/v1/events, /api/v1/webhooks, /api/v1/styles/:username/performance, /api/v1/trending/:source
|
|
301
302
|
- Write actions (subscription required): POST /api/v1/x/tweets, DELETE /api/v1/x/tweets/:id, POST|DELETE /api/v1/x/tweets/:id/like, POST /api/v1/x/tweets/:id/retweet, POST|DELETE /api/v1/x/users/:id/follow, POST /api/v1/x/dm/:userId, POST /api/v1/x/media, PATCH /api/v1/x/profile, PATCH /api/v1/x/profile/avatar, PATCH /api/v1/x/profile/banner, POST|DELETE /api/v1/x/communities, POST|DELETE /api/v1/x/communities/:id/join
|
|
302
303
|
- IMPORTANT: Always attempt the request. Never assume subscription status. The API returns a clear error if subscription is missing.
|
|
304
|
+
- MPP MODE: When configured with tempoPrivateKey (no API key), the mppx SDK auto-handles 402 challenges by paying via Tempo (USDC). Only the 7 MPP-eligible endpoints work in this mode.
|
|
303
305
|
|
|
304
306
|
## Error handling
|
|
305
307
|
- If response contains "subscription is inactive" or status 402, call POST /api/v1/subscribe to get checkout URL
|
package/src/types.ts
CHANGED
|
@@ -10,6 +10,7 @@ interface EndpointInfo {
|
|
|
10
10
|
readonly category: string;
|
|
11
11
|
readonly free: boolean;
|
|
12
12
|
readonly method: string;
|
|
13
|
+
readonly mpp?: { readonly intent: string; readonly price: string };
|
|
13
14
|
readonly parameters?: readonly EndpointParameter[];
|
|
14
15
|
readonly path: string;
|
|
15
16
|
readonly responseShape?: string;
|
|
@@ -32,10 +33,11 @@ interface ToolResult {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
interface PluginConfig {
|
|
35
|
-
readonly apiKey
|
|
36
|
+
readonly apiKey?: string;
|
|
36
37
|
readonly baseUrl?: string;
|
|
37
38
|
readonly pollingEnabled?: boolean;
|
|
38
39
|
readonly pollingInterval?: number;
|
|
40
|
+
readonly tempoPrivateKey?: string;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
interface EventPollerOptions {
|