chaiterm 0.0.6 → 0.0.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.
- package/dist/bin/chaiterm.mjs +3 -1
- package/dist/bin/chaiterm.mjs.map +1 -1
- package/dist/{config-BUazL54Y.mjs → config-B-rjHUyy.mjs} +22 -5
- package/dist/config-B-rjHUyy.mjs.map +1 -0
- package/dist/index.d.mts +11 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -1
- package/dist/config-BUazL54Y.mjs.map +0 -1
package/dist/bin/chaiterm.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as PtyClient, n as setConfigPath, r as FrpWrapper, t as Config } from "../config-
|
|
2
|
+
import { i as PtyClient, n as setConfigPath, r as FrpWrapper, t as Config } from "../config-B-rjHUyy.mjs";
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import chalk from "chalk";
|
|
@@ -98,6 +98,8 @@ async function startCommand() {
|
|
|
98
98
|
email,
|
|
99
99
|
onConnect: () => {
|
|
100
100
|
console.log(chalk.green("✓ Connected to terminal gateway"));
|
|
101
|
+
console.log("");
|
|
102
|
+
console.log(` Terminal: ${chalk.cyan(client.getTerminalName())} ${chalk.gray(`(${client.getTerminalId()})`)}`);
|
|
101
103
|
console.log(chalk.cyan(`\n📱 Access your terminal at: https://chaiterm.com/terminals\n`));
|
|
102
104
|
console.log(chalk.gray("Press Ctrl+C to disconnect\n"));
|
|
103
105
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chaiterm.mjs","names":[],"sources":["../../src/commands/init.ts","../../src/commands/start.ts","../../src/commands/expose.ts","../../src/commands/status.ts","../../src/commands/logout.ts","../../src/commands/kill.ts","../../src/bin/chaiterm.ts"],"sourcesContent":["/**\n * Init command\n *\n * Sets up chaiterm with user's email.\n * Email is used to identify which terminal belongs to which user.\n *\n * Usage:\n * chaiterm init # prompts for email\n * chaiterm init user@example.com # sets email directly\n */\n\nimport chalk from \"chalk\";\nimport readline from \"readline\";\nimport { Config } from \"../lib/config.js\";\n\n/**\n * Prompt user for input\n */\nfunction prompt(question: string): Promise<string> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\n/**\n * Basic email validation\n */\nfunction isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n}\n\nexport async function initCommand(emailArg?: string): Promise<void> {\n const config = new Config();\n\n console.log(chalk.cyan(\"\\n🚀 Chaiterm Setup\\n\"));\n\n // Check if already configured\n const existingEmail = config.getEmail();\n if (existingEmail) {\n console.log(chalk.yellow(\"⚠️ Already configured.\"));\n console.log(` Email: ${existingEmail}`);\n console.log(` Config: ${config.getPath()}`);\n console.log(\n chalk.gray(\"\\n Run 'chaiterm logout' to reset configuration.\\n\")\n );\n return;\n }\n\n // Get email from argument or prompt\n let email = emailArg;\n\n if (!email) {\n console.log(\"Enter the email you use to sign in at chaiterm.com\\n\");\n email = await prompt(chalk.white(\" Email: \"));\n }\n\n // Validate email\n if (!email || !isValidEmail(email)) {\n console.log(chalk.red(\"\\n❌ Invalid email address.\\n\"));\n process.exit(1);\n }\n\n // Save email to config\n config.setEmail(email);\n\n console.log(chalk.green(\"\\n✓ Configuration saved!\\n\"));\n console.log(` Email: ${email}`);\n console.log(` Config: ${config.getPath()}`);\n console.log(chalk.cyan(\"\\n📋 Next steps:\"));\n console.log(\" 1. Sign up at https://chaiterm.com (if you haven't)\");\n console.log(\" 2. Run 'chaiterm start' to connect your terminal\\n\");\n}\n\n","/**\n * Start command\n *\n * Starts the terminal connection (pty-client).\n *\n * Note: Token is NOT needed for terminal access.\n * Security is enforced at browser connection via session cookie.\n */\n\nimport chalk from \"chalk\";\nimport { Config } from \"../lib/config.js\";\nimport { PtyClient } from \"../lib/pty-client.js\";\n\nexport async function startCommand(): Promise<void> {\n const config = new Config();\n\n console.log(chalk.cyan(\"\\n🖥️ Starting terminal connection...\\n\"));\n\n // Only email is required for terminal access (no token needed)\n const email = config.getEmail();\n if (!email) {\n console.log(chalk.red(\"❌ Not configured.\"));\n console.log(chalk.gray(\" Run 'chaiterm init' first.\\n\"));\n process.exit(1);\n }\n\n const gatewayUrl = config.getGatewayUrl();\n\n // Print configuration\n console.log(chalk.gray(\" Config: \") + config.getPath());\n if (config.isCustomConfig()) {\n console.log(chalk.gray(\" (custom config via --config flag)\"));\n }\n console.log(\"\");\n\n console.log(` Email: ${chalk.green(email)}`);\n console.log(` Gateway: ${chalk.gray(gatewayUrl)}`);\n console.log(\"\");\n\n const client = new PtyClient({\n gatewayUrl,\n email,\n onConnect: () => {\n console.log(chalk.green(\"✓ Connected to terminal gateway\"));\n console.log(\n chalk.cyan(`\\n📱 Access your terminal at: https://chaiterm.com/terminals\\n`)\n );\n console.log(chalk.gray(\"Press Ctrl+C to disconnect\\n\"));\n },\n onDisconnect: (code, reason) => {\n console.log(chalk.yellow(`\\n⚠️ Disconnected: ${reason} (code: ${code})`));\n },\n onError: (error) => {\n console.log(chalk.red(`\\n❌ Error: ${error.message}`));\n },\n });\n\n // Handle Ctrl+C\n process.on(\"SIGINT\", () => {\n console.log(chalk.gray(\"\\n\\nShutting down...\"));\n client.disconnect();\n process.exit(0);\n });\n\n process.on(\"SIGTERM\", () => {\n client.disconnect();\n process.exit(0);\n });\n\n try {\n await client.connect();\n\n // Keep process running\n await new Promise(() => {\n // Never resolves - keeps the process alive\n });\n } catch (error) {\n const err = error as Error;\n console.log(chalk.red(`\\n❌ Failed to connect: ${err.message}`));\n console.log(chalk.gray(\"\\nTroubleshooting:\"));\n console.log(\" 1. Check your internet connection\");\n console.log(\" 2. Try 'chaiterm init' to re-configure\\n\");\n process.exit(1);\n }\n}\n\n","/**\n * Expose command\n *\n * Exposes a local port to the internet via frp tunnel.\n */\n\nimport chalk from \"chalk\";\nimport { Config } from \"../lib/config.js\";\nimport { FrpWrapper } from \"../lib/frp-wrapper.js\";\n\nexport async function exposeCommand(port: string): Promise<void> {\n const config = new Config();\n const localPort = parseInt(port, 10);\n\n if (isNaN(localPort) || localPort < 1 || localPort > 65535) {\n console.log(chalk.red(`\\n❌ Invalid port: ${port}`));\n console.log(chalk.gray(\" Port must be a number between 1 and 65535\\n\"));\n process.exit(1);\n }\n\n console.log(chalk.cyan(`\\n🌐 Exposing port ${localPort}...\\n`));\n\n if (!config.isAuthenticated()) {\n console.log(chalk.red(\"❌ Not authenticated.\"));\n console.log(chalk.gray(\" Run 'chaiterm init' first.\\n\"));\n process.exit(1);\n }\n\n const userId = config.getUserId()!;\n const token = config.getToken()!;\n const serverAddr = config.getFrpServerAddr();\n const serverPort = config.getFrpServerPort();\n\n // TODO: Request subdomain from server API\n // For now, generate a placeholder random ID\n const subdomainId = generateSubdomainId();\n\n console.log(` User: ${chalk.green(userId)}`);\n console.log(` Local port: ${chalk.green(localPort)}`);\n console.log(` Server: ${chalk.gray(`${serverAddr}:${serverPort}`)}`);\n console.log(\"\");\n\n // Add subdomain to config\n config.addSubdomain({\n id: subdomainId,\n localPort,\n active: true,\n });\n\n const frp = new FrpWrapper({\n serverAddr,\n serverPort,\n userId,\n token,\n subdomains: config.getSubdomains(),\n onConnect: () => {\n console.log(chalk.green(\"✓ Tunnel established\"));\n console.log(\n chalk.cyan(`\\n🔗 Your app is available at: https://${subdomainId}.chaiterm.com\\n`)\n );\n console.log(chalk.gray(\"Press Ctrl+C to stop\\n\"));\n },\n onDisconnect: () => {\n console.log(chalk.yellow(\"\\n⚠️ Tunnel disconnected\"));\n },\n onError: (error) => {\n console.log(chalk.red(`\\n❌ Error: ${error.message}`));\n },\n onLog: (message) => {\n // Only show important logs\n if (message.includes(\"error\") || message.includes(\"success\")) {\n console.log(chalk.gray(`[frp] ${message.trim()}`));\n }\n },\n });\n\n // Handle Ctrl+C\n process.on(\"SIGINT\", () => {\n console.log(chalk.gray(\"\\n\\nShutting down tunnel...\"));\n frp.stop();\n // Remove subdomain from config\n config.removeSubdomain(subdomainId);\n process.exit(0);\n });\n\n process.on(\"SIGTERM\", () => {\n frp.stop();\n config.removeSubdomain(subdomainId);\n process.exit(0);\n });\n\n try {\n await frp.start();\n\n // Keep process running\n await new Promise(() => {\n // Never resolves - keeps the process alive\n });\n } catch (error) {\n const err = error as Error;\n console.log(chalk.red(`\\n❌ Failed to establish tunnel: ${err.message}`));\n console.log(chalk.gray(\"\\nTroubleshooting:\"));\n console.log(\" 1. Ensure frpc is installed (brew install frpc)\");\n console.log(\" 2. Check that the local port is accessible\");\n console.log(\" 3. Verify your network allows outbound connections\\n\");\n // Remove subdomain from config on failure\n config.removeSubdomain(subdomainId);\n process.exit(1);\n }\n}\n\n/**\n * Generate a random subdomain ID\n * In production, this would come from the server\n */\nfunction generateSubdomainId(): string {\n const chars = \"abcdefghijklmnopqrstuvwxyz0123456789\";\n let result = \"\";\n for (let i = 0; i < 6; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n}\n\n","/**\n * Status command\n *\n * Shows current connection status and configuration.\n */\n\nimport chalk from \"chalk\";\nimport { Config } from \"../lib/config.js\";\n\nexport async function statusCommand(): Promise<void> {\n const config = new Config();\n\n console.log(chalk.cyan(\"\\n📊 Chaiterm Status\\n\"));\n\n // Configuration status\n const email = config.getEmail();\n if (email) {\n console.log(chalk.green(\"✓ Configured\"));\n console.log(` Email: ${email}`);\n } else {\n console.log(chalk.yellow(\"○ Not configured\"));\n console.log(chalk.gray(\" Run 'chaiterm init' to set up\"));\n }\n\n console.log(\"\");\n\n // Gateway configuration\n console.log(chalk.gray(\"Configuration:\"));\n console.log(` Gateway: ${config.getGatewayUrl()}`);\n console.log(` Config file: ${config.getPath()}`);\n\n console.log(\"\");\n\n // Help\n console.log(chalk.gray(\"Commands:\"));\n console.log(\" chaiterm start - Start terminal connection\");\n console.log(\" chaiterm logout - Clear configuration\");\n console.log(\"\");\n}\n\n","/**\n * Logout command\n *\n * Clears stored configuration.\n */\n\nimport chalk from \"chalk\";\nimport { Config } from \"../lib/config.js\";\n\nexport async function logoutCommand(): Promise<void> {\n const config = new Config();\n\n console.log(chalk.cyan(\"\\n🔒 Clearing configuration...\\n\"));\n\n const email = config.getEmail();\n if (!email) {\n console.log(chalk.yellow(\"⚠️ Not configured.\\n\"));\n return;\n }\n\n config.clear();\n\n console.log(chalk.green(\"✓ Configuration cleared\"));\n console.log(` Previous email: ${email}`);\n console.log(chalk.gray(\"\\n Run 'chaiterm init' to configure again.\\n\"));\n}\n\n","/**\n * Kill command\n *\n * Terminates any running chaiterm processes (node-pty instances).\n */\n\nimport chalk from \"chalk\";\nimport { execSync } from \"child_process\";\n\nexport async function killCommand(): Promise<void> {\n console.log(chalk.cyan(\"\\n🔍 Looking for running chaiterm processes...\\n\"));\n\n try {\n // Find chaiterm processes\n // Look for: node processes running chaiterm, or processes with node-pty\n const patterns = [\n \"chaiterm.mjs\",\n \"chaiterm.js\",\n \"pty-client\",\n \"node-pty\",\n ];\n\n let foundAny = false;\n\n for (const pattern of patterns) {\n try {\n // Use pgrep to find matching processes (excluding the current process)\n const result = execSync(\n `pgrep -f \"${pattern}\" | grep -v \"^${process.pid}$\" || true`,\n { encoding: \"utf-8\" }\n ).trim();\n\n if (result) {\n const pids = result.split(\"\\n\").filter(Boolean);\n for (const pid of pids) {\n try {\n // Get process info before killing\n const processInfo = execSync(`ps -p ${pid} -o command= 2>/dev/null || true`, {\n encoding: \"utf-8\",\n }).trim();\n\n if (processInfo && processInfo.includes(\"chaiterm\")) {\n process.kill(parseInt(pid, 10), \"SIGTERM\");\n console.log(chalk.yellow(` Killed PID ${pid}: ${processInfo.substring(0, 60)}...`));\n foundAny = true;\n }\n } catch {\n // Process might have already exited\n }\n }\n }\n } catch {\n // pgrep might not find anything, that's OK\n }\n }\n\n // Also try pkill as a fallback for any remaining chaiterm processes\n try {\n execSync('pkill -f \"chaiterm.mjs\" 2>/dev/null || true');\n execSync('pkill -f \"chaiterm start\" 2>/dev/null || true');\n } catch {\n // Ignore errors\n }\n\n if (foundAny) {\n console.log(chalk.green(\"\\n✓ Killed chaiterm processes\\n\"));\n } else {\n console.log(chalk.gray(\" No running chaiterm processes found.\\n\"));\n }\n } catch (error) {\n const err = error as Error;\n console.log(chalk.red(`\\n❌ Error: ${err.message}\\n`));\n process.exit(1);\n }\n}\n","#!/usr/bin/env node\n/**\n * Chaiterm CLI\n *\n * Main entry point for the chaiterm command line tool.\n *\n * Use --config to specify a custom config file:\n * chaiterm --config ./local-config.json start\n * chaiterm -c ~/.config/chaiterm/remote.json status\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { createRequire } from \"module\";\nimport { setConfigPath, isUsingCustomConfig, getCustomConfigPath } from \"../lib/config.js\";\nimport { initCommand } from \"../commands/init.js\";\nimport { startCommand } from \"../commands/start.js\";\nimport { exposeCommand } from \"../commands/expose.js\";\nimport { statusCommand } from \"../commands/status.js\";\nimport { logoutCommand } from \"../commands/logout.js\";\nimport { killCommand } from \"../commands/kill.js\";\n\n// Read version from package.json\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../../package.json\");\n\nconst program = new Command();\n\nprogram\n .name(\"chaiterm\")\n .description(\"Access your terminal from anywhere\")\n .version(pkg.version)\n .option(\"-c, --config <path>\", \"Path to config file (default: ~/.config/chaiterm/config.json)\")\n .hook(\"preAction\", (thisCommand) => {\n const opts = thisCommand.opts();\n if (opts.config) {\n setConfigPath(opts.config);\n }\n });\n\nprogram\n .command(\"init\")\n .description(\"Initialize chaiterm with your email\")\n .argument(\"[email]\", \"Your email (will prompt if not provided)\")\n .action(initCommand);\n\nprogram\n .command(\"start\")\n .description(\"Start terminal access (connect pty-client to gateway)\")\n .action(startCommand);\n\nprogram\n .command(\"expose\")\n .description(\"Expose a local port to the internet\")\n .argument(\"<port>\", \"Local port to expose\")\n .action(exposeCommand);\n\nprogram\n .command(\"status\")\n .description(\"Show connection status\")\n .action(statusCommand);\n\nprogram\n .command(\"logout\")\n .description(\"Clear credentials and logout\")\n .action(logoutCommand);\n\nprogram\n .command(\"kill\")\n .description(\"Kill all running chaiterm processes\")\n .action(killCommand);\n\n// Add some helpful output on errors\nprogram.showHelpAfterError();\n\n// Parse arguments\nprogram.parse();\n\n// If no command provided, show help\nif (!process.argv.slice(2).length) {\n console.log(chalk.cyan(\"\\n chaiterm - Access your terminal from anywhere\\n\"));\n program.outputHelp();\n}\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chaiterm.mjs","names":[],"sources":["../../src/commands/init.ts","../../src/commands/start.ts","../../src/commands/expose.ts","../../src/commands/status.ts","../../src/commands/logout.ts","../../src/commands/kill.ts","../../src/bin/chaiterm.ts"],"sourcesContent":["/**\n * Init command\n *\n * Sets up chaiterm with user's email.\n * Email is used to identify which terminal belongs to which user.\n *\n * Usage:\n * chaiterm init # prompts for email\n * chaiterm init user@example.com # sets email directly\n */\n\nimport chalk from \"chalk\";\nimport readline from \"readline\";\nimport { Config } from \"../lib/config.js\";\n\n/**\n * Prompt user for input\n */\nfunction prompt(question: string): Promise<string> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\n/**\n * Basic email validation\n */\nfunction isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n}\n\nexport async function initCommand(emailArg?: string): Promise<void> {\n const config = new Config();\n\n console.log(chalk.cyan(\"\\n🚀 Chaiterm Setup\\n\"));\n\n // Check if already configured\n const existingEmail = config.getEmail();\n if (existingEmail) {\n console.log(chalk.yellow(\"⚠️ Already configured.\"));\n console.log(` Email: ${existingEmail}`);\n console.log(` Config: ${config.getPath()}`);\n console.log(\n chalk.gray(\"\\n Run 'chaiterm logout' to reset configuration.\\n\")\n );\n return;\n }\n\n // Get email from argument or prompt\n let email = emailArg;\n\n if (!email) {\n console.log(\"Enter the email you use to sign in at chaiterm.com\\n\");\n email = await prompt(chalk.white(\" Email: \"));\n }\n\n // Validate email\n if (!email || !isValidEmail(email)) {\n console.log(chalk.red(\"\\n❌ Invalid email address.\\n\"));\n process.exit(1);\n }\n\n // Save email to config\n config.setEmail(email);\n\n console.log(chalk.green(\"\\n✓ Configuration saved!\\n\"));\n console.log(` Email: ${email}`);\n console.log(` Config: ${config.getPath()}`);\n console.log(chalk.cyan(\"\\n📋 Next steps:\"));\n console.log(\" 1. Sign up at https://chaiterm.com (if you haven't)\");\n console.log(\" 2. Run 'chaiterm start' to connect your terminal\\n\");\n}\n\n","/**\n * Start command\n *\n * Starts the terminal connection (pty-client).\n *\n * Note: Token is NOT needed for terminal access.\n * Security is enforced at browser connection via session cookie.\n */\n\nimport chalk from \"chalk\";\nimport { Config } from \"../lib/config.js\";\nimport { PtyClient } from \"../lib/pty-client.js\";\n\nexport async function startCommand(): Promise<void> {\n const config = new Config();\n\n console.log(chalk.cyan(\"\\n🖥️ Starting terminal connection...\\n\"));\n\n // Only email is required for terminal access (no token needed)\n const email = config.getEmail();\n if (!email) {\n console.log(chalk.red(\"❌ Not configured.\"));\n console.log(chalk.gray(\" Run 'chaiterm init' first.\\n\"));\n process.exit(1);\n }\n\n const gatewayUrl = config.getGatewayUrl();\n\n // Print configuration\n console.log(chalk.gray(\" Config: \") + config.getPath());\n if (config.isCustomConfig()) {\n console.log(chalk.gray(\" (custom config via --config flag)\"));\n }\n console.log(\"\");\n\n console.log(` Email: ${chalk.green(email)}`);\n console.log(` Gateway: ${chalk.gray(gatewayUrl)}`);\n console.log(\"\");\n\n const client = new PtyClient({\n gatewayUrl,\n email,\n onConnect: () => {\n console.log(chalk.green(\"✓ Connected to terminal gateway\"));\n console.log(\"\");\n console.log(` Terminal: ${chalk.cyan(client.getTerminalName())} ${chalk.gray(`(${client.getTerminalId()})`)}`);\n console.log(\n chalk.cyan(`\\n📱 Access your terminal at: https://chaiterm.com/terminals\\n`)\n );\n console.log(chalk.gray(\"Press Ctrl+C to disconnect\\n\"));\n },\n onDisconnect: (code, reason) => {\n console.log(chalk.yellow(`\\n⚠️ Disconnected: ${reason} (code: ${code})`));\n },\n onError: (error) => {\n console.log(chalk.red(`\\n❌ Error: ${error.message}`));\n },\n });\n\n // Handle Ctrl+C\n process.on(\"SIGINT\", () => {\n console.log(chalk.gray(\"\\n\\nShutting down...\"));\n client.disconnect();\n process.exit(0);\n });\n\n process.on(\"SIGTERM\", () => {\n client.disconnect();\n process.exit(0);\n });\n\n try {\n await client.connect();\n\n // Keep process running\n await new Promise(() => {\n // Never resolves - keeps the process alive\n });\n } catch (error) {\n const err = error as Error;\n console.log(chalk.red(`\\n❌ Failed to connect: ${err.message}`));\n console.log(chalk.gray(\"\\nTroubleshooting:\"));\n console.log(\" 1. Check your internet connection\");\n console.log(\" 2. Try 'chaiterm init' to re-configure\\n\");\n process.exit(1);\n }\n}\n\n","/**\n * Expose command\n *\n * Exposes a local port to the internet via frp tunnel.\n */\n\nimport chalk from \"chalk\";\nimport { Config } from \"../lib/config.js\";\nimport { FrpWrapper } from \"../lib/frp-wrapper.js\";\n\nexport async function exposeCommand(port: string): Promise<void> {\n const config = new Config();\n const localPort = parseInt(port, 10);\n\n if (isNaN(localPort) || localPort < 1 || localPort > 65535) {\n console.log(chalk.red(`\\n❌ Invalid port: ${port}`));\n console.log(chalk.gray(\" Port must be a number between 1 and 65535\\n\"));\n process.exit(1);\n }\n\n console.log(chalk.cyan(`\\n🌐 Exposing port ${localPort}...\\n`));\n\n if (!config.isAuthenticated()) {\n console.log(chalk.red(\"❌ Not authenticated.\"));\n console.log(chalk.gray(\" Run 'chaiterm init' first.\\n\"));\n process.exit(1);\n }\n\n const userId = config.getUserId()!;\n const token = config.getToken()!;\n const serverAddr = config.getFrpServerAddr();\n const serverPort = config.getFrpServerPort();\n\n // TODO: Request subdomain from server API\n // For now, generate a placeholder random ID\n const subdomainId = generateSubdomainId();\n\n console.log(` User: ${chalk.green(userId)}`);\n console.log(` Local port: ${chalk.green(localPort)}`);\n console.log(` Server: ${chalk.gray(`${serverAddr}:${serverPort}`)}`);\n console.log(\"\");\n\n // Add subdomain to config\n config.addSubdomain({\n id: subdomainId,\n localPort,\n active: true,\n });\n\n const frp = new FrpWrapper({\n serverAddr,\n serverPort,\n userId,\n token,\n subdomains: config.getSubdomains(),\n onConnect: () => {\n console.log(chalk.green(\"✓ Tunnel established\"));\n console.log(\n chalk.cyan(`\\n🔗 Your app is available at: https://${subdomainId}.chaiterm.com\\n`)\n );\n console.log(chalk.gray(\"Press Ctrl+C to stop\\n\"));\n },\n onDisconnect: () => {\n console.log(chalk.yellow(\"\\n⚠️ Tunnel disconnected\"));\n },\n onError: (error) => {\n console.log(chalk.red(`\\n❌ Error: ${error.message}`));\n },\n onLog: (message) => {\n // Only show important logs\n if (message.includes(\"error\") || message.includes(\"success\")) {\n console.log(chalk.gray(`[frp] ${message.trim()}`));\n }\n },\n });\n\n // Handle Ctrl+C\n process.on(\"SIGINT\", () => {\n console.log(chalk.gray(\"\\n\\nShutting down tunnel...\"));\n frp.stop();\n // Remove subdomain from config\n config.removeSubdomain(subdomainId);\n process.exit(0);\n });\n\n process.on(\"SIGTERM\", () => {\n frp.stop();\n config.removeSubdomain(subdomainId);\n process.exit(0);\n });\n\n try {\n await frp.start();\n\n // Keep process running\n await new Promise(() => {\n // Never resolves - keeps the process alive\n });\n } catch (error) {\n const err = error as Error;\n console.log(chalk.red(`\\n❌ Failed to establish tunnel: ${err.message}`));\n console.log(chalk.gray(\"\\nTroubleshooting:\"));\n console.log(\" 1. Ensure frpc is installed (brew install frpc)\");\n console.log(\" 2. Check that the local port is accessible\");\n console.log(\" 3. Verify your network allows outbound connections\\n\");\n // Remove subdomain from config on failure\n config.removeSubdomain(subdomainId);\n process.exit(1);\n }\n}\n\n/**\n * Generate a random subdomain ID\n * In production, this would come from the server\n */\nfunction generateSubdomainId(): string {\n const chars = \"abcdefghijklmnopqrstuvwxyz0123456789\";\n let result = \"\";\n for (let i = 0; i < 6; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n}\n\n","/**\n * Status command\n *\n * Shows current connection status and configuration.\n */\n\nimport chalk from \"chalk\";\nimport { Config } from \"../lib/config.js\";\n\nexport async function statusCommand(): Promise<void> {\n const config = new Config();\n\n console.log(chalk.cyan(\"\\n📊 Chaiterm Status\\n\"));\n\n // Configuration status\n const email = config.getEmail();\n if (email) {\n console.log(chalk.green(\"✓ Configured\"));\n console.log(` Email: ${email}`);\n } else {\n console.log(chalk.yellow(\"○ Not configured\"));\n console.log(chalk.gray(\" Run 'chaiterm init' to set up\"));\n }\n\n console.log(\"\");\n\n // Gateway configuration\n console.log(chalk.gray(\"Configuration:\"));\n console.log(` Gateway: ${config.getGatewayUrl()}`);\n console.log(` Config file: ${config.getPath()}`);\n\n console.log(\"\");\n\n // Help\n console.log(chalk.gray(\"Commands:\"));\n console.log(\" chaiterm start - Start terminal connection\");\n console.log(\" chaiterm logout - Clear configuration\");\n console.log(\"\");\n}\n\n","/**\n * Logout command\n *\n * Clears stored configuration.\n */\n\nimport chalk from \"chalk\";\nimport { Config } from \"../lib/config.js\";\n\nexport async function logoutCommand(): Promise<void> {\n const config = new Config();\n\n console.log(chalk.cyan(\"\\n🔒 Clearing configuration...\\n\"));\n\n const email = config.getEmail();\n if (!email) {\n console.log(chalk.yellow(\"⚠️ Not configured.\\n\"));\n return;\n }\n\n config.clear();\n\n console.log(chalk.green(\"✓ Configuration cleared\"));\n console.log(` Previous email: ${email}`);\n console.log(chalk.gray(\"\\n Run 'chaiterm init' to configure again.\\n\"));\n}\n\n","/**\n * Kill command\n *\n * Terminates any running chaiterm processes (node-pty instances).\n */\n\nimport chalk from \"chalk\";\nimport { execSync } from \"child_process\";\n\nexport async function killCommand(): Promise<void> {\n console.log(chalk.cyan(\"\\n🔍 Looking for running chaiterm processes...\\n\"));\n\n try {\n // Find chaiterm processes\n // Look for: node processes running chaiterm, or processes with node-pty\n const patterns = [\n \"chaiterm.mjs\",\n \"chaiterm.js\",\n \"pty-client\",\n \"node-pty\",\n ];\n\n let foundAny = false;\n\n for (const pattern of patterns) {\n try {\n // Use pgrep to find matching processes (excluding the current process)\n const result = execSync(\n `pgrep -f \"${pattern}\" | grep -v \"^${process.pid}$\" || true`,\n { encoding: \"utf-8\" }\n ).trim();\n\n if (result) {\n const pids = result.split(\"\\n\").filter(Boolean);\n for (const pid of pids) {\n try {\n // Get process info before killing\n const processInfo = execSync(`ps -p ${pid} -o command= 2>/dev/null || true`, {\n encoding: \"utf-8\",\n }).trim();\n\n if (processInfo && processInfo.includes(\"chaiterm\")) {\n process.kill(parseInt(pid, 10), \"SIGTERM\");\n console.log(chalk.yellow(` Killed PID ${pid}: ${processInfo.substring(0, 60)}...`));\n foundAny = true;\n }\n } catch {\n // Process might have already exited\n }\n }\n }\n } catch {\n // pgrep might not find anything, that's OK\n }\n }\n\n // Also try pkill as a fallback for any remaining chaiterm processes\n try {\n execSync('pkill -f \"chaiterm.mjs\" 2>/dev/null || true');\n execSync('pkill -f \"chaiterm start\" 2>/dev/null || true');\n } catch {\n // Ignore errors\n }\n\n if (foundAny) {\n console.log(chalk.green(\"\\n✓ Killed chaiterm processes\\n\"));\n } else {\n console.log(chalk.gray(\" No running chaiterm processes found.\\n\"));\n }\n } catch (error) {\n const err = error as Error;\n console.log(chalk.red(`\\n❌ Error: ${err.message}\\n`));\n process.exit(1);\n }\n}\n","#!/usr/bin/env node\n/**\n * Chaiterm CLI\n *\n * Main entry point for the chaiterm command line tool.\n *\n * Use --config to specify a custom config file:\n * chaiterm --config ./local-config.json start\n * chaiterm -c ~/.config/chaiterm/remote.json status\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { createRequire } from \"module\";\nimport { setConfigPath, isUsingCustomConfig, getCustomConfigPath } from \"../lib/config.js\";\nimport { initCommand } from \"../commands/init.js\";\nimport { startCommand } from \"../commands/start.js\";\nimport { exposeCommand } from \"../commands/expose.js\";\nimport { statusCommand } from \"../commands/status.js\";\nimport { logoutCommand } from \"../commands/logout.js\";\nimport { killCommand } from \"../commands/kill.js\";\n\n// Read version from package.json\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../../package.json\");\n\nconst program = new Command();\n\nprogram\n .name(\"chaiterm\")\n .description(\"Access your terminal from anywhere\")\n .version(pkg.version)\n .option(\"-c, --config <path>\", \"Path to config file (default: ~/.config/chaiterm/config.json)\")\n .hook(\"preAction\", (thisCommand) => {\n const opts = thisCommand.opts();\n if (opts.config) {\n setConfigPath(opts.config);\n }\n });\n\nprogram\n .command(\"init\")\n .description(\"Initialize chaiterm with your email\")\n .argument(\"[email]\", \"Your email (will prompt if not provided)\")\n .action(initCommand);\n\nprogram\n .command(\"start\")\n .description(\"Start terminal access (connect pty-client to gateway)\")\n .action(startCommand);\n\nprogram\n .command(\"expose\")\n .description(\"Expose a local port to the internet\")\n .argument(\"<port>\", \"Local port to expose\")\n .action(exposeCommand);\n\nprogram\n .command(\"status\")\n .description(\"Show connection status\")\n .action(statusCommand);\n\nprogram\n .command(\"logout\")\n .description(\"Clear credentials and logout\")\n .action(logoutCommand);\n\nprogram\n .command(\"kill\")\n .description(\"Kill all running chaiterm processes\")\n .action(killCommand);\n\n// Add some helpful output on errors\nprogram.showHelpAfterError();\n\n// Parse arguments\nprogram.parse();\n\n// If no command provided, show help\nif (!process.argv.slice(2).length) {\n console.log(chalk.cyan(\"\\n chaiterm - Access your terminal from anywhere\\n\"));\n program.outputHelp();\n}\n\n"],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
|
+
import os, { tmpdir } from "os";
|
|
3
|
+
import { nanoid } from "nanoid";
|
|
2
4
|
import { spawn } from "child_process";
|
|
3
5
|
import fs, { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
4
6
|
import path, { join } from "path";
|
|
5
|
-
import { tmpdir } from "os";
|
|
6
7
|
import Conf from "conf";
|
|
7
8
|
|
|
8
9
|
//#region src/lib/pty-client.ts
|
|
@@ -14,7 +15,7 @@ import Conf from "conf";
|
|
|
14
15
|
*
|
|
15
16
|
* Security Model:
|
|
16
17
|
* - No token needed for terminal access (security is at browser via session cookie)
|
|
17
|
-
* - CLI
|
|
18
|
+
* - CLI registers with email and unique terminalId
|
|
18
19
|
* - Token is only used for port exposure (frp tunnels)
|
|
19
20
|
*/
|
|
20
21
|
var PtyClient = class {
|
|
@@ -23,15 +24,31 @@ var PtyClient = class {
|
|
|
23
24
|
options;
|
|
24
25
|
reconnectTimeout = null;
|
|
25
26
|
shouldReconnect = true;
|
|
27
|
+
terminalId;
|
|
28
|
+
terminalName;
|
|
26
29
|
constructor(options) {
|
|
27
30
|
this.options = options;
|
|
31
|
+
this.terminalId = nanoid(10);
|
|
32
|
+
this.terminalName = os.hostname();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the terminal ID for this session
|
|
36
|
+
*/
|
|
37
|
+
getTerminalId() {
|
|
38
|
+
return this.terminalId;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get the terminal name (hostname) for this session
|
|
42
|
+
*/
|
|
43
|
+
getTerminalName() {
|
|
44
|
+
return this.terminalName;
|
|
28
45
|
}
|
|
29
46
|
/**
|
|
30
47
|
* Connect to the terminal gateway
|
|
31
48
|
*/
|
|
32
49
|
async connect() {
|
|
33
50
|
const { gatewayUrl, email } = this.options;
|
|
34
|
-
const url = `${gatewayUrl}/register?email=${encodeURIComponent(email)}`;
|
|
51
|
+
const url = `${gatewayUrl}/register?email=${encodeURIComponent(email)}&terminalId=${this.terminalId}&name=${encodeURIComponent(this.terminalName)}`;
|
|
35
52
|
console.log(`[pty-client] Connecting to gateway...`);
|
|
36
53
|
return new Promise((resolve, reject) => {
|
|
37
54
|
this.ws = new WebSocket(url);
|
|
@@ -95,7 +112,7 @@ var PtyClient = class {
|
|
|
95
112
|
}
|
|
96
113
|
async openPty(cols, rows) {
|
|
97
114
|
if (this.pty) {
|
|
98
|
-
console.log(`[pty-client] PTY already exists,
|
|
115
|
+
console.log(`[pty-client] PTY already exists, resizing to ${cols}x${rows}`);
|
|
99
116
|
try {
|
|
100
117
|
this.pty.resize(cols || 80, rows || 24);
|
|
101
118
|
} catch {}
|
|
@@ -462,4 +479,4 @@ var Config = class {
|
|
|
462
479
|
|
|
463
480
|
//#endregion
|
|
464
481
|
export { PtyClient as i, setConfigPath as n, FrpWrapper as r, Config as t };
|
|
465
|
-
//# sourceMappingURL=config-
|
|
482
|
+
//# sourceMappingURL=config-B-rjHUyy.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-B-rjHUyy.mjs","names":["msg: TerminalMessage","path","customConfigPath: string | null","CONFIG_DEFAULTS: Partial<ChaitermConfig>"],"sources":["../src/lib/pty-client.ts","../src/lib/frp-wrapper.ts","../src/lib/config.ts"],"sourcesContent":["/**\n * PTY Client\n *\n * Connects to the terminal gateway and spawns a local PTY.\n * This is the core of terminal access functionality.\n *\n * Security Model:\n * - No token needed for terminal access (security is at browser via session cookie)\n * - CLI registers with email and unique terminalId\n * - Token is only used for port exposure (frp tunnels)\n */\n\nimport WebSocket from \"ws\";\nimport os from \"os\";\nimport { nanoid } from \"nanoid\";\nimport type { IPty } from \"node-pty\";\nimport type { TerminalMessage } from \"../types.js\";\n\nexport interface PtyClientOptions {\n gatewayUrl: string;\n email: string;\n // Note: token is NOT needed for terminal access, only for port exposure\n onConnect?: () => void;\n onDisconnect?: (code: number, reason: string) => void;\n onError?: (error: Error) => void;\n}\n\nexport class PtyClient {\n private ws: WebSocket | null = null;\n private pty: IPty | null = null;\n private options: PtyClientOptions;\n private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;\n private shouldReconnect = true;\n private terminalId: string;\n private terminalName: string;\n\n constructor(options: PtyClientOptions) {\n this.options = options;\n // Generate unique terminal ID and name at construction time\n this.terminalId = nanoid(10);\n this.terminalName = os.hostname();\n }\n\n /**\n * Get the terminal ID for this session\n */\n getTerminalId(): string {\n return this.terminalId;\n }\n\n /**\n * Get the terminal name (hostname) for this session\n */\n getTerminalName(): string {\n return this.terminalName;\n }\n\n /**\n * Connect to the terminal gateway\n */\n async connect(): Promise<void> {\n const { gatewayUrl, email } = this.options;\n // Include terminalId and name in registration URL\n const url = `${gatewayUrl}/register?email=${encodeURIComponent(email)}&terminalId=${this.terminalId}&name=${encodeURIComponent(this.terminalName)}`;\n\n console.log(`[pty-client] Connecting to gateway...`);\n\n return new Promise((resolve, reject) => {\n this.ws = new WebSocket(url);\n\n this.ws.on(\"open\", () => {\n console.log(\"[pty-client] Connected to gateway\");\n this.options.onConnect?.();\n resolve();\n });\n\n this.ws.on(\"message\", (raw) => {\n this.handleMessage(raw.toString());\n });\n\n this.ws.on(\"close\", (code, reason) => {\n console.log(\n `[pty-client] Disconnected: code=${code}, reason=${reason}`\n );\n this.cleanup();\n this.options.onDisconnect?.(code, reason.toString());\n this.scheduleReconnect();\n });\n\n this.ws.on(\"error\", (err) => {\n console.error(`[pty-client] Error: ${err.message}`);\n this.options.onError?.(err);\n reject(err);\n });\n });\n }\n\n /**\n * Disconnect from gateway\n */\n disconnect(): void {\n this.shouldReconnect = false;\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n this.cleanup();\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n }\n\n private handleMessage(raw: string): void {\n let msg: TerminalMessage;\n try {\n msg = JSON.parse(raw);\n } catch {\n return;\n }\n\n switch (msg.kind) {\n case \"open\":\n this.openPty(msg.cols, msg.rows);\n break;\n case \"data\":\n this.pty?.write(msg.data);\n break;\n case \"resize\":\n if (this.pty && msg.cols && msg.rows) {\n this.pty.resize(msg.cols, msg.rows);\n }\n break;\n case \"close\":\n this.closePty();\n break;\n }\n }\n\n private async openPty(cols: number, rows: number): Promise<void> {\n // Each terminal session gets its own PTY\n // If PTY already exists, just resize it (for browser reconnects to same terminal)\n if (this.pty) {\n console.log(`[pty-client] PTY already exists, resizing to ${cols}x${rows}`);\n try {\n this.pty.resize(cols || 80, rows || 24);\n } catch {\n // Ignore resize errors\n }\n this.send({ kind: \"ready\", pid: this.pty.pid });\n return;\n }\n\n // Dynamic import for node-pty (native module)\n const nodePty = await import(\"node-pty\");\n\n const shell = process.env.SHELL || \"/bin/bash\";\n const cwd = process.env.HOME || process.cwd();\n\n console.log(`[pty-client] Opening NEW PTY: ${cols}x${rows}`);\n console.log(`[pty-client] Shell: ${shell}, CWD: ${cwd}`);\n\n try {\n this.pty = nodePty.spawn(shell, [], {\n name: \"xterm-256color\",\n cols: cols || 80,\n rows: rows || 24,\n cwd: cwd,\n env: process.env as Record<string, string>,\n });\n\n // Send ready message\n this.send({ kind: \"ready\", pid: this.pty.pid });\n\n // Forward PTY output to gateway\n this.pty.onData((data: string) => {\n this.send({ kind: \"data\", data });\n });\n\n // Handle PTY exit\n this.pty.onExit(({ exitCode, signal }) => {\n console.log(\n `[pty-client] PTY exited: code=${exitCode}, signal=${signal}`\n );\n this.send({ kind: \"exit\", code: exitCode, signal });\n this.pty = null;\n });\n } catch (err) {\n const error = err as Error;\n console.error(`[pty-client] Failed to spawn PTY: ${error.message}`);\n\n // Detect spawn-helper permissions issue\n let errorMessage = `\\r\\nError: Failed to spawn shell: ${error.message}\\r\\n`;\n if (\n error.message.includes(\"posix_spawnp\") &&\n process.platform !== \"win32\"\n ) {\n errorMessage += `\\r\\n`;\n errorMessage += `This is likely caused by missing execute permissions on node-pty's spawn-helper.\\r\\n`;\n errorMessage += `\\r\\n`;\n errorMessage += `To fix this, run:\\r\\n`;\n errorMessage += ` npm rebuild node-pty\\r\\n`;\n errorMessage += `\\r\\n`;\n errorMessage += `Or manually fix permissions:\\r\\n`;\n errorMessage += ` chmod +x node_modules/node-pty/prebuilds/*/spawn-helper\\r\\n`;\n errorMessage += `\\r\\n`;\n }\n\n this.send({\n kind: \"data\",\n data: errorMessage,\n });\n }\n }\n\n private closePty(): void {\n if (this.pty) {\n console.log(\"[pty-client] Closing PTY\");\n try {\n this.pty.kill();\n } catch {\n // Ignore errors\n }\n this.pty = null;\n }\n }\n\n private send(msg: TerminalMessage): void {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n }\n\n private cleanup(): void {\n this.closePty();\n }\n\n private scheduleReconnect(): void {\n if (!this.shouldReconnect || this.reconnectTimeout) return;\n\n console.log(\"[pty-client] Reconnecting in 3 seconds...\");\n this.reconnectTimeout = setTimeout(() => {\n this.reconnectTimeout = null;\n this.connect().catch(() => {\n // Will retry on next disconnect\n });\n }, 3000);\n }\n}\n","/**\n * frp Wrapper\n *\n * Wraps the frpc binary for port exposure functionality.\n * Generates config and spawns frpc process.\n */\n\nimport { spawn, type ChildProcess } from \"child_process\";\nimport { writeFileSync, mkdirSync, existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { tmpdir } from \"os\";\nimport type { SubdomainConfig } from \"../types.js\";\n\nexport interface FrpWrapperOptions {\n serverAddr: string;\n serverPort: number;\n userId: string;\n token: string;\n subdomains: SubdomainConfig[];\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n onLog?: (message: string) => void;\n}\n\nexport class FrpWrapper {\n private options: FrpWrapperOptions;\n private process: ChildProcess | null = null;\n private configPath: string;\n\n constructor(options: FrpWrapperOptions) {\n this.options = options;\n this.configPath = join(tmpdir(), \"chaiterm\", `frpc-${options.userId}.toml`);\n }\n\n /**\n * Start frpc with current configuration\n */\n async start(): Promise<void> {\n // Ensure config directory exists\n const configDir = join(tmpdir(), \"chaiterm\");\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true });\n }\n\n // Generate config file\n this.generateConfig();\n\n // Find frpc binary\n const frpcPath = await this.findFrpcBinary();\n if (!frpcPath) {\n throw new Error(\n \"frpc binary not found. Please ensure frpc is installed or bundled.\"\n );\n }\n\n console.log(`[frp-wrapper] Starting frpc with config: ${this.configPath}`);\n\n return new Promise((resolve, reject) => {\n this.process = spawn(frpcPath, [\"-c\", this.configPath], {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let connected = false;\n\n this.process.stdout?.on(\"data\", (data: Buffer) => {\n const message = data.toString();\n this.options.onLog?.(message);\n\n // Detect connection success\n if (message.includes(\"start proxy success\") && !connected) {\n connected = true;\n this.options.onConnect?.();\n resolve();\n }\n });\n\n this.process.stderr?.on(\"data\", (data: Buffer) => {\n const message = data.toString();\n this.options.onLog?.(message);\n\n // Check for errors\n if (message.includes(\"error\") || message.includes(\"Error\")) {\n this.options.onError?.(new Error(message));\n }\n });\n\n this.process.on(\"error\", (err) => {\n this.options.onError?.(err);\n reject(err);\n });\n\n this.process.on(\"close\", (code) => {\n console.log(`[frp-wrapper] frpc exited with code ${code}`);\n this.process = null;\n this.options.onDisconnect?.();\n });\n\n // Timeout if no connection after 30 seconds\n setTimeout(() => {\n if (!connected) {\n reject(new Error(\"Connection timeout\"));\n }\n }, 30000);\n });\n }\n\n /**\n * Stop frpc process\n */\n stop(): void {\n if (this.process) {\n console.log(\"[frp-wrapper] Stopping frpc\");\n this.process.kill(\"SIGTERM\");\n this.process = null;\n }\n }\n\n /**\n * Check if frpc is running\n */\n isRunning(): boolean {\n return this.process !== null;\n }\n\n /**\n * Generate frpc config file\n */\n private generateConfig(): void {\n const { serverAddr, serverPort, userId, token, subdomains } = this.options;\n\n let config = `# Auto-generated by chaiterm CLI\n# User: ${userId}\n\nserverAddr = \"${serverAddr}\"\nserverPort = ${serverPort}\n\n# Authentication\nauth.method = \"token\"\nauth.token = \"base-token\"\n\n# User identification (sent to auth plugin)\nuser = \"${userId}\"\nmetadatas.token = \"${token}\"\n\n# Encrypt tunnel\ntransport.tls.enable = true\n\n# Logging\nlog.to = \"console\"\nlog.level = \"info\"\n\n`;\n\n // Add proxy configurations for each subdomain\n for (const subdomain of subdomains) {\n if (subdomain.active) {\n config += `\n[[proxies]]\nname = \"${userId}-${subdomain.id}\"\ntype = \"http\"\nlocalPort = ${subdomain.localPort}\nsubdomain = \"${subdomain.id}\"\n`;\n }\n }\n\n writeFileSync(this.configPath, config, \"utf-8\");\n console.log(`[frp-wrapper] Config written to ${this.configPath}`);\n }\n\n /**\n * Find frpc binary in various locations\n */\n private async findFrpcBinary(): Promise<string | null> {\n const { execSync } = await import(\"child_process\");\n\n // Check common locations\n const possiblePaths = [\n // In PATH\n \"frpc\",\n // Local node_modules bin\n join(process.cwd(), \"node_modules\", \".bin\", \"frpc\"),\n // Homebrew (macOS)\n \"/opt/homebrew/bin/frpc\",\n \"/usr/local/bin/frpc\",\n // Linux\n \"/usr/bin/frpc\",\n ];\n\n for (const path of possiblePaths) {\n try {\n execSync(`${path} --version`, { stdio: \"ignore\" });\n return path;\n } catch {\n continue;\n }\n }\n\n return null;\n }\n}\n\n","/**\n * Configuration management for chaiterm CLI\n *\n * Uses config file at:\n * - Custom path via --config flag\n * - Default: ~/.config/chaiterm/config.json\n *\n * All configuration comes from the config file only.\n * Use --config flag to switch between different config files (e.g., local vs remote).\n */\n\nimport Conf from \"conf\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport type { ChaitermConfig, SubdomainConfig } from \"../types.js\";\n\n// Track custom config path if provided via --config flag\nlet customConfigPath: string | null = null;\n\n/**\n * Set custom config path (called before Config instantiation)\n */\nexport function setConfigPath(configPath: string): void {\n // Resolve to absolute path\n customConfigPath = path.resolve(configPath);\n}\n\n/**\n * Get the custom config path if set\n */\nexport function getCustomConfigPath(): string | null {\n return customConfigPath;\n}\n\n/**\n * Check if using a custom config path\n */\nexport function isUsingCustomConfig(): boolean {\n return customConfigPath !== null;\n}\n\nconst CONFIG_DEFAULTS: Partial<ChaitermConfig> = {\n // Path-based WebSocket URL (same domain as web app)\n // CLI appends /register to this URL\n gatewayUrl: \"wss://chaiterm.com/ws/pty\",\n frpServerAddr: \"frp.chaiterm.com\",\n frpServerPort: 7000,\n subdomains: [],\n};\n\nexport class Config {\n private store: Conf<ChaitermConfig>;\n private customPath: string | null;\n\n constructor() {\n this.customPath = customConfigPath;\n\n if (this.customPath) {\n // Use custom config file path\n // Ensure directory exists\n const dir = path.dirname(this.customPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n this.store = new Conf<ChaitermConfig>({\n projectName: \"chaiterm\",\n defaults: CONFIG_DEFAULTS as ChaitermConfig,\n cwd: dir,\n configName: path.basename(this.customPath, \".json\"),\n });\n } else {\n // Use default config location (~/.config/chaiterm/config.json)\n this.store = new Conf<ChaitermConfig>({\n projectName: \"chaiterm\",\n defaults: CONFIG_DEFAULTS as ChaitermConfig,\n });\n }\n }\n\n /**\n * Check if user is authenticated\n */\n isAuthenticated(): boolean {\n return !!this.store.get(\"token\") && !!this.store.get(\"userId\");\n }\n\n /**\n * Get authentication token\n */\n getToken(): string | undefined {\n return this.store.get(\"token\");\n }\n\n /**\n * Get user ID (used for frp/port exposure)\n */\n getUserId(): string | undefined {\n return this.store.get(\"userId\");\n }\n\n /**\n * Get user email (used for terminal access)\n */\n getEmail(): string | undefined {\n return this.store.get(\"email\");\n }\n\n /**\n * Set authentication credentials\n */\n setCredentials(token: string, userId: string, email?: string): void {\n this.store.set(\"token\", token);\n this.store.set(\"userId\", userId);\n if (email) {\n this.store.set(\"email\", email);\n }\n }\n\n /**\n * Set email for terminal access\n */\n setEmail(email: string): void {\n this.store.set(\"email\", email);\n }\n\n /**\n * Get gateway URL for terminal access\n */\n getGatewayUrl(): string {\n return this.store.get(\"gatewayUrl\") || CONFIG_DEFAULTS.gatewayUrl!;\n }\n\n /**\n * Get frp server address\n */\n getFrpServerAddr(): string {\n return this.store.get(\"frpServerAddr\") || CONFIG_DEFAULTS.frpServerAddr!;\n }\n\n /**\n * Get frp server port\n */\n getFrpServerPort(): number {\n return this.store.get(\"frpServerPort\") || CONFIG_DEFAULTS.frpServerPort!;\n }\n\n /**\n * Get all configured subdomains\n */\n getSubdomains(): SubdomainConfig[] {\n return this.store.get(\"subdomains\") || [];\n }\n\n /**\n * Add a subdomain configuration\n */\n addSubdomain(subdomain: SubdomainConfig): void {\n const subdomains = this.getSubdomains();\n subdomains.push(subdomain);\n this.store.set(\"subdomains\", subdomains);\n }\n\n /**\n * Remove a subdomain configuration\n */\n removeSubdomain(id: string): void {\n const subdomains = this.getSubdomains().filter((s) => s.id !== id);\n this.store.set(\"subdomains\", subdomains);\n }\n\n /**\n * Clear all configuration (logout)\n */\n clear(): void {\n this.store.clear();\n }\n\n /**\n * Get config file path (for debugging)\n */\n getPath(): string {\n return this.store.path;\n }\n\n /**\n * Check if using a custom config path\n */\n isCustomConfig(): boolean {\n return this.customPath !== null;\n }\n}\n\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Security Model:
|
|
9
9
|
* - No token needed for terminal access (security is at browser via session cookie)
|
|
10
|
-
* - CLI
|
|
10
|
+
* - CLI registers with email and unique terminalId
|
|
11
11
|
* - Token is only used for port exposure (frp tunnels)
|
|
12
12
|
*/
|
|
13
13
|
interface PtyClientOptions {
|
|
@@ -23,7 +23,17 @@ declare class PtyClient {
|
|
|
23
23
|
private options;
|
|
24
24
|
private reconnectTimeout;
|
|
25
25
|
private shouldReconnect;
|
|
26
|
+
private terminalId;
|
|
27
|
+
private terminalName;
|
|
26
28
|
constructor(options: PtyClientOptions);
|
|
29
|
+
/**
|
|
30
|
+
* Get the terminal ID for this session
|
|
31
|
+
*/
|
|
32
|
+
getTerminalId(): string;
|
|
33
|
+
/**
|
|
34
|
+
* Get the terminal name (hostname) for this session
|
|
35
|
+
*/
|
|
36
|
+
getTerminalName(): string;
|
|
27
37
|
/**
|
|
28
38
|
* Connect to the terminal gateway
|
|
29
39
|
*/
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/pty-client.ts","../src/types.ts","../src/lib/frp-wrapper.ts","../src/lib/config.ts"],"sourcesContent":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/pty-client.ts","../src/types.ts","../src/lib/frp-wrapper.ts","../src/lib/config.ts"],"sourcesContent":[],"mappings":";;AAkBA;AASA;;;;ACvBA;AAiBA;AASA;AAKA;AAOA;UDxBiB,gBAAA;;;EELA,SAAA,CAAA,EAAA,GAAA,GAAA,IAAiB;EAYrB,YAAA,CAAU,EAAA,CAAA,IAAA,EAAA,MAKA,EAAA,MAAA,EAAA,MAQN,EAAA,GAAA,IAAO;oBFdJ;;cAGP,SAAA;EGuBA,QAAA,EAAM;;;;;;;uBHdI;;;;;;;;;;;;aAwBJ;;;;;;;;;;;;;;;AA1CnB;AASA;UCvBiB,cAAA;;;EAAA;EAiBA,MAAA,EAAA,MAAA;EASA;EAKA,KAAA,EAAA,MAAA;EAOL;;;;EC7BK;EAYJ,aAAU,EAAA,MAAA;;cDPT;;AEgCD,UF7BI,eAAA,CEiIE;;;;;;;;UFxHF,gBAAA;;WAEN;;UAGM,YAAA;;;;;;KAOL,eAAA;;;;;;;;;;;;;;;;;;;;;;;AArBK,UCRA,iBAAA,CDQe;EASf,UAAA,EAAA,MAAA;EAKA,UAAA,EAAA,MAAY;EAOjB,MAAA,EAAA,MAAA;;cCxBE;;EALG,YAAA,CAAA,EAAA,GAAA,GAAiB,IAAA;EAYrB,OAAA,CAAA,EAAA,CAAA,KAAU,EAJH,KAIG,EAKA,GAAA,IAAA;;;cALV,UAAA;ECyBA,QAAA,OAAM;;;uBDpBI;;;;WAQN;;;;;;;;;;;;;;;;;;;;;cCYJ,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAoGM;;;;0BAOO"}
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chaiterm",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Access your terminal from anywhere - CLI tool for chaiterm",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"commander": "^13.1.0",
|
|
44
|
+
"nanoid": "^5.1.5",
|
|
44
45
|
"node-pty": "^1.0.0",
|
|
45
46
|
"ws": "^8.18.2",
|
|
46
47
|
"chalk": "^5.4.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-BUazL54Y.mjs","names":["msg: TerminalMessage","path","customConfigPath: string | null","CONFIG_DEFAULTS: Partial<ChaitermConfig>"],"sources":["../src/lib/pty-client.ts","../src/lib/frp-wrapper.ts","../src/lib/config.ts"],"sourcesContent":["/**\n * PTY Client\n *\n * Connects to the terminal gateway and spawns a local PTY.\n * This is the core of terminal access functionality.\n *\n * Security Model:\n * - No token needed for terminal access (security is at browser via session cookie)\n * - CLI just registers which user's machine it is by email\n * - Token is only used for port exposure (frp tunnels)\n */\n\nimport WebSocket from \"ws\";\nimport type { IPty } from \"node-pty\";\nimport type { TerminalMessage } from \"../types.js\";\n\nexport interface PtyClientOptions {\n gatewayUrl: string;\n email: string;\n // Note: token is NOT needed for terminal access, only for port exposure\n onConnect?: () => void;\n onDisconnect?: (code: number, reason: string) => void;\n onError?: (error: Error) => void;\n}\n\nexport class PtyClient {\n private ws: WebSocket | null = null;\n private pty: IPty | null = null;\n private options: PtyClientOptions;\n private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;\n private shouldReconnect = true;\n\n constructor(options: PtyClientOptions) {\n this.options = options;\n }\n\n /**\n * Connect to the terminal gateway\n */\n async connect(): Promise<void> {\n const { gatewayUrl, email } = this.options;\n // No token in URL - security is enforced at browser connection via session cookie\n const url = `${gatewayUrl}/register?email=${encodeURIComponent(email)}`;\n\n console.log(`[pty-client] Connecting to gateway...`);\n\n return new Promise((resolve, reject) => {\n this.ws = new WebSocket(url);\n\n this.ws.on(\"open\", () => {\n console.log(\"[pty-client] Connected to gateway\");\n this.options.onConnect?.();\n resolve();\n });\n\n this.ws.on(\"message\", (raw) => {\n this.handleMessage(raw.toString());\n });\n\n this.ws.on(\"close\", (code, reason) => {\n console.log(\n `[pty-client] Disconnected: code=${code}, reason=${reason}`\n );\n this.cleanup();\n this.options.onDisconnect?.(code, reason.toString());\n this.scheduleReconnect();\n });\n\n this.ws.on(\"error\", (err) => {\n console.error(`[pty-client] Error: ${err.message}`);\n this.options.onError?.(err);\n reject(err);\n });\n });\n }\n\n /**\n * Disconnect from gateway\n */\n disconnect(): void {\n this.shouldReconnect = false;\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n this.cleanup();\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n }\n\n private handleMessage(raw: string): void {\n let msg: TerminalMessage;\n try {\n msg = JSON.parse(raw);\n } catch {\n return;\n }\n\n switch (msg.kind) {\n case \"open\":\n this.openPty(msg.cols, msg.rows);\n break;\n case \"data\":\n this.pty?.write(msg.data);\n break;\n case \"resize\":\n if (this.pty && msg.cols && msg.rows) {\n this.pty.resize(msg.cols, msg.rows);\n }\n break;\n case \"close\":\n this.closePty();\n break;\n }\n }\n\n private async openPty(cols: number, rows: number): Promise<void> {\n // If PTY already exists, just resize it and send ready - don't kill it!\n // This allows multiple browser windows to share the same terminal\n if (this.pty) {\n console.log(`[pty-client] PTY already exists, reusing (resizing to ${cols}x${rows})`);\n try {\n this.pty.resize(cols || 80, rows || 24);\n } catch {\n // Ignore resize errors\n }\n this.send({ kind: \"ready\", pid: this.pty.pid });\n return;\n }\n\n // Dynamic import for node-pty (native module)\n const nodePty = await import(\"node-pty\");\n\n const shell = process.env.SHELL || \"/bin/bash\";\n const cwd = process.env.HOME || process.cwd();\n\n console.log(`[pty-client] Opening NEW PTY: ${cols}x${rows}`);\n console.log(`[pty-client] Shell: ${shell}, CWD: ${cwd}`);\n\n try {\n this.pty = nodePty.spawn(shell, [], {\n name: \"xterm-256color\",\n cols: cols || 80,\n rows: rows || 24,\n cwd: cwd,\n env: process.env as Record<string, string>,\n });\n\n // Send ready message\n this.send({ kind: \"ready\", pid: this.pty.pid });\n\n // Forward PTY output to gateway\n this.pty.onData((data: string) => {\n this.send({ kind: \"data\", data });\n });\n\n // Handle PTY exit\n this.pty.onExit(({ exitCode, signal }) => {\n console.log(\n `[pty-client] PTY exited: code=${exitCode}, signal=${signal}`\n );\n this.send({ kind: \"exit\", code: exitCode, signal });\n this.pty = null;\n });\n } catch (err) {\n const error = err as Error;\n console.error(`[pty-client] Failed to spawn PTY: ${error.message}`);\n\n // Detect spawn-helper permissions issue\n let errorMessage = `\\r\\nError: Failed to spawn shell: ${error.message}\\r\\n`;\n if (\n error.message.includes(\"posix_spawnp\") &&\n process.platform !== \"win32\"\n ) {\n errorMessage += `\\r\\n`;\n errorMessage += `This is likely caused by missing execute permissions on node-pty's spawn-helper.\\r\\n`;\n errorMessage += `\\r\\n`;\n errorMessage += `To fix this, run:\\r\\n`;\n errorMessage += ` npm rebuild node-pty\\r\\n`;\n errorMessage += `\\r\\n`;\n errorMessage += `Or manually fix permissions:\\r\\n`;\n errorMessage += ` chmod +x node_modules/node-pty/prebuilds/*/spawn-helper\\r\\n`;\n errorMessage += `\\r\\n`;\n }\n\n this.send({\n kind: \"data\",\n data: errorMessage,\n });\n }\n }\n\n private closePty(): void {\n if (this.pty) {\n console.log(\"[pty-client] Closing PTY\");\n try {\n this.pty.kill();\n } catch {\n // Ignore errors\n }\n this.pty = null;\n }\n }\n\n private send(msg: TerminalMessage): void {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n }\n\n private cleanup(): void {\n this.closePty();\n }\n\n private scheduleReconnect(): void {\n if (!this.shouldReconnect || this.reconnectTimeout) return;\n\n console.log(\"[pty-client] Reconnecting in 3 seconds...\");\n this.reconnectTimeout = setTimeout(() => {\n this.reconnectTimeout = null;\n this.connect().catch(() => {\n // Will retry on next disconnect\n });\n }, 3000);\n }\n}\n\n","/**\n * frp Wrapper\n *\n * Wraps the frpc binary for port exposure functionality.\n * Generates config and spawns frpc process.\n */\n\nimport { spawn, type ChildProcess } from \"child_process\";\nimport { writeFileSync, mkdirSync, existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { tmpdir } from \"os\";\nimport type { SubdomainConfig } from \"../types.js\";\n\nexport interface FrpWrapperOptions {\n serverAddr: string;\n serverPort: number;\n userId: string;\n token: string;\n subdomains: SubdomainConfig[];\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n onLog?: (message: string) => void;\n}\n\nexport class FrpWrapper {\n private options: FrpWrapperOptions;\n private process: ChildProcess | null = null;\n private configPath: string;\n\n constructor(options: FrpWrapperOptions) {\n this.options = options;\n this.configPath = join(tmpdir(), \"chaiterm\", `frpc-${options.userId}.toml`);\n }\n\n /**\n * Start frpc with current configuration\n */\n async start(): Promise<void> {\n // Ensure config directory exists\n const configDir = join(tmpdir(), \"chaiterm\");\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true });\n }\n\n // Generate config file\n this.generateConfig();\n\n // Find frpc binary\n const frpcPath = await this.findFrpcBinary();\n if (!frpcPath) {\n throw new Error(\n \"frpc binary not found. Please ensure frpc is installed or bundled.\"\n );\n }\n\n console.log(`[frp-wrapper] Starting frpc with config: ${this.configPath}`);\n\n return new Promise((resolve, reject) => {\n this.process = spawn(frpcPath, [\"-c\", this.configPath], {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let connected = false;\n\n this.process.stdout?.on(\"data\", (data: Buffer) => {\n const message = data.toString();\n this.options.onLog?.(message);\n\n // Detect connection success\n if (message.includes(\"start proxy success\") && !connected) {\n connected = true;\n this.options.onConnect?.();\n resolve();\n }\n });\n\n this.process.stderr?.on(\"data\", (data: Buffer) => {\n const message = data.toString();\n this.options.onLog?.(message);\n\n // Check for errors\n if (message.includes(\"error\") || message.includes(\"Error\")) {\n this.options.onError?.(new Error(message));\n }\n });\n\n this.process.on(\"error\", (err) => {\n this.options.onError?.(err);\n reject(err);\n });\n\n this.process.on(\"close\", (code) => {\n console.log(`[frp-wrapper] frpc exited with code ${code}`);\n this.process = null;\n this.options.onDisconnect?.();\n });\n\n // Timeout if no connection after 30 seconds\n setTimeout(() => {\n if (!connected) {\n reject(new Error(\"Connection timeout\"));\n }\n }, 30000);\n });\n }\n\n /**\n * Stop frpc process\n */\n stop(): void {\n if (this.process) {\n console.log(\"[frp-wrapper] Stopping frpc\");\n this.process.kill(\"SIGTERM\");\n this.process = null;\n }\n }\n\n /**\n * Check if frpc is running\n */\n isRunning(): boolean {\n return this.process !== null;\n }\n\n /**\n * Generate frpc config file\n */\n private generateConfig(): void {\n const { serverAddr, serverPort, userId, token, subdomains } = this.options;\n\n let config = `# Auto-generated by chaiterm CLI\n# User: ${userId}\n\nserverAddr = \"${serverAddr}\"\nserverPort = ${serverPort}\n\n# Authentication\nauth.method = \"token\"\nauth.token = \"base-token\"\n\n# User identification (sent to auth plugin)\nuser = \"${userId}\"\nmetadatas.token = \"${token}\"\n\n# Encrypt tunnel\ntransport.tls.enable = true\n\n# Logging\nlog.to = \"console\"\nlog.level = \"info\"\n\n`;\n\n // Add proxy configurations for each subdomain\n for (const subdomain of subdomains) {\n if (subdomain.active) {\n config += `\n[[proxies]]\nname = \"${userId}-${subdomain.id}\"\ntype = \"http\"\nlocalPort = ${subdomain.localPort}\nsubdomain = \"${subdomain.id}\"\n`;\n }\n }\n\n writeFileSync(this.configPath, config, \"utf-8\");\n console.log(`[frp-wrapper] Config written to ${this.configPath}`);\n }\n\n /**\n * Find frpc binary in various locations\n */\n private async findFrpcBinary(): Promise<string | null> {\n const { execSync } = await import(\"child_process\");\n\n // Check common locations\n const possiblePaths = [\n // In PATH\n \"frpc\",\n // Local node_modules bin\n join(process.cwd(), \"node_modules\", \".bin\", \"frpc\"),\n // Homebrew (macOS)\n \"/opt/homebrew/bin/frpc\",\n \"/usr/local/bin/frpc\",\n // Linux\n \"/usr/bin/frpc\",\n ];\n\n for (const path of possiblePaths) {\n try {\n execSync(`${path} --version`, { stdio: \"ignore\" });\n return path;\n } catch {\n continue;\n }\n }\n\n return null;\n }\n}\n\n","/**\n * Configuration management for chaiterm CLI\n *\n * Uses config file at:\n * - Custom path via --config flag\n * - Default: ~/.config/chaiterm/config.json\n *\n * All configuration comes from the config file only.\n * Use --config flag to switch between different config files (e.g., local vs remote).\n */\n\nimport Conf from \"conf\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport type { ChaitermConfig, SubdomainConfig } from \"../types.js\";\n\n// Track custom config path if provided via --config flag\nlet customConfigPath: string | null = null;\n\n/**\n * Set custom config path (called before Config instantiation)\n */\nexport function setConfigPath(configPath: string): void {\n // Resolve to absolute path\n customConfigPath = path.resolve(configPath);\n}\n\n/**\n * Get the custom config path if set\n */\nexport function getCustomConfigPath(): string | null {\n return customConfigPath;\n}\n\n/**\n * Check if using a custom config path\n */\nexport function isUsingCustomConfig(): boolean {\n return customConfigPath !== null;\n}\n\nconst CONFIG_DEFAULTS: Partial<ChaitermConfig> = {\n // Path-based WebSocket URL (same domain as web app)\n // CLI appends /register to this URL\n gatewayUrl: \"wss://chaiterm.com/ws/pty\",\n frpServerAddr: \"frp.chaiterm.com\",\n frpServerPort: 7000,\n subdomains: [],\n};\n\nexport class Config {\n private store: Conf<ChaitermConfig>;\n private customPath: string | null;\n\n constructor() {\n this.customPath = customConfigPath;\n\n if (this.customPath) {\n // Use custom config file path\n // Ensure directory exists\n const dir = path.dirname(this.customPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n this.store = new Conf<ChaitermConfig>({\n projectName: \"chaiterm\",\n defaults: CONFIG_DEFAULTS as ChaitermConfig,\n cwd: dir,\n configName: path.basename(this.customPath, \".json\"),\n });\n } else {\n // Use default config location (~/.config/chaiterm/config.json)\n this.store = new Conf<ChaitermConfig>({\n projectName: \"chaiterm\",\n defaults: CONFIG_DEFAULTS as ChaitermConfig,\n });\n }\n }\n\n /**\n * Check if user is authenticated\n */\n isAuthenticated(): boolean {\n return !!this.store.get(\"token\") && !!this.store.get(\"userId\");\n }\n\n /**\n * Get authentication token\n */\n getToken(): string | undefined {\n return this.store.get(\"token\");\n }\n\n /**\n * Get user ID (used for frp/port exposure)\n */\n getUserId(): string | undefined {\n return this.store.get(\"userId\");\n }\n\n /**\n * Get user email (used for terminal access)\n */\n getEmail(): string | undefined {\n return this.store.get(\"email\");\n }\n\n /**\n * Set authentication credentials\n */\n setCredentials(token: string, userId: string, email?: string): void {\n this.store.set(\"token\", token);\n this.store.set(\"userId\", userId);\n if (email) {\n this.store.set(\"email\", email);\n }\n }\n\n /**\n * Set email for terminal access\n */\n setEmail(email: string): void {\n this.store.set(\"email\", email);\n }\n\n /**\n * Get gateway URL for terminal access\n */\n getGatewayUrl(): string {\n return this.store.get(\"gatewayUrl\") || CONFIG_DEFAULTS.gatewayUrl!;\n }\n\n /**\n * Get frp server address\n */\n getFrpServerAddr(): string {\n return this.store.get(\"frpServerAddr\") || CONFIG_DEFAULTS.frpServerAddr!;\n }\n\n /**\n * Get frp server port\n */\n getFrpServerPort(): number {\n return this.store.get(\"frpServerPort\") || CONFIG_DEFAULTS.frpServerPort!;\n }\n\n /**\n * Get all configured subdomains\n */\n getSubdomains(): SubdomainConfig[] {\n return this.store.get(\"subdomains\") || [];\n }\n\n /**\n * Add a subdomain configuration\n */\n addSubdomain(subdomain: SubdomainConfig): void {\n const subdomains = this.getSubdomains();\n subdomains.push(subdomain);\n this.store.set(\"subdomains\", subdomains);\n }\n\n /**\n * Remove a subdomain configuration\n */\n removeSubdomain(id: string): void {\n const subdomains = this.getSubdomains().filter((s) => s.id !== id);\n this.store.set(\"subdomains\", subdomains);\n }\n\n /**\n * Clear all configuration (logout)\n */\n clear(): void {\n this.store.clear();\n }\n\n /**\n * Get config file path (for debugging)\n */\n getPath(): string {\n return this.store.path;\n }\n\n /**\n * Check if using a custom config path\n */\n isCustomConfig(): boolean {\n return this.customPath !== null;\n }\n}\n\n"],"mappings":"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"}
|