screenhand 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/README.md +193 -109
  2. package/bin/darwin-arm64/macos-bridge +0 -0
  3. package/dist/mcp-desktop.js +5876 -0
  4. package/dist/scripts/codex-monitor-daemon.js +335 -0
  5. package/dist/scripts/export-help-center.js +112 -0
  6. package/dist/scripts/marketing-loop.js +117 -0
  7. package/dist/scripts/observer-daemon.js +288 -0
  8. package/dist/scripts/orchestrator-daemon.js +399 -0
  9. package/dist/scripts/supervisor-daemon.js +272 -0
  10. package/dist/scripts/threads-campaign.js +208 -0
  11. package/dist/scripts/worker-daemon.js +228 -0
  12. package/dist/src/agent/cli.js +82 -0
  13. package/dist/src/agent/loop.js +274 -0
  14. package/dist/src/community/fetcher.js +109 -0
  15. package/dist/src/community/index.js +6 -0
  16. package/dist/src/community/publisher.js +191 -0
  17. package/dist/src/community/remote-api.js +121 -0
  18. package/dist/src/community/types.js +3 -0
  19. package/dist/src/community/validator.js +95 -0
  20. package/{src/config.ts → dist/src/config.js} +5 -10
  21. package/dist/src/context-tracker.js +489 -0
  22. package/{src/index.ts → dist/src/index.js} +32 -52
  23. package/dist/src/ingestion/coverage-auditor.js +233 -0
  24. package/dist/src/ingestion/doc-parser.js +164 -0
  25. package/dist/src/ingestion/index.js +8 -0
  26. package/dist/src/ingestion/menu-scanner.js +152 -0
  27. package/dist/src/ingestion/reference-merger.js +186 -0
  28. package/dist/src/ingestion/shortcut-extractor.js +180 -0
  29. package/dist/src/ingestion/tutorial-extractor.js +170 -0
  30. package/dist/src/ingestion/types.js +3 -0
  31. package/dist/src/jobs/manager.js +305 -0
  32. package/dist/src/jobs/runner.js +806 -0
  33. package/dist/src/jobs/store.js +102 -0
  34. package/dist/src/jobs/types.js +30 -0
  35. package/dist/src/jobs/worker.js +97 -0
  36. package/dist/src/learning/engine.js +356 -0
  37. package/dist/src/learning/index.js +9 -0
  38. package/dist/src/learning/locator-policy.js +120 -0
  39. package/dist/src/learning/pattern-policy.js +89 -0
  40. package/dist/src/learning/recovery-policy.js +116 -0
  41. package/dist/src/learning/sensor-policy.js +115 -0
  42. package/dist/src/learning/timing-model.js +204 -0
  43. package/dist/src/learning/topology-policy.js +90 -0
  44. package/dist/src/learning/types.js +9 -0
  45. package/dist/src/logging/timeline-logger.js +48 -0
  46. package/dist/src/mcp/mcp-stdio-server.js +464 -0
  47. package/dist/src/mcp/server.js +363 -0
  48. package/dist/src/mcp-entry.js +60 -0
  49. package/dist/src/memory/playbook-seeds.js +200 -0
  50. package/dist/src/memory/recall.js +222 -0
  51. package/dist/src/memory/research.js +104 -0
  52. package/dist/src/memory/seeds.js +101 -0
  53. package/dist/src/memory/service.js +446 -0
  54. package/dist/src/memory/session.js +169 -0
  55. package/dist/src/memory/store.js +451 -0
  56. package/{src/runtime/locator-cache.ts → dist/src/memory/types.js} +1 -17
  57. package/dist/src/monitor/codex-monitor.js +382 -0
  58. package/dist/src/monitor/task-queue.js +97 -0
  59. package/dist/src/monitor/types.js +62 -0
  60. package/dist/src/native/bridge-client.js +412 -0
  61. package/{src/native/macos-bridge-client.ts → dist/src/native/macos-bridge-client.js} +0 -1
  62. package/dist/src/observer/state.js +199 -0
  63. package/dist/src/observer/types.js +43 -0
  64. package/dist/src/orchestrator/state.js +68 -0
  65. package/dist/src/orchestrator/types.js +22 -0
  66. package/dist/src/perception/ax-source.js +162 -0
  67. package/dist/src/perception/cdp-source.js +162 -0
  68. package/dist/src/perception/coordinator.js +771 -0
  69. package/dist/src/perception/frame-differ.js +287 -0
  70. package/dist/src/perception/index.js +22 -0
  71. package/dist/src/perception/manager.js +199 -0
  72. package/dist/src/perception/types.js +47 -0
  73. package/dist/src/perception/vision-source.js +399 -0
  74. package/dist/src/planner/deterministic.js +298 -0
  75. package/dist/src/planner/executor.js +870 -0
  76. package/dist/src/planner/goal-store.js +92 -0
  77. package/dist/src/planner/index.js +21 -0
  78. package/dist/src/planner/planner.js +520 -0
  79. package/dist/src/planner/tool-registry.js +71 -0
  80. package/dist/src/planner/types.js +22 -0
  81. package/dist/src/platform/explorer.js +213 -0
  82. package/dist/src/platform/help-center-markdown.js +527 -0
  83. package/dist/src/platform/learner.js +257 -0
  84. package/dist/src/playbook/engine.js +486 -0
  85. package/dist/src/playbook/index.js +20 -0
  86. package/dist/src/playbook/mcp-recorder.js +204 -0
  87. package/dist/src/playbook/recorder.js +536 -0
  88. package/dist/src/playbook/runner.js +408 -0
  89. package/dist/src/playbook/store.js +312 -0
  90. package/dist/src/playbook/types.js +17 -0
  91. package/dist/src/recovery/detectors.js +156 -0
  92. package/dist/src/recovery/engine.js +327 -0
  93. package/dist/src/recovery/index.js +20 -0
  94. package/dist/src/recovery/strategies.js +274 -0
  95. package/dist/src/recovery/types.js +20 -0
  96. package/dist/src/runtime/accessibility-adapter.js +430 -0
  97. package/dist/src/runtime/app-adapter.js +64 -0
  98. package/dist/src/runtime/applescript-adapter.js +305 -0
  99. package/dist/src/runtime/ax-role-map.js +96 -0
  100. package/dist/src/runtime/browser-adapter.js +52 -0
  101. package/dist/src/runtime/cdp-chrome-adapter.js +521 -0
  102. package/dist/src/runtime/composite-adapter.js +221 -0
  103. package/dist/src/runtime/execution-contract.js +159 -0
  104. package/dist/src/runtime/executor.js +286 -0
  105. package/dist/src/runtime/locator-cache.js +50 -0
  106. package/dist/src/runtime/planning-loop.js +63 -0
  107. package/dist/src/runtime/service.js +432 -0
  108. package/dist/src/runtime/session-manager.js +63 -0
  109. package/dist/src/runtime/state-observer.js +121 -0
  110. package/dist/src/runtime/vision-adapter.js +225 -0
  111. package/dist/src/state/app-map-types.js +72 -0
  112. package/dist/src/state/app-map.js +1974 -0
  113. package/dist/src/state/entity-tracker.js +108 -0
  114. package/dist/src/state/fusion.js +96 -0
  115. package/dist/src/state/index.js +21 -0
  116. package/dist/src/state/ladder-generator.js +236 -0
  117. package/dist/src/state/persistence.js +156 -0
  118. package/dist/src/state/types.js +17 -0
  119. package/dist/src/state/world-model.js +1456 -0
  120. package/dist/src/supervisor/locks.js +186 -0
  121. package/dist/src/supervisor/supervisor.js +403 -0
  122. package/dist/src/supervisor/types.js +30 -0
  123. package/dist/src/test-mcp-protocol.js +154 -0
  124. package/dist/src/types.js +17 -0
  125. package/dist/src/util/atomic-write.js +133 -0
  126. package/dist/src/util/sanitize.js +146 -0
  127. package/dist-app-maps/com.figma.Desktop.json +959 -0
  128. package/dist-app-maps/com.hnc.Discord.json +1146 -0
  129. package/dist-app-maps/notion.id.json +2831 -0
  130. package/dist-playbooks/canva-screenhand-carousel.json +445 -0
  131. package/dist-playbooks/codex-desktop.json +76 -0
  132. package/dist-playbooks/competitor-research-stack.json +122 -0
  133. package/dist-playbooks/davinci-color-grade.json +153 -0
  134. package/dist-playbooks/davinci-edit-timeline.json +162 -0
  135. package/dist-playbooks/davinci-render.json +114 -0
  136. package/dist-playbooks/devto.json +52 -0
  137. package/dist-playbooks/discord.json +41 -0
  138. package/dist-playbooks/google-flow-create-project.json +59 -0
  139. package/dist-playbooks/google-flow-edit-image.json +90 -0
  140. package/dist-playbooks/google-flow-edit-video.json +90 -0
  141. package/dist-playbooks/google-flow-generate-image.json +68 -0
  142. package/dist-playbooks/google-flow-generate-video.json +191 -0
  143. package/dist-playbooks/google-flow-open-project.json +48 -0
  144. package/dist-playbooks/google-flow-open-scenebuilder.json +64 -0
  145. package/dist-playbooks/google-flow-search-assets.json +64 -0
  146. package/dist-playbooks/instagram.json +57 -0
  147. package/dist-playbooks/linkedin.json +52 -0
  148. package/dist-playbooks/n8n.json +43 -0
  149. package/dist-playbooks/reddit.json +52 -0
  150. package/dist-playbooks/threads.json +59 -0
  151. package/dist-playbooks/x-twitter.json +59 -0
  152. package/dist-playbooks/youtube.json +59 -0
  153. package/dist-references/canva.json +646 -0
  154. package/dist-references/codex-desktop.json +305 -0
  155. package/dist-references/davinci-resolve-keyboard.json +594 -0
  156. package/dist-references/davinci-resolve-menu-map.json +1139 -0
  157. package/dist-references/davinci-resolve-menus-batch1.json +116 -0
  158. package/dist-references/davinci-resolve-menus-batch2.json +372 -0
  159. package/dist-references/davinci-resolve-menus-batch3.json +330 -0
  160. package/dist-references/davinci-resolve-menus-batch4.json +297 -0
  161. package/dist-references/davinci-resolve-shortcuts.json +333 -0
  162. package/dist-references/devto.json +317 -0
  163. package/dist-references/discord.json +549 -0
  164. package/dist-references/figma.json +1186 -0
  165. package/dist-references/finder.json +146 -0
  166. package/dist-references/google-ads-transparency.json +95 -0
  167. package/dist-references/google-flow.json +649 -0
  168. package/dist-references/instagram.json +341 -0
  169. package/dist-references/linkedin.json +324 -0
  170. package/dist-references/meta-ad-library.json +86 -0
  171. package/dist-references/n8n.json +387 -0
  172. package/dist-references/notes.json +27 -0
  173. package/dist-references/notion.json +163 -0
  174. package/dist-references/reddit.json +341 -0
  175. package/dist-references/threads.json +337 -0
  176. package/dist-references/x-twitter.json +403 -0
  177. package/dist-references/youtube.json +373 -0
  178. package/native/macos-bridge/Package.swift +1 -0
  179. package/native/macos-bridge/Sources/AccessibilityBridge.swift +257 -36
  180. package/native/macos-bridge/Sources/AppManagement.swift +212 -2
  181. package/native/macos-bridge/Sources/CoreGraphicsBridge.swift +348 -53
  182. package/native/macos-bridge/Sources/StreamCapture.swift +136 -0
  183. package/native/macos-bridge/Sources/VisionBridge.swift +165 -7
  184. package/native/macos-bridge/Sources/main.swift +169 -16
  185. package/native/windows-bridge/Program.cs +5 -0
  186. package/native/windows-bridge/ScreenCapture.cs +124 -0
  187. package/package.json +29 -4
  188. package/scripts/postinstall.cjs +127 -0
  189. package/.claude/commands/automate.md +0 -28
  190. package/.claude/commands/debug-ui.md +0 -19
  191. package/.claude/commands/screenshot.md +0 -15
  192. package/.github/FUNDING.yml +0 -1
  193. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
  194. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  195. package/.mcp.json +0 -8
  196. package/DESKTOP_MCP_GUIDE.md +0 -92
  197. package/SECURITY.md +0 -44
  198. package/docs/architecture.md +0 -47
  199. package/install-skills.sh +0 -19
  200. package/mcp-bridge.ts +0 -271
  201. package/mcp-desktop.ts +0 -1221
  202. package/playbooks/instagram.json +0 -41
  203. package/playbooks/instagram_v2.json +0 -201
  204. package/playbooks/x_v1.json +0 -211
  205. package/scripts/devpost-live-loop.mjs +0 -421
  206. package/src/logging/timeline-logger.ts +0 -55
  207. package/src/mcp/server.ts +0 -449
  208. package/src/memory/recall.ts +0 -191
  209. package/src/memory/research.ts +0 -146
  210. package/src/memory/seeds.ts +0 -123
  211. package/src/memory/session.ts +0 -201
  212. package/src/memory/store.ts +0 -434
  213. package/src/memory/types.ts +0 -69
  214. package/src/native/bridge-client.ts +0 -239
  215. package/src/runtime/accessibility-adapter.ts +0 -487
  216. package/src/runtime/app-adapter.ts +0 -169
  217. package/src/runtime/applescript-adapter.ts +0 -376
  218. package/src/runtime/ax-role-map.ts +0 -102
  219. package/src/runtime/browser-adapter.ts +0 -129
  220. package/src/runtime/cdp-chrome-adapter.ts +0 -676
  221. package/src/runtime/composite-adapter.ts +0 -274
  222. package/src/runtime/executor.ts +0 -396
  223. package/src/runtime/planning-loop.ts +0 -81
  224. package/src/runtime/service.ts +0 -448
  225. package/src/runtime/session-manager.ts +0 -50
  226. package/src/runtime/state-observer.ts +0 -136
  227. package/src/runtime/vision-adapter.ts +0 -297
  228. package/src/types.ts +0 -297
  229. package/tests/bridge-client.test.ts +0 -176
  230. package/tests/browser-stealth.test.ts +0 -210
  231. package/tests/composite-adapter.test.ts +0 -64
  232. package/tests/mcp-server.test.ts +0 -151
  233. package/tests/memory-recall.test.ts +0 -339
  234. package/tests/memory-research.test.ts +0 -159
  235. package/tests/memory-seeds.test.ts +0 -120
  236. package/tests/memory-store.test.ts +0 -392
  237. package/tests/types.test.ts +0 -92
  238. package/tsconfig.check.json +0 -17
  239. package/tsconfig.json +0 -19
  240. package/vitest.config.ts +0 -8
  241. /package/{playbooks → dist-references}/devpost.json +0 -0
