Files

181 lines
6.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
'use strict';
// telegram-bot CLIstart 把 web 控制台掛上 pm2
// bot 的新增(引擎、token、工作目錄、權限)與啟停全部在網頁控制台操作。
const { execSync, spawn } = require('child_process');
const pm2 = require('../src/pm2util');
const HELP = `telegram-bot — 用 Telegram 操控本機 AI CLICodex / Claude Code)的 bot
用法:
telegram-bot start 檢查環境(codex/claude/pm2/git)並啟動 web 控制台(掛 pm2)
telegram-bot start --port 3799 指定控制台 port(會記住)
telegram-bot doctor 只做環境檢查
telegram-bot stop 停止控制台(bot 不受影響)
telegram-bot restart 重啟控制台
telegram-bot delete 從 pm2 移除控制台
telegram-bot status 顯示控制台與所有 bot 狀態
telegram-bot logs [--name <bot>] 跟看 log(不帶 --name 看控制台)
telegram-bot web 前景執行控制台(不經 pm2,除錯用)
bot 的新增 / 引擎(Codex 或 Claude/ token / 工作目錄 / 啟停,都在控制台網頁的「🤖 Bot 管理」分頁設定。
短別名:tgbot
`;
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 / claude(至少一個)/ 登入 / 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);
}
}
requirePm2();
// 從 telegram-codex-bot0.1.x)升級:搬家目錄、把 bot 程序切到新套件
const { migrateLegacy } = require('../src/migrate');
const notes = await migrateLegacy();
for (const n of notes) console.log(n);
if (notes.length) console.log('');
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(選 Codex 或 Claude 引擎,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)}`);
console.log(`設定目錄:${pm2.TGBOT_HOME}`);
const bots = await listBots();
if (bots.length === 0) {
console.log('還沒有 bot。打開控制台網頁新增。');
return;
}
console.table(bots.map((b) => ({
name: b.name,
engine: b.engine,
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);
});