servherd 0.0.1 → 1.0.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/CONTRIBUTING.md +250 -0
- package/LICENSE +21 -0
- package/README.md +653 -29
- package/dist/cli/commands/config.d.ts +35 -0
- package/dist/cli/commands/config.js +336 -0
- package/dist/cli/commands/info.d.ts +37 -0
- package/dist/cli/commands/info.js +98 -0
- package/dist/cli/commands/list.d.ts +26 -0
- package/dist/cli/commands/list.js +86 -0
- package/dist/cli/commands/logs.d.ts +46 -0
- package/dist/cli/commands/logs.js +292 -0
- package/dist/cli/commands/mcp.d.ts +5 -0
- package/dist/cli/commands/mcp.js +17 -0
- package/dist/cli/commands/refresh.d.ts +20 -0
- package/dist/cli/commands/refresh.js +139 -0
- package/dist/cli/commands/remove.d.ts +20 -0
- package/dist/cli/commands/remove.js +144 -0
- package/dist/cli/commands/restart.d.ts +25 -0
- package/dist/cli/commands/restart.js +177 -0
- package/dist/cli/commands/start.d.ts +37 -0
- package/dist/cli/commands/start.js +293 -0
- package/dist/cli/commands/stop.d.ts +20 -0
- package/dist/cli/commands/stop.js +108 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +160 -0
- package/dist/cli/output/formatters.d.ts +117 -0
- package/dist/cli/output/formatters.js +454 -0
- package/dist/cli/output/json-formatter.d.ts +22 -0
- package/dist/cli/output/json-formatter.js +40 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +25 -0
- package/dist/mcp/index.d.ts +14 -0
- package/dist/mcp/index.js +352 -0
- package/dist/mcp/resources/servers.d.ts +14 -0
- package/dist/mcp/resources/servers.js +128 -0
- package/dist/mcp/tools/config.d.ts +33 -0
- package/dist/mcp/tools/config.js +88 -0
- package/dist/mcp/tools/info.d.ts +36 -0
- package/dist/mcp/tools/info.js +65 -0
- package/dist/mcp/tools/list.d.ts +36 -0
- package/dist/mcp/tools/list.js +49 -0
- package/dist/mcp/tools/logs.d.ts +44 -0
- package/dist/mcp/tools/logs.js +55 -0
- package/dist/mcp/tools/refresh.d.ts +33 -0
- package/dist/mcp/tools/refresh.js +54 -0
- package/dist/mcp/tools/remove.d.ts +23 -0
- package/dist/mcp/tools/remove.js +43 -0
- package/dist/mcp/tools/restart.d.ts +23 -0
- package/dist/mcp/tools/restart.js +42 -0
- package/dist/mcp/tools/start.d.ts +38 -0
- package/dist/mcp/tools/start.js +73 -0
- package/dist/mcp/tools/stop.d.ts +23 -0
- package/dist/mcp/tools/stop.js +40 -0
- package/dist/services/config.service.d.ts +80 -0
- package/dist/services/config.service.js +227 -0
- package/dist/services/port.service.d.ts +82 -0
- package/dist/services/port.service.js +151 -0
- package/dist/services/process.service.d.ts +61 -0
- package/dist/services/process.service.js +220 -0
- package/dist/services/registry.service.d.ts +50 -0
- package/dist/services/registry.service.js +157 -0
- package/dist/types/config.d.ts +107 -0
- package/dist/types/config.js +44 -0
- package/dist/types/errors.d.ts +102 -0
- package/dist/types/errors.js +197 -0
- package/dist/types/pm2.d.ts +50 -0
- package/dist/types/pm2.js +4 -0
- package/dist/types/registry.d.ts +230 -0
- package/dist/types/registry.js +33 -0
- package/dist/utils/ci-detector.d.ts +31 -0
- package/dist/utils/ci-detector.js +68 -0
- package/dist/utils/config-drift.d.ts +71 -0
- package/dist/utils/config-drift.js +128 -0
- package/dist/utils/error-handler.d.ts +21 -0
- package/dist/utils/error-handler.js +38 -0
- package/dist/utils/log-follower.d.ts +10 -0
- package/dist/utils/log-follower.js +98 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.js +24 -0
- package/dist/utils/names.d.ts +7 -0
- package/dist/utils/names.js +20 -0
- package/dist/utils/template.d.ts +88 -0
- package/dist/utils/template.js +180 -0
- package/dist/utils/time-parser.d.ts +19 -0
- package/dist/utils/time-parser.js +54 -0
- package/docs/ci-cd.md +408 -0
- package/docs/configuration.md +325 -0
- package/docs/mcp-integration.md +411 -0
- package/examples/basic-usage/README.md +187 -0
- package/examples/ci-github-actions/workflow.yml +195 -0
- package/examples/mcp-claude-code/README.md +213 -0
- package/examples/multi-server/README.md +270 -0
- package/examples/storybook/README.md +187 -0
- package/examples/vite-project/README.md +251 -0
- package/package.json +123 -6
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import Table from "cli-table3";
|
|
3
|
+
import boxen from "boxen";
|
|
4
|
+
/**
|
|
5
|
+
* Format server status with color
|
|
6
|
+
*/
|
|
7
|
+
export function formatStatus(status) {
|
|
8
|
+
switch (status) {
|
|
9
|
+
case "online":
|
|
10
|
+
return chalk.green("● online");
|
|
11
|
+
case "stopped":
|
|
12
|
+
return chalk.gray("○ stopped");
|
|
13
|
+
case "errored":
|
|
14
|
+
return chalk.red("✖ errored");
|
|
15
|
+
default:
|
|
16
|
+
return chalk.yellow("? unknown");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Format URL for display
|
|
21
|
+
*/
|
|
22
|
+
export function formatUrl(protocol, hostname, port) {
|
|
23
|
+
return chalk.cyan(`${protocol}://${hostname}:${port}`);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Format server name for display
|
|
27
|
+
*/
|
|
28
|
+
export function formatName(name) {
|
|
29
|
+
return chalk.bold(name);
|
|
30
|
+
}
|
|
31
|
+
export function formatServerListTable(servers) {
|
|
32
|
+
if (servers.length === 0) {
|
|
33
|
+
return chalk.yellow("No servers registered");
|
|
34
|
+
}
|
|
35
|
+
const table = new Table({
|
|
36
|
+
head: [
|
|
37
|
+
chalk.bold("Name"),
|
|
38
|
+
chalk.bold("Status"),
|
|
39
|
+
chalk.bold("Port"),
|
|
40
|
+
chalk.bold("Command"),
|
|
41
|
+
chalk.bold("Working Directory"),
|
|
42
|
+
],
|
|
43
|
+
style: {
|
|
44
|
+
head: [],
|
|
45
|
+
border: [],
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
for (const { server, status, hasDrift } of servers) {
|
|
49
|
+
// Add drift indicator to name if config has drifted
|
|
50
|
+
const nameDisplay = hasDrift
|
|
51
|
+
? formatName(server.name) + chalk.yellow(" ⚡")
|
|
52
|
+
: formatName(server.name);
|
|
53
|
+
table.push([
|
|
54
|
+
nameDisplay,
|
|
55
|
+
formatStatus(status),
|
|
56
|
+
String(server.port),
|
|
57
|
+
truncateString(server.command, 30),
|
|
58
|
+
truncatePath(server.cwd, 30),
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
// Add legend if any server has drift
|
|
62
|
+
const anyDrift = servers.some(s => s.hasDrift);
|
|
63
|
+
let result = table.toString();
|
|
64
|
+
if (anyDrift) {
|
|
65
|
+
result += "\n\n" + chalk.yellow("⚡ = Config has changed since server started. Run `servherd refresh` to update.");
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
export function formatStartResult(result) {
|
|
70
|
+
const { action, server, status } = result;
|
|
71
|
+
const url = `${server.protocol}://${server.hostname}:${server.port}`;
|
|
72
|
+
const lines = [];
|
|
73
|
+
switch (action) {
|
|
74
|
+
case "started":
|
|
75
|
+
lines.push(chalk.green(`✓ Server "${server.name}" started`));
|
|
76
|
+
break;
|
|
77
|
+
case "existing":
|
|
78
|
+
lines.push(chalk.blue(`ℹ Server "${server.name}" already exists`));
|
|
79
|
+
break;
|
|
80
|
+
case "restarted":
|
|
81
|
+
lines.push(chalk.green(`✓ Server "${server.name}" restarted`));
|
|
82
|
+
break;
|
|
83
|
+
case "renamed":
|
|
84
|
+
lines.push(chalk.green(`✓ Server renamed from "${result.previousName}" to "${server.name}"`));
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
lines.push(` ${chalk.bold("Name:")} ${server.name}`);
|
|
88
|
+
lines.push(` ${chalk.bold("Port:")} ${server.port}`);
|
|
89
|
+
lines.push(` ${chalk.bold("URL:")} ${chalk.cyan(url)}`);
|
|
90
|
+
lines.push(` ${chalk.bold("Status:")} ${formatStatus(status)}`);
|
|
91
|
+
lines.push(` ${chalk.bold("CWD:")} ${server.cwd}`);
|
|
92
|
+
return lines.join("\n");
|
|
93
|
+
}
|
|
94
|
+
export function formatStopResult(results) {
|
|
95
|
+
if (results.length === 0) {
|
|
96
|
+
return chalk.yellow("No servers to stop");
|
|
97
|
+
}
|
|
98
|
+
const lines = [];
|
|
99
|
+
for (const result of results) {
|
|
100
|
+
if (result.success) {
|
|
101
|
+
lines.push(chalk.green(`✓ Server "${result.name}" stopped`));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
lines.push(chalk.red(`✖ Failed to stop "${result.name}": ${result.message}`));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return lines.join("\n");
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Format error message
|
|
111
|
+
*/
|
|
112
|
+
export function formatError(message) {
|
|
113
|
+
return chalk.red(`✖ Error: ${message}`);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Format success message
|
|
117
|
+
*/
|
|
118
|
+
export function formatSuccess(message) {
|
|
119
|
+
return chalk.green(`✓ ${message}`);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Format info message
|
|
123
|
+
*/
|
|
124
|
+
export function formatInfo(message) {
|
|
125
|
+
return chalk.blue(`ℹ ${message}`);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Format warning message
|
|
129
|
+
*/
|
|
130
|
+
export function formatWarning(message) {
|
|
131
|
+
return chalk.yellow(`⚠ ${message}`);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Truncate a string for display
|
|
135
|
+
*/
|
|
136
|
+
function truncateString(str, maxLength) {
|
|
137
|
+
if (str.length <= maxLength) {
|
|
138
|
+
return str;
|
|
139
|
+
}
|
|
140
|
+
return str.slice(0, maxLength - 3) + "...";
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Truncate a path for display
|
|
144
|
+
*/
|
|
145
|
+
function truncatePath(path, maxLength) {
|
|
146
|
+
if (path.length <= maxLength) {
|
|
147
|
+
return path;
|
|
148
|
+
}
|
|
149
|
+
const parts = path.split("/");
|
|
150
|
+
let result = "";
|
|
151
|
+
// Start from the end and work backward
|
|
152
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
153
|
+
const candidate = parts.slice(i).join("/");
|
|
154
|
+
if (candidate.length <= maxLength - 3) {
|
|
155
|
+
result = "..." + (i > 0 ? "/" : "") + candidate;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!result) {
|
|
160
|
+
// If even the last part is too long, just truncate
|
|
161
|
+
result = "..." + path.slice(-(maxLength - 3));
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Format bytes to human readable string
|
|
167
|
+
*/
|
|
168
|
+
function formatBytes(bytes) {
|
|
169
|
+
if (bytes < 1024) {
|
|
170
|
+
return `${bytes} B`;
|
|
171
|
+
}
|
|
172
|
+
else if (bytes < 1024 * 1024) {
|
|
173
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
174
|
+
}
|
|
175
|
+
else if (bytes < 1024 * 1024 * 1024) {
|
|
176
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Format uptime to human readable string
|
|
184
|
+
*/
|
|
185
|
+
function formatUptime(uptimeMs) {
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
const durationMs = now - uptimeMs;
|
|
188
|
+
const seconds = Math.floor(durationMs / 1000);
|
|
189
|
+
const minutes = Math.floor(seconds / 60);
|
|
190
|
+
const hours = Math.floor(minutes / 60);
|
|
191
|
+
const days = Math.floor(hours / 24);
|
|
192
|
+
if (days > 0) {
|
|
193
|
+
return `${days}d ${hours % 24}h`;
|
|
194
|
+
}
|
|
195
|
+
else if (hours > 0) {
|
|
196
|
+
return `${hours}h ${minutes % 60}m`;
|
|
197
|
+
}
|
|
198
|
+
else if (minutes > 0) {
|
|
199
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
return `${seconds}s`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Format server info as a boxed display
|
|
207
|
+
*/
|
|
208
|
+
export function formatServerInfo(info) {
|
|
209
|
+
const lines = [];
|
|
210
|
+
// Header
|
|
211
|
+
lines.push(chalk.bold.cyan(`Server: ${info.name}`));
|
|
212
|
+
lines.push("");
|
|
213
|
+
// Status and basic info
|
|
214
|
+
lines.push(`${chalk.bold("Status:")} ${formatStatus(info.status)}`);
|
|
215
|
+
lines.push(`${chalk.bold("URL:")} ${chalk.cyan(info.url)}`);
|
|
216
|
+
lines.push(`${chalk.bold("Port:")} ${info.port}`);
|
|
217
|
+
lines.push(`${chalk.bold("Hostname:")} ${info.hostname}`);
|
|
218
|
+
lines.push(`${chalk.bold("Protocol:")} ${info.protocol}`);
|
|
219
|
+
lines.push("");
|
|
220
|
+
// Process info
|
|
221
|
+
if (info.pid) {
|
|
222
|
+
lines.push(`${chalk.bold("PID:")} ${info.pid}`);
|
|
223
|
+
}
|
|
224
|
+
if (info.uptime) {
|
|
225
|
+
lines.push(`${chalk.bold("Uptime:")} ${formatUptime(info.uptime)}`);
|
|
226
|
+
}
|
|
227
|
+
if (info.restarts !== undefined) {
|
|
228
|
+
lines.push(`${chalk.bold("Restarts:")} ${info.restarts}`);
|
|
229
|
+
}
|
|
230
|
+
if (info.memory !== undefined) {
|
|
231
|
+
lines.push(`${chalk.bold("Memory:")} ${formatBytes(info.memory)}`);
|
|
232
|
+
}
|
|
233
|
+
if (info.cpu !== undefined) {
|
|
234
|
+
lines.push(`${chalk.bold("CPU:")} ${info.cpu.toFixed(1)}%`);
|
|
235
|
+
}
|
|
236
|
+
lines.push("");
|
|
237
|
+
// Command info
|
|
238
|
+
lines.push(`${chalk.bold("Command:")} ${info.command}`);
|
|
239
|
+
lines.push(`${chalk.bold("Resolved:")} ${info.resolvedCommand}`);
|
|
240
|
+
lines.push(`${chalk.bold("CWD:")} ${info.cwd}`);
|
|
241
|
+
lines.push(`${chalk.bold("PM2 Name:")} ${info.pm2Name}`);
|
|
242
|
+
lines.push("");
|
|
243
|
+
// Optional fields
|
|
244
|
+
if (info.description) {
|
|
245
|
+
lines.push(`${chalk.bold("Description:")} ${info.description}`);
|
|
246
|
+
}
|
|
247
|
+
if (info.tags && info.tags.length > 0) {
|
|
248
|
+
lines.push(`${chalk.bold("Tags:")} ${info.tags.join(", ")}`);
|
|
249
|
+
}
|
|
250
|
+
// Log paths
|
|
251
|
+
if (info.outLogPath) {
|
|
252
|
+
lines.push(`${chalk.bold("Out Log:")} ${info.outLogPath}`);
|
|
253
|
+
}
|
|
254
|
+
if (info.errLogPath) {
|
|
255
|
+
lines.push(`${chalk.bold("Err Log:")} ${info.errLogPath}`);
|
|
256
|
+
}
|
|
257
|
+
// Environment variables
|
|
258
|
+
if (info.env && Object.keys(info.env).length > 0) {
|
|
259
|
+
lines.push("");
|
|
260
|
+
lines.push(chalk.bold("Environment:"));
|
|
261
|
+
for (const [key, value] of Object.entries(info.env)) {
|
|
262
|
+
lines.push(` ${key}=${value}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Created at
|
|
266
|
+
lines.push("");
|
|
267
|
+
lines.push(`${chalk.bold("Created:")} ${new Date(info.createdAt).toLocaleString()}`);
|
|
268
|
+
return boxen(lines.join("\n"), {
|
|
269
|
+
padding: 1,
|
|
270
|
+
margin: 0,
|
|
271
|
+
borderStyle: "round",
|
|
272
|
+
borderColor: "cyan",
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Format logs output
|
|
277
|
+
*/
|
|
278
|
+
export function formatLogs(result) {
|
|
279
|
+
const lines = [];
|
|
280
|
+
lines.push(chalk.bold.cyan(`Logs for: ${result.name}`));
|
|
281
|
+
lines.push(`${chalk.bold("Status:")} ${formatStatus(result.status)}`);
|
|
282
|
+
if (result.outLogPath) {
|
|
283
|
+
lines.push(`${chalk.bold("Log file:")} ${result.outLogPath}`);
|
|
284
|
+
}
|
|
285
|
+
lines.push(`${chalk.bold("Lines:")} ${result.lines}`);
|
|
286
|
+
lines.push("");
|
|
287
|
+
lines.push(chalk.gray("─".repeat(60)));
|
|
288
|
+
lines.push("");
|
|
289
|
+
if (result.logs) {
|
|
290
|
+
lines.push(result.logs);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
lines.push(chalk.gray("(no logs available)"));
|
|
294
|
+
}
|
|
295
|
+
return lines.join("\n");
|
|
296
|
+
}
|
|
297
|
+
export function formatRestartResult(results) {
|
|
298
|
+
if (results.length === 0) {
|
|
299
|
+
return chalk.yellow("No servers to restart");
|
|
300
|
+
}
|
|
301
|
+
const lines = [];
|
|
302
|
+
for (const result of results) {
|
|
303
|
+
if (result.success) {
|
|
304
|
+
if (result.configRefreshed) {
|
|
305
|
+
lines.push(chalk.green(`✓ Server "${result.name}" restarted with updated config`));
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
lines.push(chalk.green(`✓ Server "${result.name}" restarted`));
|
|
309
|
+
}
|
|
310
|
+
if (result.status) {
|
|
311
|
+
lines.push(` Status: ${formatStatus(result.status)}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
lines.push(chalk.red(`✖ Failed to restart "${result.name}": ${result.message}`));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return lines.join("\n");
|
|
319
|
+
}
|
|
320
|
+
function formatRefreshResult(results, dryRun) {
|
|
321
|
+
// Check if this is a "no drift" result
|
|
322
|
+
if (results.length === 1 && results[0].skipped && results[0].name === "") {
|
|
323
|
+
return chalk.blue(`ℹ ${results[0].message}`);
|
|
324
|
+
}
|
|
325
|
+
const lines = [];
|
|
326
|
+
if (dryRun) {
|
|
327
|
+
lines.push(chalk.yellow("Dry run mode - no changes made"));
|
|
328
|
+
lines.push("");
|
|
329
|
+
}
|
|
330
|
+
for (const result of results) {
|
|
331
|
+
if (result.skipped && dryRun) {
|
|
332
|
+
lines.push(chalk.yellow(`⚠ Server "${result.name}" would be refreshed`));
|
|
333
|
+
if (result.driftDetails) {
|
|
334
|
+
lines.push(chalk.gray(` ${result.driftDetails.split("\n").join("\n ")}`));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
else if (result.success) {
|
|
338
|
+
lines.push(chalk.green(`✓ Server "${result.name}" refreshed with updated config`));
|
|
339
|
+
if (result.status) {
|
|
340
|
+
lines.push(` Status: ${formatStatus(result.status)}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
lines.push(chalk.red(`✖ Failed to refresh "${result.name}": ${result.message}`));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return lines.join("\n");
|
|
348
|
+
}
|
|
349
|
+
export function formatRemoveResult(results) {
|
|
350
|
+
if (results.length === 0) {
|
|
351
|
+
return chalk.yellow("No servers to remove");
|
|
352
|
+
}
|
|
353
|
+
const lines = [];
|
|
354
|
+
for (const result of results) {
|
|
355
|
+
if (result.success) {
|
|
356
|
+
lines.push(chalk.green(`✓ Server "${result.name}" removed`));
|
|
357
|
+
}
|
|
358
|
+
else if (result.cancelled) {
|
|
359
|
+
lines.push(chalk.yellow(`⚠ Removal of "${result.name}" cancelled`));
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
lines.push(chalk.red(`✖ Failed to remove "${result.name}": ${result.message}`));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return lines.join("\n");
|
|
366
|
+
}
|
|
367
|
+
export function formatConfigResult(result) {
|
|
368
|
+
const lines = [];
|
|
369
|
+
// Handle error
|
|
370
|
+
if (result.error) {
|
|
371
|
+
return formatError(result.error);
|
|
372
|
+
}
|
|
373
|
+
// Handle --refresh / --refresh-all
|
|
374
|
+
if (result.refreshResults) {
|
|
375
|
+
return formatRefreshResult(result.refreshResults, result.dryRun);
|
|
376
|
+
}
|
|
377
|
+
// Handle --get
|
|
378
|
+
if (result.key !== undefined && result.value !== undefined && result.updated === undefined && result.reset === undefined) {
|
|
379
|
+
return `${chalk.bold(result.key)}: ${formatValue(result.value)}`;
|
|
380
|
+
}
|
|
381
|
+
// Handle --set
|
|
382
|
+
if (result.updated !== undefined) {
|
|
383
|
+
if (result.updated) {
|
|
384
|
+
let message = formatSuccess(`Configuration "${result.key}" set to ${formatValue(result.value)}`);
|
|
385
|
+
if (result.refreshMessage) {
|
|
386
|
+
message += "\n" + chalk.blue(`ℹ ${result.refreshMessage}`);
|
|
387
|
+
}
|
|
388
|
+
return message;
|
|
389
|
+
}
|
|
390
|
+
return formatError(result.error || "Failed to update configuration");
|
|
391
|
+
}
|
|
392
|
+
// Handle --reset
|
|
393
|
+
if (result.reset !== undefined) {
|
|
394
|
+
if (result.reset) {
|
|
395
|
+
return formatSuccess("Configuration reset to defaults");
|
|
396
|
+
}
|
|
397
|
+
if (result.cancelled) {
|
|
398
|
+
return formatWarning("Reset cancelled");
|
|
399
|
+
}
|
|
400
|
+
return formatError("Failed to reset configuration");
|
|
401
|
+
}
|
|
402
|
+
// Handle --show (default)
|
|
403
|
+
if (result.config) {
|
|
404
|
+
lines.push(chalk.bold.cyan("Current Configuration"));
|
|
405
|
+
lines.push("");
|
|
406
|
+
if (result.configPath) {
|
|
407
|
+
lines.push(`${chalk.bold("Loaded from:")} ${result.configPath}`);
|
|
408
|
+
}
|
|
409
|
+
else if (result.globalConfigPath) {
|
|
410
|
+
lines.push(`${chalk.bold("Global config:")} ${result.globalConfigPath}`);
|
|
411
|
+
}
|
|
412
|
+
lines.push("");
|
|
413
|
+
const table = new Table({
|
|
414
|
+
head: [chalk.bold("Setting"), chalk.bold("Value")],
|
|
415
|
+
style: { head: [], border: [] },
|
|
416
|
+
});
|
|
417
|
+
// Flatten config for display
|
|
418
|
+
const flatConfig = flattenConfig(result.config);
|
|
419
|
+
for (const [key, value] of Object.entries(flatConfig)) {
|
|
420
|
+
table.push([key, formatValue(value)]);
|
|
421
|
+
}
|
|
422
|
+
lines.push(table.toString());
|
|
423
|
+
return lines.join("\n");
|
|
424
|
+
}
|
|
425
|
+
return "";
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Format a value for display
|
|
429
|
+
*/
|
|
430
|
+
function formatValue(value) {
|
|
431
|
+
if (value === null || value === undefined) {
|
|
432
|
+
return chalk.gray("(not set)");
|
|
433
|
+
}
|
|
434
|
+
if (typeof value === "object") {
|
|
435
|
+
return JSON.stringify(value);
|
|
436
|
+
}
|
|
437
|
+
return String(value);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Flatten a nested config object for display
|
|
441
|
+
*/
|
|
442
|
+
function flattenConfig(obj, prefix = "") {
|
|
443
|
+
const result = {};
|
|
444
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
445
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
446
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
447
|
+
Object.assign(result, flattenConfig(value, fullKey));
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
result[fullKey] = value;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return result;
|
|
454
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON output formatting utilities for CLI commands.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Standard JSON output wrapper for successful operations.
|
|
6
|
+
*/
|
|
7
|
+
export interface JsonOutput<T> {
|
|
8
|
+
success: boolean;
|
|
9
|
+
data: T;
|
|
10
|
+
error?: {
|
|
11
|
+
code: string;
|
|
12
|
+
message: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Format data as a successful JSON response.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatAsJson<T>(data: T): string;
|
|
19
|
+
/**
|
|
20
|
+
* Format an error as a JSON response.
|
|
21
|
+
*/
|
|
22
|
+
export declare function formatErrorAsJson(error: unknown): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON output formatting utilities for CLI commands.
|
|
3
|
+
*/
|
|
4
|
+
import { isServherdError } from "../../types/errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Format data as a successful JSON response.
|
|
7
|
+
*/
|
|
8
|
+
export function formatAsJson(data) {
|
|
9
|
+
const output = {
|
|
10
|
+
success: true,
|
|
11
|
+
data,
|
|
12
|
+
};
|
|
13
|
+
return JSON.stringify(output, null, 2);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Format an error as a JSON response.
|
|
17
|
+
*/
|
|
18
|
+
export function formatErrorAsJson(error) {
|
|
19
|
+
if (isServherdError(error)) {
|
|
20
|
+
const output = {
|
|
21
|
+
success: false,
|
|
22
|
+
data: null,
|
|
23
|
+
error: {
|
|
24
|
+
code: error.getCodeName(),
|
|
25
|
+
message: error.message,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
return JSON.stringify(output, null, 2);
|
|
29
|
+
}
|
|
30
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
31
|
+
const output = {
|
|
32
|
+
success: false,
|
|
33
|
+
data: null,
|
|
34
|
+
error: {
|
|
35
|
+
code: "UNKNOWN_ERROR",
|
|
36
|
+
message,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
return JSON.stringify(output, null, 2);
|
|
40
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* servherd - CLI tool and MCP server for managing development servers across projects
|
|
4
|
+
*/
|
|
5
|
+
export * from "./types/config.js";
|
|
6
|
+
export * from "./types/registry.js";
|
|
7
|
+
export * from "./types/pm2.js";
|
|
8
|
+
export * from "./utils/ci-detector.js";
|
|
9
|
+
export * from "./utils/logger.js";
|
|
10
|
+
export * from "./utils/template.js";
|
|
11
|
+
export { ConfigService } from "./services/config.service.js";
|
|
12
|
+
export { RegistryService } from "./services/registry.service.js";
|
|
13
|
+
export { PortService } from "./services/port.service.js";
|
|
14
|
+
export { ProcessService } from "./services/process.service.js";
|
|
15
|
+
export { runCLI, createProgram } from "./cli/index.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* servherd - CLI tool and MCP server for managing development servers across projects
|
|
4
|
+
*/
|
|
5
|
+
import { runCLI } from "./cli/index.js";
|
|
6
|
+
// Export types
|
|
7
|
+
export * from "./types/config.js";
|
|
8
|
+
export * from "./types/registry.js";
|
|
9
|
+
export * from "./types/pm2.js";
|
|
10
|
+
// Export utilities
|
|
11
|
+
export * from "./utils/ci-detector.js";
|
|
12
|
+
export * from "./utils/logger.js";
|
|
13
|
+
export * from "./utils/template.js";
|
|
14
|
+
// Export services
|
|
15
|
+
export { ConfigService } from "./services/config.service.js";
|
|
16
|
+
export { RegistryService } from "./services/registry.service.js";
|
|
17
|
+
export { PortService } from "./services/port.service.js";
|
|
18
|
+
export { ProcessService } from "./services/process.service.js";
|
|
19
|
+
// Export CLI
|
|
20
|
+
export { runCLI, createProgram } from "./cli/index.js";
|
|
21
|
+
// Run CLI when executed directly
|
|
22
|
+
runCLI().catch((error) => {
|
|
23
|
+
console.error("Fatal error:", error);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export interface MCPServerOptions {
|
|
3
|
+
name?: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Create and configure an MCP server for servherd
|
|
8
|
+
*/
|
|
9
|
+
export declare function createMCPServer(options?: MCPServerOptions): McpServer;
|
|
10
|
+
/**
|
|
11
|
+
* Start the MCP server in stdio mode
|
|
12
|
+
*/
|
|
13
|
+
export declare function startStdioServer(): Promise<void>;
|
|
14
|
+
export { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|