@sumeru/cli 0.1.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/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shazhou
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ 1c87be7f6e3fc7a33efca7c66f512072454c46ebb199496e30241ae6134d2d0c
@@ -0,0 +1,18 @@
1
+ import type { Adapter } from "@sumeru/core";
2
+ import type { GatewayConfig } from "@sumeru/server";
3
+ /**
4
+ * A factory function that constructs an `Adapter` from an opaque options
5
+ * blob. The blob's keys are validated by the adapter, not by the CLI.
6
+ */
7
+ export type AdapterFactory = (opts: Record<string, unknown>) => Adapter;
8
+ /** Registry of adapter factories keyed by adapter name. */
9
+ export type AdapterFactoryMap = Record<string, AdapterFactory>;
10
+ /** Default factories — wires in the built-in adapter packages. */
11
+ export declare const DEFAULT_ADAPTER_FACTORIES: AdapterFactoryMap;
12
+ /**
13
+ * Walk a parsed `gateways` map and produce the adapter registry the server
14
+ * needs. Per-gateway `config` blobs are forwarded verbatim; gateways whose
15
+ * `adapter` field does not match a known factory are silently omitted.
16
+ */
17
+ export declare function buildAdapters(gateways: Record<string, GatewayConfig>, factories?: AdapterFactoryMap): Record<string, Adapter>;
18
+ //# sourceMappingURL=build-adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-adapters.d.ts","sourceRoot":"","sources":["../src/build-adapters.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;AAExE,2DAA2D;AAC3D,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAE/D,kEAAkE;AAClE,eAAO,MAAM,yBAAyB,EAAE,iBAKvC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,EACvC,SAAS,GAAE,iBAA6C,GACtD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAczB"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Build the adapter registry for a Sumeru server from parsed gateway config.
3
+ *
4
+ * Walks the `gateways` map and dispatches on `gw.adapter` to the matching
5
+ * built-in factory (hermes, claude-code). Each gateway's `gw.config` blob is
6
+ * forwarded verbatim to the factory; absent / `null` blobs become `{}`.
7
+ *
8
+ * Unknown adapter names are silently skipped — the gateway then surfaces as
9
+ * `status: "unavailable"` via `GET /gateways`. The CLI MUST NOT crash on a
10
+ * gateway whose adapter package is not bundled.
11
+ *
12
+ * The factory map is injectable for tests; production code uses
13
+ * {@link DEFAULT_ADAPTER_FACTORIES} which wires in the real built-in packages.
14
+ *
15
+ * See `specs/cli-pass-gateway-config.md` (issue #32).
16
+ */
17
+ import { createClaudeCodeAdapter } from "@sumeru/adapter-claude-code";
18
+ import { createCodexAdapter } from "@sumeru/adapter-codex";
19
+ import { createCursorAgentAdapter } from "@sumeru/adapter-cursor-agent";
20
+ import { createHermesAdapter } from "@sumeru/adapter-hermes";
21
+ /** Default factories — wires in the built-in adapter packages. */
22
+ export const DEFAULT_ADAPTER_FACTORIES = {
23
+ hermes: (opts) => createHermesAdapter(opts),
24
+ "claude-code": (opts) => createClaudeCodeAdapter(opts),
25
+ codex: (opts) => createCodexAdapter(opts),
26
+ "cursor-agent": (opts) => createCursorAgentAdapter(opts),
27
+ };
28
+ /**
29
+ * Walk a parsed `gateways` map and produce the adapter registry the server
30
+ * needs. Per-gateway `config` blobs are forwarded verbatim; gateways whose
31
+ * `adapter` field does not match a known factory are silently omitted.
32
+ */
33
+ export function buildAdapters(gateways, factories = DEFAULT_ADAPTER_FACTORIES) {
34
+ const adapters = {};
35
+ for (const [name, gw] of Object.entries(gateways)) {
36
+ const factory = factories[gw.adapter];
37
+ if (factory === undefined) {
38
+ // Adapter package not bundled by this CLI build — leave the
39
+ // gateway's adapter slot empty so the registry reports
40
+ // `status: "unavailable"`.
41
+ continue;
42
+ }
43
+ const opts = gw.config ?? {};
44
+ adapters[name] = factory(opts);
45
+ }
46
+ return adapters;
47
+ }
48
+ //# sourceMappingURL=build-adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-adapters.js","sourceRoot":"","sources":["../src/build-adapters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAa7D,kEAAkE;AAClE,MAAM,CAAC,MAAM,yBAAyB,GAAsB;IAC3D,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC;IAC3C,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC;IACtD,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC;IACzC,cAAc,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC;CACxD,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC5B,QAAuC,EACvC,YAA+B,yBAAyB;IAExD,MAAM,QAAQ,GAA4B,EAAE,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnD,MAAM,OAAO,GAAG,SAAS,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC3B,4DAA4D;YAC5D,uDAAuD;YACvD,2BAA2B;YAC3B,SAAS;QACV,CAAC;QACD,MAAM,IAAI,GAAG,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env -S node --disable-warning=ExperimentalWarning
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env -S node --disable-warning=ExperimentalWarning
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { loadConfig, startServer, } from "@sumeru/server";
6
+ import { Command } from "commander";
7
+ import { buildAdapters } from "./build-adapters.js";
8
+ import { isProcessAlive, readPidFile, removePidFile, resolvePidFilePath, writePidFile, } from "./pid-file.js";
9
+ import { formatPortInUse, killHolder, lookupPortHolder } from "./port-check.js";
10
+ function findVersion() {
11
+ let dir = dirname(fileURLToPath(import.meta.url));
12
+ for (let i = 0; i < 5; i++) {
13
+ try {
14
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
15
+ if (pkg.name === "@sumeru/cli")
16
+ return pkg.version ?? "0.0.0";
17
+ }
18
+ catch {
19
+ /* keep walking */
20
+ }
21
+ dir = dirname(dir);
22
+ }
23
+ return "0.0.0";
24
+ }
25
+ const program = new Command();
26
+ program
27
+ .name("sumeru")
28
+ .description("Agent house — HTTP service for multi-agent management")
29
+ .version(findVersion());
30
+ program
31
+ .command("run")
32
+ .description("[planned] Run a scene with a specified adapter and model")
33
+ .requiredOption("-s, --scene <path>", "Path to scene directory or YAML")
34
+ .requiredOption("-r, --runner <type>", "Adapter type (hermes, claude-code)")
35
+ .requiredOption("-m, --model <model>", "Model identifier")
36
+ .option("-t, --timeout <seconds>", "Timeout in seconds", "300")
37
+ .option("--network", "Allow network access", true)
38
+ .option("--no-network", "Disable network access")
39
+ .option("-i, --image <image>", "Docker image")
40
+ .option("-o, --output <path>", "Output path for recording")
41
+ .action(async (opts) => {
42
+ console.log("sumeru run — not yet implemented");
43
+ console.log("Options:", JSON.stringify(opts, null, 2));
44
+ });
45
+ program
46
+ .command("list")
47
+ .description("[planned] List available scenes")
48
+ .option("-d, --dir <path>", "Scenes directory", "scenes")
49
+ .action(async (opts) => {
50
+ console.log("sumeru list — not yet implemented");
51
+ console.log("Directory:", opts.dir);
52
+ });
53
+ program
54
+ .command("start")
55
+ .description("Start the Sumeru HTTP server")
56
+ .option("-p, --port <number>", "TCP port to bind (0 = ephemeral)", "7900")
57
+ .option("-h, --host <host>", "Bind address", "127.0.0.1")
58
+ .option("-c, --config <path>", "Path to sumeru.yaml configuration file")
59
+ .option("--ocas-dir <path>", "Directory for the ocas content-addressed store (default: $SUMERU_OCAS_DIR or ~/.sumeru/ocas)")
60
+ .option("--force", "Kill any process holding the chosen port before binding (sends SIGTERM, then SIGKILL after 2s)")
61
+ .action(async (opts) => {
62
+ const port = Number.parseInt(opts.port, 10);
63
+ if (Number.isNaN(port) || port < 0) {
64
+ console.error(`Invalid --port value: ${opts.port}`);
65
+ process.exit(1);
66
+ }
67
+ const host = String(opts.host);
68
+ const force = Boolean(opts.force);
69
+ const ocasDir = typeof opts.ocasDir === "string" && opts.ocasDir.length > 0
70
+ ? opts.ocasDir
71
+ : null;
72
+ // Load config (if any) BEFORE binding a port — we want to fail loudly
73
+ // on bad config without leaving a half-started listener around.
74
+ let name = "sumeru";
75
+ let gateways = {};
76
+ let workspaceRoot = null;
77
+ if (typeof opts.config === "string" && opts.config.length > 0) {
78
+ let cfg;
79
+ try {
80
+ cfg = await loadConfig(opts.config);
81
+ }
82
+ catch (err) {
83
+ const msg = err instanceof Error ? err.message : String(err);
84
+ console.error(`Failed to load config from ${opts.config}: ${msg}`);
85
+ process.exit(1);
86
+ }
87
+ name = cfg.name;
88
+ gateways = cfg.gateways;
89
+ workspaceRoot = cfg.workspaceRoot;
90
+ }
91
+ // --- PID file lifecycle (issue #33) ---
92
+ const pidFilePath = resolvePidFilePath();
93
+ const existingPid = readPidFile(pidFilePath);
94
+ if (existingPid !== null) {
95
+ if (isProcessAlive(existingPid)) {
96
+ if (force) {
97
+ try {
98
+ await killHolder(existingPid, port, host);
99
+ console.error(`[sumeru] killed pid ${existingPid} from stale pid file`);
100
+ }
101
+ catch (err) {
102
+ const msg = err instanceof Error ? err.message : String(err);
103
+ console.error(`Failed to kill pid ${existingPid}: ${msg}`);
104
+ process.exit(1);
105
+ }
106
+ }
107
+ else {
108
+ console.error(`Another sumeru appears to be running (pid ${existingPid}, recorded in ${pidFilePath}).\n Stop it first, or run \`sumeru start … --force\` to terminate it.`);
109
+ process.exit(1);
110
+ }
111
+ }
112
+ else {
113
+ console.error(`[sumeru] removing stale pid file (pid ${existingPid} not running)`);
114
+ // fall through; writePidFile below will overwrite.
115
+ }
116
+ }
117
+ try {
118
+ writePidFile(pidFilePath, process.pid);
119
+ }
120
+ catch (err) {
121
+ const msg = err instanceof Error ? err.message : String(err);
122
+ console.error(`[sumeru] could not write pid file ${pidFilePath}: ${msg}`);
123
+ // Best-effort — continue startup.
124
+ }
125
+ try {
126
+ const server = await startServerWithRetry({
127
+ port,
128
+ host,
129
+ name,
130
+ version: findVersion(),
131
+ gateways,
132
+ workspaceRoot,
133
+ ocasDir,
134
+ force,
135
+ });
136
+ console.log(`Listening on http://${server.host}:${server.port}`);
137
+ let shuttingDown = false;
138
+ const shutdown = async (signal) => {
139
+ if (shuttingDown) {
140
+ // Second signal — escape hatch for a hung shutdown.
141
+ const code = signal === "SIGINT" ? 130 : 143;
142
+ process.exit(code);
143
+ }
144
+ shuttingDown = true;
145
+ console.error(`[sumeru] shutting down (${signal})...`);
146
+ try {
147
+ await server.stop();
148
+ try {
149
+ removePidFile(pidFilePath);
150
+ }
151
+ catch (err) {
152
+ const msg = err instanceof Error ? err.message : String(err);
153
+ console.error(`[sumeru] could not remove pid file: ${msg}`);
154
+ }
155
+ process.exit(0);
156
+ }
157
+ catch (err) {
158
+ const msg = err instanceof Error ? err.message : String(err);
159
+ console.error(`[sumeru] failed to stop server: ${msg}`);
160
+ try {
161
+ removePidFile(pidFilePath);
162
+ }
163
+ catch {
164
+ /* ignore on the failure path */
165
+ }
166
+ process.exit(1);
167
+ }
168
+ };
169
+ process.on("SIGINT", () => {
170
+ void shutdown("SIGINT");
171
+ });
172
+ process.on("SIGTERM", () => {
173
+ void shutdown("SIGTERM");
174
+ });
175
+ }
176
+ catch (err) {
177
+ try {
178
+ removePidFile(pidFilePath);
179
+ }
180
+ catch {
181
+ /* best effort on the error path */
182
+ }
183
+ const code = err instanceof Error && "code" in err
184
+ ? err.code
185
+ : null;
186
+ if (code === "EADDRINUSE") {
187
+ const holder = await lookupPortHolder(host, port);
188
+ console.error(formatPortInUse({ host, port, holder }));
189
+ }
190
+ else {
191
+ const msg = err instanceof Error ? err.message : String(err);
192
+ console.error(`Failed to start server: ${msg}`);
193
+ }
194
+ process.exit(1);
195
+ }
196
+ });
197
+ async function startServerWithRetry(args) {
198
+ try {
199
+ return await startServer({
200
+ port: args.port,
201
+ host: args.host,
202
+ name: args.name,
203
+ version: args.version,
204
+ gateways: args.gateways,
205
+ workspaceRoot: args.workspaceRoot,
206
+ adapters: buildAdapters(args.gateways),
207
+ sseHeartbeatMs: null,
208
+ sseBufferSize: null,
209
+ sseRetentionMs: null,
210
+ ocasDir: args.ocasDir,
211
+ });
212
+ }
213
+ catch (err) {
214
+ const code = err instanceof Error && "code" in err
215
+ ? err.code
216
+ : null;
217
+ if (code !== "EADDRINUSE" || !args.force)
218
+ throw err;
219
+ const holder = await lookupPortHolder(args.host, args.port);
220
+ if (holder === null) {
221
+ // Cannot identify holder — propagate the original error so the
222
+ // generic diagnostic kicks in.
223
+ throw err;
224
+ }
225
+ try {
226
+ await killHolder(holder.pid, args.port, args.host);
227
+ }
228
+ catch (killErr) {
229
+ const msg = killErr instanceof Error ? killErr.message : String(killErr);
230
+ console.error(`Failed to kill pid ${holder.pid}: ${msg}`);
231
+ process.exit(1);
232
+ }
233
+ console.error(`[sumeru] killed pid ${holder.pid} holding port ${args.port}`);
234
+ // Retry the bind once.
235
+ return startServer({
236
+ port: args.port,
237
+ host: args.host,
238
+ name: args.name,
239
+ version: args.version,
240
+ gateways: args.gateways,
241
+ workspaceRoot: args.workspaceRoot,
242
+ adapters: buildAdapters(args.gateways),
243
+ sseHeartbeatMs: null,
244
+ sseBufferSize: null,
245
+ sseRetentionMs: null,
246
+ ocasDir: args.ocasDir,
247
+ });
248
+ }
249
+ }
250
+ program.parse();
251
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * PID file management for `sumeru start` (issue #33).
3
+ *
4
+ * Best-effort pid file at `~/.sumeru/sumeru.pid` (or `$SUMERU_PID_FILE`).
5
+ *
6
+ * - `writePidFile(path, pid)` — creates parent dir with 0o700 if missing,
7
+ * writes `<pid>\n` with mode 0o600. Throws on filesystem errors so the
8
+ * caller can choose to degrade.
9
+ * - `readPidFile(path)` — returns the parsed pid, `null` if the file is
10
+ * missing OR malformed (operators may have hand-edited it; we won't crash).
11
+ * - `removePidFile(path)` — silently succeeds if the file is already gone.
12
+ * - `isProcessAlive(pid)` — uses `process.kill(pid, 0)` (no signal sent) to
13
+ * probe liveness. ESRCH → dead. EPERM → live but foreign (still treated
14
+ * as live for safety — we don't want to overwrite someone else's pid file).
15
+ *
16
+ * See specs/cli-pid-file.md.
17
+ */
18
+ /**
19
+ * Resolve the configured pid file path. Honors `SUMERU_PID_FILE` for tests;
20
+ * defaults to `~/.sumeru/sumeru.pid`. `~/` is expanded against `os.homedir()`.
21
+ */
22
+ export declare function resolvePidFilePath(): string;
23
+ /**
24
+ * Write `<pid>\n` to `path` with mode 0o600. Creates the parent dir
25
+ * (0o700) if missing. Throws on EACCES / EROFS / ENOSPC etc. — the caller
26
+ * is responsible for catching and degrading to a warning.
27
+ */
28
+ export declare function writePidFile(path: string, pid: number): void;
29
+ /**
30
+ * Read and parse the pid file. Returns `null` if the file is missing or
31
+ * the contents are not a positive integer.
32
+ */
33
+ export declare function readPidFile(path: string): number | null;
34
+ /**
35
+ * Remove the pid file. No-op if already absent. Other I/O errors propagate
36
+ * so the caller can decide whether to log.
37
+ */
38
+ export declare function removePidFile(path: string): void;
39
+ /**
40
+ * Probe whether `pid` is running. Sends signal 0 via `process.kill`, which
41
+ * performs the permission/existence check without actually delivering a
42
+ * signal.
43
+ *
44
+ * - Success → process exists and we can signal it → `true`.
45
+ * - `ESRCH` → no such process → `false`.
46
+ * - `EPERM` → process exists but belongs to another user → `true` (treat
47
+ * as live; we don't want to overwrite a foreign pid file).
48
+ * - Any other error → `false` (defensive; surfaces as "stale" to the caller).
49
+ */
50
+ export declare function isProcessAlive(pid: number): boolean;
51
+ //# sourceMappingURL=pid-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pid-file.d.ts","sourceRoot":"","sources":["../src/pid-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAaH;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAQ3C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAgB5D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqBvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA2BhD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAanD"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * PID file management for `sumeru start` (issue #33).
3
+ *
4
+ * Best-effort pid file at `~/.sumeru/sumeru.pid` (or `$SUMERU_PID_FILE`).
5
+ *
6
+ * - `writePidFile(path, pid)` — creates parent dir with 0o700 if missing,
7
+ * writes `<pid>\n` with mode 0o600. Throws on filesystem errors so the
8
+ * caller can choose to degrade.
9
+ * - `readPidFile(path)` — returns the parsed pid, `null` if the file is
10
+ * missing OR malformed (operators may have hand-edited it; we won't crash).
11
+ * - `removePidFile(path)` — silently succeeds if the file is already gone.
12
+ * - `isProcessAlive(pid)` — uses `process.kill(pid, 0)` (no signal sent) to
13
+ * probe liveness. ESRCH → dead. EPERM → live but foreign (still treated
14
+ * as live for safety — we don't want to overwrite someone else's pid file).
15
+ *
16
+ * See specs/cli-pid-file.md.
17
+ */
18
+ import { chmodSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
19
+ import { homedir } from "node:os";
20
+ import { dirname, join, resolve } from "node:path";
21
+ /**
22
+ * Resolve the configured pid file path. Honors `SUMERU_PID_FILE` for tests;
23
+ * defaults to `~/.sumeru/sumeru.pid`. `~/` is expanded against `os.homedir()`.
24
+ */
25
+ export function resolvePidFilePath() {
26
+ const env = process.env.SUMERU_PID_FILE;
27
+ const raw = typeof env === "string" && env.length > 0
28
+ ? env
29
+ : join(homedir(), ".sumeru", "sumeru.pid");
30
+ const expanded = raw.startsWith("~/") ? join(homedir(), raw.slice(2)) : raw;
31
+ return resolve(expanded);
32
+ }
33
+ /**
34
+ * Write `<pid>\n` to `path` with mode 0o600. Creates the parent dir
35
+ * (0o700) if missing. Throws on EACCES / EROFS / ENOSPC etc. — the caller
36
+ * is responsible for catching and degrading to a warning.
37
+ */
38
+ export function writePidFile(path, pid) {
39
+ const dir = dirname(path);
40
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
41
+ // `recursive: true` does not chmod an already-existing dir; nudge it.
42
+ try {
43
+ chmodSync(dir, 0o700);
44
+ }
45
+ catch {
46
+ /* best effort — operator may not own the dir */
47
+ }
48
+ writeFileSync(path, `${pid}\n`, { mode: 0o600 });
49
+ // `writeFileSync` honors mode only on create; force it for overwrite.
50
+ try {
51
+ chmodSync(path, 0o600);
52
+ }
53
+ catch {
54
+ /* best effort */
55
+ }
56
+ }
57
+ /**
58
+ * Read and parse the pid file. Returns `null` if the file is missing or
59
+ * the contents are not a positive integer.
60
+ */
61
+ export function readPidFile(path) {
62
+ let raw;
63
+ try {
64
+ raw = readFileSync(path, "utf-8");
65
+ }
66
+ catch (err) {
67
+ if (err !== null &&
68
+ typeof err === "object" &&
69
+ "code" in err &&
70
+ err.code === "ENOENT") {
71
+ return null;
72
+ }
73
+ throw err;
74
+ }
75
+ const trimmed = raw.trim();
76
+ if (trimmed.length === 0)
77
+ return null;
78
+ if (!/^\d+$/.test(trimmed))
79
+ return null;
80
+ const pid = Number.parseInt(trimmed, 10);
81
+ if (!Number.isFinite(pid) || pid <= 0)
82
+ return null;
83
+ return pid;
84
+ }
85
+ /**
86
+ * Remove the pid file. No-op if already absent. Other I/O errors propagate
87
+ * so the caller can decide whether to log.
88
+ */
89
+ export function removePidFile(path) {
90
+ try {
91
+ statSync(path);
92
+ }
93
+ catch (err) {
94
+ if (err !== null &&
95
+ typeof err === "object" &&
96
+ "code" in err &&
97
+ err.code === "ENOENT") {
98
+ return;
99
+ }
100
+ throw err;
101
+ }
102
+ try {
103
+ unlinkSync(path);
104
+ }
105
+ catch (err) {
106
+ if (err !== null &&
107
+ typeof err === "object" &&
108
+ "code" in err &&
109
+ err.code === "ENOENT") {
110
+ return;
111
+ }
112
+ throw err;
113
+ }
114
+ }
115
+ /**
116
+ * Probe whether `pid` is running. Sends signal 0 via `process.kill`, which
117
+ * performs the permission/existence check without actually delivering a
118
+ * signal.
119
+ *
120
+ * - Success → process exists and we can signal it → `true`.
121
+ * - `ESRCH` → no such process → `false`.
122
+ * - `EPERM` → process exists but belongs to another user → `true` (treat
123
+ * as live; we don't want to overwrite a foreign pid file).
124
+ * - Any other error → `false` (defensive; surfaces as "stale" to the caller).
125
+ */
126
+ export function isProcessAlive(pid) {
127
+ if (!Number.isFinite(pid) || pid <= 0)
128
+ return false;
129
+ try {
130
+ process.kill(pid, 0);
131
+ return true;
132
+ }
133
+ catch (err) {
134
+ if (err === null || typeof err !== "object" || !("code" in err)) {
135
+ return false;
136
+ }
137
+ const code = err.code;
138
+ if (code === "EPERM")
139
+ return true;
140
+ return false;
141
+ }
142
+ }
143
+ //# sourceMappingURL=pid-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pid-file.js","sourceRoot":"","sources":["../src/pid-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Port conflict detection + force-kill helpers for `sumeru start` (issue #33).
3
+ *
4
+ * - `lookupPortHolder(host, port)` shells out to `lsof -i :<port>
5
+ * -sTCP:LISTEN -t -P -n` to identify the process bound to the port.
6
+ * Returns `null` if lsof is missing OR no holder is found OR the helper
7
+ * fails for any reason — the caller should fall back to a generic
8
+ * diagnostic.
9
+ * - `formatPortInUse({ host, port, holder })` produces the operator-facing
10
+ * error block.
11
+ * - `killHolder(pid, port, host)` sends SIGTERM, waits up to 2s for the port
12
+ * to free, then SIGKILLs. Throws if the kill itself errors (e.g. EPERM).
13
+ *
14
+ * See specs/cli-startup-port-check.md.
15
+ */
16
+ export type PortHolder = {
17
+ pid: number;
18
+ command: string;
19
+ };
20
+ export type FormatPortInUseOptions = {
21
+ host: string;
22
+ port: number;
23
+ holder: PortHolder | null;
24
+ };
25
+ /**
26
+ * Resolve the process listening on `host:port` via lsof. Returns null if:
27
+ * - lsof is not on PATH (ENOENT spawn error),
28
+ * - lsof exits non-zero (no holder found),
29
+ * - the holder pid cannot be parsed,
30
+ * - any other error — we never throw to the caller.
31
+ */
32
+ export declare function lookupPortHolder(_host: string, port: number): Promise<PortHolder | null>;
33
+ /**
34
+ * Render the diagnostic the CLI prints when `EADDRINUSE` is caught.
35
+ *
36
+ * - When the holder is identified (lsof present, pid resolved), emit the
37
+ * multi-line block with `Held by pid …` and a `--force` hint.
38
+ * - When the holder is unknown (lsof missing OR could not identify), fall
39
+ * back to the legacy single-line message — no `--force` hint, since
40
+ * without a target pid `--force` cannot do anything anyway.
41
+ *
42
+ * See specs/cli-startup-port-check.md.
43
+ */
44
+ export declare function formatPortInUse(opts: FormatPortInUseOptions): string;
45
+ /**
46
+ * Send SIGTERM to `pid`. Wait up to `gracefulMs` for the port to free; if
47
+ * still bound, send SIGKILL. Resolves once the port is free or after
48
+ * `gracefulMs + killWaitMs` total.
49
+ *
50
+ * Throws if `process.kill` itself fails (e.g. EPERM or ESRCH at SIGTERM
51
+ * time — both are operator-actionable conditions).
52
+ */
53
+ export declare function killHolder(pid: number, port: number, host: string, gracefulMs?: number): Promise<void>;
54
+ //# sourceMappingURL=port-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-check.d.ts","sourceRoot":"","sources":["../src/port-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,MAAM,MAAM,UAAU,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAK5B;AA2DD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,MAAM,CAUpE;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,UAAU,SAAQ,GAChB,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Port conflict detection + force-kill helpers for `sumeru start` (issue #33).
3
+ *
4
+ * - `lookupPortHolder(host, port)` shells out to `lsof -i :<port>
5
+ * -sTCP:LISTEN -t -P -n` to identify the process bound to the port.
6
+ * Returns `null` if lsof is missing OR no holder is found OR the helper
7
+ * fails for any reason — the caller should fall back to a generic
8
+ * diagnostic.
9
+ * - `formatPortInUse({ host, port, holder })` produces the operator-facing
10
+ * error block.
11
+ * - `killHolder(pid, port, host)` sends SIGTERM, waits up to 2s for the port
12
+ * to free, then SIGKILLs. Throws if the kill itself errors (e.g. EPERM).
13
+ *
14
+ * See specs/cli-startup-port-check.md.
15
+ */
16
+ import { spawn } from "node:child_process";
17
+ import { createConnection } from "node:net";
18
+ /**
19
+ * Resolve the process listening on `host:port` via lsof. Returns null if:
20
+ * - lsof is not on PATH (ENOENT spawn error),
21
+ * - lsof exits non-zero (no holder found),
22
+ * - the holder pid cannot be parsed,
23
+ * - any other error — we never throw to the caller.
24
+ */
25
+ export async function lookupPortHolder(_host, port) {
26
+ const pid = await runLsofForPid(port);
27
+ if (pid === null)
28
+ return null;
29
+ const command = await readCommandForPid(pid);
30
+ return { pid, command };
31
+ }
32
+ function runLsofForPid(port) {
33
+ return new Promise((resolve) => {
34
+ let child;
35
+ try {
36
+ child = spawn("lsof", ["-i", `:${port}`, "-sTCP:LISTEN", "-t", "-P", "-n"], { stdio: ["ignore", "pipe", "ignore"] });
37
+ }
38
+ catch {
39
+ resolve(null);
40
+ return;
41
+ }
42
+ let out = "";
43
+ child.stdout?.on("data", (chunk) => {
44
+ out += chunk.toString("utf-8");
45
+ });
46
+ child.on("error", () => resolve(null));
47
+ child.on("close", () => {
48
+ const first = out.split("\n").find((line) => /^\d+$/.test(line.trim()));
49
+ if (first === undefined) {
50
+ resolve(null);
51
+ return;
52
+ }
53
+ const pid = Number.parseInt(first.trim(), 10);
54
+ if (!Number.isFinite(pid) || pid <= 0) {
55
+ resolve(null);
56
+ return;
57
+ }
58
+ resolve(pid);
59
+ });
60
+ });
61
+ }
62
+ function readCommandForPid(pid) {
63
+ return new Promise((resolve) => {
64
+ let child;
65
+ try {
66
+ child = spawn("ps", ["-p", String(pid), "-o", "comm="], {
67
+ stdio: ["ignore", "pipe", "ignore"],
68
+ });
69
+ }
70
+ catch {
71
+ resolve("unknown");
72
+ return;
73
+ }
74
+ let out = "";
75
+ child.stdout?.on("data", (chunk) => {
76
+ out += chunk.toString("utf-8");
77
+ });
78
+ child.on("error", () => resolve("unknown"));
79
+ child.on("close", () => {
80
+ const trimmed = out.trim();
81
+ resolve(trimmed.length > 0 ? trimmed : "unknown");
82
+ });
83
+ });
84
+ }
85
+ /**
86
+ * Render the diagnostic the CLI prints when `EADDRINUSE` is caught.
87
+ *
88
+ * - When the holder is identified (lsof present, pid resolved), emit the
89
+ * multi-line block with `Held by pid …` and a `--force` hint.
90
+ * - When the holder is unknown (lsof missing OR could not identify), fall
91
+ * back to the legacy single-line message — no `--force` hint, since
92
+ * without a target pid `--force` cannot do anything anyway.
93
+ *
94
+ * See specs/cli-startup-port-check.md.
95
+ */
96
+ export function formatPortInUse(opts) {
97
+ const { host, port, holder } = opts;
98
+ if (holder === null) {
99
+ return `Port ${port} is already in use on ${host}. Choose a different --port or stop the conflicting process.`;
100
+ }
101
+ return [
102
+ `Port ${port} is already in use on ${host}.`,
103
+ ` Held by pid ${holder.pid} (${holder.command})`,
104
+ ` Run \`sumeru start --port ${port} --force\` to terminate it, or pick a different --port.`,
105
+ ].join("\n");
106
+ }
107
+ /**
108
+ * Send SIGTERM to `pid`. Wait up to `gracefulMs` for the port to free; if
109
+ * still bound, send SIGKILL. Resolves once the port is free or after
110
+ * `gracefulMs + killWaitMs` total.
111
+ *
112
+ * Throws if `process.kill` itself fails (e.g. EPERM or ESRCH at SIGTERM
113
+ * time — both are operator-actionable conditions).
114
+ */
115
+ export async function killHolder(pid, port, host, gracefulMs = 2_000) {
116
+ process.kill(pid, "SIGTERM");
117
+ const start = Date.now();
118
+ while (Date.now() - start < gracefulMs) {
119
+ if (!(await isPortBound(host, port)))
120
+ return;
121
+ await sleep(100);
122
+ }
123
+ // Still bound — escalate.
124
+ try {
125
+ process.kill(pid, "SIGKILL");
126
+ }
127
+ catch (err) {
128
+ if (err !== null &&
129
+ typeof err === "object" &&
130
+ "code" in err &&
131
+ err.code === "ESRCH") {
132
+ // Already gone between SIGTERM and SIGKILL. That's fine.
133
+ return;
134
+ }
135
+ throw err;
136
+ }
137
+ // Best-effort wait for the kernel to release the socket.
138
+ const killStart = Date.now();
139
+ while (Date.now() - killStart < 1_000) {
140
+ if (!(await isPortBound(host, port)))
141
+ return;
142
+ await sleep(50);
143
+ }
144
+ }
145
+ function isPortBound(host, port) {
146
+ return new Promise((resolve) => {
147
+ const socket = createConnection({ host, port });
148
+ const done = (bound) => {
149
+ socket.removeAllListeners();
150
+ socket.destroy();
151
+ resolve(bound);
152
+ };
153
+ socket.once("connect", () => done(true));
154
+ socket.once("error", () => done(false));
155
+ });
156
+ }
157
+ function sleep(ms) {
158
+ return new Promise((res) => setTimeout(res, ms));
159
+ }
160
+ //# sourceMappingURL=port-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-check.js","sourceRoot":"","sources":["../src/port-check.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@sumeru/cli",
3
+ "description": "CLI for Sumeru agent house",
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "bin": {
7
+ "sumeru": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "dependencies": {
13
+ "commander": "^14.0.0",
14
+ "yaml": "^2.7.0",
15
+ "@sumeru/adapter-claude-code": "^0.1.0",
16
+ "@sumeru/adapter-codex": "^0.1.0",
17
+ "@sumeru/adapter-cursor-agent": "^0.1.0",
18
+ "@sumeru/core": "^0.1.0",
19
+ "@sumeru/server": "^0.1.0",
20
+ "@sumeru/adapter-hermes": "^0.1.0"
21
+ },
22
+ "license": "MIT",
23
+ "scripts": {
24
+ "test:ci": "npx vitest run"
25
+ }
26
+ }