machinaos 0.0.21 → 0.0.23

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.
Files changed (60) hide show
  1. package/README.md +32 -6
  2. package/bin/cli.js +0 -0
  3. package/client/dist/assets/index-5BWZnM6b.js +703 -0
  4. package/client/dist/index.html +1 -1
  5. package/client/package.json +1 -1
  6. package/client/src/Dashboard.tsx +12 -5
  7. package/client/src/ParameterPanel.tsx +6 -5
  8. package/client/src/components/AIAgentNode.tsx +35 -16
  9. package/client/src/components/CredentialsModal.tsx +450 -5
  10. package/client/src/components/TeamMonitorNode.tsx +269 -0
  11. package/client/src/components/parameterPanel/InputSection.tsx +25 -0
  12. package/client/src/contexts/WebSocketContext.tsx +38 -0
  13. package/client/src/hooks/useApiKeys.ts +44 -0
  14. package/client/src/nodeDefinitions/specializedAgentNodes.ts +59 -3
  15. package/client/src/nodeDefinitions/twitterNodes.ts +441 -0
  16. package/client/src/nodeDefinitions/utilityNodes.ts +45 -1
  17. package/client/src/nodeDefinitions.ts +7 -1
  18. package/client/src/services/executionService.ts +4 -1
  19. package/install.sh +63 -1
  20. package/package.json +5 -2
  21. package/scripts/build.js +0 -0
  22. package/scripts/clean.js +0 -0
  23. package/scripts/daemon.js +0 -0
  24. package/scripts/docker.js +0 -0
  25. package/scripts/install.js +0 -0
  26. package/scripts/postinstall.js +29 -0
  27. package/scripts/preinstall.js +67 -0
  28. package/scripts/serve-client.js +0 -0
  29. package/scripts/start.js +0 -0
  30. package/scripts/stop.js +0 -0
  31. package/scripts/sync-version.js +0 -0
  32. package/server/Dockerfile +10 -15
  33. package/server/constants.py +20 -0
  34. package/server/core/database.py +443 -3
  35. package/server/main.py +9 -1
  36. package/server/models/database.py +112 -2
  37. package/server/pyproject.toml +3 -0
  38. package/server/requirements.txt +3 -0
  39. package/server/routers/twitter.py +390 -0
  40. package/server/routers/websocket.py +320 -0
  41. package/server/services/agent_team.py +266 -0
  42. package/server/services/ai.py +43 -0
  43. package/server/services/compaction.py +39 -4
  44. package/server/services/event_waiter.py +41 -0
  45. package/server/services/handlers/__init__.py +13 -0
  46. package/server/services/handlers/ai.py +66 -2
  47. package/server/services/handlers/tools.py +84 -0
  48. package/server/services/handlers/twitter.py +297 -0
  49. package/server/services/handlers/utility.py +91 -0
  50. package/server/services/node_executor.py +15 -1
  51. package/server/services/pricing.py +270 -0
  52. package/server/services/status_broadcaster.py +79 -0
  53. package/server/services/twitter_oauth.py +410 -0
  54. package/server/skills/social_agent/twitter-search-skill/SKILL.md +146 -0
  55. package/server/skills/social_agent/twitter-send-skill/SKILL.md +142 -0
  56. package/server/skills/social_agent/twitter-user-skill/SKILL.md +165 -0
  57. package/workflows/Zeenie_full.json +459 -0
  58. package/workflows/Zeenie_small.json +459 -0
  59. package/client/dist/assets/index-YVvAiByx.js +0 -703
  60. package/server/requirements-docker.txt +0 -86
