• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

apowers313 / servherd / 20961701317

13 Jan 2026 03:07PM UTC coverage: 82.727% (+1.2%) from 81.563%
20961701317

push

github

apowers313
test: improved test coverage to 81.56%

901 of 1027 branches covered (87.73%)

Branch coverage included in aggregate %.

3601 of 4415 relevant lines covered (81.56%)

13.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.43
/src/cli/index.ts
1
import { Command } from "commander";
1✔
2
import { startAction } from "./commands/start.js";
3
import { stopAction } from "./commands/stop.js";
4
import { listAction } from "./commands/list.js";
5
import { infoAction } from "./commands/info.js";
6
import { logsAction } from "./commands/logs.js";
7
import { restartAction } from "./commands/restart.js";
8
import { removeAction } from "./commands/remove.js";
9
import { configAction } from "./commands/config.js";
10
import { mcpAction } from "./commands/mcp.js";
11
import { getVersion } from "../utils/version.js";
12

13
/**
14
 * Create the CLI program
15
 */
16
export function createProgram(): Command {
1✔
17
  const program = new Command();
9✔
18

19
  program
9✔
20
    .name("servherd")
9✔
21
    .description("CLI tool and MCP server for managing development servers across projects")
9✔
22
    .version(getVersion())
9✔
23
    .option("--json", "Output results as JSON")
9✔
24
    .option("--ci", "Force CI mode behavior (sequential port allocation, no interactive prompts)")
9✔
25
    .option("--no-ci", "Force non-CI mode behavior (overrides CI environment detection)");
9✔
26

27
  // Start command
28
  program
9✔
29
    .command("start")
9✔
30
    .description("Start a development server")
9✔
31
    .argument("[command...]", "Command to run (use -- to separate from options)")
9✔
32
    .option("-n, --name <name>", "Name for the server")
9✔
33
    .option("-p, --port <port>", "Port for the server (overrides deterministic assignment)", parseInt)
9✔
34
    .option("--protocol <protocol>", "Protocol to use (http or https)", (value) => {
9✔
35
      if (value !== "http" && value !== "https") {
×
36
        throw new Error("Protocol must be 'http' or 'https'");
×
37
      }
×
38
      return value as "http" | "https";
×
39
    })
9✔
40
    .option("-t, --tag <tag...>", "Tags for the server")
9✔
41
    .option("-d, --description <description>", "Description of the server")
9✔
42
    .option("-e, --env <KEY=VALUE...>", "Environment variables (supports {{port}}, {{hostname}}, {{url}}, {{https-cert}}, {{https-key}} templates)")
9✔
43
    .action(async (commandArgs: string[], options, cmd) => {
9✔
44
      const globalOpts = cmd.parent?.opts() ?? {};
×
45
      await startAction(commandArgs, { ...options, json: globalOpts.json });
×
46
    });
9✔
47

48
  // Stop command
49
  program
9✔
50
    .command("stop")
9✔
51
    .description("Stop a development server")
9✔
52
    .argument("[name]", "Name of the server to stop")
9✔
53
    .option("-a, --all", "Stop all servers")
9✔
54
    .option("-t, --tag <tag>", "Stop servers with this tag")
9✔
55
    .option("-f, --force", "Force stop using SIGKILL")
9✔
56
    .action(async (name: string | undefined, options, cmd) => {
9✔
57
      const globalOpts = cmd.parent?.opts() ?? {};
×
58
      await stopAction(name, { ...options, json: globalOpts.json });
×
59
    });
9✔
60

61
  // List command
62
  program
9✔
63
    .command("list")
9✔
64
    .alias("ls")
9✔
65
    .description("List all managed servers")
9✔
66
    .option("-r, --running", "Only show running servers")
9✔
67
    .option("-s, --stopped", "Only show stopped servers")
9✔
68
    .option("-t, --tag <tag>", "Filter by tag")
9✔
69
    .option("-c, --cwd <path>", "Filter by working directory")
9✔
70
    .option("--cmd <pattern>", "Filter by command pattern (glob syntax, e.g., '*storybook*')")
9✔
71
    .action(async (options, cmd) => {
9✔
72
      const globalOpts = cmd.parent?.opts() ?? {};
×
73
      await listAction({ ...options, json: globalOpts.json });
×
74
    });
9✔
75

76
  // Info command
77
  program
9✔
78
    .command("info")
9✔
79
    .description("Show detailed information about a server")
9✔
80
    .argument("<name>", "Name of the server")
9✔
81
    .action(async (name: string, options, cmd) => {
9✔
82
      const globalOpts = cmd.parent?.opts() ?? {};
×
83
      await infoAction(name, { json: globalOpts.json });
×
84
    });
9✔
85

86
  // Logs command
87
  program
9✔
88
    .command("logs")
9✔
89
    .description("View server logs")
9✔
90
    .argument("[name]", "Name of the server")
9✔
91
    .option("-n, --lines <number>", "Number of lines to show (from end)", "50")
9✔
92
    .option("-e, --error", "Show error logs instead of output logs")
9✔
93
    .option("-f, --follow", "Follow logs in real-time")
9✔
94
    .option("--since <time>", "Show logs since time (e.g., 1h, 30m, 2024-01-15)")
9✔
95
    .option("--head <number>", "Show first N lines (instead of last)")
9✔
96
    .option("--flush", "Clear logs instead of displaying")
9✔
97
    .option("-a, --all", "Apply to all servers (with --flush)")
9✔
98
    .action(async (name: string | undefined, options, cmd) => {
9✔
99
      const globalOpts = cmd.parent?.opts() ?? {};
×
100
      await logsAction(name, {
×
101
        lines: options.lines ? parseInt(options.lines, 10) : undefined,
×
102
        error: options.error,
×
103
        follow: options.follow,
×
104
        since: options.since,
×
105
        head: options.head ? parseInt(options.head, 10) : undefined,
×
106
        flush: options.flush,
×
107
        all: options.all,
×
108
        json: globalOpts.json,
×
109
      });
×
110
    });
9✔
111

112
  // Restart command
113
  program
9✔
114
    .command("restart")
9✔
115
    .description("Restart a development server")
9✔
116
    .argument("[name]", "Name of the server to restart")
9✔
117
    .option("-a, --all", "Restart all servers")
9✔
118
    .option("-t, --tag <tag>", "Restart servers with this tag")
9✔
119
    .action(async (name: string | undefined, options, cmd) => {
9✔
120
      const globalOpts = cmd.parent?.opts() ?? {};
×
121
      await restartAction(name, { ...options, json: globalOpts.json });
×
122
    });
9✔
123

124
  // Remove command
125
  program
9✔
126
    .command("remove")
9✔
127
    .alias("rm")
9✔
128
    .description("Remove a server from the registry (stops it first)")
9✔
129
    .argument("[name]", "Name of the server to remove")
9✔
130
    .option("-a, --all", "Remove all servers")
9✔
131
    .option("-t, --tag <tag>", "Remove servers with this tag")
9✔
132
    .option("-f, --force", "Skip confirmation prompt")
9✔
133
    .action(async (name: string | undefined, options, cmd) => {
9✔
134
      const globalOpts = cmd.parent?.opts() ?? {};
×
135
      await removeAction(name, { ...options, json: globalOpts.json });
×
136
    });
9✔
137

138
  // Config command
139
  program
9✔
140
    .command("config")
9✔
141
    .description("View and modify configuration")
9✔
142
    .option("-s, --show", "Show current configuration (default)")
9✔
143
    .option("-g, --get <key>", "Get a specific configuration value")
9✔
144
    .option("--set <key>", "Set a configuration value (use with --value)")
9✔
145
    .option("--value <value>", "Value to set (use with --set or --add)")
9✔
146
    .option("-r, --reset", "Reset configuration to defaults")
9✔
147
    .option("-f, --force", "Skip confirmation prompt for reset")
9✔
148
    .option("--refresh <name>", "Refresh a specific server with config drift")
9✔
149
    .option("--refresh-all", "Refresh all servers with config drift")
9✔
150
    .option("-t, --tag <tag>", "Refresh servers with this tag (use with --refresh-all)")
9✔
151
    .option("--dry-run", "Show what would be refreshed without making changes")
9✔
152
    .option("--add <name>", "Add a custom template variable (use with --value)")
9✔
153
    .option("--remove <name>", "Remove a custom template variable")
9✔
154
    .option("--list-vars", "List all custom template variables")
9✔
155
    .action(async (options, cmd) => {
9✔
156
      const globalOpts = cmd.parent?.opts() ?? {};
×
157
      await configAction({ ...options, json: globalOpts.json });
×
158
    });
9✔
159

160
  // MCP command
161
  program
9✔
162
    .command("mcp")
9✔
163
    .description("Start the MCP server (for use with Claude Code or other MCP clients)")
9✔
164
    .action(async () => {
9✔
165
      await mcpAction();
×
166
    });
9✔
167

168
  // Handle unknown commands with a helpful error message
169
  const knownCommands = ["start", "stop", "list", "ls", "info", "logs", "restart", "remove", "rm", "config", "mcp", "help"];
9✔
170
  program.on("command:*", (operands) => {
9✔
171
    const unknownCommand = operands[0];
×
172
    if (!knownCommands.includes(unknownCommand)) {
×
173
      console.error(`error: unknown command '${unknownCommand}'`);
×
174
      console.error();
×
175
      console.error("Did you mean to run an external command? Use 'start' with '--':");
×
176
      console.error(`  servherd start -- ${operands.join(" ")}`);
×
177
      console.error();
×
178
      console.error("For example:");
×
179
      console.error("  servherd start -- npx vite --port {{port}}");
×
180
      console.error("  servherd start -n my-server -- npm run dev");
×
181
      console.error();
×
182
      console.error("Run 'servherd --help' for usage information.");
×
183
      process.exit(1);
×
184
    }
×
185
  });
9✔
186

187
  return program;
9✔
188
}
9✔
189

190
/**
191
 * Run the CLI
192
 */
193
export async function runCLI(args: string[] = process.argv): Promise<void> {
×
194
  const program = createProgram();
×
195
  await program.parseAsync(args);
×
196
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc