171 lines
5.8 KiB
JavaScript
171 lines
5.8 KiB
JavaScript
#!/usr/bin/env node
|
||||
|
|
'use strict';
|
|||
|
|
// telegram-codex-bot CLI:start 把 web 控制台掛上 pm2;
|
|||
|
|
// bot 的新增(token、工作目錄、權限)與啟停全部在網頁控制台操作。
|
|||
|
|
const { execSync, spawn } = require('child_process');
|
|||
|
|
const pm2 = require('../src/pm2util');
|
|||
|
|
|
|||
|
|
const HELP = `telegram-codex-bot — 用 Telegram 操控本機 Codex CLI 的 bot
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
telegram-codex-bot start 檢查環境(codex/pm2/git)並啟動 web 控制台(掛 pm2)
|
|||
|
|
telegram-codex-bot start --port 3799 指定控制台 port(會記住)
|
|||
|
|
telegram-codex-bot doctor 只做環境檢查
|
|||
|
|
telegram-codex-bot stop 停止控制台(bot 不受影響)
|
|||
|
|
telegram-codex-bot restart 重啟控制台
|
|||
|
|
telegram-codex-bot delete 從 pm2 移除控制台
|
|||
|
|
telegram-codex-bot status 顯示控制台與所有 bot 狀態
|
|||
|
|
telegram-codex-bot logs [--name <bot>] 跟看 log(不帶 --name 看控制台)
|
|||
|
|
telegram-codex-bot web 前景執行控制台(不經 pm2,除錯用)
|
|||
|
|
|
|||
|
|
bot 的新增 / token / 工作目錄 / 允許名單 / 啟停,都在控制台網頁的「🤖 Bot 管理」分頁設定。
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
function parseArgs(argv) {
|
|||
|
|
const args = { _: [], flags: {} };
|
|||
|
|
for (let i = 0; i < argv.length; i++) {
|
|||
|
|
const a = argv[i];
|
|||
|
|
if (a.startsWith('--')) {
|
|||
|
|
const key = a.slice(2);
|
|||
|
|
const next = argv[i + 1];
|
|||
|
|
if (next !== undefined && !next.startsWith('--')) {
|
|||
|
|
args.flags[key] = next;
|
|||
|
|
i++;
|
|||
|
|
} else {
|
|||
|
|
args.flags[key] = true;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
args._.push(a);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return args;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function requirePm2() {
|
|||
|
|
try {
|
|||
|
|
execSync('pm2 -v', { stdio: 'ignore', windowsHide: true });
|
|||
|
|
} catch {
|
|||
|
|
console.error('找不到 pm2。請先安裝:npm install -g pm2');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function cmdStart(args) {
|
|||
|
|
// 環境檢查:codex CLI / 登入 / pm2 / git
|
|||
|
|
if (!args.flags['skip-checks']) {
|
|||
|
|
const { runChecks, printChecks, hasBlocker } = require('../src/doctor');
|
|||
|
|
const checks = runChecks();
|
|||
|
|
printChecks(checks);
|
|||
|
|
console.log('');
|
|||
|
|
if (hasBlocker(checks)) {
|
|||
|
|
console.error('必要工具缺失,請先安裝上面標 ❌ 的項目再啟動。(確定要硬跑可加 --skip-checks)');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (args.flags.port) {
|
|||
|
|
const settings = pm2.loadConsoleSettings();
|
|||
|
|
settings.port = Number(args.flags.port);
|
|||
|
|
if (!Number.isInteger(settings.port) || settings.port < 1 || settings.port > 65535) {
|
|||
|
|
console.error('--port 必須是 1-65535 的整數');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
if (args.flags.host) settings.host = String(args.flags.host);
|
|||
|
|
pm2.saveConsoleSettings(settings);
|
|||
|
|
}
|
|||
|
|
const { url, alreadyRunning } = await pm2.ensureConsoleOnPm2({ restart: !!args.flags.port });
|
|||
|
|
console.log(`✅ 控制台${alreadyRunning ? '已在執行' : '已掛上 pm2'}(${pm2.CONSOLE_NAME})`);
|
|||
|
|
console.log('');
|
|||
|
|
console.log(`📊 控制台:${url}`);
|
|||
|
|
console.log(' 在「🤖 Bot 管理」分頁新增 bot(token、工作目錄、權限都在裡面設定)。');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function cmdConsoleAction(action) {
|
|||
|
|
requirePm2();
|
|||
|
|
try {
|
|||
|
|
await pm2.actionByName(action, pm2.CONSOLE_NAME);
|
|||
|
|
console.log(`✅ pm2 ${action} ${pm2.CONSOLE_NAME}`);
|
|||
|
|
} catch (err) {
|
|||
|
|
console.warn(`pm2 ${action} ${pm2.CONSOLE_NAME} 失敗:${err.message}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function cmdStatus() {
|
|||
|
|
requirePm2();
|
|||
|
|
const { listBots } = require('../src/registry');
|
|||
|
|
const list = await pm2.jlist();
|
|||
|
|
const settings = pm2.loadConsoleSettings();
|
|||
|
|
const consoleProc = list.find((p) => p.name === pm2.CONSOLE_NAME);
|
|||
|
|
console.log(`控制台(${pm2.CONSOLE_NAME}):${consoleProc ? consoleProc.pm2_env?.status : '未啟動'} ${pm2.consoleUrl(settings)}`);
|
|||
|
|
|
|||
|
|
const bots = await listBots();
|
|||
|
|
if (bots.length === 0) {
|
|||
|
|
console.log('還沒有 bot。打開控制台網頁新增。');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
console.table(bots.map((b) => ({
|
|||
|
|
name: b.name,
|
|||
|
|
status: b.status,
|
|||
|
|
workDir: b.workDir,
|
|||
|
|
sandbox: b.sandbox,
|
|||
|
|
restarts: b.restarts,
|
|||
|
|
})));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function cmdLogs(args) {
|
|||
|
|
requirePm2();
|
|||
|
|
let name = pm2.CONSOLE_NAME;
|
|||
|
|
if (args.flags.name) {
|
|||
|
|
name = String(args.flags.name);
|
|||
|
|
const list = await pm2.jlist();
|
|||
|
|
if (!list.some((p) => p.name === name)) {
|
|||
|
|
console.error(`pm2 上找不到:${name}`);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
const extra = args.flags.err ? ' --err' : '';
|
|||
|
|
const child = spawn(`pm2 logs ${JSON.stringify(name)} --lines 50${extra}`, { shell: true, stdio: 'inherit' });
|
|||
|
|
child.on('exit', (code) => process.exit(code ?? 0));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function cmdWeb(args) {
|
|||
|
|
const { createWebServer } = require('../src/web/server');
|
|||
|
|
const settings = pm2.loadConsoleSettings();
|
|||
|
|
const port = args.flags.port ? Number(args.flags.port) : settings.port;
|
|||
|
|
const host = args.flags.host ? String(args.flags.host) : settings.host;
|
|||
|
|
await createWebServer({ port, host }).listen();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
(async () => {
|
|||
|
|
const args = parseArgs(process.argv.slice(2));
|
|||
|
|
const cmd = args._[0];
|
|||
|
|
switch (cmd) {
|
|||
|
|
case 'start': return cmdStart(args);
|
|||
|
|
case 'doctor': {
|
|||
|
|
const { runChecks, printChecks, hasBlocker } = require('../src/doctor');
|
|||
|
|
const checks = runChecks();
|
|||
|
|
printChecks(checks);
|
|||
|
|
process.exit(hasBlocker(checks) ? 1 : 0);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
case 'stop': return cmdConsoleAction('stop');
|
|||
|
|
case 'restart': return cmdConsoleAction('restart');
|
|||
|
|
case 'delete': return cmdConsoleAction('delete');
|
|||
|
|
case 'status': return cmdStatus(args);
|
|||
|
|
case 'logs': return cmdLogs(args);
|
|||
|
|
case 'web': return cmdWeb(args);
|
|||
|
|
case 'help':
|
|||
|
|
case undefined:
|
|||
|
|
case '--help':
|
|||
|
|
case '-h':
|
|||
|
|
console.log(HELP);
|
|||
|
|
return;
|
|||
|
|
default:
|
|||
|
|
console.error(`未知指令:${cmd}\n`);
|
|||
|
|
console.log(HELP);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
})().catch((err) => {
|
|||
|
|
console.error('❌', err.message);
|
|||
|
|
process.exit(1);
|
|||
|
|
});
|