thinkncollab-shell 2.0.0-beta.5 → 2.0.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/Readme.md +64 -17
  2. package/dist/bin/thinknsh.js +1 -1
  3. package/dist/src/commands/auth/auth.js +7 -0
  4. package/dist/src/commands/auth/login.js +1 -1
  5. package/dist/src/commands/auth/logout.js +1 -1
  6. package/dist/src/commands/auth/register.js +1 -1
  7. package/dist/src/commands/auth/whoami.js +1 -1
  8. package/dist/src/commands/dock/cluster.js +1 -1
  9. package/dist/src/commands/dock/containers.js +1 -1
  10. package/dist/src/commands/index.js +1 -1
  11. package/dist/src/commands/room/connect.js +7 -0
  12. package/dist/src/commands/room/join.js +1 -1
  13. package/dist/src/commands/room/sync.js +1 -1
  14. package/dist/src/commands/sandbox/common.js +7 -0
  15. package/dist/src/commands/sandbox/console.js +1 -1
  16. package/dist/src/commands/sandbox/dep.js +1 -1
  17. package/dist/src/commands/sandbox/deploy.js +1 -1
  18. package/dist/src/commands/sandbox/dev.js +1 -1
  19. package/dist/src/commands/sandbox/exec.js +1 -1
  20. package/dist/src/commands/sandbox/push.js +1 -1
  21. package/dist/src/commands/sandbox/sandbox.js +1 -1
  22. package/dist/src/commands/sandbox/share.js +1 -1
  23. package/dist/src/commands/sandbox/snapshot.js +1 -1
  24. package/dist/src/commands/shell/doctor.js +1 -1
  25. package/dist/src/commands/tasks/build.js +1 -1
  26. package/dist/src/commands/terminal/share.js +1 -1
  27. package/dist/src/core/api-client.js +1 -1
  28. package/dist/src/core/auth/browser-login.js +7 -0
  29. package/dist/src/core/auth/gate.js +7 -0
  30. package/dist/src/core/auth/session-store.js +7 -0
  31. package/dist/src/core/crypto/seal.js +7 -0
  32. package/dist/src/core/dependency-tracker.js +1 -1
  33. package/dist/src/core/foreground.js +7 -0
  34. package/dist/src/core/platform-socket.js +7 -0
  35. package/dist/src/core/room/sync-manager.js +7 -0
  36. package/dist/src/core/room-link.js +7 -0
  37. package/dist/src/core/shell.js +1 -1
  38. package/dist/src/core/{sandbox/snapshot-manager.js → snapshot-manager.js} +1 -1
  39. package/dist/src/core/tunnel/client.js +7 -0
  40. package/dist/src/core/tunnel/daemon.js +7 -0
  41. package/dist/src/core/tunnel/index.js +7 -0
  42. package/dist/src/core/websocket.js +1 -1
  43. package/dist/src/core/window-launcher.js +7 -0
  44. package/dist/src/dock/bench/memory.js +1 -1
  45. package/dist/src/dock/cli/commands.js +1 -1
  46. package/dist/src/dock/cli/k8s.js +7 -0
  47. package/dist/src/dock/cli/services.js +1 -1
  48. package/dist/src/dock/cli/session.js +1 -1
  49. package/dist/src/dock/cli/workspace.js +1 -1
  50. package/dist/src/dock/engine.js +1 -1
  51. package/dist/src/dock/image/certs.js +7 -0
  52. package/dist/src/dock/image/registry.js +1 -1
  53. package/dist/src/dock/room-data.js +7 -0
  54. package/dist/src/dock/runtime/network.js +1 -1
  55. package/dist/src/dock/sandbox.js +7 -0
  56. package/dist/src/dock/setup/index.js +1 -1
  57. package/dist/src/dock/workspace/share.js +7 -0
  58. package/dist/src/dock/workspace/workspace.js +1 -1
  59. package/package.json +1 -1
  60. package/dist/src/core/sandbox/arch-shim.js +0 -7
  61. package/dist/src/core/sandbox/capsule-builder.js +0 -7
  62. package/dist/src/core/sandbox/cgroups.js +0 -7
  63. package/dist/src/core/sandbox/container-monitor.js +0 -7
  64. package/dist/src/core/sandbox/crun-manager.js +0 -7
  65. package/dist/src/core/sandbox/engine.js +0 -7
  66. package/dist/src/core/sandbox/image-manager.js +0 -7
  67. package/dist/src/core/sandbox/image-store.js +0 -7
  68. package/dist/src/core/sandbox/index.js +0 -7
  69. package/dist/src/core/sandbox/macos-sandbox.js +0 -7
  70. package/dist/src/core/sandbox/microvm-manager.js +0 -7
  71. package/dist/src/core/sandbox/native-service-manager.js +0 -7
  72. package/dist/src/core/sandbox/network.js +0 -7
  73. package/dist/src/core/sandbox/oci-exporter.js +0 -7
  74. package/dist/src/core/sandbox/oci-puller.js +0 -7
  75. package/dist/src/core/sandbox/oci-runtime-spec.js +0 -7
  76. package/dist/src/core/sandbox/port-proxy-daemon.js +0 -7
  77. package/dist/src/core/sandbox/room-sync-manager.js +0 -7
  78. package/dist/src/core/sandbox/runtime-manager.js +0 -7
  79. package/dist/src/core/sandbox/storage.js +0 -7
  80. package/dist/src/core/sandbox/supervisor.js +0 -7
  81. package/dist/src/core/sandbox/tunnel-daemon.js +0 -7
  82. package/dist/src/core/sandbox/tunnel.js +0 -7
  83. package/dist/src/core/sandbox/window-launcher.js +0 -7
  84. package/dist/src/core/sandbox/windows-wsl2.js +0 -7
  85. package/dist/src/core/sandbox-window.js +0 -8
  86. /package/dist/src/core/{sandbox/policy-engine.js → policy-engine.js} +0 -0
@@ -4,4 +4,4 @@
4
4
  * PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
5
5
  * UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
6
6
  */
