lockstep-mcp 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 +21 -0
- package/README.md +669 -0
- package/dist/cli.js +367 -0
- package/dist/config.js +48 -0
- package/dist/dashboard.js +1982 -0
- package/dist/install.js +252 -0
- package/dist/macos.js +55 -0
- package/dist/prompts.js +173 -0
- package/dist/server.js +1942 -0
- package/dist/storage.js +1235 -0
- package/dist/tmux.js +87 -0
- package/dist/utils.js +35 -0
- package/dist/worktree.js +356 -0
- package/package.json +66 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { installMcpEntry, uninstallMcpEntry, getInstallStatus } from "./install.js";
|
|
2
|
+
import { getAutopilotPrompts, getPlannerPrompt, getImplementerPrompt } from "./prompts.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
// Read version from package.json
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const packageJsonPath = path.resolve(__dirname, "../package.json");
|
|
10
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
11
|
+
const VERSION = packageJson.version;
|
|
12
|
+
function parseArgs(argv) {
|
|
13
|
+
const args = {};
|
|
14
|
+
const positional = [];
|
|
15
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
16
|
+
const token = argv[i];
|
|
17
|
+
if (token.startsWith("--")) {
|
|
18
|
+
const value = argv[i + 1];
|
|
19
|
+
if (!value || value.startsWith("--")) {
|
|
20
|
+
args[token] = true;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
args[token] = value;
|
|
24
|
+
i += 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
positional.push(token);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { positional, args };
|
|
32
|
+
}
|
|
33
|
+
const LOCKSTEP_MARKER_START = "<!-- LOCKSTEP COORDINATION -->";
|
|
34
|
+
const LOCKSTEP_MARKER_END = "<!-- END LOCKSTEP -->";
|
|
35
|
+
const LOCKSTEP_INSTRUCTIONS = `${LOCKSTEP_MARKER_START}
|
|
36
|
+
This project uses lockstep-mcp for multi-agent coordination.
|
|
37
|
+
|
|
38
|
+
When starting a coordination session, call \`coordination_init\` with your role:
|
|
39
|
+
- role: "planner" - You'll plan tasks and create work items
|
|
40
|
+
- role: "implementer" - You'll claim and complete tasks
|
|
41
|
+
|
|
42
|
+
The planner will be asked to provide project context if not already set.
|
|
43
|
+
The implementer will see available tasks or be told to wait for the planner.
|
|
44
|
+
|
|
45
|
+
**If the user says "don't use lockstep" or "work independently", stop using lockstep tools and work normally.**
|
|
46
|
+
|
|
47
|
+
For more info: https://github.com/anthropics/lockstep-mcp
|
|
48
|
+
${LOCKSTEP_MARKER_END}`;
|
|
49
|
+
function printHelp() {
|
|
50
|
+
const text = `lockstep-mcp v${VERSION} - Multi-agent coordination for Claude and Codex
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
lockstep-mcp install [--claude] [--codex] [--all] [--config <path>] [--mode open|strict] [--roots <paths>] [--storage sqlite|json]
|
|
54
|
+
lockstep-mcp uninstall [--claude] [--codex] [--all] [--name <server-name>]
|
|
55
|
+
lockstep-mcp init [--force]
|
|
56
|
+
lockstep-mcp disable
|
|
57
|
+
lockstep-mcp enable
|
|
58
|
+
lockstep-mcp status
|
|
59
|
+
lockstep-mcp server [--mode open|strict] [--roots <paths>] [--storage sqlite|json] [--db-path <path>]
|
|
60
|
+
lockstep-mcp dashboard [--host <host>] [--port <port>] [--poll-ms <ms>]
|
|
61
|
+
lockstep-mcp tmux [--repo <path>] [--session <name>] [--layout windows|panes]
|
|
62
|
+
lockstep-mcp macos [--repo <path>]
|
|
63
|
+
lockstep-mcp prompts [--role planner|implementer]
|
|
64
|
+
lockstep-mcp version
|
|
65
|
+
|
|
66
|
+
Commands:
|
|
67
|
+
install Add lockstep-mcp to Claude and/or Codex MCP configs
|
|
68
|
+
uninstall Remove lockstep-mcp from configs
|
|
69
|
+
init Add coordination instructions to CLAUDE.md (creates if needed)
|
|
70
|
+
disable Remove coordination instructions from CLAUDE.md
|
|
71
|
+
enable Re-add coordination instructions to CLAUDE.md
|
|
72
|
+
status Show installation status
|
|
73
|
+
server Start the MCP server (called by Claude/Codex)
|
|
74
|
+
dashboard Start the web dashboard
|
|
75
|
+
tmux Launch Claude + Codex in tmux
|
|
76
|
+
macos Launch Claude + Codex in macOS Terminal
|
|
77
|
+
version Show version number
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
lockstep-mcp install --all # Install for both Claude and Codex
|
|
81
|
+
lockstep-mcp install --codex --mode strict # Install for Codex only
|
|
82
|
+
lockstep-mcp init # Add instructions to current project
|
|
83
|
+
lockstep-mcp status # Check installation status
|
|
84
|
+
lockstep-mcp --version # Show version
|
|
85
|
+
`;
|
|
86
|
+
process.stdout.write(text);
|
|
87
|
+
}
|
|
88
|
+
function findInstructionsFile() {
|
|
89
|
+
// Look for existing files in order of preference
|
|
90
|
+
const candidates = ["CLAUDE.md", "AGENTS.md"];
|
|
91
|
+
for (const candidate of candidates) {
|
|
92
|
+
const fullPath = path.resolve(process.cwd(), candidate);
|
|
93
|
+
if (fs.existsSync(fullPath))
|
|
94
|
+
return fullPath;
|
|
95
|
+
}
|
|
96
|
+
// Default to CLAUDE.md
|
|
97
|
+
return path.resolve(process.cwd(), "CLAUDE.md");
|
|
98
|
+
}
|
|
99
|
+
function initProject(force) {
|
|
100
|
+
const filePath = findInstructionsFile();
|
|
101
|
+
const fileName = path.basename(filePath);
|
|
102
|
+
if (fs.existsSync(filePath)) {
|
|
103
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
104
|
+
// Check if already has lockstep instructions
|
|
105
|
+
if (content.includes(LOCKSTEP_MARKER_START)) {
|
|
106
|
+
if (!force) {
|
|
107
|
+
return { file: filePath, action: "already_exists" };
|
|
108
|
+
}
|
|
109
|
+
// Remove existing section and re-add
|
|
110
|
+
const newContent = removeLockstepSection(content);
|
|
111
|
+
fs.writeFileSync(filePath, newContent + "\n\n" + LOCKSTEP_INSTRUCTIONS + "\n");
|
|
112
|
+
return { file: filePath, action: "updated" };
|
|
113
|
+
}
|
|
114
|
+
// Append to existing file
|
|
115
|
+
fs.writeFileSync(filePath, content.trimEnd() + "\n\n" + LOCKSTEP_INSTRUCTIONS + "\n");
|
|
116
|
+
return { file: filePath, action: "appended" };
|
|
117
|
+
}
|
|
118
|
+
// Create new file
|
|
119
|
+
fs.writeFileSync(filePath, `# Project Instructions\n\n${LOCKSTEP_INSTRUCTIONS}\n`);
|
|
120
|
+
return { file: filePath, action: "created" };
|
|
121
|
+
}
|
|
122
|
+
function removeLockstepSection(content) {
|
|
123
|
+
const startIdx = content.indexOf(LOCKSTEP_MARKER_START);
|
|
124
|
+
if (startIdx === -1)
|
|
125
|
+
return content;
|
|
126
|
+
const endIdx = content.indexOf(LOCKSTEP_MARKER_END);
|
|
127
|
+
if (endIdx === -1)
|
|
128
|
+
return content;
|
|
129
|
+
const before = content.slice(0, startIdx).trimEnd();
|
|
130
|
+
const after = content.slice(endIdx + LOCKSTEP_MARKER_END.length).trimStart();
|
|
131
|
+
return before + (after ? "\n\n" + after : "");
|
|
132
|
+
}
|
|
133
|
+
function disableProject() {
|
|
134
|
+
const filePath = findInstructionsFile();
|
|
135
|
+
if (!fs.existsSync(filePath)) {
|
|
136
|
+
return { file: filePath, action: "not_found" };
|
|
137
|
+
}
|
|
138
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
139
|
+
if (!content.includes(LOCKSTEP_MARKER_START)) {
|
|
140
|
+
return { file: filePath, action: "not_enabled" };
|
|
141
|
+
}
|
|
142
|
+
const newContent = removeLockstepSection(content);
|
|
143
|
+
fs.writeFileSync(filePath, newContent.trimEnd() + "\n");
|
|
144
|
+
return { file: filePath, action: "disabled" };
|
|
145
|
+
}
|
|
146
|
+
function enableProject() {
|
|
147
|
+
return initProject(false);
|
|
148
|
+
}
|
|
149
|
+
function getProjectStatus() {
|
|
150
|
+
const candidates = ["CLAUDE.md", "AGENTS.md"];
|
|
151
|
+
for (const candidate of candidates) {
|
|
152
|
+
const fullPath = path.resolve(process.cwd(), candidate);
|
|
153
|
+
if (fs.existsSync(fullPath)) {
|
|
154
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
155
|
+
if (content.includes(LOCKSTEP_MARKER_START)) {
|
|
156
|
+
return { enabled: true, file: fullPath };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { enabled: false, file: null };
|
|
161
|
+
}
|
|
162
|
+
async function main() {
|
|
163
|
+
const { positional, args } = parseArgs(process.argv.slice(2));
|
|
164
|
+
const command = positional[0] ?? "server";
|
|
165
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
166
|
+
printHelp();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (command === "version" || command === "--version" || command === "-v" || args["--version"]) {
|
|
170
|
+
process.stdout.write(`lockstep-mcp v${VERSION}\n`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (command === "install") {
|
|
174
|
+
// Determine target
|
|
175
|
+
let target = "config";
|
|
176
|
+
if (args["--all"])
|
|
177
|
+
target = "all";
|
|
178
|
+
else if (args["--claude"])
|
|
179
|
+
target = "claude";
|
|
180
|
+
else if (args["--codex"])
|
|
181
|
+
target = "codex";
|
|
182
|
+
else if (args["--config"])
|
|
183
|
+
target = "config";
|
|
184
|
+
else
|
|
185
|
+
target = "all"; // Default to all
|
|
186
|
+
const result = installMcpEntry({
|
|
187
|
+
target,
|
|
188
|
+
configPath: typeof args["--config"] === "string" ? args["--config"] : undefined,
|
|
189
|
+
name: typeof args["--name"] === "string" ? args["--name"] : undefined,
|
|
190
|
+
mode: typeof args["--mode"] === "string" ? args["--mode"] : "open",
|
|
191
|
+
roots: typeof args["--roots"] === "string" ? args["--roots"] : undefined,
|
|
192
|
+
storage: typeof args["--storage"] === "string" ? args["--storage"] : "sqlite",
|
|
193
|
+
dbPath: typeof args["--db-path"] === "string" ? args["--db-path"] : undefined,
|
|
194
|
+
dataDir: typeof args["--data-dir"] === "string" ? args["--data-dir"] : undefined,
|
|
195
|
+
logDir: typeof args["--log-dir"] === "string" ? args["--log-dir"] : undefined,
|
|
196
|
+
commandMode: typeof args["--command-mode"] === "string" ? args["--command-mode"] : undefined,
|
|
197
|
+
commandAllow: typeof args["--command-allow"] === "string" ? args["--command-allow"] : undefined,
|
|
198
|
+
});
|
|
199
|
+
if ("results" in result && result.results) {
|
|
200
|
+
for (const r of result.results) {
|
|
201
|
+
process.stdout.write(`✓ Installed lockstep to ${r.target}: ${r.configPath}\n`);
|
|
202
|
+
}
|
|
203
|
+
process.stdout.write(`\nNext step: Run 'lockstep-mcp init' in your project directory to enable coordination.\n`);
|
|
204
|
+
}
|
|
205
|
+
else if ("configPath" in result) {
|
|
206
|
+
process.stdout.write(`Wrote MCP server entry "${result.name}" to ${result.configPath}\n`);
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (command === "uninstall") {
|
|
211
|
+
let target = "all";
|
|
212
|
+
if (args["--claude"])
|
|
213
|
+
target = "claude";
|
|
214
|
+
else if (args["--codex"])
|
|
215
|
+
target = "codex";
|
|
216
|
+
const result = uninstallMcpEntry({
|
|
217
|
+
target,
|
|
218
|
+
name: typeof args["--name"] === "string" ? args["--name"] : undefined,
|
|
219
|
+
configPath: typeof args["--config"] === "string" ? args["--config"] : undefined,
|
|
220
|
+
});
|
|
221
|
+
for (const r of result.results) {
|
|
222
|
+
if (r.removed) {
|
|
223
|
+
process.stdout.write(`✓ Removed lockstep from ${r.target}\n`);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
process.stdout.write(`- lockstep not found in ${r.target}\n`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (command === "init") {
|
|
232
|
+
const force = !!args["--force"];
|
|
233
|
+
const result = initProject(force);
|
|
234
|
+
if (result.action === "already_exists") {
|
|
235
|
+
process.stdout.write(`Lockstep instructions already exist in ${result.file}\n`);
|
|
236
|
+
process.stdout.write(`Use --force to update them.\n`);
|
|
237
|
+
}
|
|
238
|
+
else if (result.action === "created") {
|
|
239
|
+
process.stdout.write(`✓ Created ${result.file} with lockstep instructions\n`);
|
|
240
|
+
}
|
|
241
|
+
else if (result.action === "appended") {
|
|
242
|
+
process.stdout.write(`✓ Added lockstep instructions to ${result.file}\n`);
|
|
243
|
+
}
|
|
244
|
+
else if (result.action === "updated") {
|
|
245
|
+
process.stdout.write(`✓ Updated lockstep instructions in ${result.file}\n`);
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (command === "disable") {
|
|
250
|
+
const result = disableProject();
|
|
251
|
+
if (result.action === "not_found") {
|
|
252
|
+
process.stdout.write(`No CLAUDE.md or AGENTS.md found in current directory.\n`);
|
|
253
|
+
}
|
|
254
|
+
else if (result.action === "not_enabled") {
|
|
255
|
+
process.stdout.write(`Lockstep instructions not found in ${result.file}\n`);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
process.stdout.write(`✓ Removed lockstep instructions from ${result.file}\n`);
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (command === "enable") {
|
|
263
|
+
const result = enableProject();
|
|
264
|
+
if (result.action === "already_exists") {
|
|
265
|
+
process.stdout.write(`Lockstep already enabled in ${result.file}\n`);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
process.stdout.write(`✓ Enabled lockstep in ${result.file}\n`);
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (command === "status") {
|
|
273
|
+
const installStatus = getInstallStatus();
|
|
274
|
+
const projectStatus = getProjectStatus();
|
|
275
|
+
process.stdout.write(`\nLockstep MCP Status\n`);
|
|
276
|
+
process.stdout.write(`${"─".repeat(50)}\n\n`);
|
|
277
|
+
process.stdout.write(`Global Installation:\n`);
|
|
278
|
+
process.stdout.write(` Claude: ${installStatus.claude ? "✓ Installed" : "✗ Not installed"}\n`);
|
|
279
|
+
process.stdout.write(` ${installStatus.claudePath}\n`);
|
|
280
|
+
process.stdout.write(` Codex: ${installStatus.codex ? "✓ Installed" : "✗ Not installed"}\n`);
|
|
281
|
+
process.stdout.write(` ${installStatus.codexPath}\n\n`);
|
|
282
|
+
process.stdout.write(`Current Project (${process.cwd()}):\n`);
|
|
283
|
+
if (projectStatus.enabled) {
|
|
284
|
+
process.stdout.write(` Coordination: ✓ Enabled\n`);
|
|
285
|
+
process.stdout.write(` Instructions: ${projectStatus.file}\n`);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
process.stdout.write(` Coordination: ✗ Not enabled\n`);
|
|
289
|
+
process.stdout.write(` Run 'lockstep-mcp init' to enable.\n`);
|
|
290
|
+
}
|
|
291
|
+
process.stdout.write(`\n`);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (command === "prompts") {
|
|
295
|
+
const role = typeof args["--role"] === "string" ? args["--role"] : undefined;
|
|
296
|
+
if (role === "planner") {
|
|
297
|
+
process.stdout.write(getPlannerPrompt());
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (role === "implementer") {
|
|
301
|
+
process.stdout.write(getImplementerPrompt());
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
process.stdout.write(getAutopilotPrompts());
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (command === "dashboard") {
|
|
308
|
+
const { startDashboard } = await import("./dashboard.js");
|
|
309
|
+
const port = typeof args["--port"] === "string" ? Number(args["--port"]) : undefined;
|
|
310
|
+
const host = typeof args["--host"] === "string" ? args["--host"] : undefined;
|
|
311
|
+
const pollMs = typeof args["--poll-ms"] === "string" ? Number(args["--poll-ms"]) : undefined;
|
|
312
|
+
await startDashboard({ port, host, pollMs });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (command === "tmux") {
|
|
316
|
+
const { launchTmux } = await import("./tmux.js");
|
|
317
|
+
const repo = typeof args["--repo"] === "string" ? args["--repo"] : undefined;
|
|
318
|
+
const session = typeof args["--session"] === "string" ? args["--session"] : undefined;
|
|
319
|
+
const claudeCmd = typeof args["--claude-cmd"] === "string" ? args["--claude-cmd"] : undefined;
|
|
320
|
+
const codexCmd = typeof args["--codex-cmd"] === "string" ? args["--codex-cmd"] : undefined;
|
|
321
|
+
const injectPrompts = args["--no-prompts"] ? false : true;
|
|
322
|
+
const layout = typeof args["--layout"] === "string" ? args["--layout"] : undefined;
|
|
323
|
+
const split = typeof args["--split"] === "string" ? args["--split"] : undefined;
|
|
324
|
+
const showDashboard = args["--no-dashboard"] ? false : true;
|
|
325
|
+
const dashboardHost = typeof args["--dashboard-host"] === "string" ? args["--dashboard-host"] : "127.0.0.1";
|
|
326
|
+
const dashboardPort = typeof args["--dashboard-port"] === "string" ? Number(args["--dashboard-port"]) : 8787;
|
|
327
|
+
const cliPath = path.resolve(fileURLToPath(import.meta.url));
|
|
328
|
+
const nodePath = process.execPath;
|
|
329
|
+
const dashboardArgs = ["dashboard", "--host", dashboardHost, "--port", String(dashboardPort)];
|
|
330
|
+
const dashboardCmd = cliPath.endsWith(".ts")
|
|
331
|
+
? `${nodePath} --import tsx ${cliPath} ${dashboardArgs.join(" ")}`
|
|
332
|
+
: `${nodePath} ${cliPath} ${dashboardArgs.join(" ")}`;
|
|
333
|
+
await launchTmux({
|
|
334
|
+
repo,
|
|
335
|
+
session,
|
|
336
|
+
claudeCmd,
|
|
337
|
+
codexCmd,
|
|
338
|
+
injectPrompts,
|
|
339
|
+
layout: layout === "panes" ? "panes" : "windows",
|
|
340
|
+
split: split === "horizontal" ? "horizontal" : "vertical",
|
|
341
|
+
dashboard: showDashboard,
|
|
342
|
+
dashboardCmd,
|
|
343
|
+
});
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (command === "macos") {
|
|
347
|
+
const { launchMacos } = await import("./macos.js");
|
|
348
|
+
const repo = typeof args["--repo"] === "string" ? args["--repo"] : undefined;
|
|
349
|
+
const claudeCmd = typeof args["--claude-cmd"] === "string" ? args["--claude-cmd"] : undefined;
|
|
350
|
+
const codexCmd = typeof args["--codex-cmd"] === "string" ? args["--codex-cmd"] : undefined;
|
|
351
|
+
const dashboardHost = typeof args["--dashboard-host"] === "string" ? args["--dashboard-host"] : "127.0.0.1";
|
|
352
|
+
const dashboardPort = typeof args["--dashboard-port"] === "string" ? Number(args["--dashboard-port"]) : 8787;
|
|
353
|
+
await launchMacos({ repo, claudeCmd, codexCmd, dashboardHost, dashboardPort });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (command === "server") {
|
|
357
|
+
const { startServer } = await import("./server.js");
|
|
358
|
+
await startServer();
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
throw new Error(`Unknown command: ${command}`);
|
|
362
|
+
}
|
|
363
|
+
main().catch((error) => {
|
|
364
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
365
|
+
process.stderr.write(`${message}\n`);
|
|
366
|
+
process.exit(1);
|
|
367
|
+
});
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { expandHome, normalizeRoots } from "./utils.js";
|
|
3
|
+
const DEFAULT_ROOT = process.cwd();
|
|
4
|
+
function parseArgValue(args, key) {
|
|
5
|
+
const idx = args.indexOf(key);
|
|
6
|
+
if (idx === -1 || idx + 1 >= args.length)
|
|
7
|
+
return undefined;
|
|
8
|
+
return args[idx + 1];
|
|
9
|
+
}
|
|
10
|
+
export function loadConfig() {
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
const serverName = parseArgValue(args, "--server-name") || process.env.COORD_SERVER_NAME || "lockstep-mcp";
|
|
13
|
+
const serverVersion = parseArgValue(args, "--server-version") || process.env.COORD_SERVER_VERSION || "0.1.0";
|
|
14
|
+
const mode = (parseArgValue(args, "--mode") || process.env.COORD_MODE || "open");
|
|
15
|
+
const rootsRaw = parseArgValue(args, "--roots") || process.env.COORD_ROOTS || DEFAULT_ROOT;
|
|
16
|
+
const roots = normalizeRoots(rootsRaw
|
|
17
|
+
.split(",")
|
|
18
|
+
.map((root) => root.trim())
|
|
19
|
+
.filter(Boolean));
|
|
20
|
+
const dataDirRaw = parseArgValue(args, "--data-dir") || process.env.COORD_DATA_DIR || "~/.lockstep-mcp/data";
|
|
21
|
+
const logDirRaw = parseArgValue(args, "--log-dir") || process.env.COORD_LOG_DIR || "~/.lockstep-mcp/logs";
|
|
22
|
+
const dataDir = path.resolve(expandHome(dataDirRaw));
|
|
23
|
+
const logDir = path.resolve(expandHome(logDirRaw));
|
|
24
|
+
const storage = (parseArgValue(args, "--storage") || process.env.COORD_STORAGE || "sqlite");
|
|
25
|
+
const dbPathRaw = parseArgValue(args, "--db-path") || process.env.COORD_DB_PATH || path.join(dataDir, "coordinator.db");
|
|
26
|
+
const commandMode = (parseArgValue(args, "--command-mode") ||
|
|
27
|
+
process.env.COORD_COMMAND_MODE ||
|
|
28
|
+
"open");
|
|
29
|
+
const commandAllowRaw = parseArgValue(args, "--command-allow") || process.env.COORD_COMMAND_ALLOW || "";
|
|
30
|
+
const commandAllow = commandAllowRaw
|
|
31
|
+
.split(",")
|
|
32
|
+
.map((cmd) => cmd.trim())
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
return {
|
|
35
|
+
serverName,
|
|
36
|
+
serverVersion,
|
|
37
|
+
dataDir,
|
|
38
|
+
logDir,
|
|
39
|
+
storage: storage === "json" ? "json" : "sqlite",
|
|
40
|
+
dbPath: path.resolve(expandHome(dbPathRaw)),
|
|
41
|
+
mode: mode === "strict" ? "strict" : "open",
|
|
42
|
+
roots,
|
|
43
|
+
command: {
|
|
44
|
+
mode: commandMode === "allowlist" ? "allowlist" : "open",
|
|
45
|
+
allow: commandAllow,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|