bus-agent 2.3.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/.env.coco +11 -0
- package/AGENTS.md +37 -0
- package/LICENSE +21 -0
- package/README.md +370 -0
- package/SKILL.md +314 -0
- package/backup.js +57 -0
- package/bin/cli.js +41 -0
- package/bridge.js +325 -0
- package/claude-mcp.json +10 -0
- package/clients/coco-client.ts +245 -0
- package/clients/coco_client.py +216 -0
- package/coco-aliases.sh +10 -0
- package/coco-cli.js +1002 -0
- package/coco-tool.js +177 -0
- package/coco.js +26 -0
- package/cursor-mcp.json +3 -0
- package/doctor.js +24 -0
- package/hermes-forwarder.js +152 -0
- package/hermes.example.json +9 -0
- package/index.js +52 -0
- package/lib/backup.js +256 -0
- package/lib/bus.js +516 -0
- package/lib/daemon.js +96 -0
- package/lib/doctor.js +333 -0
- package/lib/hermes.js +162 -0
- package/lib/mcp.js +730 -0
- package/lib/memory.js +667 -0
- package/lib/orchestrator.js +426 -0
- package/lib/scheduler.js +259 -0
- package/lib/tunnel.js +317 -0
- package/mcporter.example.json +14 -0
- package/opencode-mcp.json +10 -0
- package/package.json +76 -0
- package/scripts/install.bat +5 -0
- package/scripts/install.ps1 +100 -0
- package/setup.js +320 -0
- package/tunnel.js +66 -0
- package/webhook-gateway.js +420 -0
package/coco-tool.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CoCo Tool Wrapper — Direct bus calls from any tool/exec context
|
|
4
|
+
*
|
|
5
|
+
* Acts as a lightweight RPC bridge between tools (OpenClaw, CLI, etc.)
|
|
6
|
+
* and the CoCo Agent Bus. No MCP needed — direct file access.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node coco-tool.js <tool> [args...]
|
|
10
|
+
*
|
|
11
|
+
* Examples:
|
|
12
|
+
* node coco-tool.js agent_list '{"online_only":true}'
|
|
13
|
+
* node coco-tool.js agent_get_profile '{"name":"coco"}'
|
|
14
|
+
* node coco-tool.js message_send '{"from":"andul","to":"hermes","message":"Hello!"}'
|
|
15
|
+
* node coco-tool.js message_fetch '{"agent_name":"andul"}'
|
|
16
|
+
* node coco-tool.js agent_search '{"query":"code"}'
|
|
17
|
+
* node coco-tool.js agent_register '{"name":"andul","description":"Andul 🐺","capabilities":["chat","code","browsing"]}'
|
|
18
|
+
* node coco-tool.js coco_health '{}'
|
|
19
|
+
*/
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
|
|
23
|
+
// Load AgentBus
|
|
24
|
+
require('./lib/bus');
|
|
25
|
+
const bus = new (require('./lib/bus').AgentBus)();
|
|
26
|
+
const { HermesBridge } = require('./lib/hermes');
|
|
27
|
+
const bridge = new HermesBridge();
|
|
28
|
+
const { Scheduler } = require('./lib/scheduler');
|
|
29
|
+
const scheduler = new Scheduler(bus);
|
|
30
|
+
const { Orchestrator } = require('./lib/orchestrator');
|
|
31
|
+
const orchestrator = new Orchestrator(bus);
|
|
32
|
+
|
|
33
|
+
const tool = process.argv[2];
|
|
34
|
+
const argsRaw = process.argv[3] || '{}';
|
|
35
|
+
let args = {};
|
|
36
|
+
try { args = JSON.parse(argsRaw); } catch {}
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
let result;
|
|
40
|
+
|
|
41
|
+
switch (tool) {
|
|
42
|
+
// CoCo Health
|
|
43
|
+
case 'coco_health':
|
|
44
|
+
result = await bridge.healthCheck();
|
|
45
|
+
break;
|
|
46
|
+
|
|
47
|
+
// Registrations
|
|
48
|
+
case 'agent_register': {
|
|
49
|
+
const meta = {
|
|
50
|
+
capabilities: args.capabilities || [],
|
|
51
|
+
model: args.model || null,
|
|
52
|
+
tags: args.tags || [],
|
|
53
|
+
version: args.version || '1.0.0',
|
|
54
|
+
endpoints: args.endpoints || {},
|
|
55
|
+
tools: args.tools || [],
|
|
56
|
+
status: args.status || 'idle',
|
|
57
|
+
};
|
|
58
|
+
result = bus.registerAgent(args.name, args.description || '', meta);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case 'agent_update_profile':
|
|
62
|
+
result = bus.updateProfile(args.name, args);
|
|
63
|
+
break;
|
|
64
|
+
case 'agent_get_profile':
|
|
65
|
+
result = { agent: bus.getProfile(args.name) };
|
|
66
|
+
break;
|
|
67
|
+
case 'agent_list':
|
|
68
|
+
result = { agents: bus.listAgents(args) };
|
|
69
|
+
break;
|
|
70
|
+
case 'agent_search':
|
|
71
|
+
result = { query: args.query, agents: bus.listAgents({ search: args.query }).slice(0, args.max_results || 20) };
|
|
72
|
+
break;
|
|
73
|
+
case 'agent_heartbeat':
|
|
74
|
+
result = bus.heartbeat(args.name);
|
|
75
|
+
break;
|
|
76
|
+
case 'agent_set_status':
|
|
77
|
+
result = bus.setStatus(args.name, args.status);
|
|
78
|
+
break;
|
|
79
|
+
|
|
80
|
+
// Messages
|
|
81
|
+
case 'message_send':
|
|
82
|
+
result = bus.sendMessage(args.from, args.to, args.message, args.metadata);
|
|
83
|
+
break;
|
|
84
|
+
case 'message_broadcast':
|
|
85
|
+
result = bus.broadcastMessage(args.from, args.message);
|
|
86
|
+
break;
|
|
87
|
+
case 'message_fetch':
|
|
88
|
+
result = { messages: bus.fetchMessages(args.agent_name, args.limit) };
|
|
89
|
+
break;
|
|
90
|
+
case 'message_delete':
|
|
91
|
+
result = bus.deleteMessages(args.agent_name, args.message_ids);
|
|
92
|
+
break;
|
|
93
|
+
|
|
94
|
+
// Channels
|
|
95
|
+
case 'channel_create':
|
|
96
|
+
result = bus.createChannel(args.channel_id, args.topic, args.created_by);
|
|
97
|
+
break;
|
|
98
|
+
case 'channel_join':
|
|
99
|
+
result = bus.joinChannel(args.channel_id, args.agent_name);
|
|
100
|
+
break;
|
|
101
|
+
case 'channel_leave':
|
|
102
|
+
result = bus.leaveChannel(args.channel_id, args.agent_name);
|
|
103
|
+
break;
|
|
104
|
+
case 'channel_send':
|
|
105
|
+
result = bus.channelSend(args.channel_id, args.from, args.message);
|
|
106
|
+
break;
|
|
107
|
+
case 'channel_history':
|
|
108
|
+
result = { messages: bus.channelHistory(args.channel_id, args.limit) };
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
// Events
|
|
112
|
+
case 'system_get_events':
|
|
113
|
+
result = { events: bus.getEvents(args) };
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
// Scheduler
|
|
117
|
+
case 'scheduler_add':
|
|
118
|
+
result = scheduler.addJob(args);
|
|
119
|
+
break;
|
|
120
|
+
case 'scheduler_remove':
|
|
121
|
+
result = scheduler.removeJob(args.job_id);
|
|
122
|
+
break;
|
|
123
|
+
case 'scheduler_list':
|
|
124
|
+
result = { jobs: scheduler.listJobs() };
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
// Auto-Reply Rules
|
|
128
|
+
case 'auto_reply_add':
|
|
129
|
+
result = orchestrator.addRule(args);
|
|
130
|
+
break;
|
|
131
|
+
case 'auto_reply_remove':
|
|
132
|
+
result = orchestrator.removeRule(args.rule_id);
|
|
133
|
+
break;
|
|
134
|
+
case 'auto_reply_list':
|
|
135
|
+
result = { rules: orchestrator.listRules() };
|
|
136
|
+
break;
|
|
137
|
+
|
|
138
|
+
// Workflows
|
|
139
|
+
case 'workflow_create':
|
|
140
|
+
result = orchestrator.createWorkflow(args);
|
|
141
|
+
break;
|
|
142
|
+
case 'workflow_run':
|
|
143
|
+
result = await orchestrator.runWorkflow(args.workflow_id, args.input, args.requester);
|
|
144
|
+
break;
|
|
145
|
+
case 'workflow_remove':
|
|
146
|
+
result = orchestrator.removeWorkflow(args.workflow_id);
|
|
147
|
+
break;
|
|
148
|
+
case 'workflow_list':
|
|
149
|
+
result = { workflows: orchestrator.listWorkflows() };
|
|
150
|
+
break;
|
|
151
|
+
|
|
152
|
+
default:
|
|
153
|
+
console.error(JSON.stringify({ error: `Unknown tool: ${tool}`, tools: getToolList() }));
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
process.stdout.write(JSON.stringify(result, null, 2));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getToolList() {
|
|
161
|
+
return [
|
|
162
|
+
'coco_health',
|
|
163
|
+
'agent_register', 'agent_update_profile', 'agent_get_profile',
|
|
164
|
+
'agent_list', 'agent_search', 'agent_heartbeat', 'agent_set_status',
|
|
165
|
+
'message_send', 'message_broadcast', 'message_fetch', 'message_delete',
|
|
166
|
+
'channel_create', 'channel_join', 'channel_leave', 'channel_send', 'channel_history',
|
|
167
|
+
'system_get_events',
|
|
168
|
+
'scheduler_add', 'scheduler_remove', 'scheduler_list',
|
|
169
|
+
'auto_reply_add', 'auto_reply_remove', 'auto_reply_list',
|
|
170
|
+
'workflow_create', 'workflow_run', 'workflow_remove', 'workflow_list',
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
main().catch(err => {
|
|
175
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
176
|
+
process.exit(1);
|
|
177
|
+
});
|
package/coco.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CoCo — Convenience entry point for CLI agents
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node coco.js agents # List agents
|
|
7
|
+
* node coco.js inbox # Check your messages
|
|
8
|
+
* node coco.js send "message" # Send to last reply-to agent
|
|
9
|
+
* node coco.js send <to> msg # Send to specific agent
|
|
10
|
+
* node coco.js watch # Watch for new messages
|
|
11
|
+
*/
|
|
12
|
+
const { execSync } = require('child_process');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const CLI = path.join(__dirname, 'coco-cli.js');
|
|
16
|
+
const args = process.argv.slice(2).join(' ');
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const result = execSync(`node "${CLI}" ${args}`, {
|
|
20
|
+
encoding: 'utf-8',
|
|
21
|
+
stdio: 'inherit',
|
|
22
|
+
env: { ...process.env },
|
|
23
|
+
});
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// errors already shown via stdio
|
|
26
|
+
}
|
package/cursor-mcp.json
ADDED
package/doctor.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CoCo Doctor — Diagnostics CLI Wrapper
|
|
4
|
+
*
|
|
5
|
+
* Thin wrapper around lib/doctor.js
|
|
6
|
+
* Usage: node doctor.js [--quick] [--fix] [--report] [--watch]
|
|
7
|
+
*/
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { runDiagnostics, watchDiagnostics } = require('./lib/doctor');
|
|
10
|
+
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
const opts = {
|
|
13
|
+
busDir: path.join(__dirname, '.bus'),
|
|
14
|
+
quick: args.includes('--quick'),
|
|
15
|
+
fix: args.includes('--fix'),
|
|
16
|
+
report: args.includes('--report'),
|
|
17
|
+
watch: args.includes('--watch'),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
if (opts.watch) {
|
|
21
|
+
watchDiagnostics(opts);
|
|
22
|
+
} else {
|
|
23
|
+
runDiagnostics(opts);
|
|
24
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Hermes Auto-Forwarder — automatically forwards DMs to Hermes Agent
|
|
4
|
+
*
|
|
5
|
+
* Watches the bus for messages addressed to "hermes", sends them
|
|
6
|
+
* through Hermes CLI (ask_hermes), and posts the reply back
|
|
7
|
+
* to the sender's inbox.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node hermes-forwarder.js
|
|
11
|
+
*
|
|
12
|
+
* Set up as Scheduled Task for auto-start:
|
|
13
|
+
* See scripts/install.ps1 or register manually:
|
|
14
|
+
* New-ScheduledTaskAction -Execute "node.exe" -Argument "...\hermes-forwarder.js"
|
|
15
|
+
*/
|
|
16
|
+
const { spawn } = require('child_process');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
|
|
20
|
+
const BUS_DIR = path.join(__dirname, '.bus');
|
|
21
|
+
const MSGS_DIR = path.join(BUS_DIR, 'messages');
|
|
22
|
+
const AGENTS_FILE = path.join(BUS_DIR, 'agents.json');
|
|
23
|
+
const TARGET = 'hermes';
|
|
24
|
+
const POLL_MS = 3000;
|
|
25
|
+
const HERMES_TIMEOUT = 190000;
|
|
26
|
+
|
|
27
|
+
// Track processed message IDs
|
|
28
|
+
const processed = new Set();
|
|
29
|
+
|
|
30
|
+
function ensureDir(dir) {
|
|
31
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function register() {
|
|
35
|
+
const agents = fs.existsSync(AGENTS_FILE)
|
|
36
|
+
? JSON.parse(fs.readFileSync(AGENTS_FILE, 'utf-8'))
|
|
37
|
+
: {};
|
|
38
|
+
agents['hermes-fwd'] = {
|
|
39
|
+
name: 'hermes-fwd',
|
|
40
|
+
description: 'Hermes Auto-Forwarder — relays DMs to Hermes Agent and replies back',
|
|
41
|
+
last_seen: new Date().toISOString(),
|
|
42
|
+
registered_at: agents['hermes-fwd']?.registered_at || new Date().toISOString(),
|
|
43
|
+
};
|
|
44
|
+
fs.writeFileSync(AGENTS_FILE, JSON.stringify(agents, null, 2), 'utf-8');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Send a prompt to Hermes CLI (async with timeout).
|
|
49
|
+
* Returns the cleaned response string.
|
|
50
|
+
*/
|
|
51
|
+
function askHermes(prompt) {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
const escapedPrompt = prompt.replace(/"/g, '\\"');
|
|
54
|
+
const child = spawn(
|
|
55
|
+
'cmd.exe',
|
|
56
|
+
['/c', `hermes chat -q "${escapedPrompt}" --quiet --source tool 2>&1`],
|
|
57
|
+
{ windowsHide: true, shell: true }
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
let output = '';
|
|
61
|
+
child.stdout.on('data', (d) => { output += d.toString(); });
|
|
62
|
+
child.stderr.on('data', (d) => { output += d.toString(); });
|
|
63
|
+
|
|
64
|
+
child.on('close', () => {
|
|
65
|
+
const clean = output.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
66
|
+
const lines = clean.split('\n').filter((l) => {
|
|
67
|
+
const s = l.trim();
|
|
68
|
+
if (!s) return false;
|
|
69
|
+
if (s.startsWith('Query:') || s.startsWith('Resume') || s.startsWith('session_id:')) return false;
|
|
70
|
+
if (s.includes('Normalized model') || s.includes('Initializing')) return false;
|
|
71
|
+
if (s.includes('opencode') || s.includes('deepseek')) return false;
|
|
72
|
+
if (s.startsWith('⚠')) return false;
|
|
73
|
+
return true;
|
|
74
|
+
});
|
|
75
|
+
resolve(lines.join('\n').trim() || '(empty response)');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
child.on('error', (e) => resolve(`[Hermes error: ${e.message.substring(0, 60)}]`));
|
|
79
|
+
|
|
80
|
+
// Safety timeout
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
child.kill();
|
|
83
|
+
resolve('[Hermes timeout]');
|
|
84
|
+
}, HERMES_TIMEOUT);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function sendReply(to, originalMsg, replyText) {
|
|
89
|
+
const inboxDir = path.join(MSGS_DIR, to);
|
|
90
|
+
ensureDir(inboxDir);
|
|
91
|
+
|
|
92
|
+
const msg = {
|
|
93
|
+
id: `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
|
|
94
|
+
from: TARGET,
|
|
95
|
+
to,
|
|
96
|
+
message: replyText,
|
|
97
|
+
in_reply_to: originalMsg.id,
|
|
98
|
+
timestamp: new Date().toISOString(),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
fs.writeFileSync(path.join(inboxDir, `${msg.id}.json`), JSON.stringify(msg, null, 2), 'utf-8');
|
|
102
|
+
console.log(` 📬 Reply sent to "${to}" (id: ${msg.id})`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function poll() {
|
|
106
|
+
const inboxDir = path.join(MSGS_DIR, TARGET);
|
|
107
|
+
if (!fs.existsSync(MSGS_DIR) || !fs.existsSync(inboxDir)) return;
|
|
108
|
+
|
|
109
|
+
const files = fs.readdirSync(inboxDir).filter((f) => f.endsWith('.json'));
|
|
110
|
+
|
|
111
|
+
for (const f of files) {
|
|
112
|
+
if (processed.has(f)) continue;
|
|
113
|
+
processed.add(f);
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const msg = JSON.parse(fs.readFileSync(path.join(inboxDir, f), 'utf-8'));
|
|
117
|
+
|
|
118
|
+
// Skip messages from bots/self
|
|
119
|
+
if (['hermes', 'hermes-fwd', 'coco', 'webhook'].includes(msg.from)) continue;
|
|
120
|
+
|
|
121
|
+
console.log(`\n[${new Date().toISOString()}] 📩 DM from "${msg.from}": ${msg.message.substring(0, 80)}`);
|
|
122
|
+
|
|
123
|
+
console.log(` 🤖 Forwarding to Hermes...`);
|
|
124
|
+
const reply = await askHermes(msg.message);
|
|
125
|
+
console.log(` 💬 Hermes: ${reply.substring(0, 80)}`);
|
|
126
|
+
|
|
127
|
+
sendReply(msg.from, msg, reply);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.error(` ❌ Error processing ${f}: ${err.message}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Main ──
|
|
135
|
+
|
|
136
|
+
console.log(`
|
|
137
|
+
╔══════════════════════════════════════════╗
|
|
138
|
+
║ MCP CoCo — Hermes Auto-Forwarder ║
|
|
139
|
+
║ ║
|
|
140
|
+
║ Any DM to "hermes" → ║
|
|
141
|
+
║ ask_hermes(prompt) → reply back ║
|
|
142
|
+
║ ║
|
|
143
|
+
║ Polling every ${POLL_MS}ms ║
|
|
144
|
+
╚══════════════════════════════════════════╝
|
|
145
|
+
`);
|
|
146
|
+
|
|
147
|
+
register();
|
|
148
|
+
ensureDir(path.join(MSGS_DIR, TARGET));
|
|
149
|
+
|
|
150
|
+
// Register as forwarder on the webhook gateway too via agents.json
|
|
151
|
+
poll();
|
|
152
|
+
setInterval(poll, POLL_MS);
|
package/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Bus Agent v2.3 — Universal Agent Communication Hub
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node index.js # stdio MCP server (for mcporter)
|
|
7
|
+
* node index.js --daemon # background daemon + scheduler
|
|
8
|
+
* node index.js --health # one-shot health check
|
|
9
|
+
* node index.js --scheduler # Run scheduler standalone
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { runStdio } = require('./lib/mcp');
|
|
13
|
+
const { Daemon } = require('./lib/daemon');
|
|
14
|
+
const { AgentBus } = require('./lib/bus');
|
|
15
|
+
const { Scheduler } = require('./lib/scheduler');
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
|
|
20
|
+
if (args.includes('--daemon')) {
|
|
21
|
+
const daemon = new Daemon();
|
|
22
|
+
await daemon.start();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (args.includes('--health')) {
|
|
27
|
+
const daemon = new Daemon();
|
|
28
|
+
const ok = await daemon.healthCheck();
|
|
29
|
+
process.exit(ok ? 0 : 1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (args.includes('--scheduler')) {
|
|
33
|
+
const bus = new AgentBus();
|
|
34
|
+
const scheduler = new Scheduler(bus);
|
|
35
|
+
scheduler.start();
|
|
36
|
+
console.log('CoCo Scheduler running. Press Ctrl+C to stop.');
|
|
37
|
+
// Keep alive
|
|
38
|
+
process.on('SIGINT', () => { scheduler.stop(); process.exit(0); });
|
|
39
|
+
process.on('SIGTERM', () => { scheduler.stop(); process.exit(0); });
|
|
40
|
+
// Hang around
|
|
41
|
+
setInterval(() => {}, 60000);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Default: stdio MCP server
|
|
46
|
+
await runStdio();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
main().catch(err => {
|
|
50
|
+
console.error('FATAL:', err.message);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|