@weavetab/mcp 2.5.0-beta.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 (281) hide show
  1. package/AGENTS.md +48 -0
  2. package/ARCHITECTURE.md +590 -0
  3. package/LICENSE +631 -0
  4. package/README.md +909 -0
  5. package/SECURITY.md +196 -0
  6. package/TROUBLESHOOTING.md +365 -0
  7. package/dist/anticaptcha/detector.d.ts +49 -0
  8. package/dist/anticaptcha/detector.js +465 -0
  9. package/dist/anticaptcha/integration.d.ts +36 -0
  10. package/dist/anticaptcha/integration.js +123 -0
  11. package/dist/anticaptcha/solver.d.ts +27 -0
  12. package/dist/anticaptcha/solver.js +552 -0
  13. package/dist/audit/logger.d.ts +13 -0
  14. package/dist/audit/logger.js +177 -0
  15. package/dist/audit/telemetry.d.ts +36 -0
  16. package/dist/audit/telemetry.js +162 -0
  17. package/dist/cdp/bridge.d.ts +16 -0
  18. package/dist/cdp/bridge.js +826 -0
  19. package/dist/cdp/confirm.d.ts +26 -0
  20. package/dist/cdp/confirm.js +79 -0
  21. package/dist/cdp/connector.d.ts +37 -0
  22. package/dist/cdp/connector.js +661 -0
  23. package/dist/cdp/extension.d.ts +36 -0
  24. package/dist/cdp/extension.js +118 -0
  25. package/dist/cdp/finder.d.ts +1 -0
  26. package/dist/cdp/finder.js +2 -0
  27. package/dist/cdp/helpers.d.ts +11 -0
  28. package/dist/cdp/helpers.js +195 -0
  29. package/dist/cdp/label_cache.d.ts +12 -0
  30. package/dist/cdp/label_cache.js +51 -0
  31. package/dist/cdp/lock.d.ts +33 -0
  32. package/dist/cdp/lock.js +206 -0
  33. package/dist/cdp/omni_broker.d.ts +17 -0
  34. package/dist/cdp/omni_broker.js +119 -0
  35. package/dist/cdp/profiles.d.ts +1 -0
  36. package/dist/cdp/profiles.js +87 -0
  37. package/dist/cdp/pruner.d.ts +41 -0
  38. package/dist/cdp/pruner.js +263 -0
  39. package/dist/cdp/ref_cache.d.ts +101 -0
  40. package/dist/cdp/ref_cache.js +234 -0
  41. package/dist/cdp/retry.d.ts +5 -0
  42. package/dist/cdp/retry.js +39 -0
  43. package/dist/cdp/retry_enhanced.d.ts +44 -0
  44. package/dist/cdp/retry_enhanced.js +102 -0
  45. package/dist/cdp/security.d.ts +50 -0
  46. package/dist/cdp/security.js +237 -0
  47. package/dist/cdp/verify.d.ts +39 -0
  48. package/dist/cdp/verify.js +143 -0
  49. package/dist/cdp/walker.d.ts +91 -0
  50. package/dist/cdp/walker.js +896 -0
  51. package/dist/cdp/walker.test.d.ts +2 -0
  52. package/dist/cdp/walker.test.js +172 -0
  53. package/dist/cdp/websocket_transport.d.ts +15 -0
  54. package/dist/cdp/websocket_transport.js +118 -0
  55. package/dist/cli/plugin.d.ts +9 -0
  56. package/dist/cli/plugin.js +1134 -0
  57. package/dist/cli/updater.d.ts +1 -0
  58. package/dist/cli/updater.js +72 -0
  59. package/dist/config/loader.d.ts +120 -0
  60. package/dist/config/loader.js +704 -0
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.js +1248 -0
  63. package/dist/intelligence/hints.d.ts +11 -0
  64. package/dist/intelligence/hints.js +39 -0
  65. package/dist/intelligence/intent.d.ts +33 -0
  66. package/dist/intelligence/intent.js +171 -0
  67. package/dist/intelligence/sessionMemory.d.ts +66 -0
  68. package/dist/intelligence/sessionMemory.js +284 -0
  69. package/dist/intelligence/sessionMemory.test.d.ts +2 -0
  70. package/dist/intelligence/sessionMemory.test.js +62 -0
  71. package/dist/intelligence/trail.d.ts +64 -0
  72. package/dist/intelligence/trail.js +233 -0
  73. package/dist/intelligence/trail.test.d.ts +2 -0
  74. package/dist/intelligence/trail.test.js +110 -0
  75. package/dist/intelligence/vision.d.ts +24 -0
  76. package/dist/intelligence/vision.js +151 -0
  77. package/dist/lifecycle/mission_manager.d.ts +16 -0
  78. package/dist/lifecycle/mission_manager.js +44 -0
  79. package/dist/overlay/bubble.d.ts +16 -0
  80. package/dist/overlay/bubble.js +37 -0
  81. package/dist/overlay/ghost.d.ts +35 -0
  82. package/dist/overlay/ghost.js +508 -0
  83. package/dist/overlay/injector.d.ts +22 -0
  84. package/dist/overlay/injector.js +198 -0
  85. package/dist/plugin/interface.d.ts +134 -0
  86. package/dist/plugin/interface.js +1 -0
  87. package/dist/plugin/loader.d.ts +18 -0
  88. package/dist/plugin/loader.js +155 -0
  89. package/dist/plugin/registry.d.ts +50 -0
  90. package/dist/plugin/registry.js +234 -0
  91. package/dist/security/blacklist.d.ts +24 -0
  92. package/dist/security/blacklist.js +80 -0
  93. package/dist/security/filter.d.ts +2 -0
  94. package/dist/security/filter.js +20 -0
  95. package/dist/security/guard.d.ts +8 -0
  96. package/dist/security/guard.js +35 -0
  97. package/dist/security/ratelimit.d.ts +11 -0
  98. package/dist/security/ratelimit.js +57 -0
  99. package/dist/security/secrets.d.ts +75 -0
  100. package/dist/security/secrets.js +365 -0
  101. package/dist/sensors/actionability.d.ts +81 -0
  102. package/dist/sensors/actionability.js +303 -0
  103. package/dist/sensors/guard.d.ts +25 -0
  104. package/dist/sensors/guard.js +168 -0
  105. package/dist/sensors/mutations.d.ts +23 -0
  106. package/dist/sensors/mutations.js +93 -0
  107. package/dist/sensors/network.d.ts +67 -0
  108. package/dist/sensors/network.js +193 -0
  109. package/dist/sensors/operation_monitor.d.ts +80 -0
  110. package/dist/sensors/operation_monitor.js +203 -0
  111. package/dist/sensors/pacing.d.ts +44 -0
  112. package/dist/sensors/pacing.js +111 -0
  113. package/dist/sensors/thoughts.d.ts +2 -0
  114. package/dist/sensors/thoughts.js +59 -0
  115. package/dist/sensors/wait.d.ts +12 -0
  116. package/dist/sensors/wait.js +44 -0
  117. package/dist/server.d.ts +7 -0
  118. package/dist/server.js +1687 -0
  119. package/dist/state/agent.d.ts +36 -0
  120. package/dist/state/agent.js +101 -0
  121. package/dist/state/cache.d.ts +16 -0
  122. package/dist/state/cache.js +92 -0
  123. package/dist/state/cache.test.d.ts +2 -0
  124. package/dist/state/cache.test.js +76 -0
  125. package/dist/state/delta.d.ts +71 -0
  126. package/dist/state/delta.js +234 -0
  127. package/dist/state/delta.test.d.ts +2 -0
  128. package/dist/state/delta.test.js +279 -0
  129. package/dist/state/groups.d.ts +15 -0
  130. package/dist/state/groups.js +80 -0
  131. package/dist/state/session.d.ts +30 -0
  132. package/dist/state/session.js +155 -0
  133. package/dist/tools/accessibility.d.ts +14 -0
  134. package/dist/tools/accessibility.js +32 -0
  135. package/dist/tools/ask.d.ts +14 -0
  136. package/dist/tools/ask.js +49 -0
  137. package/dist/tools/automation.d.ts +36 -0
  138. package/dist/tools/automation.js +218 -0
  139. package/dist/tools/browser_detect.d.ts +13 -0
  140. package/dist/tools/browser_detect.js +73 -0
  141. package/dist/tools/burst.d.ts +35 -0
  142. package/dist/tools/burst.js +344 -0
  143. package/dist/tools/canvas.d.ts +25 -0
  144. package/dist/tools/canvas.js +132 -0
  145. package/dist/tools/captcha.d.ts +38 -0
  146. package/dist/tools/captcha.js +209 -0
  147. package/dist/tools/click.d.ts +36 -0
  148. package/dist/tools/click.js +561 -0
  149. package/dist/tools/click_and_wait.d.ts +33 -0
  150. package/dist/tools/click_and_wait.js +149 -0
  151. package/dist/tools/clipboard.d.ts +29 -0
  152. package/dist/tools/clipboard.js +279 -0
  153. package/dist/tools/console.d.ts +21 -0
  154. package/dist/tools/console.js +107 -0
  155. package/dist/tools/cookies.d.ts +67 -0
  156. package/dist/tools/cookies.js +121 -0
  157. package/dist/tools/design_extract.d.ts +109 -0
  158. package/dist/tools/design_extract.js +641 -0
  159. package/dist/tools/dialog.d.ts +24 -0
  160. package/dist/tools/dialog.js +109 -0
  161. package/dist/tools/drag.d.ts +15 -0
  162. package/dist/tools/drag.js +29 -0
  163. package/dist/tools/emulation.d.ts +33 -0
  164. package/dist/tools/emulation.js +69 -0
  165. package/dist/tools/eval.d.ts +20 -0
  166. package/dist/tools/eval.js +92 -0
  167. package/dist/tools/fill.d.ts +41 -0
  168. package/dist/tools/fill.js +667 -0
  169. package/dist/tools/find.d.ts +30 -0
  170. package/dist/tools/find.js +60 -0
  171. package/dist/tools/geo.d.ts +19 -0
  172. package/dist/tools/geo.js +29 -0
  173. package/dist/tools/github/analyze.d.ts +4 -0
  174. package/dist/tools/github/analyze.js +113 -0
  175. package/dist/tools/github/cache.d.ts +21 -0
  176. package/dist/tools/github/cache.js +28 -0
  177. package/dist/tools/github/index.d.ts +4 -0
  178. package/dist/tools/github/index.js +4 -0
  179. package/dist/tools/github/issues.d.ts +9 -0
  180. package/dist/tools/github/issues.js +88 -0
  181. package/dist/tools/github/pr.d.ts +5 -0
  182. package/dist/tools/github/pr.js +88 -0
  183. package/dist/tools/github/rateLimiter.d.ts +4 -0
  184. package/dist/tools/github/rateLimiter.js +46 -0
  185. package/dist/tools/github/read.d.ts +34 -0
  186. package/dist/tools/github/read.js +74 -0
  187. package/dist/tools/github/repoContext.d.ts +20 -0
  188. package/dist/tools/github/repoContext.js +129 -0
  189. package/dist/tools/highlight.d.ts +20 -0
  190. package/dist/tools/highlight.js +140 -0
  191. package/dist/tools/hover.d.ts +19 -0
  192. package/dist/tools/hover.js +57 -0
  193. package/dist/tools/inspect.d.ts +17 -0
  194. package/dist/tools/inspect.js +125 -0
  195. package/dist/tools/integration.test.d.ts +2 -0
  196. package/dist/tools/integration.test.js +96 -0
  197. package/dist/tools/key.d.ts +25 -0
  198. package/dist/tools/key.js +173 -0
  199. package/dist/tools/macro_compiler.d.ts +7 -0
  200. package/dist/tools/macro_compiler.js +82 -0
  201. package/dist/tools/mouse.d.ts +21 -0
  202. package/dist/tools/mouse.js +62 -0
  203. package/dist/tools/navigate.d.ts +16 -0
  204. package/dist/tools/navigate.js +188 -0
  205. package/dist/tools/network_intercept.d.ts +17 -0
  206. package/dist/tools/network_intercept.js +151 -0
  207. package/dist/tools/office/index.d.ts +9 -0
  208. package/dist/tools/office/index.js +7 -0
  209. package/dist/tools/office/list.d.ts +15 -0
  210. package/dist/tools/office/list.js +41 -0
  211. package/dist/tools/office/parse.d.ts +20 -0
  212. package/dist/tools/office/parse.js +172 -0
  213. package/dist/tools/pdf.d.ts +22 -0
  214. package/dist/tools/pdf.js +42 -0
  215. package/dist/tools/performance.d.ts +12 -0
  216. package/dist/tools/performance.js +47 -0
  217. package/dist/tools/performance_test.d.ts +70 -0
  218. package/dist/tools/performance_test.js +128 -0
  219. package/dist/tools/plan.d.ts +39 -0
  220. package/dist/tools/plan.js +161 -0
  221. package/dist/tools/pointer.d.ts +35 -0
  222. package/dist/tools/pointer.js +522 -0
  223. package/dist/tools/read.d.ts +30 -0
  224. package/dist/tools/read.js +488 -0
  225. package/dist/tools/recording.d.ts +45 -0
  226. package/dist/tools/recording.js +124 -0
  227. package/dist/tools/response.d.ts +129 -0
  228. package/dist/tools/response.js +5 -0
  229. package/dist/tools/scrape.d.ts +24 -0
  230. package/dist/tools/scrape.js +140 -0
  231. package/dist/tools/screenshot.d.ts +40 -0
  232. package/dist/tools/screenshot.js +79 -0
  233. package/dist/tools/scroll.d.ts +23 -0
  234. package/dist/tools/scroll.js +196 -0
  235. package/dist/tools/select.d.ts +13 -0
  236. package/dist/tools/select.js +217 -0
  237. package/dist/tools/snapshot.d.ts +27 -0
  238. package/dist/tools/snapshot.js +99 -0
  239. package/dist/tools/storage.d.ts +18 -0
  240. package/dist/tools/storage.js +29 -0
  241. package/dist/tools/tabs.d.ts +77 -0
  242. package/dist/tools/tabs.js +342 -0
  243. package/dist/tools/thought.d.ts +16 -0
  244. package/dist/tools/thought.js +79 -0
  245. package/dist/tools/type.d.ts +65 -0
  246. package/dist/tools/type.js +1061 -0
  247. package/dist/tools/type_secret.d.ts +23 -0
  248. package/dist/tools/type_secret.js +229 -0
  249. package/dist/tools/upload.d.ts +19 -0
  250. package/dist/tools/upload.js +103 -0
  251. package/dist/tools/video_compiler.d.ts +24 -0
  252. package/dist/tools/video_compiler.js +199 -0
  253. package/dist/tools/viewport.d.ts +44 -0
  254. package/dist/tools/viewport.js +230 -0
  255. package/dist/tools/wait.d.ts +19 -0
  256. package/dist/tools/wait.js +202 -0
  257. package/dist/tools/wait_for.d.ts +23 -0
  258. package/dist/tools/wait_for.js +30 -0
  259. package/dist/tools/windows.d.ts +22 -0
  260. package/dist/tools/windows.js +57 -0
  261. package/dist/ui/index.d.ts +1 -0
  262. package/dist/ui/index.js +12 -0
  263. package/docs/DEV_MODE.md +68 -0
  264. package/docs/DOCUMENTATION.md +532 -0
  265. package/docs/PLUGINS.md +538 -0
  266. package/docs/TOOLS.md +104 -0
  267. package/docs/dev_plugin.md +310 -0
  268. package/ext-dist/background.js +467 -0
  269. package/ext-dist/content.js +3089 -0
  270. package/ext-dist/dashboard.html +583 -0
  271. package/ext-dist/hud/hud.js +73 -0
  272. package/ext-dist/hud/index.html +20 -0
  273. package/ext-dist/manifest.json +77 -0
  274. package/ext-dist/sidepanel/sidepanel.js +25 -0
  275. package/ext-dist/sidepanel.html +547 -0
  276. package/ext-dist/styles.css +218 -0
  277. package/icon.png +0 -0
  278. package/mcp.json +796 -0
  279. package/package.json +113 -0
  280. package/server.json +23 -0
  281. package/smithery.yaml +11 -0
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Weavetab — tools/type_secret.ts
3
+ * browser_type_secret: Securely injects a stored credential into a page input.
4
+ *
5
+ * Security model:
6
+ * - Secret value is resolved from JsonSecretStore (never process.env).
7
+ * - Domain enforcement: hard-fails if the current tab's hostname is not in the
8
+ * key's allowed domain list. This is the primary prompt-injection defense.
9
+ * - Value travels in-process only: secretStore → CDP Input.insertText.
10
+ * It is never returned in any tool_result, never logged, never trailed.
11
+ * - The DOM node is marked data-wt-secret so all subsequent read tools
12
+ * (browser_map, browser_scrape, browser_snapshot, delta) redact it.
13
+ * - Trail log records: key name + domain only, never value.
14
+ *
15
+ * Weavetab by fy2ne
16
+ */
17
+ import CDP from "chrome-remote-interface";
18
+ import type { Config } from "../config/loader.js";
19
+ export declare function weaveTypeSecret(session: CDP.Client, args: {
20
+ id?: string;
21
+ target?: string;
22
+ envKey: string;
23
+ }, config: Config): Promise<any>;
@@ -0,0 +1,229 @@
1
+ import { buildActionMap, findAllMatches } from "../cdp/walker.js";
2
+ import { logAction } from "../audit/logger.js";
3
+ import { recordAction } from "../intelligence/trail.js";
4
+ import { SessionSecretTracker, secretStore } from "../security/secrets.js";
5
+ import { sessionRegistry } from "../state/session.js";
6
+ import { getConfigAgentId } from "../config/loader.js";
7
+ import { sendToExtension } from "../cdp/helpers.js";
8
+ import { cacheActionMap, getCachedActionMap } from "../state/cache.js";
9
+ export async function weaveTypeSecret(session, args, config) {
10
+ const startTime = Date.now();
11
+ let targetId = "default";
12
+ const active = sessionRegistry.getActive(getConfigAgentId(config));
13
+ if (active)
14
+ targetId = active.targetId;
15
+ // ── Step 1: Resolve current tab domain ───────────────────────────────────
16
+ const { Page } = session;
17
+ const { frameTree } = await Page.getFrameTree();
18
+ const url = frameTree.frame.url;
19
+ let currentHostname;
20
+ try {
21
+ currentHostname = new URL(url).hostname;
22
+ }
23
+ catch {
24
+ return {
25
+ success: false,
26
+ error_code: "INVALID_URL",
27
+ message: `[!] Cannot determine current tab domain from URL: "${url}"`,
28
+ };
29
+ }
30
+ // ── Step 2: Domain enforcement (prompt-injection defense) ─────────────────
31
+ // This check is done BEFORE resolving the secret value.
32
+ const allowedDomains = secretStore.getDomains(args.envKey);
33
+ if (allowedDomains === null) {
34
+ return {
35
+ success: false,
36
+ error_code: "SECRET_NOT_FOUND",
37
+ message: `[!] Secret not found for key: "${args.envKey}". Run: Weavetab secrets set ${args.envKey}`,
38
+ };
39
+ }
40
+ const domainAllowed = secretStore.checkDomain(args.envKey, currentHostname);
41
+ if (!domainAllowed) {
42
+ // Fail BEFORE touching the secret value — the value is never loaded.
43
+ return {
44
+ success: false,
45
+ error_code: "DOMAIN_NOT_ALLOWED",
46
+ message: `[!] Secret "${args.envKey}" is not scoped for domain "${currentHostname}". ` +
47
+ (allowedDomains.length > 0
48
+ ? `Allowed: [${allowedDomains.join(", ")}]. ` +
49
+ `To add this domain: Weavetab secrets set ${args.envKey}`
50
+ : `The allowed domain list is empty — add domains with: Weavetab secrets set ${args.envKey}`),
51
+ };
52
+ }
53
+ // ── Step 3: Resolve secret value (in-process only from here) ──────────────
54
+ const secretValue = await secretStore.getSecret(args.envKey);
55
+ if (secretValue === undefined) {
56
+ return {
57
+ success: false,
58
+ error_code: "SECRET_NOT_FOUND",
59
+ message: `[!] Secret not found for key: "${args.envKey}"`,
60
+ };
61
+ }
62
+ // ── Step 4: Build / cache action map ──────────────────────────────────────
63
+ // Use cached map to prevent stale w: refs if the DOM hasn't changed.
64
+ // The pruner's data-wt-secret guard fires on the element attribute, NOT the
65
+ // id, so secret fields survive any preceding browser_map({ prune: true }) call.
66
+ let map = getCachedActionMap(targetId, url);
67
+ if (!map) {
68
+ map = await buildActionMap(session, { lite: true }, targetId);
69
+ cacheActionMap(targetId, map);
70
+ }
71
+ const query = args.id || args.target;
72
+ if (!query) {
73
+ return { success: false, error_code: "INVALID_ARGS", message: "[!] Must provide id or target" };
74
+ }
75
+ let target = map.elements.find(el => el.id === query || el.id.endsWith(query.startsWith("w:") ? query.slice(2) : query));
76
+ if (!target) {
77
+ const matches = findAllMatches(map.elements, query);
78
+ if (matches.length === 0) {
79
+ return {
80
+ success: false,
81
+ error_code: "ELEMENT_NOT_FOUND",
82
+ message: `[!] Could not find input matching "${query}"`,
83
+ };
84
+ }
85
+ if (matches.length > 1) {
86
+ return {
87
+ success: false,
88
+ error_code: "MULTIPLE_ELEMENTS_FOUND",
89
+ message: `[!] Refusing to guess: found ${matches.length} matches for "${query}". Please use a more specific selector.`,
90
+ };
91
+ }
92
+ target = matches[0];
93
+ }
94
+ const selector = target.selector || `[data-wt-id="${target.id}"]`;
95
+ await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `injecting [REDACTED]...` } });
96
+ // ── Step 5: Resolve DOM node, mark as secret, focus ───────────────────────
97
+ const nodeInfo = await session.Runtime.evaluate({
98
+ expression: `(function(sel) {
99
+ const el = document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
100
+ if (!el) throw new Error('NOT_FOUND');
101
+
102
+ const tag = el.tagName.toLowerCase();
103
+ const isEditable = tag === 'input' || tag === 'textarea' || el.isContentEditable;
104
+ if (!isEditable) throw new Error('NOT_EDITABLE');
105
+
106
+ // Mark as secret so all subsequent read tools redact this element
107
+ el.setAttribute('data-wt-secret', 'true');
108
+ if (typeof el.select === 'function') { el.select(); }
109
+ else if (el.isContentEditable) { el.textContent = ''; el.dispatchEvent(new Event('input',{bubbles:true})); }
110
+ else { el.value = ''; el.dispatchEvent(new Event('input',{bubbles:true})); }
111
+
112
+ return el;
113
+ })(${JSON.stringify(selector)})`,
114
+ returnByValue: false
115
+ });
116
+ if (nodeInfo.exceptionDetails) {
117
+ const errMsg = nodeInfo.exceptionDetails.exception?.description || "";
118
+ if (errMsg.includes('NOT_EDITABLE')) {
119
+ return { success: false, error_code: "NOT_EDITABLE", message: `[!] Target ${selector} is not an editable field.` };
120
+ }
121
+ if (errMsg.includes('NOT_FOUND')) {
122
+ return { success: false, error_code: "ELEMENT_NOT_FOUND", message: `[!] Could not find element ${selector}.` };
123
+ }
124
+ return { success: false, error_code: "DOM_EVAL_FAILED", message: `[!] Evaluation failed while resolving target.` };
125
+ }
126
+ if (!nodeInfo.result?.objectId) {
127
+ return { success: false, error_code: "DOM_RESOLUTION_FAILED", message: `[!] Failed to resolve DOM node for ${selector}` };
128
+ }
129
+ // Request node to get backendNodeId for SessionSecretTracker
130
+ const nodeDetails = await session.DOM.requestNode({ objectId: nodeInfo.result.objectId });
131
+ if (nodeDetails.nodeId) {
132
+ const desc = await session.DOM.describeNode({ nodeId: nodeDetails.nodeId });
133
+ if (desc.node.backendNodeId) {
134
+ SessionSecretTracker.markNodeAsSecret(targetId, desc.node.backendNodeId);
135
+ }
136
+ }
137
+ // ── Step 6: Focus element ─────────────────────────────────────────────────
138
+ await session.Runtime.callFunctionOn({
139
+ functionDeclaration: `function() {
140
+ if (this.scrollIntoViewIfNeeded) this.scrollIntoViewIfNeeded();
141
+ else this.scrollIntoView({behavior:'smooth',block:'center'});
142
+ this.focus();
143
+ this.click();
144
+ }`,
145
+ objectId: nodeInfo.result.objectId,
146
+ silent: true
147
+ });
148
+ // ── Step 7: Inject secret via CDP insertText ───────────────────────────────
149
+ // This is the ONLY point where secretValue is used.
150
+ // It goes directly to CDP — never into any variable that could be returned.
151
+ SessionSecretTracker.registerSecretValue(targetId, secretValue);
152
+ await session.Input.insertText({ text: secretValue });
153
+ // ── Step 7.5: Hardened Proxy & Shadow DOM Isolation ───────────────────────
154
+ await session.Runtime.callFunctionOn({
155
+ functionDeclaration: `function() {
156
+ try {
157
+ const el = this;
158
+ // 1. Redact direct .value access
159
+ const proto = Object.getPrototypeOf(el);
160
+ const origDesc = Object.getOwnPropertyDescriptor(proto, 'value');
161
+ if (origDesc) {
162
+ Object.defineProperty(el, 'value', {
163
+ get: function() { return '[REDACTED]'; },
164
+ set: origDesc.set,
165
+ configurable: false,
166
+ enumerable: true
167
+ });
168
+ }
169
+
170
+ // 2. Defeat array spread, iteration, and coercion
171
+ Object.defineProperty(el, Symbol.iterator, {
172
+ value: function* () { yield '[REDACTED]'; },
173
+ configurable: false
174
+ });
175
+ Object.defineProperty(el, Symbol.toPrimitive, {
176
+ value: function() { return '[REDACTED]'; },
177
+ configurable: false
178
+ });
179
+ // 2b. Defeat JSON.stringify and structuredClone data leaks
180
+ Object.defineProperty(el, 'toJSON', {
181
+ value: function() { return '[REDACTED]'; },
182
+ configurable: false
183
+ });
184
+
185
+ // 3. Isolate in closed shadow DOM to prevent outer-world DOM traversal
186
+ if (el.parentNode && !el.parentNode.shadowRoot) {
187
+ const wrapper = document.createElement('div');
188
+ wrapper.className = '__wt_secret_vault';
189
+ // copy styles so layout doesn't break
190
+ const compStyle = window.getComputedStyle(el);
191
+ wrapper.style.display = compStyle.display === 'inline' ? 'inline-block' : compStyle.display;
192
+ wrapper.style.width = compStyle.width;
193
+ wrapper.style.height = compStyle.height;
194
+
195
+ el.parentNode.insertBefore(wrapper, el);
196
+ const shadow = wrapper.attachShadow({ mode: 'closed' });
197
+ shadow.appendChild(el);
198
+ }
199
+ } catch (e) {
200
+ // fail silently so as not to disrupt the page execution
201
+ }
202
+ }`,
203
+ objectId: nodeInfo.result.objectId,
204
+ silent: true
205
+ });
206
+ // Immediately zero out the local binding after use
207
+ // (TypeScript doesn't guarantee this but it signals intent to reviewers)
208
+ // secretValue = undefined; // can't reassign const — variable goes out of scope here
209
+ const executionMs = Date.now() - startTime;
210
+ // ── Step 8: Trail — key name + domain only, NEVER value ──────────────────
211
+ const finalRef = target.id.startsWith('w:') ? target.id : `w:${target.id}`;
212
+ logAction("browser_type_secret", finalRef, `[secret injected: ${args.envKey}@${currentHostname}] into ${target.label} (exec: ${executionMs}ms)`);
213
+ recordAction({
214
+ timestamp: new Date().toISOString(),
215
+ tool: "browser_type_secret",
216
+ // input contains key name and domain ONLY — value is excluded intentionally
217
+ input: { ref: finalRef, label: target.label, envKey: args.envKey, domain: currentHostname },
218
+ result: { success: true },
219
+ });
220
+ // ── Step 9: Return — no secret value in result ────────────────────────────
221
+ return {
222
+ success: true,
223
+ typed_into: target.label,
224
+ ref: finalRef,
225
+ key: args.envKey,
226
+ domain: currentHostname,
227
+ execution_ms: executionMs,
228
+ };
229
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Weavetab — tools/upload.ts
3
+ * browser_upload: inject file paths into <input type="file"> elements without OS dialog.
4
+ * Weavetab by fy2ne
5
+ */
6
+ import CDP from "chrome-remote-interface";
7
+ import type { Config } from "../config/loader.js";
8
+ export interface UploadOptions {
9
+ nodeId?: number;
10
+ ref?: string;
11
+ selector?: string;
12
+ label?: string;
13
+ filePaths: string[];
14
+ }
15
+ export declare function browserUpload(session: CDP.Client, options: UploadOptions, config: Config): Promise<{
16
+ success: boolean;
17
+ message: string;
18
+ file: string;
19
+ }>;
@@ -0,0 +1,103 @@
1
+ import { getConfigAgentId } from "../config/loader.js";
2
+ import * as fs from "node:fs";
3
+ import path from "node:path";
4
+ export async function browserUpload(session, options, config) {
5
+ const { nodeId, ref, selector, label, filePaths } = options;
6
+ if (!filePaths || filePaths.length === 0) {
7
+ throw new Error("browser_upload requires an array of filePaths");
8
+ }
9
+ // Verify files exist locally
10
+ for (const p of filePaths) {
11
+ if (!fs.existsSync(p)) {
12
+ return { success: false, message: `File not found: ${p}`, file: p };
13
+ }
14
+ }
15
+ let targetNodeId = nodeId;
16
+ let finalSelector = selector;
17
+ // Add ref resolution (e.g. "w:42") — works like other tools
18
+ const resolveRef = ref || selector || label;
19
+ let fallbackObjectId;
20
+ if (!targetNodeId && resolveRef) {
21
+ const { buildActionMap } = await import("../cdp/walker.js");
22
+ const { sessionRegistry } = await import("../state/session.js");
23
+ const targetId = sessionRegistry.getActive(getConfigAgentId(config))?.targetId ?? "default";
24
+ const map = await buildActionMap(session, { lite: true }, targetId);
25
+ const element = ref
26
+ ? map.elements.find(el => el.id === ref || el.id.endsWith(ref.startsWith('w:') ? ref.slice(2) : ref))
27
+ : selector
28
+ ? map.elements.find(el => el.id === selector || el.selector === selector)
29
+ : map.elements.find(el => el.label === label);
30
+ if (!element) {
31
+ // ENTERPRISE FIX #7: Fallback to full DOM query for hidden file inputs pruned by lite: true
32
+ try {
33
+ const query = selector
34
+ ? `(document.querySelector(${JSON.stringify(selector)}) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(${JSON.stringify(selector)}) : null))`
35
+ : `(document.querySelector('input[type="file"]') || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery('input[type="file"]') : document.querySelectorAll('input[type="file"]')[0]))`;
36
+ const res = await session.Runtime.evaluate({
37
+ expression: query,
38
+ returnByValue: false
39
+ });
40
+ if (res.result && res.result.objectId && res.result.subtype === 'node') {
41
+ fallbackObjectId = res.result.objectId;
42
+ finalSelector = selector || 'input[type="file"]';
43
+ }
44
+ }
45
+ catch (e) {
46
+ // ignore fallback errors
47
+ }
48
+ if (!fallbackObjectId) {
49
+ throw new Error(`Upload input not found matching "${resolveRef}" — use browser_map to see available elements with their ref IDs, or provide a CSS selector`);
50
+ }
51
+ }
52
+ else {
53
+ targetNodeId = element.backendNodeId;
54
+ finalSelector = element.selector;
55
+ }
56
+ }
57
+ if (!targetNodeId && !fallbackObjectId) {
58
+ throw new Error("browser_upload requires ref, selector, or label (nodeId is also accepted but prefer ref from browser_map)");
59
+ }
60
+ try {
61
+ if (fallbackObjectId) {
62
+ await session.DOM.setFileInputFiles({
63
+ objectId: fallbackObjectId,
64
+ files: filePaths
65
+ });
66
+ }
67
+ else if (targetNodeId) {
68
+ const { nodeIds } = await session.DOM.pushNodesByBackendIdsToFrontend({ backendNodeIds: [targetNodeId] });
69
+ if (!nodeIds[0])
70
+ throw new Error("Node not found in DOM");
71
+ await session.DOM.setFileInputFiles({
72
+ nodeId: nodeIds[0],
73
+ files: filePaths
74
+ });
75
+ }
76
+ else {
77
+ throw new Error("No target resolved for upload");
78
+ }
79
+ // Verify upload registered via JS
80
+ let uploaded = false;
81
+ if (finalSelector) {
82
+ const res = await session.Runtime.evaluate({
83
+ expression: `(function(sel) {
84
+ const el = document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
85
+ return el?.files?.length > 0;
86
+ })(${JSON.stringify(finalSelector)})`,
87
+ returnByValue: true
88
+ }).catch(() => null);
89
+ uploaded = res?.result?.value === true;
90
+ }
91
+ else {
92
+ uploaded = true; // Cannot verify without selector
93
+ }
94
+ return {
95
+ success: uploaded,
96
+ message: `Successfully attached ${filePaths.length} file(s)`,
97
+ file: path.basename(filePaths[0])
98
+ };
99
+ }
100
+ catch (error) {
101
+ throw new Error(`Failed to upload: ${error}`);
102
+ }
103
+ }
@@ -0,0 +1,24 @@
1
+ export interface CompileVideoOptions {
2
+ recordingDir: string;
3
+ fps: number;
4
+ format?: "webp" | "gif" | "webm" | "mp4" | "frames";
5
+ keepFrames?: boolean;
6
+ }
7
+ export interface CompileVideoResult {
8
+ success: boolean;
9
+ videoPath?: string;
10
+ format?: string;
11
+ durationSeconds?: number;
12
+ fileSizeBytes?: number;
13
+ cleanedFrames?: boolean;
14
+ error?: string;
15
+ compilerUsed?: string;
16
+ }
17
+ /**
18
+ * Checks if ffmpeg is available on the system PATH.
19
+ */
20
+ export declare function isFFmpegAvailable(): boolean;
21
+ /**
22
+ * Compiles JPEG frames in a directory into a video/animation file.
23
+ */
24
+ export declare function compileVideo(options: CompileVideoOptions): Promise<CompileVideoResult>;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Weavetab — tools/video_compiler.ts
3
+ * Assembles screencast JPEG frames into video/animation files (.webp, .gif, .webm, .mp4).
4
+ * Uses native sharp (built-in Node dependency) for WebP/GIF animations without external setup.
5
+ * Falls back to ffmpeg if installed for .mp4/.webm video encoding.
6
+ * Weavetab by fy2ne
7
+ */
8
+ import { exec, execSync } from "node:child_process";
9
+ import * as fs from "node:fs";
10
+ import * as path from "node:path";
11
+ /**
12
+ * Checks if ffmpeg is available on the system PATH.
13
+ */
14
+ export function isFFmpegAvailable() {
15
+ try {
16
+ const cmd = process.platform === "win32" ? "where ffmpeg" : "which ffmpeg";
17
+ execSync(cmd, { stdio: "ignore" });
18
+ return true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ /**
25
+ * Compiles JPEG frames in a directory into a video/animation file.
26
+ */
27
+ export async function compileVideo(options) {
28
+ const { recordingDir, fps = 3, format = "webp", keepFrames = false } = options;
29
+ if (format === "frames") {
30
+ return {
31
+ success: true,
32
+ cleanedFrames: false,
33
+ compilerUsed: "raw_frames",
34
+ };
35
+ }
36
+ if (!fs.existsSync(recordingDir)) {
37
+ return { success: false, error: `Recording directory not found: ${recordingDir}` };
38
+ }
39
+ const files = fs.readdirSync(recordingDir).filter(f => f.startsWith("frame-") && f.endsWith(".jpg")).sort();
40
+ if (files.length === 0) {
41
+ return { success: false, error: "No image frames found in recording directory" };
42
+ }
43
+ const durationSeconds = Math.round((files.length / fps) * 10) / 10;
44
+ const delayMs = Math.round(1000 / Math.max(1, fps));
45
+ // Try pure JS sharp compilation first for webp / gif (zero system dependencies required)
46
+ if (format === "webp" || format === "gif") {
47
+ try {
48
+ const sharp = (await import("sharp")).default;
49
+ // Load first frame metadata to determine dimensions
50
+ const firstFramePath = path.join(recordingDir, files[0]);
51
+ const meta = await sharp(firstFramePath).metadata();
52
+ const width = meta.width || 1280;
53
+ const height = meta.height || 720;
54
+ // Limit batch size if too large to prevent out-of-memory
55
+ const batchFiles = files.slice(0, 300);
56
+ // Create composite list vertically stacked
57
+ const compositeList = await Promise.all(batchFiles.map(async (filename, index) => ({
58
+ input: path.join(recordingDir, filename),
59
+ top: index * height,
60
+ left: 0,
61
+ })));
62
+ const canvasBuffer = await sharp({
63
+ create: {
64
+ width,
65
+ height: height * batchFiles.length,
66
+ channels: 3,
67
+ background: { r: 0, g: 0, b: 0 },
68
+ },
69
+ })
70
+ .composite(compositeList)
71
+ .png()
72
+ .toBuffer();
73
+ const outputFileName = `recording.${format}`;
74
+ const videoPath = path.join(recordingDir, outputFileName);
75
+ let videoBuffer;
76
+ if (format === "gif") {
77
+ videoBuffer = await sharp(canvasBuffer, { pageHeight: height })
78
+ .gif({ delay: delayMs })
79
+ .toBuffer();
80
+ }
81
+ else {
82
+ videoBuffer = await sharp(canvasBuffer, { pageHeight: height })
83
+ .webp({ loop: 0, delay: delayMs, quality: 80 })
84
+ .toBuffer();
85
+ }
86
+ fs.writeFileSync(videoPath, videoBuffer);
87
+ const fileSizeBytes = videoBuffer.length;
88
+ // Clean up raw frames if keepFrames is false
89
+ let cleaned = false;
90
+ if (!keepFrames) {
91
+ for (const f of files) {
92
+ try {
93
+ fs.unlinkSync(path.join(recordingDir, f));
94
+ }
95
+ catch { }
96
+ }
97
+ cleaned = true;
98
+ }
99
+ return {
100
+ success: true,
101
+ videoPath,
102
+ format,
103
+ durationSeconds,
104
+ fileSizeBytes,
105
+ cleanedFrames: cleaned,
106
+ compilerUsed: "sharp_pure_node",
107
+ };
108
+ }
109
+ catch (sharpErr) {
110
+ // Fall through to ffmpeg or manifest fallback if sharp fails
111
+ }
112
+ }
113
+ // Check if FFmpeg is available for webm / mp4 or fallback
114
+ const ffmpegPresent = isFFmpegAvailable();
115
+ if (ffmpegPresent) {
116
+ try {
117
+ const outputFileName = `recording.${format === "webp" || format === "gif" ? format : format}`;
118
+ const videoPath = path.join(recordingDir, outputFileName);
119
+ await new Promise((resolve, reject) => {
120
+ const concatFilePath = path.join(recordingDir, "concat.txt");
121
+ const concatContent = files.map(f => `file '${f.replace(/'/g, "'\\''")}'\nduration ${1 / fps}`).join("\n");
122
+ fs.writeFileSync(concatFilePath, concatContent);
123
+ let codecFlags = "";
124
+ if (format === "webm") {
125
+ codecFlags = "-c:v libvpx-vp9 -pix_fmt yuv420p -b:v 1M";
126
+ }
127
+ else if (format === "mp4") {
128
+ codecFlags = "-c:v libx264 -pix_fmt yuv420p -vf \"trunc(iw/2)*2:trunc(ih/2)*2\"";
129
+ }
130
+ else if (format === "gif") {
131
+ codecFlags = `-vf "fps=${fps},scale=640:-1:flags=lanczos"`;
132
+ }
133
+ else {
134
+ codecFlags = "-c:v libvpx-vp9 -pix_fmt yuv420p -b:v 1M";
135
+ }
136
+ const cmd = `ffmpeg -y -f concat -safe 0 -i "${concatFilePath}" ${codecFlags} "${videoPath}"`;
137
+ exec(cmd, { cwd: recordingDir }, (error) => {
138
+ try {
139
+ fs.unlinkSync(concatFilePath);
140
+ }
141
+ catch { }
142
+ if (error)
143
+ reject(error);
144
+ else
145
+ resolve();
146
+ });
147
+ });
148
+ let fileSizeBytes = 0;
149
+ if (fs.existsSync(videoPath)) {
150
+ fileSizeBytes = fs.statSync(videoPath).size;
151
+ }
152
+ let cleaned = false;
153
+ if (!keepFrames) {
154
+ for (const f of files) {
155
+ try {
156
+ fs.unlinkSync(path.join(recordingDir, f));
157
+ }
158
+ catch { }
159
+ }
160
+ cleaned = true;
161
+ }
162
+ return {
163
+ success: true,
164
+ videoPath,
165
+ format,
166
+ durationSeconds,
167
+ fileSizeBytes,
168
+ cleanedFrames: cleaned,
169
+ compilerUsed: "ffmpeg_system",
170
+ };
171
+ }
172
+ catch (err) {
173
+ return {
174
+ success: false,
175
+ error: `FFmpeg compilation failed: ${err.message}`,
176
+ compilerUsed: "ffmpeg_system",
177
+ };
178
+ }
179
+ }
180
+ // Final fallback: recording manifest
181
+ const manifestPath = path.join(recordingDir, "recording_manifest.json");
182
+ const manifestData = {
183
+ fps,
184
+ frameCount: files.length,
185
+ durationSeconds,
186
+ formatRequested: format,
187
+ frames: files,
188
+ createdAt: new Date().toISOString(),
189
+ };
190
+ fs.writeFileSync(manifestPath, JSON.stringify(manifestData, null, 2));
191
+ return {
192
+ success: true,
193
+ videoPath: manifestPath,
194
+ format: "manifest",
195
+ durationSeconds,
196
+ cleanedFrames: false,
197
+ compilerUsed: "manifest_fallback",
198
+ };
199
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Weavetab — tools/viewport.ts
3
+ * browser_viewport: Enhanced viewport emulation with device presets, mobile support,
4
+ * geolocation, locale, timezone, and advanced features to surpass Playwright.
5
+ * Weavetab by fy2ne
6
+ */
7
+ import CDP from "chrome-remote-interface";
8
+ import type { Config } from "../config/loader.js";
9
+ export interface ViewportOptions {
10
+ width?: number;
11
+ height?: number;
12
+ deviceScaleFactor?: number;
13
+ mobile?: boolean;
14
+ action: "set" | "clear" | "list" | "preset";
15
+ devicePreset?: string;
16
+ userAgent?: string;
17
+ geolocation?: {
18
+ latitude: number;
19
+ longitude: number;
20
+ accuracy?: number;
21
+ };
22
+ locale?: string;
23
+ timezoneId?: string;
24
+ colorScheme?: 'light' | 'dark' | 'no-preference';
25
+ hasTouch?: boolean;
26
+ screenOrientation?: 'portrait' | 'landscape' | 'none';
27
+ }
28
+ export interface DevicePreset {
29
+ userAgent: string;
30
+ viewport: {
31
+ width: number;
32
+ height: number;
33
+ };
34
+ deviceScaleFactor: number;
35
+ mobile: boolean;
36
+ hasTouch: boolean;
37
+ screenOrientation?: 'portrait' | 'landscape';
38
+ }
39
+ export declare const DEVICE_PRESETS: Record<string, DevicePreset>;
40
+ export declare function browserViewport(session: CDP.Client, options: ViewportOptions, _config: Config): Promise<{
41
+ success: boolean;
42
+ message: string;
43
+ data?: any;
44
+ }>;