codexbot 1.0.0 β 1.0.1
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/README.md +1 -1
- package/cli.js +39 -12
- package/package.json +2 -2
- package/src/agent.js +4 -4
- package/src/commands.js +31 -13
- package/src/main.js +27 -12
- package/src/onboard.js +62 -18
- package/src/platforms/slack.js +1 -1
- package/src/platforms/telegram.js +1 -1
- package/src/service.js +49 -14
package/README.md
CHANGED
package/cli.js
CHANGED
|
@@ -1,23 +1,50 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
3
7
|
const args = process.argv.slice(2);
|
|
4
8
|
const command = args[0];
|
|
5
9
|
|
|
10
|
+
function getVersion() {
|
|
11
|
+
try {
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
|
|
14
|
+
return pkg.version;
|
|
15
|
+
} catch {
|
|
16
|
+
return '0.0.0';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
function showHelp() {
|
|
21
|
+
const version = getVersion();
|
|
7
22
|
console.log(`
|
|
8
|
-
codexbot
|
|
23
|
+
π€ codexbot v${version}
|
|
24
|
+
Bridge Slack & Telegram to Claude/Codex AI agents
|
|
25
|
+
|
|
26
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
27
|
+
|
|
28
|
+
π¦ Commands:
|
|
29
|
+
codexbot start [options] Start the bot as a background service
|
|
30
|
+
codexbot stop Stop the background service
|
|
31
|
+
codexbot status Show service status
|
|
32
|
+
codexbot onboard Interactive setup wizard
|
|
33
|
+
|
|
34
|
+
βοΈ Options:
|
|
35
|
+
--interactive Run in interactive terminal mode
|
|
36
|
+
--claude Use Claude Agent SDK (default: Codex)
|
|
37
|
+
--disable-yolo Disable auto-accept mode
|
|
38
|
+
--help, -h Show this help message
|
|
9
39
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
40
|
+
π Examples:
|
|
41
|
+
codexbot onboard Set up platform & tokens
|
|
42
|
+
codexbot start Launch as background service
|
|
43
|
+
codexbot start --claude Launch with Claude backend
|
|
44
|
+
codexbot start --interactive --claude
|
|
45
|
+
Interactive terminal with Claude
|
|
15
46
|
|
|
16
|
-
|
|
17
|
-
--interactive Run in interactive terminal mode
|
|
18
|
-
--claude Use Claude Agent SDK instead of Codex SDK (default: Codex)
|
|
19
|
-
--disable-yolo Disable auto-accept mode
|
|
20
|
-
--help, -h Show this help message
|
|
47
|
+
π‘ Get started: run ${'\x1b[1m'}codexbot onboard${'\x1b[0m'} to configure your bot.
|
|
21
48
|
`);
|
|
22
49
|
}
|
|
23
50
|
|
|
@@ -58,7 +85,7 @@ switch (command) {
|
|
|
58
85
|
showHelp();
|
|
59
86
|
break;
|
|
60
87
|
default:
|
|
61
|
-
console.error(
|
|
88
|
+
console.error(`\n β Unknown command: ${command}\n`);
|
|
62
89
|
showHelp();
|
|
63
90
|
process.exit(1);
|
|
64
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexbot",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Control Claude and Codex AI agents from Telegram or Slack",
|
|
6
6
|
"keywords": [
|
|
@@ -47,4 +47,4 @@
|
|
|
47
47
|
"@slack/web-api": "^7.13.0",
|
|
48
48
|
"telegraf": "^4.16.0"
|
|
49
49
|
}
|
|
50
|
-
}
|
|
50
|
+
}
|
package/src/agent.js
CHANGED
|
@@ -27,7 +27,7 @@ export default class Agent extends EventEmitter {
|
|
|
27
27
|
this._initCodex();
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
console.log(`
|
|
30
|
+
console.log(` π€ Agent started (${this.backend}) in ${this.workingDir}`);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async _initCodex() {
|
|
@@ -55,8 +55,8 @@ export default class Agent extends EventEmitter {
|
|
|
55
55
|
}
|
|
56
56
|
} catch (err) {
|
|
57
57
|
if (err.name !== 'AbortError') {
|
|
58
|
-
console.error(`
|
|
59
|
-
this.emit('message',
|
|
58
|
+
console.error(` β Agent error: ${err.message}`);
|
|
59
|
+
this.emit('message', `β Error: ${err.message}`);
|
|
60
60
|
}
|
|
61
61
|
} finally {
|
|
62
62
|
this.busy = false;
|
|
@@ -254,7 +254,7 @@ export default class Agent extends EventEmitter {
|
|
|
254
254
|
this._claudeSessionId = null;
|
|
255
255
|
this.running = false;
|
|
256
256
|
this.busy = false;
|
|
257
|
-
console.log('
|
|
257
|
+
console.log(' βΉοΈ Agent stopped');
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
getStatus() {
|
package/src/commands.js
CHANGED
|
@@ -8,6 +8,17 @@ export const COMMANDS = [
|
|
|
8
8
|
{ command: 'dir', description: 'Show or change working directory' },
|
|
9
9
|
];
|
|
10
10
|
|
|
11
|
+
function formatUptime(totalSeconds) {
|
|
12
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
13
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
14
|
+
const seconds = totalSeconds % 60;
|
|
15
|
+
const parts = [];
|
|
16
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
17
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
18
|
+
parts.push(`${seconds}s`);
|
|
19
|
+
return parts.join(' ');
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
export function handle(text, agent) {
|
|
12
23
|
const trimmed = text.trim();
|
|
13
24
|
if (!trimmed.startsWith('$') && !trimmed.startsWith('/')) return { handled: false };
|
|
@@ -19,45 +30,52 @@ export function handle(text, agent) {
|
|
|
19
30
|
switch (cmd) {
|
|
20
31
|
case 'status': {
|
|
21
32
|
const s = agent.getStatus();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
if (s.running) {
|
|
34
|
+
const uptime = formatUptime(s.uptime);
|
|
35
|
+
const response = [
|
|
36
|
+
`β
*Agent is running*`,
|
|
37
|
+
``,
|
|
38
|
+
`π§ Backend: \`${s.backend}\``,
|
|
39
|
+
`β±οΈ Uptime: ${uptime}`,
|
|
40
|
+
`π CWD: \`${s.cwd}\``,
|
|
41
|
+
s.busy ? `β³ Status: Processing...` : `π€ Status: Idle`,
|
|
42
|
+
].join('\n');
|
|
43
|
+
return { handled: true, response };
|
|
44
|
+
}
|
|
45
|
+
return { handled: true, response: 'βΈοΈ Agent is not running.\nUse /start to launch it.' };
|
|
28
46
|
}
|
|
29
47
|
|
|
30
48
|
case 'stop': {
|
|
31
49
|
agent.stop();
|
|
32
|
-
return { handled: true, response: 'Agent stopped.' };
|
|
50
|
+
return { handled: true, response: 'βΉοΈ Agent stopped.' };
|
|
33
51
|
}
|
|
34
52
|
|
|
35
53
|
case 'start': {
|
|
36
54
|
if (agent.running) {
|
|
37
|
-
return { handled: true, response: 'Agent is already running.' };
|
|
55
|
+
return { handled: true, response: 'β
Agent is already running.' };
|
|
38
56
|
}
|
|
39
57
|
applyFlags(agent, parts.slice(1));
|
|
40
58
|
agent.start();
|
|
41
|
-
return { handled: true, response:
|
|
59
|
+
return { handled: true, response: `π Agent started!\nπ§ Backend: \`${agent.backend}\`` };
|
|
42
60
|
}
|
|
43
61
|
|
|
44
62
|
case 'restart': {
|
|
45
63
|
agent.stop();
|
|
46
64
|
applyFlags(agent, parts.slice(1));
|
|
47
65
|
agent.start();
|
|
48
|
-
return { handled: true, response:
|
|
66
|
+
return { handled: true, response: `π Agent restarted!\nπ§ Backend: \`${agent.backend}\`` };
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
case 'dir': {
|
|
52
70
|
const dir = parts[1];
|
|
53
71
|
if (!dir) {
|
|
54
|
-
return { handled: true, response:
|
|
72
|
+
return { handled: true, response: `π Current directory: \`${agent.workingDir}\`` };
|
|
55
73
|
}
|
|
56
74
|
if (!fs.existsSync(dir)) {
|
|
57
|
-
return { handled: true, response:
|
|
75
|
+
return { handled: true, response: `β Directory not found: \`${dir}\`` };
|
|
58
76
|
}
|
|
59
77
|
agent.workingDir = dir;
|
|
60
|
-
return { handled: true, response:
|
|
78
|
+
return { handled: true, response: `π Working directory set to: \`${dir}\`\nπ‘ Use /restart for it to take effect.` };
|
|
61
79
|
}
|
|
62
80
|
|
|
63
81
|
default:
|
package/src/main.js
CHANGED
|
@@ -4,6 +4,10 @@ import Agent from './agent.js';
|
|
|
4
4
|
import { handle as handleCommand, COMMANDS } from './commands.js';
|
|
5
5
|
import { chunkMessage } from './chunker.js';
|
|
6
6
|
|
|
7
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
8
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
9
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
10
|
+
|
|
7
11
|
export async function start(args = []) {
|
|
8
12
|
const interactive = args.includes('--interactive');
|
|
9
13
|
const config = loadConfig();
|
|
@@ -22,26 +26,26 @@ export async function start(args = []) {
|
|
|
22
26
|
|
|
23
27
|
if (!interactive) {
|
|
24
28
|
if (!config.PLATFORM) {
|
|
25
|
-
console.error(
|
|
29
|
+
console.error(`\n β No platform configured. Run ${bold('codexbot onboard')} first.\n`);
|
|
26
30
|
process.exit(1);
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
if (config.PLATFORM === 'slack') {
|
|
30
34
|
if (!config.SLACK_BOT_TOKEN || !config.SLACK_APP_TOKEN) {
|
|
31
|
-
console.error(
|
|
35
|
+
console.error(`\n β Missing Slack tokens. Run ${bold('codexbot onboard')} to configure.\n`);
|
|
32
36
|
process.exit(1);
|
|
33
37
|
}
|
|
34
38
|
const { default: SlackAdapter } = await import('./platforms/slack.js');
|
|
35
39
|
platform = new SlackAdapter(config);
|
|
36
40
|
} else if (config.PLATFORM === 'telegram') {
|
|
37
41
|
if (!config.TELEGRAM_BOT_TOKEN) {
|
|
38
|
-
console.error(
|
|
42
|
+
console.error(`\n β Missing Telegram bot token. Run ${bold('codexbot onboard')} to configure.\n`);
|
|
39
43
|
process.exit(1);
|
|
40
44
|
}
|
|
41
45
|
const { default: TelegramAdapter } = await import('./platforms/telegram.js');
|
|
42
46
|
platform = new TelegramAdapter(config);
|
|
43
47
|
} else {
|
|
44
|
-
console.error(
|
|
48
|
+
console.error(`\n β Unknown platform: ${config.PLATFORM}\n`);
|
|
45
49
|
process.exit(1);
|
|
46
50
|
}
|
|
47
51
|
}
|
|
@@ -57,11 +61,11 @@ function startInteractive(agent) {
|
|
|
57
61
|
const rl = readline.createInterface({
|
|
58
62
|
input: process.stdin,
|
|
59
63
|
output: process.stdout,
|
|
60
|
-
prompt: '
|
|
64
|
+
prompt: `\n${cyan('you')}${dim('>')} `,
|
|
61
65
|
});
|
|
62
66
|
|
|
63
67
|
agent.on('message', (text) => {
|
|
64
|
-
console.log(`\
|
|
68
|
+
console.log(`\n${cyan('bot')}${dim('>')} ${text}`);
|
|
65
69
|
rl.prompt();
|
|
66
70
|
});
|
|
67
71
|
|
|
@@ -70,7 +74,10 @@ function startInteractive(agent) {
|
|
|
70
74
|
});
|
|
71
75
|
|
|
72
76
|
agent.start();
|
|
73
|
-
console.log(
|
|
77
|
+
console.log(`\n π€ ${bold('codexbot Interactive Mode')}`);
|
|
78
|
+
console.log(` Backend: ${agent.backend}`);
|
|
79
|
+
console.log(` CWD: ${dim(agent.workingDir)}`);
|
|
80
|
+
console.log(` ${dim('Type your message or Ctrl+C to quit.')}\n`);
|
|
74
81
|
rl.prompt();
|
|
75
82
|
|
|
76
83
|
rl.on('line', async (line) => {
|
|
@@ -86,13 +93,14 @@ function startInteractive(agent) {
|
|
|
86
93
|
|
|
87
94
|
const sent = await agent.sendCommand(text);
|
|
88
95
|
if (!sent) {
|
|
89
|
-
console.log('Agent is not running. Use $start to start it.');
|
|
96
|
+
console.log(' β οΈ Agent is not running. Use $start to start it.');
|
|
90
97
|
rl.prompt();
|
|
91
98
|
}
|
|
92
99
|
});
|
|
93
100
|
|
|
94
101
|
rl.on('close', () => {
|
|
95
102
|
agent.stop();
|
|
103
|
+
console.log(`\n π Goodbye!\n`);
|
|
96
104
|
process.exit(0);
|
|
97
105
|
});
|
|
98
106
|
}
|
|
@@ -134,7 +142,7 @@ function startService(agent, platform, config) {
|
|
|
134
142
|
const sent = await agent.sendCommand(msg.text);
|
|
135
143
|
if (!sent) {
|
|
136
144
|
if (platform.stopTyping) platform.stopTyping();
|
|
137
|
-
await sendResponse(platform, currentCtx, 'Agent is not running. Use
|
|
145
|
+
await sendResponse(platform, currentCtx, 'β οΈ Agent is not running. Use /start to start it.');
|
|
138
146
|
}
|
|
139
147
|
});
|
|
140
148
|
|
|
@@ -145,14 +153,21 @@ function startService(agent, platform, config) {
|
|
|
145
153
|
platform.setCommands(COMMANDS);
|
|
146
154
|
}
|
|
147
155
|
|
|
156
|
+
const platformName = config.PLATFORM.charAt(0).toUpperCase() + config.PLATFORM.slice(1);
|
|
157
|
+
|
|
148
158
|
platform.start().then(() => {
|
|
149
|
-
console.log('
|
|
159
|
+
console.log(` β
${bold('codexbot is running')}`);
|
|
160
|
+
console.log(` Platform: ${platformName}`);
|
|
161
|
+
console.log(` Backend: ${agent.backend}`);
|
|
162
|
+
console.log(` CWD: ${dim(agent.workingDir)}`);
|
|
163
|
+
console.log(` ${dim('Press Ctrl+C to stop.')}`);
|
|
150
164
|
});
|
|
151
165
|
|
|
152
166
|
const shutdown = async () => {
|
|
153
|
-
console.log(
|
|
167
|
+
console.log(`\n βΉοΈ Shutting down codexbot...`);
|
|
154
168
|
agent.stop();
|
|
155
169
|
await platform.stop().catch(() => { });
|
|
170
|
+
console.log(` π Goodbye!\n`);
|
|
156
171
|
process.exit(0);
|
|
157
172
|
};
|
|
158
173
|
|
|
@@ -171,7 +186,7 @@ async function sendResponse(platform, ctx, text) {
|
|
|
171
186
|
replyToMessageId: ctx.replyToMessageId,
|
|
172
187
|
});
|
|
173
188
|
} catch (err) {
|
|
174
|
-
console.error(
|
|
189
|
+
console.error(` β Failed to send message: ${err.message}`);
|
|
175
190
|
}
|
|
176
191
|
}
|
|
177
192
|
}
|
package/src/onboard.js
CHANGED
|
@@ -4,29 +4,53 @@ import { loadConfig, saveConfig, CONFIG_PATH } from './config.js';
|
|
|
4
4
|
|
|
5
5
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6
6
|
|
|
7
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
8
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
9
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
10
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
11
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
12
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
13
|
+
|
|
7
14
|
function ask(question, defaultVal) {
|
|
8
15
|
return new Promise(resolve => {
|
|
9
|
-
const suffix = defaultVal ? ` [${defaultVal}]` : '';
|
|
10
|
-
rl.question(
|
|
16
|
+
const suffix = defaultVal ? dim(` [${defaultVal}]`) : '';
|
|
17
|
+
rl.question(` ${question}${suffix}: `, answer => {
|
|
11
18
|
resolve(answer.trim() || defaultVal || '');
|
|
12
19
|
});
|
|
13
20
|
});
|
|
14
21
|
}
|
|
15
22
|
|
|
23
|
+
function maskToken(token) {
|
|
24
|
+
if (!token || token.length < 10) return token || '';
|
|
25
|
+
return token.slice(0, 6) + 'β’β’β’β’' + token.slice(-4);
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
async function onboard() {
|
|
17
|
-
console.log(
|
|
29
|
+
console.log(`
|
|
30
|
+
π€ ${bold('codexbot Setup Wizard')}
|
|
31
|
+
βββββββββββββββββββββββββββββββββββββ
|
|
32
|
+
`);
|
|
18
33
|
|
|
19
34
|
const existing = loadConfig();
|
|
20
35
|
|
|
21
|
-
// If config exists, offer to keep it
|
|
36
|
+
// If config exists, show current state and offer to keep it
|
|
22
37
|
if (existing.PLATFORM) {
|
|
23
|
-
console.log('Current
|
|
24
|
-
console.log(`
|
|
25
|
-
if (existing.
|
|
38
|
+
console.log(` π ${bold('Current Configuration')}`);
|
|
39
|
+
console.log(` Platform: ${cyan(existing.PLATFORM)}`);
|
|
40
|
+
if (existing.PLATFORM === 'slack') {
|
|
41
|
+
console.log(` Bot Token: ${dim(maskToken(existing.SLACK_BOT_TOKEN))}`);
|
|
42
|
+
console.log(` App Token: ${dim(maskToken(existing.SLACK_APP_TOKEN))}`);
|
|
43
|
+
if (existing.SLACK_USER_ID) console.log(` User ID: ${dim(existing.SLACK_USER_ID)}`);
|
|
44
|
+
} else if (existing.PLATFORM === 'telegram') {
|
|
45
|
+
console.log(` Bot Token: ${dim(maskToken(existing.TELEGRAM_BOT_TOKEN))}`);
|
|
46
|
+
if (existing.TELEGRAM_ALLOWED_USER_IDS) console.log(` Allowed IDs: ${dim(existing.TELEGRAM_ALLOWED_USER_IDS)}`);
|
|
47
|
+
}
|
|
48
|
+
if (existing.WORKING_DIR) console.log(` Working Dir: ${dim(existing.WORKING_DIR)}`);
|
|
26
49
|
console.log();
|
|
50
|
+
|
|
27
51
|
const keep = await ask('Keep current config? (y/n)', 'y');
|
|
28
52
|
if (keep.toLowerCase() === 'y') {
|
|
29
|
-
console.log(
|
|
53
|
+
console.log(`\n β
Configuration unchanged.\n`);
|
|
30
54
|
rl.close();
|
|
31
55
|
return;
|
|
32
56
|
}
|
|
@@ -35,14 +59,21 @@ async function onboard() {
|
|
|
35
59
|
|
|
36
60
|
const config = { ...existing };
|
|
37
61
|
|
|
38
|
-
|
|
39
|
-
console.log('
|
|
40
|
-
console.log(
|
|
62
|
+
// Step 1: Platform
|
|
63
|
+
console.log(` ${bold('Step 1 of 3')} β ${cyan('Choose Platform')}\n`);
|
|
64
|
+
console.log(` 1) π¬ Slack`);
|
|
65
|
+
console.log(` 2) βοΈ Telegram\n`);
|
|
41
66
|
const platformChoice = await ask('Platform (1 or 2)', config.PLATFORM === 'telegram' ? '2' : '1');
|
|
42
67
|
config.PLATFORM = platformChoice === '2' ? 'telegram' : 'slack';
|
|
68
|
+
console.log(`\n β Platform: ${green(config.PLATFORM)}\n`);
|
|
69
|
+
|
|
70
|
+
// Step 2: Tokens
|
|
71
|
+
console.log(` ${bold('Step 2 of 3')} β ${cyan('Configure Tokens')}\n`);
|
|
43
72
|
|
|
44
73
|
if (config.PLATFORM === 'slack') {
|
|
45
|
-
console.log('\
|
|
74
|
+
console.log(` ${dim('You\'ll need a Slack Bot Token (xoxb-...) and App-Level Token (xapp-...).')}`);
|
|
75
|
+
console.log(` ${dim('Create these at https://api.slack.com/apps')}\n`);
|
|
76
|
+
|
|
46
77
|
config.SLACK_BOT_TOKEN = await ask('Bot Token (xoxb-...)', config.SLACK_BOT_TOKEN);
|
|
47
78
|
config.SLACK_APP_TOKEN = await ask('App-Level Token (xapp-...)', config.SLACK_APP_TOKEN);
|
|
48
79
|
config.SLACK_USER_ID = await ask('Your Slack User ID (optional, for auth)', config.SLACK_USER_ID);
|
|
@@ -50,7 +81,9 @@ async function onboard() {
|
|
|
50
81
|
delete config.TELEGRAM_BOT_TOKEN;
|
|
51
82
|
delete config.TELEGRAM_ALLOWED_USER_IDS;
|
|
52
83
|
} else {
|
|
53
|
-
console.log('\
|
|
84
|
+
console.log(` ${dim('You\'ll need a Telegram Bot Token from @BotFather.')}`);
|
|
85
|
+
console.log(` ${dim('Message @BotFather on Telegram to create a bot.')}\n`);
|
|
86
|
+
|
|
54
87
|
config.TELEGRAM_BOT_TOKEN = await ask('Bot Token', config.TELEGRAM_BOT_TOKEN);
|
|
55
88
|
config.TELEGRAM_ALLOWED_USER_IDS = await ask('Allowed User IDs (comma-separated, optional)', config.TELEGRAM_ALLOWED_USER_IDS);
|
|
56
89
|
// Clear slack keys
|
|
@@ -59,23 +92,34 @@ async function onboard() {
|
|
|
59
92
|
delete config.SLACK_USER_ID;
|
|
60
93
|
}
|
|
61
94
|
|
|
95
|
+
console.log(`\n β Tokens configured\n`);
|
|
96
|
+
|
|
97
|
+
// Step 3: Working directory
|
|
98
|
+
console.log(` ${bold('Step 3 of 3')} β ${cyan('Set Working Directory')}\n`);
|
|
99
|
+
console.log(` ${dim('The agent will execute commands in this directory.')}\n`);
|
|
100
|
+
|
|
62
101
|
let workingDir = config.WORKING_DIR || process.cwd();
|
|
63
102
|
while (true) {
|
|
64
|
-
workingDir = await ask('
|
|
103
|
+
workingDir = await ask('Working directory', workingDir);
|
|
65
104
|
if (!workingDir || fs.existsSync(workingDir)) break;
|
|
66
|
-
console.log(`Directory does not exist: ${workingDir}`);
|
|
105
|
+
console.log(` ${yellow('β ')} Directory does not exist: ${workingDir}`);
|
|
67
106
|
}
|
|
68
107
|
config.WORKING_DIR = workingDir;
|
|
69
108
|
|
|
109
|
+
console.log(`\n β Working directory: ${green(workingDir)}\n`);
|
|
110
|
+
|
|
111
|
+
// Save
|
|
70
112
|
saveConfig(config);
|
|
71
|
-
console.log(
|
|
72
|
-
console.log('
|
|
113
|
+
console.log(` βββββββββββββββββββββββββββββββββββββ`);
|
|
114
|
+
console.log(`\n β
${bold('Setup complete!')}`);
|
|
115
|
+
console.log(` π Config saved to ${dim(CONFIG_PATH)}`);
|
|
116
|
+
console.log(`\n π Run ${bold('codexbot start')} to launch your bot.\n`);
|
|
73
117
|
|
|
74
118
|
rl.close();
|
|
75
119
|
}
|
|
76
120
|
|
|
77
121
|
onboard().catch(err => {
|
|
78
|
-
console.error(
|
|
122
|
+
console.error(`\n β Setup failed: ${err.message}\n`);
|
|
79
123
|
rl.close();
|
|
80
124
|
process.exit(1);
|
|
81
125
|
});
|
package/src/platforms/slack.js
CHANGED
|
@@ -43,7 +43,7 @@ export default class TelegramAdapter {
|
|
|
43
43
|
this.bot.telegram.setMyCommands(this._commands).catch(() => { });
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
console.log('
|
|
46
|
+
console.log(' βοΈ Telegram bot connected');
|
|
47
47
|
|
|
48
48
|
// Graceful stop on signals
|
|
49
49
|
process.once('SIGINT', () => this.bot.stop('SIGINT'));
|
package/src/service.js
CHANGED
|
@@ -9,16 +9,21 @@ const LOG_FILE = path.join(CONFIG_DIR, 'codexbot.log');
|
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const CLI_PATH = path.join(__dirname, '..', 'cli.js');
|
|
11
11
|
|
|
12
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
13
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
14
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
15
|
+
|
|
12
16
|
export function startService(args = []) {
|
|
13
17
|
const config = loadConfig();
|
|
14
18
|
if (!config.PLATFORM) {
|
|
15
|
-
console.error(
|
|
19
|
+
console.error(`\n β No platform configured.\n Run ${bold('codexbot onboard')} to set up your bot.\n`);
|
|
16
20
|
process.exit(1);
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
const existing = getServicePid();
|
|
20
24
|
if (existing) {
|
|
21
|
-
console.error(
|
|
25
|
+
console.error(`\n β οΈ codexbot is already running ${dim(`(PID ${existing})`)}`);
|
|
26
|
+
console.error(` Use ${bold('codexbot stop')} first, or ${bold('codexbot status')} to check.\n`);
|
|
22
27
|
process.exit(1);
|
|
23
28
|
}
|
|
24
29
|
|
|
@@ -36,45 +41,66 @@ export function startService(args = []) {
|
|
|
36
41
|
child.unref();
|
|
37
42
|
fs.closeSync(logFd);
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
const backend = args.includes('--claude') ? 'Claude' : 'Codex';
|
|
45
|
+
const platform = config.PLATFORM.charAt(0).toUpperCase() + config.PLATFORM.slice(1);
|
|
46
|
+
|
|
47
|
+
console.log(`\n π ${bold('codexbot started!')}`);
|
|
48
|
+
console.log(` PID: ${dim(String(child.pid))}`);
|
|
49
|
+
console.log(` Platform: ${platform}`);
|
|
50
|
+
console.log(` Backend: ${backend}`);
|
|
51
|
+
console.log(` Logs: ${dim(LOG_FILE)}`);
|
|
52
|
+
console.log(`\n π‘ Use ${bold('codexbot status')} to check health or ${bold('codexbot stop')} to shut down.\n`);
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
export function stopService() {
|
|
44
56
|
const pid = getServicePid();
|
|
45
57
|
if (!pid) {
|
|
46
|
-
console.log(
|
|
58
|
+
console.log(`\n βΉοΈ codexbot is not running. Nothing to stop.\n`);
|
|
47
59
|
return;
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
try {
|
|
51
63
|
process.kill(pid, 'SIGTERM');
|
|
52
|
-
|
|
64
|
+
cleanupPid();
|
|
65
|
+
console.log(`\n βΉοΈ ${bold('codexbot stopped')} ${dim(`(PID ${pid})`)}`);
|
|
66
|
+
console.log(` Run ${bold('codexbot start')} to launch again.\n`);
|
|
53
67
|
} catch (err) {
|
|
54
68
|
if (err.code === 'ESRCH') {
|
|
55
|
-
|
|
69
|
+
cleanupPid();
|
|
70
|
+
console.log(`\n β οΈ Process not found ${dim(`(PID ${pid})`)} β stale PID file cleaned up.`);
|
|
71
|
+
console.log(` codexbot was not actually running.\n`);
|
|
56
72
|
} else {
|
|
57
|
-
console.error(
|
|
73
|
+
console.error(`\n β Failed to stop codexbot: ${err.message}\n`);
|
|
58
74
|
}
|
|
59
75
|
}
|
|
60
|
-
|
|
61
|
-
cleanupPid();
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
export function showStatus() {
|
|
65
79
|
const pid = getServicePid();
|
|
66
80
|
if (!pid) {
|
|
67
|
-
console.log(
|
|
81
|
+
console.log(`\n βΈοΈ codexbot is ${bold('not running')}`);
|
|
82
|
+
console.log(` Run ${bold('codexbot start')} to launch.\n`);
|
|
68
83
|
return;
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
const alive = isProcessAlive(pid);
|
|
72
87
|
if (alive) {
|
|
73
|
-
|
|
74
|
-
|
|
88
|
+
const config = loadConfig();
|
|
89
|
+
const platform = config.PLATFORM
|
|
90
|
+
? config.PLATFORM.charAt(0).toUpperCase() + config.PLATFORM.slice(1)
|
|
91
|
+
: 'Unknown';
|
|
92
|
+
const pidStartTime = getPidFileAge();
|
|
93
|
+
|
|
94
|
+
console.log(`\n ${green('β')} codexbot is ${bold('running')}`);
|
|
95
|
+
console.log(` PID: ${dim(String(pid))}`);
|
|
96
|
+
console.log(` Platform: ${platform}`);
|
|
97
|
+
if (pidStartTime) console.log(` Since: ${dim(pidStartTime)}`);
|
|
98
|
+
console.log(` Logs: ${dim(LOG_FILE)}`);
|
|
99
|
+
console.log();
|
|
75
100
|
} else {
|
|
76
|
-
console.log('codexbot is not running (stale PID file). Cleaning up.');
|
|
77
101
|
cleanupPid();
|
|
102
|
+
console.log(`\n β οΈ codexbot is ${bold('not running')} ${dim('(stale PID file cleaned up)')}`);
|
|
103
|
+
console.log(` Run ${bold('codexbot start')} to launch.\n`);
|
|
78
104
|
}
|
|
79
105
|
}
|
|
80
106
|
|
|
@@ -99,3 +125,12 @@ function isProcessAlive(pid) {
|
|
|
99
125
|
function cleanupPid() {
|
|
100
126
|
try { fs.unlinkSync(PID_FILE); } catch { }
|
|
101
127
|
}
|
|
128
|
+
|
|
129
|
+
function getPidFileAge() {
|
|
130
|
+
try {
|
|
131
|
+
const stat = fs.statSync(PID_FILE);
|
|
132
|
+
return stat.mtime.toLocaleString();
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|