orchestrix-yuri 2.2.0 → 2.2.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.
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { Bot } = require('grammy');
4
+ const { log } = require('../log');
4
5
 
5
6
  /**
6
7
  * Telegram channel adapter using grammy (polling mode).
@@ -19,7 +20,7 @@ class TelegramAdapter {
19
20
 
20
21
  async start() {
21
22
  if (!this.token) {
22
- throw new Error('Telegram bot token is required. Set it in ~/.yuri/config/channels.yaml or pass --telegram-token');
23
+ throw new Error('Telegram bot token is required. Run: orchestrix-yuri start --token YOUR_TOKEN');
23
24
  }
24
25
 
25
26
  this.bot = new Bot(this.token);
@@ -47,7 +48,7 @@ class TelegramAdapter {
47
48
  }
48
49
  }
49
50
  } catch (err) {
50
- console.error('[telegram] Error handling message:', err.message);
51
+ log.error(`Message handling failed: ${err.message}`);
51
52
  await ctx.reply('❌ Internal error. Check Yuri Gateway logs.').catch(() => {});
52
53
  }
53
54
  });
@@ -72,14 +73,28 @@ class TelegramAdapter {
72
73
 
73
74
  // Error handler
74
75
  this.bot.catch((err) => {
75
- console.error('[telegram] Bot error:', err.message);
76
+ const msg = err.message || String(err);
77
+ if (msg.includes('409') || msg.includes('Conflict')) {
78
+ log.error('Polling conflict — another instance is using this token.');
79
+ log.info('This instance will exit. Stop the other process and restart.');
80
+ process.exit(1);
81
+ }
82
+ log.warn(`Bot error: ${msg}`);
76
83
  });
77
84
 
85
+ // Clear any stale webhook/polling before starting
86
+ log.telegram('Connecting...');
87
+ try {
88
+ await this.bot.api.deleteWebhook({ drop_pending_updates: true });
89
+ } catch {
90
+ // ignore — not critical
91
+ }
92
+
78
93
  // Start polling
79
- console.log('[telegram] Starting bot polling...');
80
94
  await this.bot.start({
95
+ drop_pending_updates: true,
81
96
  onStart: (botInfo) => {
82
- console.log(`[telegram] Bot @${botInfo.username} is running (polling mode)`);
97
+ log.telegram(`Bot @${botInfo.username} is live (polling mode)`);
83
98
  },
84
99
  });
85
100
  }
@@ -87,7 +102,7 @@ class TelegramAdapter {
87
102
  async stop() {
88
103
  if (this.bot) {
89
104
  await this.bot.stop();
90
- console.log('[telegram] Bot stopped');
105
+ log.telegram('Bot stopped');
91
106
  }
92
107
  }
93
108
  }
@@ -80,8 +80,9 @@ function applyCliOverrides(config, opts) {
80
80
  // Persist token to channels.yaml so user only needs to pass it once
81
81
  if (shouldSave) {
82
82
  saveConfig(config);
83
- console.log(' ✅ Token saved to ~/.yuri/config/channels.yaml');
84
- console.log(' Next time just run: orchestrix-yuri start');
83
+ const { log } = require('./log');
84
+ log.ok('Token saved to ~/.yuri/config/channels.yaml');
85
+ log.info('Next time just run: orchestrix-yuri start');
85
86
  console.log('');
86
87
  }
87
88
 
@@ -8,6 +8,7 @@ const crypto = require('crypto');
8
8
  const yaml = require('js-yaml');
9
9
 
10
10
  const YURI_GLOBAL = path.join(os.homedir(), '.yuri');
11
+ const { log } = require('../log');
11
12
 
12
13
  // ── Shared Utilities (formerly in claude-cli.js) ───────────────────────────────
13
14
 
@@ -108,7 +109,7 @@ let _claudeBinary = null;
108
109
  function getClaudeBinary() {
109
110
  if (!_claudeBinary) {
110
111
  _claudeBinary = findClaudeBinary();
111
- console.log(`[claude-tmux] Using binary: ${_claudeBinary}`);
112
+ log.tmux(`Using binary: ${_claudeBinary}`);
112
113
  }
113
114
  return _claudeBinary;
114
115
  }
@@ -308,7 +309,7 @@ function ensureClaudeMd(projectRoot) {
308
309
  // Append channel mode instructions
309
310
  const separator = content.trim() ? '\n\n' : '';
310
311
  fs.writeFileSync(claudeMdPath, content + separator + CHANNEL_MODE_INSTRUCTIONS + '\n');
311
- console.log(`[claude-tmux] Channel Mode Instructions written to ${claudeMdPath}`);
312
+ log.tmux(`Channel Mode Instructions written to ${claudeMdPath}`);
312
313
  }
313
314
 
314
315
  /**
@@ -316,15 +317,15 @@ function ensureClaudeMd(projectRoot) {
316
317
  * Returns true if compact completed successfully.
317
318
  */