@@ -0,0 +1,410 @@
1
+ """
2
+ Twitter/X OAuth 2.0 with PKCE - Browser-based login flow for X API v2.
3
+
4
+ Based on official X API documentation:
5
+ - Authorization URL: https://x.com/i/oauth2/authorize
6
+ - Token URL: https://api.x.com/2/oauth2/token
7
+ - Docs: https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code
8
+
9
+ Access tokens expire in 2 hours by default. Use offline.access scope for refresh tokens.
10
+ Authorization codes expire in 30 seconds.
11
+ """
12
+
13
+ import base64
14
+ import hashlib
15
+ import secrets
16
+ import time
17
+ from typing import Any, Dict, Optional
18
+ from urllib.parse import urlencode
19
+
20
+ import httpx
21
+
22
+ from core.logging import get_logger
23
+
24
+ logger = get_logger(__name__)
25
+
26
+ # X API OAuth 2.0 endpoints (updated URLs per latest docs)
27
+ AUTHORIZATION_URL = "https://x.com/i/oauth2/authorize"
28
+ TOKEN_URL = "https://api.x.com/2/oauth2/token"
29
+ REVOKE_URL = "https://api.x.com/2/oauth2/revoke"
30
+ USER_INFO_URL = "https://api.x.com/2/users/me"
31
+
32
+ # Required scopes for full Twitter integration
33
+ # See: https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code
34
+ DEFAULT_SCOPES = [
35
+ "tweet.read", # Read tweets
36
+ "tweet.write", # Post tweets
37
+ "users.read", # User lookup
38
+ "follows.read", # Read followers/following
39
+ "like.read", # Read likes
40
+ "like.write", # Like/unlike tweets
41
+ "offline.access", # Enable refresh tokens (access tokens expire in 2 hours)
42
+ ]
43
+
44
+ # In-memory store for PKCE state (production should use Redis/database)
45
+ _oauth_states: Dict[str, Dict[str, Any]] = {}
46
+
47
+
48
+ def _generate_code_verifier() -> str:
49
+ """
50
+ Generate a cryptographically random code verifier (43-128 chars).
51
+ Per RFC 7636, must be 43-128 characters from [A-Z, a-z, 0-9, -, ., _, ~].
52
+ """
53
+ # Generate 96 random bytes, encode to base64url (128 chars after stripping padding)
54
+ random_bytes = secrets.token_bytes(96)
55
+ verifier = base64.urlsafe_b64encode(random_bytes).rstrip(b"=").decode("ascii")
56
+ return verifier[:128]
57
+
58
+
59
+ def _generate_code_challenge(code_verifier: str) -> str:
60
+ """
61
+ Generate S256 code challenge from verifier.
62
+ challenge = BASE64URL(SHA256(code_verifier))
63
+ """
64
+ digest = hashlib.sha256(code_verifier.encode("ascii")).digest()
65
+ challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
66
+ return challenge
67
+
68
+
69
+ def _generate_state() -> str:
70
+ """Generate random state parameter for CSRF protection (up to 500 chars per X docs)."""
71
+ return secrets.token_urlsafe(32)
72
+
73
+
74
+ class TwitterOAuth:
75
+ """
76
+ Twitter/X OAuth 2.0 with PKCE flow implementation.
77
+
78
+ Usage:
79
+ oauth = TwitterOAuth(client_id="...", redirect_uri="http://localhost:3010/api/twitter/callback")
80
+
81
+ # Step 1: Generate authorization URL
82
+ auth_data = oauth.generate_authorization_url()
83
+ # Redirect user to auth_data["url"]
84
+
85
+ # Step 2: Handle callback and exchange code
86
+ tokens = await oauth.exchange_code(code="...", state="...")
87
+
88
+ # Step 3: Use access_token for API calls
89
+ user_info = await oauth.get_user_info(tokens["access_token"])
90
+ """
91
+
92
+ def __init__(
93
+ self,
94
+ client_id: str,
95
+ client_secret: Optional[str] = None,
96
+ redirect_uri: str = "http://localhost:3010/api/twitter/callback",
97
+ scopes: Optional[list] = None,
98
+ ):
99
+ """
100
+ Initialize Twitter OAuth client.
101
+
102
+ Args:
103
+ client_id: Twitter OAuth 2.0 Client ID (from Developer Portal)
104
+ client_secret: Client Secret (required for confidential clients, optional for public clients with PKCE)
105
+ redirect_uri: OAuth callback URL (must match Developer Portal settings exactly)
106
+ scopes: List of OAuth scopes (defaults to DEFAULT_SCOPES)
107
+ """
108
+ self.client_id = client_id
109
+ self.client_secret = client_secret
110
+ self.redirect_uri = redirect_uri
111
+ self.scopes = scopes or DEFAULT_SCOPES
112
+
113
+ def generate_authorization_url(self) -> Dict[str, str]:
114
+ """
115
+ Generate OAuth authorization URL with PKCE parameters.
116
+
117
+ Returns:
118
+ Dict with:
119
+ - url: Authorization URL to redirect user to
120
+ - state: State parameter (for verification in callback)
121
+ - code_verifier: PKCE code verifier (save for token exchange)
122
+ """
123
+ state = _generate_state()
124
+ code_verifier = _generate_code_verifier()
125
+ code_challenge = _generate_code_challenge(code_verifier)
126
+
127
+ # Store state for verification (with expiry tracking)
128
+ _oauth_states[state] = {
129
+ "code_verifier": code_verifier,
130
+ "created_at": time.time(),
131
+ }
132
+
133
+ params = {
134
+ "response_type": "code",
135
+ "client_id": self.client_id,
136
+ "redirect_uri": self.redirect_uri,
137
+ "scope": " ".join(self.scopes),
138
+ "state": state,
139
+ "code_challenge": code_challenge,
140
+ "code_challenge_method": "S256",
141
+ }
142
+
143
+ authorization_url = f"{AUTHORIZATION_URL}?{urlencode(params)}"
144
+
145
+ logger.info("Generated Twitter OAuth authorization URL", state=state[:8])
146
+
147
+ return {
148
+ "url": authorization_url,
149
+ "state": state,
150
+ "code_verifier": code_verifier,
151
+ }
152
+
153
+ async def exchange_code(self, code: str, state: str) -> Dict[str, Any]:
154
+ """
155
+ Exchange authorization code for access token.
156
+
157
+ Note: Authorization codes expire in 30 seconds per X docs.
158
+
159
+ Args:
160
+ code: Authorization code from callback
161
+ state: State parameter for verification
162
+
163
+ Returns:
164
+ Dict with:
165
+ - success: True/False
166
+ - access_token: Bearer token for API calls (expires in 2 hours)
167
+ - refresh_token: Token for refreshing access (if offline.access scope)
168
+ - expires_in: Token expiry in seconds
169
+ - scope: Granted scopes
170
+ """
171
+ # Verify state and get code_verifier
172
+ oauth_state = _oauth_states.pop(state, None)
173
+ if not oauth_state:
174
+ logger.error("Invalid or expired OAuth state", state=state[:8] if state else "None")
175
+ return {"success": False, "error": "Invalid or expired state"}
176
+
177
+ code_verifier = oauth_state["code_verifier"]
178
+
179
+ # Prepare token request body
180
+ data = {
181
+ "grant_type": "authorization_code",
182
+ "code": code,
183
+ "redirect_uri": self.redirect_uri,
184
+ "code_verifier": code_verifier,
185
+ }
186
+
187
+ # Authentication: Basic auth for confidential clients, client_id in body for public clients
188
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
189
+
190
+ if self.client_secret:
191
+ # Confidential client: use Basic auth
192
+ credentials = base64.b64encode(
193
+ f"{self.client_id}:{self.client_secret}".encode()
194
+ ).decode()
195
+ headers["Authorization"] = f"Basic {credentials}"
196
+ else:
197
+ # Public client: include client_id in body
198
+ data["client_id"] = self.client_id
199
+
200
+ try:
201
+ async with httpx.AsyncClient(timeout=30.0) as client:
202
+ response = await client.post(
203
+ TOKEN_URL,
204
+ data=data,
205
+ headers=headers,
206
+ )
207
+
208
+ if response.status_code != 200:
209
+ error_data = response.json() if response.text else {}
210
+ logger.error(
211
+ "Token exchange failed",
212
+ status=response.status_code,
213
+ error=error_data,
214
+ )
215
+ return {
216
+ "success": False,
217
+ "error": error_data.get("error_description", error_data.get("error", "Token exchange failed")),
218
+ }
219
+
220
+ token_data = response.json()
221
+ logger.info("Twitter OAuth token exchange successful")
222
+
223
+ return {
224
+ "success": True,
225
+ "access_token": token_data.get("access_token"),
226
+ "refresh_token": token_data.get("refresh_token"),
227
+ "expires_in": token_data.get("expires_in"),
228
+ "scope": token_data.get("scope"),
229
+ "token_type": token_data.get("token_type", "Bearer"),
230
+ }
231
+
232
+ except httpx.HTTPError as e:
233
+ logger.error("HTTP error during token exchange", error=str(e))
234
+ return {"success": False, "error": str(e)}
235
+
236
+ async def refresh_access_token(self, refresh_token: str) -> Dict[str, Any]:
237
+ """
238
+ Refresh an expired access token.
239
+
240
+ Args:
241
+ refresh_token: The refresh token from previous authorization
242
+
243
+ Returns:
244
+ Dict with new access_token, refresh_token, expires_in
245
+ """
246
+ data = {
247
+ "grant_type": "refresh_token",
248
+ "refresh_token": refresh_token,
249
+ }
250
+
251
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
252
+
253
+ if self.client_secret:
254
+ credentials = base64.b64encode(
255
+ f"{self.client_id}:{self.client_secret}".encode()
256
+ ).decode()
257
+ headers["Authorization"] = f"Basic {credentials}"
258
+ else:
259
+ data["client_id"] = self.client_id
260
+
261
+ try:
262
+ async with httpx.AsyncClient(timeout=30.0) as client:
263
+ response = await client.post(
264
+ TOKEN_URL,
265
+ data=data,
266
+ headers=headers,
267
+ )
268
+
269
+ if response.status_code != 200:
270
+ error_data = response.json() if response.text else {}
271
+ logger.error(
272
+ "Token refresh failed",
273
+ status=response.status_code,
274
+ error=error_data,
275
+ )
276
+ return {
277
+ "success": False,
278
+ "error": error_data.get("error_description", "Token refresh failed"),
279
+ }
280
+
281
+ token_data = response.json()
282
+ logger.info("Twitter token refresh successful")
283
+
284
+ return {
285
+ "success": True,
286
+ "access_token": token_data.get("access_token"),
287
+ "refresh_token": token_data.get("refresh_token"),
288
+ "expires_in": token_data.get("expires_in"),
289
+ "scope": token_data.get("scope"),
290
+ }
291
+
292
+ except httpx.HTTPError as e:
293
+ logger.error("HTTP error during token refresh", error=str(e))
294
+ return {"success": False, "error": str(e)}
295
+
296
+ async def revoke_token(self, token: str, token_type: str = "access_token") -> Dict[str, Any]:
297
+ """
298
+ Revoke an access or refresh token.
299
+
300
+ Args:
301
+ token: The token to revoke
302
+ token_type: 'access_token' or 'refresh_token'
303
+
304
+ Returns:
305
+ Dict with success status
306
+ """
307
+ data = {
308
+ "token": token,
309
+ "token_type_hint": token_type,
310
+ }
311
+
312
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
313
+
314
+ if self.client_secret:
315
+ credentials = base64.b64encode(
316
+ f"{self.client_id}:{self.client_secret}".encode()
317
+ ).decode()
318
+ headers["Authorization"] = f"Basic {credentials}"
319
+ else:
320
+ data["client_id"] = self.client_id
321
+
322
+ try:
323
+ async with httpx.AsyncClient(timeout=30.0) as client:
324
+ response = await client.post(
325
+ REVOKE_URL,
326
+ data=data,
327
+ headers=headers,
328
+ )
329
+
330
+ # 200 = success, even if token was already revoked
331
+ if response.status_code == 200:
332
+ logger.info("Twitter token revoked successfully")
333
+ return {"success": True}
334
+ else:
335
+ error_data = response.json() if response.text else {}
336
+ return {
337
+ "success": False,
338
+ "error": error_data.get("error_description", "Revocation failed"),
339
+ }
340
+
341
+ except httpx.HTTPError as e:
342
+ logger.error("HTTP error during token revocation", error=str(e))
343
+ return {"success": False, "error": str(e)}
344
+
345
+ async def get_user_info(self, access_token: str) -> Dict[str, Any]:
346
+ """
347
+ Get authenticated user information.
348
+
349
+ Args:
350
+ access_token: Valid Twitter access token
351
+
352
+ Returns:
353
+ Dict with user id, username, name, profile_image_url, verified
354
+ """
355
+ try:
356
+ async with httpx.AsyncClient(timeout=30.0) as client:
357
+ response = await client.get(
358
+ USER_INFO_URL,
359
+ params={"user.fields": "id,name,username,profile_image_url,verified"},
360
+ headers={"Authorization": f"Bearer {access_token}"},
361
+ )
362
+
363
+ if response.status_code != 200:
364
+ error_data = response.json() if response.text else {}
365
+ error_detail = error_data.get("detail") or error_data.get("title", "Failed to get user info")
366
+ return {
367
+ "success": False,
368
+ "error": error_detail,
369
+ }
370
+
371
+ data = response.json()
372
+ user = data.get("data", {})
373
+
374
+ return {
375
+ "success": True,
376
+ "id": user.get("id"),
377
+ "username": user.get("username"),
378
+ "name": user.get("name"),
379
+ "profile_image_url": user.get("profile_image_url"),
380
+ "verified": user.get("verified", False),
381
+ }
382
+
383
+ except httpx.HTTPError as e:
384
+ logger.error("HTTP error getting user info", error=str(e))
385
+ return {"success": False, "error": str(e)}
386
+
387
+
388
+ def cleanup_expired_states(max_age_seconds: int = 600):
389
+ """
390
+ Remove OAuth states older than max_age_seconds (default 10 minutes).
391
+
392
+ Authorization codes expire in 30 seconds, so states older than a few
393
+ minutes are definitely stale.
394
+ """
395
+ current_time = time.time()
396
+ expired = [
397
+ state
398
+ for state, data in _oauth_states.items()
399
+ if current_time - data["created_at"] > max_age_seconds
400
+ ]
401
+ for state in expired:
402
+ _oauth_states.pop(state, None)
403
+
404
+ if expired:
405
+ logger.debug(f"Cleaned up {len(expired)} expired OAuth states")
406
+
407
+
408
+ def get_pending_state(state: str) -> Optional[Dict[str, Any]]:
409
+ """Get pending OAuth state without removing it (for verification)."""
410
+ return _oauth_states.get(state)
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: twitter-search-skill
3
+ description: Search for recent tweets on Twitter/X using keywords, hashtags, mentions, and advanced query operators.
4
+ allowed-tools: twitter_search
5
+ metadata:
6
+ author: machina
7
+ version: "1.0"
8
+ category: social
9
+ icon: "🔍"
10
+ color: "#000000"
11
+ ---
12
+
13
+ # Twitter Search Tool
14
+
15
+ Search for recent tweets on Twitter/X.
16
+
17
+ ## How It Works
18
+
19
+ This skill provides instructions for the **Twitter Search** tool node. Connect the **Twitter Search** node to an AI Agent's `input-tools` handle to enable tweet searching.
20
+
21
+ ## twitter_search Tool
22
+
23
+ Search for tweets matching a query.
24
+
25
+ ### Schema Fields
26
+
27
+ | Field | Type | Required | Description |
28
+ |-------|------|----------|-------------|
29
+ | query | string | Yes | Search query (supports operators) |
30
+ | max_results | integer | No | Number of results (10-100, default: 10) |
31
+
32
+ ### Query Operators
33
+
34
+ The X API supports advanced search operators:
35
+
36
+ | Operator | Example | Description |
37
+ |----------|---------|-------------|
38
+ | keyword | `python` | Tweets containing the word |
39
+ | phrase | `"machine learning"` | Exact phrase match |
40
+ | hashtag | `#AI` | Tweets with hashtag |
41
+ | mention | `@username` | Tweets mentioning user |
42
+ | from | `from:elonmusk` | Tweets by specific user |
43
+ | to | `to:username` | Replies to user |
44
+ | -keyword | `-spam` | Exclude keyword |
45
+ | OR | `python OR javascript` | Either term |
46
+ | lang | `lang:en` | Language filter |
47
+ | has:links | `AI has:links` | Tweets with URLs |
48
+ | has:media | `sunset has:media` | Tweets with media |
49
+ | is:retweet | `bitcoin is:retweet` | Only retweets |
50
+ | -is:retweet | `news -is:retweet` | Exclude retweets |
51
+
52
+ ### Examples
53
+
54
+ **Simple keyword search:**
55
+ ```json
56
+ {
57
+ "query": "artificial intelligence",
58
+ "max_results": 20
59
+ }
60
+ ```
61
+
62
+ **Search with hashtag:**
63
+ ```json
64
+ {
65
+ "query": "#MachineLearning",
66
+ "max_results": 50
67
+ }
68
+ ```
69
+
70
+ **Search tweets from a user:**
71
+ ```json
72
+ {
73
+ "query": "from:OpenAI",
74
+ "max_results": 10
75
+ }
76
+ ```
77
+
78
+ **Complex query:**
79
+ ```json
80
+ {
81
+ "query": "AI (startup OR company) -is:retweet lang:en",
82
+ "max_results": 100
83
+ }
84
+ ```
85
+
86
+ **Search with media:**
87
+ ```json
88
+ {
89
+ "query": "sunset has:media",
90
+ "max_results": 25
91
+ }
92
+ ```
93
+
94
+ ### Response Format
95
+
96
+ ```json
97
+ {
98
+ "success": true,
99
+ "result": {
100
+ "tweets": [
101
+ {
102
+ "id": "1234567890123456789",
103
+ "text": "Exciting developments in AI...",
104
+ "author_id": "987654321",
105
+ "created_at": "2025-02-19T10:30:00Z"
106
+ }
107
+ ],
108
+ "count": 20,
109
+ "query": "#AI"
110
+ },
111
+ "execution_time": 0.82
112
+ }
113
+ ```
114
+
115
+ ### Error Response
116
+
117
+ ```json
118
+ {
119
+ "success": false,
120
+ "error": "Search query is required",
121
+ "execution_time": 0.01
122
+ }
123
+ ```
124
+
125
+ ## Guidelines
126
+
127
+ 1. **Query length**: Keep queries concise for better results
128
+ 2. **Max results**: Limited to 100 per request (API constraint)
129
+ 3. **Recent tweets only**: X API v2 free tier searches recent tweets (last 7 days)
130
+ 4. **Rate limits**: Be mindful of API rate limits when searching repeatedly
131
+ 5. **Combine operators**: Use multiple operators for precise filtering
132
+
133
+ ## Common Use Cases
134
+
135
+ - Monitor brand mentions
136
+ - Track trending topics
137
+ - Find tweets about specific subjects
138
+ - Research competitor activity
139
+ - Gather content for curation
140
+ - Find influencers discussing topics
141
+
142
+ ## Setup Requirements
143
+
144
+ 1. Connect the **Twitter Search** node to an AI Agent's `input-tools` handle
145
+ 2. Ensure Twitter is connected (authenticated via OAuth in Credentials Modal)
146
+ 3. Your X Developer account must have appropriate API access level
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: twitter-send-skill
3
+ description: Post tweets, reply to tweets, retweet, like/unlike, and delete tweets on Twitter/X.
4
+ allowed-tools: twitter_send
5
+ metadata:
6
+ author: machina
7
+ version: "1.0"
8
+ category: social
9
+ icon: "📤"
10
+ color: "#000000"
11
+ ---
12
+
13
+ # Twitter Send Tool
14
+
15
+ Post and interact with tweets on Twitter/X.
16
+
17
+ ## How It Works
18
+
19
+ This skill provides instructions for the **Twitter Send** tool node. Connect the **Twitter Send** node to an AI Agent's `input-tools` handle to enable posting and interactions.
20
+
21
+ ## twitter_send Tool
22
+
23
+ Send tweets, replies, retweets, likes, and deletions.
24
+
25
+ ### Schema Fields
26
+
27
+ | Field | Type | Required | Description |
28
+ |-------|------|----------|-------------|
29
+ | action | string | Yes | Action type: `tweet`, `reply`, `retweet`, `like`, `unlike`, `delete` |
30
+ | text | string | If tweet/reply | Tweet text content (max 280 characters) |
31
+ | tweet_id | string | If retweet/like/unlike/delete | Target tweet ID |
32
+ | reply_to_id | string | If reply | Tweet ID to reply to |
33
+
34
+ ### Actions
35
+
36
+ | Action | Required Fields | Description |
37
+ |--------|-----------------|-------------|
38
+ | `tweet` | text | Post a new tweet |
39
+ | `reply` | text, reply_to_id | Reply to an existing tweet |
40
+ | `retweet` | tweet_id | Retweet an existing tweet |
41
+ | `like` | tweet_id | Like a tweet |
42
+ | `unlike` | tweet_id | Remove like from a tweet |
43
+ | `delete` | tweet_id | Delete your own tweet |
44
+
45
+ ### Examples
46
+
47
+ **Post a tweet:**
48
+ ```json
49
+ {
50
+ "action": "tweet",
51
+ "text": "Hello Twitter! This is my first automated tweet."
52
+ }
53
+ ```
54
+
55
+ **Reply to a tweet:**
56
+ ```json
57
+ {
58
+ "action": "reply",
59
+ "text": "Thanks for sharing this!",
60
+ "reply_to_id": "1234567890123456789"
61
+ }
62
+ ```
63
+
64
+ **Retweet:**
65
+ ```json
66
+ {
67
+ "action": "retweet",
68
+ "tweet_id": "1234567890123456789"
69
+ }
70
+ ```
71
+
72
+ **Like a tweet:**
73
+ ```json
74
+ {
75
+ "action": "like",
76
+ "tweet_id": "1234567890123456789"
77
+ }
78
+ ```
79
+
80
+ **Unlike a tweet:**
81
+ ```json
82
+ {
83
+ "action": "unlike",
84
+ "tweet_id": "1234567890123456789"
85
+ }
86
+ ```
87
+
88
+ **Delete a tweet:**
89
+ ```json
90
+ {
91
+ "action": "delete",
92
+ "tweet_id": "1234567890123456789"
93
+ }
94
+ ```
95
+
96
+ ### Response Format
97
+
98
+ ```json
99
+ {
100
+ "success": true,
101
+ "result": {
102
+ "data": {
103
+ "id": "1234567890123456789",
104
+ "text": "Hello Twitter!"
105
+ },
106
+ "action": "tweet_sent"
107
+ },
108
+ "execution_time": 0.45
109
+ }
110
+ ```
111
+
112
+ ### Error Response
113
+
114
+ ```json
115
+ {
116
+ "success": false,
117
+ "error": "Tweet text is required",
118
+ "execution_time": 0.01
119
+ }
120
+ ```
121
+
122
+ ## Guidelines
123
+
124
+ 1. **Character limit**: Tweets are limited to 280 characters
125
+ 2. **Tweet IDs**: Use the numeric ID string (e.g., `1234567890123456789`)
126
+ 3. **Rate limits**: X API has rate limits - avoid rapid posting
127
+ 4. **Content policy**: Follow X's content policies and terms of service
128
+ 5. **Threading**: Use reply action with reply_to_id to create threads
129
+
130
+ ## Common Use Cases
131
+
132
+ - Post automated updates and announcements
133
+ - Reply to mentions or specific tweets
134
+ - Like tweets matching certain criteria
135
+ - Create tweet threads by chaining replies
136
+ - Engage with followers programmatically
137
+
138
+ ## Setup Requirements
139
+
140
+ 1. Connect the **Twitter Send** node to an AI Agent's `input-tools` handle
141
+ 2. Ensure Twitter is connected (authenticated via OAuth in Credentials Modal)
142
+ 3. Your X Developer account must have appropriate API access level