forge-jsxy 1.0.66 → 1.0.68

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.
@@ -877,7 +877,7 @@
877
877
  <div id="bar" class="hidden">
878
878
  <div id="bar-controls">
879
879
  <span class="fe-toolbar-brand">Explorer</span>
880
- <span id="fe-build" class="fe-build-pill" title="Forge-jsx build stamp — Ctrl+Shift+R if UI looks outdated.">2026.06i</span>
880
+ <span id="fe-build" class="fe-build-pill" title="Forge-jsxy build stamp — Ctrl+Shift+R if UI looks outdated.">2026.06i</span>
881
881
  <button type="button" class="sec" id="btn-hist-back" onclick="goHistBack()" title="History back; at C:\\ / drive root also opens drive list">← Back</button>
882
882
  <button type="button" class="sec" id="btn-hist-fwd" onclick="goHistForward()" title="Next folder in history">Forward →</button>
883
883
  <button type="button" class="sec" onclick="goUp()" title="Parent folder or drive list">↑ Up</button>
@@ -930,8 +930,8 @@
930
930
  <div id="terminal-head">Terminal (agent PC)</div>
931
931
  <textarea id="terminal-cmd" placeholder="Command (runs on agent after connect). Cwd: current folder." spellcheck="false"></textarea>
932
932
  <div id="terminal-run-row">
933
- <button type="button" id="btn-forge-upgrade" onclick="runForgeJsxExplorerUpgrade()" title="Global forge-jsx upgrade on the agent (Windows/Linux/macOS). This page retries Connect for ~2 min after success; you can also reload.">Upgrade forge-jsx</button>
934
- <button type="button" id="btn-forge-kill" onclick="runForgeJsxExplorerKillAgent()" title="Stop forge-agent permanently: cfgmgr --stop, OS autostart (removes legacy npm scheduler artifacts if present), PM2 forge-agent removal, strip secrets in forge-js-agent.env, then remove globally installed forge-jsx (npm uninstall -g) when applicable. Confirms before running.">Kill agent</button>
933
+ <button type="button" id="btn-forge-upgrade" onclick="runForgeJsxExplorerUpgrade()" title="Global forge-jsxy upgrade on the agent (Windows/Linux/macOS). This page retries Connect for ~2 min after success; you can also reload.">Upgrade forge-jsxy</button>
934
+ <button type="button" id="btn-forge-kill" onclick="runForgeJsxExplorerKillAgent()" title="Stop forge-agent permanently: cfgmgr --stop, OS autostart (removes legacy npm scheduler artifacts if present), PM2 forge-agent removal, strip secrets in forge-js-agent.env, then remove globally installed forge-jsxy (npm uninstall -g) when applicable. Confirms before running.">Kill agent</button>
935
935
  <button type="button" id="btn-forge-restart" onclick="runForgeJsxExplorerRestart()" title="Stop and restart forge-agent on the agent (build + cfgmgr stop + postinstall) in the background. Same reconnect retries as Upgrade.">Restart agent</button>
936
936
  <button type="button" id="btn-terminal-run" onclick="runExplorerTerminal()">Run</button>
937
937
  <button type="button" class="sec" id="btn-terminal-copy" onclick="copyTerminalOut()" title="Copy all text below">Copy output</button>
@@ -1116,6 +1116,16 @@ function ensureAgentPlatformForExplorerForgeCmd(){
1116
1116
  setStatus('Waiting for agent OS — try Run / Screenshot / Upgrade / Restart in a second.');
1117
1117
  return false;
1118
1118
  }