7
- const path=require("path"),fs=require("fs-extra"),os=require("os"),chalk=require("chalk"),net=require("net"),{spawn}=require("child_process"),EventEmitter=require("events"),WebSocketManager=require("./websocket"),CommandRegistry=require("../commands"),ThinkNCollabAPI=require("./api-client"),{loadProjectConfig,hasProjectConfig}=require("./project-config"),{SandboxEngine}=require("./sandbox");function getAvailablePort(e){return new Promise(t=>{const o=net.createServer();o.listen(e,"127.0.0.1",()=>{const e=o.address().port;o.close(()=>t(e))}),o.on("error",()=>t(getAvailablePort(e+1)))})}class ThinkNCollabShell extends EventEmitter{constructor(e={}){super(),this.config={historyFile:path.join(os.homedir(),".thinknsh_history"),configDir:path.join(os.homedir(),".thinkncollab"),maxHistory:1e3,websocket:{serverUrl:e.serverUrl||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com",autoConnect:e.autoConnect||!1},...e},fs.ensureDirSync(this.config.configDir),this.ws=new WebSocketManager({...this.config.websocket,_shellRef:this}),this.currentDir=process.cwd(),this.username=os.userInfo().username,this.history=[],this.running=!1,this.notifyClients=new Set,this.tcpServer=null,this.api=new ThinkNCollabAPI({apiUrl:e.apiUrl||process.env.THINKNCOLLAB_API_URL||e.serverUrl||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com",wsUrl:e.wsUrl||process.env.THINKNCOLLAB_WS_URL||e.serverUrl||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com"}),this.api.getCurrentRoom=()=>{const e=this.ws.getCurrentRoom();return e?{id:e,name:e}:null},this.commands=new Map,this.aliases=new Map,this.variables=new Map,this.sandboxEngine=new SandboxEngine,this.activeSandbox=null,this.api.isAuthenticated()&&this.ws.setUser(this.api.getUser(),this.api.getToken()),this.loadHistory(),this.registerBuiltinCommands(),this.commandRegistry=new CommandRegistry(this),this.setupWebSocketHandlers()}async startTCPServer(){this.notifyPort=await getAvailablePort(7379);const e=require("./notify-auth");try{this.notifyToken=e.writeToken(this.notifyPort)}catch(e){this.notifyToken=null}return new Promise(t=>{this.tcpServer=net.createServer(t=>{t.on("error",()=>this.notifyClients.delete(t)),this.notifyToken?e.guard(t,this.notifyToken,()=>{this.notifyClients.add(t),t.on("close",()=>this.notifyClients.delete(t))}):t.destroy()}),this.tcpServer.listen(this.notifyPort,"127.0.0.1",()=>t()),this.tcpServer.on("error",e=>{console.log(chalk.yellow(`[Notice] Notification server: ${e.message}`)),t()})})}pushNotification(e){if(0===this.notifyClients.size)return;const t=JSON.stringify(e)+"\n";for(const e of this.notifyClients)try{e.write(t)}catch{}if("darwin"===process.platform)try{let t="";if("message"===e.type?t=`${e.from}: ${e.text}`:"task"===e.type?t=e.message||e.title||"Task update":"task_assigned"===e.type?t=`Task assigned: ${e.taskTitle||""}`:"verdict"===e.type?t=`Verdict [${e.passed?"PASSED":"FAILED"}]: ${e.title||""}`:"notification"===e.type&&(t=`${e.title||""}: ${e.message||""}`),t){const e=t.replace(/["\\]/g," ").slice(0,150);spawn("osascript",["-e",`display notification "${e}" with title "ThinkNCollab"`],{detached:!0,stdio:"ignore"}).unref()}}catch(e){}(this.isVSCode||0===this.notifyClients.size)&&this.printInlineNotification(e)}printInlineNotification(e){const t=chalk.dim(`[${(new Date).toLocaleTimeString()}]`);switch(e.type){case"message":console.log(`\n${t} ${chalk.cyan("[Chat]")} ${chalk.bold(e.from)}: ${e.text}`);break;case"task_assigned":console.log(`\n${t} ${chalk.yellow("[Task]")} Assigned: ${chalk.white(e.taskTitle)} by ${chalk.cyan(e.assignedBy||"Admin")}`);break;case"task_completed":console.log(`\n${t} ${chalk.green("[Task Done]")} ${chalk.white(e.message||e.taskTitle)}`);break;case"verdict":break;case"notification":{const o="error"===e.level?chalk.red:"warn"===e.level?chalk.yellow:chalk.blue;console.log(`\n${t} ${o(`[Alert: ${e.title||"Info"}]`)} ${e.message}`);break}case"userJoined":console.log(`\n${t} ${chalk.dim(`[User+] ${e.username} joined`)}`);break;case"userLeft":console.log(`\n${t} ${chalk.dim(`[User-] ${e.username} left`)}`)}}spawnNotificationWindow(){const e=path.join(__dirname,"notification-window.js"),t=this.notifyPort,o={...process.env,THINKNSH_NOTIFY_PORT:String(t)},s="vscode"===process.env.TERM_PROGRAM||"1"===process.env.VSCODE_INJECTION||Boolean(process.env.VSCODE_PID)||Boolean(process.env.VSCODE_GIT_IPC_HANDLE)||Boolean(process.env.VSCODE_IPC_HOOK_CLI);if(this.isVSCode=s,s)return console.log(chalk.cyan(` [VS Code Integrated Mode] Live notification server active on port ${t}`)),console.log(chalk.dim(" - Inline notifications: ENABLED (alerts & chat display directly in VS Code)")),void console.log(chalk.dim(' - Split panel view: Run "thinknsh notifications" in a split terminal tab (Cmd+\\)\n'));const n="linux"===process.platform&&(()=>{try{return fs.readFileSync("/proc/version","utf8").toLowerCase().includes("microsoft")}catch{return!1}})(),i=()=>{console.log(chalk.yellow("\n [Notice] Could not open notification window.")),console.log(chalk.dim(" Open a new terminal and run:")),console.log(chalk.cyan(` THINKNSH_NOTIFY_PORT=${t} node "${e}"\n`))};try{if(n){const s=e.replace(/^\/mnt\/([a-z])/,(e,t)=>`${t.toUpperCase()}:`).replace(/\//g,"\\"),n=e=>{const t=spawn(`start cmd.exe /k ""${e}" "${s}""`,[],{detached:!0,stdio:"ignore",env:o,shell:!0});t.on("error",i),t.unref()},a=["/mnt/c/Program Files/nodejs/node.exe","/mnt/c/Program Files (x86)/nodejs/node.exe"].find(e=>{try{return fs.existsSync(e)}catch{return!1}});if(a){const e=a.replace(/^\/mnt\/([a-z])/,(e,t)=>`${t.toUpperCase()}:`).replace(/\//g,"\\");n(e)}else{const o=spawn("cmd.exe",["/c","where","node"],{stdio:["ignore","pipe","ignore"]});let s="";o.stdout.on("data",e=>s+=e),o.on("close",()=>{const o=s.trim().split("\n")[0]?.trim();if(o?.endsWith(".exe"))n(o);else{const o=spawn(`start cmd.exe /k "wsl.exe -- bash -c 'THINKNSH_NOTIFY_PORT=${t} node \\"${e}\\"; read'"`,[],{detached:!0,stdio:"ignore",shell:!0});o.on("error",i),o.unref()}})}}else if("win32"===process.platform){const s=process.execPath;if("vscode"===process.env.TERM_PROGRAM)console.log(chalk.yellow("\n VS Code detected — open a new terminal tab and run:")),console.log(chalk.cyan(` $env:THINKNSH_NOTIFY_PORT=${t}; node "${e}"\n`));else{const t=()=>{const t=spawn(`start cmd.exe /k ""${s}" "${e}""`,[],{detached:!0,stdio:"ignore",env:o,shell:!0});t.on("error",i),t.unref()},n=spawn("wt.exe",["cmd.exe","/k",`"${s}" "${e}"`],{detached:!0,stdio:"ignore",env:o});n.on("error",t),n.unref()}}else if("darwin"===process.platform){const o=process.execPath,s=`\n tell application "Terminal"\n launch\n do script "THINKNSH_NOTIFY_PORT=${t} '${o}' '${e}'"\n activate\n end tell\n `;try{spawn("osascript",["-e",s],{detached:!0,stdio:"ignore"}).unref()}catch{const s=`\n tell application "iTerm2"\n tell current window\n create tab with default profile\n tell current session of current tab\n write text "THINKNSH_NOTIFY_PORT=${t} '${o}' '${e}'"\n end tell\n end tell\n end tell\n `;try{spawn("osascript",["-e",s],{detached:!0,stdio:"ignore"}).unref()}catch{console.log(chalk.yellow(" [Notice] Could not auto-open notification window.")),console.log(chalk.dim(` Open Terminal.app and run: THINKNSH_NOTIFY_PORT=${t} node "${e}"`))}}}else{const s=process.execPath,n=`THINKNSH_NOTIFY_PORT=${t} '${s}' '${e}'; read`,a=[["gnome-terminal",["--","bash","-c",n]],["konsole",["--noclose","-e","bash","-c",n]],["xfce4-terminal",["--hold","-e",`bash -c "${n}"`]],["tilix",["-e",`bash -c "${n}"`]],["alacritty",["-e","bash","-c",n]],["kitty",["bash","-c",n]],["xterm",["-hold","-e",`bash -c "${n}"`]]];let c=!1;for(const[e,t]of a)try{spawn(e,t,{detached:!0,stdio:"ignore",env:o}).unref(),c=!0;break}catch{}c||i()}}catch(e){i()}}setupWebSocketHandlers(){this.ws.on("connected",e=>{this.api.logEvent("connected",null,{socketId:e?.socketId}),this.api.startHeartbeat(),this.pushNotification({type:"connected",socketId:e?.socketId})}),this.ws.on("disconnected",e=>{this.api.logEvent("disconnected",null,{reason:e?.reason}),this.api.stopHeartbeat(),this.pushNotification({type:"disconnected",reason:e?.reason})}),this.ws.on("reconnected",()=>{this.api.logEvent("reconnected"),this.api.startHeartbeat(),this.pushNotification({type:"connected",socketId:"reconnected"})}),this.ws.on("task:verdict",e=>{const t=e.passed,o=t?chalk.green("─".repeat(52)):chalk.red("─".repeat(52)),s=t?chalk.bgGreen.black(" PASS "):chalk.bgRed.white(" FAIL ");console.log("\n"+o),console.log(` ${s} ${chalk.bold(e.title||"Task")} ${t?chalk.green("PASSED"):chalk.red("FAILED")}`),console.log(` ${chalk.dim("Reason :")} ${t?chalk.green(e.reason):chalk.red(e.reason)}`),e.diff&&(console.log(` ${chalk.dim("Expected:")} ${chalk.cyan(JSON.stringify(e.diff.expected??e.diff.conditions))}`),console.log(` ${chalk.dim("Actual :")} ${chalk.yellow(JSON.stringify(e.diff.actual))}`)),console.log(` ${chalk.dim("At :")} ${chalk.dim(new Date(e.at||Date.now()).toLocaleTimeString())}`),console.log(o+"\n"),this.pushNotification({type:"verdict",passed:t,title:e.title,reason:e.reason,taskId:e.taskId,at:e.at})}),this.ws.on("message",e=>{const t=this.api.getUser()?._id;if(t&&e.userId===t)return;const o=e.name||e.username||e.sender?.name||"Someone",s=e.message||e.content||e.text||"",n=chalk.dim(`[${(new Date).toLocaleTimeString()}]`),i=e.isE2EE?chalk.green(" [🔒 E2EE]"):"";console.log(`\n${n}${i} ${chalk.cyan(o+":")} ${chalk.white(s)}`),this.pushNotification({type:"message",from:o,text:s})}),this.ws.on("messageHistory",e=>{e?.length&&(console.log(chalk.dim("\n ─── Room History ───────────────────────────────")),e.forEach(e=>{const t=e.name||e.username||e.sender?.name||"Unknown",o=e.message||e.content||e.text||"",s=e.timestamp||e.createdAt,n=s?chalk.dim(`[${new Date(s).toLocaleTimeString()}]`):"",i=e.isE2EE?chalk.green(" [🔒 E2EE]"):"";console.log(` ${n}${i} ${chalk.cyan(t+":")} ${chalk.white(o)}`)}),console.log(chalk.dim(" ────────────────────────────────────────────────\n")))}),this.ws.on("userJoined",e=>{console.log(chalk.dim(`\n [User+] ${e.username} joined the room`)),this.pushNotification({type:"userJoined",username:e.username})}),this.ws.on("userLeft",e=>{console.log(chalk.dim(`\n [User-] ${e.username} left the room`)),this.pushNotification({type:"userLeft",username:e.username})}),this.ws.on("joined",e=>this.api.logEvent("room_join",e)),this.ws.on("left",e=>this.api.logEvent("room_leave",e)),this.ws.on("notification",e=>{const t=chalk.dim(`[${(new Date).toLocaleTimeString()}]`),o={success:chalk.green,warning:chalk.yellow,error:chalk.red,info:chalk.blue}[e.level]||chalk.blue;console.log(`\n${t} [ALERT] ${o(`${e.title||"Notification"}: ${e.message||""}`)}`),this.pushNotification({type:e.type||"notification",level:e.level,title:e.title,message:e.message,taskTitle:e.taskTitle,assignedBy:e.assignedBy,meta:e.meta})});let e=0;this.ws.on("error",t=>{const o=Date.now();o-e>3e4&&(e=o,console.log(chalk.yellow(`[Notice] WebSocket reconnecting: ${t.message}`))),this.pushNotification({type:"notification",level:"error",title:"WebSocket Error",message:t.message})}),this.ws.onSandboxAction(this.sandboxEngine,(e,t)=>{t?.success?console.log(chalk.cyan(`\n[Web Dashboard Action] ${e.toUpperCase()} completed: ${t.message}`)):console.log(chalk.yellow(`\n[Web Dashboard Action] ${e.toUpperCase()} failed: ${t?.message}`))})}async start(){this.showWelcome(),await this.startTCPServer(),console.log(chalk.cyan(` Notification server active on port ${chalk.bold(this.notifyPort)}`)),console.log(chalk.dim(" To open live notification panel in another tab/terminal:")),console.log(chalk.white(` thinknsh notify ${this.notifyPort}\n`)),this.running=!0,process.on("SIGINT",()=>console.log(chalk.yellow('\nUse "exit" to quit'))),!await this._bootFromProjectConfig()&&this.config.websocket.autoConnect&&await this.ws.connect().catch(()=>{});const{input:e}=require("@inquirer/prompts");for(;this.running;)try{const t=await e({message:this.getPromptText(),required:!1}).catch(()=>null);if(null===t){console.log(chalk.yellow('Use "exit" to quit'));continue}const o=t.trim();if(!o)continue;this.history.push(o),this.saveHistory(),await this.execute(o)}catch(e){"ExitPromptError"===e.name||e.message?.includes("force closed")?console.log(chalk.yellow('Use "exit" to quit')):console.log(chalk.red(`Shell error: ${e.message}`))}}async _bootFromProjectConfig(){if(!hasProjectConfig())return!1;let e,t;try{e=loadProjectConfig()}catch(e){return console.log(chalk.yellow(`[Notice] tncproject: ${e.message}\n`)),!1}console.log(chalk.dim(" [Project] tncproject found")),console.log(chalk.dim(` Room : ${e.roomId}`)),console.log(chalk.dim(` File : ${e.filePath}\n`));try{t=await this.api._request("POST","/config/verify-config",{fileBase64:e.fileBase64,roomId:e.roomId,machineId:os.hostname()})}catch(e){return console.log(chalk.red(`[Error] tncproject auth failed: ${e.message}`)),console.log(chalk.dim(" Falling back to manual login.\n")),!1}this.api._saveSession({token:t.shellToken,shellSessionId:t.shellSessionId||null,user:{_id:t._id.toString(),userId:t._id.toString(),email:t.email,name:t.name,userType:"User"},timestamp:(new Date).toISOString()}),this.ws.setUser(this.api.getUser(),t.shellToken);try{await this.ws.connect(t.shellToken)}catch(e){return console.log(chalk.yellow(`[Notice] WebSocket: ${e.message}`)),console.log(chalk.green(`[Auth] ${t.name} logged in (offline mode)\n`)),!0}try{await this.ws.joinRoom(t.roomId.toString()),console.log(chalk.green(`[Room] ${t.name} -> room "${t.roomName||t.roomId}"\n`))}catch(e){console.log(chalk.yellow(`[Notice] Could not auto-join room: ${e.message}`)),console.log(chalk.dim(' Use "join <room-id>" manually.\n'))}return!0}async execute(e,t=null){if(!(e.trim()||t&&0!==t.length))return;const o=t&&t.length>0?t:this.parseCommand(e),s=o[0].toLowerCase(),n=o.slice(1);if(["login","logout","set","unset"].includes(s)||this.api.logEvent("command",null,{cmd:s}).catch(()=>{}),this.commands.has(s)){try{await this.commands.get(s).handler(n,this)}catch(e){console.log(chalk.red(`Error: ${e.message}`))}process.stdout.write("\r\n")}else{if(!this.commandRegistry||!await this.commandRegistry.execute(e,this,t))return"win32"===process.platform&&["open","which","grep","touch","cat"].includes(s)?(console.log(chalk.red(`'${s}' is not a thinknsh command.`)),console.log(chalk.dim(" Type 'help' to see available commands.")),void process.stdout.write("\r\n")):void await this.executeSystemCommand(e);this.commandRegistry.getCommand(s)?.selfExiting||process.stdout.write("\r\n")}}getPromptText(){let e="";const t=this.ws.getCurrentRoom?.();t&&(e+=`[${t}] `),this.ws.isConnected?.()&&(e+="● "),this.activeSandbox&&(e+=`[Sandbox: ${this.activeSandbox}] `);let o=this.currentDir;const s=os.homedir();return o="win32"===process.platform?o.toLowerCase()===s.toLowerCase()?"~":o.toLowerCase().startsWith(s.toLowerCase())?"~"+o.slice(s.length).replace(/\\/g,"/"):o.replace(/\\/g,"/"):o.replace(s,"~"),`${e}${this.username}@thinknsh:${o} $`}getPrompt(){return this.getPromptText()}printMessage(e){console.log(e)}registerBuiltinCommands(){const e=(e,t,o)=>this.commands.set(e,{handler:t,description:o});e("help",this.helpCommand.bind(this),"Show available commands"),e("exit",()=>{this.cleanup(),process.exit(0)},"Exit the shell"),e("quit",()=>{this.cleanup(),process.exit(0)},"Exit the shell"),e("clear",()=>console.clear(),"Clear screen"),e("history",this.historyCommand.bind(this),"Show command history"),e("cd",this.cdCommand.bind(this),"Change directory"),e("pwd",()=>console.log(process.cwd()),"Print working directory"),e("ls",this.lsCommand.bind(this),"List files"),e("use",e=>{const t=e[0];t?(this.activeSandbox=t,console.log(chalk.green(`\n[Sandbox] Switched active execution context to: [${t}]`)),console.log(chalk.dim(" All system commands will now execute within this isolated sandbox.\n"))):console.log(chalk.red("Usage: use <sandbox-id>"))},"Switch active shell context to a TNC sandbox"),e("leave-sandbox",()=>{this.activeSandbox?(console.log(chalk.cyan(`\nExited sandbox [${this.activeSandbox}]. Back to host shell.\n`)),this.activeSandbox=null):console.log(chalk.yellow("Not currently inside any sandbox context."))},"Exit active sandbox context"),e("connect",this.connectCommand.bind(this),"Connect to WebSocket"),e("disconnect",()=>this.ws.disconnect(),"Disconnect WebSocket"),e("status",this.statusCommand.bind(this),"Show connection status"),e("join",this.joinCommand.bind(this),"Join a room"),e("leave",()=>this.ws.leaveRoom?.(),"Leave current room"),e("rooms",this.roomsCommand.bind(this),"List rooms"),e("say",this.sayCommand.bind(this),"Send a message"),e("msg",this.sayCommand.bind(this),"Send a message"),e("set",this.setCommand.bind(this),"Set a variable"),e("unset",this.unsetCommand.bind(this),"Unset a variable"),this.addAlias("ll","ls -la"),this.addAlias("..","cd .."),this.addAlias("~","cd ~"),this.addAlias("unuse","leave-sandbox")}registerCommand(e,t,o=""){this.commands.set(e,{handler:t,description:o})}addAlias(e,t){this.aliases.set(e,t)}async helpCommand(){this.commandRegistry&&console.log(this.commandRegistry.getHelpText()),console.log(chalk.dim(" Any unrecognised command runs in your system shell\n"))}historyCommand(){this.history.forEach((e,t)=>console.log(`${(t+1).toString().padStart(4)} ${e}`))}async cdCommand(e){const t=e.join(" ").trim()||os.homedir();try{let e;if(e="~"===t?os.homedir():".."===t?path.dirname(this.currentDir):path.resolve(this.currentDir,t),e=path.normalize(e),!(await fs.stat(e)).isDirectory())return void console.log(chalk.red(`cd: not a directory: ${t}`));this.previousDir=this.currentDir,process.chdir(e),this.currentDir=process.cwd()}catch{console.log(chalk.red(`cd: no such directory: ${t}`))}}async lsCommand(e){try{const t=await fs.readdir(this.currentDir),o=e.includes("-a")?t:t.filter(e=>!e.startsWith("."));(await Promise.all(o.map(async e=>({name:e,isDir:(await fs.stat(path.join(this.currentDir,e))).isDirectory()})))).forEach(e=>e.isDir?console.log(chalk.blue(e.name+"/")):console.log(e.name))}catch(e){console.log(chalk.red(`ls: ${e.message}`))}}async connectCommand(){console.log(chalk.cyan("Connecting..."));try{await this.ws.connect()}catch(e){console.log(chalk.red(`Error: ${e.message}`))}}async statusCommand(){const e=this.ws.isConnected();console.log(chalk.cyan("\nStatus:")),console.log(` WebSocket : ${e?chalk.green("Connected"):chalk.red("Disconnected")}`),e&&console.log(` Socket ID : ${chalk.dim(this.ws.getSocketId())}`);const t=this.ws.getCurrentRoom();console.log(` Room : ${t?chalk.green(t):chalk.yellow("None")}`),console.log(` Auth : ${this.api.isAuthenticated()?chalk.green("Logged in"):chalk.yellow("Not logged in")}`);const o=this.api.getUser();o&&console.log(` User : ${chalk.dim(o.name||o.email)}`),console.log("")}async joinCommand(e){if(e[0]){this.ws.isConnected()||(console.log(chalk.yellow("Connecting...")),await this.ws.connect());try{const t=await this.ws.joinRoom(e[0]);console.log(chalk.green(`Joined: ${t.roomName||e[0]}`))}catch(e){console.log(chalk.red(`Error: ${e.message}`))}}else console.log(chalk.red("Usage: join <room-id>"))}async roomsCommand(){this.ws.isConnected()?(this.ws.socket.emit("rooms:list"),this.ws.socket.once("rooms:list",e=>{e.rooms?.length?e.rooms.forEach(e=>console.log(` ${chalk.cyan(e.name||e.id)} - Members: ${e.participantCount||0}`)):console.log(" No rooms")})):console.log(chalk.yellow("Not connected"))}async sayCommand(e){const t=e.join(" ");if(t)try{await this.ws.sendMessage(t);const e=this.api.getUser()?.name||this.username;console.log(chalk.green(`You: ${t}`)),this.pushNotification({type:"message",from:`You (${e})`,text:t})}catch(e){console.log(chalk.red(`Failed: ${e.message}`))}else console.log(chalk.red("Usage: say <message>"))}async setCommand(e){if(e.length)this.variables.set(e[0],e.slice(1).join(" ")),console.log(chalk.green(`${e[0]} = ${e.slice(1).join(" ")}`));else for(const[e,t]of this.variables)console.log(` ${chalk.green(e.padEnd(12))} = ${t}`)}async unsetCommand(e){e[0]&&this.variables.delete(e[0])}getSessionPath(){return path.join(this.config.configDir,"session.json")}parseCommand(e){const t=[];let o="",s=!1,n="",i=!1;for(const a of e)"\\"!==a||i||s?i?(o+=a,i=!1):'"'!==a&&"'"!==a||s?a===n&&s?(s=!1,n=""):" "!==a||s?o+=a:o&&(t.push(o),o=""):(s=!0,n=a):i=!0;return o&&t.push(o),t}executeSystemCommand(e){return new Promise(t=>{let o=e,s={...process.env},n=this.currentDir;if(this.activeSandbox){const t=this.sandboxEngine?.loadMeta(this.activeSandbox);t&&(s.TNC_SANDBOX_ID=this.activeSandbox,t.containerPort&&(s.PORT=String(t.containerPort))),console.log(chalk.cyan(`[TNC Sandbox: ${this.activeSandbox}] ${e}`))}const i=spawn(o,[],{cwd:n,shell:!0,stdio:"inherit",env:s});i.on("close",async o=>{try{this.currentDir=process.cwd()}catch{}if(0===o)try{const t=new(require("./dependency-tracker"))(n);await t.interceptCommand(e,this)}catch(e){}process.stdout.write("\r\n"),t()}),i.on("error",()=>{console.log(chalk.red(`Command not found: ${e}`)),t()})})}showWelcome(){try{const e=require("figlet");console.log(chalk.cyan(e.textSync("ThinkNCollab",{font:"Standard"})))}catch{}console.log(chalk.cyan("\n╔══════════════════════════════════════════════════════╗\n║ ThinkNCollab Shell v0.0.20 ║\n║ Type 'help' for commands ║\n╚══════════════════════════════════════════════════════╝"))}loadHistory(){try{fs.existsSync(this.config.historyFile)&&(this.history=fs.readFileSync(this.config.historyFile,"utf8").split("\n").filter(e=>e.trim()))}catch{}}saveHistory(){try{fs.writeFileSync(this.config.historyFile,this.history.slice(-this.config.maxHistory).join("\n"))}catch{}}cleanup(){if(this.activeSandbox)try{this.sandboxEngine.freeze(this.activeSandbox),console.log(chalk.cyan(`\n[TNC Smart Freeze] Auto-froze active sandbox [${this.activeSandbox}]. 0 RAM used!`))}catch(e){}if(this.api.logout(),this.tcpServer)try{this.tcpServer.close()}catch{}this.notifyPort&&require("./notify-auth").removeToken(this.notifyPort),this.ws&&this.ws.disconnect(),this.saveHistory()}}module.exports=ThinkNCollabShell;
7
+ const path=require("path"),fs=require("fs-extra"),os=require("os"),chalk=require("chalk"),net=require("net"),{spawn}=require("child_process"),EventEmitter=require("events"),WebSocketManager=require("./websocket"),CommandRegistry=require("../commands"),ThinkNCollabAPI=require("./api-client"),{loadProjectConfig,hasProjectConfig}=require("./project-config"),{DockSandbox}=require("../dock/sandbox"),foreground=require("./foreground"),SECRET_LINE=/^(login|signin|auth\s+login)\b|--password|--token|THINKNCOLLAB_TOKEN|x-password/i;function getAvailablePort(e){return new Promise(t=>{const o=net.createServer();o.listen(e,"127.0.0.1",()=>{const e=o.address().port;o.close(()=>t(e))}),o.on("error",()=>t(getAvailablePort(e+1)))})}class ThinkNCollabShell extends EventEmitter{constructor(e={}){super(),this.config={historyFile:path.join(os.homedir(),".thinknsh_history"),configDir:path.join(os.homedir(),".thinkncollab"),maxHistory:1e3,websocket:{serverUrl:e.serverUrl||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com",autoConnect:e.autoConnect||!1},...e},fs.ensureDirSync(this.config.configDir),this.ws=new WebSocketManager({...this.config.websocket,_shellRef:this}),this.currentDir=process.cwd(),this.username=os.userInfo().username,this.history=[],this.running=!1,this.notifyClients=new Set,this.tcpServer=null,this.api=new ThinkNCollabAPI({apiUrl:e.apiUrl||process.env.THINKNCOLLAB_API_URL||e.serverUrl||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com",wsUrl:e.wsUrl||process.env.THINKNCOLLAB_WS_URL||e.serverUrl||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com"}),this.api.getCurrentRoom=()=>{const e=this.ws.getCurrentRoom();return e?{id:e,name:e}:null},this.commands=new Map,this.aliases=new Map,this.variables=new Map,this.sandboxEngine=new DockSandbox,this.activeSandbox=null,this.api.isAuthenticated()&&this.ws.setUser(this.api.getUser(),this.api.getToken()),this.loadHistory(),this.registerBuiltinCommands(),this.commandRegistry=new CommandRegistry(this),this.setupWebSocketHandlers()}async startTCPServer(){this.notifyPort=await getAvailablePort(7379);const e=require("./notify-auth");try{this.notifyToken=e.writeToken(this.notifyPort)}catch(e){this.notifyToken=null}return new Promise(t=>{this.tcpServer=net.createServer(t=>{t.on("error",()=>this.notifyClients.delete(t)),this.notifyToken?e.guard(t,this.notifyToken,()=>{this.notifyClients.add(t),t.on("close",()=>this.notifyClients.delete(t))}):t.destroy()}),this.tcpServer.listen(this.notifyPort,"127.0.0.1",()=>t()),this.tcpServer.on("error",e=>{console.log(chalk.yellow(`[Notice] Notification server: ${e.message}`)),t()})})}pushNotification(e){if(0===this.notifyClients.size)return;const t=JSON.stringify(e)+"\n";for(const e of this.notifyClients)try{e.write(t)}catch{}if("darwin"===process.platform)try{let t="";if("message"===e.type?t=`${e.from}: ${e.text}`:"task"===e.type?t=e.message||e.title||"Task update":"task_assigned"===e.type?t=`Task assigned: ${e.taskTitle||""}`:"verdict"===e.type?t=`Verdict [${e.passed?"PASSED":"FAILED"}]: ${e.title||""}`:"notification"===e.type&&(t=`${e.title||""}: ${e.message||""}`),t){const e=t.replace(/["\\]/g," ").slice(0,150);spawn("osascript",["-e",`display notification "${e}" with title "ThinkNCollab"`],{detached:!0,stdio:"ignore"}).unref()}}catch(e){}(this.isVSCode||0===this.notifyClients.size)&&this.printInlineNotification(e)}printInlineNotification(e){const t=chalk.dim(`[${(new Date).toLocaleTimeString()}]`);switch(e.type){case"message":console.log(`\n${t} ${chalk.cyan("[Chat]")} ${chalk.bold(e.from)}: ${e.text}`);break;case"task_assigned":console.log(`\n${t} ${chalk.yellow("[Task]")} Assigned: ${chalk.white(e.taskTitle)} by ${chalk.cyan(e.assignedBy||"Admin")}`);break;case"task_completed":console.log(`\n${t} ${chalk.green("[Task Done]")} ${chalk.white(e.message||e.taskTitle)}`);break;case"verdict":break;case"notification":{const o="error"===e.level?chalk.red:"warn"===e.level?chalk.yellow:chalk.blue;console.log(`\n${t} ${o(`[Alert: ${e.title||"Info"}]`)} ${e.message}`);break}case"userJoined":console.log(`\n${t} ${chalk.dim(`[User+] ${e.username} joined`)}`);break;case"userLeft":console.log(`\n${t} ${chalk.dim(`[User-] ${e.username} left`)}`)}}spawnNotificationWindow(){const e=path.join(__dirname,"notification-window.js"),t=this.notifyPort,o={...process.env,THINKNSH_NOTIFY_PORT:String(t)},s="vscode"===process.env.TERM_PROGRAM||"1"===process.env.VSCODE_INJECTION||Boolean(process.env.VSCODE_PID)||Boolean(process.env.VSCODE_GIT_IPC_HANDLE)||Boolean(process.env.VSCODE_IPC_HOOK_CLI);if(this.isVSCode=s,s)return console.log(chalk.cyan(` [VS Code Integrated Mode] Live notification server active on port ${t}`)),console.log(chalk.dim(" - Inline notifications: ENABLED (alerts & chat display directly in VS Code)")),void console.log(chalk.dim(' - Split panel view: Run "thinknsh notifications" in a split terminal tab (Cmd+\\)\n'));const n="linux"===process.platform&&(()=>{try{return fs.readFileSync("/proc/version","utf8").toLowerCase().includes("microsoft")}catch{return!1}})(),i=()=>{console.log(chalk.yellow("\n [Notice] Could not open notification window.")),console.log(chalk.dim(" Open a new terminal and run:")),console.log(chalk.cyan(` THINKNSH_NOTIFY_PORT=${t} node "${e}"\n`))};try{if(n){const s=e.replace(/^\/mnt\/([a-z])/,(e,t)=>`${t.toUpperCase()}:`).replace(/\//g,"\\"),n=e=>{const t=spawn(`start cmd.exe /k ""${e}" "${s}""`,[],{detached:!0,stdio:"ignore",env:o,shell:!0});t.on("error",i),t.unref()},a=["/mnt/c/Program Files/nodejs/node.exe","/mnt/c/Program Files (x86)/nodejs/node.exe"].find(e=>{try{return fs.existsSync(e)}catch{return!1}});if(a){const e=a.replace(/^\/mnt\/([a-z])/,(e,t)=>`${t.toUpperCase()}:`).replace(/\//g,"\\");n(e)}else{const o=spawn("cmd.exe",["/c","where","node"],{stdio:["ignore","pipe","ignore"]});let s="";o.stdout.on("data",e=>s+=e),o.on("close",()=>{const o=s.trim().split("\n")[0]?.trim();if(o?.endsWith(".exe"))n(o);else{const o=spawn(`start cmd.exe /k "wsl.exe -- bash -c 'THINKNSH_NOTIFY_PORT=${t} node \\"${e}\\"; read'"`,[],{detached:!0,stdio:"ignore",shell:!0});o.on("error",i),o.unref()}})}}else if("win32"===process.platform){const s=process.execPath;if("vscode"===process.env.TERM_PROGRAM)console.log(chalk.yellow("\n VS Code detected — open a new terminal tab and run:")),console.log(chalk.cyan(` $env:THINKNSH_NOTIFY_PORT=${t}; node "${e}"\n`));else{const t=()=>{const t=spawn(`start cmd.exe /k ""${s}" "${e}""`,[],{detached:!0,stdio:"ignore",env:o,shell:!0});t.on("error",i),t.unref()},n=spawn("wt.exe",["cmd.exe","/k",`"${s}" "${e}"`],{detached:!0,stdio:"ignore",env:o});n.on("error",t),n.unref()}}else if("darwin"===process.platform){const o=process.execPath,s=`\n tell application "Terminal"\n launch\n do script "THINKNSH_NOTIFY_PORT=${t} '${o}' '${e}'"\n activate\n end tell\n `;try{spawn("osascript",["-e",s],{detached:!0,stdio:"ignore"}).unref()}catch{const s=`\n tell application "iTerm2"\n tell current window\n create tab with default profile\n tell current session of current tab\n write text "THINKNSH_NOTIFY_PORT=${t} '${o}' '${e}'"\n end tell\n end tell\n end tell\n `;try{spawn("osascript",["-e",s],{detached:!0,stdio:"ignore"}).unref()}catch{console.log(chalk.yellow(" [Notice] Could not auto-open notification window.")),console.log(chalk.dim(` Open Terminal.app and run: THINKNSH_NOTIFY_PORT=${t} node "${e}"`))}}}else{const s=process.execPath,n=`THINKNSH_NOTIFY_PORT=${t} '${s}' '${e}'; read`,a=[["gnome-terminal",["--","bash","-c",n]],["konsole",["--noclose","-e","bash","-c",n]],["xfce4-terminal",["--hold","-e",`bash -c "${n}"`]],["tilix",["-e",`bash -c "${n}"`]],["alacritty",["-e","bash","-c",n]],["kitty",["bash","-c",n]],["xterm",["-hold","-e",`bash -c "${n}"`]]];let c=!1;for(const[e,t]of a)try{spawn(e,t,{detached:!0,stdio:"ignore",env:o}).unref(),c=!0;break}catch{}c||i()}}catch(e){i()}}setupWebSocketHandlers(){this.ws.on("connected",e=>{this.api.logEvent("connected",null,{socketId:e?.socketId}),this.api.startHeartbeat(),this.pushNotification({type:"connected",socketId:e?.socketId})}),this.ws.on("disconnected",e=>{this.api.logEvent("disconnected",null,{reason:e?.reason}),this.api.stopHeartbeat(),this.pushNotification({type:"disconnected",reason:e?.reason})}),this.ws.on("reconnected",()=>{this.api.logEvent("reconnected"),this.api.startHeartbeat(),this.pushNotification({type:"connected",socketId:"reconnected"})}),this.ws.on("task:verdict",e=>foreground.whenFree(()=>{const t=e.passed,o=t?chalk.green("─".repeat(52)):chalk.red("─".repeat(52)),s=t?chalk.bgGreen.black(" PASS "):chalk.bgRed.white(" FAIL ");console.log("\n"+o),console.log(` ${s} ${chalk.bold(e.title||"Task")} ${t?chalk.green("PASSED"):chalk.red("FAILED")}`),console.log(` ${chalk.dim("Reason :")} ${t?chalk.green(e.reason):chalk.red(e.reason)}`),e.diff&&(console.log(` ${chalk.dim("Expected:")} ${chalk.cyan(JSON.stringify(e.diff.expected??e.diff.conditions))}`),console.log(` ${chalk.dim("Actual :")} ${chalk.yellow(JSON.stringify(e.diff.actual))}`)),console.log(` ${chalk.dim("At :")} ${chalk.dim(new Date(e.at||Date.now()).toLocaleTimeString())}`),console.log(o+"\n"),this.pushNotification({type:"verdict",passed:t,title:e.title,reason:e.reason,taskId:e.taskId,at:e.at})})),this.ws.on("message",e=>foreground.whenFree(()=>{const t=this.api.getUser()?._id;if(t&&e.userId===t)return;const o=e.name||e.username||e.sender?.name||"Someone",s=e.message||e.content||e.text||"",n=chalk.dim(`[${(new Date).toLocaleTimeString()}]`),i=e.isE2EE?chalk.green(" [🔒 E2EE]"):"";console.log(`\n${n}${i} ${chalk.cyan(o+":")} ${chalk.white(s)}`),this.pushNotification({type:"message",from:o,text:s})})),this.ws.on("messageHistory",e=>foreground.whenFree(()=>{e?.length&&(console.log(chalk.dim("\n ─── Room History ───────────────────────────────")),e.forEach(e=>{const t=e.name||e.username||e.sender?.name||"Unknown",o=e.message||e.content||e.text||"",s=e.timestamp||e.createdAt,n=s?chalk.dim(`[${new Date(s).toLocaleTimeString()}]`):"",i=e.isE2EE?chalk.green(" [🔒 E2EE]"):"";console.log(` ${n}${i} ${chalk.cyan(t+":")} ${chalk.white(o)}`)}),console.log(chalk.dim(" ────────────────────────────────────────────────\n")))})),this.ws.on("userJoined",e=>foreground.whenFree(()=>{console.log(chalk.dim(`\n [User+] ${e.username} joined the room`)),this.pushNotification({type:"userJoined",username:e.username})})),this.ws.on("userLeft",e=>foreground.whenFree(()=>{console.log(chalk.dim(`\n [User-] ${e.username} left the room`)),this.pushNotification({type:"userLeft",username:e.username})})),this.ws.on("joined",e=>this.api.logEvent("room_join",e)),this.ws.on("left",e=>this.api.logEvent("room_leave",e)),this.ws.on("notification",e=>foreground.whenFree(()=>{const t=chalk.dim(`[${(new Date).toLocaleTimeString()}]`),o={success:chalk.green,warning:chalk.yellow,error:chalk.red,info:chalk.blue}[e.level]||chalk.blue;console.log(`\n${t} [ALERT] ${o(`${e.title||"Notification"}: ${e.message||""}`)}`),this.pushNotification({type:e.type||"notification",level:e.level,title:e.title,message:e.message,taskTitle:e.taskTitle,assignedBy:e.assignedBy,meta:e.meta})}));let e=0;this.ws.on("error",t=>{const o=Date.now();o-e>3e4&&(e=o,console.log(chalk.yellow(`[Notice] WebSocket reconnecting: ${t.message}`))),this.pushNotification({type:"notification",level:"error",title:"WebSocket Error",message:t.message})}),this.ws.onSandboxAction(this.sandboxEngine,(e,t)=>{t?.success?console.log(chalk.cyan(`\n[Web Dashboard Action] ${e.toUpperCase()} completed: ${t.message}`)):console.log(chalk.yellow(`\n[Web Dashboard Action] ${e.toUpperCase()} failed: ${t?.message}`))})}async start(){this.showWelcome(),await this.startTCPServer(),console.log(chalk.cyan(` Notification server active on port ${chalk.bold(this.notifyPort)}`)),console.log(chalk.dim(" To open live notification panel in another tab/terminal:")),console.log(chalk.white(` thinknsh notify ${this.notifyPort}\n`)),this.running=!0,process.on("SIGINT",()=>console.log(chalk.yellow('\nUse "exit" to quit')));const e=await this._bootFromProjectConfig();if(e||this.api.isAuthenticated()){if(!e)try{await require("./room-link").connect(this,{interactive:!0})}catch(e){console.log(chalk.dim(` Not connected to a room (${e.message}). Run "connect" when you want to.\n`))}}else console.log(chalk.cyan(" Welcome! Sign in to ThinkNCollab to get started.\n")),await this.execute("login");!e&&this.config.websocket.autoConnect&&await this.ws.connect().catch(()=>{});const{input:t}=require("@inquirer/prompts");for(;this.running;)try{const e=await t({message:this.getPromptText(),required:!1}).catch(()=>null);if(null===e){console.log(chalk.yellow('Use "exit" to quit'));continue}const o=e.trim();if(!o)continue;SECRET_LINE.test(o)||(this.history.push(o),this.saveHistory()),await this.execute(o)}catch(e){"ExitPromptError"===e.name||e.message?.includes("force closed")?console.log(chalk.yellow('Use "exit" to quit')):console.log(chalk.red(`Shell error: ${e.message}`))}}async _bootFromProjectConfig(){if(!hasProjectConfig())return!1;let e,t;try{e=loadProjectConfig()}catch(e){return console.log(chalk.yellow(`[Notice] tncproject: ${e.message}\n`)),!1}console.log(chalk.dim(" [Project] tncproject found")),console.log(chalk.dim(` Room : ${e.roomId}`)),console.log(chalk.dim(` File : ${e.filePath}\n`));try{t=await this.api._request("POST","/config/verify-config",{fileBase64:e.fileBase64,roomId:e.roomId,machineId:os.hostname()})}catch(e){return console.log(chalk.red(`[Error] tncproject auth failed: ${e.message}`)),console.log(chalk.dim(" Falling back to manual login.\n")),!1}try{this.api._adopt(t,"tncproject")}catch(e){return console.log(chalk.red(`[Error] tncproject auth failed: ${e.message}`)),!1}this.ws.setUser(this.api.getUser(),t.shellToken);try{await this.ws.connect(t.shellToken)}catch(e){return console.log(chalk.yellow(`[Notice] WebSocket: ${e.message}`)),console.log(chalk.green(`[Auth] ${t.name} logged in (offline mode)\n`)),!0}try{await this.ws.joinRoom(t.roomId.toString()),console.log(chalk.green(`[Room] ${t.name} -> room "${t.roomName||t.roomId}"\n`))}catch(e){console.log(chalk.yellow(`[Notice] Could not auto-join room: ${e.message}`)),console.log(chalk.dim(' Use "join <room-id>" manually.\n'))}return!0}async execute(e,t=null){if(!(e.trim()||t&&0!==t.length))return;const o=t&&t.length>0?t:this.parseCommand(e),s=o[0].toLowerCase(),n=o.slice(1);if(await require("./auth/gate").allow(s,this.api,n))if(["login","logout","auth","set","unset"].includes(s)||this.api.logEvent("command",null,{cmd:s}).catch(()=>{}),this.commands.has(s)){try{await this.commands.get(s).handler(n,this)}catch(e){console.log(chalk.red(`Error: ${e.message}`))}process.stdout.write("\r\n")}else{if(!this.commandRegistry||!await this.commandRegistry.execute(e,this,t))return"win32"===process.platform&&["open","which","grep","touch","cat"].includes(s)?(console.log(chalk.red(`'${s}' is not a thinknsh command.`)),console.log(chalk.dim(" Type 'help' to see available commands.")),void process.stdout.write("\r\n")):void await this.executeSystemCommand(e);this.commandRegistry.getCommand(s)?.selfExiting||process.stdout.write("\r\n")}}getPromptText(){let e="";const t=this.ws.getCurrentRoom?.();t&&(e+=`[${t}] `),this.ws.isConnected?.()&&(e+="● "),this.activeSandbox&&(e+=`[Sandbox: ${this.activeSandbox}] `);let o=this.currentDir;const s=os.homedir();return o="win32"===process.platform?o.toLowerCase()===s.toLowerCase()?"~":o.toLowerCase().startsWith(s.toLowerCase())?"~"+o.slice(s.length).replace(/\\/g,"/"):o.replace(/\\/g,"/"):o.replace(s,"~"),`${e}${this.username}@thinknsh:${o} $`}getPrompt(){return this.getPromptText()}printMessage(e){console.log(e)}registerBuiltinCommands(){const e=(e,t,o)=>this.commands.set(e,{handler:t,description:o});e("help",this.helpCommand.bind(this),"Show available commands"),e("exit",()=>{this.cleanup(),process.exit(0)},"Exit the shell"),e("quit",()=>{this.cleanup(),process.exit(0)},"Exit the shell"),e("clear",()=>console.clear(),"Clear screen"),e("history",this.historyCommand.bind(this),"Show command history"),e("cd",this.cdCommand.bind(this),"Change directory"),e("pwd",()=>console.log(process.cwd()),"Print working directory"),e("ls",this.lsCommand.bind(this),"List files"),e("use",e=>{const t=e[0];t?(this.activeSandbox=t,console.log(chalk.green(`\n[Sandbox] Switched active execution context to: [${t}]`)),console.log(chalk.dim(" System commands now run inside its container (leave-sandbox to go back).\n"))):console.log(chalk.red("Usage: use <sandbox-id>"))},"Switch active shell context to a TNC sandbox"),e("leave-sandbox",()=>{this.activeSandbox?(console.log(chalk.cyan(`\nExited sandbox [${this.activeSandbox}]. Back to host shell.\n`)),this.activeSandbox=null):console.log(chalk.yellow("Not currently inside any sandbox context."))},"Exit active sandbox context"),e("disconnect",()=>this.ws.disconnect(),"Disconnect WebSocket"),e("status",this.statusCommand.bind(this),"Show connection status"),e("leave",()=>this.ws.leaveRoom?.(),"Leave current room"),e("say",this.sayCommand.bind(this),"Send a message"),e("msg",this.sayCommand.bind(this),"Send a message"),e("set",this.setCommand.bind(this),"Set a variable"),e("unset",this.unsetCommand.bind(this),"Unset a variable"),this.addAlias("ll","ls -la"),this.addAlias("..","cd .."),this.addAlias("~","cd ~"),this.addAlias("unuse","leave-sandbox")}registerCommand(e,t,o=""){this.commands.set(e,{handler:t,description:o})}addAlias(e,t){this.aliases.set(e,t)}async helpCommand(){this.commandRegistry&&console.log(this.commandRegistry.getHelpText()),console.log(chalk.dim(" Any unrecognised command runs in your system shell\n"))}historyCommand(){this.history.forEach((e,t)=>console.log(`${(t+1).toString().padStart(4)} ${e}`))}async cdCommand(e){const t=e.join(" ").trim()||os.homedir();try{let e;if(e="~"===t?os.homedir():".."===t?path.dirname(this.currentDir):path.resolve(this.currentDir,t),e=path.normalize(e),!(await fs.stat(e)).isDirectory())return void console.log(chalk.red(`cd: not a directory: ${t}`));this.previousDir=this.currentDir,process.chdir(e),this.currentDir=process.cwd()}catch{console.log(chalk.red(`cd: no such directory: ${t}`))}}async lsCommand(e){try{const t=await fs.readdir(this.currentDir),o=e.includes("-a")?t:t.filter(e=>!e.startsWith("."));(await Promise.all(o.map(async e=>({name:e,isDir:(await fs.stat(path.join(this.currentDir,e))).isDirectory()})))).forEach(e=>e.isDir?console.log(chalk.blue(e.name+"/")):console.log(e.name))}catch(e){console.log(chalk.red(`ls: ${e.message}`))}}async statusCommand(){const e=this.ws.isConnected();console.log(chalk.cyan("\nStatus:")),console.log(` WebSocket : ${e?chalk.green("Connected"):chalk.red("Disconnected")}`),e&&console.log(` Socket ID : ${chalk.dim(this.ws.getSocketId())}`);const t=this.ws.getCurrentRoom();console.log(` Room : ${t?chalk.green(t):chalk.yellow("None")}`),console.log(` Auth : ${this.api.isAuthenticated()?chalk.green("Logged in"):chalk.yellow("Not logged in")}`);const o=this.api.getUser();o&&console.log(` User : ${chalk.dim(o.name||o.email)}`),console.log("")}async sayCommand(e){const t=e.join(" ");if(t)try{await this.ws.sendMessage(t);const e=this.api.getUser()?.name||this.username;console.log(chalk.green(`You: ${t}`)),this.pushNotification({type:"message",from:`You (${e})`,text:t})}catch(e){console.log(chalk.red(`Failed: ${e.message}`))}else console.log(chalk.red("Usage: say <message>"))}async setCommand(e){if(e.length)this.variables.set(e[0],e.slice(1).join(" ")),console.log(chalk.green(`${e[0]} = ${e.slice(1).join(" ")}`));else for(const[e,t]of this.variables)console.log(` ${chalk.green(e.padEnd(12))} = ${t}`)}async unsetCommand(e){e[0]&&this.variables.delete(e[0])}getSessionPath(){return path.join(this.config.configDir,"session.json")}parseCommand(e){const t=[];let o="",s=!1,n="",i=!1;for(const a of e)"\\"!==a||i||s?i?(o+=a,i=!1):'"'!==a&&"'"!==a||s?a===n&&s?(s=!1,n=""):" "!==a||s?o+=a:o&&(t.push(o),o=""):(s=!0,n=a):i=!0;return o&&t.push(o),t}executeSystemCommand(e){return new Promise(t=>{let o=e,s={...process.env},n=this.currentDir,i=[],a=!0;this.activeSandbox&&(console.log(chalk.cyan(`[TNC Sandbox: ${this.activeSandbox}] ${e}`)),i=[process.argv[1],"exec",process.stdin.isTTY?"-it":"-i",this.sandboxEngine.name(this.activeSandbox),"sh","-c",e],o=process.execPath,a=!1);const c=spawn(o,i,{cwd:n,shell:a,stdio:"inherit",env:s});c.on("close",async o=>{try{this.currentDir=process.cwd()}catch{}if(0===o)try{const t=new(require("./dependency-tracker"))(n);await t.interceptCommand(e,this)}catch(e){}process.stdout.write("\r\n"),t()}),c.on("error",()=>{console.log(chalk.red(`Command not found: ${e}`)),t()})})}showWelcome(){try{const e=require("figlet");console.log(chalk.cyan(e.textSync("ThinkNCollab",{font:"Standard"})))}catch{}console.log(chalk.cyan("\n╔══════════════════════════════════════════════════════╗\n║ ThinkNCollab Shell v0.0.20 ║\n║ Type 'help' for commands ║\n╚══════════════════════════════════════════════════════╝"))}loadHistory(){try{if(!fs.existsSync(this.config.historyFile))return;const e=fs.readFileSync(this.config.historyFile,"utf8").split("\n").filter(e=>e.trim());this.history=e.filter(e=>!SECRET_LINE.test(e.trim())),this.history.length!==e.length&&this.saveHistory()}catch{}}saveHistory(){try{fs.writeFileSync(this.config.historyFile,this.history.slice(-this.config.maxHistory).join("\n"),{mode:384}),fs.chmodSync(this.config.historyFile,384)}catch{}}cleanup(){if(this.activeSandbox)try{this.sandboxEngine.freeze(this.activeSandbox).catch(()=>{}),console.log(chalk.cyan(`\n[TNC Smart Freeze] Auto-froze active sandbox [${this.activeSandbox}]. 0 RAM used!`))}catch(e){}if(this.api.logEvent("shell-exit").catch(()=>{}),this.api.stopHeartbeat(),this.tcpServer)try{this.tcpServer.close()}catch{}this.notifyPort&&require("./notify-auth").removeToken(this.notifyPort),this.ws&&this.ws.disconnect(),this.saveHistory()}}module.exports=ThinkNCollabShell;
@@ -4,4 +4,4 @@
4
4
  * PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
5
5
  * UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
6
6
  */
7
- const{execSync,spawnSync}=require("child_process"),fs=require("fs-extra"),path=require("path"),os=require("os"),chalk=require("chalk"),SNAPSHOT_BASE=path.join(os.homedir(),".tnc","snapshots"),SNAPSHOT_META=path.join(SNAPSHOT_BASE,"snapshots.json");class SnapshotManager{constructor(){fs.ensureDirSync(SNAPSHOT_BASE),this.meta=this._loadMeta(),this.platform=process.platform,this.fsType=this._detectFS()}_loadMeta(){try{return fs.readJsonSync(SNAPSHOT_META)}catch{return{}}}_saveMeta(){fs.writeJsonSync(SNAPSHOT_META,this.meta,{spaces:2})}_detectFS(){if("darwin"===this.platform){try{if(execSync('diskutil info / 2>/dev/null | grep "File System"',{encoding:"utf8",stdio:"pipe"}).toLowerCase().includes("apfs"))return"apfs"}catch(e){}return"hfs+"}if("linux"===this.platform){try{const e=execSync("df -T ~/.tnc 2>/dev/null | tail -1",{encoding:"utf8",stdio:"pipe"});if(e.includes("btrfs"))return"btrfs";if(e.includes("xfs"))return"xfs";if(e.includes("ext4"))return"ext4"}catch(e){}return"ext4"}return"win32"===this.platform?"ntfs":"generic"}_getStrategy(){switch(this.fsType){case"apfs":return"apfs-cow";case"btrfs":return"btrfs-snap";case"xfs":return"reflink";case"ext4":return"reflink-auto";case"ntfs":return"robocopy";default:return"tar"}}async snapshot(e,t,s={}){const n=path.resolve(e);if(!fs.existsSync(n))throw new Error(`Source path not found: ${n}`);const a=t.replace(/[^a-zA-Z0-9_-]/g,"-"),r=`${a}-${Date.now().toString(36)}`,o=path.join(SNAPSHOT_BASE,r),i=this._getStrategy(),c=Date.now();console.log(chalk.cyan(`\n[Snapshot] Creating "${a}" using ${i}...`)),console.log(chalk.dim(` Source: ${n}`));try{switch(i){case"apfs-cow":spawnSync("cp",["-cR",n,o],{stdio:"pipe"});break;case"btrfs-snap":spawnSync("btrfs",["subvolume","snapshot",n,o],{stdio:"pipe"});break;case"reflink":fs.ensureDirSync(o),spawnSync("cp",["--reflink=always","-a",`${n}/.`,`${o}/`],{stdio:"pipe"});break;case"reflink-auto":fs.ensureDirSync(o),spawnSync("cp",["--reflink=auto","-a",`${n}/.`,`${o}/`],{stdio:"pipe"});break;case"robocopy":fs.ensureDirSync(o),spawnSync("robocopy",[n,o,"/E","/DCOPY:T"],{stdio:"pipe"});break;default:spawnSync("tar",["-czf",`${o}.tar.gz`,"-C",path.dirname(n),path.basename(n)],{stdio:"pipe"})}const e=Date.now()-c,t=this._dirSize("tar"===i?void 0:o),p=["apfs-cow","btrfs-snap","reflink"].includes(i),f={id:r,name:a,source:n,snapDir:"tar"===i?`${o}.tar.gz`:o,strategy:i,fsType:this.fsType,isCoW:p,sizeMB:t,createdAt:(new Date).toISOString(),elapsedMs:e,tags:s.tags||{},description:s.description||""};return this.meta[r]=f,this._saveMeta(),console.log(chalk.green(`[OK] Snapshot "${a}" created in ${e}ms`)),p?console.log(chalk.dim(" CoW: Hardware/Filesystem instant clone active (zero extra disk until modified) ✓")):console.log(chalk.yellow(` Notice: ${this.fsType} does not support instant CoW reflinks; standard copy performed.`)),console.log(chalk.dim(` ID: ${r}`)),f}catch(e){throw new Error(`Snapshot failed (${i}): ${e.message}`)}}async snapshotSafe(e,t,s=null,n=null,a={}){let r=!1;if(s&&n){const e=s.loadMeta(n);e&&"RUNNING"===e.state&&(r=!0,console.log(chalk.yellow(`[SnapshotSafe] Flushing & pausing container "${n}" for crash-free snapshot consistency...`)),await s.freeze(n))}try{return await this.snapshot(e,t,a)}finally{r&&s&&n&&(console.log(chalk.cyan(`[SnapshotSafe] Resuming container "${n}"...`)),await s.resume(n))}}async restore(e,t,s={}){const n=this.meta[e];if(!n)throw new Error(`Snapshot "${e}" not found.`);const a=path.resolve(t||n.source);return console.log(chalk.cyan(`\n[Snapshot] Restoring "${n.name}" → ${a}...`)),s.backup&&await this.snapshot(a,`pre-restore-${n.name}`),"tar"===n.strategy?(fs.ensureDirSync(a),spawnSync("tar",["-xzf",n.snapDir,"-C",path.dirname(a)],{stdio:"pipe"})):"btrfs-snap"===n.strategy?(fs.existsSync(a)&&spawnSync("btrfs",["subvolume","delete",a],{stdio:"pipe"}),spawnSync("btrfs",["subvolume","snapshot",n.snapDir,a],{stdio:"pipe"})):(fs.existsSync(a)&&fs.removeSync(a),fs.ensureDirSync(a),"apfs-cow"===n.strategy?spawnSync("cp",["-cR",n.snapDir,a],{stdio:"pipe"}):spawnSync("cp",["--reflink=auto","-a",`${n.snapDir}/.`,`${a}/`],{stdio:"pipe"})),console.log(chalk.green(`[OK] Restored to ${a}`)),{success:!0,target:a}}diff(e,t){const s=this.meta[e],n=this.meta[t];if(!s||!n)throw new Error("One or both snapshots not found.");if("tar"===s.strategy||"tar"===n.strategy)return{method:"tar",note:"Diff not available for tar snapshots"};try{const e=spawnSync("diff",["-rq","--brief",s.snapDir,n.snapDir],{encoding:"utf8",stdio:"pipe"}).stdout||"",t=e.trim().split("\n").filter(Boolean).length;return{method:"diff",snapshot1:s.name,snapshot2:n.name,changedFiles:t,details:e.trim().split("\n").filter(Boolean).slice(0,50)}}catch{return{method:"diff",error:"diff command failed"}}}delete(e){const t=this.meta[e];if(!t)throw new Error(`Snapshot "${e}" not found.`);if("btrfs-snap"===t.strategy)try{execSync(`btrfs subvolume delete "${t.snapDir}"`,{stdio:"pipe"})}catch(e){fs.removeSync(t.snapDir)}else fs.existsSync(t.snapDir)&&fs.removeSync(t.snapDir);return delete this.meta[e],this._saveMeta(),{success:!0,deleted:e}}list(e={}){let t=Object.values(this.meta);return e.name&&(t=t.filter(t=>t.name.includes(e.name))),t.sort((e,t)=>new Date(t.createdAt)-new Date(e.createdAt))}inspect(e){return this.meta[e]||null}prune(e=3){const t={};for(const e of Object.values(this.meta))t[e.name]||(t[e.name]=[]),t[e.name].push(e);let s=0;for(const[n,a]of Object.entries(t)){const t=a.sort((e,t)=>new Date(t.createdAt)-new Date(e.createdAt)).slice(e);for(const e of t)try{this.delete(e.id),s++}catch(e){}}return{pruned:s}}_dirSize(e){if(!e||!fs.existsSync(e))return 0;try{const t=spawnSync("du",["-sm",e],{encoding:"utf8",stdio:"pipe"});return parseInt((t.stdout||"").split(/\s+/)[0],10)||0}catch{return 0}}systemInfo(){return{platform:this.platform,fsType:this.fsType,strategy:this._getStrategy(),isCoW:["apfs","btrfs","xfs"].includes(this.fsType),snapshotBase:SNAPSHOT_BASE,totalSnapshots:Object.keys(this.meta).length}}}module.exports=SnapshotManager;
7
+ const{execSync,spawnSync}=require("child_process"),fs=require("fs-extra"),path=require("path"),os=require("os"),chalk=require("chalk"),SNAPSHOT_BASE=path.join(os.homedir(),".tnc","snapshots"),SNAPSHOT_META=path.join(SNAPSHOT_BASE,"snapshots.json");class SnapshotManager{constructor(){fs.ensureDirSync(SNAPSHOT_BASE),this.meta=this._loadMeta(),this.platform=process.platform,this.fsType=this._detectFS()}_loadMeta(){try{return fs.readJsonSync(SNAPSHOT_META)}catch{return{}}}_saveMeta(){fs.writeJsonSync(SNAPSHOT_META,this.meta,{spaces:2})}_detectFS(){if("darwin"===this.platform){try{if(execSync('diskutil info / 2>/dev/null | grep "File System"',{encoding:"utf8",stdio:"pipe"}).toLowerCase().includes("apfs"))return"apfs"}catch(e){}return"hfs+"}if("linux"===this.platform){try{const e=execSync("df -T ~/.tnc 2>/dev/null | tail -1",{encoding:"utf8",stdio:"pipe"});if(e.includes("btrfs"))return"btrfs";if(e.includes("xfs"))return"xfs";if(e.includes("ext4"))return"ext4"}catch(e){}return"ext4"}return"win32"===this.platform?"ntfs":"generic"}_getStrategy(){switch(this.fsType){case"apfs":return"apfs-cow";case"btrfs":return"btrfs-snap";case"xfs":return"reflink";case"ext4":return"reflink-auto";case"ntfs":return"robocopy";default:return"tar"}}async snapshot(e,t,s={}){const n=path.resolve(e);if(!fs.existsSync(n))throw new Error(`Source path not found: ${n}`);const a=t.replace(/[^a-zA-Z0-9_-]/g,"-"),r=`${a}-${Date.now().toString(36)}`,o=path.join(SNAPSHOT_BASE,r),i=this._getStrategy(),c=Date.now();console.log(chalk.cyan(`\n[Snapshot] Creating "${a}" using ${i}...`)),console.log(chalk.dim(` Source: ${n}`));try{switch(i){case"apfs-cow":spawnSync("cp",["-cR",n,o],{stdio:"pipe"});break;case"btrfs-snap":spawnSync("btrfs",["subvolume","snapshot",n,o],{stdio:"pipe"});break;case"reflink":fs.ensureDirSync(o),spawnSync("cp",["--reflink=always","-a",`${n}/.`,`${o}/`],{stdio:"pipe"});break;case"reflink-auto":fs.ensureDirSync(o),spawnSync("cp",["--reflink=auto","-a",`${n}/.`,`${o}/`],{stdio:"pipe"});break;case"robocopy":fs.ensureDirSync(o),spawnSync("robocopy",[n,o,"/E","/DCOPY:T"],{stdio:"pipe"});break;default:spawnSync("tar",["-czf",`${o}.tar.gz`,"-C",path.dirname(n),path.basename(n)],{stdio:"pipe"})}const e=Date.now()-c,t=this._dirSize("tar"===i?void 0:o),p=["apfs-cow","btrfs-snap","reflink"].includes(i),f={id:r,name:a,source:n,snapDir:"tar"===i?`${o}.tar.gz`:o,strategy:i,fsType:this.fsType,isCoW:p,sizeMB:t,createdAt:(new Date).toISOString(),elapsedMs:e,tags:s.tags||{},description:s.description||""};return this.meta[r]=f,this._saveMeta(),console.log(chalk.green(`[OK] Snapshot "${a}" created in ${e}ms`)),p?console.log(chalk.dim(" CoW: Hardware/Filesystem instant clone active (zero extra disk until modified) ✓")):console.log(chalk.yellow(` Notice: ${this.fsType} does not support instant CoW reflinks; standard copy performed.`)),console.log(chalk.dim(` ID: ${r}`)),f}catch(e){throw new Error(`Snapshot failed (${i}): ${e.message}`)}}async snapshotSafe(e,t,s=null,n=null,a={}){let r=!1;if(s&&n){const e=await s.loadMeta(n);e&&"RUNNING"===e.state&&(r=!0,console.log(chalk.yellow(`[SnapshotSafe] Flushing & pausing container "${n}" for crash-free snapshot consistency...`)),await s.freeze(n))}try{return await this.snapshot(e,t,a)}finally{r&&s&&n&&(console.log(chalk.cyan(`[SnapshotSafe] Resuming container "${n}"...`)),await s.resume(n))}}async restore(e,t,s={}){const n=this.meta[e];if(!n)throw new Error(`Snapshot "${e}" not found.`);const a=path.resolve(t||n.source);return console.log(chalk.cyan(`\n[Snapshot] Restoring "${n.name}" → ${a}...`)),s.backup&&await this.snapshot(a,`pre-restore-${n.name}`),"tar"===n.strategy?(fs.ensureDirSync(a),spawnSync("tar",["-xzf",n.snapDir,"-C",path.dirname(a)],{stdio:"pipe"})):"btrfs-snap"===n.strategy?(fs.existsSync(a)&&spawnSync("btrfs",["subvolume","delete",a],{stdio:"pipe"}),spawnSync("btrfs",["subvolume","snapshot",n.snapDir,a],{stdio:"pipe"})):(fs.existsSync(a)&&fs.removeSync(a),fs.ensureDirSync(a),"apfs-cow"===n.strategy?spawnSync("cp",["-cR",n.snapDir,a],{stdio:"pipe"}):spawnSync("cp",["--reflink=auto","-a",`${n.snapDir}/.`,`${a}/`],{stdio:"pipe"})),console.log(chalk.green(`[OK] Restored to ${a}`)),{success:!0,target:a}}diff(e,t){const s=this.meta[e],n=this.meta[t];if(!s||!n)throw new Error("One or both snapshots not found.");if("tar"===s.strategy||"tar"===n.strategy)return{method:"tar",note:"Diff not available for tar snapshots"};try{const e=spawnSync("diff",["-rq","--brief",s.snapDir,n.snapDir],{encoding:"utf8",stdio:"pipe"}).stdout||"",t=e.trim().split("\n").filter(Boolean).length;return{method:"diff",snapshot1:s.name,snapshot2:n.name,changedFiles:t,details:e.trim().split("\n").filter(Boolean).slice(0,50)}}catch{return{method:"diff",error:"diff command failed"}}}delete(e){const t=this.meta[e];if(!t)throw new Error(`Snapshot "${e}" not found.`);if("btrfs-snap"===t.strategy)try{execSync(`btrfs subvolume delete "${t.snapDir}"`,{stdio:"pipe"})}catch(e){fs.removeSync(t.snapDir)}else fs.existsSync(t.snapDir)&&fs.removeSync(t.snapDir);return delete this.meta[e],this._saveMeta(),{success:!0,deleted:e}}list(e={}){let t=Object.values(this.meta);return e.name&&(t=t.filter(t=>t.name.includes(e.name))),t.sort((e,t)=>new Date(t.createdAt)-new Date(e.createdAt))}inspect(e){return this.meta[e]||null}prune(e=3){const t={};for(const e of Object.values(this.meta))t[e.name]||(t[e.name]=[]),t[e.name].push(e);let s=0;for(const[n,a]of Object.entries(t)){const t=a.sort((e,t)=>new Date(t.createdAt)-new Date(e.createdAt)).slice(e);for(const e of t)try{this.delete(e.id),s++}catch(e){}}return{pruned:s}}_dirSize(e){if(!e||!fs.existsSync(e))return 0;try{const t=spawnSync("du",["-sm",e],{encoding:"utf8",stdio:"pipe"});return parseInt((t.stdout||"").split(/\s+/)[0],10)||0}catch{return 0}}systemInfo(){return{platform:this.platform,fsType:this.fsType,strategy:this._getStrategy(),isCoW:["apfs","btrfs","xfs"].includes(this.fsType),snapshotBase:SNAPSHOT_BASE,totalSnapshots:Object.keys(this.meta).length}}}module.exports=SnapshotManager;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @license ThinkNCollab Enterprise & Open Core Ecosystem
3
+ * Copyright (c) 2024-2026 ThinkNCollab Systems Inc. All rights reserved.
4
+ * PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
5
+ * UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
6
+ */
7
+ const http=require("http");class SandboxTunnel{constructor(){this.socket=null,this.sandboxId=null,this.localPort=null,this.active=!1,this._heartbeatInterval=null,this._reconnectTimer=null,this._stopping=!1,this._targetServer=null,this._userId=null,this._shellToken=null}connect(t,e,s){if(this.sandboxId=t,this.localPort=e,this._stopping=!1,this._targetServer=(s?.ws?.config?.serverUrl||s?.api?.apiUrl||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com").replace(/\/$/,""),this._userId=s?.api?.getUser?.()?._id||s?.api?.getUser?.()?.id||s?.api?.userId||s?.config?.user?._id||process.env.THINKNCOLLAB_USER_ID||null,this._shellToken=s?.config?.shellToken||s?.api?.shellToken||s?.api?.getToken?.()||process.env.THINKNCOLLAB_TOKEN||null,!this._shellToken||!this._userId)try{const t=new(require("../api-client"));this._shellToken=this._shellToken||t.getToken(),this._userId=this._userId||t.getUser()?._id||null,this._userName=t.getUser()?.name||null}catch(t){}this._doConnect()}_doConnect(){if(this._stopping)return;let t;try{t=require("socket.io-client")}catch(t){return}const e=t(this._targetServer+"/thinknsh",{transports:["websocket","polling"],timeout:5e3,reconnection:!1,auth:{userId:this._userId,shellToken:this._shellToken}});this.socket=e,e.on("connect",()=>{e.emit("thinknsh:connected",{userId:this._userId,userName:this._userName||null,shellToken:this._shellToken})}),e.on("thinknsh:ready",()=>{this.active=!0,e.emit("sandbox:tunnel-register",{sandboxId:this.sandboxId,localPort:this.localPort}),this._startHeartbeat()}),e.on("thinknsh:error",()=>{this._stopping=!0,this.active=!1}),e.on("tunnel:request",t=>{this._handleRequest(t,e)}),e.on("disconnect",()=>{this.active=!1,this._stopHeartbeat(),this._stopping||(this._reconnectTimer=setTimeout(()=>this._doConnect(),3e3))}),e.on("connect_error",()=>{this.active=!1,this._stopHeartbeat(),this._stopping||(this._reconnectTimer=setTimeout(()=>this._doConnect(),5e3))})}_handleRequest(t,e){if(!t||!t.requestId)return;const{requestId:s,method:n,path:o,headers:i,body:r}=t,h=this.localPort,a={...i||{}};delete a.host,delete a.connection,delete a["transfer-encoding"],a.host=`127.0.0.1:${h}`;const l=r?Buffer.from(r,"base64"):null;l&&l.length>0&&(a["content-length"]=l.length);const c={hostname:"127.0.0.1",port:h,path:o||"/",method:n||"GET",headers:a,timeout:3e4},u=http.request(c,t=>{const n=[];t.on("data",t=>n.push(t)),t.on("end",()=>{const o=Buffer.concat(n);e.emit("tunnel:response",{requestId:s,statusCode:t.statusCode,headers:t.headers,body:o.toString("base64")})})});u.on("error",t=>{e.emit("tunnel:response",{requestId:s,statusCode:502,headers:{"content-type":"text/plain"},body:Buffer.from(`Tunnel Error: ${t.message}`).toString("base64")})}),u.on("timeout",()=>{u.destroy(),e.emit("tunnel:response",{requestId:s,statusCode:504,headers:{"content-type":"text/plain"},body:Buffer.from("Tunnel Gateway Timeout").toString("base64")})}),l&&l.length>0&&u.write(l),u.end()}_startHeartbeat(){this._stopHeartbeat(),this._heartbeatInterval=setInterval(()=>{this.socket&&this.active&&this.socket.emit("sandbox:tunnel-ping",{sandboxId:this.sandboxId})},15e3)}_stopHeartbeat(){this._heartbeatInterval&&(clearInterval(this._heartbeatInterval),this._heartbeatInterval=null)}stop(){if(this._stopping=!0,this.active=!1,this._stopHeartbeat(),this._reconnectTimer&&clearTimeout(this._reconnectTimer),this.socket){try{this.socket.disconnect()}catch(t){}this.socket=null}}}module.exports=SandboxTunnel;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @license ThinkNCollab Enterprise & Open Core Ecosystem
3
+ * Copyright (c) 2024-2026 ThinkNCollab Systems Inc. All rights reserved.
4
+ * PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
5
+ * UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
6
+ */
7
+ const path=require("path"),fs=require("fs-extra"),os=require("os"),SandboxTunnel=require("./client"),sandboxId=process.argv[2],localPort=parseInt(process.argv[3],10),targetServer=process.argv[4]||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com",userId=process.argv[5]||process.env.THINKNCOLLAB_USER_ID||null;sandboxId&&localPort||(console.error("Usage: node daemon.js <sandboxId> <localPort> [serverUrl] [userId]"),process.exit(1));const sandboxDir=path.join(os.homedir(),".tnc","sandboxes",sandboxId);fs.ensureDirSync(sandboxDir);const pidFile=path.join(sandboxDir,"tunnel.pid");fs.writeFileSync(pidFile,String(process.pid));const tunnel=new SandboxTunnel,standaloneShellContext={ws:{config:{serverUrl:targetServer},getCurrentRoom:()=>null},api:{apiUrl:targetServer,getUser:()=>({_id:userId}),userId},config:{serverUrl:targetServer,userId}};function cleanup(){try{fs.existsSync(pidFile)&&fs.removeSync(pidFile)}catch(e){}tunnel.stop(),process.exit(0)}tunnel.connect(sandboxId,localPort,standaloneShellContext),process.on("SIGINT",cleanup),process.on("SIGTERM",cleanup),process.on("SIGHUP",cleanup),process.on("exit",()=>{try{fs.existsSync(pidFile)&&fs.removeSync(pidFile)}catch(e){}});
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @license ThinkNCollab Enterprise & Open Core Ecosystem
3
+ * Copyright (c) 2024-2026 ThinkNCollab Systems Inc. All rights reserved.
4
+ * PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
5
+ * UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
6
+ */
7
+ "use strict";const fs=require("fs"),os=require("os"),path=require("path"),{spawn}=require("child_process"),SandboxTunnel=require("./client"),pidFile=n=>path.join(os.homedir(),".tnc","sandboxes",n,"tunnel.pid");function startDaemon(n,e,i=null,o=null){stopDaemon(n),fs.mkdirSync(path.dirname(pidFile(n)),{recursive:!0});const s=spawn(process.execPath,[path.join(__dirname,"daemon.js"),n,String(e),i||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com",o||process.env.THINKNCOLLAB_USER_ID||""],{detached:!0,stdio:"ignore",windowsHide:!0});return s.unref(),fs.writeFileSync(pidFile(n),String(s.pid)),s.pid}function isTunnelDaemon(n,e){const{spawnSync:i}=require("child_process"),o=("win32"===process.platform?i("wmic",["process","where",`ProcessId=${n}`,"get","CommandLine","/value"],{encoding:"utf8",windowsHide:!0}):i("ps",["-p",String(n),"-o","command="],{encoding:"utf8"})).stdout||"";return/tunnel[/\\]daemon\.js/.test(o)&&o.includes(` ${e} `)}function readPid(n){const e=parseInt(fs.readFileSync(pidFile(n),"utf8"),10);return Number.isInteger(e)&&e>0?e:null}function stopDaemon(n){try{const e=readPid(n);if(e&&isTunnelDaemon(e,n))try{process.kill(e,"SIGTERM")}catch(n){}fs.rmSync(pidFile(n),{force:!0})}catch(n){}}function daemonRunning(n){try{const e=readPid(n);return e&&isTunnelDaemon(e,n)?e:null}catch(n){return null}}module.exports={SandboxTunnel,startDaemon,stopDaemon,daemonRunning};
@@ -4,4 +4,4 @@
4
4
  * PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
5
5
  * UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
6
6
  */
7
- const{io}=require("socket.io-client"),EventEmitter=require("events"),e2eeVault=require("./crypto/e2ee-vault");class WebSocketManager extends EventEmitter{constructor(e={}){super(),this.config={serverUrl:e.serverUrl||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com",reconnectAttempts:e.reconnectAttempts||5,reconnectDelay:e.reconnectDelay||1e3,...e},this.socket=null,this.connected=!1,this.currentUser=null,this.roomId=null,this.reconnectCount=0,this.messageQueue=[],this._authToken=null,this._handlersSetup=!1,this._serverReady=!1,this._shellRef=e._shellRef||null}connect(e=null){return new Promise((t,s)=>{try{e&&(this._authToken=e),this.socket=io(this.config.serverUrl+"/thinknsh",{transports:["websocket","polling"],reconnection:!0,reconnectionAttempts:this.config.reconnectAttempts,reconnectionDelay:this.config.reconnectDelay,auth:this._authToken?{token:this._authToken}:{}});const o=setTimeout(()=>{s(new Error("Connection timeout"))},1e4);this.socket.on("connect",()=>{clearTimeout(o),this.connected=!0,this.reconnectCount=0,this.currentUser&&this._announceUser(),this.processPendingMessages(),this.emit("connected",{socketId:this.socket.id}),t({success:!0,socketId:this.socket.id})}),this.socket.on("connect_error",e=>{clearTimeout(o),0===this.reconnectCount&&s(new Error(`Cannot connect to ${this.config.serverUrl}`)),this.reconnectCount++,this.emit("error",{message:e.message})}),this.socket.on("disconnect",e=>{this.connected=!1,this.emit("disconnected",{reason:e})}),this.socket.on("task:verdict",e=>{this.emit("task:verdict",e)}),this.socket.on("task:run-local",async e=>{try{const t=require("chalk"),{runLocal:s}=require("./local-runner"),o=e.taskId,r=this._shellRef,n=r?r.api:null;if(!n)return void console.log(t.red("\n[Error] CLI not authenticated. Run: login\n"));let i;try{i=await n._request("GET",`/thinknsh/${o}/cli`)}catch(e){return void console.log(t.red(`\n[Error] Could not fetch task info: ${e.message}\n`))}if(!i||!i.webhookSecret)return void console.log(t.red("\n[Error] Task has no webhookSecret - cannot submit result.\n"));await s({taskId:o,webhookSecret:i.webhookSecret,apiUrl:this.config.serverUrl||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com",repoUrl:i.repoUrl||i.githubUrl||i.repo_url||null,repoBranch:i.branch||i.repoBranch||"main",workflowFile:i.workflowFile||i.workflow||null,chalk:t,shell:r,onLog:e=>{this.socket.emit("task:log",{taskId:o,boardId:i.boardId,...e})}})}catch(e){console.error("task:run-local error:",e.message)}}),this.socket.on("reconnect",()=>{this.connected=!0,this.emit("reconnected",{}),this.currentUser&&this._announceUser(),this.roomId&&this.currentUser&&this._emitJoin(this.roomId)}),this._handlersSetup||(this.setupEventHandlers(),this._handlersSetup=!0)}catch(e){s(e)}})}_announceUser(){if(this.socket&&this.currentUser&&(this.socket.emit("thinknsh:connected",{userId:this.currentUser.userId,userName:this.currentUser.name,shellToken:this._authToken}),this._shellRef?.sandboxEngine))try{this._shellRef.sandboxEngine.list().forEach(e=>{this.emitSandboxSync(e,"sync")})}catch(e){}}setUser(e,t=null){if(this._user=e,!e)return;t&&(this._authToken=t);const s=(e.userId||e._id||"").toString();this.currentUser={userId:s,name:e.name||e.email||"Unknown",userType:e.userType||e.model||e.role||"User"},this.connected&&this.socket&&this._announceUser()}setupEventHandlers(){this.socket.on("thinknsh:ready",e=>{this._serverReady=!0,this.emit("ready",e)}),this.socket.on("thinknsh:joined",e=>{if(this.roomId=e.roomId,e.history?.length){const t=e.history.map(e=>{let t=e.message||e.content||e.text||"",s=!1;if("string"==typeof t&&t.startsWith("e2ee:")){const e=e2eeVault.decrypt(t,this.roomId);e!==t&&(t=e,s=!0)}return{...e,message:t,isE2EE:s}});this.emit("messageHistory",t)}this.emit("roomJoined",e)}),this.socket.on("thinknsh:message",e=>{let t=e.message||"",s=e.isE2EE||!1;if("string"==typeof t&&t.startsWith("e2ee:")){const e=e2eeVault.decrypt(t,this.roomId);e!==t&&(t=e,s=!0)}this.emit("message",{username:e.name,userId:e.userId,message:t,isE2EE:s,source:"shell",timestamp:e.timestamp})}),this.socket.on("chat-message",e=>{let t=e.message||"",s=e.isE2EE||!1;if("string"==typeof t&&t.startsWith("e2ee:")){const e=e2eeVault.decrypt(t,this.roomId);e!==t&&(t=e,s=!0)}this.emit("message",{username:e.name,userId:e.userId,message:t,isE2EE:s,source:e.source||"web",timestamp:e.timestamp})}),this.socket.on("e2ee:key-sync",e=>{!e?.roomId||this.roomId&&e.roomId!==this.roomId||this.emit("e2ee:key-sync",e)}),this.socket.on("e2ee:key-delivered",e=>{e?.packet?.rawKey&&(e2eeVault.setRawRoomKey(e.roomId,e.packet.rawKey),this.emit("e2ee:key-updated",e))}),this.socket.on("e2ee:packet",e=>{this.emit("e2ee:packet",e)}),this.socket.on("thinknsh:userJoined",e=>{this.emit("userJoined",{username:e.name,source:"shell"})}),this.socket.on("thinknsh:userLeft",e=>{this.emit("userLeft",{username:e.name,source:"shell"})}),this.socket.on("user-joined",e=>{this.emit("userJoined",{username:e,source:"web"})}),this.socket.on("user-left",e=>{this.emit("userLeft",{username:e,source:"web"})}),this.socket.on("user-list",e=>{this.emit("userList",{users:e})}),this.socket.on("thinknsh:history",e=>{this.emit("messageHistory",e.messages?.length?e.messages:[])}),this.socket.on("new-notification",e=>{this.emit("notification",{type:e.type||"info",level:e.type||"info",title:e.title||"Notification",message:e.message||"",meta:e.meta||{},taskTitle:e.taskTitle||e.meta?.taskTitle||"",assignedBy:e.assignedBy||e.meta?.assignedBy||""})}),this.socket.on("room:activity",e=>{this.emit("roomActivity",e)}),this.socket.on("thinknsh:status",e=>{this.emit("shellStatus",e)}),this.socket.on("thinknsh:notifications",e=>{this.emit("notificationList",e.notifications)}),this.socket.on("thinknsh:error",e=>{this.emit("serverError",e)}),this.socket.on("error",e=>{this.emit("serverError",e)});const e=new Set;this.socket.on("cicd:run-dispatch",async t=>{if(!t||!t.runId)return;if(e.has(t.runId))return;const s=this.roomId||this._joiningRoomId;if(!s||t.roomId&&String(t.roomId)!==String(s)){const e=require("chalk");return void console.log(e.yellow(`\n[CI/CD] Ignored run ${t.runId}: not for the room this shell joined.`))}e.add(t.runId);try{const e=require("chalk"),{runCicdLocal:s}=require("./local-runner"),o=this._shellRef,r=o?o.api:null;console.log(e.cyan(`\n[CI/CD] Execution request received for room: ${t.roomId||this.roomId}`)),await s({runId:t.runId,roomId:t.roomId||this.roomId,workflowFile:t.workflowFile||"universal-polyglot-ci.yml",workflowYaml:t.workflowYaml||"",localPath:t.localPath||"",apiUrl:r?r.baseURL:this.config.serverUrl,chalk:e,api:r,socket:this.socket})}catch(e){console.error("\n[Error] Error in CI/CD local execution:",e)}finally{setTimeout(()=>e.delete(t.runId),3e4)}});const t=new Set;this.socket.on("thinknsh:test-dispatch",async e=>{if(e&&e.runId&&e.taskId&&!t.has(e.runId)){t.add(e.runId);try{const t=require("chalk"),{spawn:s}=require("child_process"),o=require("http"),r=require("https"),n=require("crypto"),i=(e,t)=>{if(!t)return null;const s=n.createHash("sha256").update(t).digest(),o=n.randomBytes(12),r=n.createCipheriv("aes-256-gcm",s,o),i=JSON.stringify(e||{}),c=Buffer.concat([r.update(i,"utf8"),r.final()]),a=r.getAuthTag();return{iv:o.toString("hex"),tag:a.toString("hex"),cipherText:c.toString("hex")}},c=(e,t)=>{if(!(e&&e.cipherText&&e.iv&&e.tag))return null;try{const s=n.createHash("sha256").update(t).digest(),o=Buffer.from(e.iv,"hex"),r=Buffer.from(e.tag,"hex"),i=Buffer.from(e.cipherText,"hex"),c=n.createDecipheriv("aes-256-gcm",s,o);c.setAuthTag(r);const a=Buffer.concat([c.update(i),c.final()]);return JSON.parse(a.toString("utf8"))}catch(e){return null}},{runId:a,taskId:h,runResultToken:l,encryptedPayload:u}=e;let d=e.testConfig,m=e.commit,k=e.taskTitle;if(u&&l){const e=c(u,l);e&&(d=e.testConfig||d,m=e.commit||m,k=e.taskTitle||k)}console.log(t.cyan(`\n[TNC TestRunner - AES-256-GCM Secure] Task: "${k}"`)),console.log(t.gray(` Run ID : ${a}`)),console.log(t.gray(` Type : ${d?.type}`)),console.log(t.gray(` Commit : ${m?.sha?.slice(0,7)||"N/A"} — ${m?.message||""}`));let g={exitCode:1,stdout:"",stderr:"",status:null,body:null,url:null};if("exitCode"===d.type||"stdout"===d.type||"regex"===d.type){const e=d.command||d.script;e?(console.log(t.yellow(` Running : ${e}`)),g=await new Promise(o=>{const r=s(e,[],{shell:!0,cwd:process.cwd(),timeout:6e4});let n="",i="";r.stdout.on("data",e=>{n+=e.toString(),process.stdout.write(t.gray(" │ ")+e.toString())}),r.stderr.on("data",e=>{i+=e.toString(),process.stderr.write(t.red(" │ ")+e.toString())}),r.on("close",e=>{o({exitCode:e??1,stdout:n.trim(),stderr:i.trim(),status:null,body:null,url:null})}),r.on("error",e=>{o({exitCode:1,stdout:"",stderr:e.message,status:null,body:null,url:null})})}),console.log(0===g.exitCode?t.green(" [OK] Exit 0"):t.red(` [FAIL] Exit ${g.exitCode} - ${g.stderr.slice(0,100)}`))):g.stderr="No command specified in testConfig"}else if("http"===d.type){const e=d.endpoint||d.url,s=(d.method||"GET").toUpperCase();console.log(t.yellow(` Hitting : ${s} ${e}`)),g=await new Promise(n=>{const i=e?.startsWith("https")?r:o,c=new URL(e),a={hostname:c.hostname,port:c.port||("https:"===c.protocol?443:80),path:c.pathname+c.search,method:s},h=i.request(a,s=>{let o="";s.on("data",e=>o+=e.toString()),s.on("end",()=>{let r=o;try{r=JSON.parse(o)}catch(e){}console.log(t.green(` [OK] HTTP ${s.statusCode}`)),n({exitCode:0,stdout:o.slice(0,500),stderr:"",status:s.statusCode,body:r,url:e})})});h.on("error",s=>{console.log(t.red(` [Error] HTTP error: ${s.message}`)),n({exitCode:1,stdout:"",stderr:s.message,status:null,body:null,url:e})}),h.setTimeout(15e3,()=>{h.destroy(),n({exitCode:1,stdout:"",stderr:"Request timeout (15s)",status:null,body:null,url:e})})})}else if("browser"===d.type){console.log(t.yellow(` Browser Flow : ${d.url}`));try{const{runBrowserTest:e}=require("../commands/tasks/complete"),s=await e(d,null,{});g={exitCode:s.elementExists?0:1,stdout:s.bodyText?s.bodyText.slice(0,500):"",stderr:s.elementExists?"":`Element "${d.expect?.element}" not found`,elementExists:s.elementExists,elementText:s.elementText,url:s.url,status:200,body:null},console.log(t.green(` [OK] Browser check completed: elementExists=${s.elementExists}`))}catch(e){console.log(t.red(` [Error] Browser check failed: ${e.message}`)),g={exitCode:1,stdout:"",stderr:e.message,elementExists:!1,elementText:"",url:d.url,status:null,body:null}}}else{const e=d.command||d.script;e?g=await new Promise(t=>{const o=s(e,[],{shell:!0,cwd:process.cwd(),timeout:6e4});let r="",n="";o.stdout.on("data",e=>r+=e.toString()),o.stderr.on("data",e=>n+=e.toString()),o.on("close",e=>t({exitCode:e??1,stdout:r.trim(),stderr:n.trim(),status:null,body:null,url:null})),o.on("error",e=>t({exitCode:1,stdout:"",stderr:e.message,status:null,body:null,url:null}))}):g.stderr=`Unsupported testConfig type: ${d.type}`}const p={runId:a,taskId:h,runResultToken:l,encryptedResult:l?i(g,l):null,...g};this.socket.emit("thinknsh:test-result",p),console.log(t.cyan(" [AES-256-GCM] Encrypted result dispatched to TNC server.\n"))}catch(e){console.error("thinknsh:test-dispatch error:",e.message)}finally{setTimeout(()=>t.delete(e.runId),6e4)}}})}joinRoom(e){return new Promise((t,s)=>{if(!this.connected||!this.socket)return s(new Error("Not connected to server"));if(!this.currentUser)return s(new Error("Not logged in — run: login"));const o=setTimeout(()=>{this.socket.off("thinknsh:joined",r),this.socket.off("thinknsh:error",i),this.socket.off("thinknsh:ready",n),s(new Error("Join timeout"))},8e3),r=e=>{clearTimeout(o),this.socket.off("thinknsh:error",i),this.roomId=e.roomId,t(e)};let n=null;const i=e=>{clearTimeout(o),this.socket.off("thinknsh:joined",r),n&&this.socket.off("thinknsh:ready",n),s(new Error(e.message||"Join error"))},c=()=>{this.socket.once("thinknsh:joined",r),this.socket.once("thinknsh:error",i),this._emitJoin(e)};this._serverReady?c():(n=()=>{this._serverReady=!0,c()},this.socket.once("thinknsh:ready",n))})}_emitJoin(e){this.socket&&this.currentUser&&(this._joiningRoomId=e,this.socket.emit("thinknsh:join",{roomId:e,userId:this.currentUser.userId,name:this.currentUser.name,userType:this.currentUser.userType}))}leaveRoom(){return this.socket&&this.roomId&&(this.socket.emit("leave-room",{roomId:this.roomId}),this.roomId=null,this._joiningRoomId=null),Promise.resolve({success:!0})}sendMessage(e,t=null){return new Promise((s,o)=>{if(!this.socket?.connected)return o(new Error("Not connected"));const r=this.roomId;if(!r)return o(new Error("Not in any room. Use: join <room-id>"));if(!this.currentUser)return o(new Error("Not authenticated. Run: login"));const n=e?e.trim():"",i=e2eeVault.encrypt(n,r);this.socket.emit("thinknsh:message",{roomId:r,userId:this.currentUser.userId,name:this.currentUser.name,userType:this.currentUser.userType||"User",message:i,isE2EE:!0,replyTo:t||null}),s({success:!0,message:i,plaintext:n})})}getHistory(e=30,t=0){this.connected&&this.socket&&this.roomId&&this.socket.emit("thinknsh:getHistory",{roomId:this.roomId,limit:e,skip:t})}getNotifications(){this.connected&&this.currentUser&&this.socket.emit("thinknsh:getNotifications",{userId:this.currentUser.userId})}sendNotification(e,t="room"){return this.connected&&this.socket?(this.socket.emit("notification:send",{type:e.type||"info",title:e.title||"Notification",message:e.message,data:e.data||{},target:"room"===t?this.roomId:t,timestamp:(new Date).toISOString()}),Promise.resolve({success:!0})):Promise.reject(new Error("Not connected"))}markNotificationsRead(e=[]){this.connected&&this.currentUser&&this.socket.emit("thinknsh:markRead",{userId:this.currentUser.userId,notificationIds:e})}processPendingMessages(){this.messageQueue.length&&this.roomId&&this.currentUser&&(this.messageQueue.forEach(e=>this.sendMessage(e)),this.messageQueue=[])}disconnect(){this.socket&&(this.socket.removeAllListeners(),this.socket.disconnect(),this.socket=null,this.connected=!1,this.currentUser=null,this.roomId=null,this._handlersSetup=!1,this._serverReady=!1,this._serverReady=!1)}emitSandboxSync(e,t){this.connected&&this.socket&&e&&this.socket.emit("sandbox:sync",{userId:this.currentUser?.userId,roomId:this.roomId,event:t,sandbox:{id:e.id,state:e.state,pid:e.pid||null,cmd:e.cmd,hostPort:e.hostPort,containerPort:e.containerPort,isolationLevel:e.isolationLevel,memoryLimitMB:e.memoryLimitMB,startedAt:e.startedAt,frozenAt:e.frozenAt||null,platform:process.platform},timestamp:(new Date).toISOString()})}onSandboxAction(e,t){this.socket&&this.socket.on("sandbox:action",async s=>{const{sandboxId:o,action:r,requestedBy:n}=s||{};if(!o||!r)return;let i=null;try{switch(r){case"freeze":i=await e.freeze(o),i.success&&this.emitSandboxSync(i.meta,"frozen");break;case"resume":i=await e.resume(o),i.success&&this.emitSandboxSync(i.meta,"resumed");break;case"destroy":i=await e.destroy(o),this.emitSandboxSync({id:o,state:"DESTROYED"},"destroyed");break;default:i={success:!1,message:`Unknown action: ${r}`}}}catch(e){i={success:!1,message:e.message}}this.socket.emit("sandbox:action-ack",{sandboxId:o,action:r,requestedBy:n,success:i?.success,message:i?.message,timestamp:(new Date).toISOString()}),t&&t(r,i)})}isConnected(){return this.connected&&!!this.socket?.connected}getCurrentRoom(){return this.roomId}getSocketId(){return this.socket?.id||null}getParticipants(){return Promise.resolve([])}sendTyping(){}ping(){this.connected&&this.socket&&this.socket.emit("ping")}}module.exports=WebSocketManager;
7
+ const{io}=require("socket.io-client"),EventEmitter=require("events"),e2eeVault=require("./crypto/e2ee-vault");class WebSocketManager extends EventEmitter{constructor(e={}){super(),this.config={serverUrl:e.serverUrl||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com",reconnectAttempts:e.reconnectAttempts||5,reconnectDelay:e.reconnectDelay||1e3,...e},this.socket=null,this.connected=!1,this.currentUser=null,this.roomId=null,this.reconnectCount=0,this.messageQueue=[],this._authToken=null,this._handlersSetup=!1,this._serverReady=!1,this._shellRef=e._shellRef||null}connect(e=null){return new Promise((t,s)=>{try{if(e&&(this._authToken=e),this._authToken){const e=new URL(this.config.serverUrl);if("https:"!==e.protocol&&!["localhost","127.0.0.1","[::1]"].includes(e.hostname))return s(new Error(`refusing to send the sign-in token to ${e.origin}: use https://`))}this.socket=io(this.config.serverUrl+"/thinknsh",{transports:["websocket","polling"],reconnection:!0,reconnectionAttempts:this.config.reconnectAttempts,reconnectionDelay:this.config.reconnectDelay,auth:this._authToken?{token:this._authToken}:{}});const o=setTimeout(()=>{s(new Error("Connection timeout"))},1e4);this.socket.on("connect",()=>{clearTimeout(o),this.connected=!0,this.reconnectCount=0,this.currentUser&&this._announceUser(),this.processPendingMessages(),this.emit("connected",{socketId:this.socket.id}),t({success:!0,socketId:this.socket.id})}),this.socket.on("connect_error",e=>{clearTimeout(o),0===this.reconnectCount&&s(new Error(`Cannot connect to ${this.config.serverUrl}`)),this.reconnectCount++,this.emit("error",{message:e.message})}),this.socket.on("disconnect",e=>{this.connected=!1,this.emit("disconnected",{reason:e})}),this.socket.on("task:verdict",e=>{this.emit("task:verdict",e)}),this.socket.on("task:run-local",async e=>{try{const t=require("chalk"),{runLocal:s}=require("./local-runner"),o=e.taskId,r=this._shellRef,n=r?r.api:null;if(!n)return void console.log(t.red("\n[Error] CLI not authenticated. Run: login\n"));let i;try{i=await n._request("GET",`/thinknsh/${o}/cli`)}catch(e){return void console.log(t.red(`\n[Error] Could not fetch task info: ${e.message}\n`))}if(!i||!i.webhookSecret)return void console.log(t.red("\n[Error] Task has no webhookSecret - cannot submit result.\n"));await s({taskId:o,webhookSecret:i.webhookSecret,apiUrl:this.config.serverUrl||process.env.THINKNCOLLAB_SERVER||"https://thinkncollab.com",repoUrl:i.repoUrl||i.githubUrl||i.repo_url||null,repoBranch:i.branch||i.repoBranch||"main",workflowFile:i.workflowFile||i.workflow||null,chalk:t,shell:r,onLog:e=>{this.socket.emit("task:log",{taskId:o,boardId:i.boardId,...e})}})}catch(e){console.error("task:run-local error:",e.message)}}),this.socket.on("reconnect",()=>{this.connected=!0,this.emit("reconnected",{}),this.currentUser&&this._announceUser(),this.roomId&&this.currentUser&&this._emitJoin(this.roomId)}),this._handlersSetup||(this.setupEventHandlers(),this._handlersSetup=!0)}catch(e){s(e)}})}_announceUser(){if(this.socket&&this.currentUser&&(this.socket.emit("thinknsh:connected",{userId:this.currentUser.userId,userName:this.currentUser.name,shellToken:this._authToken}),this._shellRef?.sandboxEngine))try{this._shellRef.sandboxEngine.list({start:!1}).then(e=>e.forEach(e=>this.emitSandboxSync(e,"sync"))).catch(()=>{})}catch(e){}}setUser(e,t=null){if(this._user=e,!e)return;t&&(this._authToken=t);const s=(e.userId||e._id||"").toString();this.currentUser={userId:s,name:e.name||e.email||"Unknown",userType:e.userType||e.model||e.role||"User"},this.connected&&this.socket&&this._announceUser()}setupEventHandlers(){this.socket.on("thinknsh:ready",e=>{this._serverReady=!0,this.emit("ready",e)}),this.socket.on("thinknsh:joined",e=>{if(this.roomId=e.roomId,e.history?.length){const t=e.history.map(e=>{let t=e.message||e.content||e.text||"",s=!1;if("string"==typeof t&&t.startsWith("e2ee:")){const e=e2eeVault.decrypt(t,this.roomId);e!==t&&(t=e,s=!0)}return{...e,message:t,isE2EE:s}});this.emit("messageHistory",t)}this.emit("roomJoined",e)}),this.socket.on("thinknsh:message",e=>{let t=e.message||"",s=e.isE2EE||!1;if("string"==typeof t&&t.startsWith("e2ee:")){const e=e2eeVault.decrypt(t,this.roomId);e!==t&&(t=e,s=!0)}this.emit("message",{username:e.name,userId:e.userId,message:t,isE2EE:s,source:"shell",timestamp:e.timestamp})}),this.socket.on("chat-message",e=>{let t=e.message||"",s=e.isE2EE||!1;if("string"==typeof t&&t.startsWith("e2ee:")){const e=e2eeVault.decrypt(t,this.roomId);e!==t&&(t=e,s=!0)}this.emit("message",{username:e.name,userId:e.userId,message:t,isE2EE:s,source:e.source||"web",timestamp:e.timestamp})}),this.socket.on("e2ee:key-sync",e=>{!e?.roomId||this.roomId&&e.roomId!==this.roomId||this.emit("e2ee:key-sync",e)}),this.socket.on("e2ee:key-delivered",e=>{e?.packet?.rawKey&&(e2eeVault.setRawRoomKey(e.roomId,e.packet.rawKey),this.emit("e2ee:key-updated",e))}),this.socket.on("e2ee:packet",e=>{this.emit("e2ee:packet",e)}),this.socket.on("thinknsh:userJoined",e=>{this.emit("userJoined",{username:e.name,source:"shell"})}),this.socket.on("thinknsh:userLeft",e=>{this.emit("userLeft",{username:e.name,source:"shell"})}),this.socket.on("user-joined",e=>{this.emit("userJoined",{username:e,source:"web"})}),this.socket.on("user-left",e=>{this.emit("userLeft",{username:e,source:"web"})}),this.socket.on("user-list",e=>{this.emit("userList",{users:e})}),this.socket.on("thinknsh:history",e=>{this.emit("messageHistory",e.messages?.length?e.messages:[])}),this.socket.on("new-notification",e=>{this.emit("notification",{type:e.type||"info",level:e.type||"info",title:e.title||"Notification",message:e.message||"",meta:e.meta||{},taskTitle:e.taskTitle||e.meta?.taskTitle||"",assignedBy:e.assignedBy||e.meta?.assignedBy||""})}),this.socket.on("room:activity",e=>{this.emit("roomActivity",e)}),this.socket.on("thinknsh:status",e=>{this.emit("shellStatus",e)}),this.socket.on("thinknsh:notifications",e=>{this.emit("notificationList",e.notifications)}),this.socket.on("thinknsh:error",e=>{this.emit("serverError",e)}),this.socket.on("error",e=>{this.emit("serverError",e)});const e=new Set;this.socket.on("cicd:run-dispatch",async t=>{if(!t||!t.runId)return;if(e.has(t.runId))return;const s=this.roomId||this._joiningRoomId;if(!s||t.roomId&&String(t.roomId)!==String(s)){const e=require("chalk");return void console.log(e.yellow(`\n[CI/CD] Ignored run ${t.runId}: not for the room this shell joined.`))}e.add(t.runId);try{const e=require("chalk"),{runCicdLocal:s}=require("./local-runner"),o=this._shellRef,r=o?o.api:null;console.log(e.cyan(`\n[CI/CD] Execution request received for room: ${t.roomId||this.roomId}`)),await s({runId:t.runId,roomId:t.roomId||this.roomId,workflowFile:t.workflowFile||"universal-polyglot-ci.yml",workflowYaml:t.workflowYaml||"",localPath:t.localPath||"",apiUrl:r?r.baseURL:this.config.serverUrl,chalk:e,api:r,socket:this.socket})}catch(e){console.error("\n[Error] Error in CI/CD local execution:",e)}finally{setTimeout(()=>e.delete(t.runId),3e4)}});const t=new Set;this.socket.on("thinknsh:test-dispatch",async e=>{if(e&&e.runId&&e.taskId&&!t.has(e.runId)){t.add(e.runId);try{const t=require("chalk"),{spawn:s}=require("child_process"),o=require("http"),r=require("https"),n=require("crypto"),i=(e,t)=>{if(!t)return null;const s=n.createHash("sha256").update(t).digest(),o=n.randomBytes(12),r=n.createCipheriv("aes-256-gcm",s,o),i=JSON.stringify(e||{}),c=Buffer.concat([r.update(i,"utf8"),r.final()]),a=r.getAuthTag();return{iv:o.toString("hex"),tag:a.toString("hex"),cipherText:c.toString("hex")}},c=(e,t)=>{if(!(e&&e.cipherText&&e.iv&&e.tag))return null;try{const s=n.createHash("sha256").update(t).digest(),o=Buffer.from(e.iv,"hex"),r=Buffer.from(e.tag,"hex"),i=Buffer.from(e.cipherText,"hex"),c=n.createDecipheriv("aes-256-gcm",s,o);c.setAuthTag(r);const a=Buffer.concat([c.update(i),c.final()]);return JSON.parse(a.toString("utf8"))}catch(e){return null}},{runId:a,taskId:h,runResultToken:l,encryptedPayload:u}=e;let d=e.testConfig,m=e.commit,k=e.taskTitle;if(u&&l){const e=c(u,l);e&&(d=e.testConfig||d,m=e.commit||m,k=e.taskTitle||k)}console.log(t.cyan(`\n[TNC TestRunner - AES-256-GCM Secure] Task: "${k}"`)),console.log(t.gray(` Run ID : ${a}`)),console.log(t.gray(` Type : ${d?.type}`)),console.log(t.gray(` Commit : ${m?.sha?.slice(0,7)||"N/A"} — ${m?.message||""}`));let g={exitCode:1,stdout:"",stderr:"",status:null,body:null,url:null};if("exitCode"===d.type||"stdout"===d.type||"regex"===d.type){const e=d.command||d.script;e?(console.log(t.yellow(` Running : ${e}`)),g=await new Promise(o=>{const r=s(e,[],{shell:!0,cwd:process.cwd(),timeout:6e4});let n="",i="";r.stdout.on("data",e=>{n+=e.toString(),process.stdout.write(t.gray(" │ ")+e.toString())}),r.stderr.on("data",e=>{i+=e.toString(),process.stderr.write(t.red(" │ ")+e.toString())}),r.on("close",e=>{o({exitCode:e??1,stdout:n.trim(),stderr:i.trim(),status:null,body:null,url:null})}),r.on("error",e=>{o({exitCode:1,stdout:"",stderr:e.message,status:null,body:null,url:null})})}),console.log(0===g.exitCode?t.green(" [OK] Exit 0"):t.red(` [FAIL] Exit ${g.exitCode} - ${g.stderr.slice(0,100)}`))):g.stderr="No command specified in testConfig"}else if("http"===d.type){const e=d.endpoint||d.url,s=(d.method||"GET").toUpperCase();console.log(t.yellow(` Hitting : ${s} ${e}`)),g=await new Promise(n=>{const i=e?.startsWith("https")?r:o,c=new URL(e),a={hostname:c.hostname,port:c.port||("https:"===c.protocol?443:80),path:c.pathname+c.search,method:s},h=i.request(a,s=>{let o="";s.on("data",e=>o+=e.toString()),s.on("end",()=>{let r=o;try{r=JSON.parse(o)}catch(e){}console.log(t.green(` [OK] HTTP ${s.statusCode}`)),n({exitCode:0,stdout:o.slice(0,500),stderr:"",status:s.statusCode,body:r,url:e})})});h.on("error",s=>{console.log(t.red(` [Error] HTTP error: ${s.message}`)),n({exitCode:1,stdout:"",stderr:s.message,status:null,body:null,url:e})}),h.setTimeout(15e3,()=>{h.destroy(),n({exitCode:1,stdout:"",stderr:"Request timeout (15s)",status:null,body:null,url:e})})})}else if("browser"===d.type){console.log(t.yellow(` Browser Flow : ${d.url}`));try{const{runBrowserTest:e}=require("../commands/tasks/complete"),s=await e(d,null,{});g={exitCode:s.elementExists?0:1,stdout:s.bodyText?s.bodyText.slice(0,500):"",stderr:s.elementExists?"":`Element "${d.expect?.element}" not found`,elementExists:s.elementExists,elementText:s.elementText,url:s.url,status:200,body:null},console.log(t.green(` [OK] Browser check completed: elementExists=${s.elementExists}`))}catch(e){console.log(t.red(` [Error] Browser check failed: ${e.message}`)),g={exitCode:1,stdout:"",stderr:e.message,elementExists:!1,elementText:"",url:d.url,status:null,body:null}}}else{const e=d.command||d.script;e?g=await new Promise(t=>{const o=s(e,[],{shell:!0,cwd:process.cwd(),timeout:6e4});let r="",n="";o.stdout.on("data",e=>r+=e.toString()),o.stderr.on("data",e=>n+=e.toString()),o.on("close",e=>t({exitCode:e??1,stdout:r.trim(),stderr:n.trim(),status:null,body:null,url:null})),o.on("error",e=>t({exitCode:1,stdout:"",stderr:e.message,status:null,body:null,url:null}))}):g.stderr=`Unsupported testConfig type: ${d.type}`}const y={runId:a,taskId:h,runResultToken:l,encryptedResult:l?i(g,l):null,...g};this.socket.emit("thinknsh:test-result",y),console.log(t.cyan(" [AES-256-GCM] Encrypted result dispatched to TNC server.\n"))}catch(e){console.error("thinknsh:test-dispatch error:",e.message)}finally{setTimeout(()=>t.delete(e.runId),6e4)}}})}joinRoom(e){return new Promise((t,s)=>{if(!this.connected||!this.socket)return s(new Error("Not connected to server"));if(!this.currentUser)return s(new Error("Not logged in — run: login"));const o=setTimeout(()=>{this.socket.off("thinknsh:joined",r),this.socket.off("thinknsh:error",i),this.socket.off("thinknsh:ready",n),s(new Error("Join timeout"))},8e3),r=e=>{clearTimeout(o),this.socket.off("thinknsh:error",i),this.roomId=e.roomId,t(e)};let n=null;const i=e=>{clearTimeout(o),this.socket.off("thinknsh:joined",r),n&&this.socket.off("thinknsh:ready",n),s(new Error(e.message||"Join error"))},c=()=>{this.socket.once("thinknsh:joined",r),this.socket.once("thinknsh:error",i),this._emitJoin(e)};this._serverReady?c():(n=()=>{this._serverReady=!0,c()},this.socket.once("thinknsh:ready",n))})}_emitJoin(e){this.socket&&this.currentUser&&(this._joiningRoomId=e,this.socket.emit("thinknsh:join",{roomId:e,userId:this.currentUser.userId,name:this.currentUser.name,userType:this.currentUser.userType}))}leaveRoom(){return this.socket&&this.roomId&&(this.socket.emit("leave-room",{roomId:this.roomId}),this.roomId=null,this._joiningRoomId=null),Promise.resolve({success:!0})}sendMessage(e,t=null){return new Promise((s,o)=>{if(!this.socket?.connected)return o(new Error("Not connected"));const r=this.roomId;if(!r)return o(new Error("Not in any room. Use: join <room-id>"));if(!this.currentUser)return o(new Error("Not authenticated. Run: login"));const n=e?e.trim():"",i=e2eeVault.encrypt(n,r);this.socket.emit("thinknsh:message",{roomId:r,userId:this.currentUser.userId,name:this.currentUser.name,userType:this.currentUser.userType||"User",message:i,isE2EE:!0,replyTo:t||null}),s({success:!0,message:i,plaintext:n})})}getHistory(e=30,t=0){this.connected&&this.socket&&this.roomId&&this.socket.emit("thinknsh:getHistory",{roomId:this.roomId,limit:e,skip:t})}getNotifications(){this.connected&&this.currentUser&&this.socket.emit("thinknsh:getNotifications",{userId:this.currentUser.userId})}sendNotification(e,t="room"){return this.connected&&this.socket?(this.socket.emit("notification:send",{type:e.type||"info",title:e.title||"Notification",message:e.message,data:e.data||{},target:"room"===t?this.roomId:t,timestamp:(new Date).toISOString()}),Promise.resolve({success:!0})):Promise.reject(new Error("Not connected"))}markNotificationsRead(e=[]){this.connected&&this.currentUser&&this.socket.emit("thinknsh:markRead",{userId:this.currentUser.userId,notificationIds:e})}processPendingMessages(){this.messageQueue.length&&this.roomId&&this.currentUser&&(this.messageQueue.forEach(e=>this.sendMessage(e)),this.messageQueue=[])}disconnect(){this.socket&&(this.socket.removeAllListeners(),this.socket.disconnect(),this.socket=null,this.connected=!1,this.currentUser=null,this.roomId=null,this._handlersSetup=!1,this._serverReady=!1,this._serverReady=!1)}emitSandboxSync(e,t){this.connected&&this.socket&&e&&this.socket.emit("sandbox:sync",{userId:this.currentUser?.userId,roomId:this.roomId,event:t,sandbox:{id:e.id,state:e.state,pid:e.pid||null,cmd:e.cmd,hostPort:e.hostPort,containerPort:e.containerPort,isolationLevel:e.isolationLevel,memoryLimitMB:e.memoryLimitMB,image:e.image||null,container:e.container||null,runtime:e.runtime||null,roomName:e.roomName||null,userName:e.userName||null,startedAt:e.startedAt,frozenAt:e.frozenAt||null,platform:process.platform},timestamp:(new Date).toISOString()})}onSandboxAction(e,t){this.socket&&(this.socket.on("sandbox:action",async s=>{const{sandboxId:o,action:r,requestedBy:n}=s||{};if(!o||!r)return;const i=this.currentUser?.userId;if(!/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,62}$/.test(String(o))||n&&i&&String(n)!==String(i))return void this.socket.emit("sandbox:action-ack",{sandboxId:o,action:r,requestedBy:n,success:!1,message:"Only the sandbox owner can do that",timestamp:(new Date).toISOString()});let c=null;try{switch(r){case"freeze":c=await e.freeze(o),c.success&&this.emitSandboxSync(c.meta,"frozen");break;case"resume":c=await e.resume(o),c.success&&this.emitSandboxSync(c.meta,"resumed");break;case"destroy":c=await e.destroy(o),this.emitSandboxSync({id:o,state:"DESTROYED"},"destroyed");break;case"restart":await e.freeze(o),c=await e.resume(o),c.success&&this.emitSandboxSync(c.meta,"resumed");break;default:c={success:!1,message:`Unknown action: ${r}`}}}catch(e){c={success:!1,message:e.message}}this.socket.emit("sandbox:action-ack",{sandboxId:o,action:r,requestedBy:n,success:c?.success,message:c?.message,timestamp:(new Date).toISOString()}),t&&t(r,c)}),this.socket.on("sandbox:logs-request",async({requestId:t,sandboxId:s,requestedBy:o}={})=>{if(!t||!/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,62}$/.test(String(s||"")))return;const r=this.currentUser?.userId;if(o&&r&&String(o)!==String(r))return;let n;try{n=await e.logs(s,{tail:300})}catch(e){n=`[thinknsh] ${e.message}`}this.socket.emit("sandbox:logs-reply",{requestId:t,sandboxId:s,logs:n})}))}isConnected(){return this.connected&&!!this.socket?.connected}getCurrentRoom(){return this.roomId}getSocketId(){return this.socket?.id||null}getParticipants(){return Promise.resolve([])}sendTyping(){}ping(){this.connected&&this.socket&&this.socket.emit("ping")}}module.exports=WebSocketManager;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @license ThinkNCollab Enterprise & Open Core Ecosystem
3
+ * Copyright (c) 2024-2026 ThinkNCollab Systems Inc. All rights reserved.
4
+ * PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
5
+ * UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
6
+ */
7
+ const{spawn}=require("child_process"),chalk=require("chalk");class SandboxWindowLauncher{static spawn(e){const n=process.argv[1],o=process.execPath,s=`logs -f tnc-sb-${e}`;if("darwin"===process.platform){"vscode"===process.env.TERM_PROGRAM||process.env.VSCODE_INJECTION;const a=`\n tell application "Terminal"\n launch\n do script "'${o}' '${n}' ${s}"\n activate\n end tell\n `;try{return spawn("osascript",["-e",a],{detached:!0,stdio:"ignore"}).unref(),console.log(chalk.green("[TNC] Dedicated Sandbox Console opened in separate Terminal window!")),!0}catch(n){return console.log(chalk.yellow(`[Notice] Could not auto-launch Terminal window: ${n.message}`)),console.log(chalk.dim(`You can manually view it with: thinknsh logs -f tnc-sb-${e}`)),!1}}if("win32"===process.platform)try{return spawn(`start cmd.exe /k "title TNC Sandbox: ${e} && "${o}" "${n}" ${s}""`,[],{detached:!0,stdio:"ignore",shell:!0}).unref(),console.log(chalk.green("[TNC] Sandbox Console opened in separate Command Prompt window!")),!0}catch(e){return!1}const a=["x-terminal-emulator","gnome-terminal","konsole","xterm"];for(const e of a)try{return spawn(e,["-e",`'${o}' '${n}' ${s}`],{detached:!0,stdio:"ignore"}).unref(),console.log(chalk.green(`[TNC] Sandbox Console opened in ${e}!`)),!0}catch(e){}return console.log(chalk.dim(`Tip: Run "thinknsh logs -f tnc-sb-${e}" to view live logs.`)),!1}}module.exports=SandboxWindowLauncher;
@@ -4,4 +4,4 @@
4
4
  * PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
5
5
  * UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
6
6
  */
7
- "use strict";const fs=require("fs"),{spawnSync}=require("child_process"),VZ_XPC="com.apple.Virtualization.VirtualMachine",VZ_ATTRIBUTION_WINDOW_MS=15e3,UNIT={B:1,KB:1024,MB:1048576,GB:1024**3},DAEMON_SCRIPTS=/[/\\](dock[/\\]daemon-main|port-proxy-daemon|tunnel-daemon)\.js\b/;function parseFootprint(s){const e=/phys_footprint:\s*([\d.]+)\s*(B|KB|MB|GB)\b/.exec(s)||/Footprint:\s*([\d.]+)\s*(B|KB|MB|GB)\b/.exec(s);return e?Math.round(parseFloat(e[1])*UNIT[e[2]]):null}function parseKbField(s,e){const t=new RegExp(`^${e}:\\s*(\\d+)\\s*kB`,"m").exec(s);return t?1024*parseInt(t[1],10):null}function parsePsLine(s){const e=s.trim().split(/\s+/);if(e.length<8)return null;const t=parseInt(e[0],10),r=parseInt(e[1],10),n=Date.parse(e.slice(2,7).join(" "));return!Number.isInteger(t)||Number.isNaN(n)?null:{pid:t,ppid:r,started:n,args:e.slice(7).join(" ")}}function listProcesses(){const s=spawnSync("ps",["-axo","pid=,ppid=,lstart=,args="],{encoding:"utf8",maxBuffer:33554432});if(0!==s.status)throw new Error(`ps failed: ${s.stderr}`);return s.stdout.split("\n").map(parsePsLine).filter(Boolean)}function rssBytes(s){const e=spawnSync("ps",["-o","rss=","-p",String(s)],{encoding:"utf8"}),t=parseInt((e.stdout||"").trim(),10);return Number.isInteger(t)?1024*t:null}function memoryOf(s){if("darwin"===process.platform){const e=spawnSync("footprint",[String(s)],{encoding:"utf8"}),t=0===e.status?parseFootprint(e.stdout):null;if(null!==t)return{bytes:t,method:"phys_footprint"}}else if("linux"===process.platform){try{const e=parseKbField(fs.readFileSync(`/proc/${s}/smaps_rollup`,"utf8"),"Pss");if(null!==e)return{bytes:e,method:"pss"}}catch(s){}try{const e=parseKbField(fs.readFileSync(`/proc/${s}/status`,"utf8"),"VmRSS");if(null!==e)return{bytes:e,method:"rss"}}catch(s){}}const e=rssBytes(s);return null===e?null:{bytes:e,method:"rss"}}function ancestorsOf(s,e){const t=new Set;let r=e.get(s);for(;r&&!t.has(r.pid);)t.add(r.pid),r=e.get(r.ppid);return t}function findOurProcesses(s=listProcesses()){const e=new Map(s.map(s=>[s.pid,s])),t=ancestorsOf(process.pid,e),r=[],n=s.filter(s=>/[/\\]\.tnc[/\\]bin[/\\]tnc-vz\s+start\b/.test(s.args));for(const s of n)r.push({...s,role:"vm-launcher"});const o=s.filter(s=>s.args.includes(VZ_XPC));for(const s of n){const e=o.filter(e=>e.started>=s.started-1e3&&e.started-s.started<=15e3).sort((e,t)=>Math.abs(e.started-s.started)-Math.abs(t.started-s.started))[0];e&&!r.some(s=>s.pid===e.pid)&&r.push({...e,role:"vm"})}for(const e of s)t.has(e.pid)||r.some(s=>s.pid===e.pid)||DAEMON_SCRIPTS.test(e.args)&&r.push({...e,role:"daemon"});return r}function sample(){const s=findOurProcesses().map(s=>({pid:s.pid,role:s.role,args:s.args,...memoryOf(s.pid)||{bytes:0,method:"gone"}}));return{time:Date.now(),totalBytes:s.reduce((s,e)=>s+e.bytes,0),processes:s}}module.exports={parseFootprint,parseKbField,parsePsLine,findOurProcesses,memoryOf,sample,listProcesses};
7
+ "use strict";const fs=require("fs"),{spawnSync}=require("child_process"),VZ_XPC="com.apple.Virtualization.VirtualMachine",VZ_ATTRIBUTION_WINDOW_MS=15e3,UNIT={B:1,KB:1024,MB:1048576,GB:1024**3},DAEMON_SCRIPTS=/[/\\](dock[/\\]daemon-main|tunnel[/\\]daemon)\.js\b/;function parseFootprint(s){const e=/phys_footprint:\s*([\d.]+)\s*(B|KB|MB|GB)\b/.exec(s)||/Footprint:\s*([\d.]+)\s*(B|KB|MB|GB)\b/.exec(s);return e?Math.round(parseFloat(e[1])*UNIT[e[2]]):null}function parseKbField(s,e){const t=new RegExp(`^${e}:\\s*(\\d+)\\s*kB`,"m").exec(s);return t?1024*parseInt(t[1],10):null}function parsePsLine(s){const e=s.trim().split(/\s+/);if(e.length<8)return null;const t=parseInt(e[0],10),r=parseInt(e[1],10),n=Date.parse(e.slice(2,7).join(" "));return!Number.isInteger(t)||Number.isNaN(n)?null:{pid:t,ppid:r,started:n,args:e.slice(7).join(" ")}}function listProcesses(){const s=spawnSync("ps",["-axo","pid=,ppid=,lstart=,args="],{encoding:"utf8",maxBuffer:33554432});if(0!==s.status)throw new Error(`ps failed: ${s.stderr}`);return s.stdout.split("\n").map(parsePsLine).filter(Boolean)}function rssBytes(s){const e=spawnSync("ps",["-o","rss=","-p",String(s)],{encoding:"utf8"}),t=parseInt((e.stdout||"").trim(),10);return Number.isInteger(t)?1024*t:null}function memoryOf(s){if("darwin"===process.platform){const e=spawnSync("footprint",[String(s)],{encoding:"utf8"}),t=0===e.status?parseFootprint(e.stdout):null;if(null!==t)return{bytes:t,method:"phys_footprint"}}else if("linux"===process.platform){try{const e=parseKbField(fs.readFileSync(`/proc/${s}/smaps_rollup`,"utf8"),"Pss");if(null!==e)return{bytes:e,method:"pss"}}catch(s){}try{const e=parseKbField(fs.readFileSync(`/proc/${s}/status`,"utf8"),"VmRSS");if(null!==e)return{bytes:e,method:"rss"}}catch(s){}}const e=rssBytes(s);return null===e?null:{bytes:e,method:"rss"}}function ancestorsOf(s,e){const t=new Set;let r=e.get(s);for(;r&&!t.has(r.pid);)t.add(r.pid),r=e.get(r.ppid);return t}function findOurProcesses(s=listProcesses()){const e=new Map(s.map(s=>[s.pid,s])),t=ancestorsOf(process.pid,e),r=[],n=s.filter(s=>/[/\\]\.tnc[/\\]bin[/\\]tnc-vz\s+start\b/.test(s.args));for(const s of n)r.push({...s,role:"vm-launcher"});const o=s.filter(s=>s.args.includes(VZ_XPC));for(const s of n){const e=o.filter(e=>e.started>=s.started-1e3&&e.started-s.started<=15e3).sort((e,t)=>Math.abs(e.started-s.started)-Math.abs(t.started-s.started))[0];e&&!r.some(s=>s.pid===e.pid)&&r.push({...e,role:"vm"})}for(const e of s)t.has(e.pid)||r.some(s=>s.pid===e.pid)||DAEMON_SCRIPTS.test(e.args)&&r.push({...e,role:"daemon"});return r}function sample(){const s=findOurProcesses().map(s=>({pid:s.pid,role:s.role,args:s.args,...memoryOf(s.pid)||{bytes:0,method:"gone"}}));return{time:Date.now(),totalBytes:s.reduce((s,e)=>s+e.bytes,0),processes:s}}module.exports={parseFootprint,parseKbField,parsePsLine,findOurProcesses,memoryOf,sample,listProcesses};