[
  {
    "path": ".gitignore",
    "content": "node_modules\ndist"
  },
  {
    "path": "README.md",
    "content": "# AcodeX-Server\n\n> [!WARNING]\n> It is recommended to avoid using this implementation. Instead, consider using [this alternative](https://github.com/bajrangCoder/acodex_server), which is lightweight, faster, and offers several advantages.\n\nCli for **AcodeX** Plugin\n\n## Installation\n\nRun `npm install -g acodex-server` for installing.\n\n```\nnpm install -g acodex-server\n```\n\n## Usage\n\nRun `axs` or `acodex-server` or `acodeX-server` to start server\n\n```\naxs\n```\n\nand `axs --help` for help\n\n> More features coming soon...\n\n:)\n"
  },
  {
    "path": "bin/acodex-server",
    "content": "#!/data/data/com.termux/files/usr/bin/env node\nrequire(\"../dist/index.js\");\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"acodex-server\",\n  \"version\": \"1.1.7\",\n  \"description\": \"Server of Acode AcodeX plugin\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"types/index.d.ts\",\n  \"scripts\": {\n    \"start\": \"ts-node src/index.ts\",\n    \"build\": \"webpack\"\n  },\n  \"bin\": {\n    \"axs\": \"bin/acodex-server\",\n    \"acodex-server\": \"bin/acodex-server\",\n    \"acodeX-server\": \"bin/acodex-server\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/bajrangCoder/acodex-server.git\"\n  },\n  \"keywords\": [\n    \"acode\",\n    \"acodeX\",\n    \"terminal\",\n    \"server\",\n    \"cli\"\n  ],\n  \"author\": \"Raunak Raj <bajrangcoders@gmail.com>\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/bajrangCoder/acodex-server/issues\"\n  },\n  \"homepage\": \"https://github.com/bajrangCoder/acodex-server#readme\",\n  \"devDependencies\": {\n    \"@types/node\": \"^20.16.2\",\n    \"@types/ws\": \"^8.5.12\",\n    \"node-loader\": \"^2.0.0\",\n    \"ts-loader\": \"^9.5.1\",\n    \"ts-node\": \"^10.9.2\",\n    \"webpack\": \"^5.94.0\",\n    \"webpack-cli\": \"^5.1.4\"\n  },\n  \"dependencies\": {\n    \"@hono/node-server\": \"^1.12.2\",\n    \"@xterm/addon-serialize\": \"^0.13.0\",\n    \"@xterm/headless\": \"^5.5.0\",\n    \"@xterm/xterm\": \"^5.5.0\",\n    \"commander\": \"^11.1.0\",\n    \"hono\": \"^3.12.12\",\n    \"node-pty\": \"^1.0.0\",\n    \"ws\": \"^8.18.0\"\n  }\n}\n"
  },
  {
    "path": "src/helpers.ts",
    "content": "import * as os from 'os';\n\ninterface Colors {\n    reset: string;\n    black: string;\n    red: string;\n    green: string;\n    yellow: string;\n    blue: string;\n    magenta: string;\n    cyan: string;\n    white: string;\n    [key: string]: string; // Index signature to allow any string key\n}\n\nexport function coloredText(text: string | number, color: string): string {\n    const colors: Colors = {\n        reset: '\\x1b[0m',\n        black: '\\x1b[30m',\n        red: '\\x1b[31m',\n        green: '\\x1b[32m',\n        yellow: '\\x1b[33m',\n        blue: '\\x1b[34m',\n        magenta: '\\x1b[35m',\n        cyan: '\\x1b[36m',\n        white: '\\x1b[37m',\n    };\n\n    const selectedColor = colors[color] || colors.reset;\n    return `${selectedColor}${text}${colors.reset}`;\n}\n\nexport function getIPAddress(): string | boolean {\n    const interfaces = os.networkInterfaces();\n    for (const key in interfaces) {\n        if (interfaces.hasOwnProperty(key)) {\n            const ifaceList = interfaces[key];\n            if (ifaceList) {\n                for (const iface of ifaceList) {\n                    if (!iface.internal && iface.family === 'IPv4') {\n                        return iface.address;\n                    }\n                }\n            }\n        }\n    }\n    return false;\n}"
  },
  {
    "path": "src/index.ts",
    "content": "#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { startServer } from \"./terminal-server\";\nimport { getIPAddress } from \"./helpers\";\n\nconst program = new Command();\n\nprogram\n\t.name(\"axs\")\n\t.description(\"CLI of AcodeX Acode plugin\")\n\t.version(\"1.1.6\")\n\t.option(\"-p, --port <port>\", \"port to start the server\")\n\t.option(\"-i, --ip\", \"start the server on local network (ip)\")\n\t.action(options => {\n\t\tif (options.port && options.ip) {\n\t\t\tconst ipdr = getIPAddress();\n\t\t\tif (ipdr === false) {\n\t\t\t\tconsole.error(\"Failed to retrieve IP address.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstartServer(options.port, ipdr.toString());\n\t\t} else if (options.port) {\n\t\t\tstartServer(options.port);\n\t\t} else if (options.ip) {\n\t\t\tconst ipdr = getIPAddress();\n\t\t\tif (ipdr === false) {\n\t\t\t\tconsole.error(\"Failed to retrieve IP address.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstartServer(undefined, ipdr.toString());\n\t\t} else {\n\t\t\tstartServer();\n\t\t}\n\t});\n\nprogram.parse();\n"
  },
  {
    "path": "src/terminal-server.ts",
    "content": "import { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { serve } from \"@hono/node-server\";\nimport WebSocket from \"ws\";\nimport * as os from \"node:os\";\nimport * as pty from \"node-pty\";\nimport http from \"http\";\nimport { Terminal } from '@xterm/headless';\nimport { SerializeAddon } from \"@xterm/addon-serialize\";\nimport { Session } from \"../types\";\nimport { coloredText } from \"./helpers\";\n\n/** Whether to use binary transport. */\nconst USE_BINARY = os.platform() !== \"win32\";\nconst sessions: Record<number, Session> = {};\n\nexport async function startServer(\n  port: number = 8767,\n  host: string = \"0.0.0.0\"\n) {\n  const app = new Hono();\n  app.use(\"/*\", cors());\n\n  const server = serve(\n    {\n      fetch: app.fetch,\n      port,\n      hostname: host,\n    },\n    (info) => {\n      console.log(\n        `${coloredText(\n          \"AcodeX Server\",\n          \"blue\"\n        )} started 🔥\\n\\nHost: ${coloredText(\n          info.address === \"0.0.0.0\" ? \"localhost\" : info.address,\n          \"cyan\"\n        )}\\nPort: ${coloredText(info.port, \"cyan\")}`\n      );\n    }\n  );\n  const wss = new WebSocket.Server({ noServer: true });\n\n  app.get(\"/\", (c) => {\n    return c.text(\"Hello acodeX-server is working😊...\");\n  });\n\n  app.post(\"/terminals\", async (c) => {\n    try {\n      const env = { ...process.env };\n      env[\"COLORTERM\"] = \"truecolor\";\n\n      const { cols, rows } = await c.req.json();\n      if (typeof cols !== \"string\" || typeof rows !== \"string\") {\n        throw new Error(\"Unexpected query args\");\n      }\n\n      const colsInt = parseInt(cols, 10);\n      const rowsInt = parseInt(rows, 10);\n\n      const term = pty.spawn(\n        process.platform === \"win32\" ? \"pwsh.exe\" : process.env.SHELL || \"bash\",\n        [],\n        {\n          name: \"xterm-256color\",\n          cols: colsInt || 80,\n          rows: rowsInt || 24,\n          cwd: process.platform === \"win32\" ? undefined : env.HOME,\n          env,\n          encoding: USE_BINARY ? null : \"utf8\",\n        }\n      );\n\n      const xterm = new Terminal({\n        rows: rowsInt || 24,\n        cols: colsInt || 80,\n        allowProposedApi: true,\n      });\n      const serializeAddon = new SerializeAddon();\n      xterm.loadAddon(serializeAddon);\n\n      console.log(\"Created terminal with PID: \" + term.pid);\n      sessions[term.pid] = {\n        term,\n        xterm,\n        serializeAddon,\n        terminalData: \"\",\n      };\n\n      sessions[term.pid].temporaryDisposable = term.onData((data: string) => {\n        sessions[term.pid].terminalData += data;\n      });\n\n      return c.text(term.pid.toString());\n    } catch (error) {\n      console.error(error);\n      return c.json({ error: \"Failed to create terminal\" }, 500);\n    }\n  });\n\n  app.post(\"/terminals/:pid/resize\", async (c) => {\n    try {\n      const pid = parseInt(c.req.param(\"pid\"), 10);\n      const { cols, rows } = await c.req.json();\n      const colsInt = parseInt(cols, 10);\n      const rowsInt = parseInt(rows, 10);\n\n      const { term, xterm } = sessions[pid];\n\n      term.resize(colsInt, rowsInt);\n      xterm.resize(colsInt, rowsInt);\n      return c.json({ success: true });\n    } catch (error) {\n      console.error(error);\n      return c.json({ error: \"Failed to resize terminal\" }, 500);\n    }\n  });\n\n  server.on(\"upgrade\", (request, socket, head) => {\n    const pathname = new URL(\n      request.url || \"\",\n      `http://${request.headers.host}`\n    ).pathname;\n\n    if (pathname.startsWith(\"/terminals/\")) {\n      const pid = parseInt(pathname.split(\"/\").pop() || \"\", 10);\n\n      wss.handleUpgrade(request, socket, head, (ws) => {\n        wss.emit(\"connection\", ws, request, pid);\n      });\n    } else {\n      socket.destroy();\n    }\n  });\n  wss.on(\n    \"connection\",\n    (ws: WebSocket, request: http.IncomingMessage, pid: number) => {\n      try {\n        const { term, xterm, serializeAddon, terminalData } = sessions[pid];\n\n        console.log(\"Connected to terminal \" + term.pid);\n\n        if (sessions[pid].temporaryDisposable && terminalData) {\n          sessions[pid].temporaryDisposable?.dispose();\n          delete sessions[pid].temporaryDisposable;\n          xterm.write(sessions[pid].terminalData);\n        }\n\n        ws.send(sessions[pid].terminalData);\n\n        sessions[pid].dataHandler = term.onData(function (\n          data: string | Uint8Array\n        ) {\n          try {\n            xterm.write(typeof data === \"string\" ? data : new Uint8Array(data));\n            ws.send(data);\n          } catch (ex) {\n            // The WebSocket is not open, ignore\n          }\n        });\n\n        ws.on(\"message\", function (msg) {\n          term.write(msg.toString());\n        });\n\n        ws.on(\"close\", function () {\n          if (sessions[pid] && sessions[pid].dataHandler) {\n            console.log(\"Terminal \" + pid + \" is running in the background.\");\n            sessions[pid].dataHandler?.dispose();\n            delete sessions[pid].dataHandler;\n            sessions[pid].terminalData = serializeAddon.serialize();\n          }\n        });\n      } catch (error) {\n        console.error(error);\n        ws.close();\n      }\n    }\n  );\n\n  app.post(\"/terminals/:pid/terminate\", async (c) => {\n    try {\n      const pid = parseInt(c.req.param(\"pid\"), 10);\n      const session = sessions[pid];\n\n      if (!session) {\n        // Session not found\n        console.error(`Session with PID ${pid} not found.`);\n        return c.json({ error: `Session with PID ${pid} not found.` }, 404);\n      }\n\n      const { term, xterm, serializeAddon } = session;\n\n      if (term) {\n        if (session.dataHandler) {\n          session.dataHandler?.dispose();\n          delete session.dataHandler;\n          if (session.terminalData) {\n            serializeAddon.dispose();\n            session.terminalData = serializeAddon.serialize();\n          }\n        }\n        // Kill the terminal\n        term.kill();\n        let res = await new Promise((resolve) => {\n          term.onExit(() => {\n            // Dispose of xterm and remove the session after the terminal exits\n            xterm.dispose();\n            delete sessions[pid];\n            console.log(\"Closed terminal \" + pid);\n            resolve(true);\n          });\n        });\n\n        if (!res) {\n          return c.json({ error: \"Something went wrong.\" }, 500);\n        }\n        return c.json({ success: true });\n      }\n    } catch (error) {\n      console.error(error);\n      return c.json({ error: \"Failed to terminate terminal\" }, 500);\n    }\n  });\n\n  app.post(\"/execute-command\", async (c, next) => {\n    try {\n      const env = { ...process.env };\n      const { command, u_cwd } = await c.req.json();\n      if (!command) {\n        return c.json({ error: \"Command is required.\" }, 400);\n      }\n      \n      const cwd = u_cwd ? u_cwd : process.env.HOME;\n\n      // Execute the command using node-pty\n      const term = pty.spawn(\n        process.platform === \"win32\" ? \"cmd.exe\" : \"bash\",\n        [\"-c\", command],\n        {\n          name: \"xterm-256color\",\n          cols: 80,\n          rows: 24,\n          cwd: process.platform === \"win32\" ? undefined : cwd,\n          env\n        }\n      );\n\n      const pattern = [\n        \"[\\\\u001B\\\\u009B][[\\\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\\\d\\\\/#&.:=?%@~_]+)*|[a-zA-Z\\\\d]+(?:;[-a-zA-Z\\\\d\\\\/#&.:=?%@~_]*)*)?\\\\u0007)\",\n        \"(?:(?:\\\\d{1,4}(?:;\\\\d{0,4})*)?[\\\\dA-PR-TZcf-nq-uy=><~]))\",\n      ].join(\"|\");\n      const ansiRegex = new RegExp(pattern, undefined);\n      const ansiRegex2 = new RegExp(pattern, \"g\");\n\n      let output = \"\";\n\n      // Listen for data events (output from the command)\n      term.onData((data) => {\n        output += data;\n      });\n\n      // Listen for the process to exit\n      let outputData = await new Promise((resolve) => {\n        term.onExit(() => {\n          // Send the parsed output back to the client\n          let outputData = ansiRegex.test(output)\n            ? output.replace(ansiRegex2, \"\")\n            : output;\n          resolve(outputData)\n        });\n      })\n      return c.json({ output: outputData })\n\n    } catch (error) {\n      console.error(error);\n      return c.json({ error: \"Failed to execute command\" }, 500);\n    }\n  });\n}\n"
  },
  {
    "path": "src/untitled.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n        <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" />\n        <title>Document</title>\n    </head>\n    <body>\n        <script type=\"text/javascript\" charset=\"utf-8\">\nconst executeCommand = async (command) => {\n  try {\n    const response = await fetch('http://localhost:8767/execute-command', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({command,u_cwd:\"/data/data/com.termux/files/home/acode-plugin-acodex\"}),\n    });\n\n    if (!response.ok) {\n      throw new Error(`Request failed with status: ${response.status}`);\n    }\n\n    const result = await response.json();\n    console.log('Output:', result.output);\n  } catch (error) {\n    console.error('Error:', error.message);\n  }\n};\n\n// Example usage\nconst commandToExecute = 'pwd';\nexecuteCommand(commandToExecute);\n\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"esModuleInterop\": true,\n        \"skipLibCheck\": true,\n        \"target\": \"es2022\",\n        \"strict\": true,\n        \"module\": \"CommonJS\",\n        \"moduleResolution\": \"node\",\n        \"outDir\": \"./dist\",\n        \"rootDir\": \"./src\"\n    },\n    \"exclude\": [\"./node_modules\"],\n    \"include\": [\"./src/**/*.ts\"]\n}\n"
  },
  {
    "path": "types/index.d.ts",
    "content": "import { IPty, IDisposable } from 'node-pty';\nimport { Terminal } from 'xterm-headless';\nimport { SerializeAddon } from 'xterm-addon-serialize';\n\ntype Session = {\n    term: IPty;\n    xterm: Terminal;\n    serializeAddon: SerializeAddon;\n    terminalData: string;\n    temporaryDisposable?: IDisposable;\n    dataHandler?: IDisposable;\n};"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require(\"path\");\n\nmodule.exports = {\n\tentry: \"./src/index.ts\",\n\ttarget: \"node\",\n\tmode: \"production\",\n\toutput: {\n\t\tpath: path.resolve(__dirname, \"dist\"),\n\t\tfilename: \"index.js\"\n\t},\n\tresolve: {\n\t\textensions: [\".ts\", \".js\"]\n\t},\n\tmodule: {\n\t\trules: [\n\t\t\t{\n\t\t\t\ttest: /\\.ts$/,\n\t\t\t\tuse: \"ts-loader\",\n\t\t\t\texclude: /node_modules/\n\t\t\t},\n\t\t\t{\n\t\t\t\ttest: /\\.node$/,\n\t\t\t\tloader: \"node-loader\"\n\t\t\t}\n\t\t]\n\t},\n\texternals: {\n\t\t\"node-pty\": \"commonjs node-pty\",\n\t\tws: \"commonjs ws\"\n\t}\n};\n"
  }
]