@hasna/terminal 3.2.1 → 3.3.1

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/dist/cli.js CHANGED
@@ -18,6 +18,13 @@ EXAMPLES:
18
18
  terminal "kill port 3000"
19
19
  terminal "how many lines of code"
20
20
 
21
+ SETUP:
22
+ install Set up MCP server for all AI agents (Claude, Codex, Gemini)
23
+ install --claude Set up for Claude Code only
24
+ install --codex Set up for Codex only
25
+ install --gemini Set up for Gemini CLI only
26
+ uninstall Remove from all agents
27
+
21
28
  SUBCOMMANDS:
22
29
  repo Git repo state (branch + status + log)
23
30
  symbols <file> File outline (functions, classes, exports)
@@ -26,8 +33,7 @@ SUBCOMMANDS:
26
33
  sessions [stats|<id>] Session history and analytics
27
34
  recipe add|list|run|delete Reusable command recipes
28
35
  collection create|list Recipe collections
29
- mcp serve Start MCP server for AI agents
30
- mcp install --claude|--codex Install MCP server
36
+ mcp serve Start MCP server (called by agents, not you)
31
37
  discover [--days=N] [--json] Scan Claude sessions, show token savings potential
32
38
  snapshot Terminal state as JSON
33
39
  --help Show this help
@@ -61,6 +67,17 @@ if (args[0] === "--version" || args[0] === "-v") {
61
67
  }
62
68
  process.exit(0);
63
69
  }
70
+ // ── Install / Uninstall ──────────────────────────────────────────────────────
71
+ if (args[0] === "install") {
72
+ const { handleInstall } = await import("./mcp/install.js");
73
+ handleInstall(args.slice(1));
74
+ process.exit(0);
75
+ }
76
+ if (args[0] === "uninstall") {
77
+ const { handleInstall } = await import("./mcp/install.js");
78
+ handleInstall(["uninstall"]);
79
+ process.exit(0);
80
+ }
64
81
  // ── MCP commands ─────────────────────────────────────────────────────────────
65
82
  if (args[0] === "mcp") {
66
83
  if (args[1] === "serve" || args.length === 1) {
@@ -71,11 +88,12 @@ if (args[0] === "mcp") {
71
88
  });
72
89
  }
73
90
  else if (args[1] === "install") {
74
- const { handleMcpInstall } = await import("./mcp/install.js");
75
- handleMcpInstall(args.slice(2));
91
+ // Legacy: `terminal mcp install` still works
92
+ const { handleInstall } = await import("./mcp/install.js");
93
+ handleInstall(args.slice(2));
76
94
  }
77
95
  else {
78
- console.log("Usage: t mcp [serve|install]");
96
+ console.log("Usage: terminal mcp serve");
79
97
  }
80
98
  }
81
99
  // ── Hook commands ────────────────────────────────────────────────────────────
@@ -1,98 +1,189 @@
1
- // MCP installation helper register open-terminal as MCP server for various agents
1
+ // MCP installation — one command to rule them all
2
+ // `npx @hasna/terminal install` → installs globally + configures all AI agents
2
3
  import { execSync } from "child_process";
3
- import { existsSync, readFileSync, writeFileSync } from "fs";
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
4
5
  import { homedir } from "os";
5
6
  import { join } from "path";
