#!/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 ] 跟看 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); });