chaitunnel 0.0.1

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.
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ import { a as setConfigPath, n as ApiClient, r as Config, t as FrpWrapper } from "../frp-wrapper-DgAvulMB.mjs";
3
+ import { Command } from "commander";
4
+ import chalk from "chalk";
5
+
6
+ //#region src/commands/expose.ts
7
+ /**
8
+ * Expose command
9
+ *
10
+ * Exposes a local port to the internet via frp tunnel.
11
+ */
12
+ async function exposeCommand(port) {
13
+ const config = new Config();
14
+ const localPort = parseInt(port, 10);
15
+ if (isNaN(localPort) || localPort < 1 || localPort > 65535) {
16
+ console.log(chalk.red(`\nInvalid port: ${port}`));
17
+ console.log(chalk.gray(" Port must be a number between 1 and 65535\n"));
18
+ process.exit(1);
19
+ }
20
+ if (!config.isAuthenticated()) {
21
+ console.log(chalk.red("\nNot authenticated."));
22
+ console.log(chalk.gray(" Run 'chaitunnel authtoken <token>' first."));
23
+ console.log(chalk.gray(" Get your token from: https://chaiterm.com/dashboard\n"));
24
+ process.exit(1);
25
+ }
26
+ const authToken = config.getAuthToken();
27
+ const apiUrl = config.getApiUrl();
28
+ const serverAddr = config.getFrpServerAddr();
29
+ const serverPort = config.getFrpServerPort();
30
+ const tlsEnabled = config.getTlsEnabled();
31
+ const publicUrlTemplate = config.getPublicUrlTemplate();
32
+ console.log(chalk.cyan(`\nExposing port ${localPort}...\n`));
33
+ const api = new ApiClient(apiUrl, authToken);
34
+ let tunnel;
35
+ try {
36
+ tunnel = await api.createTunnel(localPort);
37
+ } catch (error) {
38
+ const err = error;
39
+ console.log(chalk.red(`\nFailed to create tunnel: ${err.message}`));
40
+ process.exit(1);
41
+ }
42
+ console.log(` Subdomain: ${chalk.green(tunnel.subdomain)}`);
43
+ console.log(` Local port: ${chalk.green(localPort)}`);
44
+ console.log(` Server: ${chalk.gray(`${serverAddr}:${serverPort}`)}`);
45
+ console.log("");
46
+ const frp = new FrpWrapper({
47
+ serverAddr,
48
+ serverPort,
49
+ authToken,
50
+ tunnelId: tunnel.id,
51
+ tlsEnabled,
52
+ proxy: {
53
+ tunnelId: tunnel.id,
54
+ subdomain: tunnel.subdomain,
55
+ localPort
56
+ },
57
+ onConnect: () => {
58
+ console.log(chalk.green("Tunnel established"));
59
+ const publicUrl = publicUrlTemplate.replace("{subdomain}", tunnel.subdomain);
60
+ console.log(chalk.cyan(`\nYour app is available at: ${publicUrl}\n`));
61
+ console.log(chalk.gray("Press Ctrl+C to stop\n"));
62
+ },
63
+ onDisconnect: () => {
64
+ console.log(chalk.yellow("\nTunnel disconnected"));
65
+ },
66
+ onError: (error) => {
67
+ console.log(chalk.red(`\nError: ${error.message}`));
68
+ },
69
+ onLog: (message) => {
70
+ if (process.env.DEBUG) console.log(chalk.gray(`[frp] ${message.trim()}`));
71
+ }
72
+ });
73
+ const cleanup = async () => {
74
+ console.log(chalk.gray("\n\nShutting down tunnel..."));
75
+ frp.stop();
76
+ try {
77
+ await api.deleteTunnel(tunnel.id);
78
+ console.log(chalk.gray("Tunnel removed."));
79
+ } catch {}
80
+ process.exit(0);
81
+ };
82
+ process.on("SIGINT", cleanup);
83
+ process.on("SIGTERM", cleanup);
84
+ try {
85
+ await frp.start();
86
+ await new Promise(() => {});
87
+ } catch (error) {
88
+ const err = error;
89
+ console.log(chalk.red(`\nFailed to establish tunnel: ${err.message}`));
90
+ console.log(chalk.gray("\nTroubleshooting:"));
91
+ console.log(" 1. Ensure frpc is installed (brew install frpc on macOS)");
92
+ console.log(" 2. Check that your local port is accessible");
93
+ console.log(" 3. Verify your network allows outbound connections\n");
94
+ try {
95
+ await api.deleteTunnel(tunnel.id);
96
+ } catch {}
97
+ process.exit(1);
98
+ }
99
+ }
100
+
101
+ //#endregion
102
+ //#region src/commands/list.ts
103
+ /**
104
+ * List command
105
+ *
106
+ * Lists all tunnels for the authenticated user.
107
+ */
108
+ async function listCommand() {
109
+ const config = new Config();
110
+ if (!config.isAuthenticated()) {
111
+ console.log(chalk.red("\nNot authenticated."));
112
+ console.log(chalk.gray(" Run 'chaitunnel authtoken <token>' first."));
113
+ console.log(chalk.gray(" Get your token from: https://chaiterm.com/dashboard\n"));
114
+ process.exit(1);
115
+ }
116
+ const authToken = config.getAuthToken();
117
+ const api = new ApiClient(config.getApiUrl(), authToken);
118
+ try {
119
+ const tunnels = await api.listTunnels();
120
+ if (tunnels.length === 0) {
121
+ console.log(chalk.yellow("\nNo active tunnels."));
122
+ console.log(chalk.gray(" Run 'chaitunnel <port>' to expose a port.\n"));
123
+ return;
124
+ }
125
+ console.log(chalk.cyan("\nYour tunnels:\n"));
126
+ console.log(chalk.gray(" " + "SUBDOMAIN".padEnd(15) + "PORT".padEnd(8) + "STATUS".padEnd(12) + "URL"));
127
+ console.log(chalk.gray(" " + "-".repeat(70)));
128
+ for (const tunnel of tunnels) {
129
+ const statusColor = tunnel.status === "active" ? chalk.green : chalk.yellow;
130
+ const url = `https://${tunnel.subdomain}.chaiterm.com`;
131
+ console.log(" " + chalk.white(tunnel.subdomain.padEnd(15)) + chalk.white(tunnel.localPort.toString().padEnd(8)) + statusColor(tunnel.status.padEnd(12)) + chalk.blue(url));
132
+ }
133
+ console.log("");
134
+ } catch (error) {
135
+ const err = error;
136
+ console.log(chalk.red(`\nFailed to list tunnels: ${err.message}\n`));
137
+ process.exit(1);
138
+ }
139
+ }
140
+
141
+ //#endregion
142
+ //#region src/commands/authtoken.ts
143
+ /**
144
+ * Authtoken command
145
+ *
146
+ * Saves the authentication token to config.
147
+ * Usage: chaitunnel authtoken <token>
148
+ */
149
+ async function authtokenCommand(token) {
150
+ if (!token || token.trim() === "") {
151
+ console.log(chalk.red("\nError: Token is required"));
152
+ console.log(chalk.gray("Usage: chaitunnel authtoken <token>\n"));
153
+ process.exit(1);
154
+ }
155
+ const config = new Config();
156
+ config.setAuthToken(token.trim());
157
+ console.log(chalk.green("\nAuthtoken saved to config"));
158
+ console.log(chalk.gray(`Config: ${config.getPath()}\n`));
159
+ console.log(chalk.cyan("You can now run:"));
160
+ console.log(chalk.white(" chaitunnel <port>\n"));
161
+ }
162
+
163
+ //#endregion
164
+ //#region src/commands/logout.ts
165
+ /**
166
+ * Logout command
167
+ *
168
+ * Clears authentication credentials.
169
+ */
170
+ async function logoutCommand() {
171
+ const config = new Config();
172
+ if (!config.isAuthenticated()) {
173
+ console.log(chalk.yellow("\nNot currently authenticated.\n"));
174
+ return;
175
+ }
176
+ config.clear();
177
+ console.log(chalk.green(`\nAuthtoken cleared.`));
178
+ console.log(chalk.gray(` Config: ${config.getPath()}\n`));
179
+ }
180
+
181
+ //#endregion
182
+ //#region src/commands/status.ts
183
+ /**
184
+ * Status command
185
+ *
186
+ * Shows current authentication and configuration status.
187
+ */
188
+ async function statusCommand() {
189
+ const config = new Config();
190
+ console.log(chalk.cyan("\nchaitunnel status\n"));
191
+ if (config.isAuthenticated()) {
192
+ const token = config.getAuthToken();
193
+ const maskedToken = token.slice(0, 6) + "..." + token.slice(-4);
194
+ console.log(` Auth: ${chalk.green("authenticated")}`);
195
+ console.log(` Token: ${chalk.gray(maskedToken)}`);
196
+ } else {
197
+ console.log(` Auth: ${chalk.yellow("not authenticated")}`);
198
+ console.log(chalk.gray(" Run 'chaitunnel authtoken <token>' to authenticate."));
199
+ console.log(chalk.gray(" Get your token from: https://chaiterm.com/dashboard"));
200
+ }
201
+ console.log("");
202
+ console.log(` API: ${chalk.gray(config.getApiUrl())}`);
203
+ console.log(` frp: ${chalk.gray(`${config.getFrpServerAddr()}:${config.getFrpServerPort()}`)}`);
204
+ console.log(` Config: ${chalk.gray(config.getPath())}`);
205
+ if (config.isCustomConfig()) console.log(chalk.yellow(" (using custom config path)"));
206
+ console.log("");
207
+ }
208
+
209
+ //#endregion
210
+ //#region src/bin/chaitunnel.ts
211
+ /**
212
+ * chaitunnel CLI
213
+ *
214
+ * Expose local ports to the internet via frp tunnels.
215
+ *
216
+ * Setup:
217
+ * 1. Get your authtoken from https://chaiterm.com/dashboard
218
+ * 2. Run: chaitunnel authtoken <token>
219
+ * 3. Run: chaitunnel <port>
220
+ */
221
+ const program = new Command();
222
+ program.name("chaitunnel").description("Expose local ports to the internet").version("0.0.1").option("-c, --config <path>", "Use custom config file").enablePositionalOptions();
223
+ program.hook("preAction", (thisCommand) => {
224
+ const opts = thisCommand.opts();
225
+ if (opts.config) setConfigPath(opts.config);
226
+ });
227
+ program.argument("[port]", "Local port to expose").action(async (port) => {
228
+ if (port) await exposeCommand(port);
229
+ else program.help();
230
+ });
231
+ program.command("authtoken").description("Save your authtoken (get it from chaiterm.com/dashboard)").argument("<token>", "Your authtoken from the dashboard").allowExcessArguments(false).passThroughOptions().action(authtokenCommand);
232
+ program.command("list").description("List your tunnels").action(listCommand);
233
+ program.command("logout").description("Clear authentication").action(logoutCommand);
234
+ program.command("status").description("Show authentication and config status").action(statusCommand);
235
+ program.parse();
236
+
237
+ //#endregion
238
+ export { };
239
+ //# sourceMappingURL=chaitunnel.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chaitunnel.mjs","names":[],"sources":["../../src/commands/expose.ts","../../src/commands/list.ts","../../src/commands/authtoken.ts","../../src/commands/logout.ts","../../src/commands/status.ts","../../src/bin/chaitunnel.ts"],"sourcesContent":["/**\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 { ApiClient } from \"../lib/api-client.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(`\\nInvalid port: ${port}`));\n console.log(chalk.gray(\" Port must be a number between 1 and 65535\\n\"));\n process.exit(1);\n }\n\n if (!config.isAuthenticated()) {\n console.log(chalk.red(\"\\nNot authenticated.\"));\n console.log(chalk.gray(\" Run 'chaitunnel authtoken <token>' first.\"));\n console.log(chalk.gray(\" Get your token from: https://chaiterm.com/dashboard\\n\"));\n process.exit(1);\n }\n\n const authToken = config.getAuthToken()!;\n const apiUrl = config.getApiUrl();\n const serverAddr = config.getFrpServerAddr();\n const serverPort = config.getFrpServerPort();\n const tlsEnabled = config.getTlsEnabled();\n const publicUrlTemplate = config.getPublicUrlTemplate();\n\n console.log(chalk.cyan(`\\nExposing port ${localPort}...\\n`));\n\n // Create tunnel via API\n const api = new ApiClient(apiUrl, authToken);\n let tunnel;\n\n try {\n tunnel = await api.createTunnel(localPort);\n } catch (error) {\n const err = error as Error;\n console.log(chalk.red(`\\nFailed to create tunnel: ${err.message}`));\n process.exit(1);\n }\n\n console.log(` Subdomain: ${chalk.green(tunnel.subdomain)}`);\n console.log(` Local port: ${chalk.green(localPort)}`);\n console.log(` Server: ${chalk.gray(`${serverAddr}:${serverPort}`)}`);\n console.log(\"\");\n\n const frp = new FrpWrapper({\n serverAddr,\n serverPort,\n authToken,\n tunnelId: tunnel.id,\n tlsEnabled,\n proxy: {\n tunnelId: tunnel.id,\n subdomain: tunnel.subdomain,\n localPort,\n },\n onConnect: () => {\n console.log(chalk.green(\"Tunnel established\"));\n const publicUrl = publicUrlTemplate.replace(\"{subdomain}\", tunnel.subdomain);\n console.log(\n chalk.cyan(`\\nYour app is available at: ${publicUrl}\\n`)\n );\n console.log(chalk.gray(\"Press Ctrl+C to stop\\n\"));\n },\n onDisconnect: () => {\n console.log(chalk.yellow(\"\\nTunnel disconnected\"));\n },\n onError: (error) => {\n console.log(chalk.red(`\\nError: ${error.message}`));\n },\n onLog: (message) => {\n // Only show important logs in verbose mode\n if (process.env.DEBUG) {\n console.log(chalk.gray(`[frp] ${message.trim()}`));\n }\n },\n });\n\n // Cleanup function\n const cleanup = async () => {\n console.log(chalk.gray(\"\\n\\nShutting down tunnel...\"));\n frp.stop();\n try {\n await api.deleteTunnel(tunnel.id);\n console.log(chalk.gray(\"Tunnel removed.\"));\n } catch {\n // Ignore cleanup errors\n }\n process.exit(0);\n };\n\n // Handle Ctrl+C\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\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(`\\nFailed to establish tunnel: ${err.message}`));\n console.log(chalk.gray(\"\\nTroubleshooting:\"));\n console.log(\" 1. Ensure frpc is installed (brew install frpc on macOS)\");\n console.log(\" 2. Check that your local port is accessible\");\n console.log(\" 3. Verify your network allows outbound connections\\n\");\n\n // Cleanup on failure\n try {\n await api.deleteTunnel(tunnel.id);\n } catch {\n // Ignore cleanup errors\n }\n process.exit(1);\n }\n}\n","/**\n * List command\n *\n * Lists all tunnels for the authenticated user.\n */\n\nimport chalk from \"chalk\";\nimport { Config } from \"../lib/config.js\";\nimport { ApiClient } from \"../lib/api-client.js\";\n\nexport async function listCommand(): Promise<void> {\n const config = new Config();\n\n if (!config.isAuthenticated()) {\n console.log(chalk.red(\"\\nNot authenticated.\"));\n console.log(chalk.gray(\" Run 'chaitunnel authtoken <token>' first.\"));\n console.log(chalk.gray(\" Get your token from: https://chaiterm.com/dashboard\\n\"));\n process.exit(1);\n }\n\n const authToken = config.getAuthToken()!;\n const apiUrl = config.getApiUrl();\n\n const api = new ApiClient(apiUrl, authToken);\n\n try {\n const tunnels = await api.listTunnels();\n\n if (tunnels.length === 0) {\n console.log(chalk.yellow(\"\\nNo active tunnels.\"));\n console.log(chalk.gray(\" Run 'chaitunnel <port>' to expose a port.\\n\"));\n return;\n }\n\n console.log(chalk.cyan(\"\\nYour tunnels:\\n\"));\n console.log(\n chalk.gray(\n \" \" +\n \"SUBDOMAIN\".padEnd(15) +\n \"PORT\".padEnd(8) +\n \"STATUS\".padEnd(12) +\n \"URL\"\n )\n );\n console.log(chalk.gray(\" \" + \"-\".repeat(70)));\n\n for (const tunnel of tunnels) {\n const statusColor =\n tunnel.status === \"active\" ? chalk.green : chalk.yellow;\n const url = `https://${tunnel.subdomain}.chaiterm.com`;\n\n console.log(\n \" \" +\n chalk.white(tunnel.subdomain.padEnd(15)) +\n chalk.white(tunnel.localPort.toString().padEnd(8)) +\n statusColor(tunnel.status.padEnd(12)) +\n chalk.blue(url)\n );\n }\n\n console.log(\"\");\n } catch (error) {\n const err = error as Error;\n console.log(chalk.red(`\\nFailed to list tunnels: ${err.message}\\n`));\n process.exit(1);\n }\n}\n","/**\n * Authtoken command\n *\n * Saves the authentication token to config.\n * Usage: chaitunnel authtoken <token>\n */\n\nimport chalk from \"chalk\";\nimport { Config } from \"../lib/config.js\";\n\nexport async function authtokenCommand(token: string): Promise<void> {\n if (!token || token.trim() === \"\") {\n console.log(chalk.red(\"\\nError: Token is required\"));\n console.log(chalk.gray(\"Usage: chaitunnel authtoken <token>\\n\"));\n process.exit(1);\n }\n\n const config = new Config();\n config.setAuthToken(token.trim());\n\n console.log(chalk.green(\"\\nAuthtoken saved to config\"));\n console.log(chalk.gray(`Config: ${config.getPath()}\\n`));\n console.log(chalk.cyan(\"You can now run:\"));\n console.log(chalk.white(\" chaitunnel <port>\\n\"));\n}\n","/**\n * Logout command\n *\n * Clears authentication credentials.\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 if (!config.isAuthenticated()) {\n console.log(chalk.yellow(\"\\nNot currently authenticated.\\n\"));\n return;\n }\n\n config.clear();\n\n console.log(chalk.green(`\\nAuthtoken cleared.`));\n console.log(chalk.gray(` Config: ${config.getPath()}\\n`));\n}\n","/**\n * Status command\n *\n * Shows current authentication and configuration status.\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(\"\\nchaitunnel status\\n\"));\n\n // Authentication\n if (config.isAuthenticated()) {\n const token = config.getAuthToken()!;\n const maskedToken = token.slice(0, 6) + \"...\" + token.slice(-4);\n console.log(` Auth: ${chalk.green(\"authenticated\")}`);\n console.log(` Token: ${chalk.gray(maskedToken)}`);\n } else {\n console.log(` Auth: ${chalk.yellow(\"not authenticated\")}`);\n console.log(chalk.gray(\" Run 'chaitunnel authtoken <token>' to authenticate.\"));\n console.log(chalk.gray(\" Get your token from: https://chaiterm.com/dashboard\"));\n }\n\n console.log(\"\");\n\n // Configuration\n console.log(` API: ${chalk.gray(config.getApiUrl())}`);\n console.log(\n ` frp: ${chalk.gray(`${config.getFrpServerAddr()}:${config.getFrpServerPort()}`)}`\n );\n console.log(` Config: ${chalk.gray(config.getPath())}`);\n\n if (config.isCustomConfig()) {\n console.log(chalk.yellow(\" (using custom config path)\"));\n }\n\n console.log(\"\");\n}\n","#!/usr/bin/env node\n/**\n * chaitunnel CLI\n *\n * Expose local ports to the internet via frp tunnels.\n *\n * Setup:\n * 1. Get your authtoken from https://chaiterm.com/dashboard\n * 2. Run: chaitunnel authtoken <token>\n * 3. Run: chaitunnel <port>\n */\n\nimport { Command } from \"commander\";\nimport { exposeCommand } from \"../commands/expose.js\";\nimport { listCommand } from \"../commands/list.js\";\nimport { authtokenCommand } from \"../commands/authtoken.js\";\nimport { logoutCommand } from \"../commands/logout.js\";\nimport { statusCommand } from \"../commands/status.js\";\nimport { setConfigPath } from \"../lib/config.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"chaitunnel\")\n .description(\"Expose local ports to the internet\")\n .version(\"0.0.1\")\n .option(\"-c, --config <path>\", \"Use custom config file\")\n .enablePositionalOptions();\n\n// Parse global options first\nprogram.hook(\"preAction\", (thisCommand) => {\n const opts = thisCommand.opts();\n if (opts.config) {\n setConfigPath(opts.config);\n }\n});\n\n// Main command: chaitunnel <port>\nprogram\n .argument(\"[port]\", \"Local port to expose\")\n .action(async (port: string | undefined) => {\n if (port) {\n await exposeCommand(port);\n } else {\n program.help();\n }\n });\n\n// Subcommands\nprogram\n .command(\"authtoken\")\n .description(\"Save your authtoken (get it from chaiterm.com/dashboard)\")\n .argument(\"<token>\", \"Your authtoken from the dashboard\")\n .allowExcessArguments(false)\n .passThroughOptions()\n .action(authtokenCommand);\n\nprogram\n .command(\"list\")\n .description(\"List your tunnels\")\n .action(listCommand);\n\nprogram\n .command(\"logout\")\n .description(\"Clear authentication\")\n .action(logoutCommand);\n\nprogram\n .command(\"status\")\n .description(\"Show authentication and config status\")\n .action(statusCommand);\n\nprogram.parse();\n"],"mappings":"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"}
@@ -0,0 +1,355 @@
1
+ import Conf from "conf";
2
+ import fs, { existsSync, mkdirSync, writeFileSync } from "fs";
3
+ import path, { join } from "path";
4
+ import { spawn } from "child_process";
5
+ import { tmpdir } from "os";
6
+
7
+ //#region src/lib/config.ts
8
+ /**
9
+ * Configuration management for chaitunnel CLI
10
+ *
11
+ * Uses shared config file with chaiterm at:
12
+ * - Custom path via --config flag
13
+ * - Default: ~/.config/chaiterm/config.json
14
+ *
15
+ * Auth flow (ngrok-style):
16
+ * 1. User gets authtoken from web dashboard
17
+ * 2. Runs: chaitunnel authtoken <token>
18
+ * 3. Token saved here, used for all API calls
19
+ */
20
+ let customConfigPath = null;
21
+ /**
22
+ * Set custom config path (called before Config instantiation)
23
+ */
24
+ function setConfigPath(configPath) {
25
+ customConfigPath = path.resolve(configPath);
26
+ }
27
+ /**
28
+ * Get the custom config path if set
29
+ */
30
+ function getCustomConfigPath() {
31
+ return customConfigPath;
32
+ }
33
+ const CONFIG_DEFAULTS = {
34
+ apiUrl: "https://chaiterm.com",
35
+ frpServerAddr: "frp.chaiterm.com",
36
+ frpServerPort: 7e3,
37
+ tlsEnabled: true,
38
+ publicUrlTemplate: "https://{subdomain}.chaiterm.com"
39
+ };
40
+ var Config = class {
41
+ store;
42
+ customPath;
43
+ constructor() {
44
+ this.customPath = customConfigPath;
45
+ if (this.customPath) {
46
+ const dir = path.dirname(this.customPath);
47
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
48
+ this.store = new Conf({
49
+ projectName: "chaiterm",
50
+ defaults: CONFIG_DEFAULTS,
51
+ cwd: dir,
52
+ configName: path.basename(this.customPath, ".json")
53
+ });
54
+ } else this.store = new Conf({
55
+ projectName: "chaiterm",
56
+ defaults: CONFIG_DEFAULTS
57
+ });
58
+ }
59
+ /**
60
+ * Check if user is authenticated (has authtoken)
61
+ */
62
+ isAuthenticated() {
63
+ return !!this.store.get("authToken");
64
+ }
65
+ /**
66
+ * Get auth token (ngrok-style token from dashboard)
67
+ */
68
+ getAuthToken() {
69
+ return this.store.get("authToken");
70
+ }
71
+ /**
72
+ * Set auth token (from chaitunnel authtoken <token>)
73
+ */
74
+ setAuthToken(token) {
75
+ this.store.set("authToken", token);
76
+ }
77
+ /**
78
+ * Get user ID (extracted from token validation response)
79
+ */
80
+ getUserId() {
81
+ return this.store.get("userId");
82
+ }
83
+ /**
84
+ * Set user ID
85
+ */
86
+ setUserId(userId) {
87
+ this.store.set("userId", userId);
88
+ }
89
+ /**
90
+ * Get user email
91
+ */
92
+ getEmail() {
93
+ return this.store.get("email");
94
+ }
95
+ /**
96
+ * Set user email
97
+ */
98
+ setEmail(email) {
99
+ this.store.set("email", email);
100
+ }
101
+ /**
102
+ * Get API base URL
103
+ */
104
+ getApiUrl() {
105
+ return this.store.get("apiUrl") || "https://chaiterm.com";
106
+ }
107
+ /**
108
+ * Get frp server address
109
+ */
110
+ getFrpServerAddr() {
111
+ return this.store.get("frpServerAddr") || "frp.chaiterm.com";
112
+ }
113
+ /**
114
+ * Get frp server port
115
+ */
116
+ getFrpServerPort() {
117
+ return this.store.get("frpServerPort") || 7e3;
118
+ }
119
+ /**
120
+ * Get TLS enabled setting
121
+ */
122
+ getTlsEnabled() {
123
+ const value = this.store.get("tlsEnabled");
124
+ return value !== void 0 ? value : true;
125
+ }
126
+ /**
127
+ * Get public URL template
128
+ */
129
+ getPublicUrlTemplate() {
130
+ return this.store.get("publicUrlTemplate") || "https://{subdomain}.chaiterm.com";
131
+ }
132
+ /**
133
+ * Clear all configuration (logout)
134
+ */
135
+ clear() {
136
+ this.store.clear();
137
+ }
138
+ /**
139
+ * Get config file path (for debugging)
140
+ */
141
+ getPath() {
142
+ return this.store.path;
143
+ }
144
+ /**
145
+ * Check if using a custom config path
146
+ */
147
+ isCustomConfig() {
148
+ return this.customPath !== null;
149
+ }
150
+ };
151
+
152
+ //#endregion
153
+ //#region src/lib/api-client.ts
154
+ var ApiClient = class {
155
+ baseUrl;
156
+ token;
157
+ constructor(baseUrl, token) {
158
+ this.baseUrl = baseUrl.replace(/\/$/, "");
159
+ this.token = token;
160
+ }
161
+ /**
162
+ * Create a new tunnel
163
+ */
164
+ async createTunnel(localPort) {
165
+ return (await this.trpcMutation("tunnel.create", { localPort })).result.data.json;
166
+ }
167
+ /**
168
+ * List user's tunnels
169
+ */
170
+ async listTunnels() {
171
+ return (await this.trpcQuery("tunnel.list")).result.data.json;
172
+ }
173
+ /**
174
+ * Delete a tunnel
175
+ */
176
+ async deleteTunnel(id) {
177
+ await this.trpcMutation("tunnel.delete", { id });
178
+ }
179
+ /**
180
+ * Get a single tunnel
181
+ */
182
+ async getTunnel(id) {
183
+ return (await this.trpcQuery("tunnel.get", { id })).result.data.json;
184
+ }
185
+ /**
186
+ * Make a tRPC query (GET)
187
+ */
188
+ async trpcQuery(procedure, input) {
189
+ const url = new URL(`${this.baseUrl}/trpc/${procedure}`);
190
+ if (input) url.searchParams.set("input", JSON.stringify(input));
191
+ const response = await fetch(url.toString(), {
192
+ method: "GET",
193
+ headers: {
194
+ Authorization: `Bearer ${this.token}`,
195
+ "Content-Type": "application/json"
196
+ }
197
+ });
198
+ if (!response.ok) {
199
+ const error = await response.text();
200
+ throw new Error(`API error: ${response.status} - ${error}`);
201
+ }
202
+ return response.json();
203
+ }
204
+ /**
205
+ * Make a tRPC mutation (POST)
206
+ */
207
+ async trpcMutation(procedure, input) {
208
+ const response = await fetch(`${this.baseUrl}/trpc/${procedure}`, {
209
+ method: "POST",
210
+ headers: {
211
+ Authorization: `Bearer ${this.token}`,
212
+ "Content-Type": "application/json"
213
+ },
214
+ body: JSON.stringify({ json: input })
215
+ });
216
+ if (!response.ok) {
217
+ const error = await response.text();
218
+ throw new Error(`API error: ${response.status} - ${error}`);
219
+ }
220
+ return response.json();
221
+ }
222
+ };
223
+
224
+ //#endregion
225
+ //#region src/lib/frp-wrapper.ts
226
+ /**
227
+ * frp Wrapper
228
+ *
229
+ * Wraps the frpc binary for port exposure functionality.
230
+ * Generates config and spawns frpc process.
231
+ */
232
+ var FrpWrapper = class {
233
+ options;
234
+ process = null;
235
+ configPath;
236
+ constructor(options) {
237
+ this.options = options;
238
+ this.configPath = join(tmpdir(), "chaitunnel", `frpc-${options.tunnelId}.toml`);
239
+ }
240
+ /**
241
+ * Start frpc with current configuration
242
+ */
243
+ async start() {
244
+ const configDir = join(tmpdir(), "chaitunnel");
245
+ if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
246
+ this.generateConfig();
247
+ const frpcPath = await this.findFrpcBinary();
248
+ if (!frpcPath) throw new Error("frpc binary not found. Please install frpc (brew install frpc on macOS)");
249
+ return new Promise((resolve, reject) => {
250
+ this.process = spawn(frpcPath, ["-c", this.configPath], { stdio: [
251
+ "ignore",
252
+ "pipe",
253
+ "pipe"
254
+ ] });
255
+ let connected = false;
256
+ this.process.stdout?.on("data", (data) => {
257
+ const message = data.toString();
258
+ this.options.onLog?.(message);
259
+ if (message.includes("start proxy success") && !connected) {
260
+ connected = true;
261
+ this.options.onConnect?.();
262
+ resolve();
263
+ }
264
+ });
265
+ this.process.stderr?.on("data", (data) => {
266
+ const message = data.toString();
267
+ this.options.onLog?.(message);
268
+ if (message.includes("error") || message.includes("Error")) this.options.onError?.(new Error(message));
269
+ });
270
+ this.process.on("error", (err) => {
271
+ this.options.onError?.(err);
272
+ reject(err);
273
+ });
274
+ this.process.on("close", () => {
275
+ this.process = null;
276
+ this.options.onDisconnect?.();
277
+ });
278
+ setTimeout(() => {
279
+ if (!connected) reject(/* @__PURE__ */ new Error("Connection timeout - could not connect to frp server"));
280
+ }, 3e4);
281
+ });
282
+ }
283
+ /**
284
+ * Stop frpc process
285
+ */
286
+ stop() {
287
+ if (this.process) {
288
+ this.process.kill("SIGTERM");
289
+ this.process = null;
290
+ }
291
+ }
292
+ /**
293
+ * Check if frpc is running
294
+ */
295
+ isRunning() {
296
+ return this.process !== null;
297
+ }
298
+ /**
299
+ * Generate frpc config file
300
+ */
301
+ generateConfig() {
302
+ const { serverAddr, serverPort, authToken, tunnelId, tlsEnabled, proxy } = this.options;
303
+ const config = `# Auto-generated by chaitunnel CLI
304
+ # Tunnel: ${tunnelId}
305
+
306
+ serverAddr = "${serverAddr}"
307
+ serverPort = ${serverPort}
308
+
309
+ # Authentication
310
+ auth.method = "token"
311
+ auth.token = "base-token"
312
+
313
+ # CLI auth token (sent to auth plugin for validation)
314
+ metadatas.token = "${authToken}"
315
+ metadatas.tunnelId = "${tunnelId}"
316
+
317
+ # TLS
318
+ transport.tls.enable = ${tlsEnabled}
319
+
320
+ # Logging
321
+ log.to = "console"
322
+ log.level = "info"
323
+
324
+ # Proxy configuration
325
+ [[proxies]]
326
+ name = "${tunnelId}"
327
+ type = "http"
328
+ localPort = ${proxy.localPort}
329
+ subdomain = "${proxy.subdomain}"
330
+ `;
331
+ writeFileSync(this.configPath, config, "utf-8");
332
+ }
333
+ /**
334
+ * Find frpc binary in various locations
335
+ */
336
+ async findFrpcBinary() {
337
+ const { execSync } = await import("child_process");
338
+ for (const path$1 of [
339
+ "frpc",
340
+ "/opt/homebrew/bin/frpc",
341
+ "/usr/local/bin/frpc",
342
+ "/usr/bin/frpc"
343
+ ]) try {
344
+ execSync(`${path$1} --version`, { stdio: "ignore" });
345
+ return path$1;
346
+ } catch {
347
+ continue;
348
+ }
349
+ return null;
350
+ }
351
+ };
352
+
353
+ //#endregion
354
+ export { setConfigPath as a, getCustomConfigPath as i, ApiClient as n, Config as r, FrpWrapper as t };
355
+ //# sourceMappingURL=frp-wrapper-DgAvulMB.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frp-wrapper-DgAvulMB.mjs","names":["customConfigPath: string | null","CONFIG_DEFAULTS: Partial<ChaitunnelConfig>","path"],"sources":["../src/lib/config.ts","../src/lib/api-client.ts","../src/lib/frp-wrapper.ts"],"sourcesContent":["/**\n * Configuration management for chaitunnel CLI\n *\n * Uses shared config file with chaiterm at:\n * - Custom path via --config flag\n * - Default: ~/.config/chaiterm/config.json\n *\n * Auth flow (ngrok-style):\n * 1. User gets authtoken from web dashboard\n * 2. Runs: chaitunnel authtoken <token>\n * 3. Token saved here, used for all API calls\n */\n\nimport Conf from \"conf\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport type { ChaitunnelConfig } 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 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// Production defaults\nconst CONFIG_DEFAULTS: Partial<ChaitunnelConfig> = {\n apiUrl: \"https://chaiterm.com\",\n frpServerAddr: \"frp.chaiterm.com\",\n frpServerPort: 7000,\n tlsEnabled: true,\n publicUrlTemplate: \"https://{subdomain}.chaiterm.com\",\n};\n\nexport class Config {\n private store: Conf<ChaitunnelConfig>;\n private customPath: string | null;\n\n constructor() {\n this.customPath = customConfigPath;\n\n if (this.customPath) {\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<ChaitunnelConfig>({\n projectName: \"chaiterm\", // Same project name to share config\n defaults: CONFIG_DEFAULTS as ChaitunnelConfig,\n cwd: dir,\n configName: path.basename(this.customPath, \".json\"),\n });\n } else {\n this.store = new Conf<ChaitunnelConfig>({\n projectName: \"chaiterm\", // Same project name to share config\n defaults: CONFIG_DEFAULTS as ChaitunnelConfig,\n });\n }\n }\n\n /**\n * Check if user is authenticated (has authtoken)\n */\n isAuthenticated(): boolean {\n return !!this.store.get(\"authToken\");\n }\n\n /**\n * Get auth token (ngrok-style token from dashboard)\n */\n getAuthToken(): string | undefined {\n return this.store.get(\"authToken\");\n }\n\n /**\n * Set auth token (from chaitunnel authtoken <token>)\n */\n setAuthToken(token: string): void {\n this.store.set(\"authToken\", token);\n }\n\n /**\n * Get user ID (extracted from token validation response)\n */\n getUserId(): string | undefined {\n return this.store.get(\"userId\");\n }\n\n /**\n * Set user ID\n */\n setUserId(userId: string): void {\n this.store.set(\"userId\", userId);\n }\n\n /**\n * Get user email\n */\n getEmail(): string | undefined {\n return this.store.get(\"email\");\n }\n\n /**\n * Set user email\n */\n setEmail(email: string): void {\n this.store.set(\"email\", email);\n }\n\n /**\n * Get API base URL\n */\n getApiUrl(): string {\n return this.store.get(\"apiUrl\") || \"https://chaiterm.com\";\n }\n\n /**\n * Get frp server address\n */\n getFrpServerAddr(): string {\n return this.store.get(\"frpServerAddr\") || \"frp.chaiterm.com\";\n }\n\n /**\n * Get frp server port\n */\n getFrpServerPort(): number {\n return this.store.get(\"frpServerPort\") || 7000;\n }\n\n /**\n * Get TLS enabled setting\n */\n getTlsEnabled(): boolean {\n const value = this.store.get(\"tlsEnabled\");\n return value !== undefined ? value : true;\n }\n\n /**\n * Get public URL template\n */\n getPublicUrlTemplate(): string {\n return this.store.get(\"publicUrlTemplate\") || \"https://{subdomain}.chaiterm.com\";\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 * API Client for chaitunnel CLI\n *\n * Makes HTTP calls to the tRPC API for tunnel management.\n */\n\nimport type { CreateTunnelResponse, TunnelInfo } from \"../types.js\";\n\nexport class ApiClient {\n private baseUrl: string;\n private token: string;\n\n constructor(baseUrl: string, token: string) {\n this.baseUrl = baseUrl.replace(/\\/$/, \"\"); // Remove trailing slash\n this.token = token;\n }\n\n /**\n * Create a new tunnel\n */\n async createTunnel(localPort: number): Promise<CreateTunnelResponse> {\n const response = await this.trpcMutation(\"tunnel.create\", { localPort });\n return response.result.data.json as CreateTunnelResponse;\n }\n\n /**\n * List user's tunnels\n */\n async listTunnels(): Promise<TunnelInfo[]> {\n const response = await this.trpcQuery(\"tunnel.list\");\n return response.result.data.json as TunnelInfo[];\n }\n\n /**\n * Delete a tunnel\n */\n async deleteTunnel(id: string): Promise<void> {\n await this.trpcMutation(\"tunnel.delete\", { id });\n }\n\n /**\n * Get a single tunnel\n */\n async getTunnel(id: string): Promise<TunnelInfo> {\n const response = await this.trpcQuery(\"tunnel.get\", { id });\n return response.result.data.json as TunnelInfo;\n }\n\n /**\n * Make a tRPC query (GET)\n */\n private async trpcQuery(\n procedure: string,\n input?: Record<string, unknown>\n ): Promise<{ result: { data: { json: unknown } } }> {\n const url = new URL(`${this.baseUrl}/trpc/${procedure}`);\n if (input) {\n url.searchParams.set(\"input\", JSON.stringify(input));\n }\n\n const response = await fetch(url.toString(), {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${this.token}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`API error: ${response.status} - ${error}`);\n }\n\n return response.json() as Promise<{ result: { data: { json: unknown } } }>;\n }\n\n /**\n * Make a tRPC mutation (POST)\n */\n private async trpcMutation(\n procedure: string,\n input: Record<string, unknown>\n ): Promise<{ result: { data: { json: unknown } } }> {\n const response = await fetch(`${this.baseUrl}/trpc/${procedure}`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ json: input }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`API error: ${response.status} - ${error}`);\n }\n\n return response.json() as Promise<{ result: { data: { json: unknown } } }>;\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\";\n\nexport interface FrpProxyConfig {\n /** Tunnel ID (used as proxy name) */\n tunnelId: string;\n /** Subdomain allocated by server */\n subdomain: string;\n /** Local port to expose */\n localPort: number;\n}\n\nexport interface FrpWrapperOptions {\n serverAddr: string;\n serverPort: number;\n /** CLI auth token for frp authentication */\n authToken: string;\n tunnelId: string;\n /** Enable TLS for frp connection */\n tlsEnabled: boolean;\n proxy: FrpProxyConfig;\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(\n tmpdir(),\n \"chaitunnel\",\n `frpc-${options.tunnelId}.toml`\n );\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(), \"chaitunnel\");\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 install frpc (brew install frpc on macOS)\"\n );\n }\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\", () => {\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 - could not connect to frp server\"));\n }\n }, 30000);\n });\n }\n\n /**\n * Stop frpc process\n */\n stop(): void {\n if (this.process) {\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, authToken, tunnelId, tlsEnabled, proxy } = this.options;\n\n const config = `# Auto-generated by chaitunnel CLI\n# Tunnel: ${tunnelId}\n\nserverAddr = \"${serverAddr}\"\nserverPort = ${serverPort}\n\n# Authentication\nauth.method = \"token\"\nauth.token = \"base-token\"\n\n# CLI auth token (sent to auth plugin for validation)\nmetadatas.token = \"${authToken}\"\nmetadatas.tunnelId = \"${tunnelId}\"\n\n# TLS\ntransport.tls.enable = ${tlsEnabled}\n\n# Logging\nlog.to = \"console\"\nlog.level = \"info\"\n\n# Proxy configuration\n[[proxies]]\nname = \"${tunnelId}\"\ntype = \"http\"\nlocalPort = ${proxy.localPort}\nsubdomain = \"${proxy.subdomain}\"\n`;\n\n writeFileSync(this.configPath, config, \"utf-8\");\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 const possiblePaths = [\n // In PATH\n \"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"],"mappings":"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"}
@@ -0,0 +1,222 @@
1
+ //#region src/lib/config.d.ts
2
+ /**
3
+ * Configuration management for chaitunnel CLI
4
+ *
5
+ * Uses shared config file with chaiterm at:
6
+ * - Custom path via --config flag
7
+ * - Default: ~/.config/chaiterm/config.json
8
+ *
9
+ * Auth flow (ngrok-style):
10
+ * 1. User gets authtoken from web dashboard
11
+ * 2. Runs: chaitunnel authtoken <token>
12
+ * 3. Token saved here, used for all API calls
13
+ */
14
+ /**
15
+ * Set custom config path (called before Config instantiation)
16
+ */
17
+ declare function setConfigPath(configPath: string): void;
18
+ /**
19
+ * Get the custom config path if set
20
+ */
21
+ declare function getCustomConfigPath(): string | null;
22
+ declare class Config {
23
+ private store;
24
+ private customPath;
25
+ constructor();
26
+ /**
27
+ * Check if user is authenticated (has authtoken)
28
+ */
29
+ isAuthenticated(): boolean;
30
+ /**
31
+ * Get auth token (ngrok-style token from dashboard)
32
+ */
33
+ getAuthToken(): string | undefined;
34
+ /**
35
+ * Set auth token (from chaitunnel authtoken <token>)
36
+ */
37
+ setAuthToken(token: string): void;
38
+ /**
39
+ * Get user ID (extracted from token validation response)
40
+ */
41
+ getUserId(): string | undefined;
42
+ /**
43
+ * Set user ID
44
+ */
45
+ setUserId(userId: string): void;
46
+ /**
47
+ * Get user email
48
+ */
49
+ getEmail(): string | undefined;
50
+ /**
51
+ * Set user email
52
+ */
53
+ setEmail(email: string): void;
54
+ /**
55
+ * Get API base URL
56
+ */
57
+ getApiUrl(): string;
58
+ /**
59
+ * Get frp server address
60
+ */
61
+ getFrpServerAddr(): string;
62
+ /**
63
+ * Get frp server port
64
+ */
65
+ getFrpServerPort(): number;
66
+ /**
67
+ * Get TLS enabled setting
68
+ */
69
+ getTlsEnabled(): boolean;
70
+ /**
71
+ * Get public URL template
72
+ */
73
+ getPublicUrlTemplate(): string;
74
+ /**
75
+ * Clear all configuration (logout)
76
+ */
77
+ clear(): void;
78
+ /**
79
+ * Get config file path (for debugging)
80
+ */
81
+ getPath(): string;
82
+ /**
83
+ * Check if using a custom config path
84
+ */
85
+ isCustomConfig(): boolean;
86
+ }
87
+ //#endregion
88
+ //#region src/types.d.ts
89
+ /**
90
+ * Shared types for chaitunnel CLI
91
+ */
92
+ interface ChaitunnelConfig {
93
+ /** Auth token from dashboard (ngrok-style) */
94
+ authToken: string;
95
+ /** User ID (from token validation) */
96
+ userId: string;
97
+ /** User email */
98
+ email: string;
99
+ /** API base URL */
100
+ apiUrl: string;
101
+ /** frp server address */
102
+ frpServerAddr: string;
103
+ /** frp server port */
104
+ frpServerPort: number;
105
+ /** Enable TLS for frp connection (default: true) */
106
+ tlsEnabled: boolean;
107
+ /** Public URL template with {subdomain} placeholder */
108
+ publicUrlTemplate: string;
109
+ }
110
+ interface TunnelInfo {
111
+ /** Tunnel ID from server */
112
+ id: string;
113
+ /** Subdomain (e.g., "k7x9m2") */
114
+ subdomain: string;
115
+ /** Local port being exposed */
116
+ localPort: number;
117
+ /** Tunnel status */
118
+ status: "active" | "inactive" | "deleted";
119
+ /** Creation timestamp */
120
+ createdAt: string;
121
+ }
122
+ interface CreateTunnelResponse {
123
+ id: string;
124
+ subdomain: string;
125
+ localPort: number;
126
+ status: string;
127
+ }
128
+ interface TunnelStatus {
129
+ subdomain: string;
130
+ localPort: number;
131
+ status: "connected" | "disconnected" | "connecting";
132
+ }
133
+ //#endregion
134
+ //#region src/lib/api-client.d.ts
135
+ declare class ApiClient {
136
+ private baseUrl;
137
+ private token;
138
+ constructor(baseUrl: string, token: string);
139
+ /**
140
+ * Create a new tunnel
141
+ */
142
+ createTunnel(localPort: number): Promise<CreateTunnelResponse>;
143
+ /**
144
+ * List user's tunnels
145
+ */
146
+ listTunnels(): Promise<TunnelInfo[]>;
147
+ /**
148
+ * Delete a tunnel
149
+ */
150
+ deleteTunnel(id: string): Promise<void>;
151
+ /**
152
+ * Get a single tunnel
153
+ */
154
+ getTunnel(id: string): Promise<TunnelInfo>;
155
+ /**
156
+ * Make a tRPC query (GET)
157
+ */
158
+ private trpcQuery;
159
+ /**
160
+ * Make a tRPC mutation (POST)
161
+ */
162
+ private trpcMutation;
163
+ }
164
+ //#endregion
165
+ //#region src/lib/frp-wrapper.d.ts
166
+ /**
167
+ * frp Wrapper
168
+ *
169
+ * Wraps the frpc binary for port exposure functionality.
170
+ * Generates config and spawns frpc process.
171
+ */
172
+ interface FrpProxyConfig {
173
+ /** Tunnel ID (used as proxy name) */
174
+ tunnelId: string;
175
+ /** Subdomain allocated by server */
176
+ subdomain: string;
177
+ /** Local port to expose */
178
+ localPort: number;
179
+ }
180
+ interface FrpWrapperOptions {
181
+ serverAddr: string;
182
+ serverPort: number;
183
+ /** CLI auth token for frp authentication */
184
+ authToken: string;
185
+ tunnelId: string;
186
+ /** Enable TLS for frp connection */
187
+ tlsEnabled: boolean;
188
+ proxy: FrpProxyConfig;
189
+ onConnect?: () => void;
190
+ onDisconnect?: () => void;
191
+ onError?: (error: Error) => void;
192
+ onLog?: (message: string) => void;
193
+ }
194
+ declare class FrpWrapper {
195
+ private options;
196
+ private process;
197
+ private configPath;
198
+ constructor(options: FrpWrapperOptions);
199
+ /**
200
+ * Start frpc with current configuration
201
+ */
202
+ start(): Promise<void>;
203
+ /**
204
+ * Stop frpc process
205
+ */
206
+ stop(): void;
207
+ /**
208
+ * Check if frpc is running
209
+ */
210
+ isRunning(): boolean;
211
+ /**
212
+ * Generate frpc config file
213
+ */
214
+ private generateConfig;
215
+ /**
216
+ * Find frpc binary in various locations
217
+ */
218
+ private findFrpcBinary;
219
+ }
220
+ //#endregion
221
+ export { ApiClient, type ChaitunnelConfig, Config, type CreateTunnelResponse, type FrpProxyConfig, FrpWrapper, type FrpWrapperOptions, type TunnelInfo, type TunnelStatus, getCustomConfigPath, setConfigPath };
222
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/config.ts","../src/types.ts","../src/lib/api-client.ts","../src/lib/frp-wrapper.ts"],"sourcesContent":[],"mappings":";;AAwBA;AAOA;AAaA;;;;ACxCA;AAmBA;AAaA;AAOA;;;;ACnCA;AAYiD,iBFIjC,aAAA,CEJiC,UAAA,EAAA,MAAA,CAAA,EAAA,IAAA;;;;AAgBf,iBFLlB,mBAAA,CAAA,CEKkB,EAAA,MAAA,GAAA,IAAA;AAOK,cFC1B,MAAA,CED0B;EAAR,QAAA,KAAA;EAAO,QAAA,UAAA;;;;AC/BtC;EASiB,eAAA,CAAA,CAAA,EAAA,OAAiB;EAerB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AHZb;AAOA;AAaa,UCxCI,gBAAA,CDwCE;;;;ECxCF,MAAA,EAAA,MAAA;EAmBA;EAaA,KAAA,EAAA,MAAA;EAOA;;;;ECnCJ;EAYoC,aAAA,EAAA,MAAA;EAAR;EAQV,UAAA,EAAA,OAAA;EAAR;EAQW,iBAAA,EAAA,MAAA;;AAOH,UDpBd,UAAA,CCoBc;EAAO;;;;EC/BrB;EASA,SAAA,EAAA,MAAA;EAeJ;;;;;UFAI,oBAAA;;;;;;UAOA,YAAA;;;;;;;cCnCJ,SAAA;EDJI,QAAA,OAAA;EAmBA,QAAA,KAAU;EAaV,WAAA,CAAA,OAAA,EAAA,MAAoB,EAAA,KAAA,EAAA,MAAA;EAOpB;;;mCCvBwB,QAAQ;EAZpC;;;EAoBkB,WAAA,CAAA,CAAA,EAAR,OAAQ,CAAA,UAAA,EAAA,CAAA;EAAR;;;EAeQ,YAAA,CAAA,EAAA,EAAA,MAAA,CAAA,EAPG,OAOH,CAAA,IAAA,CAAA;EAAO;;;yBAAP,QAAQ;EC/BtB;AASjB;AAeA;;;;;;;;;;AHZA;AAOA;AAaA;;;UGhCiB,cAAA;EFRA;EAmBA,QAAA,EAAA,MAAU;EAaV;EAOA,SAAA,EAAA,MAAY;;;;ACnChB,UCaI,iBAAA,CDbK;EAY2B,UAAA,EAAA,MAAA;EAAR,UAAA,EAAA,MAAA;EAQV;EAAR,SAAA,EAAA,MAAA;EAQW,QAAA,EAAA,MAAA;EAOK;EAAR,UAAA,EAAA,OAAA;EAAO,KAAA,ECd7B,cDc6B;;;oBCXlB;EApBH,KAAA,CAAA,EAAA,CAAA,OAAA,EAAc,MAAA,EAAA,GAAA,IAAA;AAS/B;AAea,cAAA,UAAA,CAKU;;;;uBAAA;;;;WAYN"}
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { a as setConfigPath, i as getCustomConfigPath, n as ApiClient, r as Config, t as FrpWrapper } from "./frp-wrapper-DgAvulMB.mjs";
2
+
3
+ export { ApiClient, Config, FrpWrapper, getCustomConfigPath, setConfigPath };
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "chaitunnel",
3
+ "version": "0.0.1",
4
+ "description": "Expose local ports to the internet - tunnel CLI for chaiterm",
5
+ "type": "module",
6
+ "bin": {
7
+ "chaitunnel": "dist/bin/chaitunnel.mjs"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "default": "./dist/index.mjs"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsdown",
19
+ "dev": "tsdown --watch",
20
+ "check-types": "tsc --noEmit"
21
+ },
22
+ "keywords": [
23
+ "tunnel",
24
+ "port-forwarding",
25
+ "frp",
26
+ "cli",
27
+ "expose",
28
+ "chaiterm",
29
+ "localhost"
30
+ ],
31
+ "author": "Kerem Atam",
32
+ "license": "MIT",
33
+ "homepage": "https://chaiterm.com",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/anthropics/chaiterm.git"
37
+ },
38
+ "devDependencies": {
39
+ "typescript": "^5.8.3",
40
+ "@types/node": "^22.15.29",
41
+ "tsdown": "^0.18.3"
42
+ },
43
+ "dependencies": {
44
+ "commander": "^13.1.0",
45
+ "chalk": "^5.4.1",
46
+ "conf": "^13.1.0",
47
+ "open": "^10.2.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=18"
51
+ }
52
+ }