6
- const TERMINAL_BIN = "terminal"; // the CLI binary name
7
7
  function which(cmd) {
8
8
  try {
9
- return execSync(`which ${cmd}`, { encoding: "utf8" }).trim();
9
+ return execSync(`which ${cmd}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10
10
  }
11
11
  catch {
12
12
  return null;
13
13
  }
14
14
  }
15
- export function installClaude() {
15
+ function log(icon, msg) { console.log(` ${icon} ${msg}`); }
16
+ // ── Detect what's installed ──────────────────────────────────────────────────
17
+ function hasClaude() { return !!which("claude"); }
18
+ function hasCodex() { return !!which("codex"); }
19
+ function hasGemini() { return !!which("gemini"); }
20
+ // ── Install for Claude Code ─────────────────────────────────────────────────
21
+ function installClaude(bin) {
22
+ if (!hasClaude()) {
23
+ log("–", "Claude Code not found, skipping");
24
+ return false;
25
+ }
16
26
  try {
17
- execSync(`claude mcp add --transport stdio --scope user open-terminal -- ${which(TERMINAL_BIN) ?? "npx"} ${which(TERMINAL_BIN) ? "mcp serve" : "@hasna/terminal mcp serve"}`, { stdio: "inherit" });
18
- console.log("✓ Installed open-terminal MCP server for Claude Code");
27
+ execSync(`claude mcp add --transport stdio --scope user terminal -- ${bin} mcp serve`, { stdio: ["pipe", "pipe", "pipe"] });
28
+ log("✓", "Claude Code");
19
29
  return true;
20
30
  }
21
- catch (e) {
22
- console.error("Failed to install for Claude Code:", e);
23
- return false;
31
+ catch {
32
+ // May already exist
33
+ try {
34
+ execSync(`claude mcp remove terminal -s user`, { stdio: ["pipe", "pipe", "pipe"] });
35
+ execSync(`claude mcp add --transport stdio --scope user terminal -- ${bin} mcp serve`, { stdio: ["pipe", "pipe", "pipe"] });
36
+ log("✓", "Claude Code (updated)");
37
+ return true;
38
+ }
39
+ catch (e) {
40
+ log("✗", `Claude Code — ${e}`);
41
+ return false;
42
+ }
24
43
  }
25
44
  }
26
- export function installCodex() {
27
- const configPath = join(homedir(), ".codex", "config.toml");
45
+ // ── Install for Codex ───────────────────────────────────────────────────────
46
+ function installCodex(bin) {
47
+ if (!hasCodex()) {
48
+ log("–", "Codex not found, skipping");
49
+ return false;
50
+ }
51
+ const dir = join(homedir(), ".codex");
52
+ const configPath = join(dir, "config.toml");
28
53
  try {
54
+ if (!existsSync(dir))
55
+ mkdirSync(dir, { recursive: true });
29
56
  let content = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
30
- if (content.includes("[mcp_servers.open-terminal]")) {
31
- console.log("✓ open-terminal already configured for Codex");
32
- return true;
33
- }
34
- const bin = which(TERMINAL_BIN) ?? "npx @hasna/terminal";
35
- content += `\n[mcp_servers.open-terminal]\ncommand = "${bin}"\nargs = ["mcp", "serve"]\n`;
57
+ // Remove old entry if exists
58
+ content = content.replace(/\n?\[mcp_servers\.terminal\][^\[]*/g, "");
59
+ content += `\n[mcp_servers.terminal]\ncommand = "${bin}"\nargs = ["mcp", "serve"]\n`;
36
60
  writeFileSync(configPath, content);
37
- console.log("✓ Installed open-terminal MCP server for Codex");
61
+ log("✓", "Codex");
38
62
  return true;
39
63
  }
40
64
  catch (e) {
41
- console.error("Failed to install for Codex:", e);
65
+ log("✗", `Codex ${e}`);
42
66
  return false;
43
67
  }
44
68
  }
45
- export function installGemini() {
46
- const configPath = join(homedir(), ".gemini", "settings.json");
69
+ // ── Install for Gemini ──────────────────────────────────────────────────────
70
+ function installGemini(bin) {
71
+ if (!hasGemini()) {
72
+ log("–", "Gemini CLI not found, skipping");
73
+ return false;
74
+ }
75
+ const dir = join(homedir(), ".gemini");
76
+ const configPath = join(dir, "settings.json");
47
77
  try {
78
+ if (!existsSync(dir))
79
+ mkdirSync(dir, { recursive: true });
48
80
  let config = {};
49
81
  if (existsSync(configPath)) {
50
- config = JSON.parse(readFileSync(configPath, "utf8"));
82
+ try {
83
+ config = JSON.parse(readFileSync(configPath, "utf8"));
84
+ }
85
+ catch { }
51
86
  }
52
87
  if (!config.mcpServers)
53
88
  config.mcpServers = {};
54
- const bin = which(TERMINAL_BIN) ?? "npx";
55
- const args = which(TERMINAL_BIN) ? ["mcp", "serve"] : ["@hasna/terminal", "mcp", "serve"];
56
- config.mcpServers["open-terminal"] = { command: bin, args };
89
+ config.mcpServers["terminal"] = { command: bin, args: ["mcp", "serve"] };
57
90
  writeFileSync(configPath, JSON.stringify(config, null, 2));
58
- console.log("✓ Installed open-terminal MCP server for Gemini");
91
+ log("✓", "Gemini CLI");
59
92
  return true;
60
93
  }
61
94
  catch (e) {
62
- console.error("Failed to install for Gemini:", e);
95
+ log("✗", `Gemini ${e}`);
63
96
  return false;
64
97
  }
65
98
  }
66
- export function installAll() {
67
- installClaude();
68
- installCodex();
69
- installGemini();
99
+ // ── Uninstall ───────────────────────────────────────────────────────────────
100
+ function uninstallClaude() {
101
+ if (!hasClaude())
102
+ return false;
103
+ try {
104
+ execSync(`claude mcp remove terminal -s user`, { stdio: ["pipe", "pipe", "pipe"] });
105
+ log("✓", "Removed from Claude Code");
106
+ return true;
107
+ }
108
+ catch {
109
+ return false;
110
+ }
70
111
  }
71
- export function handleMcpInstall(args) {
112
+ function uninstallCodex() {
113
+ const configPath = join(homedir(), ".codex", "config.toml");
114
+ if (!existsSync(configPath))
115
+ return false;
116
+ try {
117
+ let content = readFileSync(configPath, "utf8");
118
+ if (!content.includes("terminal"))
119
+ return false;
120
+ content = content.replace(/\n?\[mcp_servers\.terminal\][^\[]*/g, "");
121
+ writeFileSync(configPath, content);
122
+ log("✓", "Removed from Codex");
123
+ return true;
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ }
129
+ function uninstallGemini() {
130
+ const configPath = join(homedir(), ".gemini", "settings.json");
131
+ if (!existsSync(configPath))
132
+ return false;
133
+ try {
134
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
135
+ if (!config.mcpServers?.["terminal"])
136
+ return false;
137
+ delete config.mcpServers["terminal"];
138
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
139
+ log("✓", "Removed from Gemini CLI");
140
+ return true;
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ }
146
+ // ── Main install handler ────────────────────────────────────────────────────
147
+ export function handleInstall(args) {
72
148
  const flags = new Set(args);
73
- if (flags.has("--all")) {
74
- installAll();
149
+ // Uninstall
150
+ if (flags.has("uninstall") || flags.has("--uninstall")) {
151
+ console.log("\n Removing terminal MCP server...\n");
152
+ uninstallClaude();
153
+ uninstallCodex();
154
+ uninstallGemini();
155
+ console.log("\n Done. Restart your agents to apply.\n");
75
156
  return;
76
157
  }
77
- if (flags.has("--claude")) {
78
- installClaude();
158
+ // Targeted install
159
+ if (flags.has("--claude") || flags.has("--codex") || flags.has("--gemini")) {
160
+ const bin = which("terminal") ?? which("t") ?? "npx @hasna/terminal";
161
+ console.log("");
162
+ if (flags.has("--claude"))
163
+ installClaude(bin);
164
+ if (flags.has("--codex"))
165
+ installCodex(bin);
166
+ if (flags.has("--gemini"))
167
+ installGemini(bin);
168
+ console.log("");
79
169
  return;
80
170
  }
81
- if (flags.has("--codex")) {
82
- installCodex();
83
- return;
171
+ // ── Default: install everything ─────────────────────────────────────────
172
+ const bin = which("terminal") ?? which("t") ?? "npx @hasna/terminal";
173
+ console.log(`\n terminal — setting up MCP...\n`);
174
+ let count = 0;
175
+ if (installClaude(bin))
176
+ count++;
177
+ if (installCodex(bin))
178
+ count++;
179
+ if (installGemini(bin))
180
+ count++;
181
+ if (count === 0) {
182
+ console.log(`\n No agents found. Install Claude Code, Codex, or Gemini CLI first.\n`);
84
183
  }
85
- if (flags.has("--gemini")) {
86
- installGemini();
87
- return;
184
+ else {
185
+ console.log(`\n ${count} agent${count > 1 ? "s" : ""} ready. Restart to apply.\n`);
88
186
  }
89
- console.log("Usage: t mcp install [--claude|--codex|--gemini|--all]");
90
- console.log("");
91
- console.log("Install open-terminal as an MCP server for AI coding agents.");
92
- console.log("");
93
- console.log("Options:");
94
- console.log(" --claude Install for Claude Code");
95
- console.log(" --codex Install for OpenAI Codex");
96
- console.log(" --gemini Install for Gemini CLI");
97
- console.log(" --all Install for all agents");
98
187
  }
188
+ // Re-export individual installers for programmatic use
189
+ export { installClaude, installCodex, installGemini, uninstallClaude, uninstallCodex, uninstallGemini };
@@ -1,4 +1,4 @@
1
- // MCP Server for open-terminal — exposes terminal capabilities to AI agents
1
+ // MCP Server for terminal — exposes terminal capabilities to AI agents
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
@@ -56,7 +56,7 @@ function exec(command, cwd, timeout) {
56
56
  // ── server ───────────────────────────────────────────────────────────────────
57
57
  export function createServer() {
58
58
  const server = new McpServer({
59
- name: "open-terminal",
59
+ name: "terminal",
60
60
  version: "0.2.0",
61
61
  });
62
62
  // ── execute: run a command, return structured result ──────────────────────
@@ -202,10 +202,10 @@ export function createServer() {
202
202
  };
203
203
  });
204
204
  // ── status: show server info ──────────────────────────────────────────────
205
- server.tool("status", "Get open-terminal server status, capabilities, and available parsers.", async () => {
205
+ server.tool("status", "Get terminal server status, capabilities, and available parsers.", async () => {
206
206
  return {
207
207
  content: [{ type: "text", text: JSON.stringify({
208
- name: "open-terminal", version: "0.3.0", cwd: process.cwd(),
208
+ name: "terminal", version: "3.3.0", cwd: process.cwd(),
209
209
  features: ["ai-output-processing", "token-compression", "noise-filtering", "diff-caching", "lazy-execution", "progressive-disclosure"],
210
210
  }) }],
211
211
  };
@@ -541,5 +541,5 @@ export async function startMcpServer() {
541
541
  const server = createServer();
542
542
  const transport = new StdioServerTransport();
543
543
  await server.connect(transport);
544
- console.error("open-terminal MCP server running on stdio");
544
+ console.error("terminal MCP server running on stdio");
545
545
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/terminal",
3
- "version": "3.2.1",
3
+ "version": "3.3.1",
4
4
  "description": "Smart terminal wrapper for AI agents and humans — structured output, token compression, MCP server, natural language",
5
5
  "type": "module",
6
6
  "files": [
package/src/cli.tsx CHANGED
@@ -21,6 +21,13 @@ EXAMPLES:
21
21
  terminal "kill port 3000"
22
22
  terminal "how many lines of code"
23
23
 
24
+ SETUP:
25
+ install Set up MCP server for all AI agents (Claude, Codex, Gemini)
26
+ install --claude Set up for Claude Code only
27
+ install --codex Set up for Codex only
28
+ install --gemini Set up for Gemini CLI only
29
+ uninstall Remove from all agents
30
+
24
31
  SUBCOMMANDS:
25
32
  repo Git repo state (branch + status + log)
26
33
  symbols <file> File outline (functions, classes, exports)
@@ -29,8 +36,7 @@ SUBCOMMANDS:
29
36
  sessions [stats|<id>] Session history and analytics
30
37
  recipe add|list|run|delete Reusable command recipes
31
38
  collection create|list Recipe collections
32
- mcp serve Start MCP server for AI agents
33
- mcp install --claude|--codex Install MCP server
39
+ mcp serve Start MCP server (called by agents, not you)
34
40
  discover [--days=N] [--json] Scan Claude sessions, show token savings potential
35
41
  snapshot Terminal state as JSON
36
42
  --help Show this help
@@ -63,6 +69,20 @@ if (args[0] === "--version" || args[0] === "-v") {
63
69
  process.exit(0);
64
70
  }
65
71
 
72
+ // ── Install / Uninstall ──────────────────────────────────────────────────────
73
+
74
+ if (args[0] === "install") {
75
+ const { handleInstall } = await import("./mcp/install.js");
76
+ handleInstall(args.slice(1));
77
+ process.exit(0);
78
+ }
79
+
80
+ if (args[0] === "uninstall") {
81
+ const { handleInstall } = await import("./mcp/install.js");
82
+ handleInstall(["uninstall"]);
83
+ process.exit(0);
84
+ }
85
+
66
86
  // ── MCP commands ─────────────────────────────────────────────────────────────
67
87
 
68
88
  if (args[0] === "mcp") {
@@ -73,10 +93,11 @@ if (args[0] === "mcp") {
73
93
  process.exit(1);
74
94
  });
75
95
  } else if (args[1] === "install") {
76
- const { handleMcpInstall } = await import("./mcp/install.js");
77
- handleMcpInstall(args.slice(2));
96
+ // Legacy: `terminal mcp install` still works
97
+ const { handleInstall } = await import("./mcp/install.js");
98
+ handleInstall(args.slice(2));
78
99
  } else {
79
- console.log("Usage: t mcp [serve|install]");
100
+ console.log("Usage: terminal mcp serve");
80
101
  }
81
102
  }
82
103
 
@@ -1,94 +1,165 @@
1
- // MCP installation helper register open-terminal as MCP server for various agents
1
+ // MCP installation — one command to rule them all
2
+ // `npx @hasna/terminal install` → installs globally + configures all AI agents
2
3
 
3
4
  import { execSync } from "child_process";
4
- import { existsSync, readFileSync, writeFileSync } from "fs";
5
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
5
6
  import { homedir } from "os";
6
7
  import { join } from "path";
7
8
 
8
- const TERMINAL_BIN = "terminal"; // the CLI binary name
9
-
10
9
  function which(cmd: string): string | null {
11
- try {
12
- return execSync(`which ${cmd}`, { encoding: "utf8" }).trim();
13
- } catch {
14
- return null;
15
- }
10
+ try { return execSync(`which ${cmd}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim(); } catch { return null; }
16
11
  }
17
12
 
18
- export function installClaude(): boolean {
13
+ function log(icon: string, msg: string) { console.log(` ${icon} ${msg}`); }
14
+
15
+ // ── Detect what's installed ──────────────────────────────────────────────────
16
+
17
+ function hasClaude(): boolean { return !!which("claude"); }
18
+ function hasCodex(): boolean { return !!which("codex"); }
19
+ function hasGemini(): boolean { return !!which("gemini"); }
20
+
21
+ // ── Install for Claude Code ─────────────────────────────────────────────────
22
+
23
+ function installClaude(bin: string): boolean {
24
+ if (!hasClaude()) { log("–", "Claude Code not found, skipping"); return false; }
19
25
  try {
20
- execSync(
21
- `claude mcp add --transport stdio --scope user open-terminal -- ${which(TERMINAL_BIN) ?? "npx"} ${which(TERMINAL_BIN) ? "mcp serve" : "@hasna/terminal mcp serve"}`,
22
- { stdio: "inherit" }
23
- );
24
- console.log("✓ Installed open-terminal MCP server for Claude Code");
26
+ execSync(`claude mcp add --transport stdio --scope user terminal -- ${bin} mcp serve`, { stdio: ["pipe", "pipe", "pipe"] });
27
+ log("", "Claude Code");
25
28
  return true;
26
- } catch (e) {
27
- console.error("Failed to install for Claude Code:", e);
28
- return false;
29
+ } catch {
30
+ // May already exist
31
+ try {
32
+ execSync(`claude mcp remove terminal -s user`, { stdio: ["pipe", "pipe", "pipe"] });
33
+ execSync(`claude mcp add --transport stdio --scope user terminal -- ${bin} mcp serve`, { stdio: ["pipe", "pipe", "pipe"] });
34
+ log("✓", "Claude Code (updated)");
35
+ return true;
36
+ } catch (e) {
37
+ log("✗", `Claude Code — ${e}`);
38
+ return false;
39
+ }
29
40
  }
30
41
  }
31
42
 
32
- export function installCodex(): boolean {
33
- const configPath = join(homedir(), ".codex", "config.toml");
43
+ // ── Install for Codex ───────────────────────────────────────────────────────
44
+
45
+ function installCodex(bin: string): boolean {
46
+ if (!hasCodex()) { log("–", "Codex not found, skipping"); return false; }
47
+ const dir = join(homedir(), ".codex");
48
+ const configPath = join(dir, "config.toml");
34
49
  try {
50
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
35
51
  let content = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
36
- if (content.includes("[mcp_servers.open-terminal]")) {
37
- console.log("✓ open-terminal already configured for Codex");
38
- return true;
39
- }
40
- const bin = which(TERMINAL_BIN) ?? "npx @hasna/terminal";
41
- content += `\n[mcp_servers.open-terminal]\ncommand = "${bin}"\nargs = ["mcp", "serve"]\n`;
52
+ // Remove old entry if exists
53
+ content = content.replace(/\n?\[mcp_servers\.terminal\][^\[]*/g, "");
54
+ content += `\n[mcp_servers.terminal]\ncommand = "${bin}"\nargs = ["mcp", "serve"]\n`;
42
55
  writeFileSync(configPath, content);
43
- console.log("✓ Installed open-terminal MCP server for Codex");
56
+ log("✓", "Codex");
44
57
  return true;
45
58
  } catch (e) {
46
- console.error("Failed to install for Codex:", e);
59
+ log("✗", `Codex ${e}`);
47
60
  return false;
48
61
  }
49
62
  }
50
63
 
51
- export function installGemini(): boolean {
52
- const configPath = join(homedir(), ".gemini", "settings.json");
64
+ // ── Install for Gemini ──────────────────────────────────────────────────────
65
+
66
+ function installGemini(bin: string): boolean {
67
+ if (!hasGemini()) { log("–", "Gemini CLI not found, skipping"); return false; }
68
+ const dir = join(homedir(), ".gemini");
69
+ const configPath = join(dir, "settings.json");
53
70
  try {
71
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
54
72
  let config: any = {};
55
73
  if (existsSync(configPath)) {
56
- config = JSON.parse(readFileSync(configPath, "utf8"));
74
+ try { config = JSON.parse(readFileSync(configPath, "utf8")); } catch {}
57
75
  }
58
76
  if (!config.mcpServers) config.mcpServers = {};
59
- const bin = which(TERMINAL_BIN) ?? "npx";
60
- const args = which(TERMINAL_BIN) ? ["mcp", "serve"] : ["@hasna/terminal", "mcp", "serve"];
61
- config.mcpServers["open-terminal"] = { command: bin, args };
77
+ config.mcpServers["terminal"] = { command: bin, args: ["mcp", "serve"] };
62
78
  writeFileSync(configPath, JSON.stringify(config, null, 2));
63
- console.log("✓ Installed open-terminal MCP server for Gemini");
79
+ log("✓", "Gemini CLI");
64
80
  return true;
65
81
  } catch (e) {
66
- console.error("Failed to install for Gemini:", e);
82
+ log("✗", `Gemini ${e}`);
67
83
  return false;
68
84
  }
69
85
  }
70
86
 
71
- export function installAll(): void {
72
- installClaude();
73
- installCodex();
74
- installGemini();
87
+ // ── Uninstall ───────────────────────────────────────────────────────────────
88
+
89
+ function uninstallClaude(): boolean {
90
+ if (!hasClaude()) return false;
91
+ try { execSync(`claude mcp remove terminal -s user`, { stdio: ["pipe", "pipe", "pipe"] }); log("✓", "Removed from Claude Code"); return true; } catch { return false; }
75
92
  }
76
93
 
77
- export function handleMcpInstall(args: string[]): void {
94
+ function uninstallCodex(): boolean {
95
+ const configPath = join(homedir(), ".codex", "config.toml");
96
+ if (!existsSync(configPath)) return false;
97
+ try {
98
+ let content = readFileSync(configPath, "utf8");
99
+ if (!content.includes("terminal")) return false;
100
+ content = content.replace(/\n?\[mcp_servers\.terminal\][^\[]*/g, "");
101
+ writeFileSync(configPath, content);
102
+ log("✓", "Removed from Codex");
103
+ return true;
104
+ } catch { return false; }
105
+ }
106
+
107
+ function uninstallGemini(): boolean {
108
+ const configPath = join(homedir(), ".gemini", "settings.json");
109
+ if (!existsSync(configPath)) return false;
110
+ try {
111
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
112
+ if (!config.mcpServers?.["terminal"]) return false;
113
+ delete config.mcpServers["terminal"];
114
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
115
+ log("✓", "Removed from Gemini CLI");
116
+ return true;
117
+ } catch { return false; }
118
+ }
119
+
120
+ // ── Main install handler ────────────────────────────────────────────────────
121
+
122
+ export function handleInstall(args: string[]): void {
78
123
  const flags = new Set(args);
79
124
 
80
- if (flags.has("--all")) { installAll(); return; }
81
- if (flags.has("--claude")) { installClaude(); return; }
82
- if (flags.has("--codex")) { installCodex(); return; }
83
- if (flags.has("--gemini")) { installGemini(); return; }
84
-
85
- console.log("Usage: t mcp install [--claude|--codex|--gemini|--all]");
86
- console.log("");
87
- console.log("Install open-terminal as an MCP server for AI coding agents.");
88
- console.log("");
89
- console.log("Options:");
90
- console.log(" --claude Install for Claude Code");
91
- console.log(" --codex Install for OpenAI Codex");
92
- console.log(" --gemini Install for Gemini CLI");
93
- console.log(" --all Install for all agents");
125
+ // Uninstall
126
+ if (flags.has("uninstall") || flags.has("--uninstall")) {
127
+ console.log("\n Removing terminal MCP server...\n");
128
+ uninstallClaude();
129
+ uninstallCodex();
130
+ uninstallGemini();
131
+ console.log("\n Done. Restart your agents to apply.\n");
132
+ return;
133
+ }
134
+
135
+ // Targeted install
136
+ if (flags.has("--claude") || flags.has("--codex") || flags.has("--gemini")) {
137
+ const bin = which("terminal") ?? which("t") ?? "npx @hasna/terminal";
138
+ console.log("");
139
+ if (flags.has("--claude")) installClaude(bin);
140
+ if (flags.has("--codex")) installCodex(bin);
141
+ if (flags.has("--gemini")) installGemini(bin);
142
+ console.log("");
143
+ return;
144
+ }
145
+
146
+ // ── Default: install everything ─────────────────────────────────────────
147
+
148
+ const bin = which("terminal") ?? which("t") ?? "npx @hasna/terminal";
149
+
150
+ console.log(`\n terminal — setting up MCP...\n`);
151
+
152
+ let count = 0;
153
+ if (installClaude(bin)) count++;
154
+ if (installCodex(bin)) count++;
155
+ if (installGemini(bin)) count++;
156
+
157
+ if (count === 0) {
158
+ console.log(`\n No agents found. Install Claude Code, Codex, or Gemini CLI first.\n`);
159
+ } else {
160
+ console.log(`\n ${count} agent${count > 1 ? "s" : ""} ready. Restart to apply.\n`);
161
+ }
94
162
  }
163
+
164
+ // Re-export individual installers for programmatic use
165
+ export { installClaude, installCodex, installGemini, uninstallClaude, uninstallCodex, uninstallGemini };
package/src/mcp/server.ts CHANGED
@@ -1,4 +1,4 @@
1
- // MCP Server for open-terminal — exposes terminal capabilities to AI agents
1
+ // MCP Server for terminal — exposes terminal capabilities to AI agents
2
2
 
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -61,7 +61,7 @@ function exec(command: string, cwd?: string, timeout?: number): Promise<{ exitCo
61
61
 
62
62
  export function createServer(): McpServer {
63
63
  const server = new McpServer({
64
- name: "open-terminal",
64
+ name: "terminal",
65
65
  version: "0.2.0",
66
66
  });
67
67
 
@@ -251,11 +251,11 @@ export function createServer(): McpServer {
251
251
 
252
252
  server.tool(
253
253
  "status",
254
- "Get open-terminal server status, capabilities, and available parsers.",
254
+ "Get terminal server status, capabilities, and available parsers.",
255
255
  async () => {
256
256
  return {
257
257
  content: [{ type: "text" as const, text: JSON.stringify({
258
- name: "open-terminal", version: "0.3.0", cwd: process.cwd(),
258
+ name: "terminal", version: "3.3.0", cwd: process.cwd(),
259
259
  features: ["ai-output-processing", "token-compression", "noise-filtering", "diff-caching", "lazy-execution", "progressive-disclosure"],
260
260
  }) }],
261
261
  };
@@ -762,5 +762,5 @@ export async function startMcpServer(): Promise<void> {
762
762
  const server = createServer();
763
763
  const transport = new StdioServerTransport();
764
764
  await server.connect(transport);
765
- console.error("open-terminal MCP server running on stdio");
765
+ console.error("terminal MCP server running on stdio");
766
766
  }