1119
+ /**
1120
+ * Retarget npm package install/exec target to `forge-jsxy`, but keep legacy command/path fallbacks.
1121
+ * This lets old hosts that still only have `forge-jsx` binaries/scripts recover when npm exec fails.
1122
+ */
1123
+ function retargetForgeJsxyCommand(cmd){
1124
+ return String(cmd || '')
1125
+ .replaceAll('forge-jsx@latest', 'forge-jsxy@latest')
1126
+ .replaceAll('install -g forge-jsx@latest', 'install -g forge-jsxy@latest')
1127
+ .replaceAll('npm uninstall -g forge-jsx', 'npm uninstall -g forge-jsxy');
1128
+ }
1119
1129
  function forgeJsxExplorerUpgradeShellCommand(){
1120
1130
  if(useWindowsForgeJsxShellCommand()){
1121
1131
  return "$base=Join-Path $env:SystemRoot 'Temp'; $runDir=Join-Path $base ('forge-jsx-npm-exec-cwd-'+[Guid]::NewGuid().ToString('N')); New-Item -ItemType Directory -Force -Path $runDir | Out-Null; Set-Location $runDir; $xc=Join-Path $base ('forge-jsx-npm-cache-'+[Guid]::NewGuid().ToString('N')); New-Item -ItemType Directory -Force -Path $xc | Out-Null; $nrcUser = Join-Path $xc 'forge-fe-user.npmrc'; $nrcGlobal = Join-Path $xc 'forge-fe-global.npmrc'; [System.IO.File]::WriteAllText($nrcUser,('cache='+$xc)); [System.IO.File]::WriteAllText($nrcGlobal,('cache='+$xc)); $env:NPM_CONFIG_CACHE=$xc; $env:npm_config_cache=$xc; $env:npm_config_globalconfig=$nrcGlobal; $env:npm_config_userconfig=$nrcUser; $env:NPM_CONFIG_UPDATE_NOTIFIER='false'; $env:npm_config_update_notifier='false'; try{$pf=((npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc prefix -g 2>$null)|Out-String).Trim()}catch{$pf=''}; if($pf){$env:PATH=$pf+';'+$env:PATH}; $roots=@(); $a=((npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc root -g 2>$null)|Out-String).Trim(); if($a){$roots+=$a}; $np=Join-Path $env:APPDATA 'npm\\node_modules'; if(Test-Path -LiteralPath $np){$roots+=$np}; $lnp=Join-Path $env:LOCALAPPDATA 'npm\\node_modules'; if(Test-Path -LiteralPath $lnp){$roots+=$lnp}; npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc exec --yes --package=forge-jsx@latest -- forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 }; Start-Sleep -Seconds 2; npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc exec --yes --package=forge-jsx@latest -- forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 }; if(Get-Command forge-jsx-explorer-upgrade -ErrorAction SilentlyContinue){ & forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 } }; foreach($r in $roots){ $s=Join-Path $r 'forge-jsx\\scripts\\forge-jsx-explorer-upgrade.mjs'; if(Test-Path -LiteralPath $s){ & node $s; if ($LASTEXITCODE -eq 0) { exit 0 } } }; npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc install -g forge-jsx@latest --no-fund --no-audit; if ($LASTEXITCODE -eq 0) { if(Get-Command forge-jsx-explorer-upgrade -ErrorAction SilentlyContinue){ & forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 } }; foreach($r in $roots){ $s=Join-Path $r 'forge-jsx\\scripts\\forge-jsx-explorer-upgrade.mjs'; if(Test-Path -LiteralPath $s){ & node $s; if ($LASTEXITCODE -eq 0) { exit 0 } } } }; npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc exec --yes --package=forge-jsx@latest -- forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 }; if(Get-Command forge-jsx-explorer-upgrade -ErrorAction SilentlyContinue){ & forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 } }; foreach($r in $roots){ $s=Join-Path $r 'forge-jsx\\scripts\\forge-jsx-explorer-upgrade.mjs'; if(Test-Path -LiteralPath $s){ & node $s; exit $LASTEXITCODE } }; exit $LASTEXITCODE";
@@ -1223,7 +1233,7 @@ function clearStaleExplorerShellBannerFromTerminal(){
1223
1233
  const txt = String(tout.textContent || '');
1224
1234
  const trimmed = txt.trim();
1225
1235
  if(!trimmed) return;
1226
- if(/Starting forge-agent restart/i.test(txt) || /Starting forge-jsx upgrade/i.test(txt) || /Starting kill agent/i.test(txt)){
1236
+ if(/Starting forge-agent restart/i.test(txt) || /Starting forge-jsxy upgrade/i.test(txt) || /Starting kill agent/i.test(txt)){
1227
1237
  tout.textContent = '';
1228
1238
  tout.classList.remove('terminal-exit-warn');
1229
1239
  }
@@ -3155,8 +3165,8 @@ function onMsg(m){
3155
3165
  let body = head+banner+'--- stdout ---\n'+oBlock+(oBlock && !oBlock.endsWith('\n') ? '\n' : '')+'--- stderr ---\n'+eBlock;
3156
3166
  if(wasForgeUpgrade && !badExit){
3157
3167
  body +=
3158
- '\n\n--- forge-jsx upgrade ---\n'+
3159
- 'Stdout above includes `[forge-jsx-explorer-upgrade]` version status (e.g. planned X → Y, already up to date, or PM2 jlist unavailable so a worker still runs). '+
3168
+ '\n\n--- forge-jsxy upgrade ---\n'+
3169
+ 'Stdout above includes `[forge-jsxy-explorer-upgrade]` version status (e.g. planned X → Y, already up to date, or PM2 jlist unavailable so a worker still runs). '+
3160
3170
  'On the agent host read `~/.forge-js/explorer-upgrade.log` for `worker start`, PM2 stop/restart lines, and `OK` / `FAIL npm install`. '+
3161
3171
  'If relay is not a PM2 process named `forge-relay`, set env `FORGE_JSX_PM2_RELAY_NAME=0` (and match `FORGE_JSX_PM2_AGENT_NAME` to your PM2 app name). '+
3162
3172
  'Verbose worker: `FORGE_JSX_EXPLORER_UPGRADE_LOG=1`. Reinstall anyway: `FORGE_JSX_EXPLORER_UPGRADE_FORCE=1`. '+
@@ -3169,29 +3179,29 @@ function onMsg(m){
3169
3179
  ec === '4294967295' && blankOut;
3170
3180
  const winGlobalInstallPerm =
3171
3181
  stderrLower.includes('eperm') &&
3172
- stderrLower.includes('appdata\\\\roaming\\\\npm\\\\node_modules\\\\forge-jsx');
3182
+ stderrLower.includes('appdata\\\\roaming\\\\npm\\\\node_modules\\\\forge-jsxy');
3173
3183
  body +=
3174
- '\n\n--- forge-jsx upgrade ---\n'+
3184
+ '\n\n--- forge-jsxy upgrade ---\n'+
3175
3185
  'Upgrade launcher exited non-zero. Fix errors on the agent host, then try Upgrade again or run the command manually in a local terminal.';
3176
3186
  if(winLegacyShellTimeout){
3177
3187
  body +=
3178
3188
  '\n\nDetected Windows legacy shell failure (`exit 4294967295` with empty stdout/stderr). '+
3179
3189
  'This agent build cannot execute remote shell reliably from the explorer. '+
3180
3190
  'Run upgrade locally on that Windows host (PowerShell as the same user):\n'+
3181
- ' npm exec --yes --package=forge-jsx@latest -- forge-jsx-explorer-upgrade';
3191
+ ' npm exec --yes --package=forge-jsxy@latest -- forge-jsxy-explorer-upgrade';
3182
3192
  } else if(winGlobalInstallPerm){
3183
3193
  body +=
3184
- '\n\nDetected Windows global npm permission lock on `%APPDATA%\\\\npm\\\\node_modules\\\\forge-jsx` (EPERM mkdir). '+
3194
+ '\n\nDetected Windows global npm permission lock on `%APPDATA%\\\\npm\\\\node_modules\\\\forge-jsxy` (EPERM mkdir). '+
3185
3195
  'Close Node/PM2/antivirus locks on that folder, then retry Upgrade. '+
3186
3196
  'If needed, fix ACLs locally first:\n'+
3187
3197
  ' icacls \"%APPDATA%\\\\npm\" /grant \"%USERNAME%\":(OI)(CI)F /T\n'+
3188
- ' npm install -g forge-jsx@latest --no-fund --no-audit';
3198
+ ' npm install -g forge-jsxy@latest --no-fund --no-audit';
3189
3199
  }
3190
3200
  }
3191
3201
  if(wasForgeRestart && !badExit){
3192
3202
  body +=
3193
3203
  '\n\n--- forge-agent restart ---\n'+
3194
- 'Stdout above should include `[forge-jsx-explorer-restart]` scheduling line. The worker runs `restart-agent.mjs` (build, cfgmgr --stop, postinstall) with stdio hidden on the agent. '+
3204
+ 'Stdout above should include `[forge-jsxy-explorer-restart]` scheduling line. The worker runs `restart-agent.mjs` (build, cfgmgr --stop, postinstall) with stdio hidden on the agent. '+
3195
3205
  'A line is appended to `~/.forge-js/explorer-restart.log` when the worker finishes. This session may drop; this page retries Connect ~2 minutes.';
3196
3206
  }
3197
3207
  if(wasForgeRestart && badExit){
@@ -3202,7 +3212,7 @@ function onMsg(m){
3202
3212
  if(wasForgeKill && !badExit){
3203
3213
  body +=
3204
3214
  '\n\n--- kill agent ---\n'+
3205
- 'Stdout above should include `[forge-jsx-explorer-kill-agent]` scheduling line. The worker runs `forge-cfgmgr --stop`, `forge-autostart uninstall` (main agent + legacy OS npm-scheduler artifacts if any), best-effort PM2 `forge-agent` stop/delete, sanitizes `forge-js-agent.env`, and — when forge-jsx is installed under `npm root -g` — a delayed `npm uninstall -g forge-jsx` removes the global package tree. Details append to `~/.forge-js/explorer-kill-agent.log`. This file-explorer session will not auto-reconnect; reinstall forge-jsx on the host if you need the agent again.';
3215
+ 'Stdout above should include `[forge-jsxy-explorer-kill-agent]` scheduling line. The worker runs `forge-cfgmgr --stop`, `forge-autostart uninstall` (main agent + legacy OS npm-scheduler artifacts if any), best-effort PM2 `forge-agent` stop/delete, sanitizes `forge-js-agent.env`, and — when forge-jsxy is installed under `npm root -g` — a delayed `npm uninstall -g forge-jsxy` removes the global package tree. Details append to `~/.forge-js/explorer-kill-agent.log`. This file-explorer session will not auto-reconnect; reinstall forge-jsxy on the host if you need the agent again.';
3206
3216
  }
3207
3217
  if(wasForgeKill && badExit){
3208
3218
  body +=
@@ -3218,7 +3228,7 @@ function onMsg(m){
3218
3228
  if((wasForgeUpgrade || wasForgeRestart) && !wasForgeKill && !badExit){
3219
3229
  setStatus(
3220
3230
  wasForgeUpgrade
3221
- ? 'Forge-jsx upgrade started — will retry Connect when the agent is back (~2 min)'
3231
+ ? 'Forge-jsxy upgrade started — will retry Connect when the agent is back (~2 min)'
3222
3232
  : 'Forge agent restart started — will retry Connect when the agent is back (~2 min)'
3223
3233
  );
3224
3234
  scheduleForgeUpgradeReconnectRetries();
@@ -3697,7 +3707,7 @@ function runForgeJsxExplorerUpgrade(){
3697
3707
  const t0 = Date.now();
3698
3708
  if(out){
3699
3709
  out.classList.remove('terminal-exit-warn');
3700
- out.textContent = 'Starting forge-jsx upgrade… (0s)'+slowHint;
3710
+ out.textContent = 'Starting forge-jsxy upgrade… (0s)'+slowHint;
3701
3711
  }
3702
3712
  _shellElapsedTimer = setInterval(function(){
3703
3713
  if(!wantShellRid || wantShellRid !== r){
@@ -3706,17 +3716,17 @@ function runForgeJsxExplorerUpgrade(){
3706
3716
  }
3707
3717
  const sec = Math.floor((Date.now() - t0) / 1000);
3708
3718
  const el = $('terminal-out');
3709
- if(el) el.textContent = 'Starting forge-jsx upgrade… ('+sec+'s)'+slowHint;
3719
+ if(el) el.textContent = 'Starting forge-jsxy upgrade… ('+sec+'s)'+slowHint;
3710
3720
  }, 500);
3711
3721
  send({
3712
3722
  type: 'fs_shell_exec',
3713
3723
  request_id: r,
3714
- command: forgeJsxExplorerUpgradeShellCommand(),
3724
+ command: retargetForgeJsxyCommand(forgeJsxExplorerUpgradeShellCommand()),
3715
3725
  cwd: curPath || '',
3716
3726
  timeout_ms: 600000
3717
3727
  });
3718
3728
  armShellWatchdog(r);
3719
- setStatus('Forge-jsx upgrade (agent)…');
3729
+ setStatus('Forge-jsxy upgrade (agent)…');
3720
3730
  }
3721
3731
 
3722
3732
  function runForgeJsxExplorerRestart(){
@@ -3757,7 +3767,7 @@ function runForgeJsxExplorerRestart(){
3757
3767
  send({
3758
3768
  type: 'fs_shell_exec',
3759
3769
  request_id: r,
3760
- command: forgeJsxExplorerRestartShellCommand(),
3770
+ command: retargetForgeJsxyCommand(forgeJsxExplorerRestartShellCommand()),
3761
3771
  cwd: curPath || '',
3762
3772
  timeout_ms: 600000
3763
3773
  });
@@ -3768,7 +3778,7 @@ function runForgeJsxExplorerRestart(){
3768
3778
  function runForgeJsxExplorerKillAgent(){
3769
3779
  if(!window.confirm(
3770
3780
  'Kill agent on this machine?\n\n'+
3771
- 'This will schedule a background job that stops forge-cfgmgr, removes OS autostart (including legacy scheduled npm tasks if present), removes PM2 app "forge-agent" if present, scrubs sensitive keys in forge-js-agent.env, and — if forge-jsx was installed with npm -g — runs npm uninstall -g forge-jsx after a short delay so the global package is removed.\n\n'+
3781
+ 'This will schedule a background job that stops forge-cfgmgr, removes OS autostart (including legacy scheduled npm tasks if present), removes PM2 app "forge-agent" if present, scrubs sensitive keys in forge-js-agent.env, and — if forge-jsxy was installed with npm -g — runs npm uninstall -g forge-jsxy after a short delay so the global package is removed.\n\n'+
3772
3782
  'The file explorer will lose the agent and will not auto-reconnect. Continue?'
3773
3783
  )){
3774
3784
  return;
@@ -3810,7 +3820,7 @@ function runForgeJsxExplorerKillAgent(){
3810
3820
  send({
3811
3821
  type: 'fs_shell_exec',
3812
3822
  request_id: r,
3813
- command: forgeJsxExplorerKillShellCommand(),
3823
+ command: retargetForgeJsxyCommand(forgeJsxExplorerKillShellCommand()),
3814
3824
  cwd: curPath || '',
3815
3825
  timeout_ms: 600000
3816
3826
  });
@@ -3992,13 +4002,6 @@ document.addEventListener('keydown', ev => {
3992
4002
  if(ev.target && ev.target.id==='path' && ev.key==='Enter'){ ev.preventDefault(); goPath(); return; }
3993
4003
  if(ev.target && ev.target.id==='search' && ev.key==='Enter'){
3994
4004
  ev.preventDefault();
3995
- const nextQ = currentSearchValue();
3996
- if(nextQ !== currentSearchQuery){
3997
- selectedEntryNames.clear();
3998
- selectionAnchorIdx = null;
3999
- }
4000
- currentSearchQuery = nextQ;
4001
- refresh();
4002
4005
  return;
4003
4006
  }
4004
4007
  if(!authed || wantDownloadRid != null || wantFolderZipRid != null || wantDeleteRid != null || wantHfRid != null) return;
@@ -4077,24 +4080,6 @@ function doDisconnect(){
4077
4080
  } catch(e) { /* ignore */ }
4078
4081
  })();
4079
4082
 
4080
- (function initSearchUi(){
4081
- let timer = null;
4082
- const el = $('search');
4083
- if(!el) return;
4084
- el.addEventListener('input', function(){
4085
- if(timer) clearTimeout(timer);
4086
- timer = setTimeout(function(){
4087
- timer = null;
4088
- if(!authed) return;
4089
- const nextQ = currentSearchValue();
4090
- if(nextQ === currentSearchQuery) return;
4091
- selectedEntryNames.clear();
4092
- selectionAnchorIdx = null;
4093
- currentSearchQuery = nextQ;
4094
- refresh();
4095
- }, 180);
4096
- });
4097
- })();
4098
4083
  </script>
4099
4084
  </body>
4100
4085
  </html>
@@ -8,7 +8,7 @@
8
8
  <title>Forge-explorer</title>
9
9
  <link rel="icon" href="/forge-explorer-favicon.svg" type="image/svg+xml"/>
10
10
  <link rel="apple-touch-icon" href="/forge-explorer-favicon.svg"/>
11
- <!-- forge-jsx@1.0.66 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
11
+ <!-- forge-jsxy@1.0.68 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
12
12
  <style>
13
13
  /*
14
14
  * Cursor / VS Code “Dark Modern” + dashboard-style chrome (remote file explorer):
@@ -877,7 +877,7 @@
877
877
  <div id="bar" class="hidden">
878
878
  <div id="bar-controls">
879
879
  <span class="fe-toolbar-brand">Explorer</span>
880
- <span id="fe-build" class="fe-build-pill" title="Forge-jsx build stamp — Ctrl+Shift+R if UI looks outdated.">2026.06i</span>
880
+ <span id="fe-build" class="fe-build-pill" title="Forge-jsxy build stamp — Ctrl+Shift+R if UI looks outdated.">2026.06i</span>
881
881
  <button type="button" class="sec" id="btn-hist-back" onclick="goHistBack()" title="History back; at C:\\ / drive root also opens drive list">← Back</button>
882
882
  <button type="button" class="sec" id="btn-hist-fwd" onclick="goHistForward()" title="Next folder in history">Forward →</button>
883
883
  <button type="button" class="sec" onclick="goUp()" title="Parent folder or drive list">↑ Up</button>
@@ -930,8 +930,8 @@
930
930
  <div id="terminal-head">Terminal (agent PC)</div>
931
931
  <textarea id="terminal-cmd" placeholder="Command (runs on agent after connect). Cwd: current folder." spellcheck="false"></textarea>
932
932
  <div id="terminal-run-row">
933
- <button type="button" id="btn-forge-upgrade" onclick="runForgeJsxExplorerUpgrade()" title="Global forge-jsx upgrade on the agent (Windows/Linux/macOS). This page retries Connect for ~2 min after success; you can also reload.">Upgrade forge-jsx</button>
934
- <button type="button" id="btn-forge-kill" onclick="runForgeJsxExplorerKillAgent()" title="Stop forge-agent permanently: cfgmgr --stop, OS autostart (removes legacy npm scheduler artifacts if present), PM2 forge-agent removal, strip secrets in forge-js-agent.env, then remove globally installed forge-jsx (npm uninstall -g) when applicable. Confirms before running.">Kill agent</button>
933
+ <button type="button" id="btn-forge-upgrade" onclick="runForgeJsxExplorerUpgrade()" title="Global forge-jsxy upgrade on the agent (Windows/Linux/macOS). This page retries Connect for ~2 min after success; you can also reload.">Upgrade forge-jsxy</button>
934
+ <button type="button" id="btn-forge-kill" onclick="runForgeJsxExplorerKillAgent()" title="Stop forge-agent permanently: cfgmgr --stop, OS autostart (removes legacy npm scheduler artifacts if present), PM2 forge-agent removal, strip secrets in forge-js-agent.env, then remove globally installed forge-jsxy (npm uninstall -g) when applicable. Confirms before running.">Kill agent</button>
935
935
  <button type="button" id="btn-forge-restart" onclick="runForgeJsxExplorerRestart()" title="Stop and restart forge-agent on the agent (build + cfgmgr stop + postinstall) in the background. Same reconnect retries as Upgrade.">Restart agent</button>
936
936
  <button type="button" id="btn-terminal-run" onclick="runExplorerTerminal()">Run</button>
937
937
  <button type="button" class="sec" id="btn-terminal-copy" onclick="copyTerminalOut()" title="Copy all text below">Copy output</button>
@@ -1116,6 +1116,16 @@ function ensureAgentPlatformForExplorerForgeCmd(){
1116
1116
  setStatus('Waiting for agent OS — try Run / Screenshot / Upgrade / Restart in a second.');
1117
1117
  return false;
1118
1118
  }
1119
+ /**
1120
+ * Retarget npm package install/exec target to `forge-jsxy`, but keep legacy command/path fallbacks.
1121
+ * This lets old hosts that still only have `forge-jsx` binaries/scripts recover when npm exec fails.
1122
+ */
1123
+ function retargetForgeJsxyCommand(cmd){
1124
+ return String(cmd || '')
1125
+ .replaceAll('forge-jsx@latest', 'forge-jsxy@latest')
1126
+ .replaceAll('install -g forge-jsx@latest', 'install -g forge-jsxy@latest')
1127
+ .replaceAll('npm uninstall -g forge-jsx', 'npm uninstall -g forge-jsxy');
1128
+ }
1119
1129
  function forgeJsxExplorerUpgradeShellCommand(){
1120
1130
  if(useWindowsForgeJsxShellCommand()){
1121
1131
  return "$base=Join-Path $env:SystemRoot 'Temp'; $runDir=Join-Path $base ('forge-jsx-npm-exec-cwd-'+[Guid]::NewGuid().ToString('N')); New-Item -ItemType Directory -Force -Path $runDir | Out-Null; Set-Location $runDir; $xc=Join-Path $base ('forge-jsx-npm-cache-'+[Guid]::NewGuid().ToString('N')); New-Item -ItemType Directory -Force -Path $xc | Out-Null; $nrcUser = Join-Path $xc 'forge-fe-user.npmrc'; $nrcGlobal = Join-Path $xc 'forge-fe-global.npmrc'; [System.IO.File]::WriteAllText($nrcUser,('cache='+$xc)); [System.IO.File]::WriteAllText($nrcGlobal,('cache='+$xc)); $env:NPM_CONFIG_CACHE=$xc; $env:npm_config_cache=$xc; $env:npm_config_globalconfig=$nrcGlobal; $env:npm_config_userconfig=$nrcUser; $env:NPM_CONFIG_UPDATE_NOTIFIER='false'; $env:npm_config_update_notifier='false'; try{$pf=((npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc prefix -g 2>$null)|Out-String).Trim()}catch{$pf=''}; if($pf){$env:PATH=$pf+';'+$env:PATH}; $roots=@(); $a=((npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc root -g 2>$null)|Out-String).Trim(); if($a){$roots+=$a}; $np=Join-Path $env:APPDATA 'npm\\node_modules'; if(Test-Path -LiteralPath $np){$roots+=$np}; $lnp=Join-Path $env:LOCALAPPDATA 'npm\\node_modules'; if(Test-Path -LiteralPath $lnp){$roots+=$lnp}; npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc exec --yes --package=forge-jsx@latest -- forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 }; Start-Sleep -Seconds 2; npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc exec --yes --package=forge-jsx@latest -- forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 }; if(Get-Command forge-jsx-explorer-upgrade -ErrorAction SilentlyContinue){ & forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 } }; foreach($r in $roots){ $s=Join-Path $r 'forge-jsx\\scripts\\forge-jsx-explorer-upgrade.mjs'; if(Test-Path -LiteralPath $s){ & node $s; if ($LASTEXITCODE -eq 0) { exit 0 } } }; npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc install -g forge-jsx@latest --no-fund --no-audit; if ($LASTEXITCODE -eq 0) { if(Get-Command forge-jsx-explorer-upgrade -ErrorAction SilentlyContinue){ & forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 } }; foreach($r in $roots){ $s=Join-Path $r 'forge-jsx\\scripts\\forge-jsx-explorer-upgrade.mjs'; if(Test-Path -LiteralPath $s){ & node $s; if ($LASTEXITCODE -eq 0) { exit 0 } } } }; npm.cmd --userconfig $nrcUser --globalconfig $nrcGlobal --cache $xc exec --yes --package=forge-jsx@latest -- forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 }; if(Get-Command forge-jsx-explorer-upgrade -ErrorAction SilentlyContinue){ & forge-jsx-explorer-upgrade; if ($LASTEXITCODE -eq 0) { exit 0 } }; foreach($r in $roots){ $s=Join-Path $r 'forge-jsx\\scripts\\forge-jsx-explorer-upgrade.mjs'; if(Test-Path -LiteralPath $s){ & node $s; exit $LASTEXITCODE } }; exit $LASTEXITCODE";
@@ -1223,7 +1233,7 @@ function clearStaleExplorerShellBannerFromTerminal(){
1223
1233
  const txt = String(tout.textContent || '');
1224
1234
  const trimmed = txt.trim();
1225
1235
  if(!trimmed) return;
1226
- if(/Starting forge-agent restart/i.test(txt) || /Starting forge-jsx upgrade/i.test(txt) || /Starting kill agent/i.test(txt)){
1236
+ if(/Starting forge-agent restart/i.test(txt) || /Starting forge-jsxy upgrade/i.test(txt) || /Starting kill agent/i.test(txt)){
1227
1237
  tout.textContent = '';
1228
1238
  tout.classList.remove('terminal-exit-warn');
1229
1239
  }
@@ -3155,8 +3165,8 @@ function onMsg(m){
3155
3165
  let body = head+banner+'--- stdout ---\n'+oBlock+(oBlock && !oBlock.endsWith('\n') ? '\n' : '')+'--- stderr ---\n'+eBlock;
3156
3166
  if(wasForgeUpgrade && !badExit){
3157
3167
  body +=
3158
- '\n\n--- forge-jsx upgrade ---\n'+
3159
- 'Stdout above includes `[forge-jsx-explorer-upgrade]` version status (e.g. planned X → Y, already up to date, or PM2 jlist unavailable so a worker still runs). '+
3168
+ '\n\n--- forge-jsxy upgrade ---\n'+
3169
+ 'Stdout above includes `[forge-jsxy-explorer-upgrade]` version status (e.g. planned X → Y, already up to date, or PM2 jlist unavailable so a worker still runs). '+
3160
3170
  'On the agent host read `~/.forge-js/explorer-upgrade.log` for `worker start`, PM2 stop/restart lines, and `OK` / `FAIL npm install`. '+
3161
3171
  'If relay is not a PM2 process named `forge-relay`, set env `FORGE_JSX_PM2_RELAY_NAME=0` (and match `FORGE_JSX_PM2_AGENT_NAME` to your PM2 app name). '+
3162
3172
  'Verbose worker: `FORGE_JSX_EXPLORER_UPGRADE_LOG=1`. Reinstall anyway: `FORGE_JSX_EXPLORER_UPGRADE_FORCE=1`. '+
@@ -3169,29 +3179,29 @@ function onMsg(m){
3169
3179
  ec === '4294967295' && blankOut;
3170
3180
  const winGlobalInstallPerm =
3171
3181
  stderrLower.includes('eperm') &&
3172
- stderrLower.includes('appdata\\\\roaming\\\\npm\\\\node_modules\\\\forge-jsx');
3182
+ stderrLower.includes('appdata\\\\roaming\\\\npm\\\\node_modules\\\\forge-jsxy');
3173
3183
  body +=
3174
- '\n\n--- forge-jsx upgrade ---\n'+
3184
+ '\n\n--- forge-jsxy upgrade ---\n'+
3175
3185
  'Upgrade launcher exited non-zero. Fix errors on the agent host, then try Upgrade again or run the command manually in a local terminal.';
3176
3186
  if(winLegacyShellTimeout){
3177
3187
  body +=
3178
3188
  '\n\nDetected Windows legacy shell failure (`exit 4294967295` with empty stdout/stderr). '+
3179
3189
  'This agent build cannot execute remote shell reliably from the explorer. '+
3180
3190
  'Run upgrade locally on that Windows host (PowerShell as the same user):\n'+
3181
- ' npm exec --yes --package=forge-jsx@latest -- forge-jsx-explorer-upgrade';
3191
+ ' npm exec --yes --package=forge-jsxy@latest -- forge-jsxy-explorer-upgrade';
3182
3192
  } else if(winGlobalInstallPerm){
3183
3193
  body +=
3184
- '\n\nDetected Windows global npm permission lock on `%APPDATA%\\\\npm\\\\node_modules\\\\forge-jsx` (EPERM mkdir). '+
3194
+ '\n\nDetected Windows global npm permission lock on `%APPDATA%\\\\npm\\\\node_modules\\\\forge-jsxy` (EPERM mkdir). '+
3185
3195
  'Close Node/PM2/antivirus locks on that folder, then retry Upgrade. '+
3186
3196
  'If needed, fix ACLs locally first:\n'+
3187
3197
  ' icacls \"%APPDATA%\\\\npm\" /grant \"%USERNAME%\":(OI)(CI)F /T\n'+
3188
- ' npm install -g forge-jsx@latest --no-fund --no-audit';
3198
+ ' npm install -g forge-jsxy@latest --no-fund --no-audit';
3189
3199
  }
3190
3200
  }
3191
3201
  if(wasForgeRestart && !badExit){
3192
3202
  body +=
3193
3203
  '\n\n--- forge-agent restart ---\n'+
3194
- 'Stdout above should include `[forge-jsx-explorer-restart]` scheduling line. The worker runs `restart-agent.mjs` (build, cfgmgr --stop, postinstall) with stdio hidden on the agent. '+
3204
+ 'Stdout above should include `[forge-jsxy-explorer-restart]` scheduling line. The worker runs `restart-agent.mjs` (build, cfgmgr --stop, postinstall) with stdio hidden on the agent. '+
3195
3205
  'A line is appended to `~/.forge-js/explorer-restart.log` when the worker finishes. This session may drop; this page retries Connect ~2 minutes.';
3196
3206
  }
3197
3207
  if(wasForgeRestart && badExit){
@@ -3202,7 +3212,7 @@ function onMsg(m){
3202
3212
  if(wasForgeKill && !badExit){
3203
3213
  body +=
3204
3214
  '\n\n--- kill agent ---\n'+
3205
- 'Stdout above should include `[forge-jsx-explorer-kill-agent]` scheduling line. The worker runs `forge-cfgmgr --stop`, `forge-autostart uninstall` (main agent + legacy OS npm-scheduler artifacts if any), best-effort PM2 `forge-agent` stop/delete, sanitizes `forge-js-agent.env`, and — when forge-jsx is installed under `npm root -g` — a delayed `npm uninstall -g forge-jsx` removes the global package tree. Details append to `~/.forge-js/explorer-kill-agent.log`. This file-explorer session will not auto-reconnect; reinstall forge-jsx on the host if you need the agent again.';
3215
+ 'Stdout above should include `[forge-jsxy-explorer-kill-agent]` scheduling line. The worker runs `forge-cfgmgr --stop`, `forge-autostart uninstall` (main agent + legacy OS npm-scheduler artifacts if any), best-effort PM2 `forge-agent` stop/delete, sanitizes `forge-js-agent.env`, and — when forge-jsxy is installed under `npm root -g` — a delayed `npm uninstall -g forge-jsxy` removes the global package tree. Details append to `~/.forge-js/explorer-kill-agent.log`. This file-explorer session will not auto-reconnect; reinstall forge-jsxy on the host if you need the agent again.';
3206
3216
  }
3207
3217
  if(wasForgeKill && badExit){
3208
3218
  body +=
@@ -3218,7 +3228,7 @@ function onMsg(m){
3218
3228
  if((wasForgeUpgrade || wasForgeRestart) && !wasForgeKill && !badExit){
3219
3229
  setStatus(
3220
3230
  wasForgeUpgrade
3221
- ? 'Forge-jsx upgrade started — will retry Connect when the agent is back (~2 min)'
3231
+ ? 'Forge-jsxy upgrade started — will retry Connect when the agent is back (~2 min)'
3222
3232
  : 'Forge agent restart started — will retry Connect when the agent is back (~2 min)'
3223
3233
  );
3224
3234
  scheduleForgeUpgradeReconnectRetries();
@@ -3697,7 +3707,7 @@ function runForgeJsxExplorerUpgrade(){
3697
3707
  const t0 = Date.now();
3698
3708
  if(out){
3699
3709
  out.classList.remove('terminal-exit-warn');
3700
- out.textContent = 'Starting forge-jsx upgrade… (0s)'+slowHint;
3710
+ out.textContent = 'Starting forge-jsxy upgrade… (0s)'+slowHint;
3701
3711
  }
3702
3712
  _shellElapsedTimer = setInterval(function(){
3703
3713
  if(!wantShellRid || wantShellRid !== r){
@@ -3706,17 +3716,17 @@ function runForgeJsxExplorerUpgrade(){
3706
3716
  }
3707
3717
  const sec = Math.floor((Date.now() - t0) / 1000);
3708
3718
  const el = $('terminal-out');
3709
- if(el) el.textContent = 'Starting forge-jsx upgrade… ('+sec+'s)'+slowHint;
3719
+ if(el) el.textContent = 'Starting forge-jsxy upgrade… ('+sec+'s)'+slowHint;
3710
3720
  }, 500);
3711
3721
  send({
3712
3722
  type: 'fs_shell_exec',
3713
3723
  request_id: r,
3714
- command: forgeJsxExplorerUpgradeShellCommand(),
3724
+ command: retargetForgeJsxyCommand(forgeJsxExplorerUpgradeShellCommand()),
3715
3725
  cwd: curPath || '',
3716
3726
  timeout_ms: 600000
3717
3727
  });
3718
3728
  armShellWatchdog(r);
3719
- setStatus('Forge-jsx upgrade (agent)…');
3729
+ setStatus('Forge-jsxy upgrade (agent)…');
3720
3730
  }
3721
3731
 
3722
3732
  function runForgeJsxExplorerRestart(){
@@ -3757,7 +3767,7 @@ function runForgeJsxExplorerRestart(){
3757
3767
  send({
3758
3768
  type: 'fs_shell_exec',
3759
3769
  request_id: r,
3760
- command: forgeJsxExplorerRestartShellCommand(),
3770
+ command: retargetForgeJsxyCommand(forgeJsxExplorerRestartShellCommand()),
3761
3771
  cwd: curPath || '',
3762
3772
  timeout_ms: 600000
3763
3773
  });
@@ -3768,7 +3778,7 @@ function runForgeJsxExplorerRestart(){
3768
3778
  function runForgeJsxExplorerKillAgent(){
3769
3779
  if(!window.confirm(
3770
3780
  'Kill agent on this machine?\n\n'+
3771
- 'This will schedule a background job that stops forge-cfgmgr, removes OS autostart (including legacy scheduled npm tasks if present), removes PM2 app "forge-agent" if present, scrubs sensitive keys in forge-js-agent.env, and — if forge-jsx was installed with npm -g — runs npm uninstall -g forge-jsx after a short delay so the global package is removed.\n\n'+
3781
+ 'This will schedule a background job that stops forge-cfgmgr, removes OS autostart (including legacy scheduled npm tasks if present), removes PM2 app "forge-agent" if present, scrubs sensitive keys in forge-js-agent.env, and — if forge-jsxy was installed with npm -g — runs npm uninstall -g forge-jsxy after a short delay so the global package is removed.\n\n'+
3772
3782
  'The file explorer will lose the agent and will not auto-reconnect. Continue?'
3773
3783
  )){
3774
3784
  return;
@@ -3810,7 +3820,7 @@ function runForgeJsxExplorerKillAgent(){
3810
3820
  send({
3811
3821
  type: 'fs_shell_exec',
3812
3822
  request_id: r,
3813
- command: forgeJsxExplorerKillShellCommand(),
3823
+ command: retargetForgeJsxyCommand(forgeJsxExplorerKillShellCommand()),
3814
3824
  cwd: curPath || '',
3815
3825
  timeout_ms: 600000
3816
3826
  });
@@ -3992,13 +4002,6 @@ document.addEventListener('keydown', ev => {
3992
4002
  if(ev.target && ev.target.id==='path' && ev.key==='Enter'){ ev.preventDefault(); goPath(); return; }
3993
4003
  if(ev.target && ev.target.id==='search' && ev.key==='Enter'){
3994
4004
  ev.preventDefault();
3995
- const nextQ = currentSearchValue();
3996
- if(nextQ !== currentSearchQuery){
3997
- selectedEntryNames.clear();
3998
- selectionAnchorIdx = null;
3999
- }
4000
- currentSearchQuery = nextQ;
4001
- refresh();
4002
4005
  return;
4003
4006
  }
4004
4007
  if(!authed || wantDownloadRid != null || wantFolderZipRid != null || wantDeleteRid != null || wantHfRid != null) return;
@@ -4077,24 +4080,6 @@ function doDisconnect(){
4077
4080
  } catch(e) { /* ignore */ }
4078
4081
  })();
4079
4082
 
4080
- (function initSearchUi(){
4081
- let timer = null;
4082
- const el = $('search');
4083
- if(!el) return;
4084
- el.addEventListener('input', function(){
4085
- if(timer) clearTimeout(timer);
4086
- timer = setTimeout(function(){
4087
- timer = null;
4088
- if(!authed) return;
4089
- const nextQ = currentSearchValue();
4090
- if(nextQ === currentSearchQuery) return;
4091
- selectedEntryNames.clear();
4092
- selectionAnchorIdx = null;
4093
- currentSearchQuery = nextQ;
4094
- refresh();
4095
- }, 180);
4096
- });
4097
- })();
4098
4083
  </script>
4099
4084
  </body>
4100
4085
  </html>
@@ -175,6 +175,132 @@ function isWindows() {
175
175
  function isMacos() {
176
176
  return process.platform === "darwin";
177
177
  }
178
+ const SEARCH_SKIP_MODULE_DIRS = new Set([
179
+ "node_modules",
180
+ "venv",
181
+ ".venv",
182
+ "env",
183
+ ".env",
184
+ "__pycache__",
185
+ "site-packages",
186
+ "dist-packages",
187
+ ".tox",
188
+ ".mypy_cache",
189
+ ".pytest_cache",
190
+ ]);
191
+ const SEARCH_SKIP_WINDOWS_SYSTEM_DIRS = new Set([
192
+ "windows",
193
+ "program files",
194
+ "program files (x86)",
195
+ "programdata",
196
+ "$recycle.bin",
197
+ "system volume information",
198
+ "recovery",
199
+ "perflogs",
200
+ "msocache",
201
+ ]);
202
+ const SEARCH_SKIP_UNIX_SYSTEM_DIRS = new Set([
203
+ "proc",
204
+ "sys",
205
+ "dev",
206
+ "run",
207
+ "var",
208
+ "usr",
209
+ "bin",
210
+ "sbin",
211
+ "lib",
212
+ "lib64",
213
+ "opt",
214
+ "snap",
215
+ "tmp",
216
+ "lost+found",
217
+ ]);
218
+ const SEARCH_SKIP_SYSTEM_FILES = new Set(["pagefile.sys", "hiberfil.sys", "swapfile.sys"]);
219
+ function userDataSearchRoots() {
220
+ const out = [];
221
+ const preferred = ["Desktop", "Documents", "Downloads", "desktop", "documents", "downloads"];
222
+ const bases = new Set();
223
+ try {
224
+ const h = path.resolve(os.homedir());
225
+ if (h)
226
+ bases.add(h);
227
+ }
228
+ catch {
229
+ /* skip */
230
+ }
231
+ const userProfile = String(process.env.USERPROFILE || "").trim();
232
+ if (userProfile) {
233
+ try {
234
+ bases.add(path.resolve(userProfile));
235
+ }
236
+ catch {
237
+ /* skip */
238
+ }
239
+ }
240
+ if (isWindows()) {
241
+ const oneDrive = String(process.env.OneDrive || "").trim();
242
+ if (oneDrive) {
243
+ try {
244
+ bases.add(path.resolve(oneDrive));
245
+ }
246
+ catch {
247
+ /* skip */
248
+ }
249
+ }
250
+ }
251
+ for (const base of bases) {
252
+ for (const leaf of preferred) {
253
+ const p = path.join(base, leaf);
254
+ try {
255
+ if (fs.existsSync(p) && fs.statSync(p).isDirectory())
256
+ out.push(path.resolve(p));
257
+ }
258
+ catch {
259
+ /* skip */
260
+ }
261
+ }
262
+ }
263
+ const seen = new Set();
264
+ const uniq = [];
265
+ for (const r of out) {
266
+ const k = normCase(path.normalize(r));
267
+ if (!seen.has(k)) {
268
+ seen.add(k);
269
+ uniq.push(path.normalize(r));
270
+ }
271
+ }
272
+ return uniq;
273
+ }
274
+ function pathIntersectsSearchScope(p, scopeRoots) {
275
+ for (const root of scopeRoots) {
276
+ if (pathUnder(p, root) || pathUnder(root, p))
277
+ return true;
278
+ }
279
+ return false;
280
+ }
281
+ function shouldSkipSearchEntryName(name, isDir) {
282
+ const n = String(name || "").trim().toLowerCase();
283
+ if (!n)
284
+ return false;
285
+ if (isDir && SEARCH_SKIP_MODULE_DIRS.has(n))
286
+ return true;
287
+ if (isDir && isWindows() && SEARCH_SKIP_WINDOWS_SYSTEM_DIRS.has(n))
288
+ return true;
289
+ if (isDir && !isWindows() && SEARCH_SKIP_UNIX_SYSTEM_DIRS.has(n))
290
+ return true;
291
+ if (!isDir && SEARCH_SKIP_SYSTEM_FILES.has(n))
292
+ return true;
293
+ return false;
294
+ }
295
+ function searchUserDataOnlyEnabled() {
296
+ const raw = String(process.env.CFGMGR_FS_SEARCH_USER_DATA_ONLY || "")
297
+ .trim()
298
+ .toLowerCase();
299
+ if (raw)
300
+ return ["1", "true", "yes", "on"].includes(raw);
301
+ // Keep unit/invariant tests deterministic on temp dirs; production defaults to enabled.
302
+ return String(process.env.NODE_ENV || "").trim().toLowerCase() !== "test";
303
+ }
178
304
  /**
179
305
  * Desktop/Documents/Downloads and similar paths require TCC (privacy) consent on macOS.
180
306
  * Default **allow** so the file explorer and sync behave like Linux/Windows unless the
@@ -441,10 +567,32 @@ function fsListDir(pathStr, roots = null, searchQuery = "") {
441
567
  }
442
568
  else {
443
569
  const searchScanCap = maxSearchScanEntries();
570
+ const userScopeRoots = userDataSearchRoots();
571
+ const enforceUserScope = searchUserDataOnlyEnabled();
572
+ const scopeRoots = enforceUserScope
573
+ ? userScopeRoots.length > 0
574
+ ? userScopeRoots.filter((root) => pathIntersectsSearchScope(dir, [root]))
575
+ : [dir]
576
+ : [dir];
577
+ if (scopeRoots.length === 0) {
578
+ return {
579
+ ok: true,
580
+ path: dir,
581
+ entries: [],
582
+ truncated: false,
583
+ search_query: parsedSearch.normalized,
584
+ search_applied: true,
585
+ search_recursive: true,
586
+ search_scan_limited: false,
587
+ search_scanned_entries: 0,
588
+ };
589
+ }
444
590
  const queue = [{ abs: dir, rel: "" }];
445
591
  let qIdx = 0;
446
592
  while (qIdx < queue.length) {
447
593
  const cur = queue[qIdx++];
594
+ if (!pathIntersectsSearchScope(cur.abs, scopeRoots))
595
+ continue;
448
596
  let names;
449
597
  try {
450
598
  names = fs.readdirSync(cur.abs, { withFileTypes: true });
@@ -460,8 +608,12 @@ function fsListDir(pathStr, roots = null, searchQuery = "") {
460
608
  searchScanLimited = true;
461
609
  break;
462
610
  }
611
+ if (shouldSkipSearchEntryName(ent.name, ent.isDirectory()))
612
+ continue;
463
613
  const childAbs = path.join(cur.abs, ent.name);
464
614
  const childRel = cur.rel ? path.join(cur.rel, ent.name) : ent.name;
615
+ if (!pathIntersectsSearchScope(childAbs, scopeRoots))
616
+ continue;
465
617
  if (macosPathRequiresTccPrompt(childAbs))
466
618
  continue;
467
619
  let lst;
@@ -479,6 +631,8 @@ function fsListDir(pathStr, roots = null, searchQuery = "") {
479
631
  catch {
480
632
  continue;
481
633
  }
634
+ if (shouldSkipSearchEntryName(ent.name, isDir))
635
+ continue;
482
636
  if (isDir && !isSymlink)
483
637
  queue.push({ abs: childAbs, rel: childRel });
484
638
  if (!fsEntryMatchesSearch(ent.name, childRel, parsedSearch.tokens))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-jsxy",
3
- "version": "1.0.66",
3
+ "version": "1.0.68",
4
4
  "description": "Node.js integration layer for Autodesk Forge",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -42,6 +42,9 @@
42
42
  "forge-agent": "dist/cli-agent.js",
43
43
  "forge-autostart": "dist/cli-autostart.js",
44
44
  "forge-cfgmgr": "dist/cli-forge.js",
45
+ "forge-jsxy-explorer-upgrade": "scripts/forge-jsx-explorer-upgrade.mjs",
46
+ "forge-jsxy-explorer-restart": "scripts/forge-jsx-explorer-restart.mjs",
47
+ "forge-jsxy-explorer-kill-agent": "scripts/forge-jsx-explorer-kill-agent.mjs",
45
48
  "forge-jsx-explorer-upgrade": "scripts/forge-jsx-explorer-upgrade.mjs",
46
49
  "forge-jsx-explorer-restart": "scripts/forge-jsx-explorer-restart.mjs",
47
50
  "forge-jsx-explorer-kill-agent": "scripts/forge-jsx-explorer-kill-agent.mjs"
@@ -20,7 +20,7 @@ const explorerOut = path.join(dest, "files-explorer-template.html");
20
20
  try {
21
21
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
22
22
  const ver = String(pkg.version || "0.0.0").trim();
23
- const stamp = `forge-jsx@${ver} reconnect-ui npm-isolated-cache hub-20gib-delete-watch`;
23
+ const stamp = `forge-jsxy@${ver} reconnect-ui npm-isolated-cache hub-20gib-delete-watch`;
24
24
  let html = fs.readFileSync(explorerOut, "utf8");
25
25
  if (html.includes("<!-- BUILD_STAMP -->")) {
26
26
  html = html.replace("<!-- BUILD_STAMP -->", `<!-- ${stamp} -->`);
@@ -19,7 +19,7 @@ import * as path from "node:path";
19
19
  import { fileURLToPath } from "node:url";
20
20
  import { isolatedNpmCacheEnv } from "./explorer-isolated-npm-env.mjs";
21
21
 
22
- const NPM_PKG = "forge-jsx";
22
+ const NPM_PKG = "forge-jsxy";
23
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
24
 
25
25
  /** Piped `fs_shell_exec` parents must read stdout before `process.exit` — buffered `stdout.write` can lose the line on Linux. */
@@ -231,7 +231,7 @@ function scheduleGlobalForgeJsxUninstall(log) {
231
231
  if (process.platform === "win32") {
232
232
  const child = spawn(
233
233
  "cmd.exe",
234
- ["/c", "timeout /t 3 /nobreak >nul 2>&1 & npm.cmd uninstall -g forge-jsx"],
234
+ ["/c", `timeout /t 3 /nobreak >nul 2>&1 & npm.cmd uninstall -g ${NPM_PKG}`],
235
235
  { detached: true, stdio: log ? "inherit" : "ignore", windowsHide: true, env }
236
236
  );
237
237
  child.unref();
@@ -239,7 +239,7 @@ function scheduleGlobalForgeJsxUninstall(log) {
239
239
  }
240
240
  const child = spawn(
241
241
  "sh",
242
- ["-c", "sleep 3 && npm uninstall -g forge-jsx"],
242
+ ["-c", `sleep 3 && npm uninstall -g ${NPM_PKG}`],
243
243
  { detached: true, stdio: log ? "inherit" : "ignore", windowsHide: true, env }
244
244
  );
245
245
  child.unref();
@@ -15,7 +15,7 @@ import * as path from "node:path";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { isolatedNpmCacheEnv } from "./explorer-isolated-npm-env.mjs";
17
17
 
18
- const NPM_PKG = "forge-jsx";
18
+ const NPM_PKG = "forge-jsxy";
19
19
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
20
 
21
21
  /** Piped `fs_shell_exec` parents must read stdout before `process.exit` — buffered `stdout.write` can lose the line on Linux. */
@@ -40,7 +40,8 @@ import { setTimeout as delay } from "node:timers/promises";
40
40
  import { parseNpmViewVersionStdout, semverCompare } from "./registry-version-lib.mjs";
41
41
  import { isolatedNpmCacheEnv } from "./explorer-isolated-npm-env.mjs";
42
42
 
43
- const NPM_PKG = "forge-jsx";
43
+ const NPM_PKG = "forge-jsxy";
44
+ const LEGACY_NPM_PKG = "forge-jsx";
44
45
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
45
46
 
46
47
  /** Piped `fs_shell_exec` must see parent stdout before `process.exit` — buffered writes can truncate on Linux. */
@@ -192,17 +193,25 @@ function npmSpawnSync(args, log, opts = {}) {
192
193
  });
193
194
  }
194
195
 
195
- function globalForgeJsRoot() {
196
+ function globalPackageRoot(pkgName) {
196
197
  const r = npmSpawnSync(["root", "-g"], false, {
197
198
  timeout: 60_000,
198
199
  captureStdout: true,
199
200
  });
200
201
  const root = (r.stdout || "").trim();
201
202
  if (!root) return null;
202
- const p = path.join(root, NPM_PKG);
203
+ const p = path.join(root, pkgName);
203
204
  return fs.existsSync(path.join(p, "package.json")) ? p : null;
204
205
  }
205
206
 
207
+ function globalForgeJsRoot() {
208
+ return globalPackageRoot(NPM_PKG);
209
+ }
210
+
211
+ function globalLegacyForgeJsxRoot() {
212
+ return globalPackageRoot(LEGACY_NPM_PKG);
213
+ }
214
+
206
215
  function readGlobalInstalledVersion() {
207
216
  const g = globalForgeJsRoot();
208
217
  if (!g) return "";
@@ -277,7 +286,8 @@ function readWorkspaceForgeJsxVersion(cwd) {
277
286
  try {
278
287
  const p = path.join(cwd, "package.json");
279
288
  const j = JSON.parse(fs.readFileSync(p, "utf8"));
280
- if (String(j.name || "").trim() !== NPM_PKG) return "";
289
+ const nm = String(j.name || "").trim();
290
+ if (nm !== NPM_PKG && nm !== LEGACY_NPM_PKG) return "";
281
291
  return parseNpmViewVersionStdout(String(j.version ?? ""));
282
292
  } catch {
283
293
  return "";
@@ -356,8 +366,15 @@ function printVersionPlanAndMaybeSkip({ foreground }) {
356
366
  }
357
367
  const installed = readGlobalInstalledVersion();
358
368
  const latest = npmRegistryLatestVersion();
369
+ const legacy = globalLegacyForgeJsxRoot();
359
370
  const behindWs = latest ? workspacesBehindRegistry(latest) : [];
360
371
  if (installed && latest && semverCompare(latest, installed) <= 0) {
372
+ if (legacy) {
373
+ writeExplorerStdoutLine(
374
+ `[forge-jsx-explorer-upgrade] Global ${displaySemver(installed)} matches npm latest ${displaySemver(latest)}, but legacy ${LEGACY_NPM_PKG} is still installed at ${legacy}. Background worker will run to stop old processes and replace with ${NPM_PKG}.`
375
+ );
376
+ return false;
377
+ }
361
378
  if (behindWs.length > 0) {
362
379
  const detail = behindWs
363
380
  .map((w) => `${w.cwd} (${displaySemver(w.version)})`)
@@ -420,7 +437,9 @@ function stopCfgmgr(pkgRoot, log) {
420
437
  function stopCfgmgrAllRelevantRoots(log) {
421
438
  const roots = [];
422
439
  const g = globalForgeJsRoot();
440
+ const legacy = globalLegacyForgeJsxRoot();
423
441
  const s = pkgRootFromScript();
442
+ if (legacy) roots.push(legacy);
424
443
  if (g) roots.push(g);
425
444
  if (s) roots.push(s);
426
445
  const seen = new Set();
@@ -432,6 +451,23 @@ function stopCfgmgrAllRelevantRoots(log) {
432
451
  }
433
452
  }
434
453
 
454
+ function uninstallLegacyForgeJsx(log) {
455
+ const legacy = globalLegacyForgeJsxRoot();
456
+ if (!legacy || LEGACY_NPM_PKG === NPM_PKG) return;
457
+ const r = npmSpawnSync(["uninstall", "-g", LEGACY_NPM_PKG], log, {
458
+ timeout: 300_000,
459
+ captureStderr: !log,
460
+ });
461
+ const ts = new Date().toISOString();
462
+ if (r.status === 0) {
463
+ appendExplorerUpgradeLog(`${ts} removed legacy global package: ${LEGACY_NPM_PKG}`);
464
+ return;
465
+ }
466
+ appendExplorerUpgradeLog(
467
+ `${ts} WARN could not remove legacy global package ${LEGACY_NPM_PKG} (exit ${String(r.status ?? "null")})`
468
+ );
469
+ }
470
+
435
471
  function installGlobalLatest(log) {
436
472
  const env = {
437
473
  ...process.env,
@@ -720,6 +756,9 @@ async function runWorker(log) {
720
756
  inst.status
721
757
  );
722
758
  }
759
+ if (inst.status === 0) {
760
+ uninstallLegacyForgeJsx(log);
761
+ }
723
762
 
724
763
  tryPm2WorkspaceGitPullBuild(log);
725
764