terminator-mcp-agent 0.5.11 → 0.5.13

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.
Files changed (4) hide show
  1. package/README.md +16 -1
  2. package/config.js +280 -0
  3. package/index.js +35 -227
  4. package/package.json +8 -7
package/README.md CHANGED
@@ -43,7 +43,22 @@ You can also use the CLI to configure your app automatically:
43
43
  npx -y terminator-mcp-agent --add-to-app [app]
44
44
  ```
45
45
 
46
- Replace `[app]` with one of: `cursor`, `claude`, `vscode`, `insiders`, `windsurf`.
46
+ Replace `[app]` with one of:
47
+
48
+ - cursor
49
+ - claude
50
+ - vscode
51
+ - insiders
52
+ - windsurf
53
+ - cline
54
+ - roocode
55
+ - witsy
56
+ - enconvo
57
+ - boltai
58
+ - amazon-bedrock
59
+ - amazonq
60
+
61
+ If you omit `[app]`, the CLI will prompt you to select from all available options.
47
62
 
48
63
  ---
49
64
 
package/config.js ADDED
@@ -0,0 +1,280 @@
1
+ const fs = require("fs");
2
+ const os = require("os");
3
+ const path = require("path");
4
+ const { execFileSync } = require("child_process");
5
+
6
+ /**
7
+ * @typedef {Object} ClientConfig
8
+ * @property {Object.<string, any>} mcpServers
9
+ */
10
+
11
+ /**
12
+ * @typedef {Object} ClientFileTarget
13
+ * @property {"file"} type
14
+ * @property {string} path
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} ClientCommandTarget
19
+ * @property {"command"} type
20
+ * @property {string} command
21
+ */
22
+
23
+ // Initialize platform-specific paths
24
+ const homeDir = os.homedir();
25
+
26
+ const platformPaths = {
27
+ win32: {
28
+ baseDir: process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"),
29
+ vscodePath: path.join("Code", "User", "globalStorage"),
30
+ },
31
+ darwin: {
32
+ baseDir: path.join(homeDir, "Library", "Application Support"),
33
+ vscodePath: path.join("Code", "User", "globalStorage"),
34
+ },
35
+ linux: {
36
+ baseDir: process.env.XDG_CONFIG_HOME || path.join(homeDir, ".config"),
37
+ vscodePath: path.join("Code/User/globalStorage"),
38
+ },
39
+ };
40
+
41
+ const platform = process.platform;
42
+ const { baseDir, vscodePath } = platformPaths[platform] || platformPaths.linux;
43
+ const defaultClaudePath = path.join(
44
+ baseDir,
45
+ "Claude",
46
+ "claude_desktop_config.json",
47
+ );
48
+
49
+ // Define client paths using the platform-specific base directories
50
+ const clientPaths = {
51
+ claude: { type: "file", path: defaultClaudePath },
52
+ cline: {
53
+ type: "file",
54
+ path: path.join(
55
+ baseDir,
56
+ vscodePath,
57
+ "saoudrizwan.claude-dev",
58
+ "settings",
59
+ "cline_mcp_settings.json",
60
+ ),
61
+ },
62
+ roocode: {
63
+ type: "file",
64
+ path: path.join(
65
+ baseDir,
66
+ vscodePath,
67
+ "rooveterinaryinc.roo-cline",
68
+ "settings",
69
+ "mcp_settings.json",
70
+ ),
71
+ },
72
+ windsurf: {
73
+ type: "file",
74
+ path: path.join(homeDir, ".codeium", "windsurf", "mcp_config.json"),
75
+ },
76
+ witsy: { type: "file", path: path.join(baseDir, "Witsy", "settings.json") },
77
+ enconvo: {
78
+ type: "file",
79
+ path: path.join(homeDir, ".config", "enconvo", "mcp_config.json"),
80
+ },
81
+ cursor: { type: "file", path: path.join(homeDir, ".cursor", "mcp.json") },
82
+ vscode: {
83
+ type: "command",
84
+ command: process.platform === "win32" ? "code.cmd" : "code",
85
+ },
86
+ "vscode-insiders": {
87
+ type: "command",
88
+ command:
89
+ process.platform === "win32" ? "code-insiders.cmd" : "code-insiders",
90
+ },
91
+ boltai: { type: "file", path: path.join(homeDir, ".boltai", "mcp.json") },
92
+ "amazon-bedrock": {
93
+ type: "file",
94
+ path: path.join(homeDir, "Amazon Bedrock Client", "mcp_config.json"),
95
+ },
96
+ amazonq: {
97
+ type: "file",
98
+ path: path.join(homeDir, ".aws", "amazonq", "mcp.json"),
99
+ },
100
+ };
101
+
102
+ /**
103
+ * @param {string} [client]
104
+ * @returns {ClientFileTarget|ClientCommandTarget}
105
+ */
106
+ function getConfigPath(client) {
107
+ const normalizedClient = client ? client.toLowerCase() : "claude";
108
+ verbose(`Getting config path for client: ${normalizedClient}`);
109
+
110
+ const configTarget = clientPaths[normalizedClient] || {
111
+ type: "file",
112
+ path: path.join(
113
+ path.dirname(defaultClaudePath),
114
+ "..",
115
+ client || "claude",
116
+ `${normalizedClient}_config.json`,
117
+ ),
118
+ };
119
+
120
+ verbose(`Config path resolved to: ${JSON.stringify(configTarget)}`);
121
+ return configTarget;
122
+ }
123
+
124
+ /**
125
+ * @param {string} client
126
+ * @returns {ClientConfig}
127
+ */
128
+ function readConfig(client) {
129
+ verbose(`Reading config for client: ${client}`);
130
+ try {
131
+ const configPath = getConfigPath(client);
132
+
133
+ // Command-based installers (i.e. VS Code) do not currently support listing servers
134
+ if (configPath.type === "command") {
135
+ return { mcpServers: {} };
136
+ }
137
+
138
+ verbose(`Checking if config file exists at: ${configPath.path}`);
139
+ if (!fs.existsSync(configPath.path)) {
140
+ verbose(`Config file not found, returning default empty config`);
141
+ return { mcpServers: {} };
142
+ }
143
+
144
+ verbose(`Reading config file content`);
145
+ const rawConfig = JSON.parse(fs.readFileSync(configPath.path, "utf8"));
146
+ verbose(
147
+ `Config loaded successfully: ${JSON.stringify(rawConfig, null, 2)}`,
148
+ );
149
+
150
+ return {
151
+ ...rawConfig,
152
+ mcpServers: rawConfig.mcpServers || {},
153
+ };
154
+ } catch (error) {
155
+ verbose(
156
+ `Error reading config: ${error instanceof Error ? error.stack : JSON.stringify(error)}`,
157
+ );
158
+ return { mcpServers: {} };
159
+ }
160
+ }
161
+
162
+ /**
163
+ * @param {ClientConfig} config
164
+ * @param {string} [client]
165
+ */
166
+ function writeConfig(configObj, client) {
167
+ verbose(`Writing config for client: ${client || "default"}`);
168
+ verbose(`Config data: ${JSON.stringify(configObj, null, 2)}`);
169
+
170
+ if (!configObj.mcpServers || typeof configObj.mcpServers !== "object") {
171
+ verbose(`Invalid mcpServers structure in config`);
172
+ throw new Error("Invalid mcpServers structure");
173
+ }
174
+
175
+ const configPath = getConfigPath(client);
176
+ if (configPath.type === "command") {
177
+ writeConfigCommand(configObj, configPath);
178
+ } else {
179
+ writeConfigFile(configObj, configPath);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * @param {ClientConfig} config
185
+ * @param {ClientCommandTarget} target
186
+ */
187
+ function writeConfigCommand(config, target) {
188
+ const args = [];
189
+ for (const [name, server] of Object.entries(config.mcpServers)) {
190
+ args.push("--add-mcp", JSON.stringify({ ...server, name }));
191
+ }
192
+
193
+ verbose(`Running command: ${JSON.stringify([target.command, ...args])}`);
194
+
195
+ try {
196
+ const output = execFileSync(target.command, args);
197
+ verbose(`Executed command successfully: ${output.toString()}`);
198
+ } catch (error) {
199
+ verbose(
200
+ `Error executing command: ${error instanceof Error ? error.message : String(error)}`,
201
+ );
202
+
203
+ if (error && error.code === "ENOENT") {
204
+ throw new Error(
205
+ `Command '${target.command}' not found. Make sure ${target.command} is installed and on your PATH`,
206
+ );
207
+ }
208
+
209
+ throw error;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * @param {ClientConfig} config
215
+ * @param {ClientFileTarget} target
216
+ */
217
+ function writeConfigFile(config, target) {
218
+ const configDir = path.dirname(target.path);
219
+
220
+ verbose(`Ensuring config directory exists: ${configDir}`);
221
+ if (!fs.existsSync(configDir)) {
222
+ verbose(`Creating directory: ${configDir}`);
223
+ fs.mkdirSync(configDir, { recursive: true });
224
+ }
225
+
226
+ let existingConfig = { mcpServers: {} };
227
+ try {
228
+ if (fs.existsSync(target.path)) {
229
+ verbose(`Reading existing config file for merging`);
230
+ existingConfig = JSON.parse(fs.readFileSync(target.path, "utf8"));
231
+ verbose(
232
+ `Existing config loaded: ${JSON.stringify(existingConfig, null, 2)}`,
233
+ );
234
+ }
235
+ } catch (error) {
236
+ verbose(
237
+ `Error reading existing config for merge: ${error instanceof Error ? error.message : String(error)}`,
238
+ );
239
+ // If reading fails, continue with empty existing config
240
+ }
241
+
242
+ verbose(`Merging configs`);
243
+ const mergedConfig = {
244
+ ...existingConfig,
245
+ ...config,
246
+ };
247
+ verbose(`Merged config: ${JSON.stringify(mergedConfig, null, 2)}`);
248
+
249
+ verbose(`Writing config to file: ${target.path}`);
250
+ fs.writeFileSync(target.path, JSON.stringify(mergedConfig, null, 2));
251
+ verbose(`Config successfully written`);
252
+ }
253
+
254
+ function verbose(msg) {
255
+ if (process.env.MCP_VERBOSE) {
256
+ console.log(`[config] ${msg}`);
257
+ }
258
+ }
259
+
260
+ const supportedClients = [
261
+ { key: "cursor", label: "Cursor" },
262
+ { key: "claude", label: "Claude" },
263
+ { key: "vscode", label: "VS Code" },
264
+ { key: "insiders", label: "VS Code Insiders" },
265
+ { key: "windsurf", label: "Windsurf" },
266
+ { key: "cline", label: "Cline" },
267
+ { key: "roocode", label: "RooCode" },
268
+ { key: "witsy", label: "Witsy" },
269
+ { key: "enconvo", label: "Enconvo" },
270
+ { key: "boltai", label: "BoltAI" },
271
+ { key: "amazon-bedrock", label: "Amazon Bedrock" },
272
+ { key: "amazonq", label: "Amazon Q" },
273
+ ];
274
+
275
+ module.exports = {
276
+ getConfigPath,
277
+ readConfig,
278
+ writeConfig,
279
+ supportedClients,
280
+ };
package/index.js CHANGED
@@ -5,6 +5,8 @@ const path = require("path");
5
5
  const fs = require("fs");
6
6
  const os = require("os");
7
7
  const readline = require("readline");
8
+ const config = require("./config");
9
+ const { supportedClients } = require("./config");
8
10
 
9
11
  function getPlatformInfo() {
10
12
  const platform = process.platform;
@@ -36,209 +38,24 @@ function getPlatformInfo() {
36
38
  throw new Error(`Unsupported platform: ${platform} ${arch}`);
37
39
  }
38
40
 
39
- function getMcpServerEntry() {
40
- return {
41
- command: "npx",
42
- args: ["-y", "terminator-mcp-agent"],
43
- };
44
- }
45
-
46
- function addToCursorConfig() {
47
- const home = os.homedir();
48
- const configDir = path.join(home, ".cursor");
49
- const configFile = path.join(configDir, "mcp.json");
50
- if (!fs.existsSync(configDir)) {
51
- fs.mkdirSync(configDir, { recursive: true });
52
- }
53
- let config = {};
54
- if (fs.existsSync(configFile)) {
55
- try {
56
- config = JSON.parse(fs.readFileSync(configFile, "utf8"));
57
- } catch (e) {
58
- config = {};
59
- }
60
- }
61
- if (!config.mcpServers || typeof config.mcpServers !== "object") {
62
- config.mcpServers = {};
63
- }
64
- config.mcpServers["terminator-mcp-agent"] = getMcpServerEntry();
65
- fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
66
- console.log(`Cursor configuration saved to ${configFile}`);
67
- }
68
-
69
- function addToClaudeConfig() {
70
- const platform = process.platform;
71
- let configDir, configFile;
72
- if (platform === "win32") {
73
- const appData =
74
- process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
75
- configDir = path.join(appData, "Claude");
76
- configFile = path.join(configDir, "claude_desktop_config.json");
77
- } else if (platform === "darwin") {
78
- configDir = path.join(
79
- os.homedir(),
80
- "Library",
81
- "Application Support",
82
- "Claude",
83
- );
84
- configFile = path.join(configDir, "claude_desktop_config.json");
85
- } else {
86
- console.error("Claude desktop is only supported on Windows and macOS.");
87
- process.exit(1);
88
- }
89
- if (!fs.existsSync(configDir)) {
90
- console.error(
91
- `Claude desktop config directory does not exist: ${configDir}\nPlease make sure the Claude desktop app is installed for your platform.`,
92
- );
93
- process.exit(1);
94
- }
95
- let config = {};
96
- if (fs.existsSync(configFile)) {
97
- try {
98
- config = JSON.parse(fs.readFileSync(configFile, "utf8"));
99
- } catch (e) {
100
- config = {};
101
- }
102
- }
103
- if (!config.mcpServers || typeof config.mcpServers !== "object") {
104
- config.mcpServers = {};
105
- }
106
- config.mcpServers["terminator-mcp-agent"] = getMcpServerEntry();
107
- fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
108
- console.log(`Claude configuration saved to ${configFile}`);
109
- }
110
-
111
- function buildVSCodeMcpJsonArg(mcpJson) {
112
- // VS Code expects: --add-mcp "{\"name\":\"...\",...}"
113
- return `"${JSON.stringify(mcpJson).replace(/"/g, '\\"')}"`;
114
- }
115
-
116
- function addToVSCodeConfig() {
117
- // VS Code CLI-based setup
118
- console.log("Adding Terminator MCP to VS Code via code CLI...");
119
- const mcpJson = {
120
- name: "terminator-mcp-agent",
121
- command: "npx",
122
- args: ["-y", "terminator-mcp-agent"],
123
- };
124
- const jsonArg = buildVSCodeMcpJsonArg(mcpJson);
125
- const vscodeCmd = "code";
126
- try {
127
- const { spawnSync } = require("child_process");
128
- const result = spawnSync(`${vscodeCmd} --add-mcp ${jsonArg}`, [], {
129
- stdio: "inherit",
130
- shell: true,
131
- });
132
- if (result.error) {
133
- if (result.error.code === "ENOENT") {
134
- console.error(
135
- "'code' command not found in PATH. Make sure VS Code CLI is installed and available.",
136
- );
137
- } else {
138
- console.error("Failed to launch VS Code CLI:", result.error.message);
139
- }
140
- process.exit(1);
141
- }
142
- if (result.status !== 0) {
143
- console.error(`VS Code CLI exited with code ${result.status}`);
144
- process.exit(1);
145
- }
146
- console.log("Successfully added Terminator MCP to VS Code.");
147
- } catch (e) {
148
- console.error("Failed to add MCP to VS Code:", e.message);
149
- process.exit(1);
150
- }
151
- }
152
-
153
- function addToVSCodeInsidersConfig() {
154
- // VS Code Insiders CLI-based setup
155
- console.log(
156
- "Adding Terminator MCP to VS Code Insiders via code-insiders CLI...",
157
- );
158
- const mcpJson = {
159
- name: "terminator-mcp-agent",
160
- command: "npx",
161
- args: ["-y", "terminator-mcp-agent"],
162
- };
163
- const jsonArg = buildVSCodeMcpJsonArg(mcpJson);
164
- const codeInsidersCmd = "code-insiders";
41
+ function addToApp(app) {
165
42
  try {
166
- const { spawnSync } = require("child_process");
167
- const result = spawnSync(`${codeInsidersCmd} --add-mcp ${jsonArg}`, [], {
168
- stdio: "inherit",
169
- shell: true,
170
- });
171
- if (result.error) {
172
- if (result.error.code === "ENOENT") {
173
- console.error(
174
- "'code-insiders' command not found in PATH. Make sure VS Code Insiders CLI is installed and available.",
175
- );
176
- } else {
177
- console.error(
178
- "Failed to launch VS Code Insiders CLI:",
179
- result.error.message,
180
- );
181
- }
182
- process.exit(1);
183
- }
184
- if (result.status !== 0) {
185
- console.error(`VS Code Insiders CLI exited with code ${result.status}`);
186
- process.exit(1);
187
- }
188
- console.log("Successfully added Terminator MCP to VS Code Insiders.");
43
+ const client = (app || "").toLowerCase();
44
+ const mcpServer = {
45
+ command: "npx",
46
+ args: ["-y", "terminator-mcp-agent"],
47
+ };
48
+ const currentConfig = config.readConfig(client);
49
+ currentConfig.mcpServers = currentConfig.mcpServers || {};
50
+ currentConfig.mcpServers["terminator-mcp-agent"] = mcpServer;
51
+ config.writeConfig(currentConfig, client);
52
+ console.log(`Configured MCP for ${client}`);
189
53
  } catch (e) {
190
- console.error("Failed to add MCP to VS Code Insiders:", e.message);
54
+ console.error(`Failed to configure MCP for ${app}:`, e.message);
191
55
  process.exit(1);
192
56
  }
193
57
  }
194
58
 
195
- function addToWindsurfConfig() {
196
- // Windsurf config: %USERPROFILE%/.codeium/windsurf/mcp_config.json
197
- const home = os.homedir();
198
- const configDir = path.join(home, ".codeium", "windsurf");
199
- const configFile = path.join(configDir, "mcp_config.json");
200
- if (!fs.existsSync(configDir)) {
201
- fs.mkdirSync(configDir, { recursive: true });
202
- }
203
- let config = {};
204
- if (fs.existsSync(configFile)) {
205
- try {
206
- config = JSON.parse(fs.readFileSync(configFile, "utf8"));
207
- } catch (e) {
208
- config = {};
209
- }
210
- }
211
- if (!config.mcpServers || typeof config.mcpServers !== "object") {
212
- config.mcpServers = {};
213
- }
214
- config.mcpServers["terminator-mcp-agent"] = getMcpServerEntry();
215
- fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
216
- console.log(`Windsurf configuration saved to ${configFile}`);
217
- }
218
-
219
- function addToApp(app) {
220
- switch ((app || "").toLowerCase()) {
221
- case "cursor":
222
- addToCursorConfig();
223
- break;
224
- case "claude":
225
- addToClaudeConfig();
226
- break;
227
- case "vscode":
228
- addToVSCodeConfig();
229
- break;
230
- case "insiders":
231
- addToVSCodeInsidersConfig();
232
- break;
233
- case "windsurf":
234
- addToWindsurfConfig();
235
- break;
236
- default:
237
- console.error("Unknown app: " + app);
238
- process.exit(1);
239
- }
240
- }
241
-
242
59
  const argv = process.argv.slice(2);
243
60
 
244
61
  if (argv.includes("--add-to-app")) {
@@ -256,39 +73,27 @@ if (argv.includes("--add-to-app")) {
256
73
  console.log("========== Terminator MCP Setup ==========");
257
74
  console.log("Which app do you want to configure Terminator MCP for?");
258
75
  console.log("");
259
- console.log(" 1. Cursor");
260
- console.log(" 2. Claude");
261
- console.log(" 3. VS Code");
262
- console.log(" 4. VS Code Insiders");
263
- console.log(" 5. Windsurf");
76
+ const pad = (n) =>
77
+ String(n).padStart(String(supportedClients.length).length, " ");
78
+ supportedClients.forEach((client, idx) => {
79
+ console.log(` ${pad(idx + 1)}. ${client.label}`);
80
+ });
264
81
  console.log("");
265
- rl.question("Enter your choice (1-5): ", (answer) => {
266
- let selectedApp = null;
267
- switch (answer.trim()) {
268
- case "1":
269
- selectedApp = "cursor";
270
- break;
271
- case "2":
272
- selectedApp = "claude";
273
- break;
274
- case "3":
275
- selectedApp = "vscode";
276
- break;
277
- case "4":
278
- selectedApp = "insiders";
279
- break;
280
- case "5":
281
- selectedApp = "windsurf";
282
- break;
283
- default:
82
+ rl.question(
83
+ `Enter your choice (1-${supportedClients.length}): `,
84
+ (answer) => {
85
+ const idx = parseInt(answer.trim(), 10) - 1;
86
+ if (isNaN(idx) || idx < 0 || idx >= supportedClients.length) {
284
87
  console.error("Invalid choice. Skipping app configuration.");
285
88
  rl.close();
286
89
  process.exit(1);
287
- }
288
- rl.close();
289
- addToApp(selectedApp);
290
- process.exit(0);
291
- });
90
+ }
91
+ const selectedApp = supportedClients[idx].key;
92
+ rl.close();
93
+ addToApp(selectedApp);
94
+ process.exit(0);
95
+ },
96
+ );
292
97
  return;
293
98
  } else {
294
99
  addToApp(app);
@@ -315,7 +120,10 @@ if (argv.length === 0 || argv.includes("--start")) {
315
120
  }
316
121
  }
317
122
 
318
- const child = spawn(binary, [], { stdio: ["pipe", "pipe", "pipe"], shell: true });
123
+ const child = spawn(binary, [], {
124
+ stdio: ["pipe", "pipe", "pipe"],
125
+ shell: true,
126
+ });
319
127
 
320
128
  process.stdin.pipe(child.stdin);
321
129
  child.stdout.pipe(process.stdout);
package/package.json CHANGED
@@ -1,20 +1,21 @@
1
1
  {
2
2
  "bin": {
3
- "terminator-mcp": "index.js"
3
+ "terminator-mcp-agent": "index.js"
4
4
  },
5
5
  "description": "Cross-platform Model Context Protocol (MCP) agent for Terminator",
6
6
  "engines": {
7
7
  "node": ">= 10"
8
8
  },
9
9
  "files": [
10
- "index.js"
10
+ "index.js",
11
+ "config.js"
11
12
  ],
12
13
  "name": "terminator-mcp-agent",
13
14
  "optionalDependencies": {
14
- "terminator-mcp-darwin-arm64": "0.5.11",
15
- "terminator-mcp-darwin-x64": "0.5.11",
16
- "terminator-mcp-linux-x64-gnu": "0.5.11",
17
- "terminator-mcp-win32-x64-msvc": "0.5.11"
15
+ "terminator-mcp-darwin-arm64": "0.5.13",
16
+ "terminator-mcp-darwin-x64": "0.5.13",
17
+ "terminator-mcp-linux-x64-gnu": "0.5.13",
18
+ "terminator-mcp-win32-x64-msvc": "0.5.13"
18
19
  },
19
20
  "repository": {
20
21
  "type": "git",
@@ -26,5 +27,5 @@
26
27
  "sync-version": "node ./utils/sync-version.js",
27
28
  "update-badges": "node ./utils/update-badges.js"
28
29
  },
29
- "version": "0.5.11"
30
+ "version": "0.5.13"
30
31
  }