@@ -1,421 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
-
5
- const DEFAULT_MCP_PATH = "/Users/khushi/Documents/Automator/Screenhand/mcp-desktop.ts";
6
- const SIGNUP_URL = "https://secure.devpost.com/users/register?ref_content=signup_global_nav&ref_feature=signup&ref_medium=button";
7
- const ONBOARD_URL = "https://devpost.com/settings/hackathon-recommendations?return_to=https%3A%2F%2Fdevpost.com%2F";
8
- const SETTINGS_URL = "https://devpost.com/settings";
9
- const PORTFOLIO_URL = "https://devpost.com/portfolio/redirect?page=projects";
10
-
11
- const config = {
12
- mcpPath: process.env.SCREENHAND_MCP_PATH || DEFAULT_MCP_PATH,
13
- maxSteps: Number(process.env.LOOP_MAX_STEPS || 50),
14
- pollMs: Number(process.env.LOOP_POLL_MS || 1500),
15
- saveMemory: process.env.SAVE_MEMORY !== "0",
16
- profile: {
17
- firstName: process.env.DEVPOST_FIRST_NAME || "Manu",
18
- lastName: process.env.DEVPOST_LAST_NAME || "Singhal",
19
- email: process.env.DEVPOST_EMAIL || "",
20
- password: process.env.DEVPOST_PASSWORD || "",
21
- tagline: process.env.DEVPOST_TAGLINE || "Full-stack developer focused on AI and automation",
22
- github: process.env.DEVPOST_GITHUB || "manushi4",
23
- website: process.env.DEVPOST_WEBSITE || "https://github.com/manushi4",
24
- twitter: process.env.DEVPOST_TWITTER || "manu_singhal",
25
- location: process.env.DEVPOST_LOCATION || "Jaipur, Rajasthan, India",
26
- skills: process.env.DEVPOST_SKILLS || "JavaScript, Node.js, React, APIs, AI, Automation",
27
- },
28
- };
29
-
30
- const TOOL_ALLOWLIST = new Set([
31
- "focus",
32
- "browser_navigate",
33
- "browser_wait",
34
- "browser_js",
35
- "browser_page_info",
36
- "browser_tabs",
37
- "memory_save",
38
- "memory_recall",
39
- "memory_stats",
40
- ]);
41
-
42
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
43
-
44
- function parseToolText(result) {
45
- return result?.content?.find?.((c) => c.type === "text")?.text || JSON.stringify(result);
46
- }
47
-
48
- function parseMaybeJson(value) {
49
- try {
50
- return JSON.parse(value);
51
- } catch {
52
- return null;
53
- }
54
- }
55
-
56
- class DevpostLiveLoop {
57
- constructor(cfg) {
58
- this.cfg = cfg;
59
- this.transport = new StdioClientTransport({
60
- command: "npx",
61
- args: ["tsx", cfg.mcpPath],
62
- });
63
- this.client = new Client({ name: "screenhand-devpost-live-loop", version: "1.0.0" }, { capabilities: {} });
64
- }
65
-
66
- async connect() {
67
- await this.client.connect(this.transport);
68
- }
69
-
70
- async close() {
71
- try {
72
- await this.client.close();
73
- } catch {}
74
- }
75
-
76
- async call(tool, args = {}) {
77
- if (!TOOL_ALLOWLIST.has(tool)) {
78
- throw new Error(`Tool "${tool}" is not allowlisted in this live loop.`);
79
- }
80
- const res = await this.client.callTool({ name: tool, arguments: args });
81
- return parseToolText(res);
82
- }
83
-
84
- async js(code) {
85
- const raw = await this.call("browser_js", { code });
86
- const parsed = parseMaybeJson(raw);
87
- if (parsed === null) {
88
- throw new Error(`browser_js returned non-JSON: ${raw}`);
89
- }
90
- return parsed;
91
- }
92
-
93
- async observe() {
94
- return this.js(`(() => {
95
- const url = location.href;
96
- const title = document.title;
97
- const bodyText = (document.body?.innerText || '').replace(/\\s+/g, ' ').trim();
98
-
99
- const findButton = (re) => Array.from(document.querySelectorAll('button,input[type="submit"],a,[role="button"]'))
100
- .find(el => re.test((el.textContent || el.value || '').trim()));
101
-
102
- const signupBtn = findButton(/sign up with email/i);
103
- const continueBtn = findButton(/continue|next/i);
104
- const saveBtn = findButton(/save changes|save|update/i);
105
- const addProjectBtn = findButton(/add a new project/i);
106
- const emailLink = findButton(/sign up with email/i);
107
-
108
- const token = document.querySelector('#g-recaptcha-response')?.value || '';
109
- const hasCaptcha = !!document.querySelector('iframe[src*="recaptcha"], iframe[title*="reCAPTCHA"], #g-recaptcha-response');
110
- const isLoggedIn = !!document.querySelector('a[href*="/users/logout"]');
111
-
112
- const isProfilePage = /^https:\\/\\/devpost\\.com\\/[^\\/?#]+\\/?$/.test(url) &&
113
- !url.includes('/settings') &&
114
- !url.includes('/hackathons') &&
115
- !url.includes('/software') &&
116
- !url.includes('/notifications');
117
-
118
- return {
119
- url,
120
- title,
121
- isLoggedIn,
122
- hasCaptcha,
123
- captchaTokenLen: token.length,
124
- onSignup: url.includes('/users/register'),
125
- onWelcome: url.includes('/users/welcome'),
126
- onOnboarding: url.includes('/settings/hackathon-recommendations'),
127
- onSettings: url.startsWith('https://devpost.com/settings') && !url.includes('/hackathon-recommendations'),
128
- onPortfolioPage: isProfilePage,
129
- hasAddProject: !!addProjectBtn,
130
- hasSignupButton: !!signupBtn,
131
- hasContinueButton: !!continueBtn,
132
- hasSaveButton: !!saveBtn,
133
- hasEmailLink: !!emailLink,
134
- bodyHint: bodyText.slice(0, 300),
135
- };
136
- })()`);
137
- }
138
-
139
- async ensureFocus() {
140
- await this.call("focus", { bundleId: "com.google.Chrome" });
141
- }
142
-
143
- async fillSignupPage() {
144
- const creds = {
145
- firstName: this.cfg.profile.firstName,
146
- lastName: this.cfg.profile.lastName,
147
- email: this.cfg.profile.email,
148
- password: this.cfg.profile.password,
149
- };
150
- return this.js(`(() => {
151
- const creds = ${JSON.stringify(creds)};
152
- const pick = (re) => Array.from(document.querySelectorAll('a,button,[role="button"]'))
153
- .find(el => re.test((el.textContent || '').trim()));
154
-
155
- const emailLink = pick(/sign up with email/i);
156
- if (emailLink) emailLink.click();
157
-
158
- const set = (sel, value) => {
159
- const el = document.querySelector(sel);
160
- if (!el) return false;
161
- el.focus();
162
- el.value = value;
163
- el.dispatchEvent(new Event('input', { bubbles: true }));
164
- el.dispatchEvent(new Event('change', { bubbles: true }));
165
- return true;
166
- };
167
-
168
- const missing = [];
169
- if (!creds.email) missing.push('DEVPOST_EMAIL');
170
- if (!creds.password) missing.push('DEVPOST_PASSWORD');
171
-
172
- const changed = {
173
- first: creds.firstName ? set('#user_first_name', creds.firstName) : false,
174
- last: creds.lastName ? set('#user_last_name', creds.lastName) : false,
175
- email: creds.email ? set('#user_email', creds.email) : false,
176
- password: creds.password ? set('#user_password', creds.password) : false
177
- };
178
-
179
- const submit = Array.from(document.querySelectorAll('button,input[type="submit"],[role="button"]'))
180
- .find(el => /sign up with email/i.test((el.textContent || el.value || '').trim()));
181
-
182
- const token = document.querySelector('#g-recaptcha-response')?.value || '';
183
- return {
184
- url: location.href,
185
- changed,
186
- missing,
187
- submitFound: !!submit,
188
- submitDisabled: submit ? !!submit.disabled : null,
189
- hasCaptcha: !!document.querySelector('iframe[src*="recaptcha"], iframe[title*="reCAPTCHA"], #g-recaptcha-response'),
190
- captchaTokenLen: token.length
191
- };
192
- })()`);
193
- }
194
-
195
- async maybeSubmitSignup() {
196
- return this.js(`(() => {
197
- const submit = Array.from(document.querySelectorAll('button,input[type="submit"],[role="button"]'))
198
- .find(el => /sign up with email/i.test((el.textContent || el.value || '').trim()));
199
- if (!submit) return { clicked: false, reason: 'submit_not_found', url: location.href };
200
- if (submit.disabled) return { clicked: false, reason: 'submit_disabled', url: location.href };
201
- submit.click();
202
- return { clicked: true, url: location.href };
203
- })()`);
204
- }
205
-
206
- async fillOnboardingAndContinue() {
207
- const payload = {
208
- location: this.cfg.profile.location,
209
- skills: this.cfg.profile.skills,
210
- };
211
- return this.js(`(() => {
212
- const payload = ${JSON.stringify(payload)};
213
- const set = (sel, val) => {
214
- const el = document.querySelector(sel);
215
- if (!el) return false;
216
- el.focus();
217
- el.value = val;
218
- el.dispatchEvent(new Event('input', { bubbles: true }));
219
- el.dispatchEvent(new Event('change', { bubbles: true }));
220
- return true;
221
- };
222
- const check = (sel) => {
223
- const el = document.querySelector(sel);
224
- if (!el) return false;
225
- el.checked = true;
226
- el.dispatchEvent(new Event('input', { bubbles: true }));
227
- el.dispatchEvent(new Event('change', { bubbles: true }));
228
- return true;
229
- };
230
- const pickSelect = (sel, terms) => {
231
- const el = document.querySelector(sel);
232
- if (!el) return false;
233
- const list = Array.from(el.options || []);
234
- const m = list.find(o => terms.every(t => (o.textContent || '').toLowerCase().includes(t.toLowerCase())));
235
- if (!m) return false;
236
- el.value = m.value;
237
- el.dispatchEvent(new Event('input', { bubbles: true }));
238
- el.dispatchEvent(new Event('change', { bubbles: true }));
239
- return true;
240
- };
241
-
242
- const actions = {
243
- specialty: check('#user_employed_as_full-stack_developer'),
244
- skills: set('#user_tag_list', payload.skills),
245
- interestAi: check('#user_theme_ids_6'),
246
- interestWeb: check('#user_theme_ids_25'),
247
- interestBeginner: check('#user_theme_ids_23'),
248
- interestOpen: check('#user_theme_ids_22'),
249
- location: set('#user_address', payload.location),
250
- timezone: pickSelect('#user_timezone', ['chennai']) || pickSelect('#user_timezone', ['kolkata']) || pickSelect('#user_timezone', ['new delhi']),
251
- proStatus: check('#user_career_status_professional__post_grad'),
252
- employedInTech: check('#user_employed_in_software_or_tech_true'),
253
- companyHackathonsNo: check('#user_company_has_internal_hackathons_false')
254
- };
255
-
256
- const cont = Array.from(document.querySelectorAll('button,input[type="submit"],[role="button"]'))
257
- .find(el => /continue|next/i.test((el.textContent || el.value || '').trim()));
258
- if (cont && !cont.disabled) cont.click();
259
-
260
- const visibleRequired = Array.from(document.querySelectorAll('input[required],select[required],textarea[required]'))
261
- .filter(el => el.offsetParent !== null)
262
- .map(el => ({ id: el.id || null, name: el.name || null, value: (el.value || '').toString() }));
263
-
264
- return {
265
- url: location.href,
266
- actions,
267
- continueClicked: !!(cont && !cont.disabled),
268
- visibleRequired
269
- };
270
- })()`);
271
- }
272
-
273
- async fillSettingsAndSave() {
274
- const profile = this.cfg.profile;
275
- return this.js(`(() => {
276
- const p = ${JSON.stringify(profile)};
277
- const set = (sel, val, force = false) => {
278
- const el = document.querySelector(sel);
279
- if (!el || el.offsetParent === null) return false;
280
- const cur = (el.value || '').trim();
281
- if (!force && cur.length > 0) return 'kept_existing';
282
- el.focus();
283
- el.value = val;
284
- el.dispatchEvent(new Event('input', { bubbles: true }));
285
- el.dispatchEvent(new Event('change', { bubbles: true }));
286
- return true;
287
- };
288
-
289
- const actions = {
290
- first: set('#user_first_name', p.firstName),
291
- last: set('#user_last_name', p.lastName),
292
- tagline: set('#user_user_setting_attributes_tagline', p.tagline),
293
- github: set('#user_user_setting_attributes_github_login', p.github),
294
- website: set('#user_user_setting_attributes_website', p.website),
295
- twitter: set('#user_user_setting_attributes_twitter', p.twitter)
296
- };
297
-
298
- const save = Array.from(document.querySelectorAll('button,input[type="submit"],[role="button"]'))
299
- .find(el => /save changes|save|update/i.test((el.textContent || el.value || '').trim()));
300
-
301
- if (save && !save.disabled) save.click();
302
- return {
303
- url: location.href,
304
- actions,
305
- saveClicked: !!(save && !save.disabled)
306
- };
307
- })()`);
308
- }
309
-
310
- async saveMemory() {
311
- if (!this.cfg.saveMemory) return;
312
- const out = await this.call("memory_save", {
313
- task: "Devpost live loop: observe-decide-act without screenshots (signup + manual CAPTCHA handoff + onboarding + settings + portfolio check)",
314
- tags: ["devpost", "live-loop", "no-screenshot", "browser_js", "onboarding", "settings", "portfolio"],
315
- });
316
- console.log(`[memory_save] ${out}`);
317
- }
318
-
319
- async run() {
320
- await this.ensureFocus();
321
-
322
- for (let step = 1; step <= this.cfg.maxSteps; step += 1) {
323
- const state = await this.observe();
324
- console.log(`\\n[step ${step}] ${state.url}`);
325
-
326
- if (!/devpost\.com/.test(state.url)) {
327
- console.log("[action] Not on Devpost, navigating to signup.");
328
- await this.call("browser_navigate", { url: SIGNUP_URL });
329
- await this.call("browser_wait", { condition: "document.readyState === \"complete\"", timeoutMs: 20000 });
330
- await sleep(this.cfg.pollMs);
331
- continue;
332
- }
333
-
334
- if (state.onSignup) {
335
- const filled = await this.fillSignupPage();
336
- if (filled.missing.length > 0) {
337
- console.log(`[wait] Missing env vars: ${filled.missing.join(", ")}. Set them and rerun.`);
338
- return { status: "needs_credentials", state: filled };
339
- }
340
-
341
- if (filled.hasCaptcha && filled.captchaTokenLen === 0) {
342
- console.log("[wait] CAPTCHA needs manual solve. Complete it in browser; loop will continue.");
343
- await sleep(this.cfg.pollMs);
344
- continue;
345
- }
346
-
347
- if (filled.captchaTokenLen > 0) {
348
- const submit = await this.maybeSubmitSignup();
349
- console.log(`[action] Signup submit: ${JSON.stringify(submit)}`);
350
- await sleep(this.cfg.pollMs);
351
- continue;
352
- }
353
-
354
- await sleep(this.cfg.pollMs);
355
- continue;
356
- }
357
-
358
- if (state.onWelcome) {
359
- console.log("[action] Welcome page detected, moving to onboarding.");
360
- await this.call("browser_navigate", { url: ONBOARD_URL });
361
- await this.call("browser_wait", { condition: "document.readyState === \"complete\"", timeoutMs: 20000 });
362
- await sleep(this.cfg.pollMs);
363
- continue;
364
- }
365
-
366
- if (state.onOnboarding) {
367
- const onboard = await this.fillOnboardingAndContinue();
368
- console.log(`[action] Onboarding pass: continueClicked=${onboard.continueClicked}, visibleRequired=${onboard.visibleRequired.length}`);
369
- await sleep(this.cfg.pollMs);
370
- continue;
371
- }
372
-
373
- if (state.onSettings) {
374
- const settings = await this.fillSettingsAndSave();
375
- console.log(`[action] Settings saved: ${settings.saveClicked}`);
376
- await this.call("browser_navigate", { url: PORTFOLIO_URL });
377
- await this.call("browser_wait", { condition: "document.readyState === \"complete\"", timeoutMs: 20000 });
378
- await sleep(this.cfg.pollMs);
379
- continue;
380
- }
381
-
382
- if (state.onPortfolioPage && state.hasAddProject) {
383
- console.log("[done] Portfolio ready. Add a new project is visible.");
384
- await this.saveMemory();
385
- return { status: "done", state };
386
- }
387
-
388
- if (state.isLoggedIn) {
389
- console.log("[action] Logged in but not on target page, going to settings.");
390
- await this.call("browser_navigate", { url: SETTINGS_URL });
391
- await this.call("browser_wait", { condition: "document.readyState === \"complete\"", timeoutMs: 20000 });
392
- await sleep(this.cfg.pollMs);
393
- continue;
394
- }
395
-
396
- console.log("[action] Fallback to signup URL.");
397
- await this.call("browser_navigate", { url: SIGNUP_URL });
398
- await this.call("browser_wait", { condition: "document.readyState === \"complete\"", timeoutMs: 20000 });
399
- await sleep(this.cfg.pollMs);
400
- }
401
-
402
- return { status: "max_steps_reached" };
403
- }
404
- }
405
-
406
- async function main() {
407
- const loop = new DevpostLiveLoop(config);
408
- try {
409
- await loop.connect();
410
- const result = await loop.run();
411
- console.log(`\\n[result] ${JSON.stringify(result)}`);
412
- if (result.status !== "done") process.exitCode = 1;
413
- } catch (err) {
414
- console.error(`[error] ${err instanceof Error ? err.stack || err.message : String(err)}`);
415
- process.exitCode = 1;
416
- } finally {
417
- await loop.close();
418
- }
419
- }
420
-
421
- await main();
@@ -1,55 +0,0 @@
1
- // Copyright (C) 2025 Clazro Technology Private Limited
2
- // SPDX-License-Identifier: AGPL-3.0-only
3
- //
4
- // This file is part of ScreenHand.
5
- //
6
- // ScreenHand is free software: you can redistribute it and/or modify
7
- // it under the terms of the GNU Affero General Public License as
8
- // published by the Free Software Foundation, version 3.
9
- //
10
- // ScreenHand is distributed in the hope that it will be useful,
11
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
- // GNU Affero General Public License for more details.
14
- //
15
- // You should have received a copy of the GNU Affero General Public License
16
- // along with ScreenHand. If not, see <https://www.gnu.org/licenses/>.
17
-
18
- import type { ActionStatus, ActionTelemetry } from "../types.js";
19
-
20
- export class TimelineLogger {
21
- private readonly timeline: ActionTelemetry[] = [];
22
-
23
- start(action: string, sessionId: string): ActionTelemetry {
24
- return {
25
- action,
26
- sessionId,
27
- startedAt: new Date().toISOString(),
28
- locateMs: 0,
29
- actMs: 0,
30
- verifyMs: 0,
31
- retries: 0,
32
- };
33
- }
34
-
35
- finish(telemetry: ActionTelemetry, status: ActionStatus): ActionTelemetry {
36
- const finishedAt = new Date().toISOString();
37
- const totalMs =
38
- new Date(finishedAt).getTime() - new Date(telemetry.startedAt).getTime();
39
-
40
- const finalized: ActionTelemetry = {
41
- ...telemetry,
42
- finishedAt,
43
- totalMs,
44
- status,
45
- };
46
-
47
- this.timeline.push(finalized);
48
- return finalized;
49
- }
50
-
51
- getRecent(limit = 50): ActionTelemetry[] {
52
- return this.timeline.slice(-limit);
53
- }
54
- }
55
-