@hasna/terminal 3.3.0 → 3.3.2
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/mcp/install.js +15 -42
- package/dist/mcp/server.js +9 -9
- package/dist/noise-filter.js +13 -5
- package/package.json +1 -1
- package/src/mcp/install.ts +15 -42
- package/src/mcp/server.ts +9 -9
- package/src/noise-filter.ts +13 -5
package/dist/mcp/install.js
CHANGED
|
@@ -24,15 +24,15 @@ function installClaude(bin) {
|
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
26
|
try {
|
|
27
|
-
execSync(`claude mcp add --transport stdio --scope user
|
|
27
|
+
execSync(`claude mcp add --transport stdio --scope user terminal -- ${bin} mcp serve`, { stdio: ["pipe", "pipe", "pipe"] });
|
|
28
28
|
log("✓", "Claude Code");
|
|
29
29
|
return true;
|
|
30
30
|
}
|
|
31
31
|
catch {
|
|
32
32
|
// May already exist
|
|
33
33
|
try {
|
|
34
|
-
execSync(`claude mcp remove
|
|
35
|
-
execSync(`claude mcp add --transport stdio --scope user
|
|
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
36
|
log("✓", "Claude Code (updated)");
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
@@ -55,8 +55,8 @@ function installCodex(bin) {
|
|
|
55
55
|
mkdirSync(dir, { recursive: true });
|
|
56
56
|
let content = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
57
57
|
// Remove old entry if exists
|
|
58
|
-
content = content.replace(/\n?\[mcp_servers\.
|
|
59
|
-
content += `\n[mcp_servers.
|
|
58
|
+
content = content.replace(/\n?\[mcp_servers\.terminal\][^\[]*/g, "");
|
|
59
|
+
content += `\n[mcp_servers.terminal]\ncommand = "${bin}"\nargs = ["mcp", "serve"]\n`;
|
|
60
60
|
writeFileSync(configPath, content);
|
|
61
61
|
log("✓", "Codex");
|
|
62
62
|
return true;
|
|
@@ -86,7 +86,7 @@ function installGemini(bin) {
|
|
|
86
86
|
}
|
|
87
87
|
if (!config.mcpServers)
|
|
88
88
|
config.mcpServers = {};
|
|
89
|
-
config.mcpServers["
|
|
89
|
+
config.mcpServers["terminal"] = { command: bin, args: ["mcp", "serve"] };
|
|
90
90
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
91
91
|
log("✓", "Gemini CLI");
|
|
92
92
|
return true;
|
|
@@ -101,7 +101,7 @@ function uninstallClaude() {
|
|
|
101
101
|
if (!hasClaude())
|
|
102
102
|
return false;
|
|
103
103
|
try {
|
|
104
|
-
execSync(`claude mcp remove
|
|
104
|
+
execSync(`claude mcp remove terminal -s user`, { stdio: ["pipe", "pipe", "pipe"] });
|
|
105
105
|
log("✓", "Removed from Claude Code");
|
|
106
106
|
return true;
|
|
107
107
|
}
|
|
@@ -115,9 +115,9 @@ function uninstallCodex() {
|
|
|
115
115
|
return false;
|
|
116
116
|
try {
|
|
117
117
|
let content = readFileSync(configPath, "utf8");
|
|
118
|
-
if (!content.includes("
|
|
118
|
+
if (!content.includes("terminal"))
|
|
119
119
|
return false;
|
|
120
|
-
content = content.replace(/\n?\[mcp_servers\.
|
|
120
|
+
content = content.replace(/\n?\[mcp_servers\.terminal\][^\[]*/g, "");
|
|
121
121
|
writeFileSync(configPath, content);
|
|
122
122
|
log("✓", "Removed from Codex");
|
|
123
123
|
return true;
|
|
@@ -132,9 +132,9 @@ function uninstallGemini() {
|
|
|
132
132
|
return false;
|
|
133
133
|
try {
|
|
134
134
|
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
135
|
-
if (!config.mcpServers?.["
|
|
135
|
+
if (!config.mcpServers?.["terminal"])
|
|
136
136
|
return false;
|
|
137
|
-
delete config.mcpServers["
|
|
137
|
+
delete config.mcpServers["terminal"];
|
|
138
138
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
139
139
|
log("✓", "Removed from Gemini CLI");
|
|
140
140
|
return true;
|
|
@@ -148,7 +148,7 @@ export function handleInstall(args) {
|
|
|
148
148
|
const flags = new Set(args);
|
|
149
149
|
// Uninstall
|
|
150
150
|
if (flags.has("uninstall") || flags.has("--uninstall")) {
|
|
151
|
-
console.log("\n Removing
|
|
151
|
+
console.log("\n Removing terminal MCP server...\n");
|
|
152
152
|
uninstallClaude();
|
|
153
153
|
uninstallCodex();
|
|
154
154
|
uninstallGemini();
|
|
@@ -170,14 +170,7 @@ export function handleInstall(args) {
|
|
|
170
170
|
}
|
|
171
171
|
// ── Default: install everything ─────────────────────────────────────────
|
|
172
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
|
-
`);
|
|
173
|
+
console.log(`\n terminal — setting up MCP...\n`);
|
|
181
174
|
let count = 0;
|
|
182
175
|
if (installClaude(bin))
|
|
183
176
|
count++;
|
|
@@ -186,30 +179,10 @@ export function handleInstall(args) {
|
|
|
186
179
|
if (installGemini(bin))
|
|
187
180
|
count++;
|
|
188
181
|
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
|
-
`);
|
|
182
|
+
console.log(`\n No agents found. Install Claude Code, Codex, or Gemini CLI first.\n`);
|
|
198
183
|
}
|
|
199
184
|
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
|
-
`);
|
|
185
|
+
console.log(`\n ${count} agent${count > 1 ? "s" : ""} ready. Restart to apply.\n`);
|
|
213
186
|
}
|
|
214
187
|
}
|
|
215
188
|
// Re-export individual installers for programmatic use
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// MCP Server for
|
|
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";
|
|
@@ -21,9 +21,9 @@ import { shouldBeLazy, toLazy } from "../lazy-executor.js";
|
|
|
21
21
|
import { getEconomyStats, recordSaving } from "../economy.js";
|
|
22
22
|
import { captureSnapshot } from "../snapshots.js";
|
|
23
23
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
24
|
-
function exec(command, cwd, timeout) {
|
|
25
|
-
//
|
|
26
|
-
const rw = rewriteCommand(command);
|
|
24
|
+
function exec(command, cwd, timeout, allowRewrite = false) {
|
|
25
|
+
// Only rewrite when explicitly allowed (execute_smart, not raw execute)
|
|
26
|
+
const rw = allowRewrite ? rewriteCommand(command) : { changed: false, rewritten: command };
|
|
27
27
|
const actualCommand = rw.changed ? rw.rewritten : command;
|
|
28
28
|
return new Promise((resolve) => {
|
|
29
29
|
const start = Date.now();
|
|
@@ -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: "
|
|
59
|
+
name: "terminal",
|
|
60
60
|
version: "0.2.0",
|
|
61
61
|
});
|
|
62
62
|
// ── execute: run a command, return structured result ──────────────────────
|
|
@@ -135,7 +135,7 @@ export function createServer() {
|
|
|
135
135
|
cwd: z.string().optional().describe("Working directory"),
|
|
136
136
|
timeout: z.number().optional().describe("Timeout in ms (default: 30000)"),
|
|
137
137
|
}, async ({ command, cwd, timeout }) => {
|
|
138
|
-
const result = await exec(command, cwd, timeout ?? 30000);
|
|
138
|
+
const result = await exec(command, cwd, timeout ?? 30000, true); // allow rewrite for smart mode
|
|
139
139
|
const output = (result.stdout + result.stderr).trim();
|
|
140
140
|
const processed = await processOutput(command, output);
|
|
141
141
|
// Progressive disclosure: store full output, return summary + expand key
|
|
@@ -202,10 +202,10 @@ export function createServer() {
|
|
|
202
202
|
};
|
|
203
203
|
});
|
|
204
204
|
// ── status: show server info ──────────────────────────────────────────────
|
|
205
|
-
server.tool("status", "Get
|
|
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: "
|
|
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("
|
|
544
|
+
console.error("terminal MCP server running on stdio");
|
|
545
545
|
}
|
package/dist/noise-filter.js
CHANGED
|
@@ -33,15 +33,23 @@ const NOISE_PATTERNS = [
|
|
|
33
33
|
// Generic download/upload progress
|
|
34
34
|
/^\s*\d+(\.\d+)?\s*[KMG]?B\s*\/\s*\d+(\.\d+)?\s*[KMG]?B\b/,
|
|
35
35
|
];
|
|
36
|
-
// Sensitive env var patterns —
|
|
36
|
+
// Sensitive env var patterns — ONLY match actual env var assignments (export X=val, X=val at line start)
|
|
37
|
+
// NOT code lines like `const API_KEY = process.env.API_KEY` or `this.token = config.token`
|
|
37
38
|
const SENSITIVE_PATTERNS = [
|
|
38
|
-
|
|
39
|
-
/^(
|
|
39
|
+
// export KEY_NAME="value" or KEY_NAME=value (shell env vars only)
|
|
40
|
+
/^(export\s+[A-Z_]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)[A-Z_]*)=(.+)$/,
|
|
41
|
+
// Plain env assignment at start of line (no leading whitespace = not code)
|
|
42
|
+
/^([A-Z_]*(?:API_KEY|ACCESS_KEY|PRIVATE_KEY|CLIENT_SECRET|AUTH_TOKEN)[A-Z_]*)=(.+)$/,
|
|
40
43
|
];
|
|
41
|
-
/** Redact sensitive values in output (env vars,
|
|
44
|
+
/** Redact sensitive values in output (env vars only, not code) */
|
|
42
45
|
function redactSensitive(line) {
|
|
46
|
+
const trimmed = line.trim();
|
|
47
|
+
// Skip lines that look like code (have leading whitespace, semicolons, const/let/var, etc.)
|
|
48
|
+
if (/^\s*(const|let|var|this\.|private|public|protected|import|export\s+(default|const|let|function|class)|\/\/|\/\*|\*)/.test(line)) {
|
|
49
|
+
return line; // Code — never redact
|
|
50
|
+
}
|
|
43
51
|
for (const pattern of SENSITIVE_PATTERNS) {
|
|
44
|
-
const match =
|
|
52
|
+
const match = trimmed.match(pattern);
|
|
45
53
|
if (match) {
|
|
46
54
|
return `${match[1]}=[REDACTED]`;
|
|
47
55
|
}
|
package/package.json
CHANGED
package/src/mcp/install.ts
CHANGED
|
@@ -23,14 +23,14 @@ function hasGemini(): boolean { return !!which("gemini"); }
|
|
|
23
23
|
function installClaude(bin: string): boolean {
|
|
24
24
|
if (!hasClaude()) { log("–", "Claude Code not found, skipping"); return false; }
|
|
25
25
|
try {
|
|
26
|
-
execSync(`claude mcp add --transport stdio --scope user
|
|
26
|
+
execSync(`claude mcp add --transport stdio --scope user terminal -- ${bin} mcp serve`, { stdio: ["pipe", "pipe", "pipe"] });
|
|
27
27
|
log("✓", "Claude Code");
|
|
28
28
|
return true;
|
|
29
29
|
} catch {
|
|
30
30
|
// May already exist
|
|
31
31
|
try {
|
|
32
|
-
execSync(`claude mcp remove
|
|
33
|
-
execSync(`claude mcp add --transport stdio --scope user
|
|
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
34
|
log("✓", "Claude Code (updated)");
|
|
35
35
|
return true;
|
|
36
36
|
} catch (e) {
|
|
@@ -50,8 +50,8 @@ function installCodex(bin: string): boolean {
|
|
|
50
50
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
51
51
|
let content = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
52
52
|
// Remove old entry if exists
|
|
53
|
-
content = content.replace(/\n?\[mcp_servers\.
|
|
54
|
-
content += `\n[mcp_servers.
|
|
53
|
+
content = content.replace(/\n?\[mcp_servers\.terminal\][^\[]*/g, "");
|
|
54
|
+
content += `\n[mcp_servers.terminal]\ncommand = "${bin}"\nargs = ["mcp", "serve"]\n`;
|
|
55
55
|
writeFileSync(configPath, content);
|
|
56
56
|
log("✓", "Codex");
|
|
57
57
|
return true;
|
|
@@ -74,7 +74,7 @@ function installGemini(bin: string): boolean {
|
|
|
74
74
|
try { config = JSON.parse(readFileSync(configPath, "utf8")); } catch {}
|
|
75
75
|
}
|
|
76
76
|
if (!config.mcpServers) config.mcpServers = {};
|
|
77
|
-
config.mcpServers["
|
|
77
|
+
config.mcpServers["terminal"] = { command: bin, args: ["mcp", "serve"] };
|
|
78
78
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
79
79
|
log("✓", "Gemini CLI");
|
|
80
80
|
return true;
|
|
@@ -88,7 +88,7 @@ function installGemini(bin: string): boolean {
|
|
|
88
88
|
|
|
89
89
|
function uninstallClaude(): boolean {
|
|
90
90
|
if (!hasClaude()) return false;
|
|
91
|
-
try { execSync(`claude mcp remove
|
|
91
|
+
try { execSync(`claude mcp remove terminal -s user`, { stdio: ["pipe", "pipe", "pipe"] }); log("✓", "Removed from Claude Code"); return true; } catch { return false; }
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
function uninstallCodex(): boolean {
|
|
@@ -96,8 +96,8 @@ function uninstallCodex(): boolean {
|
|
|
96
96
|
if (!existsSync(configPath)) return false;
|
|
97
97
|
try {
|
|
98
98
|
let content = readFileSync(configPath, "utf8");
|
|
99
|
-
if (!content.includes("
|
|
100
|
-
content = content.replace(/\n?\[mcp_servers\.
|
|
99
|
+
if (!content.includes("terminal")) return false;
|
|
100
|
+
content = content.replace(/\n?\[mcp_servers\.terminal\][^\[]*/g, "");
|
|
101
101
|
writeFileSync(configPath, content);
|
|
102
102
|
log("✓", "Removed from Codex");
|
|
103
103
|
return true;
|
|
@@ -109,8 +109,8 @@ function uninstallGemini(): boolean {
|
|
|
109
109
|
if (!existsSync(configPath)) return false;
|
|
110
110
|
try {
|
|
111
111
|
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
112
|
-
if (!config.mcpServers?.["
|
|
113
|
-
delete config.mcpServers["
|
|
112
|
+
if (!config.mcpServers?.["terminal"]) return false;
|
|
113
|
+
delete config.mcpServers["terminal"];
|
|
114
114
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
115
115
|
log("✓", "Removed from Gemini CLI");
|
|
116
116
|
return true;
|
|
@@ -124,7 +124,7 @@ export function handleInstall(args: string[]): void {
|
|
|
124
124
|
|
|
125
125
|
// Uninstall
|
|
126
126
|
if (flags.has("uninstall") || flags.has("--uninstall")) {
|
|
127
|
-
console.log("\n Removing
|
|
127
|
+
console.log("\n Removing terminal MCP server...\n");
|
|
128
128
|
uninstallClaude();
|
|
129
129
|
uninstallCodex();
|
|
130
130
|
uninstallGemini();
|
|
@@ -147,14 +147,7 @@ export function handleInstall(args: string[]): void {
|
|
|
147
147
|
|
|
148
148
|
const bin = which("terminal") ?? which("t") ?? "npx @hasna/terminal";
|
|
149
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
|
-
`);
|
|
150
|
+
console.log(`\n terminal — setting up MCP...\n`);
|
|
158
151
|
|
|
159
152
|
let count = 0;
|
|
160
153
|
if (installClaude(bin)) count++;
|
|
@@ -162,29 +155,9 @@ export function handleInstall(args: string[]): void {
|
|
|
162
155
|
if (installGemini(bin)) count++;
|
|
163
156
|
|
|
164
157
|
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
|
-
`);
|
|
158
|
+
console.log(`\n No agents found. Install Claude Code, Codex, or Gemini CLI first.\n`);
|
|
174
159
|
} 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
|
-
`);
|
|
160
|
+
console.log(`\n ${count} agent${count > 1 ? "s" : ""} ready. Restart to apply.\n`);
|
|
188
161
|
}
|
|
189
162
|
}
|
|
190
163
|
|
package/src/mcp/server.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// MCP Server for
|
|
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";
|
|
@@ -24,9 +24,9 @@ import { captureSnapshot } from "../snapshots.js";
|
|
|
24
24
|
|
|
25
25
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
26
26
|
|
|
27
|
-
function exec(command: string, cwd?: string, timeout?: number): Promise<{ exitCode: number; stdout: string; stderr: string; duration: number; rewritten?: string }> {
|
|
28
|
-
//
|
|
29
|
-
const rw = rewriteCommand(command);
|
|
27
|
+
function exec(command: string, cwd?: string, timeout?: number, allowRewrite: boolean = false): Promise<{ exitCode: number; stdout: string; stderr: string; duration: number; rewritten?: string }> {
|
|
28
|
+
// Only rewrite when explicitly allowed (execute_smart, not raw execute)
|
|
29
|
+
const rw = allowRewrite ? rewriteCommand(command) : { changed: false, rewritten: command };
|
|
30
30
|
const actualCommand = rw.changed ? rw.rewritten : command;
|
|
31
31
|
return new Promise((resolve) => {
|
|
32
32
|
const start = Date.now();
|
|
@@ -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: "
|
|
64
|
+
name: "terminal",
|
|
65
65
|
version: "0.2.0",
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -156,7 +156,7 @@ export function createServer(): McpServer {
|
|
|
156
156
|
timeout: z.number().optional().describe("Timeout in ms (default: 30000)"),
|
|
157
157
|
},
|
|
158
158
|
async ({ command, cwd, timeout }) => {
|
|
159
|
-
const result = await exec(command, cwd, timeout ?? 30000);
|
|
159
|
+
const result = await exec(command, cwd, timeout ?? 30000, true); // allow rewrite for smart mode
|
|
160
160
|
const output = (result.stdout + result.stderr).trim();
|
|
161
161
|
const processed = await processOutput(command, output);
|
|
162
162
|
|
|
@@ -251,11 +251,11 @@ export function createServer(): McpServer {
|
|
|
251
251
|
|
|
252
252
|
server.tool(
|
|
253
253
|
"status",
|
|
254
|
-
"Get
|
|
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: "
|
|
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("
|
|
765
|
+
console.error("terminal MCP server running on stdio");
|
|
766
766
|
}
|
package/src/noise-filter.ts
CHANGED
|
@@ -41,16 +41,24 @@ const NOISE_PATTERNS: RegExp[] = [
|
|
|
41
41
|
/^\s*\d+(\.\d+)?\s*[KMG]?B\s*\/\s*\d+(\.\d+)?\s*[KMG]?B\b/,
|
|
42
42
|
];
|
|
43
43
|
|
|
44
|
-
// Sensitive env var patterns —
|
|
44
|
+
// Sensitive env var patterns — ONLY match actual env var assignments (export X=val, X=val at line start)
|
|
45
|
+
// NOT code lines like `const API_KEY = process.env.API_KEY` or `this.token = config.token`
|
|
45
46
|
const SENSITIVE_PATTERNS = [
|
|
46
|
-
|
|
47
|
-
/^(
|
|
47
|
+
// export KEY_NAME="value" or KEY_NAME=value (shell env vars only)
|
|
48
|
+
/^(export\s+[A-Z_]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)[A-Z_]*)=(.+)$/,
|
|
49
|
+
// Plain env assignment at start of line (no leading whitespace = not code)
|
|
50
|
+
/^([A-Z_]*(?:API_KEY|ACCESS_KEY|PRIVATE_KEY|CLIENT_SECRET|AUTH_TOKEN)[A-Z_]*)=(.+)$/,
|
|
48
51
|
];
|
|
49
52
|
|
|
50
|
-
/** Redact sensitive values in output (env vars,
|
|
53
|
+
/** Redact sensitive values in output (env vars only, not code) */
|
|
51
54
|
function redactSensitive(line: string): string {
|
|
55
|
+
const trimmed = line.trim();
|
|
56
|
+
// Skip lines that look like code (have leading whitespace, semicolons, const/let/var, etc.)
|
|
57
|
+
if (/^\s*(const|let|var|this\.|private|public|protected|import|export\s+(default|const|let|function|class)|\/\/|\/\*|\*)/.test(line)) {
|
|
58
|
+
return line; // Code — never redact
|
|
59
|
+
}
|
|
52
60
|
for (const pattern of SENSITIVE_PATTERNS) {
|
|
53
|
-
const match =
|
|
61
|
+
const match = trimmed.match(pattern);
|
|
54
62
|
if (match) {
|
|
55
63
|
return `${match[1]}=[REDACTED]`;
|
|
56
64
|
}
|