@weppy/roblox-mcp 1.4.1 → 1.4.2

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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Roblox MCP server and tools for AI-powered game development",
9
- "version": "1.4.1"
9
+ "version": "1.4.2"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "weppy-roblox-mcp",
14
14
  "source": "./plugins/weppy-roblox-mcp",
15
15
  "description": "MCP server for Roblox Studio integration - AI-powered game development with specialized agents and skills",
16
- "version": "1.4.1",
16
+ "version": "1.4.2",
17
17
  "author": {
18
18
  "name": "hope1026"
19
19
  },
package/CHANGELOG.md CHANGED
@@ -6,6 +6,29 @@ All notable changes to this project will be documented in this file.
6
6
 
7
7
 
8
8
 
9
+
10
+
11
+ ## [1.4.2] - 2026-03-11
12
+
13
+ ### Improved
14
+
15
+ - MCP server code refactoring for better maintainability
16
+ - Enhanced license check logic for improved reliability
17
+ - Plugin internal stability improvements
18
+
19
+
20
+ ## [1.4.1] - 2026-03-10
21
+
22
+ ### Changed
23
+
24
+ - Simplify authentication flow — legacy auth paths removed for improved connection reliability
25
+
26
+ ### Improved
27
+
28
+ - MCP server internal stability and observability improvements
29
+ - Plugin internal cleanup and configuration refinements
30
+
31
+
9
32
  ## [1.4.0] - 2026-03-08
10
33
 
11
34
  ### New Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weppy/roblox-mcp",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "MCP (Model Context Protocol) server for Roblox Studio integration - enables AI coding agents to interact with Roblox Studio in real-time",
5
5
  "main": "plugins/weppy-roblox-mcp/dist/index.js",
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weppy-roblox-mcp",
3
3
  "description": "MCP server for Roblox Studio integration - AI-powered game development with specialized agents and skills",
4
- "version": "1.4.1",
4
+ "version": "1.4.2",
5
5
  "author": {
6
6
  "name": "hope1026"
7
7
  },
@@ -114,7 +114,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
114
114
  `).filter(g=>g.length>0)}catch(h){if(h.code==="ENOENT"){r.status(200).json({entries:[],total:0,hasMore:!1});return}throw h}let p=[];for(let h=u.length-1;h>=0;h--)try{let g=JSON.parse(u[h]);if(o&&g.direction!==o||s&&g.type!==s)continue;p.push(g)}catch{continue}let d=p.length,m={entries:p.slice(a,a+i),total:d,hasMore:a+i<d};r.status(200).json(m)}getStatusSummary(){if(this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0)return{active:!0,placeId:this.ctx.activeFullSyncPlaceId};for(let[e,r]of this.ctx.places.entries())if(r.state==="syncing")return{active:!0,placeId:e};return{active:!1}}getDirectionForCategory(e){let r;if(this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0&&(r=this.ctx.places.get(this.ctx.activeFullSyncPlaceId)),!r){let i=this.ctx.getDefaultRuntimePlaceId();i!=null&&(r=this.ctx.places.get(i))}if(!r)return"forward";let n=e;return r.directions[n]??"forward"}getStatusDirect(e){let r=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(r==null)return{state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.ctx.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};let n=this.ctx.places.get(r);if(!n)return{state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.ctx.config.getPlaceRoot(r),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};let i=n.fileWatcher?.getPendingCount()??0;return{state:n.state,instanceCount:n.instanceCount,scriptCount:n.scriptCount,lastFullSync:n.lastFullSync,lastIncrementalSync:n.lastIncrementalSync,syncRoot:this.ctx.config.getPlaceRoot(r),activeClientId:n.activeClientId,reverseSyncAvailable:i>0,modifiedFileCount:i}}getConfigDirect(){return this.ctx.config.getConfig()}async getHistoryDirect(e,r){let n=parseInt(e,10),i=this.ctx.places.get(n);i&&await i.writer.flushHistory();let a=Math.min(Math.max(r?.limit??50,1),200),o=Math.max(r?.offset??0,0),s=this.ctx.config.getHistoryPath(n),c=[];try{c=(await Ga.readFile(s,"utf-8")).split(`
115
115
  `).filter(f=>f.length>0)}catch(d){if(d.code==="ENOENT")return{entries:[],total:0,hasMore:!1};throw d}let l=[];for(let d=c.length-1;d>=0;d--)try{l.push(JSON.parse(c[d]))}catch{continue}let u=l.length;return{entries:l.slice(o,o+a),total:u,hasMore:o+a<u}}getDirectionsDirect(e){let r=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(r==null)return{...vi};let n=this.ctx.places.get(r);return n?{...n.directions}:{...vi}}getProgressDirect(e){let r=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(r==null)return{state:"idle",isSyncing:!1};let n=this.ctx.places.get(r);if(!n)return{state:"idle",isSyncing:!1};if(!n.syncProgress)return{state:n.state,isSyncing:!1,lastSync:{instanceCount:n.instanceCount,scriptCount:n.scriptCount,completedAt:n.lastFullSync}};let i=n.syncProgress,a=Date.now()-i.syncStartTime,o=i.totalInstances>0?Math.min(100,Math.round(i.processedInstances/i.totalInstances*100)):0,s;if(i.processedInstances>0&&o<100){let l=a/i.processedInstances,u=i.totalInstances-i.processedInstances;s=Math.round(l*u)}let c=a>0?Math.round(i.bytesReceived/a*1e3):0;return{state:n.state,isSyncing:!0,progressPercent:o,currentService:i.currentService,currentChunk:{index:i.currentChunkIndex,total:i.currentTotalChunks},instances:{processed:i.processedInstances,total:i.totalInstances},services:{processed:i.processedServices,total:i.totalServices},elapsedMs:a,estimatedRemainingMs:s,bytesReceived:i.bytesReceived,bytesPerSecond:c}}async readSyncedFile(e,r){let n=parseInt(e,10),i=this.ctx.places.get(n);if(!i)throw new Error(`Place ${e} not found in sync cache`);let a=i.index.resolvePropsPath(r);try{return{content:await Ga.readFile(a,"utf-8"),path:a}}catch(s){if(s.code!=="ENOENT")throw s}for(let s of Fa){let c=i.index.resolveScriptPath(r,s,!1);try{return{content:await Ga.readFile(c,"utf-8"),path:c}}catch{continue}}let o=i.index.resolveValuePath(r);try{return{content:await Ga.readFile(o,"utf-8"),path:o}}catch(s){if(s.code!=="ENOENT")throw s}throw new Error(`No synced file found for instance: ${r}`)}async writeSyncedFile(e,r,n){let i=parseInt(e,10),a=this.ctx.places.get(i);if(!a)throw new Error(`Place ${e} not found in sync cache`);await a.writer.writeScript(r,"Script",n,!1)}async executeViaDisk(e,r){let n=this.ctx.getDefaultRuntimePlaceId();if(n==null)throw new Error("No active sync place for disk execution");let i=this.ctx.places.get(n);if(!i)throw new Error(`Place ${n} not found in sync cache`);switch(e){case"set_script_source":{let a=r.scriptType||r.className||"Script";return await i.writer.writeScript(r.path,a,r.source,!1),{success:!0,path:r.path}}case"set_property":return await i.writer.writeProps(r.path,{className:r.className||"Instance",name:_t(r.path),properties:{[r.property]:r.value}}),{success:!0,path:r.path};default:throw new Error(`Disk execution not supported for action: ${e}`)}}};import ke from"path";import{randomUUID as hJ}from"crypto";import{promises as qt}from"fs";var Ad=class{constructor(e){this.ctx=e}preserveLocalFilesMap=new Map;pendingServiceTrees=new Map;async handleInitStart(e,r,n){if(r.previousPlaceId!==void 0&&r.previousPlaceId!==e&&(v.info("Place promotion detected",{from:r.previousPlaceId,to:e}),await this.ctx.config.promotePlaceRoot(r.previousPlaceId,e,r.placeName),this.ctx.places.get(r.previousPlaceId)&&this.ctx.places.delete(r.previousPlaceId),this.ctx.activeFullSyncPlaceId===r.previousPlaceId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(r.previousPlaceId)),this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0&&this.ctx.activeFullSyncPlaceId!==e){n.status(409).json({error:"Conflict",message:`Place ${this.ctx.activeFullSyncPlaceId} is currently syncing. Only one place can sync at a time.`});return}let i=await this.ctx.getOrCreatePlaceContext(e,r.placeName);if(i.activeClientId&&i.activeClientId!==r.clientId&&i.activeFullSyncSessionId!==null){n.status(409).json({error:"Conflict",message:`Another client (${i.activeClientId}) is currently syncing this place`});return}if(this.ctx.activeFullSyncPlaceId=e,this.ctx.touchRuntimePlace(e),i.activeClientId=r.clientId,i.placeName=r.placeName,r.directions&&typeof r.directions=="object"){let p=r.directions,d=f=>f==="forward"||f==="reverse"||f==="bidirectional";d(p.scripts)&&(i.directions.scripts=p.scripts),d(p.values)&&(i.directions.values=p.values),d(p.containers)&&(i.directions.containers=p.containers),d(p.data)&&(i.directions.data=p.data),d(p.services)&&(i.directions.services=p.services),v.info("Sync directions received",{placeId:e,directions:i.directions})}else i.directions={...vi};i.forwardRestoreQueue=[];let a=hJ();i.activeFullSyncSessionId=a,this.pendingServiceTrees.set(a,new Map),i.instanceCount=0,i.scriptCount=0;let o=this.ctx.config.getPlaceRoot(e),s=ke.join(o,`explorer_tmp_${a}`);await qt.mkdir(s,{recursive:!0}),i.tmpIndex=new jn(o,s),i.tmpWriter=new Za(this.ctx.config,i.tmpIndex,e),i.collisionDirMap=new Map;let c=r.preserveLocalFiles;Array.isArray(c)&&c.length>0&&(this.setPreserveLocalFiles(a,c),v.info("PreserveLocalFiles set for sync",{syncId:a,fileCount:c.length}));let l={version:1,placeId:r.placeId,placeName:r.placeName,lastFullSync:null,lastIncrementalSync:null,instanceCount:0,scriptCount:0,syncMode:"mirror",propertyMode:this.ctx.config.getPropertyMode()},u=ke.join(o,".sync-meta.json");await qt.mkdir(ke.dirname(u),{recursive:!0}),await this.ctx.atomicWriteFile(u,JSON.stringify(l,null,2)+`
116
116
  `),this.startTTLTimerForPlace(i,a),i.state="initializing",i.index.resetNameCounters(),i.syncProgress={syncStartTime:Date.now(),totalInstances:r.totalInstances,totalServices:r.totalServices,processedInstances:0,processedServices:0,currentService:null,currentChunkIndex:0,currentTotalChunks:0,bytesReceived:0,processedChunks:0},i.writer.appendChangeLog(`FULL_SYNC_START clientId=${r.clientId} placeId=${r.placeId}`),i.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncStart",direction:"forward",path:`place_${r.placeId}`,details:`services:${r.totalServices} instances:${r.totalInstances}`}),v.info("Full sync started",{syncId:a,clientId:r.clientId,placeId:r.placeId,placeName:r.placeName,totalServices:r.totalServices,totalInstances:r.totalInstances}),n.status(200).json({status:"started",syncId:i.activeFullSyncSessionId})}async handleInitChunk(e,r,n){let i=this.ctx.places.get(e);if(!i||!i.activeFullSyncSessionId||!i.tmpIndex||!i.tmpWriter){n.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(i.activeClientId&&r.clientId&&i.activeClientId!==r.clientId){n.status(409).json({error:"Conflict",message:`This sync session belongs to client ${i.activeClientId}`});return}let a=i.activeFullSyncSessionId,o=this.getOrCreatePendingServiceTree(a,r),s=0,c=i.collisionDirMap;for(let u of r.instances){if(this.ctx.config.isForbiddenPath(u.path))continue;let p=i.tmpIndex.resolveParentDir(u.path),d=c.get(p)??p,{resolved:f,retroactiveRename:m}=i.tmpIndex.registerCollision(u.path,u.siblingIndex??void 0,d);m&&(await Dn(i.tmpIndex,d,m.from,m.to),this.rewritePendingEffectivePaths(o,i.tmpIndex.getExplorerRoot(),ke.join(d,m.from),ke.join(d,m.to)));let h=i.tmpIndex.resolveChildrenDir(u.path),g=ke.join(d,f);g!==h&&c.set(h,g);let b=i.tmpIndex.sanitizeName(u.name),S=u;if(d!==p||f!==b){let $=i.tmpIndex.getExplorerRoot(),k=ke.relative($,d).split(ke.sep).filter(A=>A.length>0),N=We(["game",...k,f]);S={...u,path:N}}let _=await i.tmpWriter.writeInstance(S);i.tmpIndex.setClassName(u.path,u.className,u.siblingIndex),s++,(_.propsWritten||_.valueWritten)&&i.instanceCount++,_.scriptWritten&&i.scriptCount++,o.instances.push({effectivePath:S.path,originalPath:u.path,className:u.className})}let l=this.isLastChunk(o,r.chunkIndex,r.totalChunks);if(l){let u=this.buildServiceTree(i,o);await i.tmpWriter.writeTree(r.serviceName,u);let p=this.pendingServiceTrees.get(a);p?.delete(r.serviceName),p&&p.size===0&&this.pendingServiceTrees.delete(a)}i.syncProgress&&(i.syncProgress.processedInstances+=s,i.syncProgress.currentService=r.serviceName,i.syncProgress.currentChunkIndex=r.chunkIndex,i.syncProgress.currentTotalChunks=r.totalChunks,i.syncProgress.processedChunks++,i.syncProgress.bytesReceived+=JSON.stringify(r).length,l&&i.syncProgress.processedServices++),v.debug("Sync chunk processed",{placeId:e,serviceName:r.serviceName,chunkIndex:r.chunkIndex,totalChunks:r.totalChunks,processed:s}),n.status(200).json({processed:s,service:r.serviceName})}async handleInitComplete(e,r,n){let i=this.ctx.places.get(e);if(!i||!i.activeFullSyncSessionId){n.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(i.activeClientId&&r.clientId&&i.activeClientId!==r.clientId){n.status(409).json({error:"Conflict",message:`This sync session belongs to client ${i.activeClientId}`});return}let a=i.activeFullSyncSessionId,o=this.ctx.config.getPlaceRoot(e),s=ke.join(o,"explorer"),c=ke.join(o,`explorer_tmp_${a}`),l=this.getAndClearPreserveLocalFiles(a),u=new Map,p=[];if(l.length>0){for(let m of l){let h=ke.resolve(o,m);try{let g=await qt.readFile(h,"utf-8");u.set(m,g)}catch{p.push(m)}}v.info("Backed up local files for preservation",{placeId:e,requested:l.length,backed:u.size,deleted:p.length})}try{await qt.rm(s,{recursive:!0,force:!0})}catch{}await qt.rename(c,s),i.instanceCount=r.instanceCount,i.scriptCount=r.scriptCount,i.lastFullSync=new Date().toISOString();let d={version:1,placeId:i.placeId,placeName:i.placeName,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,instanceCount:i.instanceCount,scriptCount:i.scriptCount,syncMode:"mirror",propertyMode:this.ctx.config.getPropertyMode()},f=ke.join(o,".sync-meta.json");if(await this.ctx.atomicWriteFile(f,JSON.stringify(d,null,2)+`
