talking-stick 0.1.0-alpha

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.
@@ -0,0 +1,272 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ export const SUPPORTED_HARNESSES = ["claude-code", "codex", "gemini", "opencode"];
6
+ export const DEFAULT_SERVER_NAME = "talking-stick";
7
+ export const DEFAULT_SERVER_COMMAND = ["tt", "mcp"];
8
+ function resolveOptions(options = {}) {
9
+ return {
10
+ env: options.env ?? process.env,
11
+ platform: options.platform ?? process.platform,
12
+ homeDir: options.homeDir ?? os.homedir(),
13
+ serverName: options.serverName ?? DEFAULT_SERVER_NAME,
14
+ serverCommand: options.serverCommand ?? DEFAULT_SERVER_COMMAND,
15
+ hooks: {
16
+ run: options.run ?? defaultRun,
17
+ readFile: options.readFile ?? defaultReadFile,
18
+ writeFile: options.writeFile ?? defaultWriteFile,
19
+ ensureDir: options.ensureDir ?? defaultEnsureDir,
20
+ which: options.which ?? defaultWhich
21
+ }
22
+ };
23
+ }
24
+ function defaultRun(command, args, options = {}) {
25
+ return new Promise((resolve, reject) => {
26
+ const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"], ...options });
27
+ const stdoutChunks = [];
28
+ const stderrChunks = [];
29
+ child.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
30
+ child.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
31
+ child.on("error", reject);
32
+ child.on("close", (code) => {
33
+ resolve({
34
+ exitCode: code ?? 0,
35
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
36
+ stderr: Buffer.concat(stderrChunks).toString("utf8")
37
+ });
38
+ });
39
+ });
40
+ }
41
+ function defaultReadFile(filePath) {
42
+ try {
43
+ return fs.readFileSync(filePath, "utf8");
44
+ }
45
+ catch (error) {
46
+ if (error.code === "ENOENT")
47
+ return null;
48
+ throw error;
49
+ }
50
+ }
51
+ function defaultWriteFile(filePath, data) {
52
+ fs.writeFileSync(filePath, data);
53
+ }
54
+ function defaultEnsureDir(dirPath) {
55
+ fs.mkdirSync(dirPath, { recursive: true });
56
+ }
57
+ function defaultWhich(binary) {
58
+ const pathEnv = process.env.PATH ?? "";
59
+ const separator = process.platform === "win32" ? ";" : ":";
60
+ const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
61
+ for (const dir of pathEnv.split(separator)) {
62
+ if (!dir)
63
+ continue;
64
+ for (const ext of extensions) {
65
+ const candidate = path.join(dir, binary + ext);
66
+ if (fs.existsSync(candidate))
67
+ return candidate;
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+ export function resolveOpencodeConfigPath(options = {}) {
73
+ const resolved = resolveOptions(options);
74
+ const xdg = resolved.env.XDG_CONFIG_HOME?.trim();
75
+ const base = xdg && xdg.length > 0 ? xdg : path.join(resolved.homeDir, ".config");
76
+ return path.join(base, "opencode", "opencode.json");
77
+ }
78
+ export function planInstall(harness, options = {}) {
79
+ const resolved = resolveOptions(options);
80
+ const [serverBin, ...serverArgs] = resolved.serverCommand;
81
+ if (!serverBin)
82
+ throw new Error("serverCommand must include at least the binary");
83
+ switch (harness) {
84
+ case "claude-code":
85
+ return {
86
+ kind: "exec",
87
+ harness,
88
+ command: "claude",
89
+ args: ["mcp", "add", "-s", "user", resolved.serverName, "--", serverBin, ...serverArgs],
90
+ description: `claude mcp add -s user ${resolved.serverName} -- ${resolved.serverCommand.join(" ")}`
91
+ };
92
+ case "codex":
93
+ return {
94
+ kind: "exec",
95
+ harness,
96
+ command: "codex",
97
+ args: ["mcp", "add", resolved.serverName, "--", serverBin, ...serverArgs],
98
+ description: `codex mcp add ${resolved.serverName} -- ${resolved.serverCommand.join(" ")}`
99
+ };
100
+ case "gemini":
101
+ return {
102
+ kind: "exec",
103
+ harness,
104
+ command: "gemini",
105
+ args: ["mcp", "add", "-s", "user", "-t", "stdio", resolved.serverName, serverBin, ...serverArgs],
106
+ description: `gemini mcp add -s user -t stdio ${resolved.serverName} ${resolved.serverCommand.join(" ")}`
107
+ };
108
+ case "opencode": {
109
+ const filePath = resolveOpencodeConfigPath(options);
110
+ return {
111
+ kind: "file-patch",
112
+ harness,
113
+ filePath,
114
+ description: `merge mcp.${resolved.serverName} into ${filePath}`,
115
+ apply: () => patchOpencodeConfig(filePath, resolved, "install")
116
+ };
117
+ }
118
+ default:
119
+ throw new Error(`Unknown harness: ${harness}`);
120
+ }
121
+ }
122
+ export function planUninstall(harness, options = {}) {
123
+ const resolved = resolveOptions(options);
124
+ switch (harness) {
125
+ case "claude-code":
126
+ return {
127
+ kind: "exec",
128
+ harness,
129
+ command: "claude",
130
+ args: ["mcp", "remove", "-s", "user", resolved.serverName],
131
+ description: `claude mcp remove -s user ${resolved.serverName}`
132
+ };
133
+ case "codex":
134
+ return {
135
+ kind: "exec",
136
+ harness,
137
+ command: "codex",
138
+ args: ["mcp", "remove", resolved.serverName],
139
+ description: `codex mcp remove ${resolved.serverName}`
140
+ };
141
+ case "gemini":
142
+ return {
143
+ kind: "exec",
144
+ harness,
145
+ command: "gemini",
146
+ args: ["mcp", "remove", "-s", "user", resolved.serverName],
147
+ description: `gemini mcp remove -s user ${resolved.serverName}`
148
+ };
149
+ case "opencode": {
150
+ const filePath = resolveOpencodeConfigPath(options);
151
+ return {
152
+ kind: "file-patch",
153
+ harness,
154
+ filePath,
155
+ description: `remove mcp.${resolved.serverName} from ${filePath}`,
156
+ apply: () => patchOpencodeConfig(filePath, resolved, "uninstall")
157
+ };
158
+ }
159
+ default:
160
+ throw new Error(`Unknown harness: ${harness}`);
161
+ }
162
+ }
163
+ function patchOpencodeConfig(filePath, resolved, mode) {
164
+ const existing = resolved.hooks.readFile(filePath);
165
+ const config = existing ? parseJsonOrThrow(existing, filePath) : {};
166
+ const mcp = isPlainObject(config.mcp) ? { ...config.mcp } : {};
167
+ if (mode === "install") {
168
+ mcp[resolved.serverName] = {
169
+ type: "local",
170
+ command: [...resolved.serverCommand],
171
+ enabled: true
172
+ };
173
+ }
174
+ else {
175
+ delete mcp[resolved.serverName];
176
+ }
177
+ config.mcp = mcp;
178
+ resolved.hooks.ensureDir(path.dirname(filePath));
179
+ resolved.hooks.writeFile(filePath, JSON.stringify(config, null, 2) + "\n");
180
+ }
181
+ function parseJsonOrThrow(raw, filePath) {
182
+ try {
183
+ const parsed = JSON.parse(raw);
184
+ if (!isPlainObject(parsed)) {
185
+ throw new Error(`${filePath} is not a JSON object`);
186
+ }
187
+ return parsed;
188
+ }
189
+ catch (error) {
190
+ throw new Error(`Failed to parse ${filePath} as JSON: ${error.message}`);
191
+ }
192
+ }
193
+ function isPlainObject(value) {
194
+ return typeof value === "object" && value !== null && !Array.isArray(value);
195
+ }
196
+ export function detectHarness(harness, options = {}) {
197
+ const resolved = resolveOptions(options);
198
+ switch (harness) {
199
+ case "claude-code": {
200
+ const bin = resolved.hooks.which("claude");
201
+ return { harness, detected: bin !== null, evidence: bin ?? "claude not on PATH" };
202
+ }
203
+ case "codex": {
204
+ const bin = resolved.hooks.which("codex");
205
+ return { harness, detected: bin !== null, evidence: bin ?? "codex not on PATH" };
206
+ }
207
+ case "gemini": {
208
+ const bin = resolved.hooks.which("gemini");
209
+ return { harness, detected: bin !== null, evidence: bin ?? "gemini not on PATH" };
210
+ }
211
+ case "opencode": {
212
+ const bin = resolved.hooks.which("opencode");
213
+ if (bin)
214
+ return { harness, detected: true, evidence: bin };
215
+ const configPath = resolveOpencodeConfigPath(options);
216
+ const existing = resolved.hooks.readFile(configPath);
217
+ if (existing !== null)
218
+ return { harness, detected: true, evidence: configPath };
219
+ return { harness, detected: false, evidence: "opencode not on PATH and no config file" };
220
+ }
221
+ default:
222
+ throw new Error(`Unknown harness: ${harness}`);
223
+ }
224
+ }
225
+ export async function runAction(action, options = {}) {
226
+ const resolved = resolveOptions(options);
227
+ if (action.kind === "exec") {
228
+ const result = await resolved.hooks.run(action.command, action.args);
229
+ if (result.exitCode === 0) {
230
+ return {
231
+ harness: action.harness,
232
+ ok: true,
233
+ action,
234
+ message: result.stdout.trim() || `${action.command} succeeded`
235
+ };
236
+ }
237
+ return {
238
+ harness: action.harness,
239
+ ok: false,
240
+ action,
241
+ message: (result.stderr.trim() || result.stdout.trim() || `${action.command} exited with code ${result.exitCode}`)
242
+ };
243
+ }
244
+ try {
245
+ action.apply();
246
+ return {
247
+ harness: action.harness,
248
+ ok: true,
249
+ action,
250
+ message: `Updated ${action.filePath}`
251
+ };
252
+ }
253
+ catch (error) {
254
+ return {
255
+ harness: action.harness,
256
+ ok: false,
257
+ action,
258
+ message: error.message
259
+ };
260
+ }
261
+ }
262
+ export function parseHarnessList(values) {
263
+ const result = [];
264
+ for (const value of values) {
265
+ if (!SUPPORTED_HARNESSES.includes(value)) {
266
+ throw new Error(`Unknown harness: ${value}. Supported: ${SUPPORTED_HARNESSES.join(", ")}`);
267
+ }
268
+ if (!result.includes(value))
269
+ result.push(value);
270
+ }
271
+ return result;
272
+ }
@@ -0,0 +1,171 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { isProtocolError } from "./errors.js";
5
+ import { deriveMcpHarnessIdentity } from "./identity.js";
6
+ import { createSystemProcessInspector } from "./process-utils.js";
7
+ import { TalkingStickCommands } from "./commands.js";
8
+ import { TalkingStickService } from "./service.js";
9
+ const handoffSchema = z
10
+ .object({
11
+ status: z.string(),
12
+ next_action: z.string(),
13
+ artifacts: z
14
+ .array(z.object({
15
+ path: z.string(),
16
+ lines: z.array(z.number().int()).length(2).optional(),
17
+ role: z.enum(["examine", "review", "edit", "context", "output"]),
18
+ note: z.string().optional()
19
+ }))
20
+ .optional(),
21
+ open_questions: z.array(z.string()).optional(),
22
+ do_not: z.array(z.string()).optional()
23
+ })
24
+ .passthrough();
25
+ export function createMcpServer(service = new TalkingStickService()) {
26
+ const commands = new TalkingStickCommands(service);
27
+ const resolveConnectionIdentity = createConnectionIdentityResolver();
28
+ const server = new McpServer({
29
+ name: "talking-stick",
30
+ version: "0.1.0-alpha"
31
+ });
32
+ server.registerTool("list_rooms", {
33
+ title: "List Rooms",
34
+ description: "List talking-stick rooms, optionally scoped to a path.",
35
+ inputSchema: {
36
+ context_path: z.string().optional()
37
+ }
38
+ }, async (input) => toolJson(() => service.listRooms(input)));
39
+ server.registerTool("join_path", {
40
+ title: "Join Path",
41
+ description: "Join the room resolved from an invocation context path.",
42
+ inputSchema: {
43
+ context_path: z.string().min(1),
44
+ force_new: z.boolean().optional(),
45
+ agent_id_override: z.string().min(1).optional()
46
+ }
47
+ }, async (input, extra) => toolJson(() => commands.joinPath(resolveConnectionIdentity(extra.sessionId, input.agent_id_override), {
48
+ context_path: input.context_path,
49
+ force_new: input.force_new
50
+ })));
51
+ server.registerTool("wait_for_turn", {
52
+ title: "Wait For Turn",
53
+ description: "Poll until the caller can claim the stick or takeover is available.",
54
+ inputSchema: {
55
+ room_id: z.string().min(1),
56
+ cursor: z.string().optional(),
57
+ max_wait_ms: z.number().int().nonnegative().optional()
58
+ }
59
+ }, async (input, extra) => toolJson(() => commands.waitForTurn(resolveConnectionIdentity(extra.sessionId), input)));
60
+ server.registerTool("heartbeat", {
61
+ title: "Heartbeat",
62
+ description: "Renew the current owner's lease.",
63
+ inputSchema: ownerMutationSchema()
64
+ }, async (input, extra) => toolJson(() => commands.heartbeat(resolveConnectionIdentity(extra.sessionId), input)));
65
+ server.registerTool("release_stick", {
66
+ title: "Release Stick",
67
+ description: "Release the stick to the next active member in sequence.",
68
+ inputSchema: {
69
+ ...ownerMutationSchema(),
70
+ handoff: handoffSchema
71
+ }
72
+ }, async (input, extra) => toolJson(() => commands.releaseStick(resolveConnectionIdentity(extra.sessionId), input)));
73
+ server.registerTool("pass_stick", {
74
+ title: "Pass Stick",
75
+ description: "Pass the stick to a specific active member.",
76
+ inputSchema: {
77
+ ...ownerMutationSchema(),
78
+ to_agent_id: z.string().min(1),
79
+ handoff: handoffSchema
80
+ }
81
+ }, async (input, extra) => toolJson(() => commands.passStick(resolveConnectionIdentity(extra.sessionId), input)));
82
+ server.registerTool("takeover_stick", {
83
+ title: "Takeover Stick",
84
+ description: "Explicitly take over after claim timeout or owner lease timeout.",
85
+ inputSchema: {
86
+ room_id: z.string().min(1),
87
+ expected_turn_id: z.number().int().nonnegative(),
88
+ reason: z.string().min(1)
89
+ }
90
+ }, async (input, extra) => toolJson(() => commands.takeoverStick(resolveConnectionIdentity(extra.sessionId), input)));
91
+ server.registerTool("get_room_state", {
92
+ title: "Get Room State",
93
+ description: "Read the current projected room state and membership.",
94
+ inputSchema: {
95
+ room_id: z.string().min(1)
96
+ }
97
+ }, async (input) => toolJson(() => service.getRoomState(input)));
98
+ server.registerTool("get_room_events", {
99
+ title: "Get Room Events",
100
+ description: "Read the append-only event log for a room.",
101
+ inputSchema: {
102
+ room_id: z.string().min(1),
103
+ after_event_seq: z.number().int().nonnegative().optional(),
104
+ limit: z.number().int().positive().optional()
105
+ }
106
+ }, async (input) => toolJson(() => service.getRoomEvents(input)));
107
+ return server;
108
+ }
109
+ export function createConnectionIdentityResolver(options = {}) {
110
+ const inspector = options.inspector ?? createSystemProcessInspector({ cacheTtlMs: 60_000 });
111
+ const connectionOverrides = new Map();
112
+ const connectionIdentities = new Map();
113
+ return (sessionId, override) => {
114
+ const key = sessionId ?? "__stdio__";
115
+ if (override) {
116
+ connectionOverrides.set(key, override);
117
+ connectionIdentities.delete(key);
118
+ }
119
+ const cached = connectionIdentities.get(key);
120
+ if (cached) {
121
+ return cached;
122
+ }
123
+ const identity = deriveMcpHarnessIdentity({
124
+ sessionId,
125
+ agentId: connectionOverrides.get(key),
126
+ inspector
127
+ });
128
+ connectionIdentities.set(key, identity);
129
+ return identity;
130
+ };
131
+ }
132
+ export async function runStdioServer() {
133
+ const service = new TalkingStickService();
134
+ const server = createMcpServer(service);
135
+ process.on("exit", () => service.close());
136
+ await server.connect(new StdioServerTransport());
137
+ }
138
+ function ownerMutationSchema() {
139
+ return {
140
+ room_id: z.string().min(1),
141
+ lease_id: z.string().min(1),
142
+ expected_turn_id: z.number().int().nonnegative()
143
+ };
144
+ }
145
+ async function toolJson(fn) {
146
+ try {
147
+ const result = await fn();
148
+ return {
149
+ content: [
150
+ {
151
+ type: "text",
152
+ text: JSON.stringify(result, null, 2)
153
+ }
154
+ ]
155
+ };
156
+ }
157
+ catch (error) {
158
+ if (isProtocolError(error)) {
159
+ return {
160
+ isError: true,
161
+ content: [
162
+ {
163
+ type: "text",
164
+ text: JSON.stringify(error.toJSON(), null, 2)
165
+ }
166
+ ]
167
+ };
168
+ }
169
+ throw error;
170
+ }
171
+ }
@@ -0,0 +1,101 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ const workspaceMarkers = [
5
+ "CLAUDE.md",
6
+ "AGENTS.md",
7
+ "package.json",
8
+ "pyproject.toml",
9
+ "Cargo.toml",
10
+ "go.mod"
11
+ ];
12
+ export function resolveContextPath(contextPath) {
13
+ const requestedPath = path.resolve(contextPath);
14
+ const canonicalContextPath = canonicalizeContextPath(requestedPath);
15
+ const workspaceRoot = resolveWorkspaceRoot(canonicalContextPath);
16
+ return {
17
+ requested_path: requestedPath,
18
+ canonical_context_path: canonicalContextPath,
19
+ workspace_root: workspaceRoot
20
+ };
21
+ }
22
+ export function canonicalizeContextPath(contextPath) {
23
+ const resolved = path.resolve(contextPath);
24
+ let directoryPath = resolved;
25
+ try {
26
+ const stat = fs.statSync(resolved);
27
+ if (stat.isFile()) {
28
+ directoryPath = path.dirname(resolved);
29
+ }
30
+ }
31
+ catch {
32
+ directoryPath = resolved;
33
+ }
34
+ try {
35
+ return fs.realpathSync.native(directoryPath);
36
+ }
37
+ catch {
38
+ return path.normalize(directoryPath);
39
+ }
40
+ }
41
+ export function resolveWorkspaceRoot(canonicalContextPath) {
42
+ const gitRoot = resolveGitRoot(canonicalContextPath);
43
+ if (gitRoot) {
44
+ return gitRoot;
45
+ }
46
+ const markerRoot = findNearestWorkspaceMarker(canonicalContextPath);
47
+ if (markerRoot) {
48
+ return markerRoot;
49
+ }
50
+ return canonicalContextPath;
51
+ }
52
+ export function ancestorPaths(canonicalContextPath, workspaceRoot) {
53
+ const ancestors = [];
54
+ let current = canonicalContextPath;
55
+ while (true) {
56
+ ancestors.push(current);
57
+ if (samePath(current, workspaceRoot)) {
58
+ break;
59
+ }
60
+ const parent = path.dirname(current);
61
+ if (samePath(parent, current)) {
62
+ break;
63
+ }
64
+ current = parent;
65
+ }
66
+ return ancestors.filter((candidate) => isWithinOrSame(candidate, workspaceRoot));
67
+ }
68
+ function resolveGitRoot(canonicalContextPath) {
69
+ try {
70
+ const output = execFileSync("git", ["-C", canonicalContextPath, "rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
71
+ if (!output) {
72
+ return null;
73
+ }
74
+ return fs.realpathSync.native(output);
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ function findNearestWorkspaceMarker(startPath) {
81
+ let current = startPath;
82
+ while (true) {
83
+ for (const marker of workspaceMarkers) {
84
+ if (fs.existsSync(path.join(current, marker))) {
85
+ return current;
86
+ }
87
+ }
88
+ const parent = path.dirname(current);
89
+ if (samePath(parent, current)) {
90
+ return null;
91
+ }
92
+ current = parent;
93
+ }
94
+ }
95
+ function samePath(left, right) {
96
+ return path.normalize(left) === path.normalize(right);
97
+ }
98
+ function isWithinOrSame(candidate, root) {
99
+ const relative = path.relative(root, candidate);
100
+ return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative));
101
+ }
@@ -0,0 +1,93 @@
1
+ import { execFileSync } from "node:child_process";
2
+ export function terminateKnownProcess(processRef, options) {
3
+ if (processRef.pid === null ||
4
+ processRef.pid === undefined ||
5
+ processRef.process_started_at === null ||
6
+ processRef.process_started_at === undefined ||
7
+ processRef.process_started_at.trim() === "") {
8
+ return false;
9
+ }
10
+ const inspection = options.inspector.inspect(processRef.pid);
11
+ if (!inspection?.startTime) {
12
+ return false;
13
+ }
14
+ if (inspection.startTime !== processRef.process_started_at) {
15
+ return false;
16
+ }
17
+ try {
18
+ (options.signaler ?? process).kill(processRef.pid, options.signal ?? "SIGTERM");
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ export function createSystemProcessInspector(options = {}) {
26
+ const cache = new Map();
27
+ const cacheTtlMs = options.cacheTtlMs ?? 0;
28
+ return {
29
+ inspect(pid) {
30
+ if (!Number.isInteger(pid) || pid <= 0) {
31
+ return undefined;
32
+ }
33
+ const nowMs = Date.now();
34
+ const cached = cache.get(pid);
35
+ if (cached && nowMs - cached.checked_at_ms < cacheTtlMs) {
36
+ return cached.inspection;
37
+ }
38
+ const inspection = inspectSystemProcess(pid, options);
39
+ cache.set(pid, {
40
+ checked_at_ms: nowMs,
41
+ inspection
42
+ });
43
+ return inspection;
44
+ }
45
+ };
46
+ }
47
+ function inspectSystemProcess(pid, options) {
48
+ const platform = options.platform ?? process.platform;
49
+ if (platform === "win32") {
50
+ return undefined;
51
+ }
52
+ const exists = options.processExists ?? processExistsViaSignal;
53
+ if (!exists(pid)) {
54
+ return null;
55
+ }
56
+ try {
57
+ const output = (options.execFile ?? defaultExecFile)("ps", ["-o", "ppid=", "-o", "lstart=", "-o", "command=", "-p", String(pid)], {
58
+ encoding: "utf8",
59
+ stdio: ["ignore", "pipe", "ignore"]
60
+ }).trimEnd();
61
+ if (!output.trim()) {
62
+ return null;
63
+ }
64
+ const ppidStr = output.slice(0, 8).trim();
65
+ const startTime = output.slice(8, 32).trim();
66
+ const command = output.slice(32).trimStart();
67
+ const ppid = parseInt(ppidStr, 10);
68
+ return {
69
+ pid,
70
+ ppid: isNaN(ppid) ? null : ppid,
71
+ startTime: startTime || null,
72
+ command: command || null
73
+ };
74
+ }
75
+ catch {
76
+ return undefined;
77
+ }
78
+ }
79
+ function processExistsViaSignal(pid) {
80
+ try {
81
+ process.kill(pid, 0);
82
+ return true;
83
+ }
84
+ catch (error) {
85
+ const code = error.code;
86
+ if (code === "ESRCH")
87
+ return false;
88
+ return true;
89
+ }
90
+ }
91
+ function defaultExecFile(file, args, options) {
92
+ return execFileSync(file, args, options);
93
+ }
package/dist/server.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { runStdioServer } from "./mcp-server.js";
3
+ await runStdioServer();