@xquik/tweetclaw 1.4.0 → 1.4.2

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
@@ -1,8 +1,12 @@
1
1
  # TweetClaw
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@xquik/tweetclaw)](https://www.npmjs.com/package/@xquik/tweetclaw)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+ ![GitHub stars](https://img.shields.io/github/stars/Xquik-dev/tweetclaw)
6
+
3
7
  Post tweets, reply, like, retweet, follow, DM & more - directly from your chat. Full X/Twitter automation for [OpenClaw](https://github.com/openclaw/openclaw).
4
8
 
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.**
9
+ Powered by [Xquik](https://xquik.com), the all-in-one X automation platform. **Reads from $0.00015/call - 33x cheaper than the official X API.**
6
10
 
7
11
  ## Pricing
8
12
 
@@ -14,7 +18,7 @@ TweetClaw uses Xquik's credit-based pricing. 1 credit = $0.00015.
14
18
  |---|---|---|---|
15
19
  | **Monthly cost** | **$20** | $100 | $5,000 |
16
20
  | **Cost per tweet read** | **$0.00015** | ~$0.01 | ~$0.005 |
17
- | **Cost per user lookup** | **$0.00015** | ~$0.01 | ~$0.005 |
21
+ | **Cost per user lookup** | **$0.0003** | ~$0.01 | ~$0.005 |
18
22
  | **Write actions** | **$0.0003** | Limited | Limited |
19
23
  | **Bulk extraction** | **$0.00015/result** | Not available | Not available |
20
24
  | **Monitoring + webhooks** | **Free** | Not available | Not available |
@@ -24,18 +28,23 @@ TweetClaw uses Xquik's credit-based pricing. 1 credit = $0.00015.
24
28
 
25
29
  | Operation | Credits | Cost |
26
30
  |-----------|---------|------|
27
- | Read (tweet, user, search, timeline, bookmarks, etc.) | 1 | $0.00015 |
31
+ | Read (tweet, search, timeline, bookmarks, etc.) | 1 | $0.00015 |
32
+ | Read (user profile, favoriters, followers you know, verified followers) | 2 | $0.0003 |
33
+ | Read (trends) | 3 | $0.00045 |
28
34
  | Follow check, article | 7 | $0.00105 |
29
35
  | Write (tweet, like, retweet, follow, DM, etc.) | 2 | $0.0003 |
30
- | Extraction / draw | 1/result | $0.00015/result |
36
+ | Extraction (tweets, replies, quotes, mentions, posts, likes, media, search) | 1/result | $0.00015/result |
37
+ | Extraction (followers, following, verified followers, favoriters, retweeters, community members, people search, list members, list followers) | 2/result | $0.0003/result |
38
+ | Extraction (articles) | 7/result | $0.00105/result |
39
+ | Draw | 1/entry | $0.00015/entry |
31
40
  | Monitors, webhooks, radar, compose, drafts, integrations | 0 | **Free** |
32
41
 
33
42
  ### Pay-Per-Use (No Subscription)
34
43
 
35
44
  Two options:
36
45
 
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`.
46
+ - **Credits (Stripe)**: Top up credits via the API ($10 minimum). 1 credit = $0.00015. Works with all 120 endpoints.
47
+ - **MPP (USDC)**: 16 read-only X-API endpoints accept anonymous payments via Machine Payments Protocol. No account needed. SDK: `npm i mppx`.
39
48
 
40
49
  ### Free Operations
41
50
 
@@ -49,7 +58,7 @@ openclaw plugins install @xquik/tweetclaw
49
58
 
50
59
  ## Configure
51
60
 
52
- ### Option A: API key (full access, 99 endpoints)
61
+ ### Option A: API key (full access, 120 endpoints)
53
62
 
54
63
  ```bash
55
64
  openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY'
@@ -59,18 +68,18 @@ Get a key at [dashboard.xquik.com](https://dashboard.xquik.com/).
59
68
 
60
69
  ### Option B: Credits (pay-per-use via Stripe, no subscription)
61
70
 
62
- Top up credits from the Xquik dashboard or via `POST /credits/topup`. All 99 endpoints available. 1 credit = $0.00015.
71
+ Top up credits from the Xquik dashboard or via `POST /credits/topup`. All 120 endpoints available. 1 credit = $0.00015.
63
72
 
64
- ### Option C: MPP pay-per-use (no account needed, 8 read-only endpoints)
73
+ ### Option C: MPP pay-per-use (no account needed, 16 read-only endpoints)
65
74
 
66
75
  ```bash
67
76
  npm i mppx viem
68
77
  openclaw config set plugins.entries.tweetclaw.config.tempoPrivateKey '0xYOUR_TEMPO_KEY'
69
78
  ```
70
79
 
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).
80
+ MPP (Machine Payments Protocol) lets agents pay per API call via Tempo (USDC). No account, no API key, no subscription. 16 read-only endpoints. Get a Tempo wallet at [tempo.xyz](https://tempo.xyz).
72
81
 
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).
82
+ 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.00045), X trends ($0.00045), quotes ($0.00015/tweet), replies ($0.00015/tweet), retweeters ($0.00015/user), favoriters ($0.00015/user), thread ($0.00015/tweet), user likes ($0.00015/tweet), user media ($0.00015/tweet).
74
83
 
75
84
  ### Optional settings
76
85
 
@@ -138,7 +147,7 @@ You: "Monitor @elonmusk for new tweets and follower changes"
138
147
 
139
148
  ## API Coverage
140
149
 
141
- 99 endpoints across 12 categories:
150
+ 120 endpoints across 12 categories:
142
151
 
143
152
  | Category | Examples | Cost |
144
153
  |----------|---------|------|
@@ -146,15 +155,23 @@ You: "Monitor @elonmusk for new tweets and follower changes"
146
155
  | **Media** | Upload media via URL, download tweet media, get gallery links | 1-2 credits |
147
156
  | **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
157
  | **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 |
158
+ | **Extraction** | Run extraction jobs (23 tool types: replies, followers, communities, favoriters, user_likes, user_media, etc.) | 1-7 credits/result |
150
159
  | **Draws** | Run giveaway draws on tweets, export results | 1 credit/entry |
151
160
  | **Monitoring** | Create monitors, view events, manage webhooks | Free |
152
161
  | **Automations** | Create flows, add steps, test runs, inbound webhooks | Free |
153
162
  | **Account** | Manage API keys, subscription, connected X accounts | Free |
154
163
  | **Credits** | Check balance, top up credits | Free |
155
- | **Trends** | X trending topics, curated radar from 7 sources | 1 credit / Free |
164
+ | **Trends** | X trending topics, curated radar from 7 sources | 3 credits / Free |
156
165
  | **Support** | Create tickets, reply, track status | Free |
157
166
 
167
+ ## Links
168
+
169
+ - [Xquik Platform](https://xquik.com)
170
+ - [API Documentation](https://docs.xquik.com)
171
+ - [Billing & Pricing](https://docs.xquik.com/guides/billing)
172
+ - [npm Package](https://www.npmjs.com/package/@xquik/tweetclaw)
173
+ - [OpenClaw](https://github.com/openclaw/openclaw)
174
+
158
175
  ## License
159
176
 
160
177
  MIT
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "id": "tweetclaw",
3
3
  "name": "TweetClaw",
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.",
4
+ "version": "1.4.1",
5
+ "description": "Post tweets, reply, like, retweet, follow, DM from your chat - full X/Twitter automation powered by Xquik. 120 endpoints, reads from $0.00015/call. Requires an Xquik API key or Tempo private key.",
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 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." },
10
+ "apiKey": { "type": "string", "minLength": 1, "description": "Xquik API key (get one at dashboard.xquik.com). Required for full access to all 120 endpoints." },
11
+ "tempoPrivateKey": { "type": "string", "minLength": 1, "description": "Tempo wallet private key for MPP pay-per-use mode. No account needed. 16 read-only X-API endpoints." },
12
12
  "baseUrl": { "type": "string", "default": "https://xquik.com" },
13
13
  "pollingInterval": { "type": "number", "default": 60, "description": "Event polling interval in seconds" },
14
14
  "pollingEnabled": { "type": "boolean", "default": true }
@@ -19,8 +19,8 @@
19
19
  ]
20
20
  },
21
21
  "uiHints": {
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." },
22
+ "apiKey": { "label": "Xquik API Key", "sensitive": true, "placeholder": "xq_...", "help": "Full access to all 120 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. 16 read-only X-API endpoints only. Requires mppx and viem packages." },
24
24
  "baseUrl": { "label": "API Base URL", "placeholder": "https://xquik.com", "advanced": true, "help": "Only change if using a self-hosted Xquik instance." },
25
25
  "pollingInterval": { "label": "Event Poll Interval (seconds)", "advanced": true, "help": "How often to check for new monitor events. Default: 60 seconds." },
26
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.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.",
3
+ "version": "1.4.2",
4
+ "description": "Post tweets, reply, like, retweet, follow, DM & more from OpenClaw - full X/Twitter automation via Xquik. 120 endpoints, reads from $0.00015/call.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
@@ -18,7 +18,15 @@
18
18
  "openclaw": {
19
19
  "extensions": [
20
20
  "./src/index.ts"
21
- ]
21
+ ],
22
+ "compat": {
23
+ "pluginApi": ">=2026.3.28",
24
+ "minGatewayVersion": "2026.3.28"
25
+ },
26
+ "build": {
27
+ "openclawVersion": "2026.3.28",
28
+ "pluginSdkVersion": "2026.3.28"
29
+ }
22
30
  },
23
31
  "files": [
24
32
  "src/",
@@ -31,7 +39,24 @@
31
39
  "x",
32
40
  "automation",
33
41
  "tweetclaw",
34
- "xquik"
42
+ "xquik",
43
+ "twitter-api",
44
+ "x-api",
45
+ "social-media",
46
+ "scraper",
47
+ "mcp",
48
+ "ai-agent",
49
+ "data-extraction",
50
+ "giveaway",
51
+ "monitoring",
52
+ "tweet",
53
+ "twitter-scraper",
54
+ "twitter-bot",
55
+ "x-twitter",
56
+ "tweet-api",
57
+ "twitter-automation",
58
+ "openclaw",
59
+ "mcp-server"
35
60
  ],
36
61
  "scripts": {
37
62
  "typecheck": "tsc --noEmit",
@@ -50,8 +75,12 @@
50
75
  "viem": ">=2.0.0"
51
76
  },
52
77
  "peerDependenciesMeta": {
53
- "mppx": { "optional": true },
54
- "viem": { "optional": true }
78
+ "mppx": {
79
+ "optional": true
80
+ },
81
+ "viem": {
82
+ "optional": true
83
+ }
55
84
  },
56
85
  "devDependencies": {
57
86
  "@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, 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."
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. 120 endpoints, 2 tools (explore + tweetclaw), 2 commands (/xstatus, /xtrends), background event poller. Reads from $0.00015/call - 33x 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
@@ -18,7 +18,7 @@ metadata: {"openclaw":{"emoji":"🐦","primaryEnv":"XQUIK_API_KEY","requires":{"
18
18
 
19
19
  # TweetClaw
20
20
 
21
- OpenClaw plugin for X/Twitter automation powered by Xquik. **Reads from $0.00015/call - 66x cheaper than the official X API.**
21
+ OpenClaw plugin for X/Twitter automation powered by Xquik. **Reads from $0.00015/call - 33x cheaper than the official X API.**
22
22
 
23
23
  ```bash
24
24
  openclaw plugins install @xquik/tweetclaw
@@ -32,10 +32,15 @@ TweetClaw uses Xquik's credit-based pricing. 1 credit = $0.00015.
32
32
 
33
33
  | Operation | Credits | Cost |
34
34
  |-----------|---------|------|
35
- | Read (tweet, user, search, timeline, bookmarks, etc.) | 1 | $0.00015 |
35
+ | Read (tweet, search, timeline, bookmarks, etc.) | 1 | $0.00015 |
36
+ | Read (user profile) | 2 | $0.0003 |
37
+ | Read (trends) | 3 | $0.00045 |
36
38
  | Follow check, article | 7 | $0.00105 |
37
39
  | Write (tweet, like, retweet, follow, DM, etc.) | 2 | $0.0003 |
38
- | Extraction / draw | 1/result | $0.00015/result |
40
+ | Extraction (tweets, replies, quotes, mentions, posts, likes, media, search) | 1/result | $0.00015/result |
41
+ | Extraction (followers, following, verified followers, favoriters, retweeters, community members, people search, list members, list followers) | 2/result | $0.0003/result |
42
+ | Extraction (articles) | 7/result | $0.00105/result |
43
+ | Draw | 1/entry | $0.00015/entry |
39
44
  | Monitors, webhooks, radar, compose, drafts, integrations | 0 | **Free** |
40
45
 
41
46
  ### vs Official X API
@@ -44,16 +49,16 @@ TweetClaw uses Xquik's credit-based pricing. 1 credit = $0.00015.
44
49
  |---|---|---|---|
45
50
  | **Monthly cost** | **$20** | $100 | $5,000 |
46
51
  | **Cost per tweet read** | **$0.00015** | ~$0.01 | ~$0.005 |
47
- | **Cost per user lookup** | **$0.00015** | ~$0.01 | ~$0.005 |
52
+ | **Cost per user lookup** | **$0.0003** | ~$0.01 | ~$0.005 |
48
53
  | **Write actions** | **$0.0003** | Limited | Limited |
49
54
  | **Bulk extraction** | **$0.00015/result** | Not available | Not available |
50
55
 
51
56
  ### Pay-Per-Use (No Subscription)
52
57
 
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`.
58
+ - **Credits (Stripe)**: Top up via `POST /api/v1/credits/topup` ($10 minimum). Works with all 120 endpoints.
59
+ - **MPP (USDC)**: 16 read-only endpoints accept anonymous Tempo payments. No account needed. SDK: `npm i mppx`.
55
60
 
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).
61
+ 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.00045), X trends ($0.00045), quotes ($0.00015/tweet), replies ($0.00015/tweet), retweeters ($0.00015/user), favoriters ($0.00015/user), thread ($0.00015/tweet), user likes ($0.00015/tweet), user media ($0.00015/tweet).
57
62
 
58
63
  ## When to Use
59
64
 
@@ -100,11 +105,11 @@ npm i mppx viem
100
105
  openclaw config set plugins.entries.tweetclaw.config.tempoPrivateKey '0xYOUR_KEY'
101
106
  ```
102
107
 
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.
108
+ MPP gives agents access to 16 read-only X-API endpoints without any account or subscription. The mppx SDK handles HTTP 402 payment challenges automatically.
104
109
 
105
110
  ## Tools
106
111
 
107
- TweetClaw registers 2 tools that cover the entire Xquik API (99 endpoints):
112
+ TweetClaw registers 2 tools that cover the entire Xquik API (120 endpoints):
108
113
 
109
114
  ### `explore` (free, no network)
110
115
 
@@ -307,13 +312,13 @@ Agent uses tweetclaw -> creates ticket with subject and description
307
312
  | Twitter | Search tweets, look up users, user tweets/likes/media, favoriters, mutual followers, bookmarks, notifications, timeline, DM history | 1-7 credits |
308
313
  | Composition | Compose, refine, score tweets; manage drafts | Free |
309
314
  | Styles | Analyze tweet styles, compare, performance | Mixed |
310
- | Extraction | Reply/follower/community extraction (20 tools) | 1 credit/result |
315
+ | Extraction | Reply/follower/community extraction (23 tools) | 1-7 credits/result |
311
316
  | Draws | Giveaway draws, export results | 1 credit/entry |
312
317
  | Monitoring | Create monitors, view events, webhooks | Free |
313
318
  | Automations | Create flows, add steps, test runs, inbound webhooks | Free |
314
319
  | Account | API keys, subscription, connected X accounts | Free |
315
320
  | Credits | Check balance, top up | Free |
316
- | Trends | X trending topics, curated radar from 7 sources | 1 credit / Free |
321
+ | Trends | X trending topics, curated radar from 7 sources | 3 credits / Free |
317
322
  | Support | Create tickets, reply, track status | Free |
318
323
 
319
324
  ## Tips
package/src/index.ts CHANGED
@@ -94,7 +94,7 @@ export default function register(api: OpenClawApi, fetchFunction?: FetchFunction
94
94
  api.logger.error(`TweetClaw: MPP init failed - ${error instanceof Error ? error.message : String(error)}`);
95
95
  }
96
96
  })();
97
- api.logger.info('TweetClaw: MPP mode - pay-per-use via Tempo (7 X-API endpoints, no subscription needed)');
97
+ api.logger.info('TweetClaw: MPP mode - pay-per-use via Tempo (16 X-API endpoints, no subscription needed)');
98
98
  }
99
99
 
100
100
  const request = createProxiedRequest(baseUrl, credential, fetchFunction);
package/src/mpp.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { resolveAsyncFunctionConstructor } from './tools/sandbox.js';
1
+ import { resolveAsyncFunctionConstructor } from './tools/executor.js';
2
2
 
3
3
  type ModuleLoader = (name: string) => Promise<Record<string, unknown>>;
4
4
 
@@ -0,0 +1,125 @@
1
+ import vm from 'node:vm';
2
+ import { API_SPEC } from '../api-spec.js';
3
+ import { truncateResponse } from '../truncate.js';
4
+ import type { ToolResult } from '../types.js';
5
+
6
+ const specEndpoints: ReadonlyArray<Readonly<Record<string, unknown>>> = API_SPEC.map(
7
+ (endpoint): Readonly<Record<string, unknown>> => ({ ...endpoint }),
8
+ );
9
+
10
+ function extractErrorMessage(error: unknown): string {
11
+ if (error instanceof Error) {
12
+ return `${error.constructor.name}: ${error.message}`;
13
+ }
14
+ return String(error);
15
+ }
16
+
17
+ function isAsyncFunctionConstructor(
18
+ value: unknown,
19
+ ): value is new (...parameters: readonly string[]) => (...parameters: readonly unknown[]) => Promise<unknown> {
20
+ return typeof value === 'function';
21
+ }
22
+
23
+ function getConstructorFromPrototype(proto: unknown): unknown {
24
+ if (typeof proto !== 'object' || proto === null) {
25
+ return undefined;
26
+ }
27
+ return 'constructor' in proto ? proto.constructor : undefined;
28
+ }
29
+
30
+ function resolveAsyncFunctionConstructor(prototype?: unknown): new (
31
+ ...parameters: readonly string[]
32
+ ) => (...parameters: readonly unknown[]) => Promise<unknown> {
33
+ const asyncPrototype: unknown = prototype ?? Object.getPrototypeOf(async (): Promise<void> => {});
34
+ const candidate: unknown = getConstructorFromPrototype(asyncPrototype);
35
+ if (!isAsyncFunctionConstructor(candidate)) {
36
+ throw new Error('AsyncFunction constructor not found');
37
+ }
38
+ return candidate;
39
+ }
40
+
41
+ const BLOCKED_PROPS: ReadonlySet<string | symbol> = new Set(['constructor', '__proto__', 'prototype']);
42
+
43
+ function isBlockedProperty(property: string | symbol): boolean {
44
+ return BLOCKED_PROPS.has(property);
45
+ }
46
+
47
+ function wrapValue(value: unknown): unknown {
48
+ if (value === null || value === undefined) return value;
49
+ if (typeof value !== 'object' && typeof value !== 'function') return value;
50
+ return createSafeProxy(value);
51
+ }
52
+
53
+ async function wrapAsync(promise: Promise<unknown>): Promise<unknown> {
54
+ const resolved: unknown = await promise;
55
+ return wrapValue(resolved);
56
+ }
57
+
58
+ function createCallableProxy(bound: (...a: readonly unknown[]) => unknown): unknown {
59
+ const handler: ProxyHandler<typeof bound> = {
60
+ apply(_target: typeof bound, _thisArgument: unknown, argumentsList: unknown[]): unknown {
61
+ const result: unknown = bound(...argumentsList);
62
+ if (result instanceof Promise) {
63
+ return wrapAsync(result);
64
+ }
65
+ return wrapValue(result);
66
+ },
67
+ get(_target: typeof bound, property: string | symbol): unknown {
68
+ if (isBlockedProperty(property)) return undefined;
69
+ return Reflect.get(_target, property);
70
+ },
71
+ };
72
+ return new Proxy(bound, handler);
73
+ }
74
+
75
+ function createSafeProxy(target: unknown): unknown {
76
+ if (target === null || target === undefined) return target;
77
+ if (typeof target !== 'object' && typeof target !== 'function') return target;
78
+
79
+ const handler: ProxyHandler<Record<string | symbol, unknown>> = {
80
+ get(t: Record<string | symbol, unknown>, property: string | symbol): unknown {
81
+ if (isBlockedProperty(property)) return undefined;
82
+ const value: unknown = Reflect.get(t, property);
83
+ if (typeof value === 'function') {
84
+ const bound: (...a: readonly unknown[]) => unknown = value.bind(t);
85
+ return createCallableProxy(bound);
86
+ }
87
+ return wrapValue(value);
88
+ },
89
+ };
90
+ return new Proxy(target as Record<string | symbol, unknown>, handler);
91
+ }
92
+
93
+ function runInSandbox(code: string, globals: Readonly<Record<string, unknown>>): unknown {
94
+ const rawContext: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
95
+ for (const key of Object.keys(globals)) {
96
+ const value: unknown = globals[key];
97
+ const safeValue: unknown = typeof value === 'object' && value !== null
98
+ ? createSafeProxy(value)
99
+ : value;
100
+ Reflect.set(rawContext, key, safeValue);
101
+ }
102
+ const context: vm.Context = vm.createContext(rawContext);
103
+ return vm.runInNewContext(`(${code})()`, context);
104
+ }
105
+
106
+ function successResult(content: unknown): ToolResult {
107
+ return { content: [{ text: truncateResponse(content), type: 'text' as const }] };
108
+ }
109
+
110
+ function errorResult(error: unknown): ToolResult {
111
+ return { content: [{ text: extractErrorMessage(error), type: 'text' as const }], isError: true };
112
+ }
113
+
114
+ export {
115
+ BLOCKED_PROPS,
116
+ createSafeProxy,
117
+ errorResult,
118
+ extractErrorMessage,
119
+ getConstructorFromPrototype,
120
+ resolveAsyncFunctionConstructor,
121
+ runInSandbox,
122
+ specEndpoints,
123
+ successResult,
124
+ wrapValue,
125
+ };
@@ -1,5 +1,5 @@
1
1
  import { API_SPEC } from '../api-spec.js';
2
- import { AsyncFunction, errorResult, specEndpoints, successResult } from './sandbox.js';
2
+ import { errorResult, runInSandbox, specEndpoints, successResult } from './executor.js';
3
3
  import type { EndpointInfo, ToolResult } from '../types.js';
4
4
 
5
5
  const categories = [...new Set(API_SPEC.map((endpoint) => endpoint.category))].toSorted((a, b) => a.localeCompare(b)).join(', ');
@@ -47,8 +47,7 @@ async () => {
47
47
 
48
48
  async function handleExplore(code: string): Promise<ToolResult> {
49
49
  try {
50
- const executor = new AsyncFunction('spec', `return (${code})()`);
51
- const result: unknown = await executor({ endpoints: specEndpoints });
50
+ const result: unknown = await runInSandbox(code, { spec: { endpoints: specEndpoints } });
52
51
  return successResult(result);
53
52
  } catch (error: unknown) {
54
53
  return errorResult(error);
@@ -1,5 +1,5 @@
1
1
  import { createProxiedRequest } from '../request.js';
2
- import { AsyncFunction, errorResult, specEndpoints, successResult } from './sandbox.js';
2
+ import { errorResult, runInSandbox, specEndpoints, successResult } from './executor.js';
3
3
  import type { FetchFunction, RequestFunction, ToolResult } from '../types.js';
4
4
 
5
5
  const EXECUTE_DESCRIPTION = `Execute X (Twitter) API calls: post tweets, reply, like, retweet, follow, DM, update profile, upload media, search tweets, look up users, extract data, run giveaways, monitor accounts, compose tweets, and more. Write an async arrow function.
@@ -206,11 +206,12 @@ async () => {
206
206
  body: { toolType: 'follower_explorer', targetUsername: 'elonmusk', resultsLimit: 1000 }
207
207
  });
208
208
  return job;
209
- // 20 tool types: reply_extractor, repost_extractor, quote_extractor, thread_extractor,
209
+ // 23 tool types: reply_extractor, repost_extractor, quote_extractor, thread_extractor,
210
210
  // article_extractor, follower_explorer, following_explorer, verified_follower_explorer,
211
211
  // mention_extractor, post_extractor, community_extractor, community_moderator_explorer,
212
212
  // community_post_extractor, community_search, list_member_extractor, list_post_extractor,
213
- // list_follower_explorer, space_explorer, people_search, tweet_search_extractor
213
+ // list_follower_explorer, space_explorer, people_search, tweet_search_extractor,
214
+ // favoriters, user_likes, user_media
214
215
  }
215
216
  \`\`\`
216
217
 
@@ -297,11 +298,11 @@ async () => {
297
298
 
298
299
  ## Cost
299
300
  - 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
+ - MPP pay-per-use (no account/subscription needed, 16 endpoints): GET /api/v1/x/tweets/:id ($0.00015/call), GET /api/v1/x/tweets/search ($0.00015/tweet), GET /api/v1/x/users/:username ($0.00015/call), GET /api/v1/x/users/:id/tweets ($0.00015/tweet), GET /api/v1/x/followers/check ($0.00105/call), GET /api/v1/x/articles/:tweetId ($0.00105/call), POST /api/v1/x/media/download ($0.00015/media), GET /api/v1/trends ($0.00045/call), GET /api/v1/x/trends ($0.00045/call), GET /api/v1/x/tweets/:id/quotes ($0.00015/tweet), GET /api/v1/x/tweets/:id/replies ($0.00015/tweet), GET /api/v1/x/tweets/:id/retweeters ($0.00015/user), GET /api/v1/x/tweets/:id/favoriters ($0.00015/user), GET /api/v1/x/tweets/:id/thread ($0.00015/tweet), GET /api/v1/x/users/:id/likes ($0.00015/tweet), GET /api/v1/x/users/:id/media ($0.00015/tweet)
301
302
  - 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
302
303
  - 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
303
304
  - 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.
305
+ - MPP MODE: When configured with tempoPrivateKey (no API key), the mppx SDK auto-handles 402 challenges by paying via Tempo (USDC). Only the 16 MPP-eligible endpoints work in this mode.
305
306
 
306
307
  ## Error handling
307
308
  - If response contains "subscription is inactive" or status 402, call POST /api/v1/subscribe to get checkout URL
@@ -324,10 +325,9 @@ async function handleTweetclaw(options: Readonly<TweetclawOptions>): Promise<Too
324
325
  const { apiKey, baseUrl, code, fetchFunction, timeoutMs = EXECUTION_TIMEOUT_MS } = options;
325
326
  try {
326
327
  const request: RequestFunction = createProxiedRequest(baseUrl, apiKey, fetchFunction);
327
- const executor = new AsyncFunction('xquik', 'spec', `return (${code})()`);
328
328
 
329
329
  const result: unknown = await Promise.race([
330
- executor({ request }, { endpoints: specEndpoints }),
330
+ runInSandbox(code, { xquik: { request }, spec: { endpoints: specEndpoints } }),
331
331
  new Promise<never>((_resolve, reject) => {
332
332
  setTimeout(() => {
333
333
  reject(new Error(`Execution timed out after ${String(timeoutMs / MS_PER_SECOND)}s`));
@@ -1,58 +0,0 @@
1
- import { API_SPEC } from '../api-spec.js';
2
- import { truncateResponse } from '../truncate.js';
3
- import type { ToolResult } from '../types.js';
4
-
5
- const specEndpoints: ReadonlyArray<Readonly<Record<string, unknown>>> = API_SPEC.map(
6
- (endpoint): Readonly<Record<string, unknown>> => ({ ...endpoint }),
7
- );
8
-
9
- function extractErrorMessage(error: unknown): string {
10
- if (error instanceof Error) {
11
- return `${error.constructor.name}: ${error.message}`;
12
- }
13
- return String(error);
14
- }
15
-
16
- function isAsyncFunctionConstructor(
17
- value: unknown,
18
- ): value is new (...parameters: readonly string[]) => (...parameters: readonly unknown[]) => Promise<unknown> {
19
- return typeof value === 'function';
20
- }
21
-
22
- function getConstructorFromPrototype(proto: unknown): unknown {
23
- if (typeof proto !== 'object' || proto === null) {
24
- return undefined;
25
- }
26
- return 'constructor' in proto ? proto.constructor : undefined;
27
- }
28
-
29
- function resolveAsyncFunctionConstructor(prototype?: unknown): new (
30
- ...parameters: readonly string[]
31
- ) => (...parameters: readonly unknown[]) => Promise<unknown> {
32
- const asyncPrototype: unknown = prototype ?? Object.getPrototypeOf(async (): Promise<void> => {});
33
- const candidate: unknown = getConstructorFromPrototype(asyncPrototype);
34
- if (!isAsyncFunctionConstructor(candidate)) {
35
- throw new Error('AsyncFunction constructor not found');
36
- }
37
- return candidate;
38
- }
39
-
40
- const AsyncFunction = resolveAsyncFunctionConstructor();
41
-
42
- function successResult(content: unknown): ToolResult {
43
- return { content: [{ text: truncateResponse(content), type: 'text' as const }] };
44
- }
45
-
46
- function errorResult(error: unknown): ToolResult {
47
- return { content: [{ text: extractErrorMessage(error), type: 'text' as const }], isError: true };
48
- }
49
-
50
- export {
51
- AsyncFunction,
52
- errorResult,
53
- extractErrorMessage,
54
- getConstructorFromPrototype,
55
- resolveAsyncFunctionConstructor,
56
- specEndpoints,
57
- successResult,
58
- };