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,257 @@
1
+ // Copyright (C) 2025 Clazro Technology Private Limited
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+ /**
4
+ * PlatformLearner — scrape official docs, help center, shortcuts for a platform.
5
+ *
6
+ * Crawls documentation pages via CDP, extracts structured data,
7
+ * and saves as a reference JSON.
8
+ */
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import { writeFileAtomicSync } from "../util/atomic-write.js";
12
+ /** Common URL patterns for platform documentation */
13
+ export function buildDocUrls(platform, rootUrl) {
14
+ const base = rootUrl ?? `https://${platform}.com`;
15
+ const origin = base.replace(/\/$/, "");
16
+ return [
17
+ origin,
18
+ `${origin}/help`,
19
+ `${origin}/support`,
20
+ `${origin}/docs`,
21
+ `${origin}/keyboard-shortcuts`,
22
+ `${origin}/shortcuts`,
23
+ `https://help.${platform}.com`,
24
+ `https://support.${platform}.com`,
25
+ `https://docs.${platform}.com`,
26
+ `${origin}/developers`,
27
+ `${origin}/api`,
28
+ `${origin}/changelog`,
29
+ `${origin}/whats-new`,
30
+ ];
31
+ }
32
+ /** Extract keyboard shortcuts from a page */
33
+ export async function extractShortcuts(cdpEvaluate) {
34
+ const result = await cdpEvaluate(`(() => {
35
+ const shortcuts = {};
36
+ // Look for common shortcut table patterns
37
+ const tables = document.querySelectorAll('table');
38
+ for (const table of tables) {
39
+ const rows = table.querySelectorAll('tr');
40
+ for (const row of rows) {
41
+ const cells = row.querySelectorAll('td, th');
42
+ if (cells.length >= 2) {
43
+ const text0 = (cells[0].textContent || '').trim();
44
+ const text1 = (cells[1].textContent || '').trim();
45
+ // Check if either cell contains key combos
46
+ if (text0.match(/[⌘⌥⇧⌃]|ctrl|cmd|alt|shift/i) || text1.match(/[⌘⌥⇧⌃]|ctrl|cmd|alt|shift/i)) {
47
+ shortcuts[text0] = text1;
48
+ }
49
+ }
50
+ }
51
+ }
52
+ // Also check kbd elements
53
+ const kbds = document.querySelectorAll('kbd');
54
+ for (const kbd of kbds) {
55
+ const parent = kbd.closest('li, tr, p, div');
56
+ if (parent) {
57
+ const keyText = kbd.textContent.trim();
58
+ const descText = parent.textContent.replace(keyText, '').trim().substring(0, 80);
59
+ if (keyText && descText) shortcuts[keyText] = descText;
60
+ }
61
+ }
62
+ return shortcuts;
63
+ })()`);
64
+ return result.result?.value ?? {};
65
+ }
66
+ /** Extract page content as structured text */
67
+ export async function extractPageContent(cdpEvaluate) {
68
+ const result = await cdpEvaluate(`(() => {
69
+ const headings = Array.from(document.querySelectorAll('h1, h2, h3')).map(h => h.textContent.trim()).filter(Boolean);
70
+ const links = Array.from(document.querySelectorAll('a[href]')).slice(0, 100).map(a => ({
71
+ text: (a.textContent || '').trim().substring(0, 80),
72
+ href: a.href,
73
+ })).filter(l => l.text && l.href);
74
+ return {
75
+ title: document.title,
76
+ headings,
77
+ links,
78
+ text: document.body.innerText.substring(0, 8000),
79
+ };
80
+ })()`);
81
+ return result.result?.value ?? { title: "", headings: [], links: [], text: "" };
82
+ }
83
+ /** Extract interactive element selectors from a page */
84
+ export async function extractSelectors(cdpEvaluate) {
85
+ const result = await cdpEvaluate(`(() => {
86
+ const selectors = {};
87
+ const elements = document.querySelectorAll('[data-testid], [aria-label], [role="button"], [role="tab"], [role="menuitem"]');
88
+ for (const el of Array.from(elements).slice(0, 50)) {
89
+ const testId = el.getAttribute('data-testid');
90
+ const label = el.getAttribute('aria-label');
91
+ const key = testId || label || el.textContent?.trim().substring(0, 30) || '';
92
+ if (!key) continue;
93
+
94
+ let selector = '';
95
+ if (testId) selector = '[data-testid="' + testId + '"]';
96
+ else if (el.id) selector = '#' + el.id;
97
+ else if (label) selector = '[aria-label="' + label + '"]';
98
+
99
+ if (selector) selectors[key] = selector;
100
+ }
101
+ return selectors;
102
+ })()`);
103
+ return result.result?.value ?? {};
104
+ }
105
+ /** Crawl a page via CDP: navigate, wait, extract */
106
+ export async function crawlPage(cdpClient, url, timeoutMs = 10000) {
107
+ try {
108
+ // Navigate
109
+ await cdpClient.Page.navigate({ url });
110
+ // Wait for load
111
+ await new Promise((resolve) => {
112
+ const timer = setTimeout(resolve, timeoutMs);
113
+ cdpClient.Page.loadEventFired().then(() => { clearTimeout(timer); resolve(); }).catch(() => { clearTimeout(timer); resolve(); });
114
+ });
115
+ // Extra wait for SPA content
116
+ await new Promise(r => setTimeout(r, 2000));
117
+ const evaluate = async (expr) => {
118
+ return cdpClient.Runtime.evaluate({ expression: expr, returnByValue: true, awaitPromise: true });
119
+ };
120
+ const content = await extractPageContent(evaluate);
121
+ const shortcuts = await extractShortcuts(evaluate);
122
+ const selectors = await extractSelectors(evaluate);
123
+ return { success: true, content, shortcuts, selectors };
124
+ }
125
+ catch (err) {
126
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
127
+ }
128
+ }
129
+ /** Compile crawl results into a learn result */
130
+ export function compileLearnResult(platform, crawledPages) {
131
+ const allShortcuts = {};
132
+ const allSelectors = {};
133
+ const features = [];
134
+ const tips = [];
135
+ const sourceUrls = [];
136
+ const flows = {};
137
+ const apiEndpoints = [];
138
+ const knownLimitations = [];
139
+ for (const page of crawledPages) {
140
+ sourceUrls.push(page.url);
141
+ if (page.shortcuts) {
142
+ Object.assign(allShortcuts, page.shortcuts);
143
+ }
144
+ if (page.selectors && Object.keys(page.selectors).length > 0) {
145
+ const pageName = page.content?.title?.replace(/[^a-zA-Z0-9]/g, "_").substring(0, 30) ?? "page";
146
+ allSelectors[pageName] = page.selectors;
147
+ }
148
+ if (page.content) {
149
+ // Extract features from headings
150
+ for (const h of page.content.headings) {
151
+ if (h.length > 3 && h.length < 80)
152
+ features.push(h);
153
+ }
154
+ // Look for API-related links
155
+ for (const link of page.content.links) {
156
+ if (/api|developer|endpoint|sdk|integration/i.test(link.text)) {
157
+ apiEndpoints.push(`${link.text}: ${link.href}`);
158
+ }
159
+ }
160
+ // Extract flows from numbered step sequences (e.g. "1. Click..." "2. Enter..." "3. Submit...")
161
+ const contentLines = page.content.text.split("\n");
162
+ let currentFlow = null;
163
+ for (let i = 0; i < contentLines.length; i++) {
164
+ const line = contentLines[i].trim();
165
+ const stepMatch = line.match(/^(\d+)[.)]\s+(.+)/);
166
+ if (stepMatch) {
167
+ const stepNum = parseInt(stepMatch[1], 10);
168
+ const stepText = stepMatch[2].trim();
169
+ if (stepNum === 1 && stepText.length > 5) {
170
+ // Start a new flow — use the preceding heading as the name
171
+ const heading = i > 0 ? contentLines.slice(Math.max(0, i - 3), i).find(l => l.trim().length > 3 && !l.trim().match(/^\d/)) : null;
172
+ const flowName = (heading?.trim() ?? `flow_${Object.keys(flows).length + 1}`).replace(/[^a-zA-Z0-9_ ]/g, "").substring(0, 50).trim();
173
+ currentFlow = { name: flowName, steps: [stepText] };
174
+ }
175
+ else if (currentFlow && stepNum > 1) {
176
+ currentFlow.steps.push(stepText);
177
+ }
178
+ }
179
+ else if (currentFlow && currentFlow.steps.length >= 2) {
180
+ // End of step sequence — save the flow
181
+ const key = currentFlow.name.toLowerCase().replace(/\s+/g, "_");
182
+ if (!flows[key]) {
183
+ flows[key] = { description: currentFlow.name, steps: currentFlow.steps };
184
+ }
185
+ currentFlow = null;
186
+ }
187
+ else if (line.length > 0 && !line.match(/^\d/)) {
188
+ currentFlow = null;
189
+ }
190
+ }
191
+ // Save any trailing flow
192
+ if (currentFlow && currentFlow.steps.length >= 2) {
193
+ const key = currentFlow.name.toLowerCase().replace(/\s+/g, "_");
194
+ if (!flows[key]) {
195
+ flows[key] = { description: currentFlow.name, steps: currentFlow.steps };
196
+ }
197
+ }
198
+ // Look for limitation/known-issue mentions
199
+ const text = page.content.text.toLowerCase();
200
+ if (text.includes("limitation") || text.includes("known issue") || text.includes("not supported")) {
201
+ const lines = page.content.text.split("\n");
202
+ for (const line of lines) {
203
+ if (/limitation|known issue|not supported|doesn't support|won't work/i.test(line)) {
204
+ knownLimitations.push(line.trim().substring(0, 200));
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ return {
211
+ platform,
212
+ learnedAt: new Date().toISOString(),
213
+ sourceUrls,
214
+ shortcuts: allShortcuts,
215
+ features: [...new Set(features)].slice(0, 50),
216
+ selectors: allSelectors,
217
+ flows,
218
+ apiEndpoints: [...new Set(apiEndpoints)].slice(0, 20),
219
+ knownLimitations: [...new Set(knownLimitations)].slice(0, 20),
220
+ tips,
221
+ };
222
+ }
223
+ /** Save learn result as a reference JSON */
224
+ export function saveLearnResult(referencesDir, result) {
225
+ if (!fs.existsSync(referencesDir)) {
226
+ fs.mkdirSync(referencesDir, { recursive: true });
227
+ }
228
+ const filePath = path.join(referencesDir, `${result.platform}-learned.json`);
229
+ const reference = {
230
+ id: `${result.platform}-learned`,
231
+ name: `${result.platform} — Auto-Learned from Docs`,
232
+ description: `Scraped ${result.sourceUrls.length} documentation pages. Found ${Object.keys(result.shortcuts).length} shortcuts, ${result.features.length} features.`,
233
+ platform: result.platform,
234
+ bundleId: result.bundleId ?? null,
235
+ version: "1.0.0",
236
+ tags: [result.platform, "auto-learned"],
237
+ successCount: 0,
238
+ failCount: 0,
239
+ urls: Object.fromEntries(result.sourceUrls.map((u, i) => [`doc_${i}`, u])),
240
+ selectors: result.selectors,
241
+ shortcuts: result.shortcuts,
242
+ flows: result.flows,
243
+ detection: {},
244
+ errors: [],
245
+ policyNotes: {},
246
+ _meta: {
247
+ learnedAt: result.learnedAt,
248
+ sourceUrls: result.sourceUrls,
249
+ features: result.features,
250
+ apiEndpoints: result.apiEndpoints,
251
+ knownLimitations: result.knownLimitations,
252
+ tips: result.tips,
253
+ },
254
+ };
255
+ writeFileAtomicSync(filePath, JSON.stringify(reference, null, 2));
256
+ return filePath;
257
+ }