@hasna/terminal 4.3.0 → 4.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/package.json +4 -3
- package/src/ai.ts +4 -4
- package/src/mcp/server.ts +26 -1704
- package/src/mcp/tools/batch.ts +106 -0
- package/src/mcp/tools/execute.ts +248 -0
- package/src/mcp/tools/files.ts +369 -0
- package/src/mcp/tools/git.ts +306 -0
- package/src/mcp/tools/helpers.ts +92 -0
- package/src/mcp/tools/memory.ts +170 -0
- package/src/mcp/tools/meta.ts +202 -0
- package/src/mcp/tools/process.ts +94 -0
- package/src/mcp/tools/project.ts +297 -0
- package/src/mcp/tools/search.ts +118 -0
- package/src/snapshots.ts +2 -2
- package/dist/App.js +0 -404
- package/dist/Browse.js +0 -79
- package/dist/FuzzyPicker.js +0 -47
- package/dist/Onboarding.js +0 -51
- package/dist/Spinner.js +0 -12
- package/dist/StatusBar.js +0 -49
- package/dist/ai.js +0 -315
- package/dist/cache.js +0 -42
- package/dist/cli.js +0 -778
- package/dist/command-rewriter.js +0 -64
- package/dist/command-validator.js +0 -86
- package/dist/compression.js +0 -91
- package/dist/context-hints.js +0 -285
- package/dist/diff-cache.js +0 -107
- package/dist/discover.js +0 -212
- package/dist/economy.js +0 -155
- package/dist/expand-store.js +0 -44
- package/dist/file-cache.js +0 -72
- package/dist/file-index.js +0 -62
- package/dist/history.js +0 -62
- package/dist/lazy-executor.js +0 -54
- package/dist/line-dedup.js +0 -59
- package/dist/loop-detector.js +0 -75
- package/dist/mcp/install.js +0 -189
- package/dist/mcp/server.js +0 -1375
- package/dist/noise-filter.js +0 -94
- package/dist/output-processor.js +0 -233
- package/dist/output-router.js +0 -41
- package/dist/output-store.js +0 -111
- package/dist/parsers/base.js +0 -2
- package/dist/parsers/build.js +0 -64
- package/dist/parsers/errors.js +0 -101
- package/dist/parsers/files.js +0 -78
- package/dist/parsers/git.js +0 -99
- package/dist/parsers/index.js +0 -48
- package/dist/parsers/tests.js +0 -89
- package/dist/providers/anthropic.js +0 -43
- package/dist/providers/base.js +0 -4
- package/dist/providers/cerebras.js +0 -8
- package/dist/providers/groq.js +0 -8
- package/dist/providers/index.js +0 -142
- package/dist/providers/openai-compat.js +0 -93
- package/dist/providers/xai.js +0 -8
- package/dist/recipes/model.js +0 -20
- package/dist/recipes/storage.js +0 -153
- package/dist/search/content-search.js +0 -70
- package/dist/search/file-search.js +0 -61
- package/dist/search/filters.js +0 -34
- package/dist/search/index.js +0 -5
- package/dist/search/semantic.js +0 -346
- package/dist/session-boot.js +0 -59
- package/dist/session-context.js +0 -55
- package/dist/sessions-db.js +0 -231
- package/dist/smart-display.js +0 -286
- package/dist/snapshots.js +0 -51
- package/dist/supervisor.js +0 -112
- package/dist/test-watchlist.js +0 -131
- package/dist/tokens.js +0 -17
- package/dist/tool-profiles.js +0 -129
- package/dist/tree.js +0 -94
- package/dist/usage-cache.js +0 -65
package/dist/line-dedup.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// Cross-command line deduplication — track lines already shown to agent
|
|
2
|
-
// When new output contains >50% already-seen lines, suppress them
|
|
3
|
-
const seenLines = new Set();
|
|
4
|
-
const MAX_SEEN = 5000;
|
|
5
|
-
function normalize(line) {
|
|
6
|
-
return line.trim().toLowerCase();
|
|
7
|
-
}
|
|
8
|
-
/** Deduplicate output lines against session history */
|
|
9
|
-
export function dedup(output) {
|
|
10
|
-
const lines = output.split("\n");
|
|
11
|
-
if (lines.length < 5) {
|
|
12
|
-
// Short output — add to seen, don't dedup
|
|
13
|
-
for (const l of lines) {
|
|
14
|
-
if (l.trim())
|
|
15
|
-
seenLines.add(normalize(l));
|
|
16
|
-
}
|
|
17
|
-
return { output, novelCount: lines.length, seenCount: 0, deduplicated: false };
|
|
18
|
-
}
|
|
19
|
-
let novelCount = 0;
|
|
20
|
-
let seenCount = 0;
|
|
21
|
-
const novel = [];
|
|
22
|
-
for (const line of lines) {
|
|
23
|
-
const norm = normalize(line);
|
|
24
|
-
if (!norm) {
|
|
25
|
-
novel.push(line);
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
if (seenLines.has(norm)) {
|
|
29
|
-
seenCount++;
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
novelCount++;
|
|
33
|
-
novel.push(line);
|
|
34
|
-
seenLines.add(norm);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
// Evict oldest if too large
|
|
38
|
-
if (seenLines.size > MAX_SEEN) {
|
|
39
|
-
const entries = [...seenLines];
|
|
40
|
-
for (let i = 0; i < entries.length - MAX_SEEN; i++) {
|
|
41
|
-
seenLines.delete(entries[i]);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// Only dedup if >50% were already seen
|
|
45
|
-
if (seenCount > lines.length * 0.5) {
|
|
46
|
-
const result = novel.join("\n");
|
|
47
|
-
return { output: result + `\n(${seenCount} lines already shown, omitted)`, novelCount, seenCount, deduplicated: true };
|
|
48
|
-
}
|
|
49
|
-
// Add all to seen but return full output
|
|
50
|
-
for (const l of lines) {
|
|
51
|
-
if (l.trim())
|
|
52
|
-
seenLines.add(normalize(l));
|
|
53
|
-
}
|
|
54
|
-
return { output, novelCount: lines.length, seenCount: 0, deduplicated: false };
|
|
55
|
-
}
|
|
56
|
-
/** Clear dedup history */
|
|
57
|
-
export function clearDedup() {
|
|
58
|
-
seenLines.clear();
|
|
59
|
-
}
|
package/dist/loop-detector.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
// Edit-test loop detector — detects repetitive test→edit→test patterns
|
|
2
|
-
// and suggests narrowing to specific test files
|
|
3
|
-
const history = [];
|
|
4
|
-
const MAX_HISTORY = 20;
|
|
5
|
-
// Detect test commands
|
|
6
|
-
const TEST_PATTERNS = [
|
|
7
|
-
/\bbun\s+test\b/, /\bnpm\s+test\b/, /\bnpx\s+jest\b/, /\bnpx\s+vitest\b/,
|
|
8
|
-
/\bpnpm\s+test\b/, /\byarn\s+test\b/, /\bpytest\b/, /\bgo\s+test\b/,
|
|
9
|
-
/\bcargo\s+test\b/, /\brspec\b/, /\bphpunit\b/, /\bmocha\b/,
|
|
10
|
-
];
|
|
11
|
-
function isTestCommand(cmd) {
|
|
12
|
-
return TEST_PATTERNS.some(p => p.test(cmd));
|
|
13
|
-
}
|
|
14
|
-
function isFullSuiteCommand(cmd) {
|
|
15
|
-
// Full suite = test command without specific file/pattern
|
|
16
|
-
if (!isTestCommand(cmd))
|
|
17
|
-
return false;
|
|
18
|
-
// If it has a specific file or --grep, it's already narrowed
|
|
19
|
-
if (/\.(test|spec)\.(ts|tsx|js|jsx|py|rs|go)/.test(cmd))
|
|
20
|
-
return false;
|
|
21
|
-
if (/--grep|--filter|-t\s/.test(cmd))
|
|
22
|
-
return false;
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
/** Record a command execution and detect loops */
|
|
26
|
-
export function detectLoop(command) {
|
|
27
|
-
history.push({ command, timestamp: Date.now() });
|
|
28
|
-
if (history.length > MAX_HISTORY)
|
|
29
|
-
history.shift();
|
|
30
|
-
if (!isTestCommand(command)) {
|
|
31
|
-
return { detected: false, iteration: 0, testCommand: command };
|
|
32
|
-
}
|
|
33
|
-
// Count consecutive test runs (allowing non-test commands between them)
|
|
34
|
-
let testCount = 0;
|
|
35
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
36
|
-
if (isTestCommand(history[i].command))
|
|
37
|
-
testCount++;
|
|
38
|
-
// If we hit a non-test, non-edit command, stop counting
|
|
39
|
-
// (edits are invisible to us since we only see exec'd commands)
|
|
40
|
-
}
|
|
41
|
-
if (testCount < 3 || !isFullSuiteCommand(command)) {
|
|
42
|
-
return { detected: false, iteration: testCount, testCommand: command };
|
|
43
|
-
}
|
|
44
|
-
// Detected loop — suggest narrowing
|
|
45
|
-
// Try to find a recently-mentioned test file in recent commands
|
|
46
|
-
let suggestedNarrow;
|
|
47
|
-
// Look for file paths in recent history that could be test targets
|
|
48
|
-
for (let i = history.length - 2; i >= Math.max(0, history.length - 10); i--) {
|
|
49
|
-
const cmd = history[i].command;
|
|
50
|
-
// Look for edited/touched files
|
|
51
|
-
const fileMatch = cmd.match(/(\S+\.(ts|tsx|js|jsx|py|rs|go))\b/);
|
|
52
|
-
if (fileMatch && !isTestCommand(cmd)) {
|
|
53
|
-
const file = fileMatch[1];
|
|
54
|
-
// Suggest corresponding test file
|
|
55
|
-
const testFile = file.replace(/\.(ts|tsx|js|jsx)$/, ".test.$1");
|
|
56
|
-
suggestedNarrow = command.replace(/\b(test)\b/, `test ${testFile}`);
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
// Fallback: suggest adding --grep or specific file
|
|
61
|
-
if (!suggestedNarrow) {
|
|
62
|
-
suggestedNarrow = undefined; // Can't determine which file
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
detected: true,
|
|
66
|
-
iteration: testCount,
|
|
67
|
-
testCommand: command,
|
|
68
|
-
suggestedNarrow,
|
|
69
|
-
reason: `Full test suite run ${testCount} times. Consider narrowing to specific test file.`,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
/** Reset loop detection (e.g., on session start) */
|
|
73
|
-
export function resetLoopDetector() {
|
|
74
|
-
history.length = 0;
|
|
75
|
-
}
|
package/dist/mcp/install.js
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
// MCP installation — one command to rule them all
|
|
2
|
-
// `npx @hasna/terminal install` → installs globally + configures all AI agents
|
|
3
|
-
import { execSync } from "child_process";
|
|
4
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
5
|
-
import { homedir } from "os";
|
|
6
|
-
import { join } from "path";
|
|
7
|
-
function which(cmd) {
|
|
8
|
-
try {
|
|
9
|
-
return execSync(`which ${cmd}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
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
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
execSync(`claude mcp add --transport stdio --scope user terminal -- ${bin} mcp serve`, { stdio: ["pipe", "pipe", "pipe"] });
|
|
28
|
-
log("✓", "Claude Code");
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
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
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
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");
|
|
53
|
-
try {
|
|
54
|
-
if (!existsSync(dir))
|
|
55
|
-
mkdirSync(dir, { recursive: true });
|
|
56
|
-
let content = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
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`;
|
|
60
|
-
writeFileSync(configPath, content);
|
|
61
|
-
log("✓", "Codex");
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
catch (e) {
|
|
65
|
-
log("✗", `Codex — ${e}`);
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
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");
|
|
77
|
-
try {
|
|
78
|
-
if (!existsSync(dir))
|
|
79
|
-
mkdirSync(dir, { recursive: true });
|
|
80
|
-
let config = {};
|
|
81
|
-
if (existsSync(configPath)) {
|
|
82
|
-
try {
|
|
83
|
-
config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
84
|
-
}
|
|
85
|
-
catch { }
|
|
86
|
-
}
|
|
87
|
-
if (!config.mcpServers)
|
|
88
|
-
config.mcpServers = {};
|
|
89
|
-
config.mcpServers["terminal"] = { command: bin, args: ["mcp", "serve"] };
|
|
90
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
91
|
-
log("✓", "Gemini CLI");
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
catch (e) {
|
|
95
|
-
log("✗", `Gemini — ${e}`);
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
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
|
-
}
|
|
111
|
-
}
|
|
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) {
|
|
148
|
-
const flags = new Set(args);
|
|
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");
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
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("");
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
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`);
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
console.log(`\n ${count} agent${count > 1 ? "s" : ""} ready. Restart to apply.\n`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
// Re-export individual installers for programmatic use
|
|
189
|
-
export { installClaude, installCodex, installGemini, uninstallClaude, uninstallCodex, uninstallGemini };
|