This commit is contained in:
bipproduction
2025-10-06 08:32:50 +08:00
parent 7e4791dc4b
commit 3f1e9b7473
11 changed files with 276 additions and 174 deletions

View File

@@ -1,15 +1,18 @@
#!/usr/bin/env bun #!/usr/bin/env bun
import minimist from "minimist"; import minimist from "minimist";
import os from "os";
import path from "path"; import path from "path";
import { generateEnvTypes } from "../generate/env.generate";
import checkPort from "./src/port";
import route from "./src/route";
import compose from "./src/compose"; import compose from "./src/compose";
import generateDockerfile from "./src/docker-file"; import generateDockerfile from "./src/docker-file";
import frp from "./src/frp"; import frp from "./src/frp";
import { generateEnvTypes } from "./src/generate/env.generate";
import checkPort from "./src/port";
import route from "./src/route";
import { version } from '../package.json' assert { type: 'json' }; import { version } from '../package.json' assert { type: 'json' };
import appCreate from "./src/app-create";
interface CheckPortResult { interface CheckPortResult {
port: number; port: number;
@@ -36,6 +39,7 @@ Commands:
compose Generate compose.yml from name compose Generate compose.yml from name
docker-file Generate Dockerfile docker-file Generate Dockerfile
frp Show frp proxy list frp Show frp proxy list
app-init Generate app-init.ts
Options: Options:
--env Path ke file .env (default: .env) --env Path ke file .env (default: .env)
@@ -51,10 +55,28 @@ Examples:
g3n compose <name> g3n compose <name>
g3n docker-file g3n docker-file
g3n frp g3n frp
g3n app-init
Version: ${version} Version: ${version}
`; `;
const g3nConf = path.join(os.homedir(), ".g3n.conf");
if (!(await Bun.file(g3nConf).exists())) {
const conf = `
# CODE
CODE_TOKEN=
# FRP
FRP_HOST=
FRP_USER=
FRP_SECRET=
`
Bun.write(g3nConf, conf);
console.log(`✅ G3N config created at ${g3nConf}`);
}
// Parse CLI arguments // Parse CLI arguments
const args = minimist(process.argv.slice(2)); const args = minimist(process.argv.slice(2));
@@ -90,6 +112,13 @@ async function main(): Promise<void> {
process.exit(1); process.exit(1);
}); });
break; break;
case "app-create":
if (!name) {
console.error("❌ App name is required");
return;
}
appCreate({ appName: name });
break;
default: default:
console.error(HELP_TEXT); console.error(HELP_TEXT);

160
bin/src/app-create.ts Normal file
View File

@@ -0,0 +1,160 @@
import ora from "ora"
const appRoutesTemplate = `
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import NotFound from "./pages/NotFound";
export default function AppRoutes() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
`
const postCssTemplate = `
module.exports = {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
variables: {
'mantine-breakpoint-xs': '36em',
'mantine-breakpoint-sm': '48em',
'mantine-breakpoint-md': '62em',
'mantine-breakpoint-lg': '75em',
'mantine-breakpoint-xl': '88em',
},
},
},
};
`
const appTemplate = `
import '@mantine/core/styles.css';
import { MantineProvider } from '@mantine/core';
import AppRoutes from './AppRoutes';
export function App() {
return <MantineProvider>
<AppRoutes />
</MantineProvider>;
}
`
const homeTemplate = `
export default function Home() {
return (
<div>
<h1>Home</h1>
</div>
);
}
`
const serverTemplate = `
import Elysia from "elysia";
import Swagger from "@elysiajs/swagger";
import html from "./index.html"
const Docs = new Elysia({})
.use(Swagger({
path: "/docs",
}))
const Api = new Elysia({
prefix: "/api",
})
.use(Docs)
.post("/hello", () => "Hello, world!")
const app = new Elysia()
.use(Api)
.get("/*", html)
.listen(3000, () => {
console.log("Server running at http://localhost:3000");
});
export type Server = typeof app;
`
const notFoundTemplate = `
export default function NotFound() {
return (
<div>
<h1>404 Not Found</h1>
</div>
);
}
`
const cmd = (appName: string) => `
bun init --react ${appName}
echo "init react"
cd ${appName}
echo "cd ${appName}"
bun add react-router-dom
echo "add react-router-dom"
bun add @mantine/core @mantine/hooks
echo "add @mantine/core @mantine/hooks"
bun add --dev postcss postcss-preset-mantine postcss-simple-vars
echo "add --dev postcss postcss-preset-mantine postcss-simple-vars"
bun add elysia @elysiajs/cors @elysiajs/swagger @elysiajs/eden
echo "add elysia @elysiajs/cors @elysiajs/swagger @elysiajs/eden"
cat <<EOF > postcss.config.js
${postCssTemplate}
EOF
echo "postcss.config.js"
cat <<EOF > src/App.tsx
${appTemplate}
EOF
echo "src/App.tsx"
cat <<EOF > src/AppRoutes.tsx
${appRoutesTemplate}
EOF
echo "src/AppRoutes.tsx"
mkdir src/pages
echo "mkdir src/pages"
cat <<EOF > src/pages/Home.tsx
${homeTemplate}
EOF
echo "src/pages/Home.tsx"
cat <<EOF > src/index.tsx
${serverTemplate}
EOF
echo "src/index.tsx"
cat <<EOF > src/pages/NotFound.tsx
${notFoundTemplate}
EOF
echo "src/pages/NotFound.tsx"
rm src/APITester.tsx
ls
echo "✅ done"
`
export default async function appCreate({ appName }: { appName: string }) {
const spinner = ora(`Creating app ${appName}...`).start();
const { stdout } = Bun.spawnSync(["bash", "-c", cmd(appName)]);
spinner.stop();
console.log(stdout.toString());
}

