feat: 添加 serve 命令别名并重构 server 启动逻辑

This commit is contained in:
DoSun 2026-05-06 14:32:18 +08:00
parent 5058953a30
commit 7814a6f50a

View File

@ -5942,14 +5942,70 @@ async function run(): Promise<CommanderCommand> {
await mcpResetChoicesHandler();
});
// claude server
// csc serve — HTTP API server for IDE plugins and web clients.
// Also accessible via `csc serve` alias.
if (feature("DIRECT_CONNECT")) {
const serveAction = async (opts: {
port: string;
host: string;
authToken?: string;
unix?: string;
workspace?: string;
idleTimeout: string;
maxSessions: string;
}) => {
const { startServer } = await import("./server/server.js");
const { EventBus } = await import("./server/eventBus.js");
const { SessionManager } =
await import("./server/sessionManager.js");
const { printBanner } =
await import("./server/serverBanner.js");
const { createServerLogger } =
await import("./server/serverLog.js");
const eventBus = new EventBus();
const config = {
port: parseInt(opts.port, 10),
host: opts.host,
authToken: undefined,
unix: opts.unix,
workspace: opts.workspace,
idleTimeoutMs: parseInt(opts.idleTimeout, 10),
maxSessions: parseInt(opts.maxSessions, 10),
};
const sessionManager = new SessionManager({
eventBus,
idleTimeoutMs: config.idleTimeoutMs,
maxSessions: config.maxSessions,
workspace: config.workspace,
});
await sessionManager.init();
const logger = createServerLogger();
const server = startServer(config, sessionManager);
const actualPort = server.port ?? config.port;
printBanner(config, undefined, actualPort);
let shuttingDown = false;
const shutdown = async () => {
if (shuttingDown) return;
shuttingDown = true;
server.stop(true);
eventBus.destroy();
await sessionManager.destroyAll();
process.exit(0);
};
process.once("SIGINT", () => void shutdown());
process.once("SIGTERM", () => void shutdown());
};
program
.command("server")
.description("Start a CoStrict session server")
.description("Start a CoStrict session server (alias: serve)")
.option("--port <number>", "HTTP port", "0")
.option("--host <string>", "Bind address", "0.0.0.0")
.option("--auth-token <token>", "Bearer token for auth")
.option("--host <string>", "Bind address", "127.0.0.1")
.option("--auth-token <token>", "Bearer token for auth (currently disabled)")
.option("--unix <path>", "Listen on a unix domain socket")
.option(
"--workspace <dir>",
@ -5958,96 +6014,37 @@ async function run(): Promise<CommanderCommand> {
.option(
"--idle-timeout <ms>",
"Idle timeout for detached sessions in ms (0 = never expire)",
"600000",
"1800000",
)
.option(
"--max-sessions <n>",
"Maximum concurrent sessions (0 = unlimited)",
"32",
)
.action(
async (opts: {
port: string;
host: string;
authToken?: string;
unix?: string;
workspace?: string;
idleTimeout: string;
maxSessions: string;
}) => {
const { randomBytes } = await import("crypto");
const { startServer } = await import("./server/server.js");
const { SessionManager } =
await import("./server/sessionManager.js");
const { DangerousBackend } =
await import("./server/backends/dangerousBackend.js");
const { printBanner } =
await import("./server/serverBanner.js");
const { createServerLogger } =
await import("./server/serverLog.js");
const {
writeServerLock,
removeServerLock,
probeRunningServer,
} = await import("./server/lockfile.js");
.action(serveAction);
const existing = await probeRunningServer();
if (existing) {
process.stderr.write(
`A claude server is already running (pid ${existing.pid}) at ${existing.httpUrl}\n`,
);
process.exit(1);
}
const authToken =
opts.authToken ??
`sk-ant-cc-${randomBytes(16).toString("base64url")}`;
const config = {
port: parseInt(opts.port, 10),
host: opts.host,
authToken,
unix: opts.unix,
workspace: opts.workspace,
idleTimeoutMs: parseInt(opts.idleTimeout, 10),
maxSessions: parseInt(opts.maxSessions, 10),
};
const backend = new DangerousBackend();
const sessionManager = new SessionManager(backend, {
idleTimeoutMs: config.idleTimeoutMs,
maxSessions: config.maxSessions,
});
const logger = createServerLogger();
const server = startServer(config, sessionManager, logger);
const actualPort = server.port ?? config.port;
printBanner(config, authToken, actualPort);
await writeServerLock({
pid: process.pid,
port: actualPort,
host: config.host,
httpUrl: config.unix
? `unix:${config.unix}`
: `http://${config.host}:${actualPort}`,
startedAt: Date.now(),
});
let shuttingDown = false;
const shutdown = async () => {
if (shuttingDown) return;
shuttingDown = true;
// Stop accepting new connections before tearing down sessions.
server.stop(true);
await sessionManager.destroyAll();
await removeServerLock();
process.exit(0);
};
process.once("SIGINT", () => void shutdown());
process.once("SIGTERM", () => void shutdown());
},
);
program
.command("serve")
.description("Start a CoStrict HTTP API server")
.option("--port <number>", "HTTP port", "0")
.option("--host <string>", "Bind address", "127.0.0.1")
.option("--auth-token <token>", "Bearer token for auth (currently disabled)")
.option("--unix <path>", "Listen on a unix domain socket")
.option(
"--workspace <dir>",
"Default working directory for sessions that do not specify cwd",
)
.option(
"--idle-timeout <ms>",
"Idle timeout for detached sessions in ms (0 = never expire)",
"1800000",
)
.option(
"--max-sessions <n>",
"Maximum concurrent sessions (0 = unlimited)",
"32",
)
.action(serveAction);
}
// `claude ssh <host> [dir]` — registered here only so --help shows it.