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
@@ -1,7 +0,0 @@
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 path=require("path"),os=require("os"),net=require("net"),fs=require("fs-extra"),{spawn,spawnSync,execSync}=require("child_process"),chalk=require("chalk"),TNC_DIR=path.join(os.homedir(),".tnc"),TNC_BIN_DIR=path.join(TNC_DIR,"bin"),TNC_VZ_BIN=path.join(TNC_BIN_DIR,"tnc-vz"),VM_PID_FILE=path.join(TNC_DIR,"microvm.pid"),VM_SOCK=path.join(TNC_DIR,"vmctl.sock"),KERNEL_DIR=path.join(TNC_DIR,"kernel"),SANDBOX_DIR=path.join(TNC_DIR,"sandboxes"),SETUP_SCRIPT=path.join(__dirname,"..","..","..","native","tnc-vz","scripts","setup-microvm.sh");function sendCommand(n,e=15e3){return new Promise((t,s)=>{const r=net.createConnection(VM_SOCK,()=>{const e=JSON.stringify(n)+"\n";r.write(e)});let i="";const o=setTimeout(()=>{r.destroy(),s(new Error(`MicroVM command timed out: ${n.cmd}`))},e);r.on("data",n=>{i+=n.toString();const e=i.indexOf("\n");if(-1!==e){clearTimeout(o),r.destroy();try{t(JSON.parse(i.slice(0,e)))}catch(n){s(new Error(`Bad response from MicroVM: ${i.slice(0,e)}`))}}}),r.on("error",n=>{clearTimeout(o),s(new Error(`vsock relay error: ${n.message}`))})})}class MicroVMManager{constructor(){fs.ensureDirSync(TNC_DIR),fs.ensureDirSync(SANDBOX_DIR),this._startingVM=!1}isTncVzAvailable(){try{return fs.existsSync(TNC_VZ_BIN)&&fs.statSync(TNC_VZ_BIN).size>1e4}catch(n){return!1}}isKernelReady(){const n=path.join(KERNEL_DIR,"vmlinuz"),e=path.join(KERNEL_DIR,"initramfs.img");return fs.existsSync(n)&&fs.existsSync(e)}async runSetup(){if(!fs.existsSync(SETUP_SCRIPT))throw new Error(`MicroVM setup script not found: ${SETUP_SCRIPT}\nPlease reinstall ThinkNCollab Shell: npm install -g thinkncollab-shell`);return console.log(chalk.cyan("\n[TNC MicroVM] First-time setup — downloading Alpine Linux kernel (~15MB)...")),console.log(chalk.dim("This runs once. Subsequent boots take 0.3 seconds.\n")),new Promise((n,e)=>{spawn("bash",[SETUP_SCRIPT],{stdio:"inherit"}).on("close",t=>{0===t?n():e(new Error(`MicroVM setup failed (exit ${t})`))})})}isVMRunning(){try{const n=fs.readFileSync(VM_PID_FILE,"utf8").trim(),e=parseInt(n,10);return!!e&&(process.kill(e,0),!0)}catch(n){return!1}}async pingVM(){try{const n=await sendCommand({cmd:"ping"},2e3);return n&&n.ok&&n.pong}catch(n){return!1}}async ensureVMRunning(n={}){if(this._startingVM)return await new Promise(n=>setTimeout(n,500)),this.ensureVMRunning(n);if(this.isTncVzAvailable()&&this.isKernelReady()||await this.runSetup(),this.isVMRunning()){if(await this.pingVM())return;this._killOrphanVM()}this._startingVM=!0;try{console.log(chalk.cyan("[TNC MicroVM] Starting Linux MicroVM..."));const e=["start","--cpus",String(n.cpus||2),"--mem",String(n.memMB||512),"--workspace",n.workspace||os.homedir()];spawn(TNC_VZ_BIN,e,{detached:!0,stdio:"ignore"}).unref(),console.log(chalk.dim(" Waiting for MicroVM guest-init..."));const t=Date.now()+1e4;for(;Date.now()<t;)if(await new Promise(n=>setTimeout(n,300)),fs.existsSync(VM_SOCK)&&await this.pingVM()){const n=1e4-(t-Date.now());return void console.log(chalk.green(`[TNC MicroVM] Ready in ${n}ms ✓`))}throw new Error("MicroVM did not become ready within 10 seconds")}finally{this._startingVM=!1}}_killOrphanVM(){try{const n=fs.readFileSync(VM_PID_FILE,"utf8").trim(),e=parseInt(n,10);e&&process.kill(e,"SIGTERM")}catch(n){}try{fs.removeSync(VM_PID_FILE)}catch(n){}try{fs.removeSync(VM_SOCK)}catch(n){}}async stopVM(){if(this.isTncVzAvailable()){spawnSync(TNC_VZ_BIN,["stop"],{stdio:"pipe"});try{fs.removeSync(VM_PID_FILE)}catch(n){}try{fs.removeSync(VM_SOCK)}catch(n){}console.log(chalk.green("[TNC MicroVM] Stopped."))}}async getStatus(){const n=this.isVMRunning(),e=!!n&&await this.pingVM();return{available:this.isTncVzAvailable(),kernelReady:this.isKernelReady(),running:n,responsive:e}}async start(n,e={}){await this.ensureVMRunning();let t=null,s=e.cmd||null,r=e.args||null,i="/",o=[];if(e.image)try{const n=new(require("./oci-puller"));let a=n.inspect(e.image);if(a||(console.log(chalk.dim(` Pulling image: ${e.image}...`)),a=(await n.pull(e.image)).meta),a){if(a.rootfsDir&&fs.existsSync(a.rootfsDir)&&(t=a.rootfsDir),a.env&&Array.isArray(a.env)&&(o=a.env),!r&&!e.cmd){const n=a.entrypoint||[],e=a.cmd||[];(n.length||e.length)&&(r=[...n,...e],s=r.join(" "))}a.workingDir&&""!==a.workingDir&&"/workspace"!==a.workingDir&&(i=a.workingDir)}}catch(n){console.warn(chalk.yellow(`[MicroVM] Image resolution: ${n.message}`))}t||(t=e.cwd||process.cwd()),s||r||(s="/bin/sh");const a={};for(const n of o)if("string"==typeof n){const e=n.indexOf("=");-1!==e&&(a[n.slice(0,e)]=n.slice(e+1))}if(e.env)if(Array.isArray(e.env)){for(const n of e.env)if("string"==typeof n){const e=n.indexOf("=");-1!==e&&(a[n.slice(0,e)]=n.slice(e+1))}}else if("object"==typeof e.env&&null!==e.env)for(const[n,t]of Object.entries(e.env))a[n]=String(t);const c=Object.entries(a).map(([n,e])=>`${n}=${e}`);let l=e.workdir||i||"/";"/workspace"===l&&(l="/");const m=[];if(Array.isArray(e.volumes))for(const n of e.volumes){let e=null,t=null,s="rw";if("string"==typeof n){const r=n.split(":");r.length>=2&&(e=r[0],t=r[1],!r[2]||"ro"!==r[2]&&"rw"!==r[2]||(s=r[2]))}else"object"==typeof n&&null!==n&&(e=n.host||n.source||n.src,t=n.container||n.destination||n.target||n.dst,s=n.mode||(n.readOnly?"ro":"rw"));if(e&&t){e.startsWith("~")&&(e=path.join(os.homedir(),e.slice(1)));const n=path.resolve(process.cwd(),e);try{fs.ensureDirSync(n)}catch(n){}m.push({host:n,container:t.startsWith("/")?t:`/${t}`,mode:"ro"===s?"ro":"rw"})}}const d=await sendCommand({cmd:"run",id:n,name:e.name||n,rootfs:t,exec:s,args:r,workdir:l,env:c,volumes:m},3e4);if(!d.ok)throw new Error(`MicroVM container start failed: ${d.error}`);let p=d.vm_ip&&!d.vm_ip.startsWith("127.")?d.vm_ip:null;if(!p)try{const n=await sendCommand({cmd:"ping"},2e3);n&&n.vm_ip&&!n.vm_ip.startsWith("127.")&&(p=n.vm_ip)}catch(n){}p||(p="192.168.64.2");const u={id:n,name:e.name||n,image:e.image||null,state:"RUNNING",pid:d.pid,cmd:s||(r?r.join(" "):"/bin/sh"),cwd:t,vmIp:p,hostPort:e.hostPort||null,containerPort:e.containerPort||null,volumes:m,environment:a,isolationLevel:"macos-microvm-vz",platform:"linux/arm64",createdAt:(new Date).toISOString(),startedAt:(new Date).toISOString()};return this._saveMeta(n,u),e.hostPort&&this._startPortForward(n,e.hostPort,e.containerPort||e.hostPort,p),console.log(chalk.green(`[TNC MicroVM] Container "${n}" started (PID ${d.pid} in Linux VM)`)),{success:!0,meta:u}}_startPortForward(n,e,t,s="192.168.64.2"){const r=path.join(SANDBOX_DIR,n);fs.ensureDirSync(r);const i=path.join(r,"proxy.pid");if(fs.existsSync(i)){try{const n=parseInt(fs.readFileSync(i,"utf8").trim(),10);n&&process.kill(n,"SIGTERM")}catch(n){}try{fs.removeSync(i)}catch(n){}}try{const{execSync:n}=require("child_process"),s=n(`lsof -i :${e} -sTCP:LISTEN -Fpc 2>/dev/null`,{encoding:"utf8"}).trim();if(s){const r=s.split("\n");let i=null,o="unknown";for(const n of r)n.startsWith("p")&&(i=parseInt(n.slice(1),10)),n.startsWith("c")&&(o=n.slice(1));if(i){let s=!1;try{n(`ps -p ${i} -o args= 2>/dev/null`,{encoding:"utf8"}).includes("port-proxy-daemon.js")&&i!==process.pid&&(s=!0)}catch(n){}if(!s)throw new Error(`Host port ${e} is already in use by process PID ${i} (${o}). Please stop the conflicting service or choose a different port (e.g. -p <otherPort>:${t}).`);try{process.kill(i,"SIGTERM")}catch(n){}}}}catch(n){if(n.message&&n.message.includes("Host port"))throw n}const o=path.join(__dirname,"port-proxy-daemon.js"),a=spawn(process.execPath,[o,n,String(e),String(t),s],{detached:!0,stdio:"ignore"});a.unref(),console.log(chalk.cyan(` [Port Mapping] 0.0.0.0:${e} -> ${s}:${t} (PID ${a.pid})`))}async stop(n){await this.ensureVMRunning();const e=path.join(SANDBOX_DIR,n,"proxy.pid");if(fs.existsSync(e)){try{const n=parseInt(fs.readFileSync(e,"utf8").trim(),10);n&&process.kill(n,"SIGTERM")}catch(n){}try{fs.removeSync(e)}catch(n){}}const t=await sendCommand({cmd:"stop",id:n},15e3),s=this._loadMeta(n);return s&&(s.state="STOPPED",this._saveMeta(n,s)),{success:t.ok,message:`Container "${n}" stopped.`}}async list(){if(!this.isVMRunning())return[];try{const n=await sendCommand({cmd:"ps"},5e3);return(n.ok?n.containers:[]).map(n=>({...this._loadMeta(n.id)||{},id:n.id,pid:n.pid,state:n.running?"RUNNING":"STOPPED",vmIp:n.ip,isolationLevel:"macos-microvm-vz"}))}catch(n){return[]}}async exec(n,e){return await this.ensureVMRunning(),sendCommand({cmd:"exec",id:n,exec:e},3e4)}async commit(n){return await this.ensureVMRunning(),sendCommand({cmd:"commit",id:n},6e4)}async logs(n,e=100){return await this.ensureVMRunning(),sendCommand({cmd:"logs",id:n,tail:e},1e4)}async stats(n){return await this.ensureVMRunning(),sendCommand({cmd:"stats",id:n},5e3)}async top(n){return await this.ensureVMRunning(),sendCommand({cmd:"top",id:n},1e4)}_metaPath(n){return path.join(SANDBOX_DIR,n,"meta.json")}_saveMeta(n,e){const t=path.join(SANDBOX_DIR,n);fs.ensureDirSync(t),fs.writeJsonSync(this._metaPath(n),e,{spaces:2})}_loadMeta(n){try{return fs.readJsonSync(this._metaPath(n))}catch(n){return null}}}let _instance=null;function getMicroVMManager(){return _instance||(_instance=new MicroVMManager),_instance}module.exports={MicroVMManager,getMicroVMManager,sendCommand};
@@ -1,7 +0,0 @@
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"),os=require("os"),fs=require("fs-extra"),{spawnSync}=require("child_process"),chalk=require("chalk"),DATA_DIR=path.join(os.homedir(),".tnc","data"),SANDBOX_DIR=path.join(os.homedir(),".tnc","sandboxes");class NativeServiceManager{constructor(t=null){if(t)this.engine=t;else{const{SandboxEngine:t}=require("./index");this.engine=new t}fs.ensureDirSync(DATA_DIR),fs.ensureDirSync(SANDBOX_DIR)}_findBinary(t){try{const s=spawnSync("which",[t],{stdio:"pipe"});if(0===s.status)return s.stdout.toString().trim()}catch(t){}const s=path.join("/opt/homebrew/bin",t);if(fs.existsSync(s))return s;const e=path.join("/usr/local/bin",t);if(fs.existsSync(e))return e;const o=path.join("/usr/bin",t);return fs.existsSync(o)?o:null}async startPostgres(t={}){const s=t.port||5432,e=path.join(DATA_DIR,"postgres"),o=path.join(SANDBOX_DIR,"postgres"),n=path.join(o,"output.log");fs.ensureDirSync(o);const i=this._findBinary("pg_ctl"),r=this._findBinary("initdb");if(this._findBinary("postgres"),!i||!r){if(t.autoInstall)return console.log(chalk.yellow("\n[Notice] PostgreSQL not found on host. Auto-installing native PostgreSQL via system package manager...")),this.installPostgres(),this.startPostgres({...t,autoInstall:!1});throw new Error("PostgreSQL binaries not found on host.\nInstall natively without Docker:\n Run: thinknsh service install postgres\n Or: brew install postgresql@16 (macOS) / sudo apt install postgresql (Linux)")}if(!fs.existsSync(path.join(e,"PG_VERSION"))){if(console.log(chalk.cyan(`\n[Native:Postgres] Initializing local database cluster in ${e}...`)),fs.ensureDirSync(e),0!==spawnSync(r,["-D",e,"-U","postgres","-A","trust","-E","UTF-8","--no-locale"],{stdio:"inherit"}).status)throw new Error("Failed to initialize PostgreSQL database cluster.");console.log(chalk.green("[OK] PostgreSQL cluster initialized cleanly!"))}try{const t=spawnSync("lsof",["-i",`:${s}`],{stdio:"pipe"});if(0===t.status&&t.stdout.toString().includes("postgres"))return console.log(chalk.green(`\n[OK] Native PostgreSQL is ALREADY ACTIVE on localhost:${s}!`)),console.log(chalk.dim(` Host Endpoint: postgresql://localhost:${s}/postgres`)),console.log(chalk.dim(" RAM Overhead: Native host process (~25MB, 0 MB VM)")),console.log(chalk.dim(" Docker Hub: ZERO bytes downloaded ✓\n")),this._updateServiceMeta("postgres",{state:"RUNNING",port:s,dataDir:e,logFile:n})}catch(t){}if(0===spawnSync(i,["-D",e,"status"],{stdio:"pipe"}).status)return console.log(chalk.green(`[OK] PostgreSQL is already running on port ${s}.`)),this._updateServiceMeta("postgres",{state:"RUNNING",port:s,dataDir:e,logFile:n});if(console.log(chalk.cyan(`[Native:Postgres] Starting native PostgreSQL daemon on port ${s} (0 MB VM)...`)),0!==spawnSync(i,["-D",e,"-l",n,"start","-o",`-p ${s}`],{stdio:"inherit"}).status)throw new Error(`Failed to start native PostgreSQL daemon. Check logs: ${n}`);const a=this._updateServiceMeta("postgres",{state:"RUNNING",port:s,dataDir:e,logFile:n});return console.log(chalk.green(`\n[OK] Native PostgreSQL is ACTIVE on port ${s}!`)),console.log(chalk.dim(` Host Endpoint: postgresql://postgres@localhost:${s}/postgres`)),console.log(chalk.dim(` Data Directory: ${e}`)),console.log(chalk.dim(" RAM Overhead: Native process (~25MB, 0 MB VM)")),console.log(chalk.dim(" Docker Hub: ZERO bytes downloaded ✓\n")),a}stopPostgres(){const t=path.join(DATA_DIR,"postgres"),s=this._findBinary("pg_ctl");if(!s)throw new Error("pg_ctl binary not found.");return console.log(chalk.yellow("Stopping native PostgreSQL daemon...")),spawnSync(s,["-D",t,"stop","-m","fast"],{stdio:"inherit"}),this._updateServiceMeta("postgres",{state:"STOPPED"})}statusPostgres(){const t=path.join(DATA_DIR,"postgres"),s=this._findBinary("pg_ctl");let e=!1,o="";try{const t=spawnSync("lsof",["-i",":5432"],{stdio:"pipe"});0===t.status&&t.stdout.toString().includes("postgres")&&(e=!0,o="Active on localhost:5432 (Native host process)")}catch(t){}if(!e&&s){const n=spawnSync(s,["-D",t,"status"],{stdio:"pipe"});0===n.status&&(e=!0,o=n.stdout.toString().trim())}return{service:"postgres",running:e,port:e?5432:null,dataDir:t,details:o||"Stopped"}}async startRedis(t={}){const s=t.port||6379,e=path.join(DATA_DIR,"redis"),o=path.join(SANDBOX_DIR,"redis"),n=path.join(o,"output.log");fs.ensureDirSync(e),fs.ensureDirSync(o);const i=this._findBinary("redis-server");if(!i)throw new Error("redis-server not found on host.\nInstall natively without Docker:\n macOS: brew install redis\n Linux: sudo apt install redis-server");console.log(chalk.cyan(`[Native:Redis] Starting native Redis on port ${s}...`));const{spawn:r}=require("child_process"),a=fs.openSync(n,"a");r(i,["--port",String(s),"--dir",e,"--daemonize","yes"],{detached:!0,stdio:["ignore",a,a]}).unref();const l=this._updateServiceMeta("redis",{state:"RUNNING",port:s,dataDir:e,logFile:n});return console.log(chalk.green(`[OK] Native Redis is ACTIVE on port ${s}!`)),l}stopRedis(){const t=this._findBinary("redis-cli");return t&&spawnSync(t,["shutdown"],{stdio:"pipe"}),this._updateServiceMeta("redis",{state:"STOPPED"})}installPostgres(){console.log(chalk.cyan("\n[Native:Install] Provisioning native PostgreSQL without Docker..."));const t="darwin"===process.platform,s="linux"===process.platform,e="win32"===process.platform;if(t){const t=this._findBinary("brew");if(!t)throw new Error("Homebrew not found. Please install Homebrew or install PostgreSQL manually.");if(console.log(chalk.dim(" Executing: brew install postgresql@16")),0!==spawnSync(t,["install","postgresql@16"],{stdio:"inherit"}).status)throw new Error("Failed to install PostgreSQL via Homebrew.");try{spawnSync(t,["link","postgresql@16","--force"],{stdio:"pipe"})}catch(t){}return console.log(chalk.green("✓ Native PostgreSQL 16 installed successfully!")),!0}if(s&&this._findBinary("apt-get")){if(console.log(chalk.dim(" Executing: sudo apt-get update && sudo apt-get install -y postgresql")),0!==spawnSync("sudo",["apt-get","install","-y","postgresql"],{stdio:"inherit"}).status)throw new Error("Failed to install PostgreSQL via apt.");return console.log(chalk.green("✓ Native PostgreSQL installed successfully!")),!0}if(e&&this._findBinary("winget"))return console.log(chalk.dim(" Executing: winget install PostgreSQL.PostgreSQL.16")),spawnSync("winget",["install","PostgreSQL.PostgreSQL.16","--silent"],{stdio:"inherit"}),!0;throw new Error("Unsupported platform package manager. Please install PostgreSQL manually.")}installRedis(){console.log(chalk.cyan("\n[Native:Install] Provisioning native Redis without Docker..."));const t="darwin"===process.platform,s="linux"===process.platform;if(t){const t=this._findBinary("brew");if(!t)throw new Error("Homebrew not found.");if(console.log(chalk.dim(" Executing: brew install redis")),0!==spawnSync(t,["install","redis"],{stdio:"inherit"}).status)throw new Error("Failed to install Redis via Homebrew.");return console.log(chalk.green("✓ Native Redis installed successfully!")),!0}if(s&&this._findBinary("apt-get")){if(0!==spawnSync("sudo",["apt-get","install","-y","redis-server"],{stdio:"inherit"}).status)throw new Error("Failed to install Redis via apt.");return console.log(chalk.green("✓ Native Redis installed successfully!")),!0}throw new Error("Unsupported platform package manager for Redis.")}_updateServiceMeta(t,s={}){const e=path.join(SANDBOX_DIR,t,"meta.json");fs.ensureDirSync(path.dirname(e));let o={};if(fs.existsSync(e))try{o=fs.readJsonSync(e)}catch(t){}return o={id:t,name:t,cmd:`native ${t}`,cwd:s.dataDir||o.cwd||process.cwd(),hostPort:s.port||o.hostPort,containerPort:s.port||o.containerPort,state:s.state||"RUNNING",isolationLevel:"native-host-daemon",isNative:!0,updatedAt:(new Date).toISOString()},fs.writeJsonSync(e,o,{spaces:2}),o}}module.exports=NativeServiceManager;
@@ -1,7 +0,0 @@
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{execSync}=require("child_process");class SandboxNetwork{constructor(e={}){this.bridge=e.bridgeName||"br-tnc0",this.subnet=e.subnet||"10.89.0.0/24",this.gatewayIp=e.gatewayIp||"10.89.0.1",this.isLinux="linux"===process.platform}setupBridge(){if(!this.isLinux)return{status:"unsupported_platform",platform:process.platform};try{return execSync(`ip link show ${this.bridge}`,{stdio:"ignore"}),{status:"exists",bridge:this.bridge}}catch{try{return execSync(`ip link add name ${this.bridge} type bridge`),execSync(`ip addr add ${this.gatewayIp}/24 dev ${this.bridge}`),execSync(`ip link set dev ${this.bridge} up`),execSync("sysctl -w net.ipv4.ip_forward=1",{stdio:"ignore"}),execSync(`iptables -t nat -A POSTROUTING -s ${this.subnet} ! -o ${this.bridge} -j MASQUERADE`,{stdio:"ignore"}),{status:"created",bridge:this.bridge}}catch(e){return{status:"error",message:e.message}}}}attachNetwork(e,t,i="10.89.0.2"){if(!this.isLinux)return{status:"skipped",reason:"non-linux"};const s=e.replace(/[^a-zA-Z0-9]/g,"").slice(0,6),r=`vh_${s}`,n=`vg_${s}`;try{execSync(`ip link add ${r} type veth peer name ${n}`),execSync(`ip link set ${r} master ${this.bridge}`),execSync(`ip link set ${r} up`),execSync(`ip link set ${n} netns ${t}`);const e=`nsenter -t ${t} -n`;return execSync(`${e} ip link set dev ${n} name eth0`),execSync(`${e} ip addr add ${i}/24 dev eth0`),execSync(`${e} ip link set dev lo up`),execSync(`${e} ip link set dev eth0 up`),execSync(`${e} ip route add default via ${this.gatewayIp}`),{status:"attached",vethHost:r,guestIp:i,gateway:this.gatewayIp}}catch(e){return{status:"error",message:e.message}}}mapPort(e,t,i){if(!this.isLinux||!e||!i)return!1;try{return execSync(`iptables -t nat -A PREROUTING -p tcp --dport ${e} -j DNAT --to-destination ${t}:${i}`,{stdio:"ignore"}),!0}catch{return!1}}unmapPort(e,t,i){if(!this.isLinux||!e||!i)return!1;try{return execSync(`iptables -t nat -D PREROUTING -p tcp --dport ${e} -j DNAT --to-destination ${t}:${i}`,{stdio:"ignore"}),!0}catch{return!1}}}module.exports=SandboxNetwork;
@@ -1,7 +0,0 @@
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 fs=require("fs-extra"),path=require("path"),os=require("os"),crypto=require("crypto"),{execSync,spawnSync}=require("child_process"),https=require("https"),http=require("http"),chalk=require("chalk");class OCIExporter{constructor(){this.imagesBase=path.join(os.homedir(),".tnc","images")}parsePushTarget(e){return e.startsWith("ecr://")||e.startsWith("aws://")?{type:"ecr",ref:e.replace(/^(ecr|aws):\/\//,"")}:e.startsWith("gcr://")||e.startsWith("gcp://")||e.startsWith("gar://")?{type:"gcr",ref:e.replace(/^(gcr|gcp|gar):\/\//,"")}:e.startsWith("ghcr://")||e.startsWith("ghcr.io/")?{type:"ghcr",ref:e.replace(/^ghcr:\/\//,"ghcr.io/")}:e.startsWith("docker://")||e.includes("docker.io/")?{type:"dockerhub",ref:e.replace(/^docker:\/\//,"")}:{type:"generic",ref:e}}async exportTar(e,t){const s=this._loadManifest(),o=s[e]||s[`${e}:latest`];if(!o)throw new Error(`Image "${e}" not found locally. Run: thinknsh pull ${e}`);console.log(chalk.cyan(`\n[OCI] Exporting "${e}" as OCI tar...`));const r=path.join(os.tmpdir(),`thinknsh-oci-export-${Date.now()}`);fs.ensureDirSync(r);try{fs.writeJsonSync(path.join(r,"oci-layout"),{imageLayoutVersion:"1.0.0"});const s=path.join(r,"blobs","sha256");fs.ensureDirSync(s);const n={architecture:o.platform?.split("/")[1]||"amd64",os:o.platform?.split("/")[0]||"linux",rootfs:{type:"layers",diff_ids:[]},config:{Entrypoint:o.entrypoint||[],Cmd:o.cmd||["sh"],Env:o.env||[],WorkingDir:o.workingDir||"/",Labels:o.labels||{},ExposedPorts:o.exposedPorts||{}},history:[{created:o.pulledAt||(new Date).toISOString(),created_by:`thinknsh export ${e}`}]},i=path.join(r,"layer.tar"),a=o.rootfsDir;a&&fs.existsSync(a)?(process.stdout.write(chalk.dim(" Packing rootfs... ")),spawnSync("tar",["-cf",i,"-C",a,"."],{stdio:"pipe"}),console.log(chalk.green("✓"))):spawnSync("tar",["-cf",i,"--files-from","/dev/null"],{stdio:"pipe"});const c=fs.readFileSync(i),p=crypto.createHash("sha256").update(c).digest("hex");n.rootfs.diff_ids.push(`sha256:${p}`);const h=`${i}.gz`;process.stdout.write(chalk.dim(" Compressing layer... ")),spawnSync("gzip",["-f",i],{stdio:"pipe"}),console.log(chalk.green("✓"));const l=fs.readFileSync(h),g=crypto.createHash("sha256").update(l).digest("hex"),d=l.length;fs.copyFileSync(h,path.join(s,g)),fs.removeSync(h);const u=Buffer.from(JSON.stringify(n)),y=crypto.createHash("sha256").update(u).digest("hex");fs.writeFileSync(path.join(s,y),u);const f={schemaVersion:2,mediaType:"application/vnd.oci.image.manifest.v1+json",config:{mediaType:"application/vnd.oci.image.config.v1+json",digest:`sha256:${y}`,size:u.length},layers:[{mediaType:"application/vnd.oci.image.layer.v1.tar+gzip",digest:`sha256:${g}`,size:d}],annotations:{"org.opencontainers.image.title":e,"org.opencontainers.image.created":(new Date).toISOString(),"org.opencontainers.image.vendor":"ThinkNCollab","io.thinkncollab.shell.version":"2.0.0"}},m=Buffer.from(JSON.stringify(f,null,2)),k=crypto.createHash("sha256").update(m).digest("hex");fs.writeFileSync(path.join(s,k),m);const w={schemaVersion:2,mediaType:"application/vnd.oci.image.index.v1+json",manifests:[{mediaType:"application/vnd.oci.image.manifest.v1+json",digest:`sha256:${k}`,size:m.length,annotations:{"org.opencontainers.image.ref.name":e}}]};fs.writeJsonSync(path.join(r,"index.json"),w,{spaces:2});const S=e.replace(/[^a-zA-Z0-9_.-]/g,"-"),$=t?path.resolve(t):path.join(process.cwd(),`${S}-oci.tar`);process.stdout.write(chalk.dim(" Writing OCI tar... ")),spawnSync("tar",["-cf",$,"-C",r,"."],{stdio:"pipe"}),console.log(chalk.green("✓"));const x=(fs.statSync($).size/1024/1024).toFixed(1);return console.log(chalk.green(`\n[OK] OCI tar exported: ${$} (${x}MB)`)),console.log(chalk.dim(" Compatible with: kubectl, skopeo, crane, containerd, AWS ECR, GCP Artifact Registry")),fs.removeSync(r),{success:!0,file:$,size:x,digest:`sha256:${k}`}}catch(e){throw fs.removeSync(r),new Error(`OCI export failed: ${e.message}`)}}async pushToECR(e,t){let s,o;console.log(chalk.cyan(`\n[OCI→ECR] Pushing "${e}" to AWS ECR...`));try{const e=execSync("aws ecr get-login-password --region $(aws configure get region || echo us-east-1)",{encoding:"utf8",stdio:"pipe"}).trim();s=Buffer.from(e).toString("base64")}catch(e){throw new Error(`AWS CLI not configured. Run: aws configure\n${e.message}`)}try{o=`https://${execSync("aws sts get-caller-identity --query Account --output text",{encoding:"utf8",stdio:"pipe"}).trim()}.dkr.ecr.${execSync("aws configure get region",{encoding:"utf8",stdio:"pipe"}).trim()||"us-east-1"}.amazonaws.com`}catch(e){throw new Error(`Could not determine ECR registry URL: ${e.message}`)}return console.log(chalk.dim(` Registry: ${o}/${t}`)),this._pushViaSkopeo(e,`${o}/${t}`,{token:s})}async pushToGCR(e,t){let s;console.log(chalk.cyan(`\n[OCI→GCR] Pushing "${e}" to GCP Artifact Registry...`));try{s=execSync("gcloud auth print-access-token",{encoding:"utf8",stdio:"pipe"}).trim()}catch(e){throw new Error(`gcloud CLI not configured. Run: gcloud auth login\n${e.message}`)}return this._pushViaSkopeo(e,t,{token:s})}async pushToDockerHub(e,t,s={}){return console.log(chalk.cyan(`\n[OCI→DockerHub] Pushing "${e}" to Docker Hub...`)),this._pushViaSkopeo(e,t,s)}async pushToGHCR(e,t,s={}){return console.log(chalk.cyan(`\n[OCI→GHCR] Pushing "${e}" to GitHub Container Registry...`)),this._pushViaSkopeo(e,t,s)}async _pushViaSkopeo(e,t,s={}){const o=this._commandExists("skopeo"),r=this._commandExists("crane");if(o){const o=s.token?`--dest-creds "AWS:${s.token}"`:s.username?`--dest-creds "${s.username}:${s.password}"`:"";try{const s=await this.exportTar(e);return execSync(`skopeo copy oci-archive:"${s.file}" docker://${t} ${o}`,{stdio:"inherit"}),{success:!0,pushed:t}}catch(e){throw new Error(`skopeo push failed: ${e.message}`)}}if(r)try{const s=await this.exportTar(e);return execSync(`crane push "${s.file}" ${t}`,{stdio:"inherit"}),{success:!0,pushed:t}}catch(e){throw new Error(`crane push failed: ${e.message}`)}const n=await this.exportTar(e);return console.log(chalk.yellow("\n[OCI] skopeo/crane not installed. OCI tar exported.")),console.log(chalk.dim(" Install skopeo: brew install skopeo (macOS) / apt install skopeo (Linux)")),console.log(chalk.dim(` Then run: skopeo copy oci-archive:${n.file} docker://${t}`)),{success:!1,tarFile:n.file,manual:!0}}_commandExists(e){try{return execSync(`which ${e}`,{stdio:"pipe"}),!0}catch{return!1}}_loadManifest(){const e=path.join(this.imagesBase,"manifest.json"),t=path.join(this.imagesBase,"images.json");let s={},o={};try{s=fs.readJsonSync(e)}catch(e){}try{o=fs.readJsonSync(t)}catch(e){}for(const[e,t]of Object.entries(o))s[e]||(s[e]={...t,rootfsDir:t.rootfsDir||path.join(this.imagesBase,t.id||"","rootfs"),layers:t.layers||1});return s}}module.exports=OCIExporter;
@@ -1,7 +0,0 @@
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 https=require("https"),http=require("http"),fs=require("fs-extra"),path=require("path"),os=require("os"),crypto=require("crypto"),{execSync,spawnSync}=require("child_process"),chalk=require("chalk"),ImageStore=require("./image-store"),OCI_BASE=path.join(os.homedir(),".tnc","images"),OCI_MANIFEST=path.join(OCI_BASE,"manifest.json"),REGISTRY_MAP={"docker.io":{registry:"https://registry-1.docker.io",auth:"https://auth.docker.io/token",service:"registry.docker.io"},"ghcr.io":{registry:"https://ghcr.io",auth:"https://ghcr.io/token",service:"ghcr.io"},"gcr.io":{registry:"https://gcr.io",auth:null,service:"gcr.io"},"public.ecr.aws":{registry:"https://public.ecr.aws",auth:"https://public.ecr.aws/token",service:"public.ecr.aws"}};class OCIPuller{constructor(){fs.ensureDirSync(OCI_BASE),this.store=new ImageStore,this.manifest=this._loadManifest(),this.hostArch=this._detectArch(),this.hostOS=this._detectOS()}_loadManifest(){try{return fs.readJsonSync(OCI_MANIFEST)}catch{return{}}}_saveManifest(){fs.writeJsonSync(OCI_MANIFEST,this.manifest,{spaces:2})}_detectArch(){const t=os.arch();return"arm64"===t||"aarch64"===t?"arm64":"amd64"}_detectOS(){return process.platform,"linux"}parseImageRef(t){let e="docker.io",s=t,r="latest";const o=t.split("/");if(o.length>=2&&(o[0].includes(".")||o[0].includes(":")||"localhost"===o[0])?(e=o[0],s=o.slice(1).join("/")):1===o.length?s=`library/${t}`:o[0].includes(".")||2!==o.length||(s=t),s.includes(":")){const t=s.lastIndexOf(":");r=s.slice(t+1),s=s.slice(0,t)}s=s.replace(/[^a-zA-Z0-9_\-\.\/]/g,"").replace(/\.\.+/g,""),r=r.replace(/[^a-zA-Z0-9_\-\.]/g,"").replace(/\.\.+/g,""),r||(r="latest");const i=Object.keys(REGISTRY_MAP).find(t=>e.includes(t))||e,n=REGISTRY_MAP[i]||{registry:e.startsWith("http")?e:`https://${e}`,auth:null,service:e};return{registry:e,registryKey:i,registryInfo:n,repo:s,tag:r,fullRef:`${s}:${r}`}}_fetch(t,e={}){return new Promise((s,r)=>{const o=new URL(t),i="https:"===o.protocol?https:http,n={hostname:o.hostname,port:o.port||("https:"===o.protocol?443:80),path:o.pathname+o.search,method:"GET",headers:{"User-Agent":"thinknsh/2.1 (ThinkNCollab-Shell)",...e}},a=i.request(n,t=>{if([301,302,307,308].includes(t.statusCode)&&t.headers.location){const i=t.headers.location,n=new URL(i).hostname,a={...e};return n!==o.hostname&&delete a.Authorization,this._fetch(i,a).then(s).catch(r)}const i=[];t.on("data",t=>i.push(t)),t.on("end",()=>s({status:t.statusCode,headers:t.headers,body:Buffer.concat(i)}))});a.on("error",r),a.setTimeout(3e4,()=>{a.destroy(),r(new Error("Request timeout"))}),a.end()})}_download(t,e,s={},r=""){return new Promise((o,i)=>{const n=new URL(t),a="https:"===n.protocol?https:http,c={hostname:n.hostname,port:n.port||("https:"===n.protocol?443:80),path:n.pathname+n.search,method:"GET",headers:{"User-Agent":"thinknsh/2.1 (ThinkNCollab-Shell)",...s}},l=a.request(c,t=>{if([301,302,307,308].includes(t.statusCode)&&t.headers.location){const a=t.headers.location,c=new URL(a).hostname,l={...s};return c!==n.hostname&&delete l.Authorization,this._download(a,e,l,r).then(o).catch(i)}const a=parseInt(t.headers["content-length"]||"0",10);let c=0;const l=fs.createWriteStream(e);t.on("data",t=>{if(c+=t.length,l.write(t),a>0){const t=Math.round(c/a*100),e="█".repeat(Math.floor(t/5))+"░".repeat(20-Math.floor(t/5));process.stdout.write(`\r ${chalk.dim(r)} [${chalk.cyan(e)}] ${t}% (${(c/1024/1024).toFixed(1)}MB)`)}}),t.on("end",()=>{l.end(),a>0&&process.stdout.write("\n"),o({size:c})}),t.on("error",i),l.on("error",i)});l.on("error",i),l.setTimeout(3e5,()=>{l.destroy(),i(new Error("Download timeout"))}),l.end()})}async _getToken(t,e){if(!t.auth)return null;try{const s=`${t.auth}?service=${t.service}&scope=repository:${e}:pull`,r=await this._fetch(s);if(200===r.status){const t=JSON.parse(r.body.toString());return t.token||t.access_token||null}}catch(t){}return null}async _fetchManifest(t,e,s,r){const o={Accept:["application/vnd.oci.image.manifest.v1+json","application/vnd.docker.distribution.manifest.v2+json","application/vnd.docker.distribution.manifest.list.v2+json","application/vnd.oci.image.index.v1+json"].join(", ")};r&&(o.Authorization=`Bearer ${r}`);const i=`${t.registry}/v2/${e}/manifests/${s}`,n=await this._fetch(i,o);if(401===n.status)throw new Error(`Auth failed for ${e}:${s}. Image may be private or requires login.`);if(404===n.status)throw new Error(`Image ${e}:${s} not found on registry.`);if(200!==n.status)throw new Error(`Registry returned HTTP ${n.status} for ${e}:${s}`);const a=JSON.parse(n.body.toString()),c=a.mediaType||n.headers["content-type"]||"";return c.includes("manifest.list")||c.includes("image.index")||a.manifests?this._selectPlatformManifest(a.manifests,t,e,r):a}async _selectPlatformManifest(t,e,s,r){const o=this.hostOS,i=this.hostArch;let n=t.find(t=>t.platform?.os===o&&t.platform?.architecture===i);if(n||(n=t.find(t=>t.platform?.os===o&&"amd64"===t.platform?.architecture)),n||(n=t[0]),!n)throw new Error("No compatible platform found in multi-arch manifest.");const a=n.platform?.architecture||"unknown",c=n.platform?.os||"unknown";return console.log(chalk.dim(` Platform: ${c}/${a} (host: ${this.hostOS}/${this.hostArch})`)),this._fetchManifest(e,s,n.digest,r)}async pull(t,e={}){const{registryInfo:s,repo:r,tag:o,fullRef:i}=this.parseImageRef(t),n=`${r}:${o}`;try{const t=(new(require("./policy-engine"))).evaluateImage({registry:s.registry,repo:r,tag:o,fullRef:i});if(!t.allowed)throw new Error(`[Security Policy] Image pull denied: ${t.reason}`)}catch(t){if(t.message.includes("[Security Policy]"))throw t}if(console.log(chalk.cyan(`\nPulling ${chalk.bold(n)} from ${s.registry}...`)),this.manifest[n]&&!e.force){const t=this.manifest[n];return console.log(chalk.green(`[OK] Already exists: ${n} (pulled ${new Date(t.pulledAt).toLocaleDateString()})`)),console.log(chalk.dim(" Use --force to re-pull")),{cached:!0,meta:t}}process.stdout.write(chalk.dim(" Authenticating... "));const a=await this._getToken(s,r);console.log(chalk.green("✓")),process.stdout.write(chalk.dim(" Fetching manifest... "));const c=await this._fetchManifest(s,r,o,a);console.log(chalk.green("✓"));const l=`${r.replace(/\//g,"-")}-${o}-${Date.now().toString(36)}`,h=path.join(OCI_BASE,r.replace(/\//g,"-"),o),f=path.join(h,"rootfs");fs.ensureDirSync(f);const d=c.layers||[],p=c.config,g={};a&&(g.Authorization=`Bearer ${a}`),console.log(chalk.dim(` Layers: ${d.length} | Config: ${p?.digest?.slice(0,16)}...`));for(let t=0;t<d.length;t++){const e=d[t],o=e.digest,i=o.slice(7,19),n=((e.size||0)/1024/1024).toFixed(1),a=(path.join(h,`layer-${t}.tar.gz`),o.replace(/^sha256:/,"")),c=this.store.getBlobPath(a);if(this.store.hasBlob(a))console.log(chalk.dim(` Layer ${t+1}/${d.length}: ${i} (${n}MB) — CAS cached ✓`));else{const l=`${s.registry}/v2/${r}/blobs/${o}`;await this._download(l,c,g,`Layer ${t+1}/${d.length}: ${i} (${n}MB)`),this.store.putBlob(a,c,e.size||0,e.mediaType)}let l=!1;const p=path.join(os.homedir(),".tnc","vmctl.sock");if(fs.existsSync(p))try{const{sendCommand:t}=require("./microvm-manager"),s=await t({cmd:"extract",blob:c,dest:f,mediaType:e.mediaType||""},3e4);s&&s.ok&&(l=!0)}catch(t){}if(!l)try{const t=(e.mediaType||"").includes("zstd")?["--use-compress-program=zstd","-xf",c,"-C",f]:["-xzf",c,"-C",f],s=spawnSync("tar",t,{stdio:"pipe"});if(0!==s.status&&s.stderr){const t=s.stderr.toString();t.includes("Ignoring unknown extended header keyword")||t.includes("Cannot change ownership")||console.warn(chalk.dim(` [Extract] host tar notice: ${t.trim().split("\n")[0]}`))}}catch(t){console.warn(chalk.yellow(` [Extract] host tar error: ${t.message}`))}try{this._processWhiteouts(f)}catch(t){}}let u={};if(p?.digest)try{const t=await this._fetch(`${s.registry}/v2/${r}/blobs/${p.digest}`,g);u=JSON.parse(t.body.toString())}catch(t){}const m={id:l,repo:r,tag:o,imageKey:n,imageDir:h,rootfsDir:f,registry:s.registry,platform:`${this.hostOS}/${this.hostArch}`,layers:d.length,entrypoint:u.config?.Entrypoint||[],cmd:u.config?.Cmd||[],env:u.config?.Env||[],exposedPorts:u.config?.ExposedPorts||{},workingDir:u.config?.WorkingDir||"/",labels:u.config?.Labels||{},size:d.reduce((t,e)=>t+(e.size||0),0),pulledAt:(new Date).toISOString()};this.manifest[n]=m,this._saveManifest(),this.store.registerImage({repo:r,tag:o,digest:p?.digest||null,platform:m.platform,sizeBytes:m.size,layersCount:d.length,rootfsDir:f,config:u,blobDigests:d.map(t=>t.digest)}),fs.writeJsonSync(path.join(h,"config.json"),u,{spaces:2}),fs.writeJsonSync(path.join(h,"meta.json"),m,{spaces:2});const y=(m.size/1024/1024).toFixed(1);return console.log(chalk.green(`\n[OK] Pulled ${chalk.bold(n)} (${y}MB, ${d.length} layers)`)),console.log(chalk.dim(` Stored at: ${h}`)),(m.entrypoint.length||m.cmd.length)&&console.log(chalk.dim(` Entrypoint: ${[...m.entrypoint,...m.cmd].join(" ")}`)),{cached:!1,meta:m}}list(){const t=new Map;try{const e=this.store.listImages();for(const s of e)t.set(s.fullRef||`${s.repo}:${s.tag}`,s)}catch(t){}for(const[e,s]of Object.entries(this.manifest||{})){if(!s||!s.id)continue;let e=s.repo,r=s.tag;e||(s.tag&&s.tag.includes(":")?[e,r]=s.tag.split(":"):(e=s.project||s.tag||"local",r="latest"));const o=`${e}:${r}`;t.has(o)||t.set(o,{id:s.id,repo:e,tag:r,fullRef:o,platform:s.platform||"linux/arm64",layers:s.layers||1,sizeMB:s.sizeMB||((s.size||0)/1024/1024).toFixed(1),pulledAt:s.pulledAt||s.createdAt,imageDir:s.imageDir,rootfsDir:s.rootfsDir})}return Array.from(t.values())}tag(t,e){return this.store.tagImage(t,e)}prune(){return this.store.pruneBlobs()}remove(t){try{this.store.removeImage(t)}catch(t){}const{repo:e,tag:s}=this.parseImageRef(t),r=`${e}:${s}`,o=this.manifest[r]||this.manifest[t];if(o){if(o.imageDir&&fs.existsSync(o.imageDir))try{fs.removeSync(o.imageDir)}catch(t){}delete this.manifest[r],delete this.manifest[t],this._saveManifest()}return{success:!0}}inspect(t){const e=this.store.getImage(t);if(e)return e;const{repo:s,tag:r}=this.parseImageRef(t),o=`${s}:${r}`;if(this.manifest[o])return this.manifest[o];if(this.manifest[t])return this.manifest[t];for(const e of Object.values(this.manifest))if(e&&(e.id===t||e.tag===t||e.fullRef===t))return e;return null}_processWhiteouts(t){if(fs.existsSync(t))try{const e=fs.readdirSync(t,{withFileTypes:!0});for(const s of e){const e=path.join(t,s.name);if(s.isDirectory())this._processWhiteouts(e);else if(".wh..wh..opq"===s.name)try{fs.unlinkSync(e)}catch(t){}else if(s.name.startsWith(".wh.")){const r=s.name.slice(4),o=path.join(t,r);try{fs.existsSync(o)&&fs.removeSync(o),fs.unlinkSync(e)}catch(t){}}}}catch(t){}}}module.exports=OCIPuller;
@@ -1,7 +0,0 @@
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"),os=require("os"),DEFAULT_CAPS=["CAP_CHOWN","CAP_DAC_OVERRIDE","CAP_FSETID","CAP_FOWNER","CAP_MKNOD","CAP_NET_RAW","CAP_SETGID","CAP_SETUID","CAP_SETFCAP","CAP_SETPCAP","CAP_NET_BIND_SERVICE","CAP_SYS_CHROOT","CAP_KILL","CAP_AUDIT_WRITE"];class OCIRuntimeSpec{static generate({rootfsPath:e,cmd:t=["/bin/sh"],env:s=[],workingDir:o="/",memoryLimitBytes:i=2147483648,cpuQuota:n=0,pidsLimit:r=1e3,mounts:c=[],isRootless:p=!0,terminal:a=!0}){const d=["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","TERM=xterm-256color",...s],m=c.map(e=>({destination:e.destination,type:e.type||"none",source:e.source,options:e.options||["bind","rw"]})),l={ociVersion:"1.0.2",process:{terminal:a,user:{uid:0,gid:0},args:Array.isArray(t)?t:[t],env:d,cwd:o||"/",capabilities:{bounding:[...DEFAULT_CAPS],effective:[...DEFAULT_CAPS],inheritable:[...DEFAULT_CAPS],permitted:[...DEFAULT_CAPS],ambient:[]},rlimits:[{type:"RLIMIT_NOFILE",hard:65536,soft:65536}],noNewPrivileges:!0},root:{path:e,readonly:!1},hostname:"tnc-sandbox",mounts:[{destination:"/proc",type:"proc",source:"proc",options:["nosuid","noexec","nodev"]},{destination:"/dev",type:"tmpfs",source:"tmpfs",options:["nosuid","strictatime","mode=755","size=65536k"]},{destination:"/dev/pts",type:"devpts",source:"devpts",options:["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620"]},{destination:"/dev/shm",type:"tmpfs",source:"shm",options:["nosuid","noexec","nodev","mode=1777","size=65536k"]},{destination:"/sys",type:"sysfs",source:"sysfs",options:["nosuid","noexec","nodev","ro"]},...m],linux:{resources:{memory:{limit:i,swap:i},pids:{limit:r}},namespaces:[{type:"pid"},{type:"ipc"},{type:"uts"},{type:"mount"}],seccomp:{defaultAction:"SCMP_ACT_ERRNO",architectures:["arm64"===process.arch?"SCMP_ARCH_AARCH64":"SCMP_ARCH_X86_64"],syscalls:[{names:["read","write","open","openat","close","stat","fstat","lstat","poll","lseek","mmap","mprotect","munmap","brk","rt_sigaction","rt_sigprocmask","ioctl","access","pipe","select","sched_yield","nanosleep","getpid","socket","connect","accept","sendto","recvfrom","bind","listen","getsockname","getpeername","clone","fork","vfork","execve","exit","wait4","kill","uname","fcntl","flock","fsync","getdents","getdents64","getcwd","chdir","mkdir","rmdir","unlink","unlinkat","rename","renameat","readlink","readlinkat","chmod","fchmod","chown","fchown","gettimeofday","clock_gettime","futex","epoll_create","epoll_create1","epoll_ctl","epoll_wait","epoll_pwait"],action:"SCMP_ACT_ALLOW"}]}}};return n>0&&(l.linux.resources.cpu={quota:n,period:1e5}),p&&(l.linux.namespaces.push({type:"user"}),l.linux.uidMappings=[{hostID:process.getuid?process.getuid():1e3,containerID:0,size:1}],l.linux.gidMappings=[{hostID:process.getgid?process.getgid():1e3,containerID:0,size:1}]),l}}module.exports=OCIRuntimeSpec;
@@ -1,7 +0,0 @@
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 net=require("net"),path=require("path"),fs=require("fs-extra"),os=require("os"),sandboxId=process.argv[2],hostPort=parseInt(process.argv[3],10),containerPort=parseInt(process.argv[4],10),targetIp=process.argv[5]||"192.168.64.2";(!sandboxId||isNaN(hostPort)||isNaN(containerPort))&&(console.error("Usage: node port-proxy-daemon.js <sandboxId> <hostPort> <containerPort> [targetIp]"),process.exit(1));const sandboxDir=path.join(os.homedir(),".tnc","sandboxes",sandboxId);fs.ensureDirSync(sandboxDir);const pidFile=path.join(sandboxDir,"proxy.pid");fs.writeFileSync(pidFile,String(process.pid),"utf8");const server=net.createServer(e=>{const r=net.createConnection({host:targetIp,port:containerPort});e.pipe(r),r.pipe(e),r.on("error",()=>e.destroy()),e.on("error",()=>r.destroy()),r.on("close",()=>e.destroy()),e.on("close",()=>r.destroy())});let listenAddress="127.0.0.1";try{listenAddress=require("../../dock/config").effective().publishAddress||listenAddress}catch(e){}function cleanup(){try{fs.existsSync(pidFile)&&fs.removeSync(pidFile)}catch(e){}try{server.close()}catch(e){}process.exit(0)}server.listen(hostPort,listenAddress,()=>{}),server.on("error",e=>{console.error(`[Proxy] Error binding port ${hostPort}: ${e.message}`),cleanup()}),process.on("SIGINT",cleanup),process.on("SIGTERM",cleanup),process.on("SIGHUP",cleanup),process.on("exit",()=>{try{fs.existsSync(pidFile)&&fs.removeSync(pidFile)}catch(e){}});
@@ -1,7 +0,0 @@
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"),os=require("os"),fs=require("fs-extra"),http=require("http"),https=require("https"),{spawnSync}=require("child_process"),crypto=require("crypto"),chalk=require("chalk"),e2eeVault=require("../crypto/e2ee-vault"),ROOMS_BASE=path.join(os.homedir(),".tnc","rooms"),DATA_DIR=path.join(os.homedir(),".tnc","data");global._tncP2pServers||(global._tncP2pServers=new Map);class RoomSyncManager{constructor(){fs.ensureDirSync(ROOMS_BASE)}getRoomDir(e){const t=e.replace(/[^a-zA-Z0-9_-]/g,"-"),o=path.join(ROOMS_BASE,t);return fs.ensureDirSync(o),o}_findBinary(e){try{const t=spawnSync("which",[e],{stdio:"pipe"});if(0===t.status)return t.stdout.toString().trim()}catch(e){}for(const t of["/opt/homebrew/bin","/usr/local/bin","/usr/bin"]){const o=path.join(t,e);if(fs.existsSync(o))return o}return e}_validateAndSanitizeExtracted(e,t){const o=fs.readdirSync(e,{withFileTypes:!0});for(const n of o){const o=path.join(e,n.name);if(n.isSymbolicLink())try{const e=fs.realpathSync(o),s=path.relative(t,e);if(s.startsWith("..")||path.isAbsolute(s)&&!e.startsWith(t))throw new Error(`Path traversal / symlink escape detected: "${n.name}" targets "${e}" outside snapshot root`)}catch(e){if(e.message.includes("Path traversal"))throw e;try{fs.removeSync(o)}catch(e){}continue}try{const e=fs.lstatSync(o);e.isSymbolicLink()||(3072&e.mode&&fs.chmodSync(o,-3073&e.mode),n.isDirectory()&&this._validateAndSanitizeExtracted(o,t))}catch(e){}}}_getIgnoreRules(e){const t=new Set(["node_modules",".git",".tnc",".DS_Store","logs","scratch","tmp","coverage",".next",".nuxt",".turbo",".cache"]);for(const o of[".ignoretnc",".gitignore"]){const n=path.join(e,o);if(fs.existsSync(n))try{const e=fs.readFileSync(n,"utf8").split(/\r?\n/);for(let o of e)o=o.trim(),o&&!o.startsWith("#")&&(o=o.replace(/\/+$/,""),o&&t.add(o))}catch(e){}}return t}_isBinaryFile(e){try{const t=fs.openSync(e,"r"),o=Buffer.alloc(4096),n=fs.readSync(t,o,0,4096,0);fs.closeSync(t);for(let e=0;e<n;e++)if(0===o[e])return!0;return!1}catch(e){return!1}}_mergePackageJson(e,t){try{const o=fs.readJsonSync(e),n=fs.readJsonSync(t);if(n.dependencies){o.dependencies=o.dependencies||{};for(const[e,t]of Object.entries(n.dependencies))o.dependencies[e]||(o.dependencies[e]=t)}if(n.devDependencies){o.devDependencies=o.devDependencies||{};for(const[e,t]of Object.entries(n.devDependencies))o.devDependencies[e]||(o.devDependencies[e]=t)}if(n.scripts){o.scripts=o.scripts||{};for(const[e,t]of Object.entries(n.scripts))o.scripts[e]||(o.scripts[e]=t)}return fs.writeJsonSync(e,o,{spaces:2}),!0}catch(e){return!1}}_mergeEnvFile(e,t,o){try{const n=fs.readFileSync(e,"utf8"),s=fs.readFileSync(t,"utf8"),a=new Set;for(const e of n.split(/\r?\n/)){const t=e.trim();if(t&&!t.startsWith("#")){const e=t.indexOf("=");e>0&&a.add(t.substring(0,e).trim())}}const r=[];for(const e of s.split(/\r?\n/)){const t=e.trim();if(t&&!t.startsWith("#")){const e=t.indexOf("=");if(e>0){const o=t.substring(0,e).trim();a.has(o)||(r.push(t),a.add(o))}}}if(r.length>0){const t=`\n# ── Synced from ${o||"teammate"} (${(new Date).toLocaleDateString()}) ──\n`;return fs.appendFileSync(e,t+r.join("\n")+"\n","utf8"),{addedCount:r.length,keys:r.map(e=>e.split("=")[0])}}return{addedCount:0,keys:[]}}catch(e){return{addedCount:0,keys:[]}}}_mergeWorkspaceFiles(e,t,o={}){const n={added:[],cleanMerged:[],conflicts:[],unchanged:[],updated:[]},s=o.roomId,a=o.author||"teammate",r=o.localUser||"Local",c=o.overwrite||!1,i=o.baseDir&&fs.existsSync(o.baseDir)?o.baseDir:null,l=`backup-${Date.now().toString(36)}`,d=path.join(os.homedir(),".tnc","backups",`${s||"room"}-${l}`);let h=!1;const p=(t,o=[])=>{const n=fs.readdirSync(t,{withFileTypes:!0});for(const s of n){const n=path.join(t,s.name),a=path.relative(e,n),r=a.split(path.sep);r.includes(".git")||r.includes(".tnc")||r.includes("node_modules")||(s.isDirectory()?p(n,o):s.isFile()&&o.push({full:n,rel:a}))}return o},y=p(e),f=path.join(os.tmpdir(),`tnc_empty_base_${Date.now()}`);fs.writeFileSync(f,"");try{for(const{full:e,rel:o}of y){const s=path.join(t,o);if(!fs.existsSync(s)){fs.ensureDirSync(path.dirname(s)),fs.copyFileSync(e,s),n.added.push(o);continue}const l=fs.readFileSync(e),p=fs.readFileSync(s);if(l.equals(p)){n.unchanged.push(o);continue}try{const e=path.join(d,o);fs.ensureDirSync(path.dirname(e)),fs.copyFileSync(s,e),h=!0}catch(e){}if(c){fs.copyFileSync(e,s),n.updated.push(o);continue}if("package.json"===o&&this._mergePackageJson(s,e)){n.cleanMerged.push(o);continue}if(this._isBinaryFile(e)||this._isBinaryFile(s)){const s=path.join(t,`${o}.incoming`);fs.copyFileSync(e,s),n.updated.push(`${o} (saved as .incoming)`);continue}let y=f;if(i){const e=path.join(i,o);fs.existsSync(e)&&(y=e)}0===spawnSync("git",["merge-file","-L",`Local (${r})`,"-L","Base","-L",`Incoming (${a})`,s,y,e],{stdio:"pipe"}).status?n.cleanMerged.push(o):n.conflicts.push(o)}}finally{try{fs.unlinkSync(f)}catch(e){}}if(h){n.backupDir=d;try{const e=this.getRoomDir(s);fs.writeJsonSync(path.join(e,"last_backup.json"),{backupDir:d,createdAt:(new Date).toISOString(),roomId:s})}catch(e){}}return n}rollbackLastMerge(e,t){const o=this.getRoomDir(e),n=path.join(o,"last_backup.json");if(!fs.existsSync(n))throw new Error("No previous merge backup found for this room.");const{backupDir:s}=fs.readJsonSync(n);if(!s||!fs.existsSync(s))throw new Error(`Backup directory no longer exists: ${s}`);return console.log(chalk.cyan(`\n[RoomSync] Rolling back to pre-merge snapshot: ${s}...`)),fs.copySync(s,t,{overwrite:!0}),console.log(chalk.green("[OK] Workspace successfully rolled back to pre-merge state!\n")),{success:!0}}async _captureMongoSnapshot(e,t){let o=process.env.MONGODB_URI||process.env.MONGO_URI;if(!o){const e=path.join(t,".env");if(fs.existsSync(e))try{const t=fs.readFileSync(e,"utf8").match(/^(?:MONGODB_URI|MONGO_URI)\s*=\s*["']?([^"'\r\n]+)["']?/m);t&&t[1]&&(o=t[1].trim())}catch(e){}}if(!o)return null;let n=null;try{const e=path.join(t,"node_modules","mongodb");fs.existsSync(e)&&(n=require(e).MongoClient)}catch(e){}if(!n)try{n=require("mongodb").MongoClient}catch(e){}if(!n)return null;console.log(chalk.dim(" Capturing MongoDB database snapshot (Atlas / Local)..."));let s=null;try{s=new n(o,{serverSelectionTimeoutMS:5e3,connectTimeoutMS:5e3}),await s.connect();const t=s.db(),a=new Set(["sessions","pagepathhits","pageviews","adminauditlogs","webhooklogs"]),r=(await t.listCollections().toArray()).filter(e=>!a.has(e.name)),c=path.join(e,"data","mongodb");fs.ensureDirSync(c);let i=0,l=0;const d=10;for(let e=0;e<r.length;e+=d){const o=r.slice(e,e+d);await Promise.all(o.map(async e=>{try{const o=await t.collection(e.name).find({}).limit(5e3).toArray();o.length>0&&(fs.writeJsonSync(path.join(c,`${e.name}.json`),o),i+=o.length,l++)}catch(e){}}))}if(l>0)return console.log(chalk.green(` ✓ Captured MongoDB snapshot (${l} collections, ${i} documents)!`)),{collections:l,documents:i}}catch(e){console.log(chalk.yellow(` [Notice] Could not capture MongoDB snapshot: ${e.message}`))}finally{if(s)try{await s.close()}catch(e){}}return null}async _restoreMongoSnapshot(e,t){let o=process.env.MONGODB_URI||process.env.MONGO_URI;if(!o){const e=path.join(t,".env");if(fs.existsSync(e))try{const t=fs.readFileSync(e,"utf8").match(/^(?:MONGODB_URI|MONGO_URI)\s*=\s*["']?([^"'\r\n]+)["']?/m);t&&t[1]&&(o=t[1].trim())}catch(e){}}const n=o||"mongodb://127.0.0.1:27017/thinkncollab_dev";let s=null;try{const e=path.join(t,"node_modules","mongodb");fs.existsSync(e)&&(s=require(e).MongoClient)}catch(e){}if(!s)try{s=require("mongodb").MongoClient}catch(e){}const a=fs.readdirSync(e).filter(e=>e.endsWith(".json"));if(0===a.length)return;let r=!1;if(s){let t=null;try{t=new s(n,{serverSelectionTimeoutMS:3e3,connectTimeoutMS:3e3}),await t.connect();const o=t.db();let c=0;for(const t of a){const n=path.basename(t,".json"),s=fs.readJsonSync(path.join(e,t));if(Array.isArray(s)&&s.length>0)try{await o.collection(n).deleteMany({}),await o.collection(n).insertMany(s),c+=s.length}catch(e){}}console.log(chalk.green(` ✓ Restored MongoDB database (${a.length} collections, ${c} documents)!`)),r=!0}catch(e){}finally{if(t)try{await t.close()}catch(e){}}}if(!r){const o=path.join(t,".tnc-mongo-dump");fs.copySync(e,o,{overwrite:!0}),console.log(chalk.cyan(` ✓ Saved MongoDB snapshot to .tnc-mongo-dump/ (${a.length} collections ready for import)`))}}async _captureRedisSnapshot(e,t){const o=path.join(e,"data","redis"),n=path.join(DATA_DIR,"redis"),s=path.join(n,"dump.rdb");if(fs.existsSync(s)||fs.existsSync(n)){try{const e=this._findBinary("redis-cli");spawnSync(e,["bgsave"],{stdio:"pipe",timeout:5e3})}catch(e){}if(fs.existsSync(s))return console.log(chalk.dim(" Capturing native Redis database snapshot (dump.rdb)...")),fs.ensureDirSync(o),fs.copyFileSync(s,path.join(o,"dump.rdb")),!0}let a=process.env.REDIS_URL;if(!a){const e=path.join(t,".env");if(fs.existsSync(e))try{const t=fs.readFileSync(e,"utf8").match(/^(?:REDIS_URL)\s*=\s*["']?([^"'\r\n]+)["']?/m);t&&t[1]&&(a=t[1].trim())}catch(e){}}if(a){let e=null;try{const o=path.join(t,"node_modules","ioredis");fs.existsSync(o)&&(e=require(o))}catch(e){}if(!e)try{e=require("ioredis")}catch(e){}if(e){let t=null;try{t=new e(a,{connectTimeout:3e3,lazyConnect:!0}),await t.connect();try{await t.bgsave()}catch(e){}const n=await t.keys("*");if(n.length>0){console.log(chalk.dim(` Capturing Redis in-memory cache (${n.length} keys)...`)),fs.ensureDirSync(o);const e=[];for(const o of n.slice(0,1e3))try{const n=await t.pttl(o),s=await t.dump(o);s&&e.push({k:o,ttl:n>0?n:0,v:s})}catch(e){}return fs.writeJsonSync(path.join(o,"keys.json"),e),console.log(chalk.green(` ✓ Captured Redis cache (${e.length} keys)!`)),!0}}catch(e){}finally{if(t)try{t.disconnect()}catch(e){}}}}return!1}_getLocalIp(){const e=os.networkInterfaces();for(const t of Object.keys(e))for(const o of e[t])if("IPv4"===o.family&&!o.internal)return o.address;return"127.0.0.1"}_startEphemeralP2PServer(e,t){return new Promise((o,n)=>{try{const s=crypto.randomBytes(16).toString("hex"),a=this._getLocalIp(),r=http.createServer((o,n)=>{try{const a=new URL(o.url,`http://${o.headers.host||"localhost"}`);if(a.pathname===`/p2p/state/${e}`){const e=o.headers["x-tnc-sync-token"]||a.searchParams.get("token"),r=Buffer.from(String(e||"")),c=Buffer.from(String(s));if(r.length!==c.length||!crypto.timingSafeEqual(r,c))return n.writeHead(403,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Invalid or expired P2P sync token"}));if(!fs.existsSync(t))return n.writeHead(404,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Bundle file no longer exists"}));const i=fs.statSync(t);n.writeHead(200,{"Content-Type":"application/octet-stream","Content-Length":i.size,"X-ThinkNCollab-Encrypted":"true","X-ThinkNCollab-P2P":"true","Cache-Control":"no-cache, no-store"}),console.log(chalk.dim(`\n[RoomSync:P2P] Teammate connected! Streaming AES-256-GCM encrypted state bundle (${(i.size/1024/1024).toFixed(2)} MB)...`));const l=fs.createReadStream(t);return l.pipe(n),void l.on("end",()=>{console.log(chalk.green("[RoomSync:P2P] ✓ Direct peer-to-peer encrypted transfer completed successfully!"))})}n.writeHead(404),n.end()}catch(e){console.error("[RoomSync:P2P] Server error:",e.message),n.writeHead(500),n.end()}}),c=()=>{const t=r.address().port,n={localIp:a,port:t,token:s,server:r};global._tncP2pServers.set(e,n),setTimeout(()=>{try{r.close()}catch(e){}global._tncP2pServers.delete(e)},72e5),o(n)};let i=!1;r.on("error",e=>{i||"EADDRNOTAVAIL"!==e.code&&"EINVAL"!==e.code?n(e):(i=!0,r.listen(0,"0.0.0.0",c))}),r.listen(0,a,c)}catch(e){n(e)}})}_uploadToRelay(e,t,o,n){return new Promise((s,a)=>{try{if(!fs.existsSync(o))return a(new Error(`Bundle file not found: ${o}`));const r=(n?.api?.apiUrl||process.env.THINKNCOLLAB_API_URL||"https://thinkncollab.com").replace(/\/$/,""),c=new URL(`${r}/thinknsh/${e}/cli/relay/${t}`),i="https:"===c.protocol,l=i?https:http,d=n?.api?.getToken?.()||"",h=fs.statSync(o);console.log(chalk.cyan(`\n[RoomSync:Relay] Off-LAN teammate requested state. Streaming AES-256-GCM bundle to in-memory relay (${(h.size/1024/1024).toFixed(2)} MB)...`));const p=l.request({hostname:c.hostname,port:c.port||(i?443:80),path:c.pathname+c.search,method:"POST",headers:{"Content-Type":"application/octet-stream","Content-Length":h.size,"X-ThinkNCollab-Encrypted":"true","x-shell-token":d}},e=>{let t="";e.on("data",e=>t+=e),e.on("end",()=>{if(e.statusCode>=400){let o=t;try{o=JSON.parse(t).error||t}catch(e){}413===e.statusCode?o="State bundle exceeds server relay capacity of 500 MB. Use Direct LAN P2P.":429===e.statusCode&&(o="Room relay limit reached (Max 2 concurrent active relays per room)."),console.error(chalk.red(`[RoomSync:Relay] Upload failed with HTTP ${e.statusCode}: ${o}`)),a(new Error(`Relay upload failed: ${o}`))}else console.log(chalk.green("[RoomSync:Relay] ✓ Encrypted relay stream completed for teammate!")),s()})});p.on("error",e=>{console.error(chalk.red(`[RoomSync:Relay] Network error: ${e.message}`)),a(e)}),fs.createReadStream(o).pipe(p)}catch(e){a(e)}})}async publishState(e,t,o={}){const n=this.getRoomDir(e),s=path.join(n,"states");fs.ensureDirSync(s);const a=t?.api?.getUser?.()||{name:os.userInfo().username,id:"local-user"},r=o.name||`state-${Date.now().toString(36)}`,c=`${r}-${crypto.randomBytes(3).toString("hex")}`,i=path.join(n,"staging",c);fs.ensureDirSync(i),console.log(chalk.cyan(`\n[RoomSync] Preparing encrypted environment state bundle for Room ${chalk.bold(e)}...`));const l=o.cwd||process.cwd(),d={id:c,name:r,roomId:e,author:a.name||a.username||"teammate",authorId:a.id||a._id||"unknown",createdAt:(new Date).toISOString(),description:o.description||"Live development state",services:[],files:[],encrypted:!0,cipher:"aes-256-gcm"};for(const e of["thinkncollab.yml","thinkncollab.yaml","package.json"]){const t=path.join(l,e);fs.existsSync(t)&&(fs.copySync(t,path.join(i,e)),d.files.push(e))}const h=path.join(l,".env");if(fs.existsSync(h)){const e=fs.readFileSync(h,"utf8");fs.writeFileSync(path.join(i,".env.shared"),e,"utf8"),d.files.push(".env.shared")}if(!1!==o.includeCode&&!o.noCode){console.log(chalk.dim(" Packaging project workspace files (skipping node_modules, git, and logs)..."));const e=path.join(i,"workspace");fs.ensureDirSync(e);const t=this._getIgnoreRules(l);let o=0;try{fs.copySync(l,e,{filter:e=>{const n=path.relative(l,e);if(!n)return!0;const s=n.split(path.sep);for(const e of s)if(t.has(e))return!1;const a=path.basename(e);return!(a.endsWith(".tar.gz")||a.endsWith(".tar.gz.enc")||a.endsWith(".log")||".env"===a||".env.room"===a||(o++,0))}}),d.files.push("workspace"),d.hasCode=!0,console.log(chalk.dim(` ✓ Packaged workspace project tree (${o} items)`))}catch(e){console.log(chalk.yellow(` [Notice] Non-fatal error copying workspace files: ${e.message}`))}}const p=path.join(DATA_DIR,"postgres");if(fs.existsSync(p)&&fs.existsSync(path.join(p,"PG_VERSION"))){console.log(chalk.dim(" Issuing native PostgreSQL CHECKPOINT to guarantee clean disk consistency..."));try{const e=this._findBinary("psql");spawnSync(e,["-U","postgres","-c","CHECKPOINT;"],{stdio:"pipe",timeout:5e3})}catch(e){}console.log(chalk.dim(" Capturing native PostgreSQL database snapshot..."));const e=path.join(i,"data","postgres");fs.ensureDirSync(e),"darwin"===process.platform?spawnSync("cp",["-cR",`${p}/.`,`${e}/`],{stdio:"pipe"}):spawnSync("cp",["--reflink=auto","-a",`${p}/.`,`${e}/`],{stdio:"pipe"}),d.services.push("postgres")}try{await this._captureRedisSnapshot(i,l)&&d.services.push("redis")}catch(e){}try{await this._captureMongoSnapshot(i,l)&&d.services.push("mongodb")}catch(e){}fs.writeJsonSync(path.join(i,"manifest.json"),d,{spaces:2});const y=path.join(s,`${c}.tar.gz`);console.log(chalk.dim(` Packaging bundle: ${path.basename(y)}...`)),spawnSync("tar",["-czf",y,"-C",i,"."],{stdio:"pipe"});try{fs.removeSync(i)}catch(e){}const f=path.join(s,`${c}.tar.gz.enc`);console.log(chalk.dim(" Encrypting state bundle with Room AES-256-GCM key...")),await e2eeVault.encryptFile(y,f,e);const u=((fs.statSync(f).size||0)/1024/1024).toFixed(2);d.bundleSizeMB=u,d.bundlePath=f;let m=null;try{m=await this._startEphemeralP2PServer(c,f),d.p2p={localIp:m.localIp,port:m.port,token:m.token},console.log(chalk.dim(` Direct LAN P2P endpoint active: http://${m.localIp}:${m.port}`))}catch(e){console.log(chalk.yellow(` P2P server setup skipped: ${e.message}`))}const g=path.join(n,"index.json"),S=fs.existsSync(g)?fs.readJsonSync(g):[];if(S.unshift(d),fs.writeJsonSync(g,S,{spaces:2}),t?.api?.isAuthenticated?.())try{await t.api._request("POST",`/thinknsh/${e}/cli/states`,{stateId:c,name:r,author:d.author,services:d.services,sizeMB:u,description:d.description,p2p:d.p2p,encrypted:!0,cipher:"aes-256-gcm"}),console.log(chalk.dim(" Registered state metadata with ThinkNCollab room cloud"))}catch(e){console.log(chalk.yellow(` Server state registration note: ${e.message}`))}if(t?.ws?.isConnected?.())try{t.ws.socket.emit("thinknsh:room-state-published",{roomId:e,stateId:c,name:r,author:d.author,sizeMB:u,services:d.services,p2p:d.p2p,encrypted:!0}),t.ws.socket.off("thinknsh:relay-upload-start"),t.ws.socket.on("thinknsh:relay-upload-start",o=>{o?.stateId===c&&this._uploadToRelay(e,o.transferId,f,t).catch(e=>{console.error("[RoomSync:Relay] Upload handler error:",e.message)})})}catch(e){}return console.log(chalk.green(`\n[OK] State published to Room [${e}] successfully!`)),console.log(chalk.dim(` State ID: ${c}`)),console.log(chalk.dim(" Cipher: AES-256-GCM (Zero plaintext over LAN/Relay)")),console.log(chalk.dim(` Bundle Size: ${u} MB (Zero Docker Hub needed)`)),console.log(chalk.dim(` Services: ${d.services.join(", ")||"Node runtime"}`)),console.log(chalk.cyan("\nTeammates in this room can pull this state instantly:")),console.log(chalk.bold(` thinknsh room pull ${c}\n`)),d}_downloadStream(e,t,o={}){return new Promise((n,s)=>{try{const a=new URL(e),r="https:"===a.protocol,c=r?https:http,i={hostname:a.hostname,port:a.port||(r?443:80),path:a.pathname+a.search,method:"GET",headers:{...o},timeout:5e3},l=c.request(i,e=>{if(e.statusCode>=400)return 413===e.statusCode?s(new Error("HTTP 413: State bundle exceeds maximum server relay limit (500 MB)")):429===e.statusCode?s(new Error("HTTP 429: Room relay limit reached (Max 2 concurrent active relays per room)")):s(new Error(`HTTP ${e.statusCode}: Failed to fetch stream`));const o=fs.createWriteStream(t);e.pipe(o),o.on("finish",()=>{o.close(),n()}),o.on("error",e=>{try{fs.removeSync(t)}catch(e){}s(e)})});l.on("timeout",()=>{l.destroy(new Error("Connection timed out"))}),l.on("error",e=>{s(e)}),l.end()}catch(e){s(e)}})}async pullState(e,t,o,n={}){const s=n.cwd||process.cwd(),a=this.getRoomDir(e),r=path.join(a,"states");fs.ensureDirSync(r),console.log(chalk.cyan(`\n[RoomSync] Resolving environment state in Room ${chalk.bold(e)}...`));let c=[];if(o?.api?.isAuthenticated?.())try{const t=await o.api._request("GET",`/thinknsh/${e}/cli/states`);t&&Array.isArray(t.states)&&(c=t.states)}catch(e){}const i=path.join(a,"index.json");if(fs.existsSync(i))try{const e=fs.readJsonSync(i);for(const t of e)c.some(e=>e.id===t.id)||c.push(t)}catch(e){}if(0===c.length)throw new Error(`No shared states found for room "${e}". Ask your teammate to publish one with: thinknsh room share`);const l=(t||"").toLowerCase().trim();let d=null;if(l){if(d=c.find(e=>e.id&&e.id.toLowerCase()===l||e.stateId&&e.stateId.toLowerCase()===l||e.name&&e.name.toLowerCase()===l||e.id&&e.id.toLowerCase().includes(l)||e.name&&e.name.toLowerCase().includes(l)||e.author&&e.author.toLowerCase()===l),!d)throw new Error(`State matching "${t}" not found in room ${e}. Run "thinknsh room states" to see available states.`)}else d=c[0];const h=d.id||d.stateId;console.log(chalk.cyan(` Selected state: "${chalk.bold(d.name)}" (Published by: ${chalk.bold(d.author)})`)),console.log(chalk.dim(` Size: ${d.sizeMB||d.bundleSizeMB||"0"} MB`)),console.log(chalk.dim(` Services: ${(d.services||[]).join(", ")||"Node runtime"}`));const p=path.join(r,`${h}.tar.gz.enc`),y=path.join(r,`${h}.tar.gz`);if(fs.existsSync(y)||fs.existsSync(p))console.log(chalk.dim(" Found cached local state bundle. Verifying..."));else{let t=!1;if(d.p2p&&d.p2p.localIp&&d.p2p.port&&d.p2p.token){const e=`http://${d.p2p.localIp}:${d.p2p.port}/p2p/state/${h}`;console.log(chalk.dim(` Attempting direct LAN P2P connection to teammate (${d.p2p.localIp}:${d.p2p.port})...`));try{await this._downloadStream(e,p,{"X-TNC-Sync-Token":d.p2p.token}),console.log(chalk.green(" ✓ Connected via Direct LAN P2P! Downloaded encrypted bundle at maximum speed.")),t=!0}catch(e){console.log(chalk.yellow(` Direct LAN connection unreachable (${e.message}). Switching to ThinkNCollab Cloud Relay...`))}}if(!t){if(!o?.api?.isAuthenticated?.())throw new Error("State author is not on local LAN, and you are not authenticated to request server relay. Run 'thinknsh login' first.");console.log(chalk.dim(" Requesting server-assisted in-memory stream relay..."));const n=await o.api._request("POST",`/thinknsh/${e}/cli/relay-request`,{stateId:h});if(!n?.relayUrl)throw new Error("Server could not initialize relay session: "+(n?.error||"Unknown error"));const s=`${o.api.apiUrl.replace(/\/$/,"")}${n.relayUrl}`,a=o.api.getToken();await this._downloadStream(s,p,{"x-shell-token":a}),console.log(chalk.green(" ✓ Streamed encrypted bundle via ThinkNCollab In-Memory Relay (0 customer disk bytes on server)!")),t=!0}}if(!fs.existsSync(y)){if(!fs.existsSync(p))throw new Error(`Missing encrypted bundle file: ${p}`);console.log(chalk.dim(" Decrypting AES-256-GCM bundle with Room Key & verifying authentication tag..."));try{await e2eeVault.decryptFile(p,y,e),console.log(chalk.green(" ✓ Cryptographic integrity verified! Decryption completed."))}catch(e){try{fs.removeSync(p)}catch(e){}try{fs.removeSync(y)}catch(e){}throw new Error(`SECURITY ALERT: Bundle integrity check failed or wrong room key! (${e.message})`)}}const f=path.join(a,"unpack",h);fs.ensureDirSync(f),console.log(chalk.dim(" Extracting archive with security constraints (--no-same-owner, --no-same-permissions)..."));const u=spawnSync("tar",["-xzf",y,"-C",f,"--no-same-owner","--no-same-permissions"],{stdio:"pipe"});if(0!==u.status)throw new Error(`Tar extraction failed: ${u.stderr?.toString()||"Unknown error"}`);try{this._validateAndSanitizeExtracted(f,f)}catch(e){try{fs.removeSync(f)}catch(e){}try{fs.removeSync(y)}catch(e){}throw new Error(`SECURITY VIOLATION in received snapshot: ${e.message}. Extraction terminated.`)}const m=d.services||[];if(m.includes("postgres")){const e=path.join(f,"data","postgres");if(fs.existsSync(e)){console.log(chalk.dim(" Restoring native PostgreSQL database state..."));const t=new(require("./native-service-manager"));try{t.stopPostgres()}catch(e){}const o=path.join(DATA_DIR,"postgres");if(fs.existsSync(o)){const e=path.join(DATA_DIR,`postgres-bak-${Date.now().toString(36)}`);fs.moveSync(o,e)}let n=e;!fs.existsSync(path.join(n,"PG_VERSION"))&&fs.existsSync(path.join(n,"postgres","PG_VERSION"))&&(n=path.join(n,"postgres")),fs.ensureDirSync(o),"darwin"===process.platform?spawnSync("cp",["-cR",`${n}/.`,`${o}/`],{stdio:"pipe"}):spawnSync("cp",["--reflink=auto","-a",`${n}/.`,`${o}/`],{stdio:"pipe"}),await t.startPostgres(),console.log(chalk.green(" ✓ Native PostgreSQL restored with teammate database state!"))}}if(m.includes("redis")){const e=path.join(f,"data","redis","dump.rdb");if(fs.existsSync(e)){console.log(chalk.dim(" Restoring native Redis in-memory cache state..."));const t=new(require("./native-service-manager"));try{t.stopRedis()}catch(e){}const o=path.join(DATA_DIR,"redis");fs.ensureDirSync(o),fs.copyFileSync(e,path.join(o,"dump.rdb"));try{await t.startRedis(),console.log(chalk.green(" ✓ Native Redis restored with teammate in-memory state!"))}catch(e){console.log(chalk.dim(` ✓ Saved Redis state to ${o}/dump.rdb`))}}}if(m.includes("mongodb")){const e=path.join(f,"data","mongodb");if(fs.existsSync(e)){console.log(chalk.dim(" Restoring MongoDB database state..."));try{await this._restoreMongoSnapshot(e,s)}catch(e){console.log(chalk.yellow(` [Notice] MongoDB restore skipped: ${e.message}`))}}}const g=path.join(f,"workspace");if(fs.existsSync(g)){console.log(chalk.cyan(`\n Smart Merging project workspace with ${chalk.bold(d.author)}'s state...`));const t=path.join(a,"base",d.id),r=this._mergeWorkspaceFiles(g,s,{roomId:e,author:d.author,localUser:o?.api?.getUser?.()?.name||os.userInfo().username,overwrite:n.overwrite||!1,baseDir:fs.existsSync(t)?t:null});try{const e=path.join(a,"base",d.id);fs.ensureDirSync(e),fs.copySync(g,e,{overwrite:!0})}catch(e){}r.added.length>0&&console.log(chalk.green(` ✓ ${r.added.length} new files added from teammate`)),r.cleanMerged.length>0&&console.log(chalk.green(` ✓ ${r.cleanMerged.length} files cleanly merged (zero conflicts)`)),r.updated.length>0&&console.log(chalk.cyan(` ✓ ${r.updated.length} files updated`)),r.conflicts.length>0&&(console.log(chalk.yellow.bold(`\n ⚠ [MERGE CONFLICTS DETECTED] in ${r.conflicts.length} files:`)),r.conflicts.slice(0,10).forEach(e=>{console.log(chalk.yellow(` • ${e}`))}),r.conflicts.length>10&&console.log(chalk.dim(` ... and ${r.conflicts.length-10} more`)),console.log(chalk.dim(" Standard <<<<<<< LOCAL | >>>>>>> markers inserted.")),console.log(chalk.cyan(" Open in VS Code to review and click 'Accept Change' buttons.\n"))),r.backupDir&&console.log(chalk.dim(` (Safety backup saved at ${r.backupDir}. To undo: thinknsh room undo)`))}for(const e of["package.json","thinkncollab.yml","thinkncollab.yaml"]){const t=path.join(f,e),o=path.join(s,e);fs.existsSync(t)&&!fs.existsSync(o)&&(fs.copySync(t,o),console.log(chalk.green(` ✓ Restored ${e} into workspace`)))}const S=path.join(f,".env.shared");if(fs.existsSync(S)){const e=path.join(s,".env");if(fs.existsSync(e)){const t=this._mergeEnvFile(e,S,d.author);t.addedCount>0?console.log(chalk.green(` ✓ Smart merged .env: added ${t.addedCount} new keys from teammate (${t.keys.join(", ")})!`)):console.log(chalk.dim(" ✓ .env already up-to-date with teammate configuration.")),fs.copySync(S,path.join(s,".env.room")),console.log(chalk.dim(" ✓ Full teammate environment reference saved to .env.room"))}else fs.copySync(S,e),console.log(chalk.green(" ✓ Created .env from teammate shared configuration!"))}try{fs.removeSync(f)}catch(e){}return console.log(chalk.green("\n[OK] Environment Mirrored & Merged Successfully!")),console.log(chalk.dim(` Your local environment is now in exact sync with ${d.author}.`)),console.log(chalk.dim(" Zero Docker Hub registry downloads were used.")),fs.existsSync(path.join(s,"package.json"))&&!fs.existsSync(path.join(s,"node_modules"))?console.log(chalk.cyan(' Tip: Run "npm install" to install dependencies for this project.\n')):console.log(),{success:!0,state:d}}async listStates(e,t){let o=[];if(t?.api?.isAuthenticated?.())try{const n=await t.api._request("GET",`/thinknsh/${e}/cli/states`);n&&Array.isArray(n.states)&&(o=n.states)}catch(e){}const n=this.getRoomDir(e),s=path.join(n,"index.json");if(fs.existsSync(s))try{const e=fs.readJsonSync(s);for(const t of e)o.some(e=>e.id===t.id)||o.push(t)}catch(e){}return o}}module.exports=RoomSyncManager;
@@ -1,7 +0,0 @@
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 https=require("https"),http=require("http"),fs=require("fs"),path=require("path"),os=require("os"),{execSync}=require("child_process"),RUNTIMES_BASE_DIR=path.join(os.homedir(),".tnc","runtimes","node"),LTS_MAP={22:"22.14.0",20:"20.18.3",18:"18.20.7",16:"16.20.2"};class RuntimeManager{static resolveVersion(t){if(!t||"string"!=typeof t)return null;const e=t.trim().replace(/^v/,"");if(/^\d+\.\d+\.\d+$/.test(e))return e;const n=e.match(/(?:>=|\^|~)?\s*(\d+)/);return n&&LTS_MAP[n[1]]?LTS_MAP[n[1]]:e.startsWith("20")?LTS_MAP[20]:e.startsWith("22")?LTS_MAP[22]:e.startsWith("18")?LTS_MAP[18]:null}static getPlatformInfo(){const t=process.platform,e=process.arch;let n=t;"darwin"===t?n="darwin":"linux"===t?n="linux":"win32"===t&&(n="win");let s=e;return"arm64"===e?s="arm64":"x64"===e?s="x64":"ia32"===e?s="x86":"arm"===e&&(s="armv7l"),{osName:n,archName:s}}static detectProjectNodeVersion(t){if(!t||!fs.existsSync(t))return null;const e=path.join(t,"thinkncollab.yml");if(fs.existsSync(e))try{const t=fs.readFileSync(e,"utf8"),n=t.match(/node:\s*["']?([^"'\n\r]+)["']?/i)||t.match(/version:\s*["']?([^"'\n\r]+)["']?/i)||t.match(/-\s*node@([0-9.]+)/i);if(n&&n[1]){const t=this.resolveVersion(n[1]);if(t)return t}}catch(t){}for(const e of[".nvmrc",".node-version"]){const n=path.join(t,e);if(fs.existsSync(n))try{const t=fs.readFileSync(n,"utf8").trim(),e=this.resolveVersion(t);if(e)return e}catch(t){}}const n=path.join(t,"package.json");if(fs.existsSync(n))try{const t=JSON.parse(fs.readFileSync(n,"utf8"));if(t.engines&&t.engines.node){const e=this.resolveVersion(t.engines.node);if(e)return e}}catch(t){}return null}static async ensureNodeRuntime(t,e=null){const n=e||this.detectProjectNodeVersion(t);if(!n)return null;const s=this.resolveVersion(n);if(!s)return null;if(process.versions.node.split(".")[0]===s.split(".")[0]&&!e)return null;const o=path.join(RUNTIMES_BASE_DIR,`v${s}`),r="win32"===process.platform?o:path.join(o,"bin"),i=path.join(r,"win32"===process.platform?"node.exe":"node");if(fs.existsSync(i))return{version:s,runtimeDir:o,binDir:r,nodePath:i};fs.mkdirSync(o,{recursive:!0});const{osName:c,archName:a}=this.getPlatformInfo(),l=`node-v${s}-${c}-${a}.${"win32"===process.platform?"zip":"tar.gz"}`,f=`https://nodejs.org/dist/v${s}/${l}`,d=path.join(os.tmpdir(),`tnc-${l}`);console.log(`[TNC Runtime] Project requires Node.js v${s} (Host has v${process.versions.node}).`),console.log(`[TNC Runtime] Downloading standalone hermetic runtime from ${f}...`),await this.downloadFile(f,d),console.log(`[TNC Runtime] Extracting Node.js v${s} to ${o}...`);try{if("win32"===process.platform){if(execSync(`tar -xf "${d}" -C "${o}" --strip-components=1 2>nul || powershell -command "Expand-Archive -Path '${d}' -DestinationPath '${o}' -Force"`),!fs.existsSync(i)){const t=path.join(o,`node-v${s}-${c}-${a}`);if(fs.existsSync(t)&&fs.statSync(t).isDirectory()){const e=fs.readdirSync(t);for(const n of e){const e=path.join(t,n),s=path.join(o,n);fs.existsSync(s)||fs.renameSync(e,s)}try{fs.rmdirSync(t)}catch(t){}}}}else execSync(`tar -xzf "${d}" -C "${o}" --strip-components=1`);fs.existsSync(i)&&fs.chmodSync(i,493);const t=path.join(r,"win32"===process.platform?"npm.cmd":"npm");fs.existsSync(t)&&fs.chmodSync(t,493)}finally{try{fs.existsSync(d)&&fs.unlinkSync(d)}catch(t){}}return fs.existsSync(i)?(console.log(`[TNC Runtime] Successfully provisioned standalone Node.js v${s} in ~/.tnc/runtimes/node/v${s}`),{version:s,runtimeDir:o,binDir:r,nodePath:i}):(console.warn(`[TNC Runtime] Failed to verify ${i}. Falling back to host Node.js.`),null)}static downloadFile(t,e){return new Promise((n,s)=>{const o=fs.createWriteStream(e),r=t=>{(t.startsWith("https")?https:http).get(t,i=>{if(i.statusCode>=300&&i.statusCode<400&&i.headers.location)return r(i.headers.location);if(200!==i.statusCode){o.close();try{fs.unlinkSync(e)}catch(t){}return s(new Error(`Failed to download ${t}: HTTP ${i.statusCode}`))}i.pipe(o),o.on("finish",()=>{o.close(()=>n(e))})}).on("error",t=>{o.close();try{fs.unlinkSync(e)}catch(t){}s(t)})};r(t)})}}module.exports=RuntimeManager;
@@ -1,7 +0,0 @@
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
- let fs;try{fs=require("fs-extra")}catch(r){try{fs=require("/Users/ramansingh/ThinkNCollab-Shell-V2/node_modules/fs-extra")}catch(r){const e=require("fs");fs={...e,ensureDirSync:r=>e.mkdirSync(r,{recursive:!0}),readJsonSync:r=>JSON.parse(e.readFileSync(r,"utf8")),writeJsonSync:(r,i)=>e.writeFileSync(r,JSON.stringify(i,null,2),"utf8"),removeSync:r=>e.rmSync(r,{recursive:!0,force:!0})}}}const path=require("path"),{execSync}=require("child_process");class SandboxStorage{constructor(r,e={}){this.id=r,this.rootDir=e.rootDir||path.join(process.env.HOME||"/tmp",".tnc","sandboxes",r),this.upperDir=path.join(this.rootDir,"diff"),this.workDir=path.join(this.rootDir,"work"),this.mergedDir=path.join(this.rootDir,"merged"),this.isLinux="linux"===process.platform}initStorage(r,e=null,i=null){if(fs.ensureDirSync(this.rootDir),fs.ensureDirSync(this.upperDir),fs.ensureDirSync(this.workDir),fs.ensureDirSync(this.mergedDir),this.isLinux&&r&&fs.existsSync(r))try{execSync(`mount -t overlay overlay -o lowerdir=${r},upperdir=${this.upperDir},workdir=${this.workDir} ${this.mergedDir}`)}catch(r){console.warn(`[TNC Storage] OverlayFS mount warning: ${r.message}`)}if(e&&fs.existsSync(e)){const r=path.join(this.mergedDir,"workspace");if(fs.ensureDirSync(r),this.isLinux)try{execSync(`mount --bind "${e}" "${r}"`)}catch(i){try{fs.existsSync(r)||fs.symlinkSync(e,r)}catch(r){}}else try{fs.existsSync(r)&&0===fs.readdirSync(r).length&&fs.removeSync(r),fs.existsSync(r)||fs.symlinkSync(e,r)}catch(r){}}if(i&&fs.existsSync(i)){const r=path.join(this.mergedDir,"opt","node");if(fs.ensureDirSync(r),this.isLinux)try{execSync(`mount --bind "${i}" "${r}"`)}catch(e){try{fs.existsSync(r)||fs.symlinkSync(i,r)}catch(r){}}else try{fs.existsSync(r)||fs.symlinkSync(i,r)}catch(r){}}return{rootDir:this.rootDir,mergedDir:this.mergedDir,upperDir:this.upperDir}}unmountStorage(){if(this.isLinux){const r=path.join(this.mergedDir,"opt","node");try{execSync(`umount -l "${r}"`,{stdio:"ignore"})}catch(r){}const e=path.join(this.mergedDir,"workspace");try{execSync(`umount -l "${e}"`,{stdio:"ignore"})}catch(r){}try{execSync(`umount -l "${this.mergedDir}"`,{stdio:"ignore"})}catch(r){}}}purge(){this.unmountStorage();try{return fs.removeSync(this.rootDir),!0}catch{return!1}}}module.exports=SandboxStorage;
@@ -1,7 +0,0 @@
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"),path=require("path"),fs=require("fs-extra"),{execSync}=require("child_process");class SandboxSupervisor{constructor(t){this.engine=t,this.watched=new Map,this.checkInterval=null}startSupervision(t=3e3){this.checkInterval||(this.checkInterval=setInterval(()=>this.runSupervisionCycle(),t))}stopSupervision(){this.checkInterval&&(clearInterval(this.checkInterval),this.checkInterval=null)}watch(t,e){this.watched.set(t,{meta:e,restartCount:e.restartCount||0,lastCrash:null,lastStartTime:Date.now(),rapidCrashCount:0,health:"HEALTHY"}),this.startSupervision()}unwatch(t){this.watched.delete(t),0===this.watched.size&&this.stopSupervision()}async runSupervisionCycle(){for(const[t,e]of this.watched.entries()){const s=this.engine.loadMeta(t);if(!s){this.watched.delete(t);continue}if("FROZEN"===s.state||"DESTROYED"===s.state)continue;let a=!1;if(s.pid)try{process.kill(s.pid,0),a=!0}catch{a=!1}if(a||"RUNNING"!==s.state){if(a){const a=this.gatherTelemetry(s.pid,s.cwd);s.cpuPercent=a.cpu,s.memoryMB=a.memory,a.addons&&a.addons.length>0&&(s.addons=a.addons),a.sfu&&(s.sfu=a.sfu);const n=Date.now();if(!e.lastHealthProbe||n-e.lastHealthProbe>=15e3)if(e.lastHealthProbe=n,s.hostPort){const t=await this.probeHttp(s.hostPort,s.healthPath||"/health");s.health=t?"HEALTHY":"DEGRADED"}else s.health="HEALTHY";this.engine.saveMeta(t,s)}}else await this.handleCrash(t,s,e)}}async handleCrash(t,e,s){const a=Date.now();s.restartCount=(s.restartCount||0)+1,s.lastCrash=(new Date).toISOString(),e.restartCount=s.restartCount,e.state="CRASH_RECOVERING",e.health="STARTING";const n=path.join(this.engine.baseDir,t,"crash.log"),r=`[${s.lastCrash}] CRASH DETECTED on PID ${e.pid}. Auto-healing trigger (#${s.restartCount})\n`;try{fs.appendFileSync(n,r,"utf8")}catch(t){}const o=s.lastStartTime&&a-s.lastStartTime<4e3;if(s.rapidCrashCount=o?(s.rapidCrashCount||0)+1:1,s.rapidCrashCount>=3||s.restartCount>5)return e.state="DEAD",e.health="DEAD",this.engine.saveMeta(t,e),this.unwatch(t),console.log(`\n[TNC:AutoHeal] Container "${t}" terminated after repeated fatal crashes. Process halted.`),void console.log(`Check logs with: thinknsh logs -f ${t}`);console.log(`\n[TNC:AutoHeal] Container "${t}" crashed! Reviving process (Restart #${s.restartCount})...`);try{s.lastStartTime=Date.now();const a=await this.engine.start(t,{cmd:e.cmd,cwd:e.cwd,hostPort:e.hostPort,containerPort:e.containerPort,memoryLimit:e.memoryLimit});a.success&&(e.pid=a.meta.pid,e.state="RUNNING",e.health="HEALTHY",this.engine.saveMeta(t,e),console.log(`[TNC:AutoHeal] Container "${t}" successfully revived on PID ${e.pid}!`))}catch(s){e.state="DEAD",e.health="DEAD",this.engine.saveMeta(t,e),this.unwatch(t)}}probeHttp(t,e="/health"){return new Promise(s=>{const a=http.request({hostname:"127.0.0.1",port:t,path:e,method:"GET",headers:{"User-Agent":"TNC-Supervisor/1.0"},timeout:1e3},a=>{404!==a.statusCode||"/health"!==e?s(a.statusCode<500):this.probeHttp(t,"/").then(s)});a.on("error",()=>s(!1)),a.on("timeout",()=>{a.destroy(),s(!1)}),a.end()})}gatherTelemetry(t,e=null){if(!t)return{cpu:0,memory:0,sfu:null,addons:[]};try{let s=[t];try{const e=execSync(`pgrep -P ${t} 2>/dev/null || echo ""`,{encoding:"utf8"}).trim();if(e){const t=e.split(/\s+/).filter(Boolean);s.push(...t);for(const e of t)try{const t=execSync(`pgrep -P ${e} 2>/dev/null || echo ""`,{encoding:"utf8"}).trim();t&&s.push(...t.split(/\s+/).filter(Boolean))}catch(t){}}}catch(t){}s=Array.from(new Set(s.map(Number).filter(Boolean)));let a=0,n=0,r=null;const o=new Map;for(const e of s)try{const s=execSync(`ps -p ${e} -o comm=,%cpu=,rss= 2>/dev/null || echo ""`,{encoding:"utf8"}).trim();if(s){const i=s.split(/\s+/).filter(Boolean),c=i[0]||"",h=parseFloat(i[1])||0,l=parseInt(i[2],10)||0;if(a+=h,n+=l,c.includes("mediasoup-worker")||s.includes("mediasoup"))r={active:!0,workerPid:e,ports:"10000-10100"},o.set("sfu",{id:"sfu",name:"SFU Media (mediasoup)",icon:"fas fa-video",pid:e,status:"active",ports:"10000-10100/udp"});else if(c.includes("redis-server")||s.includes("redis"))o.set("redis",{id:"redis",name:"Redis Broker",icon:"fas fa-database",pid:e,status:"active",ports:"6379"});else if(!(e===t||c.includes("npm")||c.includes("sh")||c.includes("bash")||c.includes("entrypoint"))){const t=path.basename(c);o.set(`worker-${e}`,{id:`worker-${e}`,name:`${t} (Worker)`,icon:t.includes("python")?"fab fa-python":"fas fa-microchip",pid:e,status:"active"})}}}catch(t){}if(e)try{const t=path.join(e,"thinkncollab.yml");if(fs.existsSync(t)){const e=fs.readFileSync(t,"utf8");if(e.includes("addons:")){const t=e.split("\n");let s=!1;for(const e of t){const t=e.trim();if("addons:"!==t){if(s&&t.endsWith(":")&&!t.startsWith("-")&&(s=!1),s&&t.startsWith("-")){const e=t.replace(/^-\s*/,"").replace(/["']/g,"");o.has(e)||o.set(e,{id:e,name:"sfu"===e?"SFU Media (mediasoup)":"redis"===e?"Redis Broker":e,icon:"sfu"===e?"fas fa-video":"redis"===e?"fas fa-database":"fas fa-puzzle-piece",status:"active"})}}else s=!0}}}}catch(t){}const i=Math.round(n/1024);return{cpu:Math.round(10*a)/10,memory:i,sfu:r,addons:Array.from(o.values())}}catch{return{cpu:0,memory:0,sfu:null,addons:[]}}}}module.exports=SandboxSupervisor;
@@ -1,7 +0,0 @@
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("./tunnel"),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 tunnel-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){}});
@@ -1,7 +0,0 @@
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){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||process.env.THINKNCOLLAB_TOKEN||null,this._doConnect()}_doConnect(){if(this._stopping)return;let t;try{t=require("socket.io-client")}catch(e){try{t=require("/Users/ramansingh/ThinkNCollab-Shell-V2/node_modules/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",()=>{this.active=!0,e.emit("sandbox:tunnel-register",{sandboxId:this.sandboxId,localPort:this.localPort,userId:this._userId}),this._startHeartbeat()}),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:r,body:i}=t,a=this.localPort,h={...r||{}};delete h.host,delete h.connection,delete h["transfer-encoding"],h.host=`127.0.0.1:${a}`;const l=i?Buffer.from(i,"base64"):null;l&&l.length>0&&(h["content-length"]=l.length);const c={hostname:"127.0.0.1",port:a,path:o||"/",method:n||"GET",headers:h,timeout:3e4},d=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")})})});d.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")})}),d.on("timeout",()=>{d.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&&d.write(l),d.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;
@@ -1,7 +0,0 @@
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"),path=require("path"),chalk=require("chalk");class SandboxWindowLauncher{static spawn(e){const n=path.resolve(__dirname,"../sandbox-window.js"),o=process.execPath;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}' '${e}'"\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 ${e}`)),!1}}if("win32"===process.platform)try{return spawn(`start cmd.exe /k "title TNC Sandbox: ${e} && "${o}" "${n}" ${e}""`,[],{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 t of a)try{return spawn(t,["-e",`${o} ${n} ${e}`],{detached:!0,stdio:"ignore"}).unref(),console.log(chalk.green(`[TNC] Sandbox Console opened in ${t}!`)),!0}catch(e){}return console.log(chalk.dim(`Tip: Run "thinknsh logs -f ${e}" to view live logs.`)),!1}}module.exports=SandboxWindowLauncher;
@@ -1,7 +0,0 @@
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{execSync,spawn}=require("child_process");let fs;try{fs=require("fs-extra")}catch(e){try{fs=require("/Users/ramansingh/ThinkNCollab-Shell-V2/node_modules/fs-extra")}catch(e){const t=require("fs");fs={...t,ensureDirSync:e=>t.mkdirSync(e,{recursive:!0}),readJsonSync:e=>JSON.parse(t.readFileSync(e,"utf8")),writeJsonSync:(e,r)=>t.writeFileSync(e,JSON.stringify(r,null,2),"utf8"),removeSync:e=>t.rmSync(e,{recursive:!0,force:!0})}}}const path=require("path"),os=require("os");class WindowsWSL2Sandbox{constructor(){this.isWindows="win32"===process.platform,this._wsl2Available=null,this._defaultDistro=null}detect(){if(!this.isWindows)return{available:!1,reason:"not-windows"};if(null!==this._wsl2Available)return{available:this._wsl2Available,distro:this._defaultDistro};try{const e=execSync("wsl.exe --list --quiet 2>nul",{encoding:"utf16le",timeout:5e3}).split("\n").map(e=>e.replace(/\0/g,"").trim()).filter(Boolean);return 0===e.length?(this._wsl2Available=!1,{available:!1,reason:"no-distros"}):(this._defaultDistro=e.find(e=>e.toLowerCase().includes("ubuntu"))||e[0],this._defaultDistro=this._defaultDistro.replace(/\s*\(default\)/i,"").trim(),this._wsl2Available=!0,{available:!0,distro:this._defaultDistro,allDistros:e})}catch{return this._wsl2Available=!1,{available:!1,reason:"wsl-not-installed"}}}toWslPath(e){if(!e)return"/home";try{return execSync(`wsl.exe wslpath -u "${e.replace(/\\/g,"\\\\")}"`,{encoding:"utf8",timeout:3e3}).trim()||this._manualConvert(e)}catch{return this._manualConvert(e)}}_manualConvert(e){return e.replace(/^([A-Za-z]):\\/,(e,t)=>`/mnt/${t.toLowerCase()}/`).replace(/\\/g,"/")}setupPortProxy(e,t){if(!this.isWindows)return{success:!1};try{const r=execSync("wsl.exe -- bash -c \"ip -4 addr show eth0 | grep -oP '(?<=inet )\\d+\\.\\d+\\.\\d+\\.\\d+'\"",{encoding:"utf8",timeout:5e3}).trim();if(!r)return{success:!1,reason:"no-wsl-ip"};execSync(`netsh interface portproxy add v4tov4 listenport=${e} listenaddress=0.0.0.0 connectport=${t} connectaddress=${r}`,{stdio:"ignore",timeout:5e3});try{execSync(`netsh advfirewall firewall add rule name="TNC-${e}" dir=in action=allow protocol=TCP localport=${e}`,{stdio:"ignore",timeout:5e3})}catch(e){}return{success:!0,wslIp:r,hostPort:e,containerPort:t}}catch(e){return{success:!1,reason:e.message}}}removePortProxy(e){if(!this.isWindows)return!1;try{execSync(`netsh interface portproxy delete v4tov4 listenport=${e} listenaddress=0.0.0.0`,{stdio:"ignore",timeout:3e3});try{execSync(`netsh advfirewall firewall delete rule name="TNC-${e}"`,{stdio:"ignore",timeout:3e3})}catch(e){}return!0}catch{return!1}}spawnInWSL2(e,t,r={}){const{cwd:s=process.cwd(),hostPort:n=null,containerPort:i=null,memoryLimit:o="2g",distro:l=this._defaultDistro,outFd:a=null,runtimeBinDir:c=null}=r,u=this.detect();if(!u.available)return{success:!1,reason:u.reason,fallback:"host-process"};const d=this.toWslPath(s),h=c?this.toWslPath(c):null;let f=null;n&&i&&(f=this.setupPortProxy(n,i));const w=this._parseMemoryMB(o),p=1024*w,m=[];h&&m.push(`export PATH="${h}:$PATH"`),m.push(`cd "${d}" 2>/dev/null || cd ~`,`ulimit -v ${p} 2>/dev/null || true`,"ulimit -n 4096 2>/dev/null || true",'eval "$TNC_CMD"');const y=m.join("; "),S=spawn("wsl.exe",[...l?["-d",l]:[],"--","bash","-c",y],{detached:!0,stdio:["ignore",a||"ignore",a||"ignore"],env:{...process.env,TNC_CMD:t,TNC_SANDBOX_ID:e,TNC_PLATFORM:"wsl2-kernel",PORT:String(i||n||3e3),WSL_SANDBOX:"1",PATH:c?`${c};${process.env.PATH||""}`:process.env.PATH}});return S.unref(),{success:!0,child:S,isolationLevel:"wsl2-kernel",memoryLimitMB:w,distro:l||"default",wslCwd:d,portProxy:f}}killSandbox(e,t){if(!this.isWindows||!e)return!1;try{execSync(`taskkill /F /T /PID ${e} 2>nul`,{stdio:"ignore"})}catch(e){}try{execSync(`wsl.exe -- bash -c "pkill -f TNC_SANDBOX_ID=${t} 2>/dev/null || true"`,{stdio:"ignore",timeout:3e3})}catch(e){}return!0}freezeCleanup(e){this.isWindows&&e.hostPort&&this.removePortProxy(e.hostPort)}generateWSLConfig(e=4,t=2){const r=path.join(os.homedir(),".wslconfig"),s=`\n[wsl2]\nmemory=${e}GB\nprocessors=${t}\nswap=0\nlocalhostForwarding=true\nnestedVirtualization=false\n`.trim();return fs.writeFileSync(r,s),{path:r,config:s}}getMemoryUsage(e){if(!this.isWindows||!e)return 0;try{const t=execSync(`wmic process where ProcessId=${e} get WorkingSetSize /value 2>nul`,{encoding:"utf8",timeout:3e3}).match(/WorkingSetSize=(\d+)/);if(t)return Math.round(parseInt(t[1])/1024/1024)}catch(e){}return 0}_parseMemoryMB(e){const t=String(e||"2g").toLowerCase().trim().match(/^(\d+(?:\.\d+)?)([kmg]?)b?$/);if(!t)return 2048;const r=parseFloat(t[1]);switch(t[2]){case"k":return Math.floor(r/1024);case"m":default:return Math.floor(r);case"g":return Math.floor(1024*r)}}}module.exports=WindowsWSL2Sandbox;
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * @license ThinkNCollab Enterprise & Open Core Ecosystem
4
- * Copyright (c) 2024-2026 ThinkNCollab Systems Inc. All rights reserved.
5
- * PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
6
- * UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
7
- */
8
- const fs=require("fs"),path=require("path"),os=require("os"),chalk=require("chalk"),{spawn}=require("child_process"),SandboxTunnel=require("./sandbox/tunnel"),sandboxId=process.argv[2];sandboxId||(console.log(chalk.red("Error: Sandbox ID required.")),console.log(chalk.dim("Usage: node sandbox-window.js <sandbox-id>")),process.exit(1));const sandboxDir=path.join(os.homedir(),".tnc","sandboxes",sandboxId),metaPath=path.join(sandboxDir,"meta.json"),logPath=path.join(sandboxDir,"output.log");function clearScreen(){process.stdout.write("c")}function getMeta(){if(fs.existsSync(metaPath))try{return JSON.parse(fs.readFileSync(metaPath,"utf8"))}catch(o){}return null}function drawHeader(o){clearScreen();const t=process.stdout.columns||80,n="━".repeat(t),e=` [Sandbox] ThinkNCollab - Console [${sandboxId}] `,s=e.padStart(Math.floor((t+e.length)/2)).padEnd(t);console.log(chalk.cyan.bold(n)),console.log(chalk.bgCyan.black.bold(s)),console.log(chalk.cyan.bold(n));const a=o?o.state:"UNKNOWN",l="RUNNING"===a?chalk.green.bold:"FROZEN"===a?chalk.blue.bold:chalk.red.bold,r=o&&o.pid?o.pid:chalk.dim("none"),c=o&&o.hostPort?`${o.hostPort} -> ${o.containerPort||o.hostPort}`:o&&o.containerPort?o.containerPort:"none",i=o&&o.cmd?o.cmd:"dev";console.log(` State: ${l(a)} | PID: ${chalk.yellow(r)} | Port: ${chalk.magenta(c)} | Command: ${chalk.white(i)}`),console.log(chalk.dim(` Logs: ${logPath}`)),console.log(chalk.dim(" Press Ctrl+C to close this console window (sandbox will keep running).\n")),console.log(chalk.dim("─".repeat(t)))}function streamLogs(){if(drawHeader(getMeta()),!fs.existsSync(logPath)){console.log(chalk.dim("Waiting for sandbox log stream to start..."));const o=setInterval(()=>{fs.existsSync(logPath)&&(clearInterval(o),startTailing())},500);return}startTailing()}function startTailing(){let o=0;if(fs.existsSync(logPath))try{const t=fs.readFileSync(logPath,"utf8");process.stdout.write(t),o=Buffer.byteLength(t)}catch(o){}"win32"!==process.platform?spawn("tail",["-n","50","-F",logPath],{stdio:["ignore","inherit","inherit"]}).on("error",()=>{watchWithPolling()}):watchWithPolling()}function watchWithPolling(){let o=fs.existsSync(logPath)?fs.statSync(logPath).size:0;setInterval(()=>{if(!fs.existsSync(logPath))return;const t=fs.statSync(logPath);t.size>o?(fs.createReadStream(logPath,{start:o,end:t.size}).pipe(process.stdout),o=t.size):t.size<o&&(o=t.size)},300)}process.stdout.isTTY&&process.stdout.write(`]2;TNC Sandbox: ${sandboxId}`);const meta=getMeta();let tunnel=null;meta&&meta.hostPort&&(tunnel=new SandboxTunnel,tunnel.connect(sandboxId,meta.hostPort)),process.on("SIGINT",()=>{tunnel&&tunnel.stop(),console.log(chalk.dim(`\n\n[Console disconnected. Sandbox "${sandboxId}" continues running in background.]`)),process.exit(0)}),streamLogs();