@xquik/tweetclaw 1.2.1 → 1.4.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 CHANGED
@@ -2,7 +2,44 @@
2
2
 
3
3
  Post tweets, reply, like, retweet, follow, DM & more - directly from your chat. Full X/Twitter automation for [OpenClaw](https://github.com/openclaw/openclaw).
4
4
 
5
- Powered by [Xquik](https://xquik.com), the all-in-one X automation platform.
5
+ Powered by [Xquik](https://xquik.com), the all-in-one X automation platform. **Reads from $0.00015/call - 66x cheaper than the official X API.**
6
+
7
+ ## Pricing
8
+
9
+ TweetClaw uses Xquik's credit-based pricing. 1 credit = $0.00015.
10
+
11
+ ### vs Official X API
12
+
13
+ | | Xquik (via TweetClaw) | X API Basic | X API Pro |
14
+ |---|---|---|---|
15
+ | **Monthly cost** | **$20** | $100 | $5,000 |
16
+ | **Cost per tweet read** | **$0.00015** | ~$0.01 | ~$0.005 |
17
+ | **Cost per user lookup** | **$0.00015** | ~$0.01 | ~$0.005 |
18
+ | **Write actions** | **$0.0003** | Limited | Limited |
19
+ | **Bulk extraction** | **$0.00015/result** | Not available | Not available |
20
+ | **Monitoring + webhooks** | **Free** | Not available | Not available |
21
+ | **Giveaway draws** | **$0.00015/entry** | Not available | Not available |
22
+
23
+ ### Per-Operation Costs
24
+
25
+ | Operation | Credits | Cost |
26
+ |-----------|---------|------|
27
+ | Read (tweet, user, search, timeline, bookmarks, etc.) | 1 | $0.00015 |
28
+ | Follow check, article | 7 | $0.00105 |
29
+ | Write (tweet, like, retweet, follow, DM, etc.) | 2 | $0.0003 |
30
+ | Extraction / draw | 1/result | $0.00015/result |
31
+ | Monitors, webhooks, radar, compose, drafts, integrations | 0 | **Free** |
32
+
33
+ ### Pay-Per-Use (No Subscription)
34
+
35
+ Two options:
36
+
37
+ - **Credits (Stripe)**: Top up credits via the API ($10 minimum). 1 credit = $0.00015. Works with all 99 endpoints.
38
+ - **MPP (USDC)**: 8 read-only X-API endpoints accept anonymous payments via Machine Payments Protocol. No account needed. SDK: `npm i mppx`.
39
+
40
+ ### Free Operations
41
+
42
+ Tweet composition, style analysis, drafts, curated radar (7 sources), account management, integrations, automations, support tickets - all free, no credits consumed.
6
43
 
7
44
  ## Install
8
45
 
@@ -12,14 +49,30 @@ openclaw plugins install @xquik/tweetclaw
12
49
 
13
50
  ## Configure
14
51
 
15
- 1. Get an API key at [xquik.com/account-manager](https://xquik.com/account-manager)
16
- 2. Set it in OpenClaw:
52
+ ### Option A: API key (full access, 99 endpoints)
17
53
 
18
54
  ```bash
19
55
  openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY'
20
56
  ```
21
57
 
22
- Optional settings:
58
+ Get a key at [dashboard.xquik.com](https://dashboard.xquik.com/).
59
+
60
+ ### Option B: Credits (pay-per-use via Stripe, no subscription)
61
+
62
+ Top up credits from the Xquik dashboard or via `POST /credits/topup`. All 99 endpoints available. 1 credit = $0.00015.
63
+
64
+ ### Option C: MPP pay-per-use (no account needed, 8 read-only endpoints)
65
+
66
+ ```bash
67
+ npm i mppx viem
68
+ openclaw config set plugins.entries.tweetclaw.config.tempoPrivateKey '0xYOUR_TEMPO_KEY'
69
+ ```
70
+
71
+ 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).
72
+
73
+ MPP-eligible endpoints: tweet lookup ($0.00015), tweet search ($0.00015/tweet), user lookup ($0.00015), user tweets ($0.00015/tweet), follower check ($0.00105), article lookup ($0.00105), media download ($0.00015/media), trends ($0.00015).
74
+
75
+ ### Optional settings
23
76
 
24
77
  ```bash
25
78
  openclaw config set plugins.entries.tweetclaw.config.pollingEnabled true
@@ -66,7 +119,7 @@ Instant responses, no LLM needed:
66
119
 
67
120
  | Command | Description |
68
121
  |---------|-------------|
69
- | `/xstatus` | Account info, subscription status, usage |
122
+ | `/xstatus` | Account info, subscription status, usage, credit balance |
70
123
  | `/xtrends` | Trending topics from curated sources |
71
124
  | `/xtrends tech` | Trending topics filtered by category |
72
125
 
@@ -85,36 +138,22 @@ You: "Monitor @elonmusk for new tweets and follower changes"
85
138
 
86
139
  ## API Coverage
87
140
 
88
- 40+ endpoints across these categories:
89
-
90
- | Category | Examples |
91
- |----------|---------|
92
- | **Write Actions** | Post tweets, reply, like, retweet, follow, unfollow, DM, update profile & avatar |
93
- | **Media** | Upload media via URL, download tweet media, get gallery links |
94
- | **Twitter** | Search tweets, look up users, check follow relationships |
95
- | **Composition** | Compose, refine, score tweets; manage drafts; analyze writing styles |
96
- | **Extraction** | Run extraction jobs (reply-extractor, community-explorer, etc.) |
97
- | **Draws** | Run giveaway draws on tweets, export results |
98
- | **Monitoring** | Create monitors, view events, manage webhooks |
99
- | **Account** | Manage API keys, subscription, connected X accounts |
100
- | **Trends** | X trending topics, curated radar from multiple sources |
101
-
102
- ## Pricing
103
-
104
- **Free tier** (no subscription needed):
105
- - Tweet composition, style analysis, drafts
106
- - Curated trending radar
107
- - Account management, API keys
108
- - Integrations management
109
-
110
- **Subscription ($20/month)** for full access:
111
- - Write actions (post, reply, like, retweet, follow, DM, update profile)
112
- - Tweet search, user lookup, media download
113
- - Extractions, giveaway draws
114
- - Account monitors, events, webhooks
115
- - X trending topics
116
-
117
- When a paid endpoint returns 402, TweetClaw automatically provides a checkout URL.
141
+ 99 endpoints across 12 categories:
142
+
143
+ | Category | Examples | Cost |
144
+ |----------|---------|------|
145
+ | **Write Actions** | Post tweets, reply, like, retweet, follow, unfollow, DM, update profile, avatar, banner | 2 credits |
146
+ | **Media** | Upload media via URL, download tweet media, get gallery links | 1-2 credits |
147
+ | **Twitter** | Search tweets, look up users, user tweets/likes/media, favoriters, mutual followers, check follows, articles, bookmarks, notifications, timeline, DM history | 1-7 credits |
148
+ | **Composition** | Compose, refine, score tweets; manage drafts; analyze writing styles | Free |
149
+ | **Extraction** | Run extraction jobs (20 tool types: replies, followers, communities, etc.) | 1 credit/result |
150
+ | **Draws** | Run giveaway draws on tweets, export results | 1 credit/entry |
151
+ | **Monitoring** | Create monitors, view events, manage webhooks | Free |
152
+ | **Automations** | Create flows, add steps, test runs, inbound webhooks | Free |
153
+ | **Account** | Manage API keys, subscription, connected X accounts | Free |
154
+ | **Credits** | Check balance, top up credits | Free |
155
+ | **Trends** | X trending topics, curated radar from 7 sources | 1 credit / Free |
156
+ | **Support** | Create tickets, reply, track status | Free |
118
157
 
119
158
  ## License
120
159
 
@@ -1,21 +1,26 @@
1
1
  {
2
2
  "id": "tweetclaw",
3
3
  "name": "TweetClaw",
4
- "version": "1.2.1",
5
- "description": "Post tweets, reply, like, retweet, follow, DM from your chat - full X/Twitter automation powered by Xquik",
4
+ "version": "1.4.0",
5
+ "description": "Post tweets, reply, like, retweet, follow, DM from your chat - full X/Twitter automation powered by Xquik. 99 endpoints, reads from $0.00015/call.",
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-manager)" },
10
+ "apiKey": { "type": "string", "minLength": 1, "description": "Xquik API key (get one at dashboard.xquik.com). Required for full access to all 99 endpoints." },
11
+ "tempoPrivateKey": { "type": "string", "minLength": 1, "description": "Tempo wallet private key for MPP pay-per-use mode. No account needed. 8 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
- "required": ["apiKey"]
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-manager. Required for all API calls." },
22
+ "apiKey": { "label": "Xquik API Key", "sensitive": true, "placeholder": "xq_...", "help": "Full access to all 99 endpoints. Generate at dashboard.xquik.com." },
23
+ "tempoPrivateKey": { "label": "Tempo Private Key (MPP)", "sensitive": true, "placeholder": "0x...", "help": "Pay-per-use mode via Machine Payments Protocol. No account needed. 8 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,7 +1,7 @@
1
1
  {
2
2
  "name": "@xquik/tweetclaw",
3
- "version": "1.2.1",
4
- "description": "Post tweets, reply, like, retweet, follow, DM & more from OpenClaw - full X/Twitter automation via Xquik",
3
+ "version": "1.4.0",
4
+ "description": "Post tweets, reply, like, retweet, follow, DM & more from OpenClaw - full X/Twitter automation via Xquik. 99 endpoints, reads from $0.00015/call.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
@@ -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
- "openclaw": ">=2026.2.0"
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. 40+ endpoints, 2 tools (explore + tweetclaw), 2 commands (/xstatus, /xtrends), background event poller."
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. 99 endpoints, 2 tools (explore + tweetclaw), 2 commands (/xstatus, /xtrends), background event poller. Reads from $0.00015/call - 66x cheaper than the official X API."
4
4
  homepage: https://xquik.com
5
5
  read_when:
6
6
  - Posting, replying, liking, retweeting, or following on X/Twitter
@@ -11,17 +11,50 @@ 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"]},"tags":["twitter","x","automation","social-media","tweets","scraping","giveaway","monitoring","rest-api"]}}
14
+ - Checking credit balance or topping up credits
15
+ - Browsing bookmarks, notifications, timeline, or DM history
16
+ metadata: {"openclaw":{"emoji":"🐦","primaryEnv":"XQUIK_API_KEY","requires":{"env":["XQUIK_API_KEY"]},"tags":["twitter","x","automation","social-media","tweets","scraping","giveaway","monitoring","rest-api","cheap-api"]}}
15
17
  ---
16
18
 
17
19
  # TweetClaw
18
20
 
19
- OpenClaw plugin for X/Twitter automation powered by Xquik. Install via:
21
+ OpenClaw plugin for X/Twitter automation powered by Xquik. **Reads from $0.00015/call - 66x cheaper than the official X API.**
20
22
 
21
23
  ```bash
22
24
  openclaw plugins install @xquik/tweetclaw
23
25
  ```
24
26
 
27
+ ## Pricing
28
+
29
+ TweetClaw uses Xquik's credit-based pricing. 1 credit = $0.00015.
30
+
31
+ ### Per-Operation Costs
32
+
33
+ | Operation | Credits | Cost |
34
+ |-----------|---------|------|
35
+ | Read (tweet, user, search, timeline, bookmarks, etc.) | 1 | $0.00015 |
36
+ | Follow check, article | 7 | $0.00105 |
37
+ | Write (tweet, like, retweet, follow, DM, etc.) | 2 | $0.0003 |
38
+ | Extraction / draw | 1/result | $0.00015/result |
39
+ | Monitors, webhooks, radar, compose, drafts, integrations | 0 | **Free** |
40
+
41
+ ### vs Official X API
42
+
43
+ | | Xquik | X API Basic | X API Pro |
44
+ |---|---|---|---|
45
+ | **Monthly cost** | **$20** | $100 | $5,000 |
46
+ | **Cost per tweet read** | **$0.00015** | ~$0.01 | ~$0.005 |
47
+ | **Cost per user lookup** | **$0.00015** | ~$0.01 | ~$0.005 |
48
+ | **Write actions** | **$0.0003** | Limited | Limited |
49
+ | **Bulk extraction** | **$0.00015/result** | Not available | Not available |
50
+
51
+ ### Pay-Per-Use (No Subscription)
52
+
53
+ - **Credits (Stripe)**: Top up via `POST /api/v1/credits/topup` ($10 minimum). Works with all 99 endpoints.
54
+ - **MPP (USDC)**: 8 read-only endpoints accept anonymous Tempo payments. No account needed. SDK: `npm i mppx`.
55
+
56
+ MPP pricing: tweet lookup ($0.00015), tweet search ($0.00015/tweet), user lookup ($0.00015), user tweets ($0.00015/tweet), follower check ($0.00105), article ($0.00105), media download ($0.00015/media), trends ($0.00015).
57
+
25
58
  ## When to Use
26
59
 
27
60
  Use TweetClaw when the user wants to:
@@ -32,6 +65,9 @@ Use TweetClaw when the user wants to:
32
65
  - Update their X profile, avatar, or banner
33
66
  - Upload media and tweet with images
34
67
  - Search tweets or look up user profiles
68
+ - Get user's recent tweets, liked tweets, or media tweets
69
+ - See who liked a tweet (favoriters) or mutual followers
70
+ - Browse bookmarks, notifications, timeline, or DM history
35
71
  - Extract bulk data (followers, replies, communities, spaces)
36
72
  - Run giveaway draws from tweet replies
37
73
  - Monitor X accounts for new activity
@@ -40,20 +76,35 @@ Use TweetClaw when the user wants to:
40
76
  - Check trending topics on X
41
77
  - Download tweet media (images, videos, GIFs)
42
78
  - Set up Telegram alerts for monitor events
79
+ - Create and manage automation flows (triggers, steps, test runs)
80
+ - Check credit balance or top up credits
81
+ - Open and manage support tickets
82
+ - Read X Articles (long-form posts)
43
83
 
44
84
  Do NOT use TweetClaw for browsing X in a browser, analytics dashboards, scheduling future posts, or managing X ads.
45
85
 
46
86
  ## Configuration
47
87
 
48
- Set your Xquik API key (get one at [xquik.com/account-manager](https://xquik.com/account-manager)):
88
+ ### API key mode (full access)
49
89
 
50
90
  ```bash
51
91
  openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY'
52
92
  ```
53
93
 
94
+ Get a key at [dashboard.xquik.com](https://dashboard.xquik.com/).
95
+
96
+ ### MPP mode (no account, pay-per-use via Tempo/USDC)
97
+
98
+ ```bash
99
+ npm i mppx viem
100
+ openclaw config set plugins.entries.tweetclaw.config.tempoPrivateKey '0xYOUR_KEY'
101
+ ```
102
+
103
+ MPP gives agents access to 8 read-only X-API endpoints without any account or subscription. The mppx SDK handles HTTP 402 payment challenges automatically.
104
+
54
105
  ## Tools
55
106
 
56
- TweetClaw registers 2 tools that cover the entire Xquik API (40+ endpoints):
107
+ TweetClaw registers 2 tools that cover the entire Xquik API (99 endpoints):
57
108
 
58
109
  ### `explore` (free, no network)
59
110
 
@@ -87,7 +138,7 @@ async () => {
87
138
 
88
139
  | Command | Description |
89
140
  |---------|-------------|
90
- | `/xstatus` | Account info, subscription status, usage |
141
+ | `/xstatus` | Account info, subscription status, usage, credit balance |
91
142
  | `/xtrends` | Trending topics from curated sources |
92
143
  | `/xtrends tech` | Trending topics filtered by category |
93
144
 
@@ -149,6 +200,27 @@ You: "Search tweets about AI agents"
149
200
  Agent uses tweetclaw -> calls search endpoint with query
150
201
  ```
151
202
 
203
+ ### Get user activity
204
+
205
+ ```
206
+ You: "Show me @elonmusk's recent tweets"
207
+ Agent uses tweetclaw -> GET /api/v1/x/users/{id}/tweets
208
+ ```
209
+
210
+ ### Check who liked a tweet
211
+
212
+ ```
213
+ You: "Who liked this tweet?"
214
+ Agent uses tweetclaw -> GET /api/v1/x/tweets/{id}/favoriters
215
+ ```
216
+
217
+ ### Browse bookmarks and timeline
218
+
219
+ ```
220
+ You: "Show my bookmarks" or "What's on my timeline?"
221
+ Agent uses tweetclaw -> GET /api/v1/x/bookmarks or GET /api/v1/x/timeline
222
+ ```
223
+
152
224
  ### Run a giveaway draw
153
225
 
154
226
  ```
@@ -198,28 +270,51 @@ You: "What's trending on X right now?"
198
270
  Agent uses tweetclaw -> returns curated trending topics from 7 sources
199
271
  ```
200
272
 
201
- ## API Categories
273
+ ### Check credits and top up
202
274
 
203
- | Category | Examples | Free |
204
- |----------|---------|------|
205
- | Write Actions | Post tweets, reply, like, retweet, follow, DM, update profile | No |
206
- | Media | Upload media, download tweet media | No |
207
- | Twitter | Search tweets, look up users, check follows | No |
208
- | Composition | Compose, refine, score tweets; manage drafts | Yes |
209
- | Styles | Analyze tweet styles, compare, performance | Mixed |
210
- | Extraction | Reply/follower/community extraction (20 tools) | No |
211
- | Draws | Giveaway draws, export results | No |
212
- | Monitoring | Create monitors, view events, webhooks | No |
213
- | Account | API keys, subscription, connected X accounts | Yes |
214
- | Trends | X trending topics, curated radar from 7 sources | Mixed |
275
+ ```
276
+ You: "How many credits do I have?" or "Top up my credits"
277
+ Agent uses tweetclaw -> GET /api/v1/credits or POST /api/v1/credits/topup
278
+ ```
215
279
 
216
- ## Pricing
280
+ ### Create an automation flow (free)
217
281
 
218
- Free tier (no subscription): tweet composition, style analysis, drafts, curated radar, account management, integrations.
282
+ ```
283
+ You: "Create an automation that sends a DM when I get a new follower"
284
+ Agent uses tweetclaw -> creates flow with monitor_event trigger, adds send_dm step, tests it
285
+ ```
219
286
 
220
- Subscription ($20/month): write actions, search, media, extractions, draws, monitors, X trending.
287
+ ### Read an X Article
221
288
 
222
- When a paid endpoint returns 402, TweetClaw provides a checkout URL.
289
+ ```
290
+ You: "Get the full article from this tweet: https://x.com/user/status/123"
291
+ Agent uses tweetclaw -> calls /api/v1/x/articles/:tweetId, returns title, body, images
292
+ ```
293
+
294
+ ### Open a support ticket (free)
295
+
296
+ ```
297
+ You: "Open a support ticket about my monitor not working"
298
+ Agent uses tweetclaw -> creates ticket with subject and description
299
+ ```
300
+
301
+ ## API Categories
302
+
303
+ | Category | Examples | Cost |
304
+ |----------|---------|------|
305
+ | Write Actions | Post tweets, reply, like, retweet, follow, DM, update profile, avatar, banner | 2 credits |
306
+ | Media | Upload media, download tweet media | 1-2 credits |
307
+ | Twitter | Search tweets, look up users, user tweets/likes/media, favoriters, mutual followers, bookmarks, notifications, timeline, DM history | 1-7 credits |
308
+ | Composition | Compose, refine, score tweets; manage drafts | Free |
309
+ | Styles | Analyze tweet styles, compare, performance | Mixed |
310
+ | Extraction | Reply/follower/community extraction (20 tools) | 1 credit/result |
311
+ | Draws | Giveaway draws, export results | 1 credit/entry |
312
+ | Monitoring | Create monitors, view events, webhooks | Free |
313
+ | Automations | Create flows, add steps, test runs, inbound webhooks | Free |
314
+ | Account | API keys, subscription, connected X accounts | Free |
315
+ | Credits | Check balance, top up | Free |
316
+ | Trends | X trending topics, curated radar from 7 sources | 1 credit / Free |
317
+ | Support | Create tickets, reply, track status | Free |
223
318
 
224
319
  ## Tips
225
320
 
@@ -229,5 +324,6 @@ When a paid endpoint returns 402, TweetClaw provides a checkout URL.
229
324
  - For write actions (post, like, follow, DM), always pass the `account` parameter with the X username
230
325
  - Follow/unfollow/DM require a numeric user ID - look up the user first via `/api/v1/x/users/:username`
231
326
  - On 402 errors, call `POST /api/v1/subscribe` to get a checkout URL instead of giving up
232
- - Use `/xstatus` to quickly check subscription and usage without invoking the AI agent
327
+ - Use `/xstatus` to quickly check subscription, usage, and credit balance without invoking the AI agent
233
328
  - The compose workflow (compose/refine/score) is free and helps draft high-engagement tweets
329
+ - Top up credits via `POST /api/v1/credits/topup` for pay-per-use without a subscription
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, links (tweet_search_extractor)', in: 'body', name: 'mediaType', required: false, type: 'string' },
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: 'Include or exclude replies (tweet_search_extractor): include, exclude', in: 'body', name: 'replies', required: false, type: 'string' },
30
- { description: 'Include or exclude retweets (tweet_search_extractor): include, exclude', in: 'body', name: 'retweets', required: false, type: 'string' },
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: 'Number of items to return', in: 'query', name: 'count', required: false, type: 'number' },
309
- { description: 'Lookback window in hours', in: 'query', name: 'hours', required: false, type: 'number' },
310
- { description: 'Region filter (us, global, etc.)', in: 'query', name: 'region', required: false, type: 'string' },
311
- { description: 'Source filter (google, hackernews, reddit, etc.)', in: 'query', name: 'source', required: false, type: 'string' },
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 }], total }',
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 (reply-extractor, community-explorer, etc.)', in: 'body', name: 'toolType', required: true, type: 'string' },
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 tweet search tools', in: 'body', name: 'searchQuery', required: false, type: 'string' },
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
- return typeof value === 'object' && value !== null && 'apiKey' in value;
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 request = createProxiedRequest(baseUrl, apiKey, fetchFunction);
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
- api.registerCommand({
108
- description: 'Show Xquik account status & usage',
109
- handler: async () => {
110
- const text = await handleXStatus(request);
111
- return { text };
112
- },
113
- name: 'xstatus',
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
  }
@@ -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
- - Subscription required: /api/v1/styles (X API refresh when cache >7 days), /api/v1/x/tweets, /api/v1/x/users, /api/v1/x/followers, /api/v1/x/media, /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/trends, /api/v1/trending/:source
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: string;
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 {