117
- `),this.clearTTLTimerForPlace(i,a),i.index.clearAllHashes(),i.index.clearClassMappings(),i.tmpIndex){let m=i.tmpIndex.getExplorerRoot(),h=i.index.getExplorerRoot();for(let[g,b]of i.tmpIndex.getAllHashes()){let S=ke.relative(m,g),_=ke.resolve(h,S);i.index.updateHashByValue(_,b)}for(let[g,b]of i.tmpIndex.getAllFileHashes()){let S=ke.relative(m,g),_=ke.resolve(h,S);i.index.updateFileHashByValue(_,b)}i.index.resetNameCounters(),i.index.mergeNameMappingsFrom(i.tmpIndex)}if(i.tmpIndex=null,i.tmpWriter&&(i.tmpWriter.stopChangeLogFlusher(),i.tmpWriter=null),this.pendingServiceTrees.delete(a),i.collisionDirMap=null,await i.index.saveToDisk(),i.state="syncing",i.activeFullSyncSessionId=null,i.syncProgress=null,this.ctx.touchRuntimePlace(e),this.ctx.activeFullSyncPlaceId=null,await this.ctx.startFileWatcherForPlace(i),i.fileWatcher&&await i.fileWatcher.waitUntilReady(),u.size>0){for(let[m,h]of u){let g=ke.resolve(o,m);try{await qt.mkdir(ke.dirname(g),{recursive:!0}),await qt.writeFile(g,h,"utf-8")}catch(b){v.warn("Failed to restore preserved file",{path:m,error:b instanceof Error?b.message:String(b)})}}v.info("Restored preserved local files",{placeId:e,count:u.size})}if(p.length>0){let m=0;for(let h of p){let g=ke.resolve(o,h);try{await qt.unlink(g),i.index.removeHash(g),m++}catch(b){b.code!=="ENOENT"&&v.warn("Failed to delete preserved-as-deleted file",{path:h,error:b instanceof Error?b.message:String(b)})}}m>0&&(await i.index.saveToDisk(),v.info("Deleted locally-removed files from new sync",{placeId:e,count:m}))}i.writer.appendChangeLog(`FULL_SYNC_COMPLETE instances=${i.instanceCount} scripts=${i.scriptCount}`),i.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncComplete",direction:"forward",path:`place_${e}`,details:`instances:${i.instanceCount} scripts:${i.scriptCount}`}),v.info("Full sync completed",{placeId:e,instanceCount:i.instanceCount,scriptCount:i.scriptCount}),n.status(200).json({status:"completed",instanceCount:i.instanceCount,scriptCount:i.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}setPreserveLocalFiles(e,r){this.preserveLocalFilesMap.set(e,r)}getAndClearPreserveLocalFiles(e){let r=this.preserveLocalFilesMap.get(e)||[];return this.preserveLocalFilesMap.delete(e),r}clearPreserveLocalFiles(e){this.preserveLocalFilesMap.delete(e)}clearPendingServiceTrees(e){this.pendingServiceTrees.delete(e)}startTTLTimerForPlace(e,r){let n=setTimeout(async()=>{v.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:r});let i=this.ctx.config.getPlaceRoot(e.placeId),a=ke.join(i,`explorer_tmp_${r}`);try{await qt.rm(a,{recursive:!0,force:!0})}catch(o){v.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===r&&(e.activeFullSyncSessionId=null,e.activeClientId=null,e.state="idle",e.tmpIndex=null,this.pendingServiceTrees.delete(r),e.collisionDirMap=null,e.tmpWriter&&(e.tmpWriter.stopChangeLogFlusher(),e.tmpWriter=null),this.ctx.activeFullSyncPlaceId===e.placeId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(e.placeId))},ER);n&&typeof n=="object"&&"unref"in n&&n.unref(),e.incompleteSyncTimer=n}getOrCreatePendingServiceTree(e,r){let n=this.pendingServiceTrees.get(e);n||(n=new Map,this.pendingServiceTrees.set(e,n));let i=n.get(r.serviceName);if(i)return i;let a={serviceName:r.tree?.name??r.serviceName,serviceClassName:r.tree?.className??r.serviceClassName,zeroBasedChunkIndex:r.chunkIndex===0,instances:[]};return n.set(r.serviceName,a),a}isLastChunk(e,r,n){return n<=1?!0:e.zeroBasedChunkIndex?r>=n-1:r>=n}buildServiceTree(e,r){let n={name:r.serviceName,className:r.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let i of r.instances){let a=this.resolveEffectiveSegments(e,i.effectivePath),o=ut(i.originalPath);this.upsertTreeNode(n,a,o,i.className)}return this.recomputeTreeChildCounts(n),n.syncedAt=new Date().toISOString(),n}resolveEffectiveSegments(e,r){return e.tmpIndex?ut(r).map(n=>e.tmpIndex.sanitizeName(n)):ut(r)}rewritePendingEffectivePaths(e,r,n,i){let a=ke.relative(r,n).split(ke.sep).filter(l=>l.length>0),o=ke.relative(r,i).split(ke.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=We(["game",...a]),c=We(["game",...o]);for(let l of e.instances){if(l.effectivePath===s){l.effectivePath=c;continue}(l.effectivePath.startsWith(`${s}.`)||l.effectivePath.startsWith(`${s}[`))&&(l.effectivePath=`${c}${l.effectivePath.slice(s.length)}`)}}upsertTreeNode(e,r,n,i){if(r.length<=1)return;let a=e.children;for(let o=1;o<r.length;o++){let s=r[o],c=n[o],l=o===r.length-1,u=a.find(p=>p.name===s);u?l&&(u.className=i,c!==void 0&&c!==s&&(u.originalName=c)):(u={name:s,className:l?i:"Folder",childCount:0,children:[]},c!==void 0&&c!==s&&(u.originalName=c),a.push(u)),u.children||(u.children=[]),a=u.children}}recomputeTreeChildCounts(e){let r=n=>{let i=n.children??[];n.children=i;for(let a of i)r(a);n.childCount=i.length};for(let n of e.children)r(n);e.childCount=e.children.length}clearTTLTimerForPlace(e,r){e.incompleteSyncTimer&&e.activeFullSyncSessionId===r&&(clearTimeout(e.incompleteSyncTimer),e.incompleteSyncTimer=null)}async cleanupStaleTempDirs(){let e=this.ctx.config.getSyncRoot();try{let r=await qt.readdir(e,{withFileTypes:!0});for(let n of r)if(n.isDirectory()){if(n.name.startsWith("explorer_tmp_")){let i=ke.join(e,n.name);v.warn("Removing stale temp directory from crashed sync",{dir:n.name});try{await qt.rm(i,{recursive:!0,force:!0})}catch(a){v.error(`Failed to remove stale temp dir: ${n.name}`,a instanceof Error?a:new Error(String(a)))}}if(n.name.startsWith("place_")){let i=ke.join(e,n.name);try{let a=await qt.readdir(i,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=ke.join(i,o.name);v.warn("Removing stale temp directory from crashed sync",{dir:`${n.name}/${o.name}`});try{await qt.rm(s,{recursive:!0,force:!0})}catch(c){v.error(`Failed to remove stale temp dir: ${n.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(r){if(r.code==="ENOENT")return;v.warn("Failed to scan for stale temp dirs",{error:r instanceof Error?r.message:String(r)})}}};import Rr from"path";import{promises as DR}from"fs";var Md=class{constructor(e){this.ctx=e}async handleReversePending(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}let a={pending:i.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:i.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:i.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(n),r.status(200).json(a)}async handleReverseSyncChanges(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({changes:[],count:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(i.state!=="syncing"){r.status(400).json({error:"Not syncing",message:"Reverse sync is only available when sync is active"});return}let a=i.fileWatcher?.drainPendingChanges()??[],o=a.length>0?await i.reader.buildChangesFromPending(a):[];this.ctx.touchRuntimePlace(n),r.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,r){let n=e.body,i=n.placeId??this.ctx.getDefaultRuntimePlaceId();if(i==null){r.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=n.appliedFiles??n.appliedPaths;if(!a||!Array.isArray(a)){r.status(400).json({error:"Validation error",message:"appliedFiles (or appliedPaths) must be an array of relative file paths"});return}let o=this.ctx.places.get(i);if(!o){r.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}this.ctx.touchRuntimePlace(i);let s=this.ctx.config.getPlaceRoot(i),c=0,l=[];for(let u of a){let p=Rr.resolve(s,u);if(!Dd(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await DR.readFile(p,"utf-8"),f=o.index.computeHash(d);o.index.updateHashByValue(p,f),o.index.updateFileHashByValue(p,f),c++}catch(d){let f=d.code;if(f==="ENOENT"){o.index.removeHash(p),o.index.removeHashesUnder(p);let m=this.resolveInstancePathForAppliedPath(o.index,p);if(m){let h=_t(m),g=wt(m);g&&h&&await o.writer.removeFromTree(g,h)}c++}else f==="EISDIR"?c++:l.push({path:u,error:d instanceof Error?d.message:String(d)})}}c>0&&await o.index.saveToDisk(),c>0&&o.writer.appendHistory({timestamp:new Date().toISOString(),type:"reverseApply",direction:"reverse",path:`place_${i}`,details:`applied:${c} failed:${l.length}`}),r.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,r){let n=e.body;if(!n.fsPath||!n.resolution){r.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:i,resolution:a}=n,o=this.ctx.config.getSyncRoot();if(!Dd(o,Rr.resolve(o,i))){r.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){r.status(200).json({status:"skipped",fsPath:i});return}let s;if(n.placeId&&(s=this.ctx.places.get(n.placeId)),!s){let d=i.match(/^place_(\d+)(?:_[^/]+)?\//);if(d){let f=parseInt(d[1],10);s=this.ctx.places.get(f)}else s=Array.from(this.ctx.places.values())[0]}if(!s){r.status(404).json({error:"No active place context",message:"No sync session is active. Start a sync first."});return}let c=this.ctx.config.getPlaceRoot(s.placeId),l=Rr.resolve(c,i);if(!Dd(c,l)){r.status(403).json({error:"Forbidden",message:"Path is outside the place root"});return}let u=s.index,p=s.reader;if(a==="apply-studio"){u.resolveFile(l,"apply-studio"),await u.saveToDisk(),r.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:i});return}if(a==="apply-file"){let d=await DR.readFile(l,"utf-8"),f=u.computeHash(d);u.resolveFile(l,"apply-file",f),await u.saveToDisk();let m=p.getFileType(l),h=p.resolveInstancePathFromFile(l);r.status(200).json({status:"resolved",resolution:"apply-file",fsPath:i,instancePath:h,fileType:m,content:d});return}r.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({added:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(!i.fileWatcher){r.status(200).json({added:0});return}let a=await i.fileWatcher.rescan();this.ctx.touchRuntimePlace(n),v.info("Reverse rescan completed",{placeId:n,added:a}),r.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,r){let n=e.resolveInstancePathFromFsPath(r);if(n)return n;let i=e.getExplorerRoot(),a=Rr.relative(i,r);if(a.startsWith("..")||a===""||Rr.isAbsolute(a))return null;let o=a.split(Rr.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=Rr.basename(r),c=Rr.dirname(r),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Ua.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=i;for(let f of o){d=Rr.join(d,f);let m=Rr.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return We(p)}};function OR(t){if(!t||typeof t!="object")return;let e=t;if(Array.isArray(e.instances))for(let r of e.instances)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={});if(Array.isArray(e.changes))for(let r of e.changes)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={})}var Ud=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;constructor(e){this.config=new yd(e),this.apiHandler=new Nd(this),this.changeProcessor=new Od(this),this.initHandler=new Ad(this),this.reverseHandler=new Md(this),this.places=new vd({max:3,dispose:(r,n)=>{v.info("Disposing place context (LRU eviction)",{placeId:n}),this.activeFullSyncPlaceId===n&&(this.activeFullSyncPlaceId=null),this.activeRuntimeSyncPlaceId===n&&(this.activeRuntimeSyncPlaceId=null),r.fileWatcher&&(r.fileWatcher.stop().catch(i=>{v.error("Error stopping file watcher during dispose",i)}),r.fileWatcher=null),r.writer.stopChangeLogFlusher(),r.incompleteSyncTimer&&(clearTimeout(r.incompleteSyncTimer),r.incompleteSyncTimer=null),r.index.saveToDisk().catch(i=>{v.error("Error saving index during dispose",i)}),r.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(r.activeFullSyncSessionId),r.tmpWriter&&(r.tmpWriter.stopChangeLogFlusher(),r.tmpWriter=null),r.tmpIndex=null,r.collisionDirMap=null}})}async getOrCreatePlaceContext(e,r){let n=this.places.get(e);if(n&&r){let i=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,r);a!==i&&(v.info("Place root migrated, recreating context",{placeId:e,from:i,to:a}),this.places.delete(e),n=void 0)}if(!n){let i=await this.config.resolvePlaceRoot(e,r),a=f_.join(i,"explorer");await Ka.mkdir(i,{recursive:!0}),await Ka.mkdir(a,{recursive:!0});let o=new jn(i,a);await o.loadFromDisk();let s=new Za(this.config,o,e),c=new _d(this.config,o,i);s.startChangeLogFlusher(),n={placeId:e,placeName:"",index:o,writer:s,reader:c,fileWatcher:null,state:"idle",activeClientId:null,activeFullSyncSessionId:null,instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,tmpIndex:null,tmpWriter:null,incompleteSyncTimer:null,changesSinceLastSave:0,directions:{...vi},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,n),v.info("Created new place context",{placeId:e,placeRoot:i})}return n}async handleSyncInit(e,r){try{OR(e.body);let n=CR(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.phase==="start"?i.placeId??null:i.phase==="chunk"||i.phase==="complete"?this.activeFullSyncPlaceId:null;if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in start phase or must be set by previous start"});return}switch(i.phase){case"start":await this.initHandler.handleInitStart(a,i,r);break;case"chunk":await this.initHandler.handleInitChunk(a,i,r);break;case"complete":await this.initHandler.handleInitComplete(a,i,r);break}}catch(n){this.sendError(r,n,"handleSyncInit")}}async handleSyncUpdate(e,r){try{OR(e.body);let n=zR(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.placeId??this.getDefaultRuntimePlaceId();if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in body or must have an active sync session"});return}let o=await this.getOrCreatePlaceContext(a);if(o.activeFullSyncSessionId!==null||o.state==="initializing"){r.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=i.clientId,this.touchRuntimePlace(a);let s=PR(o,i.changes);v.info("Sync update received",{placeId:a,clientId:i.clientId,changeCount:s.length,receivedCount:i.changes.length,types:s.map(f=>f.type)});let c=[],l=[],u=0,p=new Map;for(let f of s)try{let m=await this.changeProcessor.processChangeForPlace(o,f,p);m?l.push(m):u++}catch(m){let h="path"in f?f.path:"oldPath"in f?f.oldPath:"unknown";c.push({path:h,error:m instanceof Error?m.message:String(m)})}for(let f of p.values())try{await zd(o,f)}catch(m){c.push({path:f.instancePath,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=TR&&(await o.index.saveToDisk(),o.changesSinceLastSave=0),o.lastIncrementalSync=new Date().toISOString();let d={processed:u,failed:c.length,errors:c,syncedAt:o.lastIncrementalSync};l.length>0&&(d.conflicts=l),r.status(200).json(d)}catch(n){this.sendError(r,n,"handleSyncUpdate")}}getDefaultRuntimePlaceId(){if(this.activeRuntimeSyncPlaceId!==null&&this.activeRuntimeSyncPlaceId!==void 0){if(this.places.has(this.activeRuntimeSyncPlaceId))return this.activeRuntimeSyncPlaceId;this.activeRuntimeSyncPlaceId=null}if(this.activeFullSyncPlaceId!==null&&this.activeFullSyncPlaceId!==void 0&&this.places.has(this.activeFullSyncPlaceId))return this.activeRuntimeSyncPlaceId=this.activeFullSyncPlaceId,this.activeRuntimeSyncPlaceId;for(let[e,r]of this.places.entries())if(r.state==="syncing"||r.state==="initializing")return this.activeRuntimeSyncPlaceId=e,e;return this.activeRuntimeSyncPlaceId=null,null}touchRuntimePlace(e){this.activeRuntimeSyncPlaceId=e}clearRuntimePlaceIfMatch(e){this.activeRuntimeSyncPlaceId===e&&(this.activeRuntimeSyncPlaceId=null)}resolveQueryPlaceId(e,r="runtime"){let n=e.query.placeId;if(n){let i=parseInt(n,10);if(!isNaN(i))return i}return r==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,r){try{let n=this.resolveQueryPlaceId(e);if(n==null){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let i=this.places.get(n);if(!i){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getPlaceRoot(n),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let a=i.fileWatcher?.getPendingCount()??0;this.touchRuntimePlace(n);let o={state:i.state,instanceCount:i.instanceCount,scriptCount:i.scriptCount,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(n),activeClientId:i.activeClientId,reverseSyncAvailable:a>0,modifiedFileCount:a,directions:i.directions,fileWatcherActive:i.fileWatcher!==null,forwardOnlyClasses:[...La]};r.status(200).json(o)}catch(n){this.sendError(r,n,"handleSyncStatus")}}async handleSyncStop(e,r){try{let n=e.body,i=n.placeId??this.getDefaultRuntimePlaceId();if(i==null){r.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(i);if(!a){r.status(200).json({status:"idle",state:"idle",placeId:i,message:`No sync context for place ${i}`});return}if(n.clientId&&a.activeClientId&&n.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){r.status(409).json({error:"Conflict",message:`This sync session belongs to client ${a.activeClientId}`});return}let o=a.activeFullSyncSessionId;if(o&&(this.initHandler.clearPreserveLocalFiles(o),this.initHandler.clearPendingServiceTrees(o)),a.fileWatcher&&(await a.fileWatcher.stop(),a.fileWatcher=null),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let s=this.config.getPlaceRoot(i),c=f_.join(s,`explorer_tmp_${o}`);await Ka.rm(c,{recursive:!0,force:!0}).catch(()=>{})}a.tmpWriter&&(a.tmpWriter.stopChangeLogFlusher(),a.tmpWriter=null),a.tmpIndex=null,a.collisionDirMap=null,a.activeFullSyncSessionId=null,a.syncProgress=null,a.state="idle",a.activeClientId=null,a.instanceCount=0,a.scriptCount=0,this.activeFullSyncPlaceId===i&&(this.activeFullSyncPlaceId=null),this.clearRuntimePlaceIfMatch(i),v.info("Sync stopped",{placeId:i,reason:n.reason??"requested"}),r.status(200).json({status:"stopped",state:"idle",placeId:i})}catch(n){this.sendError(r,n,"handleSyncStop")}}async handleSyncConfig(e,r){try{if(e.method==="GET"){r.status(200).json(this.config.getConfig());return}let n=RR(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data;i.propertyMode!==void 0&&this.config.setPropertyMode(i.propertyMode),i.maxDepth!==void 0&&this.config.updateConfig({maxDepth:i.maxDepth}),i.maxInstances!==void 0&&this.config.updateConfig({maxInstances:i.maxInstances}),r.status(200).json({status:"updated",config:this.config.getConfig()})}catch(n){this.sendError(r,n,"handleSyncConfig")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),v.info("SyncController initialized")}catch(e){v.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{this.places.clear(),v.info("SyncController shut down")}catch(e){v.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,r){let n=e+".tmp."+gJ().slice(0,8);try{await Ka.writeFile(n,r,"utf-8"),await Ka.rename(n,e)}catch(i){throw await Ka.unlink(n).catch(()=>{}),i}}async handlePreCheck(e,r){try{await this.apiHandler.handlePreCheck(e,r)}catch(n){this.sendError(r,n,"handlePreCheck")}}async handleSyncDirections(e,r){try{await this.apiHandler.handleSyncDirections(e,r)}catch(n){this.sendError(r,n,"handleSyncDirections")}}async handleForwardRestoreList(e,r){try{await this.apiHandler.handleForwardRestoreList(e,r)}catch(n){this.sendError(r,n,"handleForwardRestoreList")}}async handleReversePending(e,r){try{await this.reverseHandler.handleReversePending(e,r)}catch(n){this.sendError(r,n,"handleReversePending")}}async handleReverseSyncChanges(e,r){try{await this.reverseHandler.handleReverseSyncChanges(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,r){try{await this.reverseHandler.handleReverseSyncResult(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncResult")}}async handleResolveConflict(e,r){try{await this.reverseHandler.handleResolveConflict(e,r)}catch(n){this.sendError(r,n,"handleResolveConflict")}}async handleReverseRescan(e,r){try{await this.reverseHandler.handleReverseRescan(e,r)}catch(n){this.sendError(r,n,"handleReverseRescan")}}async handleSyncHistory(e,r){try{await this.apiHandler.handleSyncHistory(e,r)}catch(n){this.sendError(r,n,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){v.debug("Skipping file watcher start - place not syncing",{placeId:e.placeId,state:e.state});return}e.fileWatcher&&await e.fileWatcher.stop();let r=f_.join(this.config.getPlaceRoot(e.placeId),"explorer");e.fileWatcher=new Cd(r,e.index),e.writer.setOnWriteCallback(n=>{e.fileWatcher?.suppressPath(n)}),e.fileWatcher.setDirectionChecker(n=>{let i=Xz(n);return e.directions[i]}),e.fileWatcher.setOnForwardViolation(n=>{e.forwardRestoreQueue.includes(n)||(e.forwardRestoreQueue.push(n),v.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:n,queueSize:e.forwardRestoreQueue.length}))}),await e.fileWatcher.start(),v.info("File watcher started for reverse sync",{placeId:e.placeId})}getStatusSummary(){return this.apiHandler.getStatusSummary()}getDirectionForCategory(e){return this.apiHandler.getDirectionForCategory(e)}getStatusDirect(e){return this.apiHandler.getStatusDirect(e)}getConfigDirect(){return this.apiHandler.getConfigDirect()}async getHistoryDirect(e,r){return this.apiHandler.getHistoryDirect(e,r)}getDirectionsDirect(e){return this.apiHandler.getDirectionsDirect(e)}getProgressDirect(e){return this.apiHandler.getProgressDirect(e)}async readSyncedFile(e,r){return this.apiHandler.readSyncedFile(e,r)}async writeSyncedFile(e,r,n){await this.apiHandler.writeSyncedFile(e,r,n)}async executeViaDisk(e,r){return this.apiHandler.executeViaDisk(e,r)}sendError(e,r,n){let i=r instanceof Error?r.message:String(r);if(i.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:i});return}let a=r.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:i});return}v.error(`SyncController.${n} failed`,r instanceof Error?r:new Error(i)),e.status(500).json({error:"Internal error",message:`${n}: ${i}`})}};import{randomUUID as vJ}from"crypto";function NR(t,e){let r=vJ(),n=r.replace(/-/g,"").substring(0,8).toUpperCase(),i=`${n.substring(0,4)}-${n.substring(4,8)}`;return{config:t,app:e,instanceId:r,sessionId:i,startTime:Date.now(),baseUrl:`http://${t.httpHost}:${t.httpPort}`,commandQueue:new Map,pendingCommands:new Map,globalPendingCommands:[],totalCommandsProcessed:0,pluginClients:new Map,mcpInstances:new Map,sseClients:new Set,cachedSelectionMap:new Map,isClientMode:!1,clientModeHealthTimer:null,clientModeConsecutiveHealthFailures:0,clientModeUpstreamReachable:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,historyManager:null,analyticsManager:null,licenseState:null,syncController:null,aiClientName:"",syncedSessionToken:null}}var AR=to(fd(),1);function MR(t){let e=AR.default.json();t.app.use((r,n,i)=>{if(r.path.startsWith("/sync/")){i();return}e(r,n,i)}),t.app.use((r,n,i)=>{n.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),n.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),n.setHeader("Access-Control-Allow-Headers","Content-Type"),i()}),t.app.use((r,n,i)=>{v.debug(`${r.method} ${r.path}`,{ip:r.ip}),i()})}import{randomUUID as Ec}from"crypto";var Xe="1.4.0";function UR(t){let e=new Set;t.aiClientName&&e.add(t.aiClientName);for(let r of t.mcpInstances.values())r.aiClientName&&e.add(r.aiClientName);return Array.from(e)}function yJ(t){let r=Date.now();return Array.from(t.pluginClients.values()).filter(n=>r-n.lastSeen<1e4)}function FR(t){let r=Date.now();for(let[n,i]of t.mcpInstances)i.lastSeen&&r-i.lastSeen>15e3&&(t.mcpInstances.delete(n),v.debug("Removed stale MCP instance",{instanceId:n,lastSeen:i.lastSeen}))}function LR(t,e,r){let n=e.query.clientId;if(n&&t.pluginClients.has(n)){let i=t.pluginClients.get(n);i.lastSeen=Date.now()}try{let i=e.body;if(!i||!Array.isArray(i.selection)||typeof i.count!="number"){v.warn("Invalid selection update request",{body:i}),r.status(400).json({error:"Invalid request body"});return}let a=n||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:i.selection,count:i.count,timestamp:o,clientId:a}),v.debug("Selection cache updated",{count:i.count,clientId:a,timestamp:o}),r.json({status:"ok",timestamp:o})}catch(i){v.error("Error handling selection update",i),r.status(500).json({error:"Internal server error"})}}function qR(t,e,r){let n=parseInt(e.query.maxAge)||3e4,i=Pc(t,n);i?r.json({cached:!0,...i}):r.json({cached:!1,message:"No cached selection available"})}function Pc(t,e=3e4,r){if(t.cachedSelectionMap.size===0)return null;let n;if(r)n=t.cachedSelectionMap.get(r);else for(let a of t.cachedSelectionMap.values())(!n||a.timestamp>n.timestamp)&&(n=a);if(!n)return null;let i=Date.now()-n.timestamp;return e===0||i<=e?n:null}function ZR(t,e,r){try{let n=e.body;if(!n.clientId){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.get(n.clientId),a=Date.now(),o={clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,placeId:n.placeId,connectedAt:i?.connectedAt||a,lastSeen:a,commandsProcessed:i?.commandsProcessed||0,connectionType:"polling"};t.pluginClients.set(n.clientId,o),t.pendingCommands.has(n.clientId)||t.pendingCommands.set(n.clientId,[]),t.analyticsManager&&(n.pluginVersion&&t.analyticsManager.setPluginVersion(n.pluginVersion),t.analyticsManager.trackPluginConnected()),v.info("Plugin client registered",{clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,isReconnect:!!i}),t.licenseState&&t.licenseState.triggerImmediateRefreshOnConnect(n.clientId),r.json({status:"ok",clientId:n.clientId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,connectedAt:o.connectedAt,aiClientNames:UR(t),serverStartTime:t.startTime,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){v.error("Error registering plugin client",n),r.status(500).json({error:"Internal server error"})}}function HR(t,e,r){let n=e.body?.clientId;if(!n){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.has(n);t.pluginClients.delete(n),t.pendingCommands.delete(n),v.info("Plugin client unregistered",{clientId:n,existed:i}),r.json({status:"ok",existed:i})}function BR(t,e,r){try{let n=e.body;if(!n.instanceId){r.status(400).json({error:"Missing instanceId"});return}let i=Date.now(),a={instanceId:n.instanceId,pid:n.pid,connectedAt:i,isServer:!1,lastSeen:i};n.aiClientName&&(a.aiClientName=n.aiClientName),n.cwd&&(a.cwd=n.cwd),t.mcpInstances.set(n.instanceId,a),v.info("MCP instance registered (client mode)",{instanceId:n.instanceId,pid:n.pid,cwd:n.cwd}),r.json({status:"ok",instanceId:n.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){v.error("Error registering MCP instance",n),r.status(500).json({error:"Internal server error"})}}function VR(t,e,r){let n=e.body?.instanceId;if(!n){r.status(400).json({error:"Missing instanceId"});return}let i=t.mcpInstances.has(n);t.mcpInstances.delete(n),v.info("MCP instance unregistered",{instanceId:n,existed:i}),r.json({status:"ok",existed:i})}function WR(t,e,r){let{instanceId:n,aiClientName:i}=e.body;if(n&&i){let a=t.mcpInstances.get(n);a&&(a.aiClientName=i)}r.json({status:"ok"})}function JR(t){let r=Date.now();for(let[n,i]of t.pluginClients)r-i.lastSeen>3e4&&(t.pluginClients.delete(n),t.pendingCommands.delete(n));return FR(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,uptime:r-t.startTime,serverStartTime:t.startTime,serverExecutable:process.execPath,serverHost:t.config.httpHost,serverPort:t.config.httpPort,serverPid:process.pid,mcpInstances:[{instanceId:t.instanceId,pid:process.pid,connectedAt:t.startTime,isServer:!0,cwd:process.cwd()},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function GR(t,e){e.json(JR(t))}function KR(t,e,r){let n=e.query.instanceId;n&&t.mcpInstances.has(n)&&(t.mcpInstances.get(n).lastSeen=Date.now()),FR(t);let i=yJ(t),a=t.sseClients.size,c={...{status:"online",connectedClients:a+i.length,queuedCommands:t.commandQueue.size,uptime:Date.now()-t.startTime,version:Xe,isClientMode:t.isClientMode,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:UR(t),sseClients:a,pollingClients:i.length,pluginClients:i.map(l=>({clientId:l.clientId,projectName:l.projectName,lastSeen:Date.now()-l.lastSeen})),...t.isClientMode?{upstream:{reachable:t.clientModeUpstreamReachable,consecutiveFailures:t.clientModeConsecutiveHealthFailures,lastSuccessAt:t.clientModeLastHealthSuccessAt,lastFailureAt:t.clientModeLastHealthFailureAt,lastError:t.clientModeLastHealthError,baseUrl:t.baseUrl}}:{}};r.json(c)}function XR(t,e,r,n){let i=e.ip||e.socket.remoteAddress||"";if(!(i==="127.0.0.1"||i==="::1"||i==="::ffff:127.0.0.1"||i==="localhost")){v.warn("Shutdown request rejected from non-localhost",{ip:i}),r.status(403).json({error:"Forbidden: localhost only"});return}v.info("Shutdown request received, initiating graceful shutdown",{requestedBy:i,uptime:Date.now()-t.startTime}),r.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await n(),v.info("Graceful shutdown completed"),process.exit(0)}catch(o){v.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function Fd(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){v.warn("Failed to fetch connection info from server",{error:e})}return JR(t)}async function Ld(t,e,r){if(!r)return{success:!1,error:"Sync controller not available. Project sync is not initialized."};try{switch(t){case"sync_status":{let n=e.placeId;return{success:!0,data:r.getStatusDirect(n)}}case"sync_config":return{success:!0,data:r.getConfigDirect()};case"sync_history":{let n=e.placeId,i=e.query;return n==null?{success:!1,error:"placeId is required for sync_history"}:{success:!0,data:await r.getHistoryDirect(n,i)}}case"sync_directions":{let n=e.placeId;return{success:!0,data:r.getDirectionsDirect(n)}}case"sync_progress":{let n=e.placeId;return{success:!0,data:r.getProgressDirect(n)}}case"sync_read_file":{let n=e.placeId,i=e.instancePath;return n==null||!i?{success:!1,error:"placeId and instancePath are required"}:{success:!0,data:await r.readSyncedFile(n,i)}}case"sync_write_file":{let n=e.placeId,i=e.instancePath,a=e.content;return n==null||!i||a===void 0?{success:!1,error:"placeId, instancePath, and content are required"}:(await r.writeSyncedFile(n,i,a),{success:!0,data:{written:!0,placeId:n,instancePath:i}})}default:return{success:!1,error:`Unknown sync action: ${t}`}}}catch(n){let i=n instanceof Error?n.message:"Unknown error";return v.error("Sync action failed",{action:t,error:i}),{success:!1,error:i}}}async function m_(t,e,r){if(!r)return{success:!1,error:"Sync controller not available for disk operations."};try{return{success:!0,data:await r.executeViaDisk(t,e)}}catch(n){let i=n instanceof Error?n.message:"Unknown error";return v.error("Disk action failed",{action:t,error:i}),{success:!1,error:i}}}var qd=100;function YR(t,e,r){v.info("Plugin connected via SSE"),r.setHeader("Content-Type","text/event-stream"),r.setHeader("Cache-Control","no-cache"),r.setHeader("Connection","keep-alive"),t.sseClients.add(r),h_(r,{event:"command",id:Ec(),data:{action:"connected",requestId:Ec(),params:{serverVersion:Xe,timestamp:Date.now()}}});let n=setInterval(()=>{h_(r,{event:"command",id:Ec(),data:{action:"keepalive",requestId:Ec(),params:{timestamp:Date.now()}}})},3e4);r.on("close",()=>{v.info("Plugin disconnected from SSE"),clearInterval(n),t.sseClients.delete(r)})}function h_(t,e){let r=JSON.stringify(e.data);t.write(`event: ${e.event}
117
+ `),this.clearTTLTimerForPlace(i,a),i.index.clearAllHashes(),i.index.clearClassMappings(),i.tmpIndex){let m=i.tmpIndex.getExplorerRoot(),h=i.index.getExplorerRoot();for(let[g,b]of i.tmpIndex.getAllHashes()){let S=ke.relative(m,g),_=ke.resolve(h,S);i.index.updateHashByValue(_,b)}for(let[g,b]of i.tmpIndex.getAllFileHashes()){let S=ke.relative(m,g),_=ke.resolve(h,S);i.index.updateFileHashByValue(_,b)}i.index.resetNameCounters(),i.index.mergeNameMappingsFrom(i.tmpIndex)}if(i.tmpIndex=null,i.tmpWriter&&(i.tmpWriter.stopChangeLogFlusher(),i.tmpWriter=null),this.pendingServiceTrees.delete(a),i.collisionDirMap=null,await i.index.saveToDisk(),i.state="syncing",i.activeFullSyncSessionId=null,i.syncProgress=null,this.ctx.touchRuntimePlace(e),this.ctx.activeFullSyncPlaceId=null,await this.ctx.startFileWatcherForPlace(i),i.fileWatcher&&await i.fileWatcher.waitUntilReady(),u.size>0){for(let[m,h]of u){let g=ke.resolve(o,m);try{await qt.mkdir(ke.dirname(g),{recursive:!0}),await qt.writeFile(g,h,"utf-8")}catch(b){v.warn("Failed to restore preserved file",{path:m,error:b instanceof Error?b.message:String(b)})}}v.info("Restored preserved local files",{placeId:e,count:u.size})}if(p.length>0){let m=0;for(let h of p){let g=ke.resolve(o,h);try{await qt.unlink(g),i.index.removeHash(g),m++}catch(b){b.code!=="ENOENT"&&v.warn("Failed to delete preserved-as-deleted file",{path:h,error:b instanceof Error?b.message:String(b)})}}m>0&&(await i.index.saveToDisk(),v.info("Deleted locally-removed files from new sync",{placeId:e,count:m}))}i.writer.appendChangeLog(`FULL_SYNC_COMPLETE instances=${i.instanceCount} scripts=${i.scriptCount}`),i.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncComplete",direction:"forward",path:`place_${e}`,details:`instances:${i.instanceCount} scripts:${i.scriptCount}`}),v.info("Full sync completed",{placeId:e,instanceCount:i.instanceCount,scriptCount:i.scriptCount}),n.status(200).json({status:"completed",instanceCount:i.instanceCount,scriptCount:i.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}setPreserveLocalFiles(e,r){this.preserveLocalFilesMap.set(e,r)}getAndClearPreserveLocalFiles(e){let r=this.preserveLocalFilesMap.get(e)||[];return this.preserveLocalFilesMap.delete(e),r}clearPreserveLocalFiles(e){this.preserveLocalFilesMap.delete(e)}clearPendingServiceTrees(e){this.pendingServiceTrees.delete(e)}startTTLTimerForPlace(e,r){let n=setTimeout(async()=>{v.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:r});let i=this.ctx.config.getPlaceRoot(e.placeId),a=ke.join(i,`explorer_tmp_${r}`);try{await qt.rm(a,{recursive:!0,force:!0})}catch(o){v.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===r&&(e.activeFullSyncSessionId=null,e.activeClientId=null,e.state="idle",e.tmpIndex=null,this.pendingServiceTrees.delete(r),e.collisionDirMap=null,e.tmpWriter&&(e.tmpWriter.stopChangeLogFlusher(),e.tmpWriter=null),this.ctx.activeFullSyncPlaceId===e.placeId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(e.placeId))},ER);n&&typeof n=="object"&&"unref"in n&&n.unref(),e.incompleteSyncTimer=n}getOrCreatePendingServiceTree(e,r){let n=this.pendingServiceTrees.get(e);n||(n=new Map,this.pendingServiceTrees.set(e,n));let i=n.get(r.serviceName);if(i)return i;let a={serviceName:r.tree?.name??r.serviceName,serviceClassName:r.tree?.className??r.serviceClassName,zeroBasedChunkIndex:r.chunkIndex===0,instances:[]};return n.set(r.serviceName,a),a}isLastChunk(e,r,n){return n<=1?!0:e.zeroBasedChunkIndex?r>=n-1:r>=n}buildServiceTree(e,r){let n={name:r.serviceName,className:r.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let i of r.instances){let a=this.resolveEffectiveSegments(e,i.effectivePath),o=ut(i.originalPath);this.upsertTreeNode(n,a,o,i.className)}return this.recomputeTreeChildCounts(n),n.syncedAt=new Date().toISOString(),n}resolveEffectiveSegments(e,r){return e.tmpIndex?ut(r).map(n=>e.tmpIndex.sanitizeName(n)):ut(r)}rewritePendingEffectivePaths(e,r,n,i){let a=ke.relative(r,n).split(ke.sep).filter(l=>l.length>0),o=ke.relative(r,i).split(ke.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=We(["game",...a]),c=We(["game",...o]);for(let l of e.instances){if(l.effectivePath===s){l.effectivePath=c;continue}(l.effectivePath.startsWith(`${s}.`)||l.effectivePath.startsWith(`${s}[`))&&(l.effectivePath=`${c}${l.effectivePath.slice(s.length)}`)}}upsertTreeNode(e,r,n,i){if(r.length<=1)return;let a=e.children;for(let o=1;o<r.length;o++){let s=r[o],c=n[o],l=o===r.length-1,u=a.find(p=>p.name===s);u?l&&(u.className=i,c!==void 0&&c!==s&&(u.originalName=c)):(u={name:s,className:l?i:"Folder",childCount:0,children:[]},c!==void 0&&c!==s&&(u.originalName=c),a.push(u)),u.children||(u.children=[]),a=u.children}}recomputeTreeChildCounts(e){let r=n=>{let i=n.children??[];n.children=i;for(let a of i)r(a);n.childCount=i.length};for(let n of e.children)r(n);e.childCount=e.children.length}clearTTLTimerForPlace(e,r){e.incompleteSyncTimer&&e.activeFullSyncSessionId===r&&(clearTimeout(e.incompleteSyncTimer),e.incompleteSyncTimer=null)}async cleanupStaleTempDirs(){let e=this.ctx.config.getSyncRoot();try{let r=await qt.readdir(e,{withFileTypes:!0});for(let n of r)if(n.isDirectory()){if(n.name.startsWith("explorer_tmp_")){let i=ke.join(e,n.name);v.warn("Removing stale temp directory from crashed sync",{dir:n.name});try{await qt.rm(i,{recursive:!0,force:!0})}catch(a){v.error(`Failed to remove stale temp dir: ${n.name}`,a instanceof Error?a:new Error(String(a)))}}if(n.name.startsWith("place_")){let i=ke.join(e,n.name);try{let a=await qt.readdir(i,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=ke.join(i,o.name);v.warn("Removing stale temp directory from crashed sync",{dir:`${n.name}/${o.name}`});try{await qt.rm(s,{recursive:!0,force:!0})}catch(c){v.error(`Failed to remove stale temp dir: ${n.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(r){if(r.code==="ENOENT")return;v.warn("Failed to scan for stale temp dirs",{error:r instanceof Error?r.message:String(r)})}}};import Rr from"path";import{promises as DR}from"fs";var Md=class{constructor(e){this.ctx=e}async handleReversePending(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}let a={pending:i.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:i.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:i.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(n),r.status(200).json(a)}async handleReverseSyncChanges(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({changes:[],count:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(i.state!=="syncing"){r.status(400).json({error:"Not syncing",message:"Reverse sync is only available when sync is active"});return}let a=i.fileWatcher?.drainPendingChanges()??[],o=a.length>0?await i.reader.buildChangesFromPending(a):[];this.ctx.touchRuntimePlace(n),r.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,r){let n=e.body,i=n.placeId??this.ctx.getDefaultRuntimePlaceId();if(i==null){r.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=n.appliedFiles??n.appliedPaths;if(!a||!Array.isArray(a)){r.status(400).json({error:"Validation error",message:"appliedFiles (or appliedPaths) must be an array of relative file paths"});return}let o=this.ctx.places.get(i);if(!o){r.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}this.ctx.touchRuntimePlace(i);let s=this.ctx.config.getPlaceRoot(i),c=0,l=[];for(let u of a){let p=Rr.resolve(s,u);if(!Dd(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await DR.readFile(p,"utf-8"),f=o.index.computeHash(d);o.index.updateHashByValue(p,f),o.index.updateFileHashByValue(p,f),c++}catch(d){let f=d.code;if(f==="ENOENT"){o.index.removeHash(p),o.index.removeHashesUnder(p);let m=this.resolveInstancePathForAppliedPath(o.index,p);if(m){let h=_t(m),g=wt(m);g&&h&&await o.writer.removeFromTree(g,h)}c++}else f==="EISDIR"?c++:l.push({path:u,error:d instanceof Error?d.message:String(d)})}}c>0&&await o.index.saveToDisk(),c>0&&o.writer.appendHistory({timestamp:new Date().toISOString(),type:"reverseApply",direction:"reverse",path:`place_${i}`,details:`applied:${c} failed:${l.length}`}),r.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,r){let n=e.body;if(!n.fsPath||!n.resolution){r.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:i,resolution:a}=n,o=this.ctx.config.getSyncRoot();if(!Dd(o,Rr.resolve(o,i))){r.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){r.status(200).json({status:"skipped",fsPath:i});return}let s;if(n.placeId&&(s=this.ctx.places.get(n.placeId)),!s){let d=i.match(/^place_(\d+)(?:_[^/]+)?\//);if(d){let f=parseInt(d[1],10);s=this.ctx.places.get(f)}else s=Array.from(this.ctx.places.values())[0]}if(!s){r.status(404).json({error:"No active place context",message:"No sync session is active. Start a sync first."});return}let c=this.ctx.config.getPlaceRoot(s.placeId),l=Rr.resolve(c,i);if(!Dd(c,l)){r.status(403).json({error:"Forbidden",message:"Path is outside the place root"});return}let u=s.index,p=s.reader;if(a==="apply-studio"){u.resolveFile(l,"apply-studio"),await u.saveToDisk(),r.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:i});return}if(a==="apply-file"){let d=await DR.readFile(l,"utf-8"),f=u.computeHash(d);u.resolveFile(l,"apply-file",f),await u.saveToDisk();let m=p.getFileType(l),h=p.resolveInstancePathFromFile(l);r.status(200).json({status:"resolved",resolution:"apply-file",fsPath:i,instancePath:h,fileType:m,content:d});return}r.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({added:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(!i.fileWatcher){r.status(200).json({added:0});return}let a=await i.fileWatcher.rescan();this.ctx.touchRuntimePlace(n),v.info("Reverse rescan completed",{placeId:n,added:a}),r.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,r){let n=e.resolveInstancePathFromFsPath(r);if(n)return n;let i=e.getExplorerRoot(),a=Rr.relative(i,r);if(a.startsWith("..")||a===""||Rr.isAbsolute(a))return null;let o=a.split(Rr.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=Rr.basename(r),c=Rr.dirname(r),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Ua.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=i;for(let f of o){d=Rr.join(d,f);let m=Rr.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return We(p)}};function OR(t){if(!t||typeof t!="object")return;let e=t;if(Array.isArray(e.instances))for(let r of e.instances)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={});if(Array.isArray(e.changes))for(let r of e.changes)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={})}var Ud=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;constructor(e){this.config=new yd(e),this.apiHandler=new Nd(this),this.changeProcessor=new Od(this),this.initHandler=new Ad(this),this.reverseHandler=new Md(this),this.places=new vd({max:3,dispose:(r,n)=>{v.info("Disposing place context (LRU eviction)",{placeId:n}),this.activeFullSyncPlaceId===n&&(this.activeFullSyncPlaceId=null),this.activeRuntimeSyncPlaceId===n&&(this.activeRuntimeSyncPlaceId=null),r.fileWatcher&&(r.fileWatcher.stop().catch(i=>{v.error("Error stopping file watcher during dispose",i)}),r.fileWatcher=null),r.writer.stopChangeLogFlusher(),r.incompleteSyncTimer&&(clearTimeout(r.incompleteSyncTimer),r.incompleteSyncTimer=null),r.index.saveToDisk().catch(i=>{v.error("Error saving index during dispose",i)}),r.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(r.activeFullSyncSessionId),r.tmpWriter&&(r.tmpWriter.stopChangeLogFlusher(),r.tmpWriter=null),r.tmpIndex=null,r.collisionDirMap=null}})}async getOrCreatePlaceContext(e,r){let n=this.places.get(e);if(n&&r){let i=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,r);a!==i&&(v.info("Place root migrated, recreating context",{placeId:e,from:i,to:a}),this.places.delete(e),n=void 0)}if(!n){let i=await this.config.resolvePlaceRoot(e,r),a=f_.join(i,"explorer");await Ka.mkdir(i,{recursive:!0}),await Ka.mkdir(a,{recursive:!0});let o=new jn(i,a);await o.loadFromDisk();let s=new Za(this.config,o,e),c=new _d(this.config,o,i);s.startChangeLogFlusher(),n={placeId:e,placeName:"",index:o,writer:s,reader:c,fileWatcher:null,state:"idle",activeClientId:null,activeFullSyncSessionId:null,instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,tmpIndex:null,tmpWriter:null,incompleteSyncTimer:null,changesSinceLastSave:0,directions:{...vi},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,n),v.info("Created new place context",{placeId:e,placeRoot:i})}return n}async handleSyncInit(e,r){try{OR(e.body);let n=CR(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.phase==="start"?i.placeId??null:i.phase==="chunk"||i.phase==="complete"?this.activeFullSyncPlaceId:null;if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in start phase or must be set by previous start"});return}switch(i.phase){case"start":await this.initHandler.handleInitStart(a,i,r);break;case"chunk":await this.initHandler.handleInitChunk(a,i,r);break;case"complete":await this.initHandler.handleInitComplete(a,i,r);break}}catch(n){this.sendError(r,n,"handleSyncInit")}}async handleSyncUpdate(e,r){try{OR(e.body);let n=zR(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.placeId??this.getDefaultRuntimePlaceId();if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in body or must have an active sync session"});return}let o=await this.getOrCreatePlaceContext(a);if(o.activeFullSyncSessionId!==null||o.state==="initializing"){r.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=i.clientId,this.touchRuntimePlace(a);let s=PR(o,i.changes);v.info("Sync update received",{placeId:a,clientId:i.clientId,changeCount:s.length,receivedCount:i.changes.length,types:s.map(f=>f.type)});let c=[],l=[],u=0,p=new Map;for(let f of s)try{let m=await this.changeProcessor.processChangeForPlace(o,f,p);m?l.push(m):u++}catch(m){let h="path"in f?f.path:"oldPath"in f?f.oldPath:"unknown";c.push({path:h,error:m instanceof Error?m.message:String(m)})}for(let f of p.values())try{await zd(o,f)}catch(m){c.push({path:f.instancePath,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=TR&&(await o.index.saveToDisk(),o.changesSinceLastSave=0),o.lastIncrementalSync=new Date().toISOString();let d={processed:u,failed:c.length,errors:c,syncedAt:o.lastIncrementalSync};l.length>0&&(d.conflicts=l),r.status(200).json(d)}catch(n){this.sendError(r,n,"handleSyncUpdate")}}getDefaultRuntimePlaceId(){if(this.activeRuntimeSyncPlaceId!==null&&this.activeRuntimeSyncPlaceId!==void 0){if(this.places.has(this.activeRuntimeSyncPlaceId))return this.activeRuntimeSyncPlaceId;this.activeRuntimeSyncPlaceId=null}if(this.activeFullSyncPlaceId!==null&&this.activeFullSyncPlaceId!==void 0&&this.places.has(this.activeFullSyncPlaceId))return this.activeRuntimeSyncPlaceId=this.activeFullSyncPlaceId,this.activeRuntimeSyncPlaceId;for(let[e,r]of this.places.entries())if(r.state==="syncing"||r.state==="initializing")return this.activeRuntimeSyncPlaceId=e,e;return this.activeRuntimeSyncPlaceId=null,null}touchRuntimePlace(e){this.activeRuntimeSyncPlaceId=e}clearRuntimePlaceIfMatch(e){this.activeRuntimeSyncPlaceId===e&&(this.activeRuntimeSyncPlaceId=null)}resolveQueryPlaceId(e,r="runtime"){let n=e.query.placeId;if(n){let i=parseInt(n,10);if(!isNaN(i))return i}return r==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,r){try{let n=this.resolveQueryPlaceId(e);if(n==null){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let i=this.places.get(n);if(!i){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getPlaceRoot(n),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let a=i.fileWatcher?.getPendingCount()??0;this.touchRuntimePlace(n);let o={state:i.state,instanceCount:i.instanceCount,scriptCount:i.scriptCount,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(n),activeClientId:i.activeClientId,reverseSyncAvailable:a>0,modifiedFileCount:a,directions:i.directions,fileWatcherActive:i.fileWatcher!==null,forwardOnlyClasses:[...La]};r.status(200).json(o)}catch(n){this.sendError(r,n,"handleSyncStatus")}}async handleSyncStop(e,r){try{let n=e.body,i=n.placeId??this.getDefaultRuntimePlaceId();if(i==null){r.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(i);if(!a){r.status(200).json({status:"idle",state:"idle",placeId:i,message:`No sync context for place ${i}`});return}if(n.clientId&&a.activeClientId&&n.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){r.status(409).json({error:"Conflict",message:`This sync session belongs to client ${a.activeClientId}`});return}let o=a.activeFullSyncSessionId;if(o&&(this.initHandler.clearPreserveLocalFiles(o),this.initHandler.clearPendingServiceTrees(o)),a.fileWatcher&&(await a.fileWatcher.stop(),a.fileWatcher=null),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let s=this.config.getPlaceRoot(i),c=f_.join(s,`explorer_tmp_${o}`);await Ka.rm(c,{recursive:!0,force:!0}).catch(()=>{})}a.tmpWriter&&(a.tmpWriter.stopChangeLogFlusher(),a.tmpWriter=null),a.tmpIndex=null,a.collisionDirMap=null,a.activeFullSyncSessionId=null,a.syncProgress=null,a.state="idle",a.activeClientId=null,a.instanceCount=0,a.scriptCount=0,this.activeFullSyncPlaceId===i&&(this.activeFullSyncPlaceId=null),this.clearRuntimePlaceIfMatch(i),v.info("Sync stopped",{placeId:i,reason:n.reason??"requested"}),r.status(200).json({status:"stopped",state:"idle",placeId:i})}catch(n){this.sendError(r,n,"handleSyncStop")}}async handleSyncConfig(e,r){try{if(e.method==="GET"){r.status(200).json(this.config.getConfig());return}let n=RR(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data;i.propertyMode!==void 0&&this.config.setPropertyMode(i.propertyMode),i.maxDepth!==void 0&&this.config.updateConfig({maxDepth:i.maxDepth}),i.maxInstances!==void 0&&this.config.updateConfig({maxInstances:i.maxInstances}),r.status(200).json({status:"updated",config:this.config.getConfig()})}catch(n){this.sendError(r,n,"handleSyncConfig")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),v.info("SyncController initialized")}catch(e){v.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{this.places.clear(),v.info("SyncController shut down")}catch(e){v.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,r){let n=e+".tmp."+gJ().slice(0,8);try{await Ka.writeFile(n,r,"utf-8"),await Ka.rename(n,e)}catch(i){throw await Ka.unlink(n).catch(()=>{}),i}}async handlePreCheck(e,r){try{await this.apiHandler.handlePreCheck(e,r)}catch(n){this.sendError(r,n,"handlePreCheck")}}async handleSyncDirections(e,r){try{await this.apiHandler.handleSyncDirections(e,r)}catch(n){this.sendError(r,n,"handleSyncDirections")}}async handleForwardRestoreList(e,r){try{await this.apiHandler.handleForwardRestoreList(e,r)}catch(n){this.sendError(r,n,"handleForwardRestoreList")}}async handleReversePending(e,r){try{await this.reverseHandler.handleReversePending(e,r)}catch(n){this.sendError(r,n,"handleReversePending")}}async handleReverseSyncChanges(e,r){try{await this.reverseHandler.handleReverseSyncChanges(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,r){try{await this.reverseHandler.handleReverseSyncResult(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncResult")}}async handleResolveConflict(e,r){try{await this.reverseHandler.handleResolveConflict(e,r)}catch(n){this.sendError(r,n,"handleResolveConflict")}}async handleReverseRescan(e,r){try{await this.reverseHandler.handleReverseRescan(e,r)}catch(n){this.sendError(r,n,"handleReverseRescan")}}async handleSyncHistory(e,r){try{await this.apiHandler.handleSyncHistory(e,r)}catch(n){this.sendError(r,n,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){v.debug("Skipping file watcher start - place not syncing",{placeId:e.placeId,state:e.state});return}e.fileWatcher&&await e.fileWatcher.stop();let r=f_.join(this.config.getPlaceRoot(e.placeId),"explorer");e.fileWatcher=new Cd(r,e.index),e.writer.setOnWriteCallback(n=>{e.fileWatcher?.suppressPath(n)}),e.fileWatcher.setDirectionChecker(n=>{let i=Xz(n);return e.directions[i]}),e.fileWatcher.setOnForwardViolation(n=>{e.forwardRestoreQueue.includes(n)||(e.forwardRestoreQueue.push(n),v.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:n,queueSize:e.forwardRestoreQueue.length}))}),await e.fileWatcher.start(),v.info("File watcher started for reverse sync",{placeId:e.placeId})}getStatusSummary(){return this.apiHandler.getStatusSummary()}getDirectionForCategory(e){return this.apiHandler.getDirectionForCategory(e)}getStatusDirect(e){return this.apiHandler.getStatusDirect(e)}getConfigDirect(){return this.apiHandler.getConfigDirect()}async getHistoryDirect(e,r){return this.apiHandler.getHistoryDirect(e,r)}getDirectionsDirect(e){return this.apiHandler.getDirectionsDirect(e)}getProgressDirect(e){return this.apiHandler.getProgressDirect(e)}async readSyncedFile(e,r){return this.apiHandler.readSyncedFile(e,r)}async writeSyncedFile(e,r,n){await this.apiHandler.writeSyncedFile(e,r,n)}async executeViaDisk(e,r){return this.apiHandler.executeViaDisk(e,r)}sendError(e,r,n){let i=r instanceof Error?r.message:String(r);if(i.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:i});return}let a=r.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:i});return}v.error(`SyncController.${n} failed`,r instanceof Error?r:new Error(i)),e.status(500).json({error:"Internal error",message:`${n}: ${i}`})}};import{randomUUID as vJ}from"crypto";function NR(t,e){let r=vJ(),n=r.replace(/-/g,"").substring(0,8).toUpperCase(),i=`${n.substring(0,4)}-${n.substring(4,8)}`;return{config:t,app:e,instanceId:r,sessionId:i,startTime:Date.now(),baseUrl:`http://${t.httpHost}:${t.httpPort}`,commandQueue:new Map,pendingCommands:new Map,globalPendingCommands:[],totalCommandsProcessed:0,pluginClients:new Map,mcpInstances:new Map,sseClients:new Set,cachedSelectionMap:new Map,isClientMode:!1,clientModeHealthTimer:null,clientModeConsecutiveHealthFailures:0,clientModeUpstreamReachable:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,historyManager:null,analyticsManager:null,licenseState:null,syncController:null,aiClientName:"",syncedSessionToken:null}}var AR=to(fd(),1);function MR(t){let e=AR.default.json();t.app.use((r,n,i)=>{if(r.path.startsWith("/sync/")){i();return}e(r,n,i)}),t.app.use((r,n,i)=>{n.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),n.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),n.setHeader("Access-Control-Allow-Headers","Content-Type"),i()}),t.app.use((r,n,i)=>{v.debug(`${r.method} ${r.path}`,{ip:r.ip}),i()})}import{randomUUID as Ec}from"crypto";var Xe="1.4.2";function UR(t){let e=new Set;t.aiClientName&&e.add(t.aiClientName);for(let r of t.mcpInstances.values())r.aiClientName&&e.add(r.aiClientName);return Array.from(e)}function yJ(t){let r=Date.now();return Array.from(t.pluginClients.values()).filter(n=>r-n.lastSeen<1e4)}function FR(t){let r=Date.now();for(let[n,i]of t.mcpInstances)i.lastSeen&&r-i.lastSeen>15e3&&(t.mcpInstances.delete(n),v.debug("Removed stale MCP instance",{instanceId:n,lastSeen:i.lastSeen}))}function LR(t,e,r){let n=e.query.clientId;if(n&&t.pluginClients.has(n)){let i=t.pluginClients.get(n);i.lastSeen=Date.now()}try{let i=e.body;if(!i||!Array.isArray(i.selection)||typeof i.count!="number"){v.warn("Invalid selection update request",{body:i}),r.status(400).json({error:"Invalid request body"});return}let a=n||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:i.selection,count:i.count,timestamp:o,clientId:a}),v.debug("Selection cache updated",{count:i.count,clientId:a,timestamp:o}),r.json({status:"ok",timestamp:o})}catch(i){v.error("Error handling selection update",i),r.status(500).json({error:"Internal server error"})}}function qR(t,e,r){let n=parseInt(e.query.maxAge)||3e4,i=Pc(t,n);i?r.json({cached:!0,...i}):r.json({cached:!1,message:"No cached selection available"})}function Pc(t,e=3e4,r){if(t.cachedSelectionMap.size===0)return null;let n;if(r)n=t.cachedSelectionMap.get(r);else for(let a of t.cachedSelectionMap.values())(!n||a.timestamp>n.timestamp)&&(n=a);if(!n)return null;let i=Date.now()-n.timestamp;return e===0||i<=e?n:null}function ZR(t,e,r){try{let n=e.body;if(!n.clientId){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.get(n.clientId),a=Date.now(),o={clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,placeId:n.placeId,connectedAt:i?.connectedAt||a,lastSeen:a,commandsProcessed:i?.commandsProcessed||0,connectionType:"polling"};t.pluginClients.set(n.clientId,o),t.pendingCommands.has(n.clientId)||t.pendingCommands.set(n.clientId,[]),t.analyticsManager&&(n.pluginVersion&&t.analyticsManager.setPluginVersion(n.pluginVersion),t.analyticsManager.trackPluginConnected()),v.info("Plugin client registered",{clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,isReconnect:!!i}),t.licenseState&&t.licenseState.triggerImmediateRefreshOnConnect(n.clientId),r.json({status:"ok",clientId:n.clientId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,connectedAt:o.connectedAt,aiClientNames:UR(t),serverStartTime:t.startTime,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){v.error("Error registering plugin client",n),r.status(500).json({error:"Internal server error"})}}function HR(t,e,r){let n=e.body?.clientId;if(!n){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.has(n);t.pluginClients.delete(n),t.pendingCommands.delete(n),v.info("Plugin client unregistered",{clientId:n,existed:i}),r.json({status:"ok",existed:i})}function BR(t,e,r){try{let n=e.body;if(!n.instanceId){r.status(400).json({error:"Missing instanceId"});return}let i=Date.now(),a={instanceId:n.instanceId,pid:n.pid,connectedAt:i,isServer:!1,lastSeen:i};n.aiClientName&&(a.aiClientName=n.aiClientName),n.cwd&&(a.cwd=n.cwd),t.mcpInstances.set(n.instanceId,a),v.info("MCP instance registered (client mode)",{instanceId:n.instanceId,pid:n.pid,cwd:n.cwd}),r.json({status:"ok",instanceId:n.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){v.error("Error registering MCP instance",n),r.status(500).json({error:"Internal server error"})}}function VR(t,e,r){let n=e.body?.instanceId;if(!n){r.status(400).json({error:"Missing instanceId"});return}let i=t.mcpInstances.has(n);t.mcpInstances.delete(n),v.info("MCP instance unregistered",{instanceId:n,existed:i}),r.json({status:"ok",existed:i})}function WR(t,e,r){let{instanceId:n,aiClientName:i}=e.body;if(n&&i){let a=t.mcpInstances.get(n);a&&(a.aiClientName=i)}r.json({status:"ok"})}function JR(t){let r=Date.now();for(let[n,i]of t.pluginClients)r-i.lastSeen>3e4&&(t.pluginClients.delete(n),t.pendingCommands.delete(n));return FR(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,uptime:r-t.startTime,serverStartTime:t.startTime,serverExecutable:process.execPath,serverHost:t.config.httpHost,serverPort:t.config.httpPort,serverPid:process.pid,mcpInstances:[{instanceId:t.instanceId,pid:process.pid,connectedAt:t.startTime,isServer:!0,cwd:process.cwd()},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function GR(t,e){e.json(JR(t))}function KR(t,e,r){let n=e.query.instanceId;n&&t.mcpInstances.has(n)&&(t.mcpInstances.get(n).lastSeen=Date.now()),FR(t);let i=yJ(t),a=t.sseClients.size,c={...{status:"online",connectedClients:a+i.length,queuedCommands:t.commandQueue.size,uptime:Date.now()-t.startTime,version:Xe,isClientMode:t.isClientMode,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:UR(t),sseClients:a,pollingClients:i.length,pluginClients:i.map(l=>({clientId:l.clientId,projectName:l.projectName,lastSeen:Date.now()-l.lastSeen})),...t.isClientMode?{upstream:{reachable:t.clientModeUpstreamReachable,consecutiveFailures:t.clientModeConsecutiveHealthFailures,lastSuccessAt:t.clientModeLastHealthSuccessAt,lastFailureAt:t.clientModeLastHealthFailureAt,lastError:t.clientModeLastHealthError,baseUrl:t.baseUrl}}:{}};r.json(c)}function XR(t,e,r,n){let i=e.ip||e.socket.remoteAddress||"";if(!(i==="127.0.0.1"||i==="::1"||i==="::ffff:127.0.0.1"||i==="localhost")){v.warn("Shutdown request rejected from non-localhost",{ip:i}),r.status(403).json({error:"Forbidden: localhost only"});return}v.info("Shutdown request received, initiating graceful shutdown",{requestedBy:i,uptime:Date.now()-t.startTime}),r.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await n(),v.info("Graceful shutdown completed"),process.exit(0)}catch(o){v.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function Fd(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){v.warn("Failed to fetch connection info from server",{error:e})}return JR(t)}async function Ld(t,e,r){if(!r)return{success:!1,error:"Sync controller not available. Project sync is not initialized."};try{switch(t){case"sync_status":{let n=e.placeId;return{success:!0,data:r.getStatusDirect(n)}}case"sync_config":return{success:!0,data:r.getConfigDirect()};case"sync_history":{let n=e.placeId,i=e.query;return n==null?{success:!1,error:"placeId is required for sync_history"}:{success:!0,data:await r.getHistoryDirect(n,i)}}case"sync_directions":{let n=e.placeId;return{success:!0,data:r.getDirectionsDirect(n)}}case"sync_progress":{let n=e.placeId;return{success:!0,data:r.getProgressDirect(n)}}case"sync_read_file":{let n=e.placeId,i=e.instancePath;return n==null||!i?{success:!1,error:"placeId and instancePath are required"}:{success:!0,data:await r.readSyncedFile(n,i)}}case"sync_write_file":{let n=e.placeId,i=e.instancePath,a=e.content;return n==null||!i||a===void 0?{success:!1,error:"placeId, instancePath, and content are required"}:(await r.writeSyncedFile(n,i,a),{success:!0,data:{written:!0,placeId:n,instancePath:i}})}default:return{success:!1,error:`Unknown sync action: ${t}`}}}catch(n){let i=n instanceof Error?n.message:"Unknown error";return v.error("Sync action failed",{action:t,error:i}),{success:!1,error:i}}}async function m_(t,e,r){if(!r)return{success:!1,error:"Sync controller not available for disk operations."};try{return{success:!0,data:await r.executeViaDisk(t,e)}}catch(n){let i=n instanceof Error?n.message:"Unknown error";return v.error("Disk action failed",{action:t,error:i}),{success:!1,error:i}}}var qd=100;function YR(t,e,r){v.info("Plugin connected via SSE"),r.setHeader("Content-Type","text/event-stream"),r.setHeader("Cache-Control","no-cache"),r.setHeader("Connection","keep-alive"),t.sseClients.add(r),h_(r,{event:"command",id:Ec(),data:{action:"connected",requestId:Ec(),params:{serverVersion:Xe,timestamp:Date.now()}}});let n=setInterval(()=>{h_(r,{event:"command",id:Ec(),data:{action:"keepalive",requestId:Ec(),params:{timestamp:Date.now()}}})},3e4);r.on("close",()=>{v.info("Plugin disconnected from SSE"),clearInterval(n),t.sseClients.delete(r)})}function h_(t,e){let r=JSON.stringify(e.data);t.write(`event: ${e.event}
118
118
  `),t.write(`id: ${e.id}
119
119
  `),t.write(`data: ${r}
120
120
 
@@ -126,7 +126,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
126
126
  `)}this.initialized=!0,v.info("Tool history manager initialized",{sessionId:e,localHistoryEnabled:this.config.enableLocalHistory,localStatisticsEnabled:this.config.enableLocalStatistics,dataDir:this.config.dataDir})}catch(r){throw v.error("Failed to initialize tool history manager",r),r}}async ensureDirectories(){await Dr.promises.mkdir(this.historyDir,{recursive:!0}),await Dr.promises.mkdir(this.statisticsDir,{recursive:!0})}async loadStatistics(){try{let e=await Dr.promises.readFile(this.statisticsFilePath,"utf-8");this.statistics=JSON.parse(e),v.debug("Loaded existing statistics",{totalCalls:this.statistics.totalCalls,totalSessions:this.statistics.totalSessions})}catch(e){e.code==="ENOENT"?(this.statistics=$_(),v.debug("No existing statistics file, starting fresh")):(v.error("Failed to load statistics, starting fresh",e),this.statistics=$_())}}async saveStatistics(){if(this.config.enableLocalStatistics){if(this.saveStatisticsDebounceTimer){this.pendingStatisticsSave=!0;return}this.statistics.lastUpdated=new Date().toISOString();try{await Dr.promises.writeFile(this.statisticsFilePath,JSON.stringify(this.statistics,null,2))}catch(e){v.error("Failed to save statistics",e)}this.saveStatisticsDebounceTimer=setTimeout(async()=>{this.saveStatisticsDebounceTimer=null,this.pendingStatisticsSave&&(this.pendingStatisticsSave=!1,await this.saveStatistics())},1e3)}}async recordSuccess(e,r,n,i,a){if(!this.initialized){v.warn("Tool history manager not initialized, skipping record");return}let o=Xa(e),s=new Date().toISOString();if(this.config.enableLocalStatistics&&(this.updateToolStats(e,o,!0,i,s),await this.saveStatistics()),this.config.enableLocalHistory&&this.historyFilePath){this.sequenceNumber++;let c={id:bj(),sessionId:this.sessionId,timestamp:s,sequenceNumber:this.sequenceNumber,toolName:e,tier:o,parameters:r,result:n,executionTimeMs:i,clientId:a};try{await Dr.promises.appendFile(this.historyFilePath,JSON.stringify(c)+`
127
127
  `)}catch(l){v.error("Failed to write history entry",l)}}}async recordFailure(e,r,n,i,a){if(!this.initialized){v.warn("Tool history manager not initialized, skipping record");return}let o=Xa(e),s=new Date().toISOString();if(this.config.enableLocalStatistics){this.updateToolStats(e,o,!1,i||0,s),await this.saveStatistics();let c={id:bj(),sessionId:this.sessionId,timestamp:s,toolName:e,tier:o,parameters:r,errorMessage:n,errorCode:a,executionTimeMs:i};try{await Dr.promises.appendFile(this.failureLogPath,JSON.stringify(c)+`
128
128
  `)}catch(l){v.error("Failed to write failure log",l)}}}updateToolStats(e,r,n,i,a){this.statistics.totalCalls++,n?this.statistics.totalSuccessCalls++:this.statistics.totalFailedCalls++,this.statistics.tierSummary[r].totalCalls++,n?this.statistics.tierSummary[r].successCount++:this.statistics.tierSummary[r].failureCount++,this.statistics.tools[e]||(this.statistics.tools[e]={toolName:e,tier:r,totalCalls:0,successCount:0,failureCount:0,totalExecutionTimeMs:0,avgExecutionTimeMs:0,firstUsed:a,lastUsed:a});let o=this.statistics.tools[e];o.totalCalls++,o.totalExecutionTimeMs+=i,o.avgExecutionTimeMs=Math.round(o.totalExecutionTimeMs/o.totalCalls),o.lastUsed=a,n?o.successCount++:o.failureCount++}getStatistics(){return{...this.statistics}}getTopTools(e=10){return Object.values(this.statistics.tools).sort((r,n)=>n.totalCalls-r.totalCalls).slice(0,e)}async shutdown(){if(this.initialized){if(this.saveStatisticsDebounceTimer&&(clearTimeout(this.saveStatisticsDebounceTimer),this.saveStatisticsDebounceTimer=null),this.config.enableLocalStatistics){this.statistics.lastUpdated=new Date().toISOString();try{await Dr.promises.writeFile(this.statisticsFilePath,JSON.stringify(this.statistics,null,2))}catch(e){v.error("Failed to save final statistics",e)}}if(this.config.enableLocalHistory&&this.historyFilePath){let e={sessionId:this.sessionId,startTime:"",endTime:new Date().toISOString(),totalCommands:this.sequenceNumber,pid:process.pid,version:Xe};try{await Dr.promises.appendFile(this.historyFilePath,`# SESSION_END ${JSON.stringify(e)}
129
- `)}catch(r){v.error("Failed to write session end marker",r)}}this.initialized=!1,v.info("Tool history manager shutdown",{sessionId:this.sessionId,totalCommands:this.sequenceNumber})}}isLocalHistoryEnabled(){return this.config.enableLocalHistory}isLocalStatisticsEnabled(){return this.config.enableLocalStatistics}getHistoryDir(){return this.historyDir}getStatisticsDir(){return this.statisticsDir}};import{createHash as UJ,randomUUID as FJ}from"crypto";import*as _i from"fs";import*as Gd from"path";import*as wj from"os";var I_={"claude-code":"Claude Code",claude:"Claude",cursor:"Cursor","codex-cli":"Codex CLI","openai-codex":"Codex CLI","gemini-cli":"Gemini CLI",windsurf:"Windsurf",cline:"Cline",continue:"Continue",zed:"Zed",vscode:"VS Code"};function _j(t){let e=t.toLowerCase();if(I_[e])return I_[e];for(let[r,n]of Object.entries(I_))if(e.includes(r))return n;return t}function Jd(){return process.env.CLAUDECODE||process.env.CLAUDE_CODE_ENTRYPOINT?{name:"Claude Code",version:""}:process.env.CURSOR_EDITOR?{name:"Cursor",version:""}:process.env.WINDSURF_EDITOR?{name:"Windsurf",version:""}:process.env.CONTINUE_GLOBAL_DIR?{name:"Continue",version:""}:process.env.CLINE_CONTEXT?{name:"Cline",version:""}:null}var LJ="https://www.google-analytics.com/mp/collect",qJ="G-87FMYC2KLT",ZJ="ANCUVw5rTZ6ZRMOBd_RLMw",HJ=6e4,BJ=20,VJ=25,WJ=5e3,JJ=".weppy-roblox-mcp",GJ="device-id.json",Kd=class{enabled;clientId;deviceKey;sessionId="";gaSessionId=0;sessionStartTime=0;lastEngagementAt=0;toolCallsTotal=0;toolCallsSuccess=0;toolCallsFailed=0;eventQueue=[];flushTimer=null;aiClient="unknown";pluginVersion="";tier="basic";userPropertiesDirty=!0;constructor(e){this.enabled=this.resolveEnabled(e.enabled),this.clientId=this.loadOrCreateDeviceId(),this.deviceKey=this.createDeviceKey(this.clientId),this.enabled||v.debug("Telemetry disabled")}getAnalyticsDirPath(){return Gd.join(wj.homedir(),JJ)}getDeviceIdPath(){return Gd.join(this.getAnalyticsDirPath(),GJ)}loadOrCreateDeviceId(){let e=this.getDeviceIdPath();try{let n=_i.readFileSync(e,"utf-8"),i=JSON.parse(n);if(typeof i.deviceId=="string"&&i.deviceId.length>0)return i.deviceId}catch{}let r=FJ();try{_i.mkdirSync(this.getAnalyticsDirPath(),{recursive:!0}),_i.writeFileSync(e,JSON.stringify({deviceId:r},null,2),"utf-8")}catch(n){v.warn("Failed to persist telemetry device ID",{error:n})}return r}createDeviceKey(e){return UJ("sha256").update(e).digest("hex").slice(0,32)}resolveEnabled(e){let r=process.env.ENABLE_TELEMETRY;if(r!==void 0&&r!=="")return r.toLowerCase()==="true"||r==="1";try{let n=Gd.join(this.getAnalyticsDirPath(),"analytics.json"),i=_i.readFileSync(n,"utf-8"),a=JSON.parse(i);if(typeof a.enabled=="boolean")return a.enabled}catch{}return e??!1}initialize(e){if(!this.enabled)return;let r=Date.now();this.sessionId=e,this.sessionStartTime=r,this.gaSessionId=Math.floor(r/1e3),this.lastEngagementAt=r;let n=Jd();n&&(this.aiClient=n.name),this.flushTimer=setInterval(()=>this.flush(),HJ),this.flushTimer.unref(),v.debug("Telemetry initialized",{sessionId:e})}getCommonEventParams(){let e=Date.now(),r=this.getEngagementTimeMsec(e);return{device_key:this.deviceKey,mcp_version:Xe,plugin_version:this.pluginVersion||"unknown",platform:process.platform,user_tier:this.tier,ai_client:this.aiClient,session_id:this.gaSessionId,engagement_time_msec:r,timestamp_micros:e*1e3,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"unknown",locale:Intl.DateTimeFormat().resolvedOptions().locale||"unknown"}}getEngagementTimeMsec(e){if(this.lastEngagementAt<=0)return this.lastEngagementAt=e,100;let r=Math.max(100,e-this.lastEngagementAt);return this.lastEngagementAt=e,r}trackSessionStart(){this.enabled&&this.enqueue({name:"mcp_session_start",params:{...this.getCommonEventParams(),server_session_id:this.sessionId}})}trackSessionEnd(){if(!this.enabled)return;let e=Math.round((Date.now()-this.sessionStartTime)/1e3);this.enqueue({name:"mcp_session_end",params:{...this.getCommonEventParams(),server_session_id:this.sessionId,duration_sec:e,tool_calls_total:this.toolCallsTotal,tool_calls_success:this.toolCallsSuccess,tool_calls_failed:this.toolCallsFailed}})}trackToolCall(e,r,n,i,a,o){if(!this.enabled)return;this.toolCallsTotal++,n?this.toolCallsSuccess++:this.toolCallsFailed++;let s={...this.getCommonEventParams(),tool_name:e,required_tier:r,success:n?1:0,tool_result:n?"success":"error"};i&&(s.action=i),!n&&a&&(s.error_type=a),!n&&o&&(s.error_detail=o),this.enqueue({name:"mcp_tool_call",params:s})}trackToolError(e,r,n,i,a){if(!this.enabled)return;let o={...this.getCommonEventParams(),tool_name:e,required_tier:r};n&&(o.action=n),i&&(o.error_type=i),a&&(o.error_detail=a),this.enqueue({name:"mcp_tool_error",params:o})}trackPluginConnected(){this.enabled&&this.enqueue({name:"mcp_plugin_connected",params:{...this.getCommonEventParams(),server_session_id:this.sessionId}})}setAiClient(e){this.aiClient=e,this.userPropertiesDirty=!0}setPluginVersion(e){this.pluginVersion=e,this.userPropertiesDirty=!0}setTier(e){this.tier=e,this.userPropertiesDirty=!0}isEnabled(){return this.enabled}enqueue(e){this.eventQueue.push(e),this.eventQueue.length>=BJ&&this.flush()}flush(){if(this.eventQueue.length===0)return;let e=this.eventQueue.splice(0,VJ),r={client_id:this.clientId,events:e};this.userPropertiesDirty&&(r.user_properties={mcp_version:{value:Xe},plugin_version:{value:this.pluginVersion||"unknown"},device_key:{value:this.deviceKey},platform:{value:process.platform},user_tier:{value:this.tier},ai_client:{value:this.aiClient}},this.userPropertiesDirty=!1),this.sendToGA4(r),this.eventQueue.length>0&&this.flush()}sendToGA4(e){let r=`${LJ}?measurement_id=${qJ}&api_secret=${ZJ}`,n=new AbortController,i=setTimeout(()=>n.abort(),WJ);fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:n.signal}).then(()=>{clearTimeout(i),v.debug("Telemetry events sent",{count:e.events.length})}).catch(()=>{clearTimeout(i)})}async shutdown(){this.enabled&&(this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null),this.eventQueue.length>0&&this.flush(),await new Promise(e=>setTimeout(e,500)),v.debug("Telemetry shutdown complete"))}};var Xd=class extends Error{statusCode;constructor(e,r){super(e),this.name="LicenseApiError",this.statusCode=r}};function KJ(t){return new Promise(e=>setTimeout(e,t))}function Sj(t){let e={canUsePro:!1,status:"unlicensed",checkedAt:Math.floor(Date.now()/1e3)};if(!t||typeof t!="object")return e;let r=t,n=typeof r.status=="string"?r.status:"unlicensed",i=new Set(["active","unknown","grace","revoked","invalid","unlicensed"]),a={canUsePro:r.canUsePro===!0,status:i.has(n)?n:"unlicensed",checkedAt:typeof r.checkedAt=="number"?r.checkedAt:e.checkedAt};if(typeof r.maskedKey=="string"&&(a.maskedKey=r.maskedKey),(r.billingState==="ok"||r.billingState==="cancelled"||r.billingState==="expired"||r.billingState==="payment_failed"||r.billingState==="refunded"||r.billingState==="chargeback"||r.billingState==="unknown")&&(a.billingState=r.billingState),typeof r.reason=="string"&&(a.reason=r.reason),(r.decisionSource==="provider_live"||r.decisionSource==="provider_webhook"||r.decisionSource==="license_server_cache")&&(a.decisionSource=r.decisionSource),r.abuse&&typeof r.abuse=="object"){let o=r.abuse;(o.blocked===!0||o.blocked===!1)&&(a.abuse={blocked:o.blocked===!0},typeof o.blockedUntil=="number"&&(a.abuse.blockedUntil=o.blockedUntil),typeof o.blockReason=="string"&&(a.abuse.blockReason=o.blockReason))}return typeof r.sessionToken=="string"&&r.sessionToken.length>0&&(a.sessionToken=r.sessionToken),typeof r.lastLicenseServerSuccessAt=="number"&&(a.lastLicenseServerSuccessAt=r.lastLicenseServerSuccessAt,a.lastSuccessfulCheckAt=r.lastLicenseServerSuccessAt),typeof r.graceUntil=="number"&&(a.graceUntil=r.graceUntil,a.fallbackUntil=r.graceUntil),typeof r.lastSuccessfulCheckAt=="number"&&(a.lastSuccessfulCheckAt=r.lastSuccessfulCheckAt),typeof r.nextRecheckAt=="number"&&(a.nextRecheckAt=r.nextRecheckAt),typeof r.fallbackUntil=="number"&&(a.fallbackUntil=r.fallbackUntil),typeof a.lastSuccessfulCheckAt=="number"&&a.lastLicenseServerSuccessAt===void 0&&(a.lastLicenseServerSuccessAt=a.lastSuccessfulCheckAt),typeof a.fallbackUntil=="number"&&a.graceUntil===void 0&&(a.graceUntil=a.fallbackUntil),a}var Yd=class{config;constructor(e){this.config=e}isConfigured(){return this.config.baseUrl.length>0&&this.config.projectId.length>0&&this.config.provider.length>0}getV2ActionPath(e,r){let n=encodeURIComponent(this.config.projectId),i=encodeURIComponent(e);return`/v2/projects/${n}/providers/${i}/license/${r}`}async bearerRequest(e,r,n,i){let a=JSON.stringify(n),o=new URL(r,this.config.baseUrl),s={"content-type":"application/json"};i&&(s.authorization=`Bearer ${i}`);let c=0,l=null;for(;c<=this.config.retryCount;){let u=new AbortController,p=setTimeout(()=>u.abort(),this.config.timeoutMs);try{let d=await fetch(o.toString(),{method:e,headers:s,body:a,signal:u.signal});clearTimeout(p);let f=await d.text(),m=f.length>0?JSON.parse(f):null;if(!d.ok){let h=m&&typeof m=="object"&&typeof m.error=="string"?m.error:`License API error ${d.status}`;throw new Xd(h,d.status)}return m}catch(d){if(clearTimeout(p),d instanceof Xd&&d.statusCode<500)throw d;l=d instanceof Error?d:new Error("Unknown license API error"),c+=1,c<=this.config.retryCount&&await KJ(this.config.retryDelayMs)}}throw l??new Error("License API request failed")}async activate(e){let r=e.provider.trim().toLowerCase();if(!r)throw new Error("provider is required");let n={licenseKey:e.licenseKey,mcpInstanceId:e.mcpInstanceId},i=e.productRef??e.productPermalink;i&&(n.productRef=i),e.pluginClientId&&(n.pluginClientId=e.pluginClientId),e.deviceId&&(n.deviceId=e.deviceId);let a=await this.bearerRequest("POST",this.getV2ActionPath(r,"activate"),n),o=a?.state??a,s=Sj(o);return a?.sessionToken&&(s.sessionToken=a.sessionToken),s}async refresh(e){let r=e.provider.trim().toLowerCase();if(!r)throw new Error("provider is required");let n=e.sessionToken;if(!n)throw new Error("sessionToken is required for refresh");let i={};e.pluginClientId&&(i.pluginClientId=e.pluginClientId),e.mcpInstanceId&&(i.mcpInstanceId=e.mcpInstanceId),e.deviceId&&(i.deviceId=e.deviceId);let a=await this.bearerRequest("POST",this.getV2ActionPath(r,"refresh"),i,n),o=a?.state??a,s=Sj(o);return a?.sessionToken&&(s.sessionToken=a.sessionToken),s}};import $j from"path";import XJ from"os";import{promises as P_}from"fs";var YJ="license-state.json",E_=4;function QJ(){return $j.join(XJ.homedir(),".weppy-roblox-mcp",YJ)}var eG=new Set(["active","unknown","grace","revoked","invalid","unlicensed"]);function tG(){return{canUsePro:!1,status:"unlicensed",checkedAt:Math.floor(Date.now()/1e3)}}function Qd(){return{version:E_,updatedAt:Date.now(),state:tG()}}function rG(){return Math.floor(Date.now()/1e3)}function kj(t){let e=typeof t?.status=="string"&&eG.has(t.status)?t.status:"unlicensed",r={canUsePro:t?.canUsePro===!0,status:e,checkedAt:typeof t?.checkedAt=="number"?t.checkedAt:rG()};return typeof t?.maskedKey=="string"&&(r.maskedKey=t.maskedKey),typeof t?.billingState=="string"&&(r.billingState=t.billingState),typeof t?.reason=="string"&&(r.reason=t.reason),(t?.decisionSource==="provider_live"||t?.decisionSource==="provider_webhook"||t?.decisionSource==="license_server_cache")&&(r.decisionSource=t.decisionSource),t?.abuse&&typeof t.abuse=="object"&&t.abuse.blocked!==void 0&&(r.abuse={blocked:t.abuse.blocked===!0},typeof t.abuse.blockedUntil=="number"&&(r.abuse.blockedUntil=t.abuse.blockedUntil),typeof t.abuse.blockReason=="string"&&(r.abuse.blockReason=t.abuse.blockReason)),typeof t?.lastLicenseServerSuccessAt=="number"&&(r.lastLicenseServerSuccessAt=t.lastLicenseServerSuccessAt),typeof t?.graceUntil=="number"&&(r.graceUntil=t.graceUntil),typeof t?.lastSuccessfulCheckAt=="number"&&(r.lastSuccessfulCheckAt=t.lastSuccessfulCheckAt),typeof t?.nextRecheckAt=="number"&&(r.nextRecheckAt=t.nextRecheckAt),typeof t?.fallbackUntil=="number"&&(r.fallbackUntil=t.fallbackUntil),typeof r.lastLicenseServerSuccessAt=="number"&&r.lastSuccessfulCheckAt===void 0&&(r.lastSuccessfulCheckAt=r.lastLicenseServerSuccessAt),typeof r.lastSuccessfulCheckAt=="number"&&r.lastLicenseServerSuccessAt===void 0&&(r.lastLicenseServerSuccessAt=r.lastSuccessfulCheckAt),typeof r.graceUntil=="number"&&r.fallbackUntil===void 0&&(r.fallbackUntil=r.graceUntil),typeof r.fallbackUntil=="number"&&r.graceUntil===void 0&&(r.graceUntil=r.fallbackUntil),r}var ef=class{cacheFilePath;cache=Qd();constructor(e){this.cacheFilePath=e??QJ()}getState(){return{...this.cache.state}}getProvider(){return this.cache.provider}getSessionToken(){return this.cache.sessionToken}async initialize(){try{let e=await P_.readFile(this.cacheFilePath,"utf8"),r=JSON.parse(e);if(!r||typeof r!="object"){this.cache=Qd();return}let n=r,i=n.state&&typeof n.state=="object"?n.state:void 0,a=kj(i),o={version:E_,updatedAt:typeof n.updatedAt=="number"?n.updatedAt:Date.now(),state:a};typeof n.provider=="string"&&n.provider.trim()!==""&&(o.provider=n.provider.trim().toLowerCase()),typeof n.sessionToken=="string"&&n.sessionToken.length>0&&(o.sessionToken=n.sessionToken),this.cache=o}catch{this.cache=Qd()}}async persistState(e,r,n){let i={version:E_,updatedAt:Date.now(),state:kj(e)},a=r??this.cache.provider;typeof a=="string"&&a.trim()!==""&&(i.provider=a.trim().toLowerCase());let o=n??e.sessionToken??this.cache.sessionToken;typeof o=="string"&&o.length>0&&(i.sessionToken=o),this.cache=i,await this.write()}getBackupData(){let e=this.cache.sessionToken,r=this.cache.provider;return!r||!e?null:{sessionToken:e,provider:r,savedAt:this.cache.updatedAt}}async clear(){this.cache=Qd(),await this.write()}async write(){await P_.mkdir($j.dirname(this.cacheFilePath),{recursive:!0}),await P_.writeFile(this.cacheFilePath,JSON.stringify(this.cache,null,2),{mode:384})}};var Ij=10*60*60,nG=14*60*60;function Nn(){return Math.floor(Date.now()/1e3)}function iG(t){return{...t,checkedAt:t.checkedAt||Nn()}}var tf=class{client;cache;mcpInstanceId;fallbackMaxSeconds;random;refreshTimer=null;refreshInFlight=!1;constructor(e,r,n,i={}){this.client=e,this.cache=r,this.mcpInstanceId=n,this.fallbackMaxSeconds=Math.max(1,i.fallbackMaxHours??48)*60*60,this.random=i.random??Math.random}async initialize(){await this.cache.initialize(),this.scheduleFromCurrentState()}async shutdown(){this.clearScheduledRefresh()}isConfigured(){return this.client.isConfigured()}clearScheduledRefresh(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null)}computeNextRefreshAt(e=Nn()){let r=nG-Ij,n=Ij+Math.floor(this.random()*(r+1));return e+n}scheduleRefreshAt(e){this.clearScheduledRefresh();let r=Nn(),n=Math.max(0,(e-r)*1e3);this.refreshTimer=setTimeout(()=>{this.runScheduledRefresh()},n),this.refreshTimer.unref?.()}scheduleFromCurrentState(){if(!this.client.isConfigured()){this.clearScheduledRefresh();return}let e=!!this.cache.getSessionToken(),r=this.cache.getProvider();if(!e||!r){this.clearScheduledRefresh();return}let n=this.cache.getState(),i=typeof n.nextRecheckAt=="number"?n.nextRecheckAt:this.computeNextRefreshAt();this.scheduleRefreshAt(i)}getLastSuccessAt(e){return e.lastLicenseServerSuccessAt??e.lastSuccessfulCheckAt}getGraceUntil(e){return e.graceUntil??e.fallbackUntil}withCompatibilityFields(e){let r={...e},n=this.getLastSuccessAt(r);typeof n=="number"&&(r.lastLicenseServerSuccessAt=n,r.lastSuccessfulCheckAt=n);let i=this.getGraceUntil(r);return typeof i=="number"&&(r.graceUntil=i,r.fallbackUntil=i),r}async runScheduledRefresh(){if(!this.refreshInFlight){this.refreshInFlight=!0;try{await this.refresh({})}catch(e){v.warn("Scheduled license refresh failed",{error:e instanceof Error?e.message:"unknown_error"})}finally{this.refreshInFlight=!1,this.scheduleFromCurrentState()}}}evaluateEffectiveState(e){let r=iG(e),n=Nn();if(r.status==="revoked"||r.status==="invalid"||r.status==="unlicensed")return{...r,canUsePro:!1};if(r.status==="active")return r.canUsePro?r:{...r,canUsePro:!1,status:"invalid",reason:r.reason??"invalid_active_state"};if(r.status==="grace"){let i=this.getGraceUntil(r);return typeof i=="number"&&n<=i?{...r,canUsePro:!0}:{...r,canUsePro:!1,status:"unlicensed",reason:"network_unavailable_fallback_window_exceeded"}}if(r.status==="unknown"){let i=this.getLastSuccessAt(r);return typeof i=="number"&&n<=i+this.fallbackMaxSeconds&&r.canUsePro?{...r,status:"grace",canUsePro:!0,graceUntil:i+this.fallbackMaxSeconds,fallbackUntil:i+this.fallbackMaxSeconds,reason:"network_unavailable_fallback_to_last_success"}:{...r,canUsePro:!1,status:"unlicensed",reason:r.reason??"license_check_unavailable"}}return{...r,canUsePro:!1,status:"unlicensed",reason:r.reason??"license_unknown_state"}}buildPersistedState(e,r){let n=e.checkedAt||Nn(),i=this.computeNextRefreshAt(n),a={...e,checkedAt:n,nextRecheckAt:i};if(e.canUsePro&&e.status==="active")return a.lastLicenseServerSuccessAt=n,a.lastSuccessfulCheckAt=n,a.graceUntil=n+this.fallbackMaxSeconds,a.fallbackUntil=n+this.fallbackMaxSeconds,this.withCompatibilityFields(a);let o=this.getLastSuccessAt(r);return typeof o=="number"&&(a.lastLicenseServerSuccessAt=o,a.lastSuccessfulCheckAt=o,a.graceUntil=o+this.fallbackMaxSeconds,a.fallbackUntil=o+this.fallbackMaxSeconds),this.withCompatibilityFields(a)}buildFallbackState(e){let r=Nn(),n=this.computeNextRefreshAt(r);if(e.canUsePro&&typeof this.getLastSuccessAt(e)=="number"&&r<=this.getLastSuccessAt(e)+this.fallbackMaxSeconds){let i=this.getLastSuccessAt(e);return{...e,canUsePro:!0,status:"grace",checkedAt:r,lastLicenseServerSuccessAt:i,lastSuccessfulCheckAt:i,graceUntil:i+this.fallbackMaxSeconds,fallbackUntil:i+this.fallbackMaxSeconds,nextRecheckAt:n,reason:"network_unavailable_fallback_to_last_success"}}return this.withCompatibilityFields({...e,canUsePro:!1,status:"unlicensed",checkedAt:r,nextRecheckAt:n,reason:"network_unavailable_fallback_window_exceeded"})}getStatus(){let e=this.cache.getState(),r=this.evaluateEffectiveState(e),n=!!this.cache.getSessionToken();return this.client.isConfigured()&&n&&this.cache.getProvider()&&typeof e.nextRecheckAt=="number"&&Nn()>=e.nextRecheckAt&&!this.refreshInFlight&&this.runScheduledRefresh(),this.withCompatibilityFields(r)}async activate(e){if(!this.client.isConfigured())throw new Error("License API is not configured. Set LICENSE_API_BASE_URL, LICENSE_PROJECT_ID, LICENSE_PROVIDER.");let r={licenseKey:e.licenseKey,provider:e.provider.trim().toLowerCase(),mcpInstanceId:this.mcpInstanceId};if(!r.provider)throw new Error("provider is required");e.pluginClientId&&(r.pluginClientId=e.pluginClientId),e.deviceId&&(r.deviceId=e.deviceId);let n=await this.client.activate(r),i=this.buildPersistedState(n,this.cache.getState());return await this.cache.persistState(i,r.provider,n.sessionToken),this.scheduleFromCurrentState(),this.withCompatibilityFields(this.evaluateEffectiveState(i))}async refresh(e){if(!this.client.isConfigured())return this.getStatus();let r=(e.provider??this.cache.getProvider())?.trim().toLowerCase();if(!r){let a={canUsePro:!1,status:"unlicensed",checkedAt:Nn(),reason:"license_provider_missing"};return await this.cache.persistState(a),this.clearScheduledRefresh(),a}let n=e.sessionToken??this.cache.getSessionToken();if(n)try{let a={sessionToken:n,provider:r,mcpInstanceId:this.mcpInstanceId};e.pluginClientId&&(a.pluginClientId=e.pluginClientId),e.deviceId&&(a.deviceId=e.deviceId);let o=await this.client.refresh(a),s=this.buildPersistedState(o,this.cache.getState());return await this.cache.persistState(s,r,o.sessionToken),this.scheduleFromCurrentState(),this.withCompatibilityFields(this.evaluateEffectiveState(s))}catch(a){v.warn("License refresh failed, falling back",{error:a instanceof Error?a.message:"unknown_error"})}let i=this.buildFallbackState(this.cache.getState());return await this.cache.persistState(i,r),this.scheduleFromCurrentState(),this.withCompatibilityFields(this.evaluateEffectiveState(i))}async triggerImmediateRefreshOnConnect(e){if(!(!this.client.isConfigured()||!this.cache.getSessionToken()||!this.cache.getProvider())&&!this.refreshInFlight)try{this.refreshInFlight=!0;let n={};e&&(n.pluginClientId=e),await this.refresh(n)}catch(n){v.warn("Immediate license refresh on connect failed",{error:n instanceof Error?n.message:"unknown_error"})}finally{this.refreshInFlight=!1}}async evaluateProAccess(){let e=this.getStatus();if(e.canUsePro)return{allowed:!0,state:e};if(e.reason==="device_temporarily_blocked")return{allowed:!1,state:e,reason:"This device is temporarily blocked due to multi-device activity."};let r="License required";return e.status==="revoked"?r="License revoked. Pro tools are disabled.":e.status==="invalid"?r="License invalid. Please re-enter a valid license key.":(e.status==="unknown"||e.status==="grace")&&(r=e.reason??"License check is temporarily unavailable."),{allowed:!1,state:e,reason:r}}getBackupData(){return this.cache.getBackupData()}async persistSyncedToken(e,r){let n=this.cache.getState();await this.cache.persistState(n,r,e),this.scheduleFromCurrentState()}async clearCredentials(){await this.cache.clear(),this.clearScheduledRefresh()}};function aG(t){let e=t.toLowerCase();return/upstream_server_down/.test(e)?{errorType:"bridge_error",errorDetail:"upstream_down"}:/^http [45]\d\d/.test(e)?{errorType:"bridge_error",errorDetail:"http_error"}:/timeout/.test(e)?{errorType:"command_timeout",errorDetail:"timeout"}:/econnrefused|socket hang up|econnreset|epipe|network/.test(e)?{errorType:"bridge_error",errorDetail:"connection"}:/abort/.test(e)?{errorType:"command_timeout",errorDetail:"aborted"}:/runtime error|traceback|attempt to/.test(e)?{errorType:"plugin_runtime",errorDetail:"runtime_error"}:/instance not found|not found in/.test(e)?{errorType:"execution_failed",errorDetail:"not_found"}:/parent not found/.test(e)?{errorType:"execution_failed",errorDetail:"parent_not_found"}:/is required|missing required/.test(e)?{errorType:"execution_failed",errorDetail:"missing_param"}:/invalid classname|invalid class/.test(e)?{errorType:"execution_failed",errorDetail:"invalid_class"}:/unknown action/.test(e)?{errorType:"execution_failed",errorDetail:"unknown_action"}:/failed to insert asset/.test(e)?{errorType:"execution_failed",errorDetail:"asset_error"}:/failed to read property|property access denied/.test(e)?{errorType:"execution_failed",errorDetail:"property_error"}:/must be|invalid|cannot/.test(e)?{errorType:"execution_failed",errorDetail:"validation"}:{errorType:"execution_failed",errorDetail:"other"}}function oG(t){let r=(t instanceof Error?t.message:String(t)).toLowerCase();return/timeout/.test(r)?{errorType:"command_timeout",errorDetail:"timeout"}:/econnrefused|socket hang up|network|econnreset|epipe/.test(r)?{errorType:"bridge_error",errorDetail:"connection"}:/eaddrinuse/.test(r)?{errorType:"bridge_error",errorDetail:"port_in_use"}:{errorType:"exception",errorDetail:"unexpected"}}var T_=class{server;httpBridge;config;historyManager;analyticsManager;licenseStateManager;constructor(e){this.config=e,this.server=new Nu({name:"weppy-roblox-mcp",version:Xe},{capabilities:{tools:{}}}),this.httpBridge=new hj(e),this.historyManager=new Wd({enableLocalHistory:e.enableLocalHistory??!1,enableLocalStatistics:e.enableLocalStatistics??!1,dataDir:e.dataDir??On()}),this.analyticsManager=new Kd({enabled:e.enableTelemetry});let r=new Yd({baseUrl:e.licenseApiBaseUrl||"https://roblox-license-api.hope841026.workers.dev",projectId:e.licenseProjectId||"roblox-mcp",provider:e.licenseProvider||"gumroad",timeoutMs:e.licenseRequestTimeoutMs??8e3,retryCount:e.licenseRetryCount??1,retryDelayMs:e.licenseRetryDelayMs??300}),n=new ef;this.licenseStateManager=new tf(r,n,this.httpBridge.getInstanceId(),{fallbackMaxHours:e.licenseFallbackMaxHours??48}),this.httpBridge.setHistoryManager(this.historyManager),this.httpBridge.setAnalyticsManager(this.analyticsManager),this.httpBridge.setLicenseStateManager(this.licenseStateManager),this.setupHandlers(),v.info("MCP Server initialized",{name:"weppy-roblox-mcp",version:Xe,tools:Fs.length,localHistoryEnabled:e.enableLocalHistory??!1,localStatisticsEnabled:e.enableLocalStatistics??!1,telemetryEnabled:e.enableTelemetry??!1,licenseApiConfigured:this.licenseStateManager.isConfigured()})}setupHandlers(){this.server.setRequestHandler(Wg,async()=>(v.debug("Received tools/list request"),{tools:Fs})),this.server.setRequestHandler(cs,async e=>{let{name:r,arguments:n}=e.params;v.info("Received tool call",{tool:r,args:n});try{let i=await this.executeTool(r,n||{});return i.success?{content:[{type:"text",text:i.data!==void 0?JSON.stringify(i.data,null,2):JSON.stringify({success:!0,message:"Command executed"})}]}:{content:[{type:"text",text:JSON.stringify({success:!1,error:i.error},null,2)}],isError:!0}}catch(i){return v.error("Tool execution failed",i),{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}})}async executeInternalAction(e,r,n){if(e==="get_cached_selection"){let o=r.maxAge,s=this.httpBridge.getCachedSelection(o!==void 0?o:3e4);if(!s)return{success:!0,data:{cached:!1,message:"No cached selection data available. The plugin may not be connected or no selection changes have occurred yet."}};let c=Date.now()-s.timestamp;return{success:!0,data:{cached:!0,selection:s.selection,count:s.count,timestamp:s.timestamp,age:c}}}if(e==="get_connection_info")return{success:!0,data:await this.httpBridge.getConnectionInfo()};if(!e.startsWith("sync_"))return{success:!1,error:`Unknown internal action: ${e}`,failureCode:"INTERNAL_FAILED"};if(!n&&this.httpBridge.getIsClientMode()){let o=await this.httpBridge.executeCommand(e,r),s={success:o.success};return o.data!==void 0&&(s.data=o.data),o.error&&(s.error=o.error),o.success||(s.failureCode="PROXY_FAILED"),s}let i=await Ld(e,r,n),a={success:i.success};return i.data!==void 0&&(a.data=i.data),i.error&&(a.error=i.error),i.success||(a.failureCode="INTERNAL_FAILED"),a}async executePluginBatch(e,r){let n={commands:e.map(({action:a,params:o})=>({action:a,params:o})),stopOnError:r},i=await this.httpBridge.executeCommand("batch_execute",n);if(i.success&&Array.isArray(i.data?.results)){let a=i.data.results;return e.map((o,s)=>{let c=a[s];if(!c)return{index:o.index,action:o.action,success:!1,error:"Batch result missing"};let l={index:o.index,action:o.action,success:c.success};return c.data!==void 0&&(l.data=c.data),c.error&&(l.error=c.error),l})}return e.map(a=>({index:a.index,action:a.action,success:!1,error:i.error||"Batch execution failed"}))}async executeTool(e,r){let n=Date.now();if(!Fs.find(c=>c.name===e)){let c=`Unknown tool: ${e}`,l=Date.now()-n;return await this.historyManager.recordFailure(e,r,c,l,"UNKNOWN_TOOL"),this.analyticsManager.trackToolCall(e,"basic",!1,void 0,"execution_failed","unknown_tool"),this.analyticsManager.trackToolError(e,"basic",void 0,"execution_failed","unknown_tool"),{success:!1,error:c}}let{action:a,params:o}=S_(e,r),s=Xa(a);if(s==="pro"){let c=await this.licenseStateManager.evaluateProAccess();if(!c.allowed){if(c.state.reason==="device_temporarily_blocked"){let l=Date.now()-n,u=c.reason||"This device is temporarily blocked";return await this.historyManager.recordFailure(e,r,u,l,"DEVICE_BLOCKED"),this.analyticsManager.trackToolCall(e,s,!1,a,"device_blocked"),this.analyticsManager.trackToolError(e,s,a,"device_blocked"),{success:!1,error:`${u} (status=${c.state.status})`}}this.analyticsManager.trackToolCall(e,s,!1,a,"delegated_to_plugin"),v.debug("Pro action delegated to plugin for fallback",{toolName:e,action:a})}}v.debug("Executing tool",{toolName:e,action:a,args:o,tier:s});try{let c=this.httpBridge.getSyncController(),l=k_(e,a,c);if(l.target==="internal"){let d=await this.executeInternalAction(a,o,c),f=Date.now()-n;return d.success?(await this.historyManager.recordSuccess(e,r,d.data,f),this.analyticsManager.trackToolCall(e,s,!0,a),{success:!0,data:d.data}):(await this.historyManager.recordFailure(e,r,d.error||"Internal operation failed",f,d.failureCode||"INTERNAL_FAILED"),this.analyticsManager.trackToolCall(e,s,!1,a,"execution_failed","other"),this.analyticsManager.trackToolError(e,s,a,"execution_failed","other"),{success:!1,error:d.error||"Internal operation failed"})}if(l.target==="disk"){let d=await m_(a,o,c),f=Date.now()-n;if(!d.success)v.warn("Disk execution failed, falling back to plugin",{action:a,error:d.error});else return await this.historyManager.recordSuccess(e,r,d.data,f),this.analyticsManager.trackToolCall(e,s,!0,a),{success:!0,data:d.data}}if(a==="batch_execute"&&Array.isArray(o.commands)){let d=o.commands,f=o.stopOnError!==!1,m=[],h=[],g=async()=>{if(h.length===0)return!1;let k=await this.executePluginBatch(h,f);h=[];let N=!1;for(let A of k)m[A.index]={action:A.action,success:A.success,data:A.data,error:A.error},!A.success&&f&&(N=!0);return N},b=!1;for(let k=0;k<d.length;k++){let N=d[k],A=N.params||{},Le=k_(N.toolName||"",N.action,c).target;if(Le==="plugin"){h.push({index:k,action:N.action,params:A,toolName:N.toolName});continue}if(await g()){b=!0;break}let $e;if(Le==="internal"){let ye=await this.executeInternalAction(N.action,A,c);$e={success:ye.success,data:ye.data,error:ye.error}}else{let ye=await m_(N.action,A,c);if(ye.success)$e={success:!0,data:ye.data};else{v.warn("Disk execution failed inside batch, falling back to plugin",{action:N.action,error:ye.error});let pt=await this.httpBridge.executeCommand(N.action,A);$e={success:pt.success,data:pt.data,error:pt.error}}}if(m[k]={action:N.action,success:$e.success,data:$e.data,error:$e.error},!$e.success&&f){b=!0;break}}b||(b=await g());let S=m.filter(Boolean),_=Date.now()-n,$=S.every(k=>k.success);return await this.historyManager.recordSuccess(e,r,{results:S},_),this.analyticsManager.trackToolCall(e,s,$,a),$||this.analyticsManager.trackToolError(e,s,a,"execution_failed","other"),{success:!0,data:{results:S,totalCommands:S.length}}}let u=await this.httpBridge.executeCommand(a,o),p=Date.now()-n;if(!u.success){let d=u.error||"Tool execution failed",{errorType:f,errorDetail:m}=aG(d);return await this.historyManager.recordFailure(e,r,d,p,f.toUpperCase()),this.analyticsManager.trackToolCall(e,s,!1,a,f,m),this.analyticsManager.trackToolError(e,s,a,f,m),f==="bridge_error"||f==="command_timeout"?v.warn(`Tool ${f}`,{toolName:e,action:a,errorDetail:m,error:d}):f==="plugin_runtime"&&v.warn("Plugin runtime error",{toolName:e,action:a,error:d}),{success:!1,error:d}}return await this.historyManager.recordSuccess(e,r,u.data,p),this.analyticsManager.trackToolCall(e,s,!0,a),{success:!0,data:u.data}}catch(c){let{errorType:l,errorDetail:u}=oG(c),p=Date.now()-n,d=c instanceof Error?c.message:"Unknown error";return v.error(`Tool ${l}`,{toolName:e,action:a,error:d}),await this.historyManager.recordFailure(e,r,d,p,l.toUpperCase()),this.analyticsManager.trackToolCall(e,s,!1,a,l,u),this.analyticsManager.trackToolError(e,s,a,l,u),{success:!1,error:d}}}async start(){v.info("Starting Weppy Roblox MCP Server"),await this.licenseStateManager.initialize(),await this.httpBridge.start();let e=this.httpBridge.getSessionId();try{await this.historyManager.initialize(e)}catch(a){v.warn("Tool history manager initialization failed, continuing without history/statistics",a)}this.analyticsManager.initialize(e);let r=this.licenseStateManager.getStatus();this.analyticsManager.setTier(r.canUsePro?"pro":"basic"),this.analyticsManager.trackSessionStart();let n=Jd();n&&(this.httpBridge.setAiClientInfo(n.name,n.version),v.info("AI client detected via environment",{name:n.name})),this.server.oninitialized=()=>{try{let a=this.server.getClientVersion();if(a?.name){let o=_j(a.name),s=a.version||"";this.httpBridge.setAiClientInfo(o,s),v.info("AI client detected via MCP protocol",{raw:a.name,display:o,version:s})}}catch(a){v.debug("Could not detect AI client info from protocol",{error:a})}};let i=new Mu;await this.server.connect(i),v.info("MCP Server started successfully",{transport:"stdio",httpBridge:`${this.config.httpHost}:${this.config.httpPort}`,sessionId:e})}async stop(){v.info("Stopping MCP Server"),this.analyticsManager.trackSessionEnd(),await this.analyticsManager.shutdown(),await this.historyManager.shutdown(),await this.httpBridge.stop(),await this.licenseStateManager.shutdown(),await this.server.close(),v.info("MCP Server stopped")}},Pj=T_;function C_(t,e){return t===void 0||t===""?e:t.toLowerCase()==="true"||t==="1"}function sG(){let t={httpPort:parseInt(process.env.HTTP_PORT||"3002",10),httpHost:process.env.HTTP_HOST||"127.0.0.1",logLevel:process.env.LOG_LEVEL||"info",requestTimeout:parseInt(process.env.REQUEST_TIMEOUT||"30000",10),enableLocalHistory:C_(process.env.ENABLE_LOCAL_HISTORY,!1),enableLocalStatistics:C_(process.env.ENABLE_LOCAL_STATISTICS,!1),dataDir:process.env.DATA_DIR||On(),enableTelemetry:C_(process.env.ENABLE_TELEMETRY,!1),licenseApiBaseUrl:process.env.LICENSE_API_BASE_URL||"https://roblox-license-api.hope841026.workers.dev",licenseProjectId:process.env.LICENSE_PROJECT_ID||"roblox-mcp",licenseProvider:process.env.LICENSE_PROVIDER||"gumroad",licenseRequestTimeoutMs:parseInt(process.env.LICENSE_REQUEST_TIMEOUT_MS||"8000",10),licenseRetryCount:parseInt(process.env.LICENSE_RETRY_COUNT||"1",10),licenseRetryDelayMs:parseInt(process.env.LICENSE_RETRY_DELAY_MS||"300",10),licenseFallbackMaxHours:parseInt(process.env.LICENSE_FALLBACK_MAX_HOURS||"48",10)};return v.setLevel(t.logLevel),t}async function cG(){try{let t=sG();v.info("Weppy Roblox MCP Server",{version:Xe,config:{httpHost:t.httpHost,httpPort:t.httpPort,logLevel:t.logLevel,requestTimeout:t.requestTimeout,enableLocalHistory:t.enableLocalHistory,enableLocalStatistics:t.enableLocalStatistics,enableTelemetry:t.enableTelemetry,dataDir:t.dataDir,licenseApiBaseUrl:t.licenseApiBaseUrl,licenseProjectId:t.licenseProjectId,licenseProvider:t.licenseProvider,licenseRequestTimeoutMs:t.licenseRequestTimeoutMs,licenseRetryCount:t.licenseRetryCount,licenseRetryDelayMs:t.licenseRetryDelayMs,licenseFallbackMaxHours:t.licenseFallbackMaxHours}});let e=new Pj(t);await e.start();let r=async n=>{v.info(`Received ${n}, shutting down gracefully`);try{await e.stop(),process.exit(0)}catch(i){v.error("Error during shutdown",i),process.exit(1)}};process.on("SIGINT",()=>r("SIGINT")),process.on("SIGTERM",()=>r("SIGTERM")),process.on("uncaughtException",n=>{v.error("Uncaught exception",n),process.exit(1)}),process.on("unhandledRejection",n=>{v.error("Unhandled rejection",n),process.exit(1)})}catch(t){v.error("Failed to start server",t),process.exit(1)}}cG();
129
+ `)}catch(r){v.error("Failed to write session end marker",r)}}this.initialized=!1,v.info("Tool history manager shutdown",{sessionId:this.sessionId,totalCommands:this.sequenceNumber})}}isLocalHistoryEnabled(){return this.config.enableLocalHistory}isLocalStatisticsEnabled(){return this.config.enableLocalStatistics}getHistoryDir(){return this.historyDir}getStatisticsDir(){return this.statisticsDir}};import{createHash as UJ,randomUUID as FJ}from"crypto";import*as _i from"fs";import*as Gd from"path";import*as wj from"os";var I_={"claude-code":"Claude Code",claude:"Claude",cursor:"Cursor","codex-cli":"Codex CLI","openai-codex":"Codex CLI","gemini-cli":"Gemini CLI",windsurf:"Windsurf",cline:"Cline",continue:"Continue",zed:"Zed",vscode:"VS Code"};function _j(t){let e=t.toLowerCase();if(I_[e])return I_[e];for(let[r,n]of Object.entries(I_))if(e.includes(r))return n;return t}function Jd(){return process.env.CLAUDECODE||process.env.CLAUDE_CODE_ENTRYPOINT?{name:"Claude Code",version:""}:process.env.CURSOR_EDITOR?{name:"Cursor",version:""}:process.env.WINDSURF_EDITOR?{name:"Windsurf",version:""}:process.env.CONTINUE_GLOBAL_DIR?{name:"Continue",version:""}:process.env.CLINE_CONTEXT?{name:"Cline",version:""}:null}var LJ="https://www.google-analytics.com/mp/collect",qJ="G-87FMYC2KLT",ZJ="ANCUVw5rTZ6ZRMOBd_RLMw",HJ=6e4,BJ=20,VJ=25,WJ=5e3,JJ=".weppy-roblox-mcp",GJ="device-id.json",Kd=class{enabled;clientId;deviceKey;sessionId="";gaSessionId=0;sessionStartTime=0;lastEngagementAt=0;toolCallsTotal=0;toolCallsSuccess=0;toolCallsFailed=0;eventQueue=[];flushTimer=null;aiClient="unknown";pluginVersion="";tier="basic";userPropertiesDirty=!0;constructor(e){this.enabled=this.resolveEnabled(e.enabled),this.clientId=this.loadOrCreateDeviceId(),this.deviceKey=this.createDeviceKey(this.clientId),this.enabled||v.debug("Telemetry disabled")}getAnalyticsDirPath(){return Gd.join(wj.homedir(),JJ)}getDeviceIdPath(){return Gd.join(this.getAnalyticsDirPath(),GJ)}loadOrCreateDeviceId(){let e=this.getDeviceIdPath();try{let n=_i.readFileSync(e,"utf-8"),i=JSON.parse(n);if(typeof i.deviceId=="string"&&i.deviceId.length>0)return i.deviceId}catch{}let r=FJ();try{_i.mkdirSync(this.getAnalyticsDirPath(),{recursive:!0}),_i.writeFileSync(e,JSON.stringify({deviceId:r},null,2),"utf-8")}catch(n){v.warn("Failed to persist telemetry device ID",{error:n})}return r}createDeviceKey(e){return UJ("sha256").update(e).digest("hex").slice(0,32)}resolveEnabled(e){let r=process.env.ENABLE_TELEMETRY;if(r!==void 0&&r!=="")return r.toLowerCase()==="true"||r==="1";try{let n=Gd.join(this.getAnalyticsDirPath(),"analytics.json"),i=_i.readFileSync(n,"utf-8"),a=JSON.parse(i);if(typeof a.enabled=="boolean")return a.enabled}catch{}return e??!1}initialize(e){if(!this.enabled)return;let r=Date.now();this.sessionId=e,this.sessionStartTime=r,this.gaSessionId=Math.floor(r/1e3),this.lastEngagementAt=r;let n=Jd();n&&(this.aiClient=n.name),this.flushTimer=setInterval(()=>this.flush(),HJ),this.flushTimer.unref(),v.debug("Telemetry initialized",{sessionId:e})}getCommonEventParams(){let e=Date.now(),r=this.getEngagementTimeMsec(e);return{device_key:this.deviceKey,mcp_version:Xe,plugin_version:this.pluginVersion||"unknown",platform:process.platform,user_tier:this.tier,ai_client:this.aiClient,session_id:this.gaSessionId,engagement_time_msec:r,timestamp_micros:e*1e3,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"unknown",locale:Intl.DateTimeFormat().resolvedOptions().locale||"unknown"}}getEngagementTimeMsec(e){if(this.lastEngagementAt<=0)return this.lastEngagementAt=e,100;let r=Math.max(100,e-this.lastEngagementAt);return this.lastEngagementAt=e,r}trackSessionStart(){this.enabled&&this.enqueue({name:"mcp_session_start",params:{...this.getCommonEventParams(),server_session_id:this.sessionId}})}trackSessionEnd(){if(!this.enabled)return;let e=Math.round((Date.now()-this.sessionStartTime)/1e3);this.enqueue({name:"mcp_session_end",params:{...this.getCommonEventParams(),server_session_id:this.sessionId,duration_sec:e,tool_calls_total:this.toolCallsTotal,tool_calls_success:this.toolCallsSuccess,tool_calls_failed:this.toolCallsFailed}})}trackToolCall(e,r,n,i,a,o){if(!this.enabled)return;this.toolCallsTotal++,n?this.toolCallsSuccess++:this.toolCallsFailed++;let s={...this.getCommonEventParams(),tool_name:e,required_tier:r,success:n?1:0,tool_result:n?"success":"error"};i&&(s.action=i),!n&&a&&(s.error_type=a),!n&&o&&(s.error_detail=o),this.enqueue({name:"mcp_tool_call",params:s})}trackToolError(e,r,n,i,a){if(!this.enabled)return;let o={...this.getCommonEventParams(),tool_name:e,required_tier:r};n&&(o.action=n),i&&(o.error_type=i),a&&(o.error_detail=a),this.enqueue({name:"mcp_tool_error",params:o})}trackPluginConnected(){this.enabled&&this.enqueue({name:"mcp_plugin_connected",params:{...this.getCommonEventParams(),server_session_id:this.sessionId}})}setAiClient(e){this.aiClient=e,this.userPropertiesDirty=!0}setPluginVersion(e){this.pluginVersion=e,this.userPropertiesDirty=!0}setTier(e){this.tier=e,this.userPropertiesDirty=!0}isEnabled(){return this.enabled}enqueue(e){this.eventQueue.push(e),this.eventQueue.length>=BJ&&this.flush()}flush(){if(this.eventQueue.length===0)return;let e=this.eventQueue.splice(0,VJ),r={client_id:this.clientId,events:e};this.userPropertiesDirty&&(r.user_properties={mcp_version:{value:Xe},plugin_version:{value:this.pluginVersion||"unknown"},device_key:{value:this.deviceKey},platform:{value:process.platform},user_tier:{value:this.tier},ai_client:{value:this.aiClient}},this.userPropertiesDirty=!1),this.sendToGA4(r),this.eventQueue.length>0&&this.flush()}sendToGA4(e){let r=`${LJ}?measurement_id=${qJ}&api_secret=${ZJ}`,n=new AbortController,i=setTimeout(()=>n.abort(),WJ);fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:n.signal}).then(()=>{clearTimeout(i),v.debug("Telemetry events sent",{count:e.events.length})}).catch(()=>{clearTimeout(i)})}async shutdown(){this.enabled&&(this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null),this.eventQueue.length>0&&this.flush(),await new Promise(e=>setTimeout(e,500)),v.debug("Telemetry shutdown complete"))}};var Xd=class extends Error{statusCode;constructor(e,r){super(e),this.name="LicenseApiError",this.statusCode=r}};function KJ(t){return new Promise(e=>setTimeout(e,t))}function Sj(t){let e={canUsePro:!1,status:"unlicensed",checkedAt:Math.floor(Date.now()/1e3)};if(!t||typeof t!="object")return e;let r=t,n=typeof r.status=="string"?r.status:"unlicensed",i=new Set(["active","unknown","grace","revoked","invalid","unlicensed"]),a={canUsePro:r.canUsePro===!0,status:i.has(n)?n:"unlicensed",checkedAt:typeof r.checkedAt=="number"?r.checkedAt:e.checkedAt};if(typeof r.maskedKey=="string"&&(a.maskedKey=r.maskedKey),(r.billingState==="ok"||r.billingState==="cancelled"||r.billingState==="expired"||r.billingState==="payment_failed"||r.billingState==="refunded"||r.billingState==="chargeback"||r.billingState==="unknown")&&(a.billingState=r.billingState),typeof r.reason=="string"&&(a.reason=r.reason),(r.decisionSource==="provider_live"||r.decisionSource==="provider_webhook"||r.decisionSource==="license_server_cache")&&(a.decisionSource=r.decisionSource),r.abuse&&typeof r.abuse=="object"){let o=r.abuse;(o.blocked===!0||o.blocked===!1)&&(a.abuse={blocked:o.blocked===!0},typeof o.blockedUntil=="number"&&(a.abuse.blockedUntil=o.blockedUntil),typeof o.blockReason=="string"&&(a.abuse.blockReason=o.blockReason))}return typeof r.sessionToken=="string"&&r.sessionToken.length>0&&(a.sessionToken=r.sessionToken),typeof r.tokenStatus=="string"&&(a.tokenStatus=r.tokenStatus),typeof r.tokenReason=="string"&&(a.tokenReason=r.tokenReason),typeof r.statusDetail=="string"&&(a.statusDetail=r.statusDetail),typeof r.lastLicenseServerSuccessAt=="number"&&(a.lastLicenseServerSuccessAt=r.lastLicenseServerSuccessAt,a.lastSuccessfulCheckAt=r.lastLicenseServerSuccessAt),typeof r.graceUntil=="number"&&(a.graceUntil=r.graceUntil,a.fallbackUntil=r.graceUntil),typeof r.lastSuccessfulCheckAt=="number"&&(a.lastSuccessfulCheckAt=r.lastSuccessfulCheckAt),typeof r.nextRecheckAt=="number"&&(a.nextRecheckAt=r.nextRecheckAt),typeof r.fallbackUntil=="number"&&(a.fallbackUntil=r.fallbackUntil),typeof a.lastSuccessfulCheckAt=="number"&&a.lastLicenseServerSuccessAt===void 0&&(a.lastLicenseServerSuccessAt=a.lastSuccessfulCheckAt),typeof a.fallbackUntil=="number"&&a.graceUntil===void 0&&(a.graceUntil=a.fallbackUntil),a}var Yd=class{config;constructor(e){this.config=e}isConfigured(){return this.config.baseUrl.length>0&&this.config.projectId.length>0&&this.config.provider.length>0}getV2ActionPath(e,r){let n=encodeURIComponent(this.config.projectId),i=encodeURIComponent(e);return`/v2/projects/${n}/providers/${i}/license/${r}`}async bearerRequest(e,r,n,i){let a=JSON.stringify(n),o=new URL(r,this.config.baseUrl),s={"content-type":"application/json"};i&&(s.authorization=`Bearer ${i}`);let c=0,l=null;for(;c<=this.config.retryCount;){let u=new AbortController,p=setTimeout(()=>u.abort(),this.config.timeoutMs);try{let d=await fetch(o.toString(),{method:e,headers:s,body:a,signal:u.signal});clearTimeout(p);let f=await d.text(),m=f.length>0?JSON.parse(f):null;if(!d.ok){let h=m&&typeof m=="object"&&typeof m.error=="string"?m.error:`License API error ${d.status}`;throw new Xd(h,d.status)}return m}catch(d){if(clearTimeout(p),d instanceof Xd&&d.statusCode<500)throw d;l=d instanceof Error?d:new Error("Unknown license API error"),c+=1,c<=this.config.retryCount&&await KJ(this.config.retryDelayMs)}}throw l??new Error("License API request failed")}async activate(e){let r=e.provider.trim().toLowerCase();if(!r)throw new Error("provider is required");let n={licenseKey:e.licenseKey,mcpInstanceId:e.mcpInstanceId},i=e.productRef??e.productPermalink;i&&(n.productRef=i),e.pluginClientId&&(n.pluginClientId=e.pluginClientId),e.deviceId&&(n.deviceId=e.deviceId);let a=await this.bearerRequest("POST",this.getV2ActionPath(r,"activate"),n),o=a?.state??a,s=Sj(o);return a?.sessionToken&&(s.sessionToken=a.sessionToken),s}async refresh(e){let r=e.provider.trim().toLowerCase();if(!r)throw new Error("provider is required");let n=e.sessionToken;if(!n)throw new Error("sessionToken is required for refresh");let i={};e.pluginClientId&&(i.pluginClientId=e.pluginClientId),e.mcpInstanceId&&(i.mcpInstanceId=e.mcpInstanceId),e.deviceId&&(i.deviceId=e.deviceId);let a=await this.bearerRequest("POST",this.getV2ActionPath(r,"refresh"),i,n),o=a?.state??a,s=Sj(o);return a?.sessionToken&&(s.sessionToken=a.sessionToken),s}};import $j from"path";import XJ from"os";import{promises as P_}from"fs";var YJ="license-state.json",E_=4;function QJ(){return $j.join(XJ.homedir(),".weppy-roblox-mcp",YJ)}var eG=new Set(["active","unknown","grace","revoked","invalid","unlicensed"]);function tG(){return{canUsePro:!1,status:"unlicensed",checkedAt:Math.floor(Date.now()/1e3)}}function Qd(){return{version:E_,updatedAt:Date.now(),state:tG()}}function rG(){return Math.floor(Date.now()/1e3)}function kj(t){let e=typeof t?.status=="string"&&eG.has(t.status)?t.status:"unlicensed",r={canUsePro:t?.canUsePro===!0,status:e,checkedAt:typeof t?.checkedAt=="number"?t.checkedAt:rG()};return typeof t?.maskedKey=="string"&&(r.maskedKey=t.maskedKey),typeof t?.billingState=="string"&&(r.billingState=t.billingState),typeof t?.reason=="string"&&(r.reason=t.reason),(t?.decisionSource==="provider_live"||t?.decisionSource==="provider_webhook"||t?.decisionSource==="license_server_cache")&&(r.decisionSource=t.decisionSource),t?.abuse&&typeof t.abuse=="object"&&t.abuse.blocked!==void 0&&(r.abuse={blocked:t.abuse.blocked===!0},typeof t.abuse.blockedUntil=="number"&&(r.abuse.blockedUntil=t.abuse.blockedUntil),typeof t.abuse.blockReason=="string"&&(r.abuse.blockReason=t.abuse.blockReason)),typeof t?.tokenStatus=="string"&&(r.tokenStatus=t.tokenStatus),typeof t?.statusDetail=="string"&&(r.statusDetail=t.statusDetail),typeof t?.lastLicenseServerSuccessAt=="number"&&(r.lastLicenseServerSuccessAt=t.lastLicenseServerSuccessAt),typeof t?.graceUntil=="number"&&(r.graceUntil=t.graceUntil),typeof t?.lastSuccessfulCheckAt=="number"&&(r.lastSuccessfulCheckAt=t.lastSuccessfulCheckAt),typeof t?.nextRecheckAt=="number"&&(r.nextRecheckAt=t.nextRecheckAt),typeof t?.fallbackUntil=="number"&&(r.fallbackUntil=t.fallbackUntil),typeof r.lastLicenseServerSuccessAt=="number"&&r.lastSuccessfulCheckAt===void 0&&(r.lastSuccessfulCheckAt=r.lastLicenseServerSuccessAt),typeof r.lastSuccessfulCheckAt=="number"&&r.lastLicenseServerSuccessAt===void 0&&(r.lastLicenseServerSuccessAt=r.lastSuccessfulCheckAt),typeof r.graceUntil=="number"&&r.fallbackUntil===void 0&&(r.fallbackUntil=r.graceUntil),typeof r.fallbackUntil=="number"&&r.graceUntil===void 0&&(r.graceUntil=r.fallbackUntil),r}var ef=class{cacheFilePath;cache=Qd();constructor(e){this.cacheFilePath=e??QJ()}getState(){return{...this.cache.state}}getProvider(){return this.cache.provider}getSessionToken(){return this.cache.sessionToken}async initialize(){try{let e=await P_.readFile(this.cacheFilePath,"utf8"),r=JSON.parse(e);if(!r||typeof r!="object"){this.cache=Qd();return}let n=r,i=n.state&&typeof n.state=="object"?n.state:void 0,a=kj(i),o={version:E_,updatedAt:typeof n.updatedAt=="number"?n.updatedAt:Date.now(),state:a};typeof n.provider=="string"&&n.provider.trim()!==""&&(o.provider=n.provider.trim().toLowerCase()),typeof n.sessionToken=="string"&&n.sessionToken.length>0&&(o.sessionToken=n.sessionToken),this.cache=o}catch{this.cache=Qd()}}async persistState(e,r,n){let i={version:E_,updatedAt:Date.now(),state:kj(e)},a=r??this.cache.provider;typeof a=="string"&&a.trim()!==""&&(i.provider=a.trim().toLowerCase());let o=n??e.sessionToken??this.cache.sessionToken;typeof o=="string"&&o.length>0&&(i.sessionToken=o),this.cache=i,await this.write()}getBackupData(){let e=this.cache.sessionToken,r=this.cache.provider;return!r||!e?null:{sessionToken:e,provider:r,savedAt:this.cache.updatedAt}}async clear(){this.cache=Qd(),await this.write()}async write(){await P_.mkdir($j.dirname(this.cacheFilePath),{recursive:!0}),await P_.writeFile(this.cacheFilePath,JSON.stringify(this.cache,null,2),{mode:384})}};var Ij=10*60*60,nG=14*60*60;function Nn(){return Math.floor(Date.now()/1e3)}function iG(t){return{...t,checkedAt:t.checkedAt||Nn()}}var tf=class{client;cache;mcpInstanceId;fallbackMaxSeconds;random;refreshTimer=null;refreshInFlight=!1;constructor(e,r,n,i={}){this.client=e,this.cache=r,this.mcpInstanceId=n,this.fallbackMaxSeconds=Math.max(1,i.fallbackMaxHours??48)*60*60,this.random=i.random??Math.random}async initialize(){await this.cache.initialize(),this.scheduleFromCurrentState()}async shutdown(){this.clearScheduledRefresh()}isConfigured(){return this.client.isConfigured()}clearScheduledRefresh(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null)}computeNextRefreshAt(e=Nn()){let r=nG-Ij,n=Ij+Math.floor(this.random()*(r+1));return e+n}scheduleRefreshAt(e){this.clearScheduledRefresh();let r=Nn(),n=Math.max(0,(e-r)*1e3);this.refreshTimer=setTimeout(()=>{this.runScheduledRefresh()},n),this.refreshTimer.unref?.()}scheduleFromCurrentState(){if(!this.client.isConfigured()){this.clearScheduledRefresh();return}let e=!!this.cache.getSessionToken(),r=this.cache.getProvider();if(!e||!r){this.clearScheduledRefresh();return}let n=this.cache.getState(),i=typeof n.nextRecheckAt=="number"?n.nextRecheckAt:this.computeNextRefreshAt();this.scheduleRefreshAt(i)}getLastSuccessAt(e){return e.lastLicenseServerSuccessAt??e.lastSuccessfulCheckAt}getGraceUntil(e){return e.graceUntil??e.fallbackUntil}withCompatibilityFields(e){let r={...e},n=this.getLastSuccessAt(r);typeof n=="number"&&(r.lastLicenseServerSuccessAt=n,r.lastSuccessfulCheckAt=n);let i=this.getGraceUntil(r);return typeof i=="number"&&(r.graceUntil=i,r.fallbackUntil=i),r}async runScheduledRefresh(){if(!this.refreshInFlight){this.refreshInFlight=!0;try{await this.refresh({})}catch(e){v.warn("Scheduled license refresh failed",{error:e instanceof Error?e.message:"unknown_error"})}finally{this.refreshInFlight=!1,this.scheduleFromCurrentState()}}}withLocalTransitionDiagnostics(e,r,n){let{tokenStatus:i,tokenReason:a,statusDetail:o,...s}=e;return{...s,...r,statusDetail:n}}buildSuccessfulRemoteState(e,r,n){let i={...e,checkedAt:r,nextRecheckAt:n},a=this.getLastSuccessAt(e),o=this.getGraceUntil(e);return i.lastLicenseServerSuccessAt=typeof a=="number"?a:r,i.lastSuccessfulCheckAt=i.lastLicenseServerSuccessAt,i.graceUntil=typeof o=="number"?o:r+this.fallbackMaxSeconds,i.fallbackUntil=i.graceUntil,this.withCompatibilityFields(i)}evaluateEffectiveState(e){let r=iG(e),n=Nn();if(r.status==="revoked"||r.status==="invalid"||r.status==="unlicensed")return{...r,canUsePro:!1};if(r.status==="active")return r.canUsePro?r:this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"invalid",reason:r.reason??"invalid_active_state"},"invalid_local_evaluation");if(r.status==="grace"){let i=this.getGraceUntil(r);return typeof i=="number"&&n<=i?{...r,canUsePro:!0}:this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"unlicensed",reason:"network_unavailable_fallback_window_exceeded"},"unlicensed_local_evaluation")}if(r.status==="unknown"){let i=this.getLastSuccessAt(r);return typeof i=="number"&&n<=i+this.fallbackMaxSeconds&&r.canUsePro?this.withLocalTransitionDiagnostics(r,{status:"grace",canUsePro:!0,graceUntil:i+this.fallbackMaxSeconds,fallbackUntil:i+this.fallbackMaxSeconds,reason:"network_unavailable_fallback_to_last_success"},"grace_local_evaluation"):this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"unlicensed",reason:r.reason??"license_check_unavailable"},"unlicensed_local_evaluation")}return this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"unlicensed",reason:r.reason??"license_unknown_state"},"unlicensed_local_evaluation")}buildPersistedState(e){let r=e.checkedAt||Nn(),n=this.computeNextRefreshAt(r),i={...e,checkedAt:r,nextRecheckAt:n};return e.canUsePro&&(e.status==="active"||e.status==="grace")?this.buildSuccessfulRemoteState(e,r,n):this.withCompatibilityFields(i)}buildFallbackState(e){let r=Nn(),n=this.computeNextRefreshAt(r);if(e.canUsePro&&typeof this.getLastSuccessAt(e)=="number"&&r<=this.getLastSuccessAt(e)+this.fallbackMaxSeconds){let i=this.getLastSuccessAt(e);return this.withLocalTransitionDiagnostics(e,{canUsePro:!0,status:"grace",checkedAt:r,lastLicenseServerSuccessAt:i,lastSuccessfulCheckAt:i,graceUntil:i+this.fallbackMaxSeconds,fallbackUntil:i+this.fallbackMaxSeconds,nextRecheckAt:n,reason:"network_unavailable_fallback_to_last_success"},"grace_network_fallback")}return this.withCompatibilityFields(this.withLocalTransitionDiagnostics(e,{canUsePro:!1,status:"unlicensed",checkedAt:r,nextRecheckAt:n,reason:"network_unavailable_fallback_window_exceeded"},"unlicensed_fallback_expired"))}getStatus(){let e=this.cache.getState(),r=this.evaluateEffectiveState(e),n=!!this.cache.getSessionToken();return this.client.isConfigured()&&n&&this.cache.getProvider()&&typeof e.nextRecheckAt=="number"&&Nn()>=e.nextRecheckAt&&!this.refreshInFlight&&this.runScheduledRefresh(),this.withCompatibilityFields(r)}async activate(e){if(!this.client.isConfigured())throw new Error("License API is not configured. Set LICENSE_API_BASE_URL, LICENSE_PROJECT_ID, LICENSE_PROVIDER.");let r={licenseKey:e.licenseKey,provider:e.provider.trim().toLowerCase(),mcpInstanceId:this.mcpInstanceId};if(!r.provider)throw new Error("provider is required");e.pluginClientId&&(r.pluginClientId=e.pluginClientId),e.deviceId&&(r.deviceId=e.deviceId);let n=await this.client.activate(r),i=this.buildPersistedState(n);await this.cache.persistState(i,r.provider,n.sessionToken??""),this.scheduleFromCurrentState();let a=this.withCompatibilityFields(this.evaluateEffectiveState(i));return n.sessionToken&&(a.sessionToken=n.sessionToken),a}async refresh(e){if(!this.client.isConfigured())return this.getStatus();let r=(e.provider??this.cache.getProvider())?.trim().toLowerCase();if(!r){let a={canUsePro:!1,status:"unlicensed",checkedAt:Nn(),reason:"license_provider_missing"};return await this.cache.persistState(a),this.clearScheduledRefresh(),a}let n=e.sessionToken??this.cache.getSessionToken();if(n)try{let a={sessionToken:n,provider:r,mcpInstanceId:this.mcpInstanceId};e.pluginClientId&&(a.pluginClientId=e.pluginClientId),e.deviceId&&(a.deviceId=e.deviceId);let o=await this.client.refresh(a),s=this.buildPersistedState(o);return await this.cache.persistState(s,r,o.sessionToken),this.scheduleFromCurrentState(),this.withCompatibilityFields(this.evaluateEffectiveState(s))}catch(a){v.warn("License refresh failed, falling back",{error:a instanceof Error?a.message:"unknown_error"});let o=this.buildFallbackState(this.cache.getState());return await this.cache.persistState(o,r),this.scheduleFromCurrentState(),this.withCompatibilityFields(this.evaluateEffectiveState(o))}let i=this.evaluateEffectiveState(this.cache.getState());return await this.cache.persistState(i,r),this.scheduleFromCurrentState(),this.withCompatibilityFields(i)}async triggerImmediateRefreshOnConnect(e){if(!(!this.client.isConfigured()||!this.cache.getSessionToken()||!this.cache.getProvider())&&!this.refreshInFlight)try{this.refreshInFlight=!0;let n={};e&&(n.pluginClientId=e),await this.refresh(n)}catch(n){v.warn("Immediate license refresh on connect failed",{error:n instanceof Error?n.message:"unknown_error"})}finally{this.refreshInFlight=!1}}async evaluateProAccess(){let e=this.getStatus();if(e.canUsePro)return{allowed:!0,state:e};if(e.reason==="device_temporarily_blocked")return{allowed:!1,state:e,reason:"This device is temporarily blocked due to multi-device activity."};let r="License required";return e.status==="revoked"?r="License revoked. Pro tools are disabled.":e.status==="invalid"?r="License invalid. Please re-enter a valid license key.":(e.status==="unknown"||e.status==="grace")&&(r=e.reason??"License check is temporarily unavailable."),{allowed:!1,state:e,reason:r}}getBackupData(){return this.cache.getBackupData()}async persistSyncedToken(e,r){let n=this.cache.getState();await this.cache.persistState(n,r,e),this.scheduleFromCurrentState()}async clearCredentials(){await this.cache.clear(),this.clearScheduledRefresh()}};function aG(t){let e=t.toLowerCase();return/upstream_server_down/.test(e)?{errorType:"bridge_error",errorDetail:"upstream_down"}:/^http [45]\d\d/.test(e)?{errorType:"bridge_error",errorDetail:"http_error"}:/timeout/.test(e)?{errorType:"command_timeout",errorDetail:"timeout"}:/econnrefused|socket hang up|econnreset|epipe|network/.test(e)?{errorType:"bridge_error",errorDetail:"connection"}:/abort/.test(e)?{errorType:"command_timeout",errorDetail:"aborted"}:/runtime error|traceback|attempt to/.test(e)?{errorType:"plugin_runtime",errorDetail:"runtime_error"}:/instance not found|not found in/.test(e)?{errorType:"execution_failed",errorDetail:"not_found"}:/parent not found/.test(e)?{errorType:"execution_failed",errorDetail:"parent_not_found"}:/is required|missing required/.test(e)?{errorType:"execution_failed",errorDetail:"missing_param"}:/invalid classname|invalid class/.test(e)?{errorType:"execution_failed",errorDetail:"invalid_class"}:/unknown action/.test(e)?{errorType:"execution_failed",errorDetail:"unknown_action"}:/failed to insert asset/.test(e)?{errorType:"execution_failed",errorDetail:"asset_error"}:/failed to read property|property access denied/.test(e)?{errorType:"execution_failed",errorDetail:"property_error"}:/must be|invalid|cannot/.test(e)?{errorType:"execution_failed",errorDetail:"validation"}:{errorType:"execution_failed",errorDetail:"other"}}function oG(t){let r=(t instanceof Error?t.message:String(t)).toLowerCase();return/timeout/.test(r)?{errorType:"command_timeout",errorDetail:"timeout"}:/econnrefused|socket hang up|network|econnreset|epipe/.test(r)?{errorType:"bridge_error",errorDetail:"connection"}:/eaddrinuse/.test(r)?{errorType:"bridge_error",errorDetail:"port_in_use"}:{errorType:"exception",errorDetail:"unexpected"}}var T_=class{server;httpBridge;config;historyManager;analyticsManager;licenseStateManager;constructor(e){this.config=e,this.server=new Nu({name:"weppy-roblox-mcp",version:Xe},{capabilities:{tools:{}}}),this.httpBridge=new hj(e),this.historyManager=new Wd({enableLocalHistory:e.enableLocalHistory??!1,enableLocalStatistics:e.enableLocalStatistics??!1,dataDir:e.dataDir??On()}),this.analyticsManager=new Kd({enabled:e.enableTelemetry});let r=new Yd({baseUrl:e.licenseApiBaseUrl||"https://roblox-license-api.hope841026.workers.dev",projectId:e.licenseProjectId||"roblox-mcp",provider:e.licenseProvider||"gumroad",timeoutMs:e.licenseRequestTimeoutMs??8e3,retryCount:e.licenseRetryCount??1,retryDelayMs:e.licenseRetryDelayMs??300}),n=new ef;this.licenseStateManager=new tf(r,n,this.httpBridge.getInstanceId(),{fallbackMaxHours:e.licenseFallbackMaxHours??48}),this.httpBridge.setHistoryManager(this.historyManager),this.httpBridge.setAnalyticsManager(this.analyticsManager),this.httpBridge.setLicenseStateManager(this.licenseStateManager),this.setupHandlers(),v.info("MCP Server initialized",{name:"weppy-roblox-mcp",version:Xe,tools:Fs.length,localHistoryEnabled:e.enableLocalHistory??!1,localStatisticsEnabled:e.enableLocalStatistics??!1,telemetryEnabled:e.enableTelemetry??!1,licenseApiConfigured:this.licenseStateManager.isConfigured()})}setupHandlers(){this.server.setRequestHandler(Wg,async()=>(v.debug("Received tools/list request"),{tools:Fs})),this.server.setRequestHandler(cs,async e=>{let{name:r,arguments:n}=e.params;v.info("Received tool call",{tool:r,args:n});try{let i=await this.executeTool(r,n||{});return i.success?{content:[{type:"text",text:i.data!==void 0?JSON.stringify(i.data,null,2):JSON.stringify({success:!0,message:"Command executed"})}]}:{content:[{type:"text",text:JSON.stringify({success:!1,error:i.error},null,2)}],isError:!0}}catch(i){return v.error("Tool execution failed",i),{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}})}async executeInternalAction(e,r,n){if(e==="get_cached_selection"){let o=r.maxAge,s=this.httpBridge.getCachedSelection(o!==void 0?o:3e4);if(!s)return{success:!0,data:{cached:!1,message:"No cached selection data available. The plugin may not be connected or no selection changes have occurred yet."}};let c=Date.now()-s.timestamp;return{success:!0,data:{cached:!0,selection:s.selection,count:s.count,timestamp:s.timestamp,age:c}}}if(e==="get_connection_info")return{success:!0,data:await this.httpBridge.getConnectionInfo()};if(!e.startsWith("sync_"))return{success:!1,error:`Unknown internal action: ${e}`,failureCode:"INTERNAL_FAILED"};if(!n&&this.httpBridge.getIsClientMode()){let o=await this.httpBridge.executeCommand(e,r),s={success:o.success};return o.data!==void 0&&(s.data=o.data),o.error&&(s.error=o.error),o.success||(s.failureCode="PROXY_FAILED"),s}let i=await Ld(e,r,n),a={success:i.success};return i.data!==void 0&&(a.data=i.data),i.error&&(a.error=i.error),i.success||(a.failureCode="INTERNAL_FAILED"),a}async executePluginBatch(e,r){let n={commands:e.map(({action:a,params:o})=>({action:a,params:o})),stopOnError:r},i=await this.httpBridge.executeCommand("batch_execute",n);if(i.success&&Array.isArray(i.data?.results)){let a=i.data.results;return e.map((o,s)=>{let c=a[s];if(!c)return{index:o.index,action:o.action,success:!1,error:"Batch result missing"};let l={index:o.index,action:o.action,success:c.success};return c.data!==void 0&&(l.data=c.data),c.error&&(l.error=c.error),l})}return e.map(a=>({index:a.index,action:a.action,success:!1,error:i.error||"Batch execution failed"}))}async executeTool(e,r){let n=Date.now();if(!Fs.find(c=>c.name===e)){let c=`Unknown tool: ${e}`,l=Date.now()-n;return await this.historyManager.recordFailure(e,r,c,l,"UNKNOWN_TOOL"),this.analyticsManager.trackToolCall(e,"basic",!1,void 0,"execution_failed","unknown_tool"),this.analyticsManager.trackToolError(e,"basic",void 0,"execution_failed","unknown_tool"),{success:!1,error:c}}let{action:a,params:o}=S_(e,r),s=Xa(a);if(s==="pro"){let c=await this.licenseStateManager.evaluateProAccess();if(!c.allowed){if(c.state.reason==="device_temporarily_blocked"){let l=Date.now()-n,u=c.reason||"This device is temporarily blocked";return await this.historyManager.recordFailure(e,r,u,l,"DEVICE_BLOCKED"),this.analyticsManager.trackToolCall(e,s,!1,a,"device_blocked"),this.analyticsManager.trackToolError(e,s,a,"device_blocked"),{success:!1,error:`${u} (status=${c.state.status})`}}this.analyticsManager.trackToolCall(e,s,!1,a,"delegated_to_plugin"),v.debug("Pro action delegated to plugin for fallback",{toolName:e,action:a})}}v.debug("Executing tool",{toolName:e,action:a,args:o,tier:s});try{let c=this.httpBridge.getSyncController(),l=k_(e,a,c);if(l.target==="internal"){let d=await this.executeInternalAction(a,o,c),f=Date.now()-n;return d.success?(await this.historyManager.recordSuccess(e,r,d.data,f),this.analyticsManager.trackToolCall(e,s,!0,a),{success:!0,data:d.data}):(await this.historyManager.recordFailure(e,r,d.error||"Internal operation failed",f,d.failureCode||"INTERNAL_FAILED"),this.analyticsManager.trackToolCall(e,s,!1,a,"execution_failed","other"),this.analyticsManager.trackToolError(e,s,a,"execution_failed","other"),{success:!1,error:d.error||"Internal operation failed"})}if(l.target==="disk"){let d=await m_(a,o,c),f=Date.now()-n;if(!d.success)v.warn("Disk execution failed, falling back to plugin",{action:a,error:d.error});else return await this.historyManager.recordSuccess(e,r,d.data,f),this.analyticsManager.trackToolCall(e,s,!0,a),{success:!0,data:d.data}}if(a==="batch_execute"&&Array.isArray(o.commands)){let d=o.commands,f=o.stopOnError!==!1,m=[],h=[],g=async()=>{if(h.length===0)return!1;let k=await this.executePluginBatch(h,f);h=[];let N=!1;for(let A of k)m[A.index]={action:A.action,success:A.success,data:A.data,error:A.error},!A.success&&f&&(N=!0);return N},b=!1;for(let k=0;k<d.length;k++){let N=d[k],A=N.params||{},Le=k_(N.toolName||"",N.action,c).target;if(Le==="plugin"){h.push({index:k,action:N.action,params:A,toolName:N.toolName});continue}if(await g()){b=!0;break}let $e;if(Le==="internal"){let ye=await this.executeInternalAction(N.action,A,c);$e={success:ye.success,data:ye.data,error:ye.error}}else{let ye=await m_(N.action,A,c);if(ye.success)$e={success:!0,data:ye.data};else{v.warn("Disk execution failed inside batch, falling back to plugin",{action:N.action,error:ye.error});let pt=await this.httpBridge.executeCommand(N.action,A);$e={success:pt.success,data:pt.data,error:pt.error}}}if(m[k]={action:N.action,success:$e.success,data:$e.data,error:$e.error},!$e.success&&f){b=!0;break}}b||(b=await g());let S=m.filter(Boolean),_=Date.now()-n,$=S.every(k=>k.success);return await this.historyManager.recordSuccess(e,r,{results:S},_),this.analyticsManager.trackToolCall(e,s,$,a),$||this.analyticsManager.trackToolError(e,s,a,"execution_failed","other"),{success:!0,data:{results:S,totalCommands:S.length}}}let u=await this.httpBridge.executeCommand(a,o),p=Date.now()-n;if(!u.success){let d=u.error||"Tool execution failed",{errorType:f,errorDetail:m}=aG(d);return await this.historyManager.recordFailure(e,r,d,p,f.toUpperCase()),this.analyticsManager.trackToolCall(e,s,!1,a,f,m),this.analyticsManager.trackToolError(e,s,a,f,m),f==="bridge_error"||f==="command_timeout"?v.warn(`Tool ${f}`,{toolName:e,action:a,errorDetail:m,error:d}):f==="plugin_runtime"&&v.warn("Plugin runtime error",{toolName:e,action:a,error:d}),{success:!1,error:d}}return await this.historyManager.recordSuccess(e,r,u.data,p),this.analyticsManager.trackToolCall(e,s,!0,a),{success:!0,data:u.data}}catch(c){let{errorType:l,errorDetail:u}=oG(c),p=Date.now()-n,d=c instanceof Error?c.message:"Unknown error";return v.error(`Tool ${l}`,{toolName:e,action:a,error:d}),await this.historyManager.recordFailure(e,r,d,p,l.toUpperCase()),this.analyticsManager.trackToolCall(e,s,!1,a,l,u),this.analyticsManager.trackToolError(e,s,a,l,u),{success:!1,error:d}}}async start(){v.info("Starting Weppy Roblox MCP Server"),await this.licenseStateManager.initialize(),await this.httpBridge.start();let e=this.httpBridge.getSessionId();try{await this.historyManager.initialize(e)}catch(a){v.warn("Tool history manager initialization failed, continuing without history/statistics",a)}this.analyticsManager.initialize(e);let r=this.licenseStateManager.getStatus();this.analyticsManager.setTier(r.canUsePro?"pro":"basic"),this.analyticsManager.trackSessionStart();let n=Jd();n&&(this.httpBridge.setAiClientInfo(n.name,n.version),v.info("AI client detected via environment",{name:n.name})),this.server.oninitialized=()=>{try{let a=this.server.getClientVersion();if(a?.name){let o=_j(a.name),s=a.version||"";this.httpBridge.setAiClientInfo(o,s),v.info("AI client detected via MCP protocol",{raw:a.name,display:o,version:s})}}catch(a){v.debug("Could not detect AI client info from protocol",{error:a})}};let i=new Mu;await this.server.connect(i),v.info("MCP Server started successfully",{transport:"stdio",httpBridge:`${this.config.httpHost}:${this.config.httpPort}`,sessionId:e})}async stop(){v.info("Stopping MCP Server"),this.analyticsManager.trackSessionEnd(),await this.analyticsManager.shutdown(),await this.historyManager.shutdown(),await this.httpBridge.stop(),await this.licenseStateManager.shutdown(),await this.server.close(),v.info("MCP Server stopped")}},Pj=T_;function C_(t,e){return t===void 0||t===""?e:t.toLowerCase()==="true"||t==="1"}function sG(){let t={httpPort:parseInt(process.env.HTTP_PORT||"3002",10),httpHost:process.env.HTTP_HOST||"127.0.0.1",logLevel:process.env.LOG_LEVEL||"info",requestTimeout:parseInt(process.env.REQUEST_TIMEOUT||"30000",10),enableLocalHistory:C_(process.env.ENABLE_LOCAL_HISTORY,!1),enableLocalStatistics:C_(process.env.ENABLE_LOCAL_STATISTICS,!1),dataDir:process.env.DATA_DIR||On(),enableTelemetry:C_(process.env.ENABLE_TELEMETRY,!1),licenseApiBaseUrl:process.env.LICENSE_API_BASE_URL||"https://roblox-license-api.hope841026.workers.dev",licenseProjectId:process.env.LICENSE_PROJECT_ID||"roblox-mcp",licenseProvider:process.env.LICENSE_PROVIDER||"gumroad",licenseRequestTimeoutMs:parseInt(process.env.LICENSE_REQUEST_TIMEOUT_MS||"8000",10),licenseRetryCount:parseInt(process.env.LICENSE_RETRY_COUNT||"1",10),licenseRetryDelayMs:parseInt(process.env.LICENSE_RETRY_DELAY_MS||"300",10),licenseFallbackMaxHours:parseInt(process.env.LICENSE_FALLBACK_MAX_HOURS||"48",10)};return v.setLevel(t.logLevel),t}async function cG(){try{let t=sG();v.info("Weppy Roblox MCP Server",{version:Xe,config:{httpHost:t.httpHost,httpPort:t.httpPort,logLevel:t.logLevel,requestTimeout:t.requestTimeout,enableLocalHistory:t.enableLocalHistory,enableLocalStatistics:t.enableLocalStatistics,enableTelemetry:t.enableTelemetry,dataDir:t.dataDir,licenseApiBaseUrl:t.licenseApiBaseUrl,licenseProjectId:t.licenseProjectId,licenseProvider:t.licenseProvider,licenseRequestTimeoutMs:t.licenseRequestTimeoutMs,licenseRetryCount:t.licenseRetryCount,licenseRetryDelayMs:t.licenseRetryDelayMs,licenseFallbackMaxHours:t.licenseFallbackMaxHours}});let e=new Pj(t);await e.start();let r=async n=>{v.info(`Received ${n}, shutting down gracefully`);try{await e.stop(),process.exit(0)}catch(i){v.error("Error during shutdown",i),process.exit(1)}};process.on("SIGINT",()=>r("SIGINT")),process.on("SIGTERM",()=>r("SIGTERM")),process.on("uncaughtException",n=>{v.error("Uncaught exception",n),process.exit(1)}),process.on("unhandledRejection",n=>{v.error("Unhandled rejection",n),process.exit(1)})}catch(t){v.error("Failed to start server",t),process.exit(1)}}cG();
130
130
  /*! Bundled license information:
131
131
 
132
132
  depd/index.js: