apexbot 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +307 -0
- package/dist/adapters/manifold.js +166 -0
- package/dist/adapters/telegram.js +133 -0
- package/dist/agent/agentManager.js +315 -0
- package/dist/backtest/backtester.js +24 -0
- package/dist/channels/channelManager.js +72 -0
- package/dist/channels/discord.js +136 -0
- package/dist/channels/telegram.js +186 -0
- package/dist/cli/index.js +838 -0
- package/dist/core/eventBus.js +33 -0
- package/dist/decisionEngine.js +69 -0
- package/dist/eventBus.js +21 -0
- package/dist/gateway/dashboard.js +1009 -0
- package/dist/gateway/index.js +303 -0
- package/dist/index.js +195 -0
- package/dist/math/ev.js +25 -0
- package/dist/math/kelly.js +24 -0
- package/dist/rateLimiter.js +42 -0
- package/dist/safety/failsafe.js +32 -0
- package/dist/safety/llmChecker.js +148 -0
- package/dist/sessions/sessionManager.js +120 -0
- package/dist/strategy/arbitrage.js +151 -0
- package/package.json +78 -0
- package/scripts/install.ps1 +114 -0
- package/scripts/install.sh +146 -0
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* ApexBot CLI - Command line interface
|
|
5
|
+
* Inspired by Clawdbot's elegant CLI design
|
|
6
|
+
* 100% FREE with Ollama (local AI)
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
require("dotenv/config");
|
|
43
|
+
const commander_1 = require("commander");
|
|
44
|
+
const gateway_1 = require("../gateway");
|
|
45
|
+
const telegram_1 = require("../channels/telegram");
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
// Import CLI utilities (CommonJS compatible versions)
|
|
49
|
+
const chalk = require('chalk');
|
|
50
|
+
const boxen = require('boxen');
|
|
51
|
+
const ora = require('ora');
|
|
52
|
+
const inquirer = require('inquirer');
|
|
53
|
+
const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.apexbot');
|
|
54
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
55
|
+
const VERSION = '2026.1.27-1';
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
57
|
+
// ASCII Art Mascot - Fox (Apex predator, clever, open-source friendly)
|
|
58
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
59
|
+
const MASCOT = `
|
|
60
|
+
/\\ /\\
|
|
61
|
+
//\\\\_//\\\\ ____
|
|
62
|
+
\\_ _/ / /
|
|
63
|
+
/ * * \\ /^^^]
|
|
64
|
+
\\_\\O/_/ [ ]
|
|
65
|
+
/ \\_ [ /
|
|
66
|
+
\\ \\_ / /
|
|
67
|
+
[ [ / \\/ _/
|
|
68
|
+
_[ [ \\ /_/
|
|
69
|
+
`;
|
|
70
|
+
const LOGO = `
|
|
71
|
+
█████╗ ██████╗ ███████╗██╗ ██╗██████╗ ██████╗ ████████╗
|
|
72
|
+
██╔══██╗██╔══██╗██╔════╝╚██╗██╔╝██╔══██╗██╔═══██╗╚══██╔══╝
|
|
73
|
+
███████║██████╔╝█████╗ ╚███╔╝ ██████╔╝██║ ██║ ██║
|
|
74
|
+
██╔══██║██╔═══╝ ██╔══╝ ██╔██╗ ██╔══██╗██║ ██║ ██║
|
|
75
|
+
██║ ██║██║ ███████╗██╔╝ ██╗██████╔╝╚██████╔╝ ██║
|
|
76
|
+
╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝
|
|
77
|
+
`;
|
|
78
|
+
const MINI_LOGO = `
|
|
79
|
+
_ ____ _______ ______ ___ _____
|
|
80
|
+
/ \\ | _ \\| ____\\ \\/ / __ ) / _ \\_ _|
|
|
81
|
+
/ _ \\ | |_) | _| \\ /| _ \\| | | || |
|
|
82
|
+
/ ___ \\| __/| |___ / \\| |_) | |_| || |
|
|
83
|
+
/_/ \\_\\_| |_____/_/\\_\\____/ \\___/ |_|
|
|
84
|
+
`;
|
|
85
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
86
|
+
// Helper Functions
|
|
87
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
88
|
+
function showBanner() {
|
|
89
|
+
console.clear();
|
|
90
|
+
console.log(chalk.cyan(LOGO));
|
|
91
|
+
console.log(chalk.gray(` ψ ApexBot ${VERSION} — Your free, private AI assistant ψ`));
|
|
92
|
+
console.log('');
|
|
93
|
+
}
|
|
94
|
+
function showMascot() {
|
|
95
|
+
console.log(chalk.yellow(MASCOT));
|
|
96
|
+
}
|
|
97
|
+
function showSection(title, content) {
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log(chalk.yellow(`● ${title}`));
|
|
100
|
+
console.log('');
|
|
101
|
+
console.log(content);
|
|
102
|
+
}
|
|
103
|
+
function showBox(content, title) {
|
|
104
|
+
console.log(boxen(content, {
|
|
105
|
+
padding: 1,
|
|
106
|
+
margin: 1,
|
|
107
|
+
borderStyle: 'round',
|
|
108
|
+
borderColor: 'cyan',
|
|
109
|
+
title: title,
|
|
110
|
+
titleAlignment: 'center',
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
function loadConfig() {
|
|
114
|
+
try {
|
|
115
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
116
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
// ignore
|
|
121
|
+
}
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
function ensureConfigDir() {
|
|
125
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
126
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function saveConfig(config) {
|
|
130
|
+
ensureConfigDir();
|
|
131
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
132
|
+
}
|
|
133
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
134
|
+
// Ollama Detection
|
|
135
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
136
|
+
async function checkOllama(url = 'http://localhost:11434') {
|
|
137
|
+
try {
|
|
138
|
+
const res = await fetch(`${url}/api/tags`);
|
|
139
|
+
if (res.ok) {
|
|
140
|
+
const data = await res.json();
|
|
141
|
+
const models = (data.models || []).map((m) => m.name);
|
|
142
|
+
return { running: true, models };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
// not running
|
|
147
|
+
}
|
|
148
|
+
return { running: false, models: [] };
|
|
149
|
+
}
|
|
150
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
151
|
+
// Main CLI Program
|
|
152
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
153
|
+
const program = new commander_1.Command();
|
|
154
|
+
program
|
|
155
|
+
.name('apexbot')
|
|
156
|
+
.description('ApexBot - Your free, private AI assistant powered by Ollama')
|
|
157
|
+
.version(VERSION);
|
|
158
|
+
// ─────────────────────────────────────────────────────────────────
|
|
159
|
+
// ONBOARD Command - Interactive Setup (like Clawdbot)
|
|
160
|
+
// ─────────────────────────────────────────────────────────────────
|
|
161
|
+
program
|
|
162
|
+
.command('onboard')
|
|
163
|
+
.alias('setup')
|
|
164
|
+
.alias('init')
|
|
165
|
+
.description('Interactive setup wizard')
|
|
166
|
+
.action(async () => {
|
|
167
|
+
showBanner();
|
|
168
|
+
showMascot();
|
|
169
|
+
console.log(chalk.green(' ψ FRESH DAILY ψ'));
|
|
170
|
+
console.log(chalk.cyan(' ApexBot onboarding'));
|
|
171
|
+
console.log('');
|
|
172
|
+
// ══════════════════════════════════════════════════════════════
|
|
173
|
+
// Security Section (like Clawdbot)
|
|
174
|
+
// ══════════════════════════════════════════════════════════════
|
|
175
|
+
showSection('Security', `${chalk.gray('Please read:')} ${chalk.blue('https://github.com/apexbot/docs/security')}
|
|
176
|
+
|
|
177
|
+
ApexBot agents can run commands, read/write files, and act through any tools you
|
|
178
|
+
enable. They can only send messages on channels you configure (for example, an account
|
|
179
|
+
you log in on this machine, or a bot account like Telegram/Discord).
|
|
180
|
+
|
|
181
|
+
${chalk.yellow("If you're new to this, start with Ollama and least privilege.")} It helps limit what
|
|
182
|
+
an agent can do if it's tricked or makes a mistake.
|
|
183
|
+
|
|
184
|
+
${chalk.cyan('✨ Good news:')} ApexBot uses ${chalk.green('Ollama (local AI)')} by default — your data never leaves
|
|
185
|
+
your computer. No cloud APIs, no tracking, 100% private and FREE.`);
|
|
186
|
+
console.log('');
|
|
187
|
+
const { continueSetup } = await inquirer.prompt([{
|
|
188
|
+
type: 'confirm',
|
|
189
|
+
name: 'continueSetup',
|
|
190
|
+
message: chalk.yellow('I understand this is powerful and inherently risky. Continue?'),
|
|
191
|
+
default: false,
|
|
192
|
+
}]);
|
|
193
|
+
if (!continueSetup) {
|
|
194
|
+
console.log('');
|
|
195
|
+
console.log(chalk.gray('Setup cancelled. Run `apexbot onboard` when ready.'));
|
|
196
|
+
process.exit(0);
|
|
197
|
+
}
|
|
198
|
+
console.log('');
|
|
199
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
200
|
+
// ══════════════════════════════════════════════════════════════
|
|
201
|
+
// Step 1: AI Provider (Ollama is default)
|
|
202
|
+
// ══════════════════════════════════════════════════════════════
|
|
203
|
+
showSection('Step 1: AI Provider', `${chalk.green('🌟 RECOMMENDED:')} Ollama (Local AI - 100% FREE, Private, No API Keys!)
|
|
204
|
+
|
|
205
|
+
Ollama runs AI models locally on your machine. Your conversations stay private.
|
|
206
|
+
No internet required after initial model download.`);
|
|
207
|
+
const { provider } = await inquirer.prompt([{
|
|
208
|
+
type: 'list',
|
|
209
|
+
name: 'provider',
|
|
210
|
+
message: 'Choose AI provider:',
|
|
211
|
+
choices: [
|
|
212
|
+
{ name: `${chalk.green('⭐')} Ollama (LOCAL - 100% FREE) ${chalk.gray('[RECOMMENDED]')}`, value: 'ollama' },
|
|
213
|
+
new inquirer.Separator(),
|
|
214
|
+
{ name: ` Kimi K2.5 (Cloud - requires API key)`, value: 'kimi' },
|
|
215
|
+
{ name: ` Google Gemini (Cloud - requires API key)`, value: 'google' },
|
|
216
|
+
{ name: ` Anthropic Claude (Cloud - requires API key)`, value: 'anthropic' },
|
|
217
|
+
{ name: ` OpenAI GPT (Cloud - requires API key)`, value: 'openai' },
|
|
218
|
+
],
|
|
219
|
+
default: 'ollama',
|
|
220
|
+
}]);
|
|
221
|
+
const config = { agent: { provider }, channels: {}, gateway: {} };
|
|
222
|
+
if (provider === 'ollama') {
|
|
223
|
+
// Check if Ollama is running
|
|
224
|
+
const spinner = ora('Checking Ollama connection...').start();
|
|
225
|
+
const ollamaUrl = 'http://localhost:11434';
|
|
226
|
+
const ollama = await checkOllama(ollamaUrl);
|
|
227
|
+
if (ollama.running) {
|
|
228
|
+
spinner.succeed(chalk.green('Ollama is running!'));
|
|
229
|
+
config.agent.apiUrl = ollamaUrl;
|
|
230
|
+
if (ollama.models.length > 0) {
|
|
231
|
+
console.log('');
|
|
232
|
+
console.log(chalk.cyan('Available models:'));
|
|
233
|
+
ollama.models.forEach((m, i) => console.log(chalk.gray(` ${i + 1}. ${m}`)));
|
|
234
|
+
console.log('');
|
|
235
|
+
const { model } = await inquirer.prompt([{
|
|
236
|
+
type: 'list',
|
|
237
|
+
name: 'model',
|
|
238
|
+
message: 'Choose model:',
|
|
239
|
+
choices: [
|
|
240
|
+
...ollama.models.map(m => ({ name: m, value: m })),
|
|
241
|
+
new inquirer.Separator(),
|
|
242
|
+
{ name: chalk.gray('Enter custom model name...'), value: '_custom' },
|
|
243
|
+
],
|
|
244
|
+
}]);
|
|
245
|
+
if (model === '_custom') {
|
|
246
|
+
const { customModel } = await inquirer.prompt([{
|
|
247
|
+
type: 'input',
|
|
248
|
+
name: 'customModel',
|
|
249
|
+
message: 'Model name:',
|
|
250
|
+
default: 'llama3.2',
|
|
251
|
+
}]);
|
|
252
|
+
config.agent.model = customModel;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
config.agent.model = model;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
spinner.warn(chalk.yellow('No models found. You need to pull a model first.'));
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log(chalk.cyan('Recommended models for ApexBot:'));
|
|
262
|
+
console.log(chalk.gray(' • llama3.2 - Fast, good for chat (3.2B params)'));
|
|
263
|
+
console.log(chalk.gray(' • llama3.1:8b - Better quality (8B params)'));
|
|
264
|
+
console.log(chalk.gray(' • qwen2.5:14b - Excellent multilingual (14B params)'));
|
|
265
|
+
console.log(chalk.gray(' • codellama - Best for coding tasks'));
|
|
266
|
+
console.log('');
|
|
267
|
+
console.log(chalk.yellow('Run: ollama pull llama3.2'));
|
|
268
|
+
console.log('');
|
|
269
|
+
const { model } = await inquirer.prompt([{
|
|
270
|
+
type: 'input',
|
|
271
|
+
name: 'model',
|
|
272
|
+
message: 'Model to use (will be pulled if needed):',
|
|
273
|
+
default: 'llama3.2',
|
|
274
|
+
}]);
|
|
275
|
+
config.agent.model = model;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
spinner.fail(chalk.red('Ollama is not running!'));
|
|
280
|
+
console.log('');
|
|
281
|
+
showBox(`${chalk.yellow('Ollama is not running or not installed.')}
|
|
282
|
+
|
|
283
|
+
To install Ollama:
|
|
284
|
+
${chalk.cyan('Windows:')} Download from https://ollama.com
|
|
285
|
+
${chalk.cyan('macOS:')} brew install ollama
|
|
286
|
+
${chalk.cyan('Linux:')} curl -fsSL https://ollama.com/install.sh | sh
|
|
287
|
+
|
|
288
|
+
Then start Ollama:
|
|
289
|
+
${chalk.green('ollama serve')}
|
|
290
|
+
|
|
291
|
+
And pull a model:
|
|
292
|
+
${chalk.green('ollama pull llama3.2')}`, 'Installation Guide');
|
|
293
|
+
const { continueAnyway } = await inquirer.prompt([{
|
|
294
|
+
type: 'confirm',
|
|
295
|
+
name: 'continueAnyway',
|
|
296
|
+
message: 'Continue setup anyway? (You can start Ollama later)',
|
|
297
|
+
default: true,
|
|
298
|
+
}]);
|
|
299
|
+
if (!continueAnyway) {
|
|
300
|
+
console.log(chalk.gray('Run `apexbot onboard` after starting Ollama.'));
|
|
301
|
+
process.exit(0);
|
|
302
|
+
}
|
|
303
|
+
config.agent.apiUrl = ollamaUrl;
|
|
304
|
+
const { model } = await inquirer.prompt([{
|
|
305
|
+
type: 'input',
|
|
306
|
+
name: 'model',
|
|
307
|
+
message: 'Model to use:',
|
|
308
|
+
default: 'llama3.2',
|
|
309
|
+
}]);
|
|
310
|
+
config.agent.model = model;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
// Cloud provider - need API key
|
|
315
|
+
const { apiKey } = await inquirer.prompt([{
|
|
316
|
+
type: 'password',
|
|
317
|
+
name: 'apiKey',
|
|
318
|
+
message: `Enter ${provider.toUpperCase()} API key:`,
|
|
319
|
+
mask: '*',
|
|
320
|
+
}]);
|
|
321
|
+
config.agent.apiKey = apiKey;
|
|
322
|
+
if (provider === 'kimi') {
|
|
323
|
+
config.agent.apiUrl = 'https://api.moonshot.cn/v1';
|
|
324
|
+
config.agent.model = 'kimi-k2-5';
|
|
325
|
+
}
|
|
326
|
+
else if (provider === 'google') {
|
|
327
|
+
config.agent.model = 'gemini-2.0-flash';
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
console.log('');
|
|
331
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
332
|
+
// ══════════════════════════════════════════════════════════════
|
|
333
|
+
// Step 2: Channels
|
|
334
|
+
// ══════════════════════════════════════════════════════════════
|
|
335
|
+
showSection('Step 2: Channels', `ApexBot can connect to multiple messaging platforms.
|
|
336
|
+
Configure at least one channel to start chatting.`);
|
|
337
|
+
const { channels } = await inquirer.prompt([{
|
|
338
|
+
type: 'checkbox',
|
|
339
|
+
name: 'channels',
|
|
340
|
+
message: 'Select channels to configure (space to toggle, enter to confirm):',
|
|
341
|
+
choices: [
|
|
342
|
+
{ name: '📱 Telegram', value: 'telegram' },
|
|
343
|
+
{ name: '🎮 Discord', value: 'discord' },
|
|
344
|
+
{ name: '🌐 WebChat (built-in)', value: 'webchat' },
|
|
345
|
+
],
|
|
346
|
+
validate: (input) => {
|
|
347
|
+
if (input.length === 0) {
|
|
348
|
+
return 'Please select at least one channel (use spacebar to select)';
|
|
349
|
+
}
|
|
350
|
+
return true;
|
|
351
|
+
},
|
|
352
|
+
}]);
|
|
353
|
+
if (channels.includes('telegram')) {
|
|
354
|
+
console.log('');
|
|
355
|
+
console.log(chalk.cyan('Telegram Setup:'));
|
|
356
|
+
console.log(chalk.gray('Get a bot token from @BotFather on Telegram'));
|
|
357
|
+
console.log('');
|
|
358
|
+
const { telegramToken } = await inquirer.prompt([{
|
|
359
|
+
type: 'input',
|
|
360
|
+
name: 'telegramToken',
|
|
361
|
+
message: 'Telegram Bot Token:',
|
|
362
|
+
}]);
|
|
363
|
+
if (telegramToken) {
|
|
364
|
+
config.channels.telegram = { botToken: telegramToken };
|
|
365
|
+
const { allowedUsers } = await inquirer.prompt([{
|
|
366
|
+
type: 'input',
|
|
367
|
+
name: 'allowedUsers',
|
|
368
|
+
message: 'Allowed user IDs (comma-separated, or * for all):',
|
|
369
|
+
default: '*',
|
|
370
|
+
}]);
|
|
371
|
+
if (allowedUsers && allowedUsers !== '*') {
|
|
372
|
+
config.channels.telegram.allowedUsers = allowedUsers.split(',').map((s) => parseInt(s.trim(), 10));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (channels.includes('discord')) {
|
|
377
|
+
console.log('');
|
|
378
|
+
console.log(chalk.cyan('Discord Setup:'));
|
|
379
|
+
console.log(chalk.gray('Get a bot token from Discord Developer Portal'));
|
|
380
|
+
console.log('');
|
|
381
|
+
const { discordToken } = await inquirer.prompt([{
|
|
382
|
+
type: 'input',
|
|
383
|
+
name: 'discordToken',
|
|
384
|
+
message: 'Discord Bot Token:',
|
|
385
|
+
}]);
|
|
386
|
+
if (discordToken) {
|
|
387
|
+
config.channels.discord = { botToken: discordToken };
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
console.log('');
|
|
391
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
392
|
+
// ══════════════════════════════════════════════════════════════
|
|
393
|
+
// Step 3: Gateway Settings
|
|
394
|
+
// ══════════════════════════════════════════════════════════════
|
|
395
|
+
showSection('Step 3: Gateway', `The gateway is the central hub for ApexBot.
|
|
396
|
+
WebChat UI will be available at http://localhost:<port>/chat`);
|
|
397
|
+
const { port } = await inquirer.prompt([{
|
|
398
|
+
type: 'input',
|
|
399
|
+
name: 'port',
|
|
400
|
+
message: 'Gateway port:',
|
|
401
|
+
default: '18789',
|
|
402
|
+
validate: (input) => {
|
|
403
|
+
const num = parseInt(input, 10);
|
|
404
|
+
return num > 0 && num < 65536 ? true : 'Please enter a valid port number';
|
|
405
|
+
},
|
|
406
|
+
}]);
|
|
407
|
+
config.gateway.port = parseInt(port, 10);
|
|
408
|
+
config.gateway.token = Math.random().toString(36).substring(2, 15);
|
|
409
|
+
// ══════════════════════════════════════════════════════════════
|
|
410
|
+
// Save Config
|
|
411
|
+
// ══════════════════════════════════════════════════════════════
|
|
412
|
+
saveConfig(config);
|
|
413
|
+
console.log('');
|
|
414
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
415
|
+
console.log('');
|
|
416
|
+
showBox(`${chalk.green('✅ Configuration saved!')}
|
|
417
|
+
|
|
418
|
+
Config file: ${chalk.cyan(CONFIG_FILE)}`, '🎉 Setup Complete!');
|
|
419
|
+
console.log('');
|
|
420
|
+
// Ask to start gateway immediately
|
|
421
|
+
const { startNow } = await inquirer.prompt([{
|
|
422
|
+
type: 'confirm',
|
|
423
|
+
name: 'startNow',
|
|
424
|
+
message: chalk.yellow('🚀 Start ApexBot gateway now?'),
|
|
425
|
+
default: true,
|
|
426
|
+
}]);
|
|
427
|
+
if (startNow) {
|
|
428
|
+
console.log('');
|
|
429
|
+
console.log(chalk.green('Starting gateway...'));
|
|
430
|
+
console.log('');
|
|
431
|
+
// Import and start gateway directly
|
|
432
|
+
await startGatewayServer(config, {});
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
console.log('');
|
|
436
|
+
console.log(chalk.gray('To start later, run:'));
|
|
437
|
+
console.log(chalk.green(' npx ts-node src/cli/index.ts gateway'));
|
|
438
|
+
console.log('');
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
// ─────────────────────────────────────────────────────────────────
|
|
442
|
+
// Gateway Start Function (reusable)
|
|
443
|
+
// ─────────────────────────────────────────────────────────────────
|
|
444
|
+
async function startGatewayServer(config, options = {}) {
|
|
445
|
+
console.log('');
|
|
446
|
+
console.log(chalk.cyan(MINI_LOGO));
|
|
447
|
+
console.log(chalk.gray(` v${VERSION} — Starting gateway...`));
|
|
448
|
+
console.log('');
|
|
449
|
+
const port = parseInt(options.port || config.gateway?.port || '18789', 10);
|
|
450
|
+
const gateway = new gateway_1.Gateway({
|
|
451
|
+
port,
|
|
452
|
+
host: '127.0.0.1',
|
|
453
|
+
token: config.gateway?.token,
|
|
454
|
+
verbose: options.verbose,
|
|
455
|
+
});
|
|
456
|
+
// Configure agent
|
|
457
|
+
gateway.agents.configure(config.agent);
|
|
458
|
+
// Register channels
|
|
459
|
+
if (config.channels?.telegram?.botToken) {
|
|
460
|
+
const telegram = new telegram_1.TelegramChannel({
|
|
461
|
+
botToken: config.channels.telegram.botToken,
|
|
462
|
+
allowedUsers: config.channels.telegram.allowedUsers,
|
|
463
|
+
});
|
|
464
|
+
gateway.channels.register(telegram);
|
|
465
|
+
}
|
|
466
|
+
// Start gateway
|
|
467
|
+
await gateway.start();
|
|
468
|
+
await gateway.channels.connectAll();
|
|
469
|
+
console.log('');
|
|
470
|
+
console.log(chalk.green('✅ ApexBot is running!'));
|
|
471
|
+
console.log('');
|
|
472
|
+
console.log(` ${chalk.cyan('Dashboard:')} http://127.0.0.1:${port}/chat`);
|
|
473
|
+
console.log(` ${chalk.cyan('API:')} http://127.0.0.1:${port}`);
|
|
474
|
+
console.log(` ${chalk.cyan('WebSocket:')} ws://127.0.0.1:${port}`);
|
|
475
|
+
console.log('');
|
|
476
|
+
console.log(chalk.gray('Press Ctrl+C to stop.'));
|
|
477
|
+
console.log('');
|
|
478
|
+
// Handle shutdown
|
|
479
|
+
process.on('SIGINT', async () => {
|
|
480
|
+
console.log('');
|
|
481
|
+
console.log(chalk.yellow('🛑 Shutting down...'));
|
|
482
|
+
await gateway.stop();
|
|
483
|
+
process.exit(0);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
// ─────────────────────────────────────────────────────────────────
|
|
487
|
+
// GATEWAY Command
|
|
488
|
+
// ─────────────────────────────────────────────────────────────────
|
|
489
|
+
program
|
|
490
|
+
.command('gateway')
|
|
491
|
+
.alias('start')
|
|
492
|
+
.alias('run')
|
|
493
|
+
.description('Start the ApexBot gateway')
|
|
494
|
+
.option('-p, --port <port>', 'Gateway port')
|
|
495
|
+
.option('-v, --verbose', 'Verbose logging')
|
|
496
|
+
.action(async (options) => {
|
|
497
|
+
const config = loadConfig();
|
|
498
|
+
if (!config.agent?.provider) {
|
|
499
|
+
console.log('');
|
|
500
|
+
console.log(chalk.yellow('⚠️ ApexBot is not configured yet.'));
|
|
501
|
+
console.log('');
|
|
502
|
+
console.log(`Run ${chalk.green('apexbot onboard')} to set up your bot.`);
|
|
503
|
+
console.log('');
|
|
504
|
+
process.exit(1);
|
|
505
|
+
}
|
|
506
|
+
await startGatewayServer(config, options);
|
|
507
|
+
});
|
|
508
|
+
// ─────────────────────────────────────────────────────────────────
|
|
509
|
+
// STATUS Command
|
|
510
|
+
// ─────────────────────────────────────────────────────────────────
|
|
511
|
+
program
|
|
512
|
+
.command('status')
|
|
513
|
+
.description('Show ApexBot status')
|
|
514
|
+
.action(async () => {
|
|
515
|
+
const config = loadConfig();
|
|
516
|
+
console.log('');
|
|
517
|
+
console.log(chalk.cyan(MINI_LOGO));
|
|
518
|
+
console.log('');
|
|
519
|
+
const spinner = ora('Checking status...').start();
|
|
520
|
+
// Check config
|
|
521
|
+
const hasConfig = fs.existsSync(CONFIG_FILE);
|
|
522
|
+
// Check Ollama
|
|
523
|
+
let ollamaStatus = '❌ Not checked';
|
|
524
|
+
if (config.agent?.provider === 'ollama') {
|
|
525
|
+
const ollama = await checkOllama(config.agent.apiUrl || 'http://localhost:11434');
|
|
526
|
+
ollamaStatus = ollama.running
|
|
527
|
+
? chalk.green(`✅ Running (${ollama.models.length} models)`)
|
|
528
|
+
: chalk.red('❌ Not running');
|
|
529
|
+
}
|
|
530
|
+
// Check gateway
|
|
531
|
+
let gatewayStatus = chalk.gray('Not running');
|
|
532
|
+
try {
|
|
533
|
+
const port = config.gateway?.port || 18789;
|
|
534
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`);
|
|
535
|
+
if (res.ok) {
|
|
536
|
+
const data = await res.json();
|
|
537
|
+
gatewayStatus = chalk.green(`✅ Running (${data.sessions} sessions)`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
catch (e) {
|
|
541
|
+
gatewayStatus = chalk.gray('Not running');
|
|
542
|
+
}
|
|
543
|
+
spinner.stop();
|
|
544
|
+
console.log(chalk.cyan('Status:'));
|
|
545
|
+
console.log('');
|
|
546
|
+
console.log(` Config: ${hasConfig ? chalk.green('✅ ' + CONFIG_FILE) : chalk.yellow('⚠️ Not configured')}`);
|
|
547
|
+
console.log(` Provider: ${config.agent?.provider || chalk.gray('None')}`);
|
|
548
|
+
console.log(` Model: ${config.agent?.model || chalk.gray('None')}`);
|
|
549
|
+
console.log(` Ollama: ${ollamaStatus}`);
|
|
550
|
+
console.log(` Gateway: ${gatewayStatus}`);
|
|
551
|
+
console.log(` Channels: ${Object.keys(config.channels || {}).join(', ') || chalk.gray('None')}`);
|
|
552
|
+
console.log('');
|
|
553
|
+
if (!hasConfig) {
|
|
554
|
+
console.log(chalk.yellow(`Run ${chalk.green('apexbot onboard')} to set up your bot.`));
|
|
555
|
+
console.log('');
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
// ─────────────────────────────────────────────────────────────────
|
|
559
|
+
// CONFIG Command
|
|
560
|
+
// ─────────────────────────────────────────────────────────────────
|
|
561
|
+
program
|
|
562
|
+
.command('config')
|
|
563
|
+
.description('Show or edit configuration')
|
|
564
|
+
.option('--show', 'Show current config')
|
|
565
|
+
.option('--reset', 'Reset configuration')
|
|
566
|
+
.action(async (options) => {
|
|
567
|
+
if (options.reset) {
|
|
568
|
+
const { confirm } = await inquirer.prompt([{
|
|
569
|
+
type: 'confirm',
|
|
570
|
+
name: 'confirm',
|
|
571
|
+
message: chalk.red('Are you sure you want to reset all configuration?'),
|
|
572
|
+
default: false,
|
|
573
|
+
}]);
|
|
574
|
+
if (confirm) {
|
|
575
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
576
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
577
|
+
}
|
|
578
|
+
console.log(chalk.green('✅ Configuration reset.'));
|
|
579
|
+
console.log(`Run ${chalk.cyan('apexbot onboard')} to set up again.`);
|
|
580
|
+
}
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const config = loadConfig();
|
|
584
|
+
console.log('');
|
|
585
|
+
console.log(chalk.cyan('Current configuration:'));
|
|
586
|
+
console.log('');
|
|
587
|
+
console.log(chalk.gray(CONFIG_FILE));
|
|
588
|
+
console.log('');
|
|
589
|
+
// Hide sensitive data
|
|
590
|
+
const safeConfig = JSON.parse(JSON.stringify(config));
|
|
591
|
+
if (safeConfig.agent?.apiKey) {
|
|
592
|
+
safeConfig.agent.apiKey = '***hidden***';
|
|
593
|
+
}
|
|
594
|
+
if (safeConfig.channels?.telegram?.botToken) {
|
|
595
|
+
safeConfig.channels.telegram.botToken = '***hidden***';
|
|
596
|
+
}
|
|
597
|
+
if (safeConfig.channels?.discord?.botToken) {
|
|
598
|
+
safeConfig.channels.discord.botToken = '***hidden***';
|
|
599
|
+
}
|
|
600
|
+
console.log(JSON.stringify(safeConfig, null, 2));
|
|
601
|
+
console.log('');
|
|
602
|
+
});
|
|
603
|
+
// ─────────────────────────────────────────────────────────────────
|
|
604
|
+
// MODELS Command - List/pull Ollama models
|
|
605
|
+
// ─────────────────────────────────────────────────────────────────
|
|
606
|
+
program
|
|
607
|
+
.command('models')
|
|
608
|
+
.description('List available Ollama models')
|
|
609
|
+
.option('--pull <model>', 'Pull a new model')
|
|
610
|
+
.action(async (options) => {
|
|
611
|
+
const config = loadConfig();
|
|
612
|
+
const ollamaUrl = config.agent?.apiUrl || 'http://localhost:11434';
|
|
613
|
+
if (options.pull) {
|
|
614
|
+
console.log('');
|
|
615
|
+
console.log(chalk.cyan(`Pulling model: ${options.pull}`));
|
|
616
|
+
console.log(chalk.gray('This may take a while...'));
|
|
617
|
+
console.log('');
|
|
618
|
+
// Use exec to run ollama pull
|
|
619
|
+
const { exec } = require('child_process');
|
|
620
|
+
exec(`ollama pull ${options.pull}`, (error, stdout, stderr) => {
|
|
621
|
+
if (error) {
|
|
622
|
+
console.log(chalk.red(`Error: ${error.message}`));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
console.log(stdout);
|
|
626
|
+
if (stderr)
|
|
627
|
+
console.log(stderr);
|
|
628
|
+
console.log(chalk.green('✅ Model pulled successfully!'));
|
|
629
|
+
});
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const spinner = ora('Fetching models...').start();
|
|
633
|
+
const ollama = await checkOllama(ollamaUrl);
|
|
634
|
+
spinner.stop();
|
|
635
|
+
if (!ollama.running) {
|
|
636
|
+
console.log('');
|
|
637
|
+
console.log(chalk.red('❌ Ollama is not running.'));
|
|
638
|
+
console.log(chalk.gray('Start it with: ollama serve'));
|
|
639
|
+
console.log('');
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
console.log('');
|
|
643
|
+
console.log(chalk.cyan('Available models:'));
|
|
644
|
+
console.log('');
|
|
645
|
+
if (ollama.models.length === 0) {
|
|
646
|
+
console.log(chalk.gray(' No models installed.'));
|
|
647
|
+
console.log('');
|
|
648
|
+
console.log(chalk.yellow('Recommended models:'));
|
|
649
|
+
console.log(chalk.gray(' • ollama pull llama3.2 - Fast, good for chat'));
|
|
650
|
+
console.log(chalk.gray(' • ollama pull qwen2.5:14b - Excellent multilingual'));
|
|
651
|
+
console.log(chalk.gray(' • ollama pull codellama - Best for coding'));
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
ollama.models.forEach((m, i) => {
|
|
655
|
+
const current = m === config.agent?.model ? chalk.green(' ← current') : '';
|
|
656
|
+
console.log(` ${i + 1}. ${m}${current}`);
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
console.log('');
|
|
660
|
+
});
|
|
661
|
+
// ─────────────────────────────────────────────────────────────────
|
|
662
|
+
// DAEMON Command - Manage ApexBot daemon (like clawdbot daemon)
|
|
663
|
+
// ─────────────────────────────────────────────────────────────────
|
|
664
|
+
program
|
|
665
|
+
.command('daemon')
|
|
666
|
+
.description('Manage ApexBot daemon')
|
|
667
|
+
.argument('<action>', 'start, stop, restart, or status')
|
|
668
|
+
.action(async (action) => {
|
|
669
|
+
const config = loadConfig();
|
|
670
|
+
const pidFile = path.join(CONFIG_DIR, 'daemon.pid');
|
|
671
|
+
const logFile = path.join(CONFIG_DIR, 'daemon.log');
|
|
672
|
+
switch (action) {
|
|
673
|
+
case 'start': {
|
|
674
|
+
// Check if already running
|
|
675
|
+
if (fs.existsSync(pidFile)) {
|
|
676
|
+
const oldPid = fs.readFileSync(pidFile, 'utf8').trim();
|
|
677
|
+
try {
|
|
678
|
+
process.kill(parseInt(oldPid), 0);
|
|
679
|
+
console.log('');
|
|
680
|
+
console.log(chalk.yellow(`⚠️ Daemon already running (PID: ${oldPid})`));
|
|
681
|
+
console.log(chalk.gray(`Use 'apexbot daemon restart' to restart.`));
|
|
682
|
+
console.log('');
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
catch (e) {
|
|
686
|
+
// Process not running, clean up stale PID file
|
|
687
|
+
fs.unlinkSync(pidFile);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
console.log('');
|
|
691
|
+
console.log(chalk.cyan('🚀 Starting ApexBot daemon...'));
|
|
692
|
+
// Spawn detached process (cross-platform)
|
|
693
|
+
const { spawn } = require('child_process');
|
|
694
|
+
const out = fs.openSync(logFile, 'a');
|
|
695
|
+
const err = fs.openSync(logFile, 'a');
|
|
696
|
+
const isWindows = process.platform === 'win32';
|
|
697
|
+
const child = spawn(isWindows ? 'cmd.exe' : 'npx', isWindows
|
|
698
|
+
? ['/c', 'npx', 'ts-node', 'src/cli/index.ts', 'gateway']
|
|
699
|
+
: ['ts-node', 'src/cli/index.ts', 'gateway'], {
|
|
700
|
+
cwd: process.cwd(),
|
|
701
|
+
detached: true,
|
|
702
|
+
stdio: ['ignore', out, err],
|
|
703
|
+
shell: false,
|
|
704
|
+
});
|
|
705
|
+
fs.writeFileSync(pidFile, String(child.pid));
|
|
706
|
+
child.unref();
|
|
707
|
+
console.log(chalk.green(`✅ Daemon started (PID: ${child.pid})`));
|
|
708
|
+
console.log('');
|
|
709
|
+
console.log(` ${chalk.cyan('Dashboard:')} http://127.0.0.1:${config.gateway?.port || 18789}/chat`);
|
|
710
|
+
console.log(` ${chalk.cyan('Logs:')} ${logFile}`);
|
|
711
|
+
console.log('');
|
|
712
|
+
console.log(chalk.gray('Use "apexbot daemon status" to check status.'));
|
|
713
|
+
console.log(chalk.gray('Use "apexbot daemon stop" to stop.'));
|
|
714
|
+
console.log('');
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
case 'stop': {
|
|
718
|
+
if (!fs.existsSync(pidFile)) {
|
|
719
|
+
console.log('');
|
|
720
|
+
console.log(chalk.yellow('⚠️ Daemon is not running.'));
|
|
721
|
+
console.log('');
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim());
|
|
725
|
+
console.log('');
|
|
726
|
+
console.log(chalk.cyan(`🛑 Stopping daemon (PID: ${pid})...`));
|
|
727
|
+
try {
|
|
728
|
+
process.kill(pid, 'SIGTERM');
|
|
729
|
+
fs.unlinkSync(pidFile);
|
|
730
|
+
console.log(chalk.green('✅ Daemon stopped.'));
|
|
731
|
+
}
|
|
732
|
+
catch (e) {
|
|
733
|
+
if (e.code === 'ESRCH') {
|
|
734
|
+
fs.unlinkSync(pidFile);
|
|
735
|
+
console.log(chalk.yellow('⚠️ Daemon was not running (stale PID file removed).'));
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
console.log(chalk.red(`❌ Failed to stop daemon: ${e.message}`));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
console.log('');
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
case 'restart': {
|
|
745
|
+
console.log('');
|
|
746
|
+
console.log(chalk.cyan('🔄 Restarting daemon...'));
|
|
747
|
+
// Stop first
|
|
748
|
+
if (fs.existsSync(pidFile)) {
|
|
749
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim());
|
|
750
|
+
try {
|
|
751
|
+
process.kill(pid, 'SIGTERM');
|
|
752
|
+
fs.unlinkSync(pidFile);
|
|
753
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
754
|
+
}
|
|
755
|
+
catch (e) {
|
|
756
|
+
// Ignore if already dead
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// Then start
|
|
760
|
+
const { spawn } = require('child_process');
|
|
761
|
+
const out = fs.openSync(logFile, 'a');
|
|
762
|
+
const err = fs.openSync(logFile, 'a');
|
|
763
|
+
const isWindows = process.platform === 'win32';
|
|
764
|
+
const child = spawn(isWindows ? 'cmd.exe' : 'npx', isWindows
|
|
765
|
+
? ['/c', 'npx', 'ts-node', 'src/cli/index.ts', 'gateway']
|
|
766
|
+
: ['ts-node', 'src/cli/index.ts', 'gateway'], {
|
|
767
|
+
cwd: process.cwd(),
|
|
768
|
+
detached: true,
|
|
769
|
+
stdio: ['ignore', out, err],
|
|
770
|
+
shell: false,
|
|
771
|
+
});
|
|
772
|
+
fs.writeFileSync(pidFile, String(child.pid));
|
|
773
|
+
child.unref();
|
|
774
|
+
console.log(chalk.green(`✅ Daemon restarted (PID: ${child.pid})`));
|
|
775
|
+
console.log('');
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
case 'status': {
|
|
779
|
+
console.log('');
|
|
780
|
+
console.log(chalk.cyan(MINI_LOGO));
|
|
781
|
+
console.log('');
|
|
782
|
+
if (!fs.existsSync(pidFile)) {
|
|
783
|
+
console.log(chalk.yellow('⚠️ Daemon is not running.'));
|
|
784
|
+
console.log('');
|
|
785
|
+
console.log(chalk.gray('Start with: apexbot daemon start'));
|
|
786
|
+
console.log('');
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim());
|
|
790
|
+
let isRunning = false;
|
|
791
|
+
try {
|
|
792
|
+
process.kill(pid, 0);
|
|
793
|
+
isRunning = true;
|
|
794
|
+
}
|
|
795
|
+
catch (e) {
|
|
796
|
+
isRunning = false;
|
|
797
|
+
}
|
|
798
|
+
if (isRunning) {
|
|
799
|
+
console.log(chalk.green(`✅ Daemon is running (PID: ${pid})`));
|
|
800
|
+
console.log('');
|
|
801
|
+
// Try to get status from gateway
|
|
802
|
+
try {
|
|
803
|
+
const port = config.gateway?.port || 18789;
|
|
804
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`);
|
|
805
|
+
const data = await res.json();
|
|
806
|
+
console.log(` ${chalk.cyan('Uptime:')} ${Math.floor(data.uptime)}s`);
|
|
807
|
+
console.log(` ${chalk.cyan('Sessions:')} ${data.sessions}`);
|
|
808
|
+
console.log(` ${chalk.cyan('Dashboard:')} http://127.0.0.1:${port}/chat`);
|
|
809
|
+
}
|
|
810
|
+
catch (e) {
|
|
811
|
+
console.log(chalk.gray(' (Gateway not responding yet)'));
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
console.log(chalk.yellow('⚠️ Daemon process died (cleaning up PID file).'));
|
|
816
|
+
fs.unlinkSync(pidFile);
|
|
817
|
+
}
|
|
818
|
+
console.log('');
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
default:
|
|
822
|
+
console.log('');
|
|
823
|
+
console.log(chalk.red(`Unknown action: ${action}`));
|
|
824
|
+
console.log('');
|
|
825
|
+
console.log('Usage: apexbot daemon <start|stop|restart|status>');
|
|
826
|
+
console.log('');
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
// ─────────────────────────────────────────────────────────────────
|
|
830
|
+
// Default action - show help with banner
|
|
831
|
+
// ─────────────────────────────────────────────────────────────────
|
|
832
|
+
program
|
|
833
|
+
.action(() => {
|
|
834
|
+
showBanner();
|
|
835
|
+
showMascot();
|
|
836
|
+
program.help();
|
|
837
|
+
});
|
|
838
|
+
program.parse();
|