orchestrix-yuri 2.2.1 → 2.3.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/bin/install.js CHANGED
@@ -13,8 +13,11 @@ if (command === 'install') {
13
13
  const projectRoot = args[1] || process.cwd();
14
14
  migrate(projectRoot);
15
15
  } else if (command === 'start' || command === 'serve') {
16
- // Delegate to serve.js with remaining args
17
16
  require('./serve');
17
+ } else if (command === 'stop') {
18
+ require('./stop');
19
+ } else if (command === 'status') {
20
+ require('./status');
18
21
  } else if (command === '--version' || command === '-v' || command === '-V') {
19
22
  const { version } = require('../package.json');
20
23
  console.log(version);
@@ -26,6 +29,8 @@ if (command === 'install') {
26
29
  orchestrix-yuri install Install Yuri skill + global memory
27
30
  orchestrix-yuri start Start the Channel Gateway
28
31
  orchestrix-yuri start --token TOKEN Start & save Telegram Bot token (first time only)
32
+ orchestrix-yuri stop Stop the running gateway
33
+ orchestrix-yuri status Show gateway status
29
34
  orchestrix-yuri migrate [path] Migrate legacy memory.yaml
30
35
  orchestrix-yuri --version Show version
31
36
  orchestrix-yuri --help Show this help message
package/bin/status.js ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const { execSync } = require('child_process');
8
+
9
+ const PID_FILE = path.join(os.homedir(), '.yuri', 'gateway.pid');
10
+
11
+ function status() {
12
+ // Check PID
13
+ let pid = null;
14
+ let running = false;
15
+
16
+ if (fs.existsSync(PID_FILE)) {
17
+ pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
18
+ try {
19
+ process.kill(pid, 0);
20
+ running = true;
21
+ } catch {
22
+ running = false;
23
+ }
24
+ }
25
+
26
+ // Check tmux session
27
+ let tmuxAlive = false;
28
+ try {
29
+ execSync('tmux has-session -t yuri-gateway 2>/dev/null');
30
+ tmuxAlive = true;
31
+ } catch {
32
+ tmuxAlive = false;
33
+ }
34
+
35
+ console.log('');
36
+ console.log(' Yuri Gateway Status');
37
+ console.log(' ───────────────────');
38
+ console.log(` Gateway process: ${running ? `\x1b[32mrunning\x1b[0m (PID ${pid})` : '\x1b[90mnot running\x1b[0m'}`);
39
+ console.log(` tmux session: ${tmuxAlive ? '\x1b[32myuri-gateway (active)\x1b[0m' : '\x1b[90mnone\x1b[0m'}`);
40
+
41
+ // Check config
42
+ const configPath = path.join(os.homedir(), '.yuri', 'config', 'channels.yaml');
43
+ if (fs.existsSync(configPath)) {
44
+ const content = fs.readFileSync(configPath, 'utf8');
45
+ const hasToken = /token:\s*".+"/.test(content) || /token:\s*'.+'/.test(content);
46
+ console.log(` Telegram token: ${hasToken ? '\x1b[32mconfigured\x1b[0m' : '\x1b[90mnot set\x1b[0m'}`);
47
+ } else {
48
+ console.log(' Config: \x1b[90mnot found\x1b[0m');
49
+ }
50
+
51
+ console.log('');
52
+ }
53
+
54
+ status();
package/bin/stop.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const { execSync } = require('child_process');
8
+
9
+ const PID_FILE = path.join(os.homedir(), '.yuri', 'gateway.pid');
10
+
11
+ function stop() {
12
+ if (!fs.existsSync(PID_FILE)) {
13
+ console.log(' No gateway is running (no PID file found).');
14
+ process.exit(0);
15
+ }
16
+
17
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
18
+
19
+ // Check if process is alive
20
+ try {
21
+ process.kill(pid, 0); // signal 0 = just check existence
22
+ } catch {
23
+ console.log(` Gateway PID ${pid} is not running. Cleaning up stale PID file.`);
24
+ fs.unlinkSync(PID_FILE);
25
+ cleanupTmux();
26
+ process.exit(0);
27
+ }
28
+
29
+ // Send SIGTERM for graceful shutdown
30
+ console.log(` Stopping gateway (PID ${pid})...`);
31
+ try {
32
+ process.kill(pid, 'SIGTERM');
33
+ } catch (err) {
34
+ console.error(` ❌ Failed to stop: ${err.message}`);
35
+ process.exit(1);
36
+ }
37
+
38
+ // Wait briefly then verify
39
+ setTimeout(() => {
40
+ try {
41
+ process.kill(pid, 0);
42
+ // Still alive, force kill
43
+ console.log(' Process did not exit gracefully, sending SIGKILL...');
44
+ process.kill(pid, 'SIGKILL');
45
+ } catch {
46
+ // Dead, good
47
+ }
48
+ cleanupTmux();
49
+ try { fs.unlinkSync(PID_FILE); } catch {}
50
+ console.log(' ✅ Gateway stopped.');
51
+ }, 2000);
52
+ }
53
+
54
+ function cleanupTmux() {
55
+ try {
56
+ execSync('tmux kill-session -t yuri-gateway 2>/dev/null');
57
+ } catch {
58
+ // no session to kill
59
+ }
60
+ }
61
+
62
+ stop();
@@ -82,12 +82,16 @@ class TelegramAdapter {
82
82
  log.warn(`Bot error: ${msg}`);
83
83
  });
84
84
 
85
- // Clear any stale webhook/polling before starting
85
+ // Force-disconnect any stale polling connection before starting.
86
+ // deleteWebhook clears webhooks but does NOT terminate existing
87
+ // long-polling getUpdates connections. A short getUpdates call
88
+ // with timeout=0 "steals" the connection, terminating the old one.
86
89
  log.telegram('Connecting...');
87
90
  try {
88
91
  await this.bot.api.deleteWebhook({ drop_pending_updates: true });
92
+ await this.bot.api.raw.getUpdates({ offset: -1, limit: 1, timeout: 0 });
89
93
  } catch {
90
- // ignore — not critical
94
+ // ignore — best effort cleanup
91
95
  }
92
96
 
93
97
  // Start polling
@@ -1,10 +1,15 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
3
6
  const { loadConfig, applyCliOverrides } = require('./config');
4
7
  const { Router } = require('./router');
5
8
  const { TelegramAdapter } = require('./channels/telegram');
6
9
  const { log, c } = require('./log');
7
10
 
11
+ const PID_FILE = path.join(os.homedir(), '.yuri', 'gateway.pid');
12
+
8
13
  /**
9
14
  * Start the Yuri Gateway.
10
15
  *
@@ -39,7 +44,7 @@ async function startGateway(opts = {}) {
39
44
  } catch (err) {
40
45
  if (err.message.includes('409') || err.message.includes('Conflict')) {
41
46
  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.');
47
+ log.info('Run: orchestrix-yuri stop');
43
48
  } else {
44
49
  log.error(`Telegram failed to start: ${err.message}`);
45
50
  }
@@ -66,6 +71,9 @@ async function startGateway(opts = {}) {
66
71
  process.exit(1);
67
72
  }
68
73
 
74
+ // Write PID file for `orchestrix-yuri stop`
75
+ fs.writeFileSync(PID_FILE, String(process.pid));
76
+
69
77
  log.banner('Yuri Gateway is running. Press Ctrl+C to stop.');
70
78
 
71
79
  // Graceful shutdown
@@ -76,6 +84,7 @@ async function startGateway(opts = {}) {
76
84
  for (const adapter of adapters) {
77
85
  await adapter.stop().catch(() => {});
78
86
  }
87
+ try { fs.unlinkSync(PID_FILE); } catch {}
79
88
  process.exit(0);
80
89
  };
81
90
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-yuri",
3
- "version": "2.2.1",
3
+ "version": "2.3.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": {