@inkeep/agents-cli 0.1.0 → 0.1.2

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,193 +0,0 @@
1
- import { execSync, spawn } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
3
- import { dirname, resolve } from 'node:path';
4
- import { pathToFileURL } from 'node:url';
5
- import chalk from 'chalk';
6
- import ora from 'ora';
7
- export async function mcpStartCommand(graphPath, options) {
8
- let spinner = ora('Loading graph configuration...').start();
9
- try {
10
- // Resolve the graph path
11
- const fullPath = resolve(graphPath);
12
- if (!existsSync(fullPath)) {
13
- spinner.fail(`Graph file not found: ${graphPath}`);
14
- process.exit(1);
15
- }
16
- spinner.text = 'Starting MCP servers...';
17
- // Get the directory of the graph file for proper module resolution
18
- const graphDir = dirname(fullPath);
19
- // Check if it's a TypeScript file
20
- if (fullPath.endsWith('.ts')) {
21
- spinner.text = 'Checking TypeScript runtime...';
22
- // Check if tsx is available in the graph directory
23
- try {
24
- execSync('npx tsx --version', {
25
- stdio: 'pipe',
26
- cwd: graphDir,
27
- });
28
- }
29
- catch {
30
- spinner.fail('TypeScript runtime not found');
31
- console.log(chalk.yellow('\nTo use TypeScript graph files:'));
32
- console.log(chalk.gray(' Option 1: Compile your graph first'));
33
- console.log(chalk.gray(' cd inkeep-chat && pnpm build'));
34
- console.log(chalk.gray(' inkeep mcp start inkeep-chat/dist/examples/graph.graph.js'));
35
- console.log(chalk.gray('\n Option 2: Ensure tsx is installed in the graph directory'));
36
- console.log(chalk.gray(' cd inkeep-chat && pnpm add -D tsx'));
37
- process.exit(1);
38
- }
39
- }
40
- // Convert to file URL for import
41
- const fileUrl = pathToFileURL(fullPath).href;
42
- // Spawn tsx process to run the runner with the graph file
43
- const child = spawn('npx', [
44
- 'tsx',
45
- '--eval',
46
- `
47
- import('${fileUrl}').then(async module => {
48
- const servers = module.servers || module.tools || [];
49
- const graph = module.graph;
50
-
51
- let graphId = 'unknown';
52
- if (graph && typeof graph.getId === 'function') {
53
- graphId = graph.getId();
54
- }
55
-
56
- const startedServers = [];
57
- let nextPort = 3100;
58
-
59
- for (const server of servers) {
60
- if (!server) continue;
61
-
62
- // Try to get name using getName() method or direct property access
63
- let name = 'unnamed';
64
- if (typeof server.getName === 'function') {
65
- name = server.getName();
66
- } else if (server.config && server.config.name) {
67
- name = server.config.name;
68
- } else if (server.name) {
69
- name = server.name;
70
- }
71
-
72
- // Try to get server URL
73
- let serverUrl = undefined;
74
- if (typeof server.getServerUrl === 'function') {
75
- serverUrl = server.getServerUrl();
76
- } else if (server.config && server.config.serverUrl) {
77
- serverUrl = server.config.serverUrl;
78
- } else if (server.serverUrl) {
79
- serverUrl = server.serverUrl;
80
- }
81
-
82
- const isLocal = !serverUrl;
83
- const port = isLocal ? (server.port || server.config?.port || nextPort++) : undefined;
84
-
85
- console.log(JSON.stringify({
86
- type: 'server_started',
87
- name,
88
- port,
89
- serverUrl,
90
- deployment: isLocal ? 'local' : 'remote'
91
- }));
92
-
93
- startedServers.push(name);
94
- }
95
-
96
- console.log(JSON.stringify({
97
- type: 'all_started',
98
- count: startedServers.length,
99
- graphId
100
- }));
101
-
102
- if (!${options.detached || false}) {
103
- // Keep process alive
104
- process.stdin.resume();
105
-
106
- process.on('SIGINT', () => {
107
- console.log(JSON.stringify({ type: 'shutting_down' }));
108
- process.exit(0);
109
- });
110
- }
111
- }).catch(error => {
112
- console.error(JSON.stringify({
113
- type: 'error',
114
- message: error.message
115
- }));
116
- process.exit(1);
117
- });
118
- `,
119
- ], {
120
- stdio: ['inherit', 'pipe', 'pipe'],
121
- cwd: graphDir, // Set working directory to graph file's directory
122
- env: {
123
- ...process.env,
124
- ENVIRONMENT: process.env.ENVIRONMENT || 'test',
125
- DB_FILE_NAME: process.env.DB_FILE_NAME || ':memory:',
126
- INKEEP_TENANT_ID: process.env.INKEEP_TENANT_ID || 'test-tenant',
127
- ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || 'test-key',
128
- },
129
- });
130
- let serverCount = 0;
131
- let graphId = 'unknown';
132
- // Handle stdout
133
- child.stdout?.on('data', (data) => {
134
- const lines = data.toString().split('\n').filter(Boolean);
135
- for (const line of lines) {
136
- try {
137
- const msg = JSON.parse(line);
138
- if (msg.type === 'server_started') {
139
- const icon = msg.deployment === 'local' ? '🏠' : '☁️';
140
- const location = msg.deployment === 'local'
141
- ? `on port ${chalk.cyan(msg.port)}`
142
- : `at ${chalk.cyan(msg.serverUrl)}`;
143
- spinner.succeed(`${icon} Started ${chalk.green(msg.name)} ${location}`);
144
- spinner = ora().start(); // Create new spinner for next server
145
- serverCount++;
146
- }
147
- else if (msg.type === 'all_started') {
148
- spinner.stop();
149
- console.log(chalk.green(`\n✅ Started ${msg.count} MCP server(s)`));
150
- graphId = msg.graphId || 'unknown';
151
- if (!options.detached) {
152
- console.log(chalk.gray('\nServers are running. Press Ctrl+C to stop all servers.\n'));
153
- }
154
- }
155
- else if (msg.type === 'shutting_down') {
156
- console.log(chalk.yellow('\n\nShutting down MCP servers...'));
157
- }
158
- else if (msg.type === 'error') {
159
- spinner.fail(`Error: ${msg.message}`);
160
- }
161
- }
162
- catch {
163
- // Not JSON, just print it
164
- if (options.verbose) {
165
- console.log(chalk.gray(line));
166
- }
167
- }
168
- }
169
- });
170
- // Handle stderr
171
- child.stderr?.on('data', (data) => {
172
- if (options.verbose) {
173
- console.error(chalk.red(data.toString()));
174
- }
175
- });
176
- // Handle exit
177
- child.on('exit', (code) => {
178
- if (code !== 0 && code !== null) {
179
- spinner.fail('Failed to start MCP servers');
180
- process.exit(code);
181
- }
182
- });
183
- // Forward SIGINT to child
184
- process.on('SIGINT', () => {
185
- child.kill('SIGINT');
186
- });
187
- }
188
- catch (error) {
189
- spinner.fail('Failed to start MCP servers');
190
- console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
191
- process.exit(1);
192
- }
193
- }
@@ -1,5 +0,0 @@
1
- export interface McpStartOptions {
2
- detached?: boolean;
3
- verbose?: boolean;
4
- }
5
- export declare function mcpStartCommand(graphPath: string, options: McpStartOptions): Promise<void>;
@@ -1,217 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join, resolve } from 'node:path';
4
- import chalk from 'chalk';
5
- import ora from 'ora';
6
- import { PortManager } from '../utils/port-manager.js';
7
- import { loadTypeScriptModule } from '../utils/ts-loader.js';
8
- const MCP_DIR = join(homedir(), '.inkeep', 'mcp');
9
- const REGISTRY_FILE = join(MCP_DIR, 'servers.json');
10
- // Ensure MCP directory exists
11
- if (!existsSync(MCP_DIR)) {
12
- mkdirSync(MCP_DIR, { recursive: true });
13
- }
14
- function loadRegistry() {
15
- if (!existsSync(REGISTRY_FILE)) {
16
- return { servers: [] };
17
- }
18
- try {
19
- return JSON.parse(readFileSync(REGISTRY_FILE, 'utf-8'));
20
- }
21
- catch {
22
- return { servers: [] };
23
- }
24
- }
25
- function saveRegistry(registry) {
26
- writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2));
27
- }
28
- function cleanupStaleServers(registry) {
29
- // Remove servers whose processes are no longer running
30
- const activeServers = registry.servers.filter((server) => {
31
- try {
32
- // Check if process is still running
33
- process.kill(server.pid, 0);
34
- return true;
35
- }
36
- catch {
37
- return false;
38
- }
39
- });
40
- return { servers: activeServers };
41
- }
42
- export async function mcpStartCommand(graphPath, options) {
43
- const spinner = ora('Loading graph configuration...').start();
44
- try {
45
- // Resolve the graph path
46
- const fullPath = resolve(graphPath);
47
- if (!existsSync(fullPath)) {
48
- spinner.fail(`Graph file not found: ${graphPath}`);
49
- process.exit(1);
50
- }
51
- spinner.text = 'Importing graph configuration...';
52
- // Import the graph module
53
- let module;
54
- if (fullPath.endsWith('.ts')) {
55
- // For TypeScript files, use our loader
56
- try {
57
- module = await loadTypeScriptModule(fullPath);
58
- }
59
- catch (error) {
60
- spinner.fail('Failed to load TypeScript graph configuration');
61
- console.error(chalk.red('Error:'), error.message);
62
- process.exit(1);
63
- }
64
- }
65
- else {
66
- // For JavaScript files, import directly
67
- module = await import(fullPath);
68
- }
69
- // Find exported tools (both regular exports and default export)
70
- const tools = [];
71
- const toolMap = new Map();
72
- // Check for exported servers (new convention)
73
- if (module.servers && module.servers.__type === 'array' && module.servers.items) {
74
- tools.push(...module.servers.items);
75
- }
76
- // Check for exported tools (backwards compatibility)
77
- if (module.tools && module.tools.__type === 'array' && module.tools.items) {
78
- tools.push(...module.tools.items);
79
- }
80
- // Check for individual tool exports
81
- for (const [key, value] of Object.entries(module)) {
82
- if (key === 'default' || key === 'graph' || key === 'tools' || key === 'servers')
83
- continue;
84
- // Check if it looks like a tool (has execute function or is an IPC tool)
85
- if (value && typeof value === 'object') {
86
- const hasExecute = value.hasExecute === true;
87
- const hasInit = value.hasInit === true;
88
- const hasGetServerUrl = value.hasGetServerUrl === true;
89
- if (hasExecute || (hasInit && hasGetServerUrl)) {
90
- tools.push(value);
91
- toolMap.set(key, value);
92
- }
93
- }
94
- }
95
- // Get graph ID
96
- let graphId = 'unknown';
97
- if (module.graph && module.graph.graphId) {
98
- graphId = module.graph.graphId;
99
- }
100
- if (tools.length === 0) {
101
- spinner.warn('No MCP tools found in graph configuration');
102
- console.log(chalk.gray('\nTo use MCP tools, export them from your graph file:'));
103
- console.log(chalk.gray(' export const myTool = ipcTool({ ... });'));
104
- console.log(chalk.gray(' export const tools = [myTool, anotherTool];'));
105
- process.exit(0);
106
- }
107
- spinner.succeed(`Found ${tools.length} MCP tool(s) in graph`);
108
- // Load and clean registry
109
- const registry = cleanupStaleServers(loadRegistry());
110
- // Start each tool
111
- const startedServers = [];
112
- const portManager = PortManager.getInstance();
113
- for (const tool of tools) {
114
- const toolSpinner = ora(`Starting MCP server: ${tool.name || 'unnamed'}`).start();
115
- try {
116
- // Check if tool is already running
117
- const existingServer = registry.servers.find((s) => s.graphId === graphId && s.toolId === (tool.id || tool.name));
118
- if (existingServer) {
119
- // Check if process is still alive
120
- try {
121
- process.kill(existingServer.pid, 0);
122
- toolSpinner.warn(`Server ${tool.name} already running (PID: ${existingServer.pid})`);
123
- continue;
124
- }
125
- catch {
126
- // Process is dead, remove from registry
127
- registry.servers = registry.servers.filter((s) => s.pid !== existingServer.pid);
128
- portManager.releasePort(existingServer.port);
129
- }
130
- }
131
- // Determine deployment type
132
- const isLocal = tool.hasExecute === true ||
133
- tool.hasInit === true ||
134
- (!tool.serverUrl && !tool.deployment);
135
- const deployment = tool.deployment || (isLocal ? 'local' : 'remote');
136
- // For TypeScript files, we can't actually initialize or call methods
137
- // We just use the metadata we extracted
138
- // Get server details
139
- let port;
140
- let serverUrl;
141
- if (deployment === 'local') {
142
- // Allocate port for local server
143
- if (tool.port) {
144
- port = await portManager.allocatePort(tool.port);
145
- }
146
- else {
147
- port = await portManager.allocatePort();
148
- }
149
- serverUrl = `http://localhost:${port}/mcp`;
150
- }
151
- else {
152
- // Remote server
153
- serverUrl = tool.serverUrl;
154
- }
155
- const server = {
156
- pid: process.pid, // Will be updated if we spawn a subprocess
157
- graphId,
158
- toolId: tool.id || tool.name || 'unknown',
159
- name: tool.name || 'unnamed',
160
- port,
161
- serverUrl,
162
- deployment,
163
- transport: tool.transport || 'ipc',
164
- command: graphPath,
165
- startedAt: new Date().toISOString(),
166
- description: tool.description,
167
- };
168
- startedServers.push(server);
169
- registry.servers.push(server);
170
- const deploymentIcon = deployment === 'local' ? '🏠' : '☁️';
171
- toolSpinner.succeed(`${deploymentIcon} Started ${chalk.green(tool.name || 'unnamed')} ` +
172
- (deployment === 'local' ? `on port ${chalk.cyan(port)}` : `at ${chalk.cyan(serverUrl)}`));
173
- if (options.verbose) {
174
- console.log(chalk.gray(` ID: ${tool.id}`));
175
- console.log(chalk.gray(` Type: ${deployment} MCP server`));
176
- console.log(chalk.gray(` Description: ${tool.description || 'N/A'}`));
177
- }
178
- }
179
- catch (error) {
180
- toolSpinner.fail(`Failed to start ${tool.name || 'unnamed'}: ${error}`);
181
- }
182
- }
183
- // Save updated registry
184
- saveRegistry(registry);
185
- console.log(chalk.green(`\n✅ Started ${startedServers.length} MCP server(s)`));
186
- if (!options.detached) {
187
- console.log(chalk.gray('\nServers are running. Press Ctrl+C to stop all servers.\n'));
188
- // Handle graceful shutdown
189
- process.on('SIGINT', () => {
190
- console.log(chalk.yellow('\n\nShutting down MCP servers...'));
191
- // Stop all tools
192
- for (const tool of tools) {
193
- if (typeof tool.stop === 'function') {
194
- try {
195
- tool.stop();
196
- }
197
- catch (error) {
198
- console.error(chalk.red(`Error stopping tool: ${error}`));
199
- }
200
- }
201
- }
202
- // Remove from registry
203
- const updatedRegistry = loadRegistry();
204
- updatedRegistry.servers = updatedRegistry.servers.filter((s) => !startedServers.some((started) => started.pid === s.pid));
205
- saveRegistry(updatedRegistry);
206
- process.exit(0);
207
- });
208
- // Keep process alive
209
- process.stdin.resume();
210
- }
211
- }
212
- catch (error) {
213
- spinner.fail('Failed to start MCP servers');
214
- console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
215
- process.exit(1);
216
- }
217
- }
@@ -1 +0,0 @@
1
- export declare function mcpStatusCommand(): Promise<void>;
@@ -1,96 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join } from 'node:path';
4
- import chalk from 'chalk';
5
- import Table from 'cli-table3';
6
- const MCP_DIR = join(homedir(), '.inkeep', 'mcp');
7
- const REGISTRY_FILE = join(MCP_DIR, 'servers.json');
8
- function loadRegistry() {
9
- if (!existsSync(REGISTRY_FILE)) {
10
- return { servers: [] };
11
- }
12
- try {
13
- return JSON.parse(readFileSync(REGISTRY_FILE, 'utf-8'));
14
- }
15
- catch {
16
- return { servers: [] };
17
- }
18
- }
19
- function isProcessRunning(pid) {
20
- try {
21
- process.kill(pid, 0);
22
- return true;
23
- }
24
- catch {
25
- return false;
26
- }
27
- }
28
- function formatUptime(ms) {
29
- const seconds = Math.floor(ms / 1000);
30
- const minutes = Math.floor(seconds / 60);
31
- const hours = Math.floor(minutes / 60);
32
- const days = Math.floor(hours / 24);
33
- if (days > 0)
34
- return `${days}d ${hours % 24}h`;
35
- if (hours > 0)
36
- return `${hours}h ${minutes % 60}m`;
37
- if (minutes > 0)
38
- return `${minutes}m ${seconds % 60}s`;
39
- return `${seconds}s`;
40
- }
41
- export async function mcpStatusCommand() {
42
- const registry = loadRegistry();
43
- if (registry.servers.length === 0) {
44
- console.log(chalk.gray('No MCP servers registered'));
45
- console.log(chalk.gray('\nStart servers with: inkeep mcp start <graph-file>'));
46
- return;
47
- }
48
- // Create table
49
- const table = new Table({
50
- head: [
51
- chalk.cyan('PID'),
52
- chalk.cyan('Graph'),
53
- chalk.cyan('Tool'),
54
- chalk.cyan('Port'),
55
- chalk.cyan('Status'),
56
- chalk.cyan('Uptime'),
57
- ],
58
- style: {
59
- head: [],
60
- border: ['gray'],
61
- },
62
- });
63
- let activeCount = 0;
64
- let staleCount = 0;
65
- for (const server of registry.servers) {
66
- const isRunning = isProcessRunning(server.pid);
67
- const uptime = new Date().getTime() - new Date(server.startedAt).getTime();
68
- const uptimeStr = formatUptime(uptime);
69
- if (isRunning) {
70
- activeCount++;
71
- }
72
- else {
73
- staleCount++;
74
- }
75
- table.push([
76
- isRunning ? chalk.green(server.pid.toString()) : chalk.red(server.pid.toString()),
77
- server.graphId,
78
- server.toolId,
79
- server.port ? chalk.cyan(server.port.toString()) : chalk.gray('N/A'),
80
- isRunning ? chalk.green('● Running') : chalk.red('● Stopped'),
81
- isRunning ? uptimeStr : chalk.gray('N/A'),
82
- ]);
83
- }
84
- console.log(chalk.bold('\n📡 MCP Server Status\n'));
85
- console.log(table.toString());
86
- console.log();
87
- console.log(chalk.gray('Summary:'));
88
- console.log(chalk.green(` ● ${activeCount} active`), chalk.red(` ● ${staleCount} stopped`));
89
- if (staleCount > 0) {
90
- console.log(chalk.yellow('\n⚠️ Some servers have stopped. Run "inkeep mcp stop --all" to clean up.'));
91
- }
92
- console.log(chalk.gray('\nCommands:'));
93
- console.log(chalk.gray(' • Start servers: inkeep mcp start <graph-file>'));
94
- console.log(chalk.gray(' • Stop servers: inkeep mcp stop'));
95
- console.log(chalk.gray(' • Stop all: inkeep mcp stop --all'));
96
- }
@@ -1,5 +0,0 @@
1
- export interface McpStopOptions {
2
- all?: boolean;
3
- graph?: string;
4
- }
5
- export declare function mcpStopCommand(options: McpStopOptions): Promise<void>;
@@ -1,160 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join } from 'node:path';
4
- import chalk from 'chalk';
5
- import inquirer from 'inquirer';
6
- import ora from 'ora';
7
- const MCP_DIR = join(homedir(), '.inkeep', 'mcp');
8
- const REGISTRY_FILE = join(MCP_DIR, 'servers.json');
9
- function loadRegistry() {
10
- if (!existsSync(REGISTRY_FILE)) {
11
- return { servers: [] };
12
- }
13
- try {
14
- return JSON.parse(readFileSync(REGISTRY_FILE, 'utf-8'));
15
- }
16
- catch {
17
- return { servers: [] };
18
- }
19
- }
20
- function saveRegistry(registry) {
21
- writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2));
22
- }
23
- function stopServer(server) {
24
- try {
25
- process.kill(server.pid, 'SIGTERM');
26
- // Give it a moment to terminate gracefully
27
- setTimeout(() => {
28
- try {
29
- // Check if still running and force kill if needed
30
- process.kill(server.pid, 0);
31
- process.kill(server.pid, 'SIGKILL');
32
- }
33
- catch {
34
- // Process already terminated
35
- }
36
- }, 1000);
37
- return true;
38
- }
39
- catch (error) {
40
- // Process might already be dead
41
- return false;
42
- }
43
- }
44
- export async function mcpStopCommand(options) {
45
- const spinner = ora('Loading MCP server registry...').start();
46
- try {
47
- const registry = loadRegistry();
48
- if (registry.servers.length === 0) {
49
- spinner.info('No MCP servers are currently running');
50
- process.exit(0);
51
- }
52
- spinner.stop();
53
- let serversToStop = [];
54
- if (options.all) {
55
- // Stop all servers
56
- serversToStop = registry.servers;
57
- }
58
- else if (options.graph) {
59
- // Stop servers for specific graph
60
- serversToStop = registry.servers.filter((s) => s.graphId === options.graph);
61
- if (serversToStop.length === 0) {
62
- console.log(chalk.yellow(`No servers found for graph: ${options.graph}`));
63
- process.exit(0);
64
- }
65
- }
66
- else {
67
- // Interactive selection
68
- const groupedServers = new Map();
69
- // Group servers by graph
70
- for (const server of registry.servers) {
71
- const servers = groupedServers.get(server.graphId) || [];
72
- servers.push(server);
73
- groupedServers.set(server.graphId, servers);
74
- }
75
- // Create choices
76
- const choices = [
77
- { name: chalk.red('Stop all servers'), value: 'all' },
78
- new inquirer.Separator(),
79
- ];
80
- for (const [graphId, servers] of groupedServers) {
81
- choices.push({
82
- name: `${chalk.cyan(graphId)} (${servers.length} server${servers.length > 1 ? 's' : ''})`,
83
- value: graphId,
84
- });
85
- for (const server of servers) {
86
- const uptime = new Date().getTime() - new Date(server.startedAt).getTime();
87
- const uptimeStr = formatUptime(uptime);
88
- choices.push({
89
- name: chalk.gray(` └─ ${server.toolId} `) +
90
- (server.port ? chalk.gray(`(port ${server.port}) `) : '') +
91
- chalk.gray(`[${uptimeStr}]`),
92
- value: `tool:${server.pid}`,
93
- });
94
- }
95
- }
96
- const answer = await inquirer.prompt([
97
- {
98
- type: 'list',
99
- name: 'selection',
100
- message: 'Select servers to stop:',
101
- choices,
102
- pageSize: 15,
103
- },
104
- ]);
105
- if (answer.selection === 'all') {
106
- serversToStop = registry.servers;
107
- }
108
- else if (answer.selection.startsWith('tool:')) {
109
- const pid = parseInt(answer.selection.replace('tool:', ''));
110
- serversToStop = registry.servers.filter((s) => s.pid === pid);
111
- }
112
- else {
113
- serversToStop = registry.servers.filter((s) => s.graphId === answer.selection);
114
- }
115
- }
116
- if (serversToStop.length === 0) {
117
- console.log(chalk.yellow('No servers selected'));
118
- process.exit(0);
119
- }
120
- // Stop the servers
121
- const stopSpinner = ora(`Stopping ${serversToStop.length} server(s)...`).start();
122
- let stoppedCount = 0;
123
- for (const server of serversToStop) {
124
- const stopped = stopServer(server);
125
- if (stopped) {
126
- stoppedCount++;
127
- if (options.all || options.graph) {
128
- console.log(chalk.gray(` • Stopped ${server.toolId} (PID: ${server.pid})`));
129
- }
130
- }
131
- }
132
- // Update registry
133
- registry.servers = registry.servers.filter((s) => !serversToStop.some((toStop) => toStop.pid === s.pid));
134
- saveRegistry(registry);
135
- if (stoppedCount > 0) {
136
- stopSpinner.succeed(`Stopped ${stoppedCount} MCP server(s)`);
137
- }
138
- else {
139
- stopSpinner.warn('No servers were stopped (they may have already terminated)');
140
- }
141
- }
142
- catch (error) {
143
- spinner.fail('Failed to stop MCP servers');
144
- console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
145
- process.exit(1);
146
- }
147
- }
148
- function formatUptime(ms) {
149
- const seconds = Math.floor(ms / 1000);
150
- const minutes = Math.floor(seconds / 60);
151
- const hours = Math.floor(minutes / 60);
152
- const days = Math.floor(hours / 24);
153
- if (days > 0)
154
- return `${days}d ${hours % 24}h`;
155
- if (hours > 0)
156
- return `${hours}h ${minutes % 60}m`;
157
- if (minutes > 0)
158
- return `${minutes}m ${seconds % 60}s`;
159
- return `${seconds}s`;
160
- }