@hasna/terminal 3.2.1 → 3.3.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/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,216 @@
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 open-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 open-terminal -s user`, { stdio: ["pipe", "pipe", "pipe"] });
35
+ execSync(`claude mcp add --transport stdio --scope user open-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";
57
+ // Remove old entry if exists
58
+ content = content.replace(/\n?\[mcp_servers\.open-terminal\][^\[]*/g, "");
35
59
  content += `\n[mcp_servers.open-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["open-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 open-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("open-terminal"))
119
+ return false;
120
+ content = content.replace(/\n?\[mcp_servers\.open-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?.["open-terminal"])
136
+ return false;
137
+ delete config.mcpServers["open-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 open-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(`
174
+ ┌─────────────────────────────────────┐
175
+ │ open-terminal │
176
+ │ Smart terminal for AI agents │
177
+ └─────────────────────────────────────┘
178
+
179
+ Setting up MCP server for all agents...
180
+ `);
181
+ let count = 0;
182
+ if (installClaude(bin))
183
+ count++;
184
+ if (installCodex(bin))
185
+ count++;
186
+ if (installGemini(bin))
187
+ count++;
188
+ if (count === 0) {
189
+ console.log(`
190
+ No AI agents found. Install one first:
191
+
192
+ npm i -g @anthropic-ai/claude-code # Claude Code
193
+ npm i -g @openai/codex # Codex
194
+ npm i -g @anthropic-ai/gemini-cli # Gemini CLI
195
+
196
+ Then run: terminal install
197
+ `);
84
198
  }
85
- if (flags.has("--gemini")) {
86
- installGemini();
87
- return;
199
+ else {
200
+ console.log(`
201
+ Done. ${count} agent${count > 1 ? "s" : ""} configured.
202
+ Restart your agent to start using open-terminal.
203
+
204
+ Your AI agent now has these tools:
205
+ execute_smart Run any command, get AI-summarized output
206
+ execute_diff Run command, see only what changed
207
+ search_content Smart grep with file grouping
208
+ search_files Find files by pattern
209
+ read_symbol Read a function by name (not whole file)
210
+ boot Full project context in one call
211
+ repo_state Git status + log + diff in one call
212
+ `);
88
213
  }
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
214
  }
215
+ // Re-export individual installers for programmatic use
216
+ export { installClaude, installCodex, installGemini, uninstallClaude, uninstallCodex, uninstallGemini };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/terminal",
3
- "version": "3.2.1",
3
+ "version": "3.3.0",
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,192 @@
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 open-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 open-terminal -s user`, { stdio: ["pipe", "pipe", "pipe"] });
33
+ execSync(`claude mcp add --transport stdio --scope user open-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";
52
+ // Remove old entry if exists
53
+ content = content.replace(/\n?\[mcp_servers\.open-terminal\][^\[]*/g, "");
41
54
  content += `\n[mcp_servers.open-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["open-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 open-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("open-terminal")) return false;
100
+ content = content.replace(/\n?\[mcp_servers\.open-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?.["open-terminal"]) return false;
113
+ delete config.mcpServers["open-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 open-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(`
151
+ ┌─────────────────────────────────────┐
152
+ │ open-terminal │
153
+ │ Smart terminal for AI agents │
154
+ └─────────────────────────────────────┘
155
+
156
+ Setting up MCP server for all agents...
157
+ `);
158
+
159
+ let count = 0;
160
+ if (installClaude(bin)) count++;
161
+ if (installCodex(bin)) count++;
162
+ if (installGemini(bin)) count++;
163
+
164
+ if (count === 0) {
165
+ console.log(`
166
+ No AI agents found. Install one first:
167
+
168
+ npm i -g @anthropic-ai/claude-code # Claude Code
169
+ npm i -g @openai/codex # Codex
170
+ npm i -g @anthropic-ai/gemini-cli # Gemini CLI
171
+
172
+ Then run: terminal install
173
+ `);
174
+ } else {
175
+ console.log(`
176
+ Done. ${count} agent${count > 1 ? "s" : ""} configured.
177
+ Restart your agent to start using open-terminal.
178
+
179
+ Your AI agent now has these tools:
180
+ execute_smart Run any command, get AI-summarized output
181
+ execute_diff Run command, see only what changed
182
+ search_content Smart grep with file grouping
183
+ search_files Find files by pattern
184
+ read_symbol Read a function by name (not whole file)
185
+ boot Full project context in one call
186
+ repo_state Git status + log + diff in one call
187
+ `);
188
+ }
94
189
  }
190
+
191
+ // Re-export individual installers for programmatic use
192
+ export { installClaude, installCodex, installGemini, uninstallClaude, uninstallCodex, uninstallGemini };