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.
Files changed (212) hide show
  1. package/README.md +165 -446
  2. package/bin/darwin-arm64/macos-bridge +0 -0
  3. package/dist/mcp-desktop.js +3615 -400
  4. package/dist/scripts/export-help-center.js +112 -0
  5. package/dist/scripts/marketing-loop.js +117 -0
  6. package/dist/scripts/observer-daemon.js +288 -0
  7. package/dist/scripts/orchestrator-daemon.js +399 -0
  8. package/dist/scripts/threads-campaign.js +208 -0
  9. package/dist/src/community/fetcher.js +109 -0
  10. package/dist/src/community/index.js +6 -0
  11. package/dist/src/community/publisher.js +191 -0
  12. package/dist/src/community/remote-api.js +121 -0
  13. package/dist/src/community/types.js +3 -0
  14. package/dist/src/community/validator.js +95 -0
  15. package/dist/src/context-tracker.js +489 -0
  16. package/dist/src/ingestion/coverage-auditor.js +233 -0
  17. package/dist/src/ingestion/doc-parser.js +164 -0
  18. package/dist/src/ingestion/index.js +8 -0
  19. package/dist/src/ingestion/menu-scanner.js +152 -0
  20. package/dist/src/ingestion/reference-merger.js +186 -0
  21. package/dist/src/ingestion/shortcut-extractor.js +180 -0
  22. package/dist/src/ingestion/tutorial-extractor.js +170 -0
  23. package/dist/src/ingestion/types.js +3 -0
  24. package/dist/src/jobs/manager.js +82 -14
  25. package/dist/src/jobs/runner.js +138 -15
  26. package/dist/src/learning/engine.js +356 -0
  27. package/dist/src/learning/index.js +9 -0
  28. package/dist/src/learning/locator-policy.js +120 -0
  29. package/dist/src/learning/pattern-policy.js +89 -0
  30. package/dist/src/learning/recovery-policy.js +116 -0
  31. package/dist/src/learning/sensor-policy.js +115 -0
  32. package/dist/src/learning/timing-model.js +204 -0
  33. package/dist/src/learning/topology-policy.js +90 -0
  34. package/dist/src/learning/types.js +9 -0
  35. package/dist/src/logging/timeline-logger.js +4 -1
  36. package/dist/src/memory/playbook-seeds.js +200 -0
  37. package/dist/src/memory/recall.js +60 -8
  38. package/dist/src/memory/service.js +30 -5
  39. package/dist/src/memory/store.js +34 -5
  40. package/dist/src/native/bridge-client.js +253 -31
  41. package/dist/src/observer/state.js +199 -0
  42. package/dist/src/observer/types.js +43 -0
  43. package/dist/src/orchestrator/state.js +68 -0
  44. package/dist/src/orchestrator/types.js +22 -0
  45. package/dist/src/perception/ax-source.js +162 -0
  46. package/dist/src/perception/cdp-source.js +162 -0
  47. package/dist/src/perception/coordinator.js +771 -0
  48. package/dist/src/perception/frame-differ.js +287 -0
  49. package/dist/src/perception/index.js +22 -0
  50. package/dist/src/perception/manager.js +199 -0
  51. package/dist/src/perception/types.js +47 -0
  52. package/dist/src/perception/vision-source.js +399 -0
  53. package/dist/src/planner/deterministic.js +298 -0
  54. package/dist/src/planner/executor.js +870 -0
  55. package/dist/src/planner/goal-store.js +92 -0
  56. package/dist/src/planner/index.js +21 -0
  57. package/dist/src/planner/planner.js +520 -0
  58. package/dist/src/planner/tool-registry.js +71 -0
  59. package/dist/src/planner/types.js +22 -0
  60. package/dist/src/platform/explorer.js +213 -0
  61. package/dist/src/platform/help-center-markdown.js +527 -0
  62. package/dist/src/platform/learner.js +257 -0
  63. package/dist/src/playbook/engine.js +296 -11
  64. package/dist/src/playbook/mcp-recorder.js +204 -0
  65. package/dist/src/playbook/recorder.js +3 -2
  66. package/dist/src/playbook/runner.js +1 -1
  67. package/dist/src/playbook/store.js +139 -10
  68. package/dist/src/recovery/detectors.js +156 -0
  69. package/dist/src/recovery/engine.js +327 -0
  70. package/dist/src/recovery/index.js +20 -0
  71. package/dist/src/recovery/strategies.js +274 -0
  72. package/dist/src/recovery/types.js +20 -0
  73. package/dist/src/runtime/accessibility-adapter.js +55 -18
  74. package/dist/src/runtime/applescript-adapter.js +8 -2
  75. package/dist/src/runtime/cdp-chrome-adapter.js +1 -1
  76. package/dist/src/runtime/executor.js +23 -3
  77. package/dist/src/runtime/locator-cache.js +24 -2
  78. package/dist/src/runtime/service.js +59 -15
  79. package/dist/src/runtime/session-manager.js +4 -1
  80. package/dist/src/runtime/vision-adapter.js +2 -1
  81. package/dist/src/state/app-map-types.js +72 -0
  82. package/dist/src/state/app-map.js +1974 -0
  83. package/dist/src/state/entity-tracker.js +108 -0
  84. package/dist/src/state/fusion.js +96 -0
  85. package/dist/src/state/index.js +21 -0
  86. package/dist/src/state/ladder-generator.js +236 -0
  87. package/dist/src/state/persistence.js +156 -0
  88. package/dist/src/state/types.js +17 -0
  89. package/dist/src/state/world-model.js +1456 -0
  90. package/dist/src/util/atomic-write.js +19 -4
  91. package/dist/src/util/sanitize.js +146 -0
  92. package/dist-app-maps/com.figma.Desktop.json +959 -0
  93. package/dist-app-maps/com.hnc.Discord.json +1146 -0
  94. package/dist-app-maps/notion.id.json +2831 -0
  95. package/dist-playbooks/canva-screenhand-carousel.json +445 -0
  96. package/dist-playbooks/codex-desktop.json +76 -0
  97. package/dist-playbooks/competitor-research-stack.json +122 -0
  98. package/dist-playbooks/davinci-color-grade.json +153 -0
  99. package/dist-playbooks/davinci-edit-timeline.json +162 -0
  100. package/dist-playbooks/davinci-render.json +114 -0
  101. package/dist-playbooks/devto.json +52 -0
  102. package/dist-playbooks/discord.json +41 -0
  103. package/dist-playbooks/google-flow-create-project.json +59 -0
  104. package/dist-playbooks/google-flow-edit-image.json +90 -0
  105. package/dist-playbooks/google-flow-edit-video.json +90 -0
  106. package/dist-playbooks/google-flow-generate-image.json +68 -0
  107. package/dist-playbooks/google-flow-generate-video.json +191 -0
  108. package/dist-playbooks/google-flow-open-project.json +48 -0
  109. package/dist-playbooks/google-flow-open-scenebuilder.json +64 -0
  110. package/dist-playbooks/google-flow-search-assets.json +64 -0
  111. package/dist-playbooks/instagram.json +57 -0
  112. package/dist-playbooks/linkedin.json +52 -0
  113. package/dist-playbooks/n8n.json +43 -0
  114. package/dist-playbooks/reddit.json +52 -0
  115. package/dist-playbooks/threads.json +59 -0
  116. package/dist-playbooks/x-twitter.json +59 -0
  117. package/dist-playbooks/youtube.json +59 -0
  118. package/dist-references/canva.json +646 -0
  119. package/dist-references/codex-desktop.json +305 -0
  120. package/dist-references/davinci-resolve-keyboard.json +594 -0
  121. package/dist-references/davinci-resolve-menu-map.json +1139 -0
  122. package/dist-references/davinci-resolve-menus-batch1.json +116 -0
  123. package/dist-references/davinci-resolve-menus-batch2.json +372 -0
  124. package/dist-references/davinci-resolve-menus-batch3.json +330 -0
  125. package/dist-references/davinci-resolve-menus-batch4.json +297 -0
  126. package/dist-references/davinci-resolve-shortcuts.json +333 -0
  127. package/dist-references/devpost.json +186 -0
  128. package/dist-references/devto.json +317 -0
  129. package/dist-references/discord.json +549 -0
  130. package/dist-references/figma.json +1186 -0
  131. package/dist-references/finder.json +146 -0
  132. package/dist-references/google-ads-transparency.json +95 -0
  133. package/dist-references/google-flow.json +649 -0
  134. package/dist-references/instagram.json +341 -0
  135. package/dist-references/linkedin.json +324 -0
  136. package/dist-references/meta-ad-library.json +86 -0
  137. package/dist-references/n8n.json +387 -0
  138. package/dist-references/notes.json +27 -0
  139. package/dist-references/notion.json +163 -0
  140. package/dist-references/reddit.json +341 -0
  141. package/dist-references/threads.json +337 -0
  142. package/dist-references/x-twitter.json +403 -0
  143. package/dist-references/youtube.json +373 -0
  144. package/native/macos-bridge/Package.swift +22 -0
  145. package/native/macos-bridge/Sources/AccessibilityBridge.swift +482 -0
  146. package/native/macos-bridge/Sources/AppManagement.swift +339 -0
  147. package/native/macos-bridge/Sources/CoreGraphicsBridge.swift +537 -0
  148. package/native/macos-bridge/Sources/ObserverBridge.swift +120 -0
  149. package/native/macos-bridge/Sources/StreamCapture.swift +136 -0
  150. package/native/macos-bridge/Sources/VisionBridge.swift +238 -0
  151. package/native/macos-bridge/Sources/main.swift +498 -0
  152. package/native/windows-bridge/AppManagement.cs +234 -0
  153. package/native/windows-bridge/InputBridge.cs +436 -0
  154. package/native/windows-bridge/Program.cs +270 -0
  155. package/native/windows-bridge/ScreenCapture.cs +453 -0
  156. package/native/windows-bridge/UIAutomationBridge.cs +571 -0
  157. package/native/windows-bridge/WindowsBridge.csproj +17 -0
  158. package/package.json +12 -1
  159. package/scripts/postinstall.cjs +127 -0
  160. package/dist/.audit-log.jsonl +0 -55
  161. package/dist/.screenhand/memory/.lock +0 -1
  162. package/dist/.screenhand/memory/actions.jsonl +0 -85
  163. package/dist/.screenhand/memory/errors.jsonl +0 -5
  164. package/dist/.screenhand/memory/errors.jsonl.bak +0 -4
  165. package/dist/.screenhand/memory/state.json +0 -35
  166. package/dist/.screenhand/memory/state.json.bak +0 -35
  167. package/dist/.screenhand/memory/strategies.jsonl +0 -12
  168. package/dist/agent/cli.js +0 -73
  169. package/dist/agent/loop.js +0 -258
  170. package/dist/config.js +0 -9
  171. package/dist/index.js +0 -56
  172. package/dist/logging/timeline-logger.js +0 -29
  173. package/dist/mcp/mcp-stdio-server.js +0 -448
  174. package/dist/mcp/server.js +0 -347
  175. package/dist/mcp-entry.js +0 -59
  176. package/dist/memory/recall.js +0 -160
  177. package/dist/memory/research.js +0 -98
  178. package/dist/memory/seeds.js +0 -89
  179. package/dist/memory/session.js +0 -161
  180. package/dist/memory/store.js +0 -391
  181. package/dist/memory/types.js +0 -4
  182. package/dist/monitor/codex-monitor.js +0 -377
  183. package/dist/monitor/task-queue.js +0 -84
  184. package/dist/monitor/types.js +0 -49
  185. package/dist/native/bridge-client.js +0 -174
  186. package/dist/native/macos-bridge-client.js +0 -5
  187. package/dist/npm-publish-helper.js +0 -117
  188. package/dist/npm-token-cdp.js +0 -113
  189. package/dist/npm-token-create.js +0 -135
  190. package/dist/npm-token-finish.js +0 -126
  191. package/dist/playbook/engine.js +0 -193
  192. package/dist/playbook/index.js +0 -4
  193. package/dist/playbook/recorder.js +0 -519
  194. package/dist/playbook/runner.js +0 -392
  195. package/dist/playbook/store.js +0 -166
  196. package/dist/playbook/types.js +0 -4
  197. package/dist/runtime/accessibility-adapter.js +0 -377
  198. package/dist/runtime/app-adapter.js +0 -48
  199. package/dist/runtime/applescript-adapter.js +0 -283
  200. package/dist/runtime/ax-role-map.js +0 -80
  201. package/dist/runtime/browser-adapter.js +0 -36
  202. package/dist/runtime/cdp-chrome-adapter.js +0 -505
  203. package/dist/runtime/composite-adapter.js +0 -205
  204. package/dist/runtime/executor.js +0 -250
  205. package/dist/runtime/locator-cache.js +0 -12
  206. package/dist/runtime/planning-loop.js +0 -47
  207. package/dist/runtime/service.js +0 -372
  208. package/dist/runtime/session-manager.js +0 -28
  209. package/dist/runtime/state-observer.js +0 -105
  210. package/dist/runtime/vision-adapter.js +0 -208
  211. package/dist/test-mcp-protocol.js +0 -138
  212. package/dist/types.js +0 -1
