screenhand 0.2.0 → 0.3.1
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 +165 -446
- package/bin/darwin-arm64/macos-bridge +0 -0
- package/dist/mcp-desktop.js +3615 -400
- package/dist/scripts/export-help-center.js +112 -0
- package/dist/scripts/marketing-loop.js +117 -0
- package/dist/scripts/observer-daemon.js +288 -0
- package/dist/scripts/orchestrator-daemon.js +399 -0
- package/dist/scripts/threads-campaign.js +208 -0
- package/dist/src/community/fetcher.js +109 -0
- package/dist/src/community/index.js +6 -0
- package/dist/src/community/publisher.js +191 -0
- package/dist/src/community/remote-api.js +121 -0
- package/dist/src/community/types.js +3 -0
- package/dist/src/community/validator.js +95 -0
- package/dist/src/context-tracker.js +489 -0
- package/dist/src/ingestion/coverage-auditor.js +233 -0
- package/dist/src/ingestion/doc-parser.js +164 -0
- package/dist/src/ingestion/index.js +8 -0
- package/dist/src/ingestion/menu-scanner.js +152 -0
- package/dist/src/ingestion/reference-merger.js +186 -0
- package/dist/src/ingestion/shortcut-extractor.js +180 -0
- package/dist/src/ingestion/tutorial-extractor.js +170 -0
- package/dist/src/ingestion/types.js +3 -0
- package/dist/src/jobs/manager.js +82 -14
- package/dist/src/jobs/runner.js +138 -15
- package/dist/src/learning/engine.js +356 -0
- package/dist/src/learning/index.js +9 -0
- package/dist/src/learning/locator-policy.js +120 -0
- package/dist/src/learning/pattern-policy.js +89 -0
- package/dist/src/learning/recovery-policy.js +116 -0
- package/dist/src/learning/sensor-policy.js +115 -0
- package/dist/src/learning/timing-model.js +204 -0
- package/dist/src/learning/topology-policy.js +90 -0
- package/dist/src/learning/types.js +9 -0
- package/dist/src/logging/timeline-logger.js +4 -1
- package/dist/src/memory/playbook-seeds.js +200 -0
- package/dist/src/memory/recall.js +60 -8
- package/dist/src/memory/service.js +30 -5
- package/dist/src/memory/store.js +34 -5
- package/dist/src/native/bridge-client.js +253 -31
- package/dist/src/observer/state.js +199 -0
- package/dist/src/observer/types.js +43 -0
- package/dist/src/orchestrator/state.js +68 -0
- package/dist/src/orchestrator/types.js +22 -0
- package/dist/src/perception/ax-source.js +162 -0
- package/dist/src/perception/cdp-source.js +162 -0
- package/dist/src/perception/coordinator.js +771 -0
- package/dist/src/perception/frame-differ.js +287 -0
- package/dist/src/perception/index.js +22 -0
- package/dist/src/perception/manager.js +199 -0
- package/dist/src/perception/types.js +47 -0
- package/dist/src/perception/vision-source.js +399 -0
- package/dist/src/planner/deterministic.js +298 -0
- package/dist/src/planner/executor.js +870 -0
- package/dist/src/planner/goal-store.js +92 -0
- package/dist/src/planner/index.js +21 -0
- package/dist/src/planner/planner.js +520 -0
- package/dist/src/planner/tool-registry.js +71 -0
- package/dist/src/planner/types.js +22 -0
- package/dist/src/platform/explorer.js +213 -0
- package/dist/src/platform/help-center-markdown.js +527 -0
- package/dist/src/platform/learner.js +257 -0
- package/dist/src/playbook/engine.js +296 -11
- package/dist/src/playbook/mcp-recorder.js +204 -0
- package/dist/src/playbook/recorder.js +3 -2
- package/dist/src/playbook/runner.js +1 -1
- package/dist/src/playbook/store.js +139 -10
- package/dist/src/recovery/detectors.js +156 -0
- package/dist/src/recovery/engine.js +327 -0
- package/dist/src/recovery/index.js +20 -0
- package/dist/src/recovery/strategies.js +274 -0
- package/dist/src/recovery/types.js +20 -0
- package/dist/src/runtime/accessibility-adapter.js +55 -18
- package/dist/src/runtime/applescript-adapter.js +8 -2
- package/dist/src/runtime/cdp-chrome-adapter.js +1 -1
- package/dist/src/runtime/executor.js +23 -3
- package/dist/src/runtime/locator-cache.js +24 -2
- package/dist/src/runtime/service.js +59 -15
- package/dist/src/runtime/session-manager.js +4 -1
- package/dist/src/runtime/vision-adapter.js +2 -1
- package/dist/src/state/app-map-types.js +72 -0
- package/dist/src/state/app-map.js +1974 -0
- package/dist/src/state/entity-tracker.js +108 -0
- package/dist/src/state/fusion.js +96 -0
- package/dist/src/state/index.js +21 -0
- package/dist/src/state/ladder-generator.js +236 -0
- package/dist/src/state/persistence.js +156 -0
- package/dist/src/state/types.js +17 -0
- package/dist/src/state/world-model.js +1456 -0
- package/dist/src/util/atomic-write.js +19 -4
- package/dist/src/util/sanitize.js +146 -0
- package/dist-app-maps/com.figma.Desktop.json +959 -0
- package/dist-app-maps/com.hnc.Discord.json +1146 -0
- package/dist-app-maps/notion.id.json +2831 -0
- package/dist-playbooks/canva-screenhand-carousel.json +445 -0
- package/dist-playbooks/codex-desktop.json +76 -0
- package/dist-playbooks/competitor-research-stack.json +122 -0
- package/dist-playbooks/davinci-color-grade.json +153 -0
- package/dist-playbooks/davinci-edit-timeline.json +162 -0
- package/dist-playbooks/davinci-render.json +114 -0
- package/dist-playbooks/devto.json +52 -0
- package/dist-playbooks/discord.json +41 -0
- package/dist-playbooks/google-flow-create-project.json +59 -0
- package/dist-playbooks/google-flow-edit-image.json +90 -0
- package/dist-playbooks/google-flow-edit-video.json +90 -0
- package/dist-playbooks/google-flow-generate-image.json +68 -0
- package/dist-playbooks/google-flow-generate-video.json +191 -0
- package/dist-playbooks/google-flow-open-project.json +48 -0
- package/dist-playbooks/google-flow-open-scenebuilder.json +64 -0
- package/dist-playbooks/google-flow-search-assets.json +64 -0
- package/dist-playbooks/instagram.json +57 -0
- package/dist-playbooks/linkedin.json +52 -0
- package/dist-playbooks/n8n.json +43 -0
- package/dist-playbooks/reddit.json +52 -0
- package/dist-playbooks/threads.json +59 -0
- package/dist-playbooks/x-twitter.json +59 -0
- package/dist-playbooks/youtube.json +59 -0
- package/dist-references/canva.json +646 -0
- package/dist-references/codex-desktop.json +305 -0
- package/dist-references/davinci-resolve-keyboard.json +594 -0
- package/dist-references/davinci-resolve-menu-map.json +1139 -0
- package/dist-references/davinci-resolve-menus-batch1.json +116 -0
- package/dist-references/davinci-resolve-menus-batch2.json +372 -0
- package/dist-references/davinci-resolve-menus-batch3.json +330 -0
- package/dist-references/davinci-resolve-menus-batch4.json +297 -0
- package/dist-references/davinci-resolve-shortcuts.json +333 -0
- package/dist-references/devpost.json +186 -0
- package/dist-references/devto.json +317 -0
- package/dist-references/discord.json +549 -0
- package/dist-references/figma.json +1186 -0
- package/dist-references/finder.json +146 -0
- package/dist-references/google-ads-transparency.json +95 -0
- package/dist-references/google-flow.json +649 -0
- package/dist-references/instagram.json +341 -0
- package/dist-references/linkedin.json +324 -0
- package/dist-references/meta-ad-library.json +86 -0
- package/dist-references/n8n.json +387 -0
- package/dist-references/notes.json +27 -0
- package/dist-references/notion.json +163 -0
- package/dist-references/reddit.json +341 -0
- package/dist-references/threads.json +337 -0
- package/dist-references/x-twitter.json +403 -0
- package/dist-references/youtube.json +373 -0
- package/native/macos-bridge/Package.swift +22 -0
- package/native/macos-bridge/Sources/AccessibilityBridge.swift +482 -0
- package/native/macos-bridge/Sources/AppManagement.swift +339 -0
- package/native/macos-bridge/Sources/CoreGraphicsBridge.swift +537 -0
- package/native/macos-bridge/Sources/ObserverBridge.swift +120 -0
- package/native/macos-bridge/Sources/StreamCapture.swift +136 -0
- package/native/macos-bridge/Sources/VisionBridge.swift +238 -0
- package/native/macos-bridge/Sources/main.swift +498 -0
- package/native/windows-bridge/AppManagement.cs +234 -0
- package/native/windows-bridge/InputBridge.cs +436 -0
- package/native/windows-bridge/Program.cs +270 -0
- package/native/windows-bridge/ScreenCapture.cs +453 -0
- package/native/windows-bridge/UIAutomationBridge.cs +571 -0
- package/native/windows-bridge/WindowsBridge.csproj +17 -0
- package/package.json +12 -1
- package/scripts/postinstall.cjs +127 -0
- package/dist/.audit-log.jsonl +0 -55
- package/dist/.screenhand/memory/.lock +0 -1
- package/dist/.screenhand/memory/actions.jsonl +0 -85
- package/dist/.screenhand/memory/errors.jsonl +0 -5
- package/dist/.screenhand/memory/errors.jsonl.bak +0 -4
- package/dist/.screenhand/memory/state.json +0 -35
- package/dist/.screenhand/memory/state.json.bak +0 -35
- package/dist/.screenhand/memory/strategies.jsonl +0 -12
- package/dist/agent/cli.js +0 -73
- package/dist/agent/loop.js +0 -258
- package/dist/config.js +0 -9
- package/dist/index.js +0 -56
- package/dist/logging/timeline-logger.js +0 -29
- package/dist/mcp/mcp-stdio-server.js +0 -448
- package/dist/mcp/server.js +0 -347
- package/dist/mcp-entry.js +0 -59
- package/dist/memory/recall.js +0 -160
- package/dist/memory/research.js +0 -98
- package/dist/memory/seeds.js +0 -89
- package/dist/memory/session.js +0 -161
- package/dist/memory/store.js +0 -391
- package/dist/memory/types.js +0 -4
- package/dist/monitor/codex-monitor.js +0 -377
- package/dist/monitor/task-queue.js +0 -84
- package/dist/monitor/types.js +0 -49
- package/dist/native/bridge-client.js +0 -174
- package/dist/native/macos-bridge-client.js +0 -5
- package/dist/npm-publish-helper.js +0 -117
- package/dist/npm-token-cdp.js +0 -113
- package/dist/npm-token-create.js +0 -135
- package/dist/npm-token-finish.js +0 -126
- package/dist/playbook/engine.js +0 -193
- package/dist/playbook/index.js +0 -4
- package/dist/playbook/recorder.js +0 -519
- package/dist/playbook/runner.js +0 -392
- package/dist/playbook/store.js +0 -166
- package/dist/playbook/types.js +0 -4
- package/dist/runtime/accessibility-adapter.js +0 -377
- package/dist/runtime/app-adapter.js +0 -48
- package/dist/runtime/applescript-adapter.js +0 -283
- package/dist/runtime/ax-role-map.js +0 -80
- package/dist/runtime/browser-adapter.js +0 -36
- package/dist/runtime/cdp-chrome-adapter.js +0 -505
- package/dist/runtime/composite-adapter.js +0 -205
- package/dist/runtime/executor.js +0 -250
- package/dist/runtime/locator-cache.js +0 -12
- package/dist/runtime/planning-loop.js +0 -47
- package/dist/runtime/service.js +0 -372
- package/dist/runtime/session-manager.js +0 -28
- package/dist/runtime/state-observer.js +0 -105
- package/dist/runtime/vision-adapter.js +0 -208
- package/dist/test-mcp-protocol.js +0 -138
- package/dist/types.js +0 -1
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "x-twitter",
|
|
3
|
+
"name": "X/Twitter Automation",
|
|
4
|
+
"description": "Battle-tested playbook for X/Twitter browser automation via CDP. Covers feed, like, reply, retweet, bookmark, create post, search, follow/unfollow, and profile viewing. X has excellent data-testid attributes for reliable selectors.",
|
|
5
|
+
"platform": "x-twitter",
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"urlPatterns": [
|
|
8
|
+
"*x.com*",
|
|
9
|
+
"*twitter.com*"
|
|
10
|
+
],
|
|
11
|
+
"tags": [
|
|
12
|
+
"x",
|
|
13
|
+
"twitter",
|
|
14
|
+
"social",
|
|
15
|
+
"browser",
|
|
16
|
+
"cdp"
|
|
17
|
+
],
|
|
18
|
+
"successCount": 0,
|
|
19
|
+
"failCount": 0,
|
|
20
|
+
"urls": {
|
|
21
|
+
"home": "https://x.com/home",
|
|
22
|
+
"explore": "https://x.com/explore",
|
|
23
|
+
"notifications": "https://x.com/notifications",
|
|
24
|
+
"messages": "https://x.com/messages",
|
|
25
|
+
"bookmarks": "https://x.com/i/bookmarks",
|
|
26
|
+
"profile": "https://x.com/{username}",
|
|
27
|
+
"post": "https://x.com/{username}/status/{tweetId}",
|
|
28
|
+
"search": "https://x.com/search?q={query}&src=typed_query",
|
|
29
|
+
"compose": "https://x.com/compose/post",
|
|
30
|
+
"settings": "https://x.com/settings"
|
|
31
|
+
},
|
|
32
|
+
"selectors": {
|
|
33
|
+
"nav": {
|
|
34
|
+
"home": "[data-testid='AppTabBar_Home_Link']",
|
|
35
|
+
"explore": "[data-testid='AppTabBar_Explore_Link']",
|
|
36
|
+
"notifications": "[data-testid='AppTabBar_Notifications_Link']",
|
|
37
|
+
"messages": "[data-testid='AppTabBar_DirectMessage_Link']",
|
|
38
|
+
"profile": "[data-testid='AppTabBar_Profile_Link']",
|
|
39
|
+
"compose": "[data-testid='SideNav_NewTweet_Button']",
|
|
40
|
+
"more_menu": "[data-testid='AppTabBar_More_Menu']"
|
|
41
|
+
},
|
|
42
|
+
"search": {
|
|
43
|
+
"input": "input[data-testid='SearchBox_Search_Input']",
|
|
44
|
+
"clear": "[data-testid='searchBoxClearButton']"
|
|
45
|
+
},
|
|
46
|
+
"tweet": {
|
|
47
|
+
"container": "article[data-testid='tweet']",
|
|
48
|
+
"username": "[data-testid='User-Name']",
|
|
49
|
+
"text": "[data-testid='tweetText']",
|
|
50
|
+
"photo": "[data-testid='tweetPhoto']",
|
|
51
|
+
"video": "[data-testid='videoPlayer']",
|
|
52
|
+
"caret": "[data-testid='caret']",
|
|
53
|
+
"verified": "[data-testid='icon-verified']"
|
|
54
|
+
},
|
|
55
|
+
"post_actions": {
|
|
56
|
+
"reply": "[data-testid='reply']",
|
|
57
|
+
"retweet": "[data-testid='retweet']",
|
|
58
|
+
"unretweet": "[data-testid='unretweet']",
|
|
59
|
+
"like": "[data-testid='like']",
|
|
60
|
+
"unlike": "[data-testid='unlike']",
|
|
61
|
+
"bookmark": "[data-testid='bookmark']",
|
|
62
|
+
"removeBookmark": "[data-testid='removeBookmark']",
|
|
63
|
+
"share": "button[aria-label='Share post']"
|
|
64
|
+
},
|
|
65
|
+
"compose": {
|
|
66
|
+
"textbox": "div[data-testid='tweetTextarea_0'][role='textbox']",
|
|
67
|
+
"post_button_inline": "[data-testid='tweetButtonInline']",
|
|
68
|
+
"post_button": "[data-testid='tweetButton']",
|
|
69
|
+
"media_button": "[data-testid='fileInput']",
|
|
70
|
+
"gif_button": "[data-testid='gifSearchButton']",
|
|
71
|
+
"emoji_button": "[data-testid='emojiButton']",
|
|
72
|
+
"poll_button": "[data-testid='pollButton']",
|
|
73
|
+
"schedule_button": "[data-testid='scheduleOption']"
|
|
74
|
+
},
|
|
75
|
+
"retweet_menu": {
|
|
76
|
+
"confirm": "[data-testid='retweetConfirm']",
|
|
77
|
+
"quote": "menuitem with text 'Quote'"
|
|
78
|
+
},
|
|
79
|
+
"profile": {
|
|
80
|
+
"follow": "[data-testid='{userId}-follow']",
|
|
81
|
+
"unfollow": "[data-testid='{userId}-unfollow']",
|
|
82
|
+
"edit_profile": "[data-testid='editProfileButton']",
|
|
83
|
+
"followers": "a[href='/{username}/verified_followers']",
|
|
84
|
+
"following": "a[href='/{username}/following']"
|
|
85
|
+
},
|
|
86
|
+
"dm": {
|
|
87
|
+
"new_chat": "[data-testid='dm-new-chat-button']",
|
|
88
|
+
"search_input": "input[data-testid='new-dm-search-input']",
|
|
89
|
+
"inbox_panel": "[data-testid='dm-inbox-panel']",
|
|
90
|
+
"conversation_item": "[data-testid*='dm-conversation-item']",
|
|
91
|
+
"composer_textarea": "[data-testid='dm-composer-textarea']",
|
|
92
|
+
"composer_form": "[data-testid='dm-composer-form']",
|
|
93
|
+
"attachment_button": "[data-testid='dm-composer-attachment-button']",
|
|
94
|
+
"gif_button": "[data-testid='dm-composer-gif-button']",
|
|
95
|
+
"emoji_button": "[data-testid='dm-composer-emoji-button']",
|
|
96
|
+
"message_text": "[data-testid^='message-text-']",
|
|
97
|
+
"message_list": "[data-testid='dm-message-list']"
|
|
98
|
+
},
|
|
99
|
+
"confirm_dialog": {
|
|
100
|
+
"confirm": "[data-testid='confirmationSheetConfirm']",
|
|
101
|
+
"cancel": "[data-testid='confirmationSheetCancel']"
|
|
102
|
+
},
|
|
103
|
+
"auto_discovered": {}
|
|
104
|
+
},
|
|
105
|
+
"detection": {
|
|
106
|
+
"is_logged_in": "!!document.querySelector('[data-testid=\"AppTabBar_Home_Link\"]')",
|
|
107
|
+
"is_feed": "window.location.pathname === '/home'",
|
|
108
|
+
"is_explore": "window.location.pathname === '/explore'",
|
|
109
|
+
"is_profile": "/^\\/[a-zA-Z0-9_]+\\/?$/.test(window.location.pathname) && !['home','explore','notifications','messages','settings','compose','search'].includes(window.location.pathname.replace(/\\//g,''))",
|
|
110
|
+
"is_tweet": "/\\/status\\/\\d+/.test(window.location.pathname)",
|
|
111
|
+
"is_search": "window.location.pathname === '/search'",
|
|
112
|
+
"is_dm": "window.location.pathname.startsWith('/messages') || window.location.pathname.startsWith('/i/chat')",
|
|
113
|
+
"has_dialog_open": "!!document.querySelector('div[role=\"dialog\"]')"
|
|
114
|
+
},
|
|
115
|
+
"flows": {
|
|
116
|
+
"like_post": {
|
|
117
|
+
"steps": [
|
|
118
|
+
"Find tweet: article[data-testid='tweet']",
|
|
119
|
+
"Find Like button: [data-testid='like'] within tweet",
|
|
120
|
+
"Use browser_human_click on the Like button",
|
|
121
|
+
"Verify: [data-testid='unlike'] now exists in that tweet"
|
|
122
|
+
],
|
|
123
|
+
"guards": [
|
|
124
|
+
"Must be logged in",
|
|
125
|
+
"Tweet must not already be liked (no unlike testid)"
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
"reply_to_tweet": {
|
|
129
|
+
"steps": [
|
|
130
|
+
"Find Reply button: [data-testid='reply'] in tweet",
|
|
131
|
+
"Use browser_human_click on Reply button",
|
|
132
|
+
"Wait 1.5s for reply dialog to open",
|
|
133
|
+
"Find textbox: div[data-testid='tweetTextarea_0'][role='textbox']",
|
|
134
|
+
"Type reply via browser_fill_form with ~70ms delay",
|
|
135
|
+
"Click post button: [data-testid='tweetButtonInline']",
|
|
136
|
+
"Wait 1s for reply to post"
|
|
137
|
+
],
|
|
138
|
+
"guards": [
|
|
139
|
+
"Must be logged in"
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
"retweet": {
|
|
143
|
+
"steps": [
|
|
144
|
+
"Find Retweet button: [data-testid='retweet'] in tweet",
|
|
145
|
+
"Use JS dispatch (mousedown + mouseup + click) on the retweet button",
|
|
146
|
+
"Wait 1s for retweet dropdown menu to appear",
|
|
147
|
+
"Click Repost: [data-testid='retweetConfirm']",
|
|
148
|
+
"Verify: [data-testid='unretweet'] now exists in that tweet"
|
|
149
|
+
],
|
|
150
|
+
"guards": [
|
|
151
|
+
"Must be logged in",
|
|
152
|
+
"Tweet must not already be retweeted"
|
|
153
|
+
],
|
|
154
|
+
"why": "browser_human_click does not reliably open the retweet menu on X. JS dispatch with full mousedown+mouseup+click sequence works."
|
|
155
|
+
},
|
|
156
|
+
"bookmark_tweet": {
|
|
157
|
+
"steps": [
|
|
158
|
+
"Find Bookmark button: [data-testid='bookmark'] in tweet",
|
|
159
|
+
"Use JS dispatch (mousedown + mouseup + click) on the bookmark button",
|
|
160
|
+
"Verify: [data-testid='removeBookmark'] now exists in that tweet"
|
|
161
|
+
],
|
|
162
|
+
"guards": [
|
|
163
|
+
"Must be logged in"
|
|
164
|
+
],
|
|
165
|
+
"why": "Like retweet, bookmark requires JS dispatch events, not browser_human_click."
|
|
166
|
+
},
|
|
167
|
+
"create_post": {
|
|
168
|
+
"steps": [
|
|
169
|
+
"Click compose button: [data-testid='SideNav_NewTweet_Button']",
|
|
170
|
+
"Wait 1.5s for compose dialog to open",
|
|
171
|
+
"Find textbox: div[data-testid='tweetTextarea_0'][role='textbox']",
|
|
172
|
+
"Type tweet via browser_fill_form with ~60ms delay",
|
|
173
|
+
"Click post button: [data-testid='tweetButtonInline'] or [data-testid='tweetButton']",
|
|
174
|
+
"Wait for tweet to publish"
|
|
175
|
+
],
|
|
176
|
+
"guards": [
|
|
177
|
+
"Must be logged in"
|
|
178
|
+
],
|
|
179
|
+
"why": "X has two post buttons: tweetButtonInline (in-page compose) and tweetButton (dialog compose). Try inline first."
|
|
180
|
+
},
|
|
181
|
+
"search": {
|
|
182
|
+
"steps": [
|
|
183
|
+
"Navigate to x.com/search?q={query}&src=typed_query",
|
|
184
|
+
"Wait 2s for search results to load",
|
|
185
|
+
"Extract tweets from article[data-testid='tweet'] elements"
|
|
186
|
+
],
|
|
187
|
+
"guards": [
|
|
188
|
+
"Must be logged in"
|
|
189
|
+
],
|
|
190
|
+
"why": "Direct URL navigation is more reliable than typing in search box + Enter. X's search input doesn't respond to JS keydown Enter events."
|
|
191
|
+
},
|
|
192
|
+
"follow_profile": {
|
|
193
|
+
"steps": [
|
|
194
|
+
"Navigate to x.com/{username}",
|
|
195
|
+
"Wait 2s for profile to load",
|
|
196
|
+
"Find Follow button: [data-testid='{userId}-follow']",
|
|
197
|
+
"Use JS dispatch (mousedown + mouseup + click) on Follow button",
|
|
198
|
+
"Verify: [data-testid='{userId}-unfollow'] now exists"
|
|
199
|
+
],
|
|
200
|
+
"guards": [
|
|
201
|
+
"Must be logged in",
|
|
202
|
+
"Must not already be following"
|
|
203
|
+
],
|
|
204
|
+
"why": "X follow buttons use data-testid format '{userId}-follow' where userId is numeric."
|
|
205
|
+
},
|
|
206
|
+
"unfollow_profile": {
|
|
207
|
+
"steps": [
|
|
208
|
+
"Find Following button: [data-testid='{userId}-unfollow']",
|
|
209
|
+
"Use JS dispatch click on Following button",
|
|
210
|
+
"Wait 1.5s for confirmation dialog",
|
|
211
|
+
"Click confirm: [data-testid='confirmationSheetConfirm']",
|
|
212
|
+
"Verify: [data-testid='{userId}-follow'] now exists"
|
|
213
|
+
],
|
|
214
|
+
"guards": [
|
|
215
|
+
"Must be logged in",
|
|
216
|
+
"Must currently be following"
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
"send_dm": {
|
|
220
|
+
"steps": [
|
|
221
|
+
"Navigate to x.com/messages (may require DM passcode)",
|
|
222
|
+
"Click new chat button: [data-testid='dm-new-chat-button'] via JS dispatch",
|
|
223
|
+
"Wait 1.5s for new DM dialog to open",
|
|
224
|
+
"Type recipient username into input[data-testid='new-dm-search-input'] via browser_fill_form",
|
|
225
|
+
"Wait 2s for search results to load",
|
|
226
|
+
"Click matching recipient from li elements in dialog via browser_human_click",
|
|
227
|
+
"Alternative: click existing conversation via [data-testid*='dm-conversation-item']",
|
|
228
|
+
"Wait for message composer to appear",
|
|
229
|
+
"Set message text in textarea[data-testid='dm-composer-textarea'] via native value setter + input event",
|
|
230
|
+
"Press Enter via keydown event to send",
|
|
231
|
+
"Verify: new message appears in [data-testid^='message-text-'] list"
|
|
232
|
+
],
|
|
233
|
+
"guards": [
|
|
234
|
+
"Must be logged in",
|
|
235
|
+
"DM passcode must be entered if enabled"
|
|
236
|
+
],
|
|
237
|
+
"why": "X DM textarea requires native value setter (Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set) to bypass React. browser_fill_form and browser_type don't persist values. Enter keydown sends the message."
|
|
238
|
+
},
|
|
239
|
+
"scroll_and_extract_feed": {
|
|
240
|
+
"steps": [
|
|
241
|
+
"Navigate to x.com/home",
|
|
242
|
+
"Verify logged in (check for AppTabBar_Home_Link)",
|
|
243
|
+
"Extract visible tweets: username from [data-testid='User-Name'], text from [data-testid='tweetText']",
|
|
244
|
+
"Scroll down to load more tweets",
|
|
245
|
+
"Wait 1s for lazy-loaded tweets",
|
|
246
|
+
"Extract next batch"
|
|
247
|
+
],
|
|
248
|
+
"guards": [
|
|
249
|
+
"Must be logged in"
|
|
250
|
+
]
|
|
251
|
+
},
|
|
252
|
+
"view_profile": {
|
|
253
|
+
"steps": [
|
|
254
|
+
"Navigate to x.com/{username}",
|
|
255
|
+
"Wait 2s for profile to load",
|
|
256
|
+
"Extract profile info: [data-testid='UserName'], [data-testid='UserDescription']",
|
|
257
|
+
"Extract followers/following counts",
|
|
258
|
+
"Extract pinned and recent tweets"
|
|
259
|
+
],
|
|
260
|
+
"guards": [
|
|
261
|
+
"Must be logged in",
|
|
262
|
+
"Profile must not be private or suspended"
|
|
263
|
+
]
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
"errors": [
|
|
267
|
+
{
|
|
268
|
+
"error": "Login required / redirected to login",
|
|
269
|
+
"context": "Any action when not logged in",
|
|
270
|
+
"solution": "Log in manually first. ScreenHand cannot bypass login.",
|
|
271
|
+
"severity": "high"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"error": "Rate limited / Action Blocked",
|
|
275
|
+
"context": "Too many likes, retweets, or replies",
|
|
276
|
+
"solution": "Wait 15-30 minutes. X rate limits are strict.",
|
|
277
|
+
"severity": "high"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"error": "Retweet/Bookmark menu not opening with browser_human_click",
|
|
281
|
+
"context": "CDP mouse events at element coordinates don't trigger X's React event handlers for dropdown menus",
|
|
282
|
+
"solution": "Use JS dispatch: el.dispatchEvent(new MouseEvent('mousedown', {bubbles:true})); el.dispatchEvent(new MouseEvent('mouseup', {bubbles:true})); el.dispatchEvent(new MouseEvent('click', {bubbles:true}));",
|
|
283
|
+
"severity": "high"
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"error": "Share button triggers native share sheet",
|
|
287
|
+
"context": "X's share post button opens the OS-level share sheet, not a web dropdown",
|
|
288
|
+
"solution": "Cannot capture via CDP. Use Copy Link from the tweet's more options (caret) menu instead, or construct the URL manually: x.com/{username}/status/{tweetId}.",
|
|
289
|
+
"severity": "medium"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"error": "Search doesn't submit on Enter key via JS",
|
|
293
|
+
"context": "JS keydown/keypress events for Enter don't trigger X's React search submission",
|
|
294
|
+
"solution": "Navigate directly to x.com/search?q={query}&src=typed_query instead of typing in search box.",
|
|
295
|
+
"severity": "medium"
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"error": "DM requires passcode pin",
|
|
299
|
+
"context": "X DMs are protected by a 4-digit passcode on the recovery page",
|
|
300
|
+
"solution": "Use browser_fill_form to type digit '1' into first input[type='text'][placeholder=' '] — inputs auto-advance. Or navigate directly if passcode was already entered this session.",
|
|
301
|
+
"severity": "medium"
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"error": "DM textarea doesn't hold value from browser_fill_form or browser_type",
|
|
305
|
+
"context": "X DM composer textarea uses React controlled input that ignores CDP key events",
|
|
306
|
+
"solution": "Use native value setter: Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set.call(textarea, text); then dispatch input event. Send with Enter keydown.",
|
|
307
|
+
"severity": "high"
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
"error": "New DM search result click doesn't select user",
|
|
311
|
+
"context": "Clicking li elements in new DM dialog doesn't always register",
|
|
312
|
+
"solution": "Use existing conversations via [data-testid*='dm-conversation-item'] with JS dispatch click as fallback.",
|
|
313
|
+
"severity": "medium"
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"error": "Tab IDs change after Chrome reconnect",
|
|
317
|
+
"context": "CDP tab IDs are regenerated when MCP server restarts",
|
|
318
|
+
"solution": "Always call browser_tabs first to get fresh tab IDs before interacting.",
|
|
319
|
+
"severity": "medium"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
"error": "Frontmost app switches to VS Code when approving tool calls",
|
|
323
|
+
"context": "AccessibilityAdapter attaches to frontmost app",
|
|
324
|
+
"solution": "Use CDP browser tools (browser_js, browser_human_click, browser_fill_form) — they work regardless of which app is frontmost.",
|
|
325
|
+
"severity": "high"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"error": "Focus failed: com.apple.Terminal is frontmost instead of com.apple.Safari",
|
|
329
|
+
"context": "tool: focus, domain: native:com.nonexistent.FakeApp",
|
|
330
|
+
"solution": "No resolution yet — investigate and update this entry",
|
|
331
|
+
"severity": "medium"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"error": "Focus failed: com.apple.Terminal is frontmost instead of com.apple.finder",
|
|
335
|
+
"context": "tool: focus, domain: native:com.nonexistent.FakeApp",
|
|
336
|
+
"solution": "No resolution yet — investigate and update this entry",
|
|
337
|
+
"severity": "medium"
|
|
338
|
+
}
|
|
339
|
+
],
|
|
340
|
+
"policyNotes": {
|
|
341
|
+
"rate_limits": [
|
|
342
|
+
"Likes: ~100/day (stricter than Instagram)",
|
|
343
|
+
"Retweets: ~50/day",
|
|
344
|
+
"Replies: ~50/day",
|
|
345
|
+
"Follows: ~50/day",
|
|
346
|
+
"Tweets: ~50/day (more for Premium)"
|
|
347
|
+
],
|
|
348
|
+
"safety": [
|
|
349
|
+
"Never automate login",
|
|
350
|
+
"Add 3-10s random delays between actions",
|
|
351
|
+
"Use browser_stealth before interacting",
|
|
352
|
+
"Don't exceed rate limits — X suspends aggressively",
|
|
353
|
+
"Some actions need JS dispatch instead of browser_human_click (retweet, bookmark, follow)",
|
|
354
|
+
"Use browser_fill_form with 50-100ms delays for typing"
|
|
355
|
+
],
|
|
356
|
+
"tool_preferences": [
|
|
357
|
+
"browser_human_click — for Like and Reply buttons",
|
|
358
|
+
"JS dispatch (mousedown+mouseup+click) — for Retweet, Bookmark, Follow (menus/state changes)",
|
|
359
|
+
"browser_fill_form — for tweet compose and reply textboxes",
|
|
360
|
+
"browser_navigate — for search (direct URL) and profile navigation",
|
|
361
|
+
"browser_js — for extracting data, clicking confirm dialogs, verifying state changes"
|
|
362
|
+
]
|
|
363
|
+
},
|
|
364
|
+
"steps": [
|
|
365
|
+
{
|
|
366
|
+
"action": "navigate",
|
|
367
|
+
"url": "https://x.com/home",
|
|
368
|
+
"description": "Open X home timeline"
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
"action": "wait",
|
|
372
|
+
"ms": 2000,
|
|
373
|
+
"description": "Wait for timeline to load"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"action": "extract",
|
|
377
|
+
"target": "article[data-testid='tweet']",
|
|
378
|
+
"format": "text",
|
|
379
|
+
"description": "Extract first visible tweets"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"action": "scroll",
|
|
383
|
+
"direction": "down",
|
|
384
|
+
"amount": 5,
|
|
385
|
+
"description": "Scroll down for more tweets"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"action": "wait",
|
|
389
|
+
"ms": 1000,
|
|
390
|
+
"description": "Wait for new tweets to load"
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
"action": "extract",
|
|
394
|
+
"target": "article[data-testid='tweet']",
|
|
395
|
+
"format": "text",
|
|
396
|
+
"description": "Extract tweets after scroll"
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
"action": "screenshot",
|
|
400
|
+
"description": "Capture current timeline state"
|
|
401
|
+
}
|
|
402
|
+
]
|
|
403
|
+
}
|