41
bin/src/code.ts Normal file
View File

@@ -0,0 +1,41 @@
import path from "path";
import ora from "ora";
async function query({ question }: { question: string }) {
const spinner = ora("Processing...").start();
const response = await fetch(
"https://cloud-aiflow.wibudev.com/api/v1/prediction/4da85628-c638-43d3-9491-4cd0a7e6b1b8",
{
headers: {
Authorization: "Bearer v3WdPjn61bNDsEYCO5_LYPRs16ICKjpQE6lF60DjpNo",
"Content-Type": "application/json"
},
method: "POST",
body: JSON.stringify({ question })
}
);
const result: { text: string } = await response.json() as { text: string };
spinner.stop();
return result.text;
}
async function code({ sourcePath }: { sourcePath: string }) {
const file = Bun.file(sourcePath);
const content = await file.text();
const result = await query({
question: content
});
const output = `${path.dirname(sourcePath)}/${path.basename(sourcePath, path.extname(sourcePath))}.md`;
Bun.write(output, result);
console.log(`✅ Code generated at ${output}`);
}
if (import.meta.main) {
code({ sourcePath: "bin/g3n.ts" });
}
export default code;

View File

@@ -3,7 +3,7 @@ import { promises as fs } from "fs";
import * as os from "os"; import * as os from "os";
import * as path from "path"; import * as path from "path";
const CONFIG_FILE = path.join(os.homedir(), ".frpdev.conf"); const CONFIG_FILE = path.join(os.homedir(), ".g3n.conf");
interface FrpConfig { interface FrpConfig {
FRP_HOST: string; FRP_HOST: string;

View File

@@ -12,6 +12,7 @@
"dedent": "^1.7.0", "dedent": "^1.7.0",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"minimist": "^1.2.8", "minimist": "^1.2.8",
"ora": "^9.0.0",
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5", "typescript": "^5",
@@ -49,22 +50,56 @@
"@types/minimist": ["@types/minimist@1.2.5", "", {}, "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag=="], "@types/minimist": ["@types/minimist@1.2.5", "", {}, "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag=="],
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
"cli-spinners": ["cli-spinners@3.3.0", "", {}, "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"dedent": ["dedent@1.7.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ=="], "dedent": ["dedent@1.7.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ=="],
"dotenv": ["dotenv@17.2.1", "", {}, "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ=="], "dotenv": ["dotenv@17.2.1", "", {}, "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ=="],
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="],
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
"ora": ["ora@9.0.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.2.2", "string-width": "^8.1.0", "strip-ansi": "^7.1.2" } }, "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
"string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="],
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
"yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
} }
} }

1
compose_dir.txt Normal file
View File

@@ -0,0 +1 @@
"https://cld-dkr-makuro-seafile.wibudev.com/seafhttp/files/03c405b3-1ff0-4dff-9283-e18832a7f8d4/compose_dir.txt"

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bun #!/usr/bin/env bun
import minimist from "minimist"; import minimist from "minimist";
import { generateEnvTypes } from "./generate/env.generate.js"; import { generateEnvTypes } from "./bin/src/generate/env.generate.ts";
import path from "path"; import path from "path";
import net from "net"; import net from "net";

View File

@@ -1,6 +1,6 @@
{ {
"name": "g3n", "name": "g3n",
"version": "1.0.9", "version": "1.0.13",
"type": "module", "type": "module",
"bin": { "bin": {
"g3n": "./bin/g3n.ts" "g3n": "./bin/g3n.ts"
@@ -16,6 +16,7 @@
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
"dedent": "^1.7.0", "dedent": "^1.7.0",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"minimist": "^1.2.8" "minimist": "^1.2.8",
"ora": "^9.0.0"
} }
} }

