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.
- package/README.md +193 -109
- package/bin/darwin-arm64/macos-bridge +0 -0
- package/dist/mcp-desktop.js +5876 -0
- package/dist/scripts/codex-monitor-daemon.js +335 -0
- 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/supervisor-daemon.js +272 -0
- package/dist/scripts/threads-campaign.js +208 -0
- package/dist/scripts/worker-daemon.js +228 -0
- package/dist/src/agent/cli.js +82 -0
- package/dist/src/agent/loop.js +274 -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/{src/config.ts → dist/src/config.js} +5 -10
- package/dist/src/context-tracker.js +489 -0
- package/{src/index.ts → dist/src/index.js} +32 -52
- 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 +305 -0
- package/dist/src/jobs/runner.js +806 -0
- package/dist/src/jobs/store.js +102 -0
- package/dist/src/jobs/types.js +30 -0
- package/dist/src/jobs/worker.js +97 -0
- 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 +48 -0
- package/dist/src/mcp/mcp-stdio-server.js +464 -0
- package/dist/src/mcp/server.js +363 -0
- package/dist/src/mcp-entry.js +60 -0
- package/dist/src/memory/playbook-seeds.js +200 -0
- package/dist/src/memory/recall.js +222 -0
- package/dist/src/memory/research.js +104 -0
- package/dist/src/memory/seeds.js +101 -0
- package/dist/src/memory/service.js +446 -0
- package/dist/src/memory/session.js +169 -0
- package/dist/src/memory/store.js +451 -0
- package/{src/runtime/locator-cache.ts → dist/src/memory/types.js} +1 -17
- package/dist/src/monitor/codex-monitor.js +382 -0
- package/dist/src/monitor/task-queue.js +97 -0
- package/dist/src/monitor/types.js +62 -0
- package/dist/src/native/bridge-client.js +412 -0
- package/{src/native/macos-bridge-client.ts → dist/src/native/macos-bridge-client.js} +0 -1
- 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 +486 -0
- package/dist/src/playbook/index.js +20 -0
- package/dist/src/playbook/mcp-recorder.js +204 -0
- package/dist/src/playbook/recorder.js +536 -0
- package/dist/src/playbook/runner.js +408 -0
- package/dist/src/playbook/store.js +312 -0
- package/dist/src/playbook/types.js +17 -0
- 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 +430 -0
- package/dist/src/runtime/app-adapter.js +64 -0
- package/dist/src/runtime/applescript-adapter.js +305 -0
- package/dist/src/runtime/ax-role-map.js +96 -0
- package/dist/src/runtime/browser-adapter.js +52 -0
- package/dist/src/runtime/cdp-chrome-adapter.js +521 -0
- package/dist/src/runtime/composite-adapter.js +221 -0
- package/dist/src/runtime/execution-contract.js +159 -0
- package/dist/src/runtime/executor.js +286 -0
- package/dist/src/runtime/locator-cache.js +50 -0
- package/dist/src/runtime/planning-loop.js +63 -0
- package/dist/src/runtime/service.js +432 -0
- package/dist/src/runtime/session-manager.js +63 -0
- package/dist/src/runtime/state-observer.js +121 -0
- package/dist/src/runtime/vision-adapter.js +225 -0
- 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/supervisor/locks.js +186 -0
- package/dist/src/supervisor/supervisor.js +403 -0
- package/dist/src/supervisor/types.js +30 -0
- package/dist/src/test-mcp-protocol.js +154 -0
- package/dist/src/types.js +17 -0
- package/dist/src/util/atomic-write.js +133 -0
- 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/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 +1 -0
- package/native/macos-bridge/Sources/AccessibilityBridge.swift +257 -36
- package/native/macos-bridge/Sources/AppManagement.swift +212 -2
- package/native/macos-bridge/Sources/CoreGraphicsBridge.swift +348 -53
- package/native/macos-bridge/Sources/StreamCapture.swift +136 -0
- package/native/macos-bridge/Sources/VisionBridge.swift +165 -7
- package/native/macos-bridge/Sources/main.swift +169 -16
- package/native/windows-bridge/Program.cs +5 -0
- package/native/windows-bridge/ScreenCapture.cs +124 -0
- package/package.json +29 -4
- package/scripts/postinstall.cjs +127 -0
- package/.claude/commands/automate.md +0 -28
- package/.claude/commands/debug-ui.md +0 -19
- package/.claude/commands/screenshot.md +0 -15
- package/.github/FUNDING.yml +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.mcp.json +0 -8
- package/DESKTOP_MCP_GUIDE.md +0 -92
- package/SECURITY.md +0 -44
- package/docs/architecture.md +0 -47
- package/install-skills.sh +0 -19
- package/mcp-bridge.ts +0 -271
- package/mcp-desktop.ts +0 -1221
- package/playbooks/instagram.json +0 -41
- package/playbooks/instagram_v2.json +0 -201
- package/playbooks/x_v1.json +0 -211
- package/scripts/devpost-live-loop.mjs +0 -421
- package/src/logging/timeline-logger.ts +0 -55
- package/src/mcp/server.ts +0 -449
- package/src/memory/recall.ts +0 -191
- package/src/memory/research.ts +0 -146
- package/src/memory/seeds.ts +0 -123
- package/src/memory/session.ts +0 -201
- package/src/memory/store.ts +0 -434
- package/src/memory/types.ts +0 -69
- package/src/native/bridge-client.ts +0 -239
- package/src/runtime/accessibility-adapter.ts +0 -487
- package/src/runtime/app-adapter.ts +0 -169
- package/src/runtime/applescript-adapter.ts +0 -376
- package/src/runtime/ax-role-map.ts +0 -102
- package/src/runtime/browser-adapter.ts +0 -129
- package/src/runtime/cdp-chrome-adapter.ts +0 -676
- package/src/runtime/composite-adapter.ts +0 -274
- package/src/runtime/executor.ts +0 -396
- package/src/runtime/planning-loop.ts +0 -81
- package/src/runtime/service.ts +0 -448
- package/src/runtime/session-manager.ts +0 -50
- package/src/runtime/state-observer.ts +0 -136
- package/src/runtime/vision-adapter.ts +0 -297
- package/src/types.ts +0 -297
- package/tests/bridge-client.test.ts +0 -176
- package/tests/browser-stealth.test.ts +0 -210
- package/tests/composite-adapter.test.ts +0 -64
- package/tests/mcp-server.test.ts +0 -151
- package/tests/memory-recall.test.ts +0 -339
- package/tests/memory-research.test.ts +0 -159
- package/tests/memory-seeds.test.ts +0 -120
- package/tests/memory-store.test.ts +0 -392
- package/tests/types.test.ts +0 -92
- package/tsconfig.check.json +0 -17
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -8
- /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
|
-
|