318
319
  async function proactiveCompact(name) {
319
- console.log('[claude-tmux] Proactive /compact triggered');
320
+ log.tmux('Proactive /compact triggered');
320
321
  injectMessage(name, '/compact focus on the most recent user conversation and any pending operations');
321
322
 
322
323
  const ok = await waitForIdle(name, 120000); // compact can take up to 2min
323
324
  if (ok) {
324
325
  _messageCount = 0;
325
- console.log('[claude-tmux] Proactive /compact completed');
326
+ log.tmux('Proactive /compact completed');
326
327
  } else {
327
- console.warn('[claude-tmux] Proactive /compact timed out');
328
+ log.warn('Proactive /compact timed out');
328
329
  }
329
330
  return ok;
330
331
  }
@@ -378,7 +379,7 @@ async function createSession(engineConfig) {
378
379
  }
379
380
 
380
381
  _sessionReady = true;
381
- console.log(`[claude-tmux] Session "${sessionName}" ready (cwd: ${projectRoot})`);
382
+ log.tmux(`Session "${sessionName}" ready (cwd: ${projectRoot})`);
382
383
  }
383
384
 
384
385
  /**
@@ -450,7 +451,7 @@ async function captureResponse(name, marker, engineConfig) {
450
451
  const poll = () => {
451
452
  // Timeout: return whatever we have
452
453
  if (Date.now() > deadline) {
453
- console.warn('[claude-tmux] Response capture timed out');
454
+ log.warn('Response capture timed out');
454
455
  const raw = capturePaneRaw(name, 500);
455
456
  return resolve(extractResponse(raw, marker));
456
457
  }
@@ -503,7 +504,7 @@ async function captureResponse(name, marker, engineConfig) {
503
504
  }
504
505
 
505
506
  if (stableCount >= stableThreshold && sawProcessing) {
506
- console.log('[claude-tmux] Response detected via content stability');
507
+ log.tmux('Response detected via content stability');
507
508
  return resolve(extractResponse(raw, marker));
508
509
  }
509
510
 
@@ -575,7 +576,7 @@ async function ensureSession(engineConfig) {
575
576
  await createSession(engineConfig);
576
577
  return;
577
578
  } catch (err) {
578
- console.error(`[claude-tmux] Session init attempt ${attempt}/${maxRetries} failed: ${err.message}`);
579
+ log.warn(`Session init attempt ${attempt}/${maxRetries} failed: ${err.message}`);
579
580
  if (attempt === maxRetries) throw err;
580
581
  // Brief pause before retry
581
582
  await new Promise((r) => setTimeout(r, 3000));
@@ -627,7 +628,7 @@ async function callClaude(opts) {
627
628
  _messageCount++;
628
629
  resolve(result);
629
630
  } catch (err) {
630
- console.error('[claude-tmux] callClaude error:', err.message);
631
+ log.error(`callClaude error: ${err.message}`);
631
632
 
632
633
  // Mark session as not ready so it gets recreated next time
633
634
  _sessionReady = false;
@@ -656,7 +657,7 @@ function composePrompt(userMessage, _chatHistory) {
656
657
  function destroySession() {
657
658
  if (_sessionName && hasSession(_sessionName)) {
658
659
  tmuxSafe(`kill-session -t ${_sessionName}`);
659
- console.log(`[claude-tmux] Session "${_sessionName}" destroyed.`);
660
+ log.tmux(`Session "${_sessionName}" destroyed.`);
660
661
  }
661
662
  _sessionName = null;
662
663
  _sessionReady = false;
@@ -3,6 +3,7 @@
3
3
  const { loadConfig, applyCliOverrides } = require('./config');
4
4
  const { Router } = require('./router');
5
5
  const { TelegramAdapter } = require('./channels/telegram');
6
+ const { log, c } = require('./log');
6
7
 
7
8
  /**
8
9
  * Start the Yuri Gateway.
@@ -12,9 +13,7 @@ const { TelegramAdapter } = require('./channels/telegram');
12
13
  * @param {string} [opts.port] - Override server port
13
14
  */