2
x.sh Normal file
View File

@@ -0,0 +1,2 @@
curl -o compose_dir.txt -H "Authorization: Bearer fa49bf1774cad2ec89d2882ae2c6ac1f5d7df445" \
"https://cld-dkr-makuro-seafile.wibudev.com/api2/repos/e23626dc-cc18-4bb8-8fbc-d103b7d33bc8/file/?p=/compose_dir.txt&op=download"

167
x.ts
View File

@@ -1,167 +0,0 @@
#!/usr/bin/env bun
import { promises as fs } from "fs";
import * as os from "os";
import * as path from "path";
const CONFIG_FILE = path.join(os.homedir(), ".frpdev.conf");
interface FrpConfig {
FRP_HOST: string;
FRP_PORT: string;
FRP_USER: string;
FRP_SECRET: string;
FRP_PROTO: string;
}
interface ProxyConf {
type?: string;
remotePort?: number;
subdomain?: string;
customDomains?: string[];
}
interface Proxy {
name?: string;
status?: string;
conf?: ProxyConf;
}
interface ProxyResponse {
proxies?: Proxy[];
}
async function ensureConfigFile(): Promise<void> {
try {
await fs.access(CONFIG_FILE);
} catch {
const template = `
FRP_HOST=""
FRP_USER=""
FRP_SECRET=""
`;
console.error(`❌ Config not found. Template created at: ${CONFIG_FILE}`);
console.log(template);
process.exit(1);
}
}
async function loadConfig(): Promise<FrpConfig> {
await ensureConfigFile();
const raw = await fs.readFile(CONFIG_FILE, "utf8");
const lines = raw
.split("\n")
.map((line) => line.trim())
.filter(Boolean);
const conf: Record<string, string> = {};
for (const line of lines) {
const [key, ...rest] = line.split("=");
if (!key) continue;
let value = rest.join("=").trim();
if (value.startsWith('"') && value.endsWith('"')) {
value = value.slice(1, -1);
}
conf[key] = value;
}
return {
FRP_HOST: conf.FRP_HOST || "",
FRP_PORT: "443",
FRP_USER: conf.FRP_USER || "",
FRP_SECRET: conf.FRP_SECRET || "",
FRP_PROTO: "https",
};
}
async function fetchFrp(config: FrpConfig, url: string): Promise<ProxyResponse> {
const fullUrl = `${config.FRP_PROTO}://${config.FRP_HOST}:${config.FRP_PORT}${url}`;
try {
const resp = await fetch(fullUrl, {
headers: {
Authorization:
"Basic " +
Buffer.from(`${config.FRP_USER}:${config.FRP_SECRET}`).toString("base64"),
},
});
if (!resp.ok) return { proxies: [] };
return (await resp.json()) as ProxyResponse;
} catch {
return { proxies: [] };
}
}
function sortProxies(proxies: Proxy[]): Proxy[] {
return [...proxies].sort((a, b) => {
const order = (status?: string) =>
status?.toLowerCase() === "online" || status?.toLowerCase() === "running"
? 0
: 1;
return order(a.status) - order(b.status);
});
}
function formatTable(headers: string[], rows: string[][]): string {
const allRows = [headers, ...rows];
const colWidths = headers.map((_, i) =>
Math.max(...allRows.map((row) => (row[i] || "").length)),
);
return allRows
.map((row) =>
row.map((cell, i) => (cell || "").padEnd(colWidths[i] ?? 0)).join(" ").trimEnd(),
)
.join("\n");
}
async function printTable(
title: string,
headers: string[],
rows: string[][],
): Promise<void> {
console.log(`========== ${title} ==========`);
if (rows.length === 0) {
console.log("No proxies found.\n");
return;
}
console.log(formatTable(headers, rows));
console.log();
}
async function main(): Promise<void> {
const config = await loadConfig();
const [tcpResp, httpResp] = await Promise.all([
fetchFrp(config, "/api/proxy/tcp"),
fetchFrp(config, "/api/proxy/http"),
]);
const tcpRows: string[][] = sortProxies(tcpResp.proxies || []).map((p) => [
p.name ?? "-",
p.status ?? "-",
p.conf?.remotePort?.toString() ?? "-",
]);
await printTable("TCP PROXIES", ["NAME", "STATUS", "PORT"], tcpRows);
const httpRows: string[][] = sortProxies(httpResp.proxies || []).map((p) => [
p.name ?? "-",
p.status ?? "-",
Array.isArray(p.conf?.customDomains) ? p.conf.customDomains.join(",") : "",
]);
await printTable(
"HTTP PROXIES",
["NAME", "STATUS", "CUSTOM_DOMAIN"],
httpRows,
);
}
main().catch((err) => {
console.error("❌ Error:", err);
process.exit(1);
});