@@ -0,0 +1,341 @@
1
+ {
2
+ "id": "instagram",
3
+ "name": "Instagram Automation",
4
+ "description": "Battle-tested playbook for Instagram browser automation via CDP. Covers feed, search, like, comment, save, DM, create post, follow/unfollow, and profile viewing.",
5
+ "platform": "instagram",
6
+ "version": "4.0.0",
7
+ "urlPatterns": [
8
+ "*instagram.com*"
9
+ ],
10
+ "tags": [
11
+ "instagram",
12
+ "social",
13
+ "browser",
14
+ "cdp"
15
+ ],
16
+ "successCount": 0,
17
+ "failCount": 0,
18
+ "urls": {
19
+ "home": "https://www.instagram.com/",
20
+ "explore": "https://www.instagram.com/explore/",
21
+ "reels": "https://www.instagram.com/reels/",
22
+ "messages": "https://www.instagram.com/direct/inbox/",
23
+ "profile": "https://www.instagram.com/{username}/",
24
+ "post": "https://www.instagram.com/p/{shortcode}/",
25
+ "settings": "https://www.instagram.com/accounts/edit/"
26
+ },
27
+ "selectors": {
28
+ "nav": {
29
+ "home": "a[href='/']",
30
+ "search": "svg[aria-label='Search']",
31
+ "explore": "a[href='/explore/']",
32
+ "reels": "a[href='/reels/']",
33
+ "messages": "a[href='/direct/inbox/']",
34
+ "notifications": "svg[aria-label='Notifications']",
35
+ "create": "svg[aria-label='New post']",
36
+ "profile": "svg[aria-label='Profile']"
37
+ },
38
+ "search": {
39
+ "input": "input[placeholder='Search']",
40
+ "clear": "svg[aria-label='Clear search input']",
41
+ "result_link": "a[href^='/'][role='link']"
42
+ },
43
+ "post": {
44
+ "like_button": "svg[aria-label='Like']",
45
+ "unlike_button": "svg[aria-label='Unlike']",
46
+ "comment_button": "svg[aria-label='Comment']",
47
+ "share_button": "svg[aria-label='Share Post']",
48
+ "save_button": "svg[aria-label='Save']",
49
+ "unsave_button": "svg[aria-label='Remove']",
50
+ "more_options": "svg[aria-label='More options']",
51
+ "comment_input": "textarea[aria-label='Add a comment…']"
52
+ },
53
+ "feed": {
54
+ "article": "article",
55
+ "username": "article header a span",
56
+ "follow_button": "button:has-text('Follow')",
57
+ "like_count": "section span",
58
+ "caption": "span[dir='auto']"
59
+ },
60
+ "profile": {
61
+ "follow_button": "header button:has-text('Follow')",
62
+ "following_button": "header button:has-text('Following')",
63
+ "message_button": "header button:has-text('Message')",
64
+ "posts_count": "header section ul li:nth-child(1) span span",
65
+ "followers_count": "header section ul li:nth-child(2) a span span",
66
+ "following_count": "header section ul li:nth-child(3) a span span",
67
+ "post_grid": "article a[href^='/p/']"
68
+ },
69
+ "dm": {
70
+ "new_message": "svg[aria-label='New message']",
71
+ "search_input": "input[name='queryBox']",
72
+ "message_input": "div[role='textbox'][aria-label='Message']",
73
+ "send_button": "button:has-text('Send')",
74
+ "chat_button": "div[role='button']:has-text('Chat')"
75
+ },
76
+ "create_post": {
77
+ "new_post": "svg[aria-label='New post']",
78
+ "file_input": "input[type='file']",
79
+ "next_button": "button:has-text('Next')",
80
+ "share_button": "button:has-text('Share')",
81
+ "caption_input": "div[role='textbox'][aria-label*='caption']"
82
+ }
83
+ },
84
+ "detection": {
85
+ "is_logged_in": "!!document.querySelector('svg[aria-label=\"Home\"]')",
86
+ "is_feed": "window.location.pathname === '/'",
87
+ "is_explore": "window.location.pathname.startsWith('/explore')",
88
+ "is_profile": "/^\\/[a-zA-Z0-9._]+\\/?$/.test(window.location.pathname)",
89
+ "is_post": "window.location.pathname.startsWith('/p/')",
90
+ "is_dm": "window.location.pathname.startsWith('/direct/')",
91
+ "has_search_open": "!!document.querySelector('input[placeholder=\"Search\"]')",
92
+ "has_dialog_open": "!!document.querySelector('div[role=\"dialog\"]')"
93
+ },
94
+ "flows": {
95
+ "like_post": {
96
+ "steps": [
97
+ "Find Like button: svg[aria-label='Like'] in the post",
98
+ "Use browser_human_click on the Like SVG (realistic CDP mouse events)",
99
+ "Verify heart turned red: svg[aria-label='Unlike'] now exists"
100
+ ],
101
+ "guards": [
102
+ "Must be logged in",
103
+ "Post must not already be liked (no Unlike button)"
104
+ ],
105
+ "why": "browser_human_click dispatches mouseMoved -> mousePressed -> mouseReleased at element coordinates, bypassing Instagram's simple click detection"
106
+ },
107
+ "comment_on_post": {
108
+ "steps": [
109
+ "Find comment textarea: textarea[aria-label='Add a comment…']",
110
+ "Use browser_fill_form with ~80ms delay for human-like typing",
111
+ "Find Post button: element with textContent === 'Post'",
112
+ "Click Post button via browser_js (.click())",
113
+ "Wait 1.5s for comment to appear in the thread"
114
+ ],
115
+ "guards": [
116
+ "Must be logged in",
117
+ "Comments must be enabled on the post"
118
+ ]
119
+ },
120
+ "save_post": {
121
+ "steps": [
122
+ "Find Save button: svg[aria-label='Save']",
123
+ "Use browser_human_click on the Save SVG",
124
+ "Verify bookmark is filled: svg[aria-label='Remove'] now exists"
125
+ ],
126
+ "guards": [
127
+ "Must be logged in",
128
+ "Post must not already be saved"
129
+ ]
130
+ },
131
+ "send_dm": {
132
+ "steps": [
133
+ "Navigate to instagram.com/direct/inbox/",
134
+ "Click New message: browser_human_click on svg[aria-label='New message']",
135
+ "Wait 1s for dialog to open",
136
+ "Type recipient username into input[name='queryBox'] via browser_fill_form",
137
+ "Wait 2s for search results to load",
138
+ "Click matching recipient from search results",
139
+ "Click Chat button to open conversation",
140
+ "Wait 1.5s for message input to appear",
141
+ "Type message into div[role='textbox'][aria-label='Message'] via browser_fill_form",
142
+ "Click Send button or find button with text 'Send'"
143
+ ],
144
+ "guards": [
145
+ "Must be logged in",
146
+ "Recipient must exist"
147
+ ]
148
+ },
149
+ "create_post": {
150
+ "steps": [
151
+ "Click New post: svg[aria-label='New post'] via its parent element",
152
+ "Wait 1.5s for 'Create new post' dialog to appear",
153
+ "Use file input (input[type='file']) to set image/video",
154
+ "Click Next to proceed to filters",
155
+ "Click Next again to proceed to caption",
156
+ "Type caption into caption textbox via browser_fill_form",
157
+ "Click Share to publish"
158
+ ],
159
+ "guards": [
160
+ "Must be logged in",
161
+ "File must be JPEG, PNG, AVIF, HEIC, or MP4"
162
+ ],
163
+ "why": "File input accepts: image/avif, image/jpeg, image/png, image/heic, image/heif, video/mp4, video/quicktime"
164
+ },
165
+ "follow_profile": {
166
+ "steps": [
167
+ "Navigate to instagram.com/{username}/",
168
+ "Wait 2s for profile to load",
169
+ "Find Follow button in header: header button with text 'Follow'",
170
+ "Click Follow button via browser_js",
171
+ "Verify button changed to 'Following'"
172
+ ],
173
+ "guards": [
174
+ "Must be logged in",
175
+ "Must not already be following"
176
+ ]
177
+ },
178
+ "unfollow_profile": {
179
+ "steps": [
180
+ "Find Following button in header: header button with text 'Following'",
181
+ "Click Following button to open unfollow menu",
182
+ "Wait 1s for menu to appear",
183
+ "Click Unfollow button in the popup",
184
+ "Verify button changed back to 'Follow'"
185
+ ],
186
+ "guards": [
187
+ "Must be logged in",
188
+ "Must currently be following"
189
+ ]
190
+ },
191
+ "search_profile": {
192
+ "steps": [
193
+ "Click Search icon: svg[aria-label='Search'] in sidebar",
194
+ "Wait 500ms for search input to appear",
195
+ "Type search query into input[placeholder='Search'] via browser_fill_form",
196
+ "Wait 2s for results to load",
197
+ "Extract profile results from search panel",
198
+ "Click first matching profile link"
199
+ ],
200
+ "guards": [
201
+ "Must be logged in"
202
+ ]
203
+ },
204
+ "scroll_and_extract_feed": {
205
+ "steps": [
206
+ "Navigate to instagram.com",
207
+ "Verify logged in (check for Home icon)",
208
+ "Extract visible posts (username, likes, caption) from article elements",
209
+ "Scroll down to load more content",
210
+ "Wait 1s for lazy-loaded posts",
211
+ "Extract next batch of posts"
212
+ ],
213
+ "guards": [
214
+ "Must be logged in"
215
+ ]
216
+ },
217
+ "view_profile": {
218
+ "steps": [
219
+ "Navigate to instagram.com/{username}",
220
+ "Wait 2s for profile header to load",
221
+ "Extract posts, followers, following counts from header",
222
+ "Extract first 12 post thumbnails from article a[href^='/p/']"
223
+ ],
224
+ "guards": [
225
+ "Must be logged in",
226
+ "Profile must not be private or must follow"
227
+ ]
228
+ }
229
+ },
230
+ "errors": [
231
+ {
232
+ "error": "Login required / redirected to accounts/login",
233
+ "context": "Any action when not logged in",
234
+ "solution": "Log in manually first. ScreenHand cannot bypass login.",
235
+ "severity": "high"
236
+ },
237
+ {
238
+ "error": "Rate limited / Action Blocked",
239
+ "context": "Too many likes, follows, or comments",
240
+ "solution": "Wait 15-30 minutes. Limits: ~60 likes/hr, ~20 follows/hr, ~20 comments/hr.",
241
+ "severity": "high"
242
+ },
243
+ {
244
+ "error": "Like button click not registering",
245
+ "context": "Instagram detects simple JS .click() on Like button",
246
+ "solution": "Use browser_human_click which dispatches realistic CDP mouse events (mouseMoved -> mousePressed -> mouseReleased).",
247
+ "severity": "high"
248
+ },
249
+ {
250
+ "error": "SVG element has (0,0) bounding box",
251
+ "context": "Some SVG elements return zero-dimension bounding rects",
252
+ "solution": "Walk up the parent chain to find an ancestor with real dimensions, or use the post-specific page where elements render properly.",
253
+ "severity": "medium"
254
+ },
255
+ {
256
+ "error": "Clicked wrong element (e.g. wrong Post button)",
257
+ "context": "Generic selectors like div[role='button'] match multiple elements",
258
+ "solution": "Use browser_js to find specific element by textContent match: document.querySelectorAll('button').find(b => b.textContent.trim() === 'Post')",
259
+ "severity": "medium"
260
+ },
261
+ {
262
+ "error": "Search input not found",
263
+ "context": "Search panel didn't open",
264
+ "solution": "Wait 500ms after clicking Search icon, then look for input[placeholder='Search'].",
265
+ "severity": "medium"
266
+ },
267
+ {
268
+ "error": "Element not found / selector returned null",
269
+ "context": "Instagram lazy-loads content",
270
+ "solution": "Scroll to ensure element is in viewport, then re-query.",
271
+ "severity": "medium"
272
+ },
273
+ {
274
+ "error": "Frontmost app switches to VS Code when approving tool calls",
275
+ "context": "AccessibilityAdapter attaches to frontmost app",
276
+ "solution": "Use CDP browser tools (browser_js, browser_human_click, browser_fill_form) — they work regardless of which app is frontmost.",
277
+ "severity": "high"
278
+ }
279
+ ],
280
+ "policyNotes": {
281
+ "rate_limits": [
282
+ "Likes: ~60/hr, ~150/day",
283
+ "Follows: ~20/hr, ~100/day",
284
+ "Comments: ~20/hr, ~60/day",
285
+ "DMs: ~50/day"
286
+ ],
287
+ "safety": [
288
+ "Never automate login",
289
+ "Add 2-5s random delays between actions",
290
+ "Use browser_stealth before interacting",
291
+ "Don't exceed rate limits",
292
+ "Use browser_human_click for Like/Save/Follow (not JS .click())",
293
+ "Use browser_fill_form with 50-100ms delays for typing (anti-detection)"
294
+ ],
295
+ "tool_preferences": [
296
+ "browser_human_click — for buttons Instagram protects (Like, Save, Follow)",
297
+ "browser_fill_form — for text inputs with human-like keystroke delays",
298
+ "browser_js — for finding elements by textContent, extracting data, clicking unprotected buttons",
299
+ "browser_navigate — for page navigation (works in background)"
300
+ ]
301
+ },
302
+ "steps": [
303
+ {
304
+ "action": "navigate",
305
+ "url": "https://www.instagram.com/",
306
+ "description": "Open Instagram home feed"
307
+ },
308
+ {
309
+ "action": "wait",
310
+ "ms": 1500,
311
+ "description": "Wait for feed to load"
312
+ },
313
+ {
314
+ "action": "extract",
315
+ "target": "article",
316
+ "format": "text",
317
+ "description": "Extract first visible post content"
318
+ },
319
+ {
320
+ "action": "scroll",
321
+ "direction": "down",
322
+ "amount": 5,
323
+ "description": "Scroll down to see more posts"
324
+ },
325
+ {
326
+ "action": "wait",
327
+ "ms": 1000,
328
+ "description": "Wait for new posts to load"
329
+ },
330
+ {
331
+ "action": "extract",
332
+ "target": "article",
333
+ "format": "text",
334
+ "description": "Extract posts after scroll"
335
+ },
336
+ {
337
+ "action": "screenshot",
338
+ "description": "Capture current feed state"
339
+ }
340
+ ]
341
+ }
@@ -0,0 +1,324 @@
1
+ {
2
+ "id": "linkedin",
3
+ "name": "LinkedIn Automation",
4
+ "description": "Battle-tested playbook for LinkedIn browser automation via CDP. Covers posting, liking, commenting, connecting, messaging, search, and profile management.",
5
+ "platform": "linkedin",
6
+ "version": "1.0.0",
7
+ "urlPatterns": [
8
+ "*linkedin.com*"
9
+ ],
10
+ "tags": [
11
+ "linkedin",
12
+ "social",
13
+ "professional",
14
+ "browser",
15
+ "cdp"
16
+ ],
17
+ "successCount": 0,
18
+ "failCount": 0,
19
+ "urls": {
20
+ "feed": "https://www.linkedin.com/feed/",
21
+ "profile": "https://www.linkedin.com/in/{slug}/",
22
+ "messaging": "https://www.linkedin.com/messaging/",
23
+ "new_message": "https://www.linkedin.com/messaging/thread/new/",
24
+ "notifications": "https://www.linkedin.com/notifications/",
25
+ "network": "https://www.linkedin.com/mynetwork/",
26
+ "search_all": "https://www.linkedin.com/search/results/all/?keywords={query}",
27
+ "search_people": "https://www.linkedin.com/search/results/people/?keywords={query}",
28
+ "search_posts": "https://www.linkedin.com/search/results/content/?keywords={query}",
29
+ "search_companies": "https://www.linkedin.com/search/results/companies/?keywords={query}",
30
+ "jobs": "https://www.linkedin.com/jobs/",
31
+ "settings": "https://www.linkedin.com/psettings/"
32
+ },
33
+ "selectors": {
34
+ "nav": {
35
+ "home": "button[aria-label*='Home']",
36
+ "network": "a[aria-label*='My Network']",
37
+ "jobs": "button[aria-label*='Jobs']",
38
+ "messaging": "a[aria-label*='Messaging']",
39
+ "notifications": "a[aria-label*='Notifications']",
40
+ "me": "button:has-text('Me')",
41
+ "search": "input[aria-label='Search']"
42
+ },
43
+ "post_creation": {
44
+ "start_post": "button:has-text('Start a post')",
45
+ "text_editor": ".ql-editor",
46
+ "post_button": "button:has-text('Post')",
47
+ "emoji_button": "button[aria-label*='Emoji']",
48
+ "rewrite_ai": "button:has-text('Rewrite with AI')"
49
+ },
50
+ "post_actions": {
51
+ "like": "button[aria-label='React Like']",
52
+ "unlike": "button[aria-label*='Remove Like'], button[aria-pressed='true'][aria-label*='Like']",
53
+ "comment": "button[aria-label='Comment']",
54
+ "repost": "button[aria-label*='Repost']",
55
+ "send": "button[aria-label='Send in a private message']"
56
+ },
57
+ "comment": {
58
+ "textbox": ".ql-editor[data-placeholder='Add a comment…']",
59
+ "submit": "button.comments-comment-box__submit-button, button:has-text('Post'):closest([class*='comment'])"
60
+ },
61
+ "connect": {
62
+ "invite_button": "button[aria-label*='Invite'][aria-label*='connect']",
63
+ "send_without_note": "button[aria-label='Send without a note']",
64
+ "send_now": "button[aria-label='Send now']",
65
+ "add_note": "button[aria-label='Add a note']"
66
+ },
67
+ "messaging": {
68
+ "compose": "button:has-text('Compose a new message')",
69
+ "recipient_input": "input[placeholder='Type a name or multiple names']",
70
+ "message_box": ".msg-form__contenteditable, div[role='textbox'][aria-label*='message']",
71
+ "send_button": "button[aria-label='Send'], button.msg-form__send-button",
72
+ "search_messages": "input[name='searchTerm']"
73
+ },
74
+ "profile": {
75
+ "connect_button": "button[aria-label*='connect']",
76
+ "follow_button": "button[aria-label*='Follow']",
77
+ "message_button": "button:has-text('Message')",
78
+ "more_button": "button[aria-label='More']",
79
+ "enhance_profile": "button:has-text('Enhance profile')"
80
+ },
81
+ "search": {
82
+ "input": "input[aria-label='Search']",
83
+ "results": ".search-results-container li, .reusable-search__result-container",
84
+ "filter_people": "button:has-text('People')",
85
+ "filter_posts": "button:has-text('Posts')",
86
+ "filter_companies": "button:has-text('Companies')"
87
+ }
88
+ },
89
+ "detection": {
90
+ "is_logged_in": "!!document.querySelector('button[aria-label*=\"Home\"]')",
91
+ "is_feed": "window.location.pathname === '/feed/' || window.location.pathname === '/feed'",
92
+ "is_profile": "window.location.pathname.startsWith('/in/')",
93
+ "is_messaging": "window.location.pathname.startsWith('/messaging')",
94
+ "is_search": "window.location.pathname.startsWith('/search')",
95
+ "is_network": "window.location.pathname.startsWith('/mynetwork')",
96
+ "has_post_dialog": "!!document.querySelector('[role=\"dialog\"] .ql-editor')"
97
+ },
98
+ "flows": {
99
+ "create_post": {
100
+ "steps": [
101
+ "Navigate to linkedin.com/feed/",
102
+ "Find and click 'Start a post' button (search by textContent)",
103
+ "Wait 1.5s for post creation dialog to open",
104
+ "Find text editor: .ql-editor (Quill editor)",
105
+ "Type post content via browser_fill_form with ~40ms delay",
106
+ "Click Post button in dialog",
107
+ "Wait 2s for post to publish"
108
+ ],
109
+ "guards": [
110
+ "Must be logged in"
111
+ ],
112
+ "why": "LinkedIn uses Quill editor (.ql-editor) for both posts and comments. The 'Start a post' button doesn't have an aria-label — find it by textContent match."
113
+ },
114
+ "like_post": {
115
+ "steps": [
116
+ "Find Like button: button[aria-label='React Like']",
117
+ "Use JS dispatch (mousedown + mouseup + click) on Like button",
118
+ "Verify: button aria-pressed changes or aria-label changes to include 'Remove'"
119
+ ],
120
+ "guards": [
121
+ "Must be logged in",
122
+ "Post must not already be liked"
123
+ ]
124
+ },
125
+ "comment_on_post": {
126
+ "steps": [
127
+ "Click Comment button: button[aria-label='Comment']",
128
+ "Wait 1.5s for comment box to expand",
129
+ "Find comment textbox: .ql-editor[data-placeholder='Add a comment…']",
130
+ "Type comment via browser_fill_form with ~60ms delay",
131
+ "Find Post button inside the comment area (not the main post dialog)",
132
+ "Click Post to submit comment"
133
+ ],
134
+ "guards": [
135
+ "Must be logged in"
136
+ ]
137
+ },
138
+ "send_connection": {
139
+ "steps": [
140
+ "Navigate to person's profile or search results",
141
+ "Find Connect/Invite button: button[aria-label*='Invite'][aria-label*='connect']",
142
+ "Click Connect button",
143
+ "Wait 1.5s for connection dialog to appear",
144
+ "Optionally click 'Add a note' and type a message",
145
+ "Click 'Send without a note' or 'Send now'",
146
+ "Connection request sent"
147
+ ],
148
+ "guards": [
149
+ "Must be logged in",
150
+ "Must not already be connected"
151
+ ]
152
+ },
153
+ "send_message": {
154
+ "steps": [
155
+ "Navigate to linkedin.com/messaging/thread/new/",
156
+ "Wait 1.5s for compose view to load",
157
+ "Type recipient name into input[placeholder='Type a name or multiple names']",
158
+ "Wait 1.5s for autocomplete results",
159
+ "Click matching recipient",
160
+ "Type message into .msg-form__contenteditable",
161
+ "Click Send button"
162
+ ],
163
+ "guards": [
164
+ "Must be logged in",
165
+ "Recipient must be a 1st-degree connection for free accounts"
166
+ ]
167
+ },
168
+ "search": {
169
+ "steps": [
170
+ "Navigate to linkedin.com/search/results/all/?keywords={query}",
171
+ "Wait 2s for results to load",
172
+ "Extract results from .search-results-container li",
173
+ "Filter by People/Posts/Companies tabs as needed"
174
+ ],
175
+ "guards": [
176
+ "Must be logged in"
177
+ ],
178
+ "why": "Direct URL navigation with encoded query is more reliable than typing in search bar."
179
+ },
180
+ "follow_profile": {
181
+ "steps": [
182
+ "Navigate to linkedin.com/in/{slug}/",
183
+ "Wait 2s for profile to load",
184
+ "Find Follow button: button[aria-label*='Follow']",
185
+ "Click Follow button",
186
+ "Verify button state changes"
187
+ ],
188
+ "guards": [
189
+ "Must be logged in"
190
+ ]
191
+ },
192
+ "view_profile": {
193
+ "steps": [
194
+ "Navigate to linkedin.com/in/{slug}/",
195
+ "Wait 2s for profile to load",
196
+ "Extract name from h1, headline from .text-body-medium",
197
+ "Scroll to see experience, education, skills"
198
+ ],
199
+ "guards": [
200
+ "Must be logged in"
201
+ ]
202
+ },
203
+ "scroll_and_extract_feed": {
204
+ "steps": [
205
+ "Navigate to linkedin.com/feed/",
206
+ "Wait 2s for feed to load",
207
+ "Extract visible posts from .feed-shared-update-v2 elements",
208
+ "Get author, text, engagement counts",
209
+ "Scroll down for more posts",
210
+ "Wait 1.5s for lazy-loaded content"
211
+ ],
212
+ "guards": [
213
+ "Must be logged in"
214
+ ]
215
+ }
216
+ },
217
+ "errors": [
218
+ {
219
+ "error": "Login required",
220
+ "context": "Any action when not logged in",
221
+ "solution": "Log in manually. LinkedIn uses email/password or SSO.",
222
+ "severity": "high"
223
+ },
224
+ {
225
+ "error": "'Start a post' button has no aria-label",
226
+ "context": "Cannot use aria-label selector to find post creation button",
227
+ "solution": "Search by textContent: find button where text === 'Start a post'.",
228
+ "severity": "medium"
229
+ },
230
+ {
231
+ "error": "Comment Post button conflicts with main Post button",
232
+ "context": "Both post creation and comment have a 'Post' button",
233
+ "solution": "Find the Post button that's closest to the comment area: b.closest('[class*=\"comment\"]')",
234
+ "severity": "medium"
235
+ },
236
+ {
237
+ "error": "Like button uses 'React Like' not just 'Like'",
238
+ "context": "aria-label is 'React Like' not 'Like'",
239
+ "solution": "Use button[aria-label='React Like'] selector.",
240
+ "severity": "low"
241
+ },
242
+ {
243
+ "error": "Repost button not visible on all posts",
244
+ "context": "Some posts don't show Repost option",
245
+ "solution": "Check if button[aria-label*='Repost'] exists before clicking.",
246
+ "severity": "low"
247
+ },
248
+ {
249
+ "error": "LinkedIn uses Quill editor (.ql-editor)",
250
+ "context": "Both posts and comments use contenteditable Quill editor divs",
251
+ "solution": "Use browser_fill_form with .ql-editor selector. For comments, use data-placeholder attribute to distinguish.",
252
+ "severity": "medium"
253
+ },
254
+ {
255
+ "error": "Messaging requires 1st-degree connection",
256
+ "context": "Free accounts can only message connections",
257
+ "solution": "Send connection request first, or use InMail (Premium).",
258
+ "severity": "medium"
259
+ },
260
+ {
261
+ "error": "Frontmost app switches to VS Code when approving tool calls",
262
+ "context": "AccessibilityAdapter attaches to frontmost app",
263
+ "solution": "Use CDP browser tools — they work regardless of which app is frontmost.",
264
+ "severity": "high"
265
+ }
266
+ ],
267
+ "policyNotes": {
268
+ "rate_limits": [
269
+ "Connection requests: ~100/week",
270
+ "Messages: ~50/day (free), more with Premium",
271
+ "Posts: ~5/day recommended",
272
+ "Comments: ~30/day",
273
+ "Profile views: LinkedIn tracks and limits automated viewing"
274
+ ],
275
+ "safety": [
276
+ "Never automate login",
277
+ "LinkedIn aggressively detects automation — add 5-15s random delays",
278
+ "Use browser_stealth before interacting",
279
+ "Don't exceed connection request limits",
280
+ "Use browser_fill_form with 40-80ms delays for human-like typing",
281
+ "LinkedIn may require CAPTCHA or phone verification if suspicious activity detected"
282
+ ],
283
+ "tool_preferences": [
284
+ "browser_fill_form — for Quill editor posts/comments, search, messaging",
285
+ "JS dispatch (mousedown+mouseup+click) — for Like button (React Like)",
286
+ "browser_human_click — for Comment, Connect buttons",
287
+ "browser_js — for finding elements by textContent, submitting forms",
288
+ "browser_navigate — for search (direct URL), profile/messaging navigation"
289
+ ]
290
+ },
291
+ "steps": [
292
+ {
293
+ "action": "navigate",
294
+ "url": "https://www.linkedin.com/feed/",
295
+ "description": "Open LinkedIn feed"
296
+ },
297
+ {
298
+ "action": "wait",
299
+ "ms": 2000,
300
+ "description": "Wait for feed to load"
301
+ },
302
+ {
303
+ "action": "extract",
304
+ "target": ".feed-shared-update-v2",
305
+ "format": "text",
306
+ "description": "Extract visible posts"
307
+ },
308
+ {
309
+ "action": "scroll",
310
+ "direction": "down",
311
+ "amount": 5,
312
+ "description": "Scroll for more posts"
313
+ },
314
+ {
315
+ "action": "wait",
316
+ "ms": 1500,
317
+ "description": "Wait for lazy-loaded posts"
318
+ },
319
+ {
320
+ "action": "screenshot",
321
+ "description": "Capture feed state"
322
+ }
323
+ ]
324
+ }