14
15
  async function startGateway(opts = {}) {
15
- console.log('');
16
- console.log(' 🚀 Yuri Gateway starting...');
17
- console.log('');
16
+ log.banner('🚀 Yuri Gateway starting...');
18
17
 
19
18
  // Load and merge config
20
19
  let config = loadConfig();
@@ -38,37 +37,41 @@ async function startGateway(opts = {}) {
38
37
  try {
39
38
  await telegram.start();
40
39
  } catch (err) {
41
- console.error(` ❌ Telegram failed to start: ${err.message}`);
40
+ if (err.message.includes('409') || err.message.includes('Conflict')) {
41
+ log.error('Another bot instance is already running with this token.');
42
+ log.info('Stop the other instance first, or wait a moment and retry.');
43
+ } else {
44
+ log.error(`Telegram failed to start: ${err.message}`);
45
+ }
42
46
  process.exit(1);
43
47
  }
44
48
  }
45
49
 
46
50
  // Start Feishu adapter if enabled (placeholder)
47
51
  if (config.channels.feishu.enabled) {
48
- console.log(' ⚠️ Feishu adapter not yet implemented. Skipping.');
52
+ log.warn('Feishu adapter not yet implemented. Skipping.');
49
53
  }
50
54
 
51
55
  // Check if any channel is active
52
56
  if (adapters.length === 0) {
53
57
  console.error('');
54
- console.error('No channels enabled. Set up your Telegram bot token:');
58
+ log.error('No channels enabled. Set up your Telegram bot token:');
55
59
  console.error('');
56
- console.error(' orchestrix-yuri start --token YOUR_BOT_TOKEN');
60
+ console.error(` ${c.cyan}orchestrix-yuri start --token YOUR_BOT_TOKEN${c.reset}`);
57
61
  console.error('');
58
- console.error(' The token is saved automatically. After that, just run:');
62
+ log.info('The token is saved automatically. After that, just run:');
59
63
  console.error('');
60
- console.error(' orchestrix-yuri start');
64
+ console.error(` ${c.cyan}orchestrix-yuri start${c.reset}`);
61
65
  console.error('');
62
66
  process.exit(1);
63
67
  }
64
68
 
65
- console.log('');
66
- console.log(' Yuri Gateway is running. Press Ctrl+C to stop.');
67
- console.log('');
69
+ log.banner('Yuri Gateway is running. Press Ctrl+C to stop.');
68
70
 
69
71
  // Graceful shutdown
70
72
  const shutdown = async () => {
71
- console.log('\n Shutting down Yuri Gateway...');
73
+ console.log('');
74
+ log.info('Shutting down Yuri Gateway...');
72
75
  await router.shutdown();
73
76
  for (const adapter of adapters) {
74
77
  await adapter.stop().catch(() => {});
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ // ── ANSI Color Helpers ─────────────────────────────────────────────────────────
4
+ // No dependencies — raw escape codes for terminal coloring.
5
+
6
+ const c = {
7
+ reset: '\x1b[0m',
8
+ bold: '\x1b[1m',
9
+ dim: '\x1b[2m',
10
+ red: '\x1b[31m',
11
+ green: '\x1b[32m',
12
+ yellow: '\x1b[33m',
13
+ blue: '\x1b[34m',
14
+ magenta: '\x1b[35m',
15
+ cyan: '\x1b[36m',
16
+ white: '\x1b[37m',
17
+ gray: '\x1b[90m',
18
+ bgRed: '\x1b[41m',
19
+ bgGreen: '\x1b[42m',
20
+ bgBlue: '\x1b[44m',
21
+ };
22
+
23
+ const tag = (color, label) => `${color}${c.bold}[${label}]${c.reset}`;
24
+
25
+ const log = {
26
+ // Tagged module loggers
27
+ gateway: (msg) => console.log(`${tag(c.magenta, 'gateway')} ${msg}`),
28
+ router: (msg) => console.log(`${tag(c.blue, 'router')} ${msg}`),
29
+ tmux: (msg) => console.log(`${tag(c.cyan, 'tmux')} ${msg}`),
30
+ telegram:(msg) => console.log(`${tag(c.green, 'telegram')} ${msg}`),
31
+
32
+ // Levels
33
+ ok: (msg) => console.log(` ${c.green}✅ ${msg}${c.reset}`),
34
+ warn: (msg) => console.warn(` ${c.yellow}⚠️ ${msg}${c.reset}`),
35
+ error: (msg) => console.error(` ${c.red}❌ ${msg}${c.reset}`),
36
+ info: (msg) => console.log(` ${c.dim}${msg}${c.reset}`),
37
+
38
+ // Banner
39
+ banner: (msg) => console.log(`\n ${c.bold}${c.magenta}${msg}${c.reset}\n`),
40
+ };
41
+
42
+ module.exports = { log, c };
@@ -8,6 +8,7 @@ const yaml = require('js-yaml');
8
8
  const { ChatHistory } = require('./history');
9
9
  const { OwnerBinding } = require('./binding');
10
10
  const engine = require('./engine/claude-tmux');
11
+ const { log } = require('./log');
11
12
 
12
13
  const YURI_GLOBAL = path.join(os.homedir(), '.yuri');
13
14
 
@@ -52,7 +53,7 @@ class Router {
52
53
  }
53
54
 
54
55
  if (authResult.firstBind) {
55
- console.log(`[router] First bind: ${msg.channelType} chat ${msg.chatId} (${msg.userName})`);
56
+ log.router(`First bind: ${msg.channelType} chat ${msg.chatId} (${msg.userName})`);
56
57
  }
57
58
 
58
59
  // Handle /start command
@@ -94,7 +95,7 @@ class Router {
94
95
  const prompt = engine.composePrompt(msg.text, chatHistory);
95
96
 
96
97
  // ═══ WORK: Call Claude engine ═══
97
- console.log(`[router] Processing: "${msg.text.slice(0, 80)}..." → cwd: ${projectRoot || '~'}`);
98
+ log.router(`Processing: "${msg.text.slice(0, 80)}..." → cwd: ${projectRoot || '~'}`);
98
99
  const result = await engine.callClaude({
99
100
  prompt,
100
101
  cwd: projectRoot,
@@ -114,7 +115,7 @@ class Router {
114
115
  // ═══ ENGINE: Update Focus (code-enforced) ═══
115
116
  this._updateGlobalFocus(msg, projectRoot);
116
117
 
117
- console.log(`[router] Reply: "${result.reply.slice(0, 80)}..."`);
118
+ log.router(`Reply: "${result.reply.slice(0, 80)}..."`);
118
119
  return { text: result.reply };
119
120
  }
120
121
 
@@ -132,7 +133,7 @@ class Router {
132
133
  const ONE_HOUR = 3600_000;
133
134
 
134
135
  if (gap > ONE_HOUR) {
135
- console.log(`[router] Catch-up: ${Math.round(gap / 60000)}min since last active. Refreshing portfolio.`);
136
+ log.router(`Catch-up: ${Math.round(gap / 60000)}min since last active. Refreshing portfolio.`);
136
137
  this._refreshPortfolioPulse();
137
138
  }
138
139
  }
@@ -215,7 +216,7 @@ class Router {
215
216
  focus.updated_at = new Date().toISOString();
216
217
  fs.writeFileSync(focusPath, yaml.dump(focus, { lineWidth: -1 }));
217
218
  } catch (err) {
218
- console.error('[router] Failed to update focus:', err.message);
219
+ log.warn(`Failed to update focus: ${err.message}`);
219
220
  }
220
221
  }
221
222
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-yuri",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Yuri — Meta-Orchestrator for Orchestrix. Drive your entire project lifecycle with natural language.",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {