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 CHANGED
@@ -36,7 +36,7 @@ npm install -g codexbot
36
36
  Or install from source:
37
37
 
38
38
  ```bash
39
- git clone https://github.com/nicklama/codexbot.git
39
+ git clone https://github.com/dinhquan/codexbot.git
40
40
  cd codexbot
41
41
  npm install
42
42
  npm link
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 - Bridge Slack/Telegram to Claude/Codex via their SDKs
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
- Usage:
11
- codexbot start [options] Start the bot as a background service
12
- codexbot stop Stop the background service
13
- codexbot status Show service status
14
- codexbot onboard Interactive setup (platform, tokens, config)
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
- Options:
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(`Unknown command: ${command}`);
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.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(`[codexbot] Agent started (${this.backend}) in ${this.workingDir}`);
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(`[codexbot] Agent error:`, err.message);
59
- this.emit('message', `Error: ${err.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('[codexbot] Agent stopped');
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
- const upMin = Math.floor(s.uptime / 60);
23
- const upSec = s.uptime % 60;
24
- const response = s.running
25
- ? `Running: ${s.backend}${s.busy ? ' (busy)' : ''}\nUptime: ${upMin}m ${upSec}s\nCWD: ${s.cwd}`
26
- : 'Agent is not running.';
27
- return { handled: true, response };
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: 'Agent started.' };
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: 'Agent restarted.' };
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: `Current directory: ${agent.workingDir}` };
72
+ return { handled: true, response: `πŸ“‚ Current directory: \`${agent.workingDir}\`` };
55
73
  }
56
74
  if (!fs.existsSync(dir)) {
57
- return { handled: true, response: `Directory not found: ${dir}` };
75
+ return { handled: true, response: `❌ Directory not found: \`${dir}\`` };
58
76
  }
59
77
  agent.workingDir = dir;
60
- return { handled: true, response: `Working directory set to: ${dir}\nRestart agent for it to take effect.` };
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('No platform configured. Run "codexbot onboard" first.');
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('Missing Slack tokens. Run "codexbot onboard" to configure.');
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('Missing Telegram bot token. Run "codexbot onboard" to configure.');
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(`Unknown platform: ${config.PLATFORM}`);
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: '\nyou> ',
64
+ prompt: `\n${cyan('you')}${dim('>')} `,
61
65
  });
62
66
 
63
67
  agent.on('message', (text) => {
64
- console.log(`\nbot> ${text}`);
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(`[codexbot] Interactive mode (${agent.backend}). Type your message or Ctrl+C to quit.\n`);
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 $start to start it.');
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('[codexbot] Bot is running. Press Ctrl+C to stop.');
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('\n[codexbot] Shutting down...');
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('[codexbot] Failed to send message:', err.message);
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(`${question}${suffix}: `, answer => {
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('\n=== codexbot Setup ===\n');
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 config:');
24
- console.log(` Platform: ${existing.PLATFORM}`);
25
- if (existing.WORKING_DIR) console.log(` Working dir: ${existing.WORKING_DIR}`);
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('Config unchanged.');
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
- console.log('Choose platform:');
39
- console.log(' 1) Slack');
40
- console.log(' 2) Telegram');
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('\n--- Slack Configuration ---');
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('\n--- Telegram Configuration ---');
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('\nWorking directory (optional)', workingDir);
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(`\nConfig saved to ${CONFIG_PATH}`);
72
- console.log('Run "codexbot start" to launch the bot.\n');
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('Setup failed:', err.message);
122
+ console.error(`\n ❌ Setup failed: ${err.message}\n`);
79
123
  rl.close();
80
124
  process.exit(1);
81
125
  });
@@ -42,7 +42,7 @@ export default class SlackAdapter {
42
42
  });
43
43
 
44
44
  await this.app.start();
45
- console.log('[codexbot] Slack connected (Socket Mode)');
45
+ console.log(' πŸ’¬ Slack connected (Socket Mode)');
46
46
  }
47
47
 
48
48
  onMessage(callback) {
@@ -43,7 +43,7 @@ export default class TelegramAdapter {
43
43
  this.bot.telegram.setMyCommands(this._commands).catch(() => { });
44
44
  }
45
45
 
46
- console.log('[codexbot] Telegram bot started');
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('No platform configured. Run "codexbot onboard" first.');
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(`codexbot is already running (PID ${existing}). Use "codexbot stop" first.`);
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
- console.log(`codexbot started (PID ${child.pid})`);
40
- console.log(`Logs: ${LOG_FILE}`);
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('codexbot is not running.');
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
- console.log(`codexbot stopped (PID ${pid})`);
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
- console.log('codexbot process not found (stale PID file). Cleaning up.');
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(`Failed to stop codexbot: ${err.message}`);
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('codexbot is not running.');
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
- console.log(`codexbot is running (PID ${pid})`);
74
- console.log(`Logs: ${LOG_FILE}`);
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
+ }