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
@@ -0,0 +1,6 @@
1
+ // Copyright (C) 2025 Clazro Technology Private Limited
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+ export { PlaybookPublisher } from "./publisher.js";
4
+ export { PlaybookFetcher } from "./fetcher.js";
5
+ export { PlaybookValidator } from "./validator.js";
6
+ export { RemoteCommunityAPI } from "./remote-api.js";
@@ -0,0 +1,191 @@
1
+ // Copyright (C) 2025 Clazro Technology Private Limited
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import * as os from "node:os";
6
+ import { writeFileAtomicSync } from "../util/atomic-write.js";
7
+ import { RemoteCommunityAPI } from "./remote-api.js";
8
+ /**
9
+ * PlaybookPublisher — prepares and publishes validated playbooks
10
+ * to a local shared repository and optionally to a remote API
11
+ * (when SCREENHAND_COMMUNITY_URL is set).
12
+ */
13
+ export class PlaybookPublisher {
14
+ repoDir;
15
+ remote;
16
+ constructor(repoDir, remote) {
17
+ this.repoDir = repoDir ?? path.join(os.homedir(), ".screenhand", "community");
18
+ this.remote = remote ?? RemoteCommunityAPI.fromEnv();
19
+ fs.mkdirSync(this.repoDir, { recursive: true });
20
+ }
21
+ /**
22
+ * Publish a validated local playbook to the community repository.
23
+ * Requires the playbook to have been run successfully at least minRuns times.
24
+ */
25
+ publish(playbook, successRate, executionCount, _minRuns) {
26
+ // Server-side minimum — cannot be overridden by caller
27
+ const MIN_RUNS = 3;
28
+ if (!Number.isFinite(executionCount) || executionCount < MIN_RUNS)
29
+ return null;
30
+ if (!Number.isFinite(successRate) || successRate < 0.5)
31
+ return null;
32
+ // Cross-check client-provided metrics against actual playbook data.
33
+ // ALWAYS use the playbook's own tracked counts — never trust client values.
34
+ // If the playbook has no tracked runs, it cannot be verified for publishing.
35
+ const actualRuns = playbook.successCount + playbook.failCount;
36
+ if (actualRuns < MIN_RUNS)
37
+ return null; // No tracked data = not publishable
38
+ const verifiedRate = playbook.successCount / actualRuns;
39
+ if (verifiedRate < 0.5)
40
+ return null;
41
+ const shared = {
42
+ id: `community_${playbook.id}_${Date.now().toString(36)}`,
43
+ name: playbook.name,
44
+ description: playbook.description,
45
+ platform: playbook.platform,
46
+ bundleId: playbook.bundleId ?? "",
47
+ version: "1.0.0",
48
+ steps: this.convertSteps(playbook.steps),
49
+ metadata: {
50
+ author: "anonymous",
51
+ publishedAt: new Date().toISOString(),
52
+ updatedAt: new Date().toISOString(),
53
+ os: process.platform,
54
+ successRate: verifiedRate,
55
+ executionCount: actualRuns,
56
+ tags: playbook.tags ?? [],
57
+ },
58
+ ratings: {
59
+ upvotes: 0,
60
+ downvotes: 0,
61
+ score: 0,
62
+ reportCount: 0,
63
+ },
64
+ };
65
+ // Strip sensitive data from params
66
+ for (const step of shared.steps) {
67
+ this.sanitizeParams(step.params);
68
+ }
69
+ // Sanitize ID to prevent path traversal
70
+ const safeId = shared.id.replace(/[^a-zA-Z0-9_\-]/g, "_");
71
+ const filePath = path.join(this.repoDir, `${safeId}.json`);
72
+ // Verify the file stays inside repoDir
73
+ const resolved = path.resolve(filePath);
74
+ if (!resolved.startsWith(path.resolve(this.repoDir))) {
75
+ return null;
76
+ }
77
+ writeFileAtomicSync(filePath, JSON.stringify(shared, null, 2) + "\n");
78
+ // Best-effort sync to remote API
79
+ if (this.remote) {
80
+ void this.remote.publish(shared).catch(() => { });
81
+ }
82
+ return shared;
83
+ }
84
+ /**
85
+ * List all published playbooks in the local repository.
86
+ */
87
+ list() {
88
+ const playbooks = [];
89
+ try {
90
+ const files = fs.readdirSync(this.repoDir);
91
+ for (const file of files) {
92
+ if (!file.endsWith(".json"))
93
+ continue;
94
+ try {
95
+ const raw = fs.readFileSync(path.join(this.repoDir, file), "utf-8");
96
+ playbooks.push(JSON.parse(raw));
97
+ }
98
+ catch { /* skip */ }
99
+ }
100
+ }
101
+ catch { /* dir not found */ }
102
+ return playbooks;
103
+ }
104
+ convertSteps(steps) {
105
+ return steps.map((step) => ({
106
+ action: step.action,
107
+ tool: this.actionToTool(step.action),
108
+ params: this.extractParams(step),
109
+ description: step.description ?? `${step.action} step`,
110
+ }));
111
+ }
112
+ extractParams(step) {
113
+ const params = {};
114
+ if (step.target !== undefined)
115
+ params.target = step.target;
116
+ if (step.text !== undefined)
117
+ params.text = step.text;
118
+ if (step.url !== undefined)
119
+ params.url = step.url;
120
+ if (step.keys !== undefined)
121
+ params.keys = step.keys;
122
+ if (step.menuPath !== undefined)
123
+ params.menuPath = step.menuPath;
124
+ if (step.ms !== undefined)
125
+ params.ms = step.ms;
126
+ if (step.direction !== undefined)
127
+ params.direction = step.direction;
128
+ if (step.amount !== undefined)
129
+ params.amount = step.amount;
130
+ // Normalize to MCP tool param shapes
131
+ if (Array.isArray(params.keys)) {
132
+ params.combo = params.keys.join("+");
133
+ delete params.keys;
134
+ }
135
+ if (Array.isArray(params.menuPath)) {
136
+ params.menuPath = params.menuPath.join("/");
137
+ }
138
+ return params;
139
+ }
140
+ actionToTool(action) {
141
+ switch (action) {
142
+ case "click": return "click_with_fallback";
143
+ case "type": return "type_with_fallback";
144
+ case "press": return "key";
145
+ case "navigate": return "browser_navigate";
146
+ case "wait": return "wait_for_state";
147
+ case "scroll": return "scroll_with_fallback";
148
+ default: return action;
149
+ }
150
+ }
151
+ /**
152
+ * Remove potentially sensitive values from params.
153
+ */
154
+ sanitizeParams(params) {
155
+ const sensitiveKeys = [
156
+ "password", "token", "secret", "credential",
157
+ "apikey", "api_key", "auth_token", "secret_key",
158
+ "access_key", "private_key",
159
+ ];
160
+ /** Patterns that indicate sensitive values regardless of key name */
161
+ const sensitiveValuePatterns = [
162
+ /sk-ant-api/i, // Anthropic API keys
163
+ /sk-[a-zA-Z0-9]{20,}/i, // OpenAI-style keys
164
+ /^ghp_[a-zA-Z0-9]{36}$/i, // GitHub PATs
165
+ /^xox[bpsar]-/i, // Slack tokens
166
+ /\bexport\s+\w*(?:KEY|TOKEN|SECRET|PASSWORD)\b/i, // env var exports
167
+ ];
168
+ for (const key of Object.keys(params)) {
169
+ if (sensitiveKeys.some((s) => key.toLowerCase() === s || key.toLowerCase().replace(/[^a-z_]/g, "") === s)) {
170
+ delete params[key];
171
+ continue;
172
+ }
173
+ const val = params[key];
174
+ // Check string values for sensitive patterns
175
+ if (typeof val === "string") {
176
+ if (sensitiveValuePatterns.some((p) => p.test(val))) {
177
+ delete params[key];
178
+ continue;
179
+ }
180
+ // Strip absolute file paths
181
+ if (val.startsWith("/") && (val.includes("/Users/") || val.includes("/home/"))) {
182
+ params[key] = path.basename(val);
183
+ }
184
+ }
185
+ // Recurse into nested objects
186
+ if (val && typeof val === "object" && !Array.isArray(val)) {
187
+ this.sanitizeParams(val);
188
+ }
189
+ }
190
+ }
191
+ }
@@ -0,0 +1,121 @@
1
+ // Copyright (C) 2025 Clazro Technology Private Limited
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+ /**
4
+ * RemoteCommunityAPI — optional remote backend for community playbook sharing.
5
+ *
6
+ * When `SCREENHAND_COMMUNITY_URL` is set, publish/fetch operations
7
+ * go to the remote API in addition to local disk. The local repo
8
+ * remains the source of truth; remote is best-effort sync.
9
+ *
10
+ * Expected API endpoints:
11
+ * POST /playbooks — publish a playbook
12
+ * GET /playbooks?... — search playbooks (query params from PlaybookQuery)
13
+ * GET /playbooks/:id — get a single playbook
14
+ * POST /playbooks/:id/rate — rate a playbook { score: 1 | -1 }
15
+ */
16
+ export class RemoteCommunityAPI {
17
+ baseUrl;
18
+ timeoutMs;
19
+ constructor(baseUrl, timeoutMs = 5_000) {
20
+ // Strip trailing slash
21
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
22
+ this.timeoutMs = timeoutMs;
23
+ }
24
+ /**
25
+ * Get the configured remote URL, or null if not configured.
26
+ */
27
+ static fromEnv() {
28
+ const url = process.env["SCREENHAND_COMMUNITY_URL"];
29
+ if (!url)
30
+ return null;
31
+ return new RemoteCommunityAPI(url);
32
+ }
33
+ /**
34
+ * Publish a playbook to the remote API.
35
+ * Returns the server-assigned ID, or null on failure.
36
+ */
37
+ async publish(playbook) {
38
+ try {
39
+ const res = await fetchWithTimeout(`${this.baseUrl}/playbooks`, {
40
+ method: "POST",
41
+ headers: { "Content-Type": "application/json" },
42
+ body: JSON.stringify(playbook),
43
+ }, this.timeoutMs);
44
+ if (!res.ok)
45
+ return null;
46
+ const body = (await res.json());
47
+ return body.id ?? playbook.id;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * Fetch playbooks from the remote API matching a query.
55
+ */
56
+ async fetch(query) {
57
+ try {
58
+ const params = new URLSearchParams();
59
+ if (query.platform)
60
+ params.set("platform", query.platform);
61
+ if (query.bundleId)
62
+ params.set("bundleId", query.bundleId);
63
+ if (query.workflow)
64
+ params.set("workflow", query.workflow);
65
+ if (query.minScore !== undefined)
66
+ params.set("minScore", String(query.minScore));
67
+ if (query.limit !== undefined)
68
+ params.set("limit", String(query.limit));
69
+ const res = await fetchWithTimeout(`${this.baseUrl}/playbooks?${params.toString()}`, { method: "GET" }, this.timeoutMs);
70
+ if (!res.ok)
71
+ return [];
72
+ return (await res.json());
73
+ }
74
+ catch {
75
+ return [];
76
+ }
77
+ }
78
+ /**
79
+ * Get a specific playbook by ID.
80
+ */
81
+ async get(id) {
82
+ try {
83
+ const res = await fetchWithTimeout(`${this.baseUrl}/playbooks/${encodeURIComponent(id)}`, { method: "GET" }, this.timeoutMs);
84
+ if (!res.ok)
85
+ return null;
86
+ return (await res.json());
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
92
+ /**
93
+ * Rate a playbook (upvote/downvote).
94
+ */
95
+ async rate(id, score) {
96
+ try {
97
+ const res = await fetchWithTimeout(`${this.baseUrl}/playbooks/${encodeURIComponent(id)}/rate`, {
98
+ method: "POST",
99
+ headers: { "Content-Type": "application/json" },
100
+ body: JSON.stringify({ score }),
101
+ }, this.timeoutMs);
102
+ return res.ok;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ }
108
+ }
109
+ /**
110
+ * Fetch with AbortController timeout.
111
+ */
112
+ async function fetchWithTimeout(url, init, timeoutMs) {
113
+ const controller = new AbortController();
114
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
115
+ try {
116
+ return await fetch(url, { ...init, signal: controller.signal });
117
+ }
118
+ finally {
119
+ clearTimeout(timer);
120
+ }
121
+ }
@@ -0,0 +1,3 @@
1
+ // Copyright (C) 2025 Clazro Technology Private Limited
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+ export {};
@@ -0,0 +1,95 @@
1
+ // Copyright (C) 2025 Clazro Technology Private Limited
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+ /**
4
+ * Tools that community playbooks are allowed to call.
5
+ * Excludes: applescript (arbitrary code exec), browser_js (XSS),
6
+ * browser_stealth (anti-detection), memory_* (data exfil),
7
+ * supervisor_* (system control), job_* (persistence).
8
+ */
9
+ const SAFE_TOOLS = new Set([
10
+ "click", "click_text", "click_with_fallback", "type_text", "type_with_fallback",
11
+ "key", "drag", "scroll", "scroll_with_fallback", "focus", "launch",
12
+ "screenshot", "screenshot_file", "ocr", "ui_tree", "ui_find", "ui_press",
13
+ "ui_set_value", "menu_click", "wait_for_state", "read_with_fallback",
14
+ "locate_with_fallback", "select_with_fallback",
15
+ "browser_open", "browser_navigate", "browser_click", "browser_type",
16
+ "browser_dom", "browser_wait", "browser_page_info", "browser_tabs",
17
+ "browser_fill_form", "browser_human_click",
18
+ "windows", "apps",
19
+ ]);
20
+ /**
21
+ * PlaybookValidator — tests a community playbook against the live app
22
+ * before accepting it into the local collection.
23
+ */
24
+ export class PlaybookValidator {
25
+ executeTool;
26
+ constructor(executeTool) {
27
+ this.executeTool = executeTool;
28
+ }
29
+ /**
30
+ * Validate a community playbook by executing its steps.
31
+ * Runs all steps and reports success/failure.
32
+ */
33
+ async validate(playbook) {
34
+ if (playbook.steps.length === 0) {
35
+ return {
36
+ playbook,
37
+ success: false,
38
+ stepsCompleted: 0,
39
+ totalSteps: 0,
40
+ errors: ["Playbook has no steps"],
41
+ validatedAt: new Date().toISOString(),
42
+ };
43
+ }
44
+ const errors = [];
45
+ let stepsCompleted = 0;
46
+ // Pre-validate: reject playbooks that use unsafe tools
47
+ for (const step of playbook.steps) {
48
+ if (!SAFE_TOOLS.has(step.tool)) {
49
+ return {
50
+ playbook,
51
+ success: false,
52
+ stepsCompleted: 0,
53
+ totalSteps: playbook.steps.length,
54
+ errors: [`Step "${step.description}" uses blocked tool "${step.tool}". Community playbooks cannot use this tool.`],
55
+ validatedAt: new Date().toISOString(),
56
+ };
57
+ }
58
+ }
59
+ for (const step of playbook.steps) {
60
+ try {
61
+ const result = await this.executeTool(step.tool, step.params);
62
+ if (result.ok) {
63
+ stepsCompleted++;
64
+ }
65
+ else {
66
+ errors.push(`Step ${stepsCompleted + 1} ("${step.description}") failed: ${result.error ?? "unknown error"}`);
67
+ break; // Stop on first failure
68
+ }
69
+ }
70
+ catch (err) {
71
+ errors.push(`Step ${stepsCompleted + 1} ("${step.description}") threw: ${err instanceof Error ? err.message : String(err)}`);
72
+ break;
73
+ }
74
+ }
75
+ return {
76
+ playbook,
77
+ success: stepsCompleted === playbook.steps.length,
78
+ stepsCompleted,
79
+ totalSteps: playbook.steps.length,
80
+ errors,
81
+ validatedAt: new Date().toISOString(),
82
+ };
83
+ }
84
+ /**
85
+ * Validate multiple playbooks and return the best one.
86
+ */
87
+ async findBest(playbooks) {
88
+ for (const pb of playbooks) {
89
+ const result = await this.validate(pb);
90
+ if (result.success)
91
+ return result;
92
+ }
93
+ return null;
94
+ }
95
+ }
@@ -14,17 +14,12 @@
14
14
  //
15
15
  // You should have received a copy of the GNU Affero General Public License
16
16
  // along with ScreenHand. If not, see <https://www.gnu.org/licenses/>.
17
-
18
- import type { ActionBudget } from "./types.js";
19
-
20
- export const DEFAULT_ACTION_BUDGET: ActionBudget = {
21
- locateMs: 800,
22
- actMs: 200,
23
- verifyMs: 2000,
24
- maxRetries: 1,
17
+ export const DEFAULT_ACTION_BUDGET = {
18
+ locateMs: 800,
19
+ actMs: 200,
20
+ verifyMs: 2000,
21
+ maxRetries: 1,
25
22
  };
26
-
27
23
  export const DEFAULT_NAVIGATE_TIMEOUT_MS = 10_000;
28
24
  export const DEFAULT_WAIT_TIMEOUT_MS = 2_000;
29
25
  export const DEFAULT_PROFILE = "automation";
30
-