@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.
Files changed (75) hide show
  1. package/package.json +4 -3
  2. package/src/ai.ts +4 -4
  3. package/src/mcp/server.ts +26 -1704
  4. package/src/mcp/tools/batch.ts +106 -0
  5. package/src/mcp/tools/execute.ts +248 -0
  6. package/src/mcp/tools/files.ts +369 -0
  7. package/src/mcp/tools/git.ts +306 -0
  8. package/src/mcp/tools/helpers.ts +92 -0
  9. package/src/mcp/tools/memory.ts +170 -0
  10. package/src/mcp/tools/meta.ts +202 -0
  11. package/src/mcp/tools/process.ts +94 -0
  12. package/src/mcp/tools/project.ts +297 -0
  13. package/src/mcp/tools/search.ts +118 -0
  14. package/src/snapshots.ts +2 -2
  15. package/dist/App.js +0 -404
  16. package/dist/Browse.js +0 -79
  17. package/dist/FuzzyPicker.js +0 -47
  18. package/dist/Onboarding.js +0 -51
  19. package/dist/Spinner.js +0 -12
  20. package/dist/StatusBar.js +0 -49
  21. package/dist/ai.js +0 -315
  22. package/dist/cache.js +0 -42
  23. package/dist/cli.js +0 -778
  24. package/dist/command-rewriter.js +0 -64
  25. package/dist/command-validator.js +0 -86
  26. package/dist/compression.js +0 -91
  27. package/dist/context-hints.js +0 -285
  28. package/dist/diff-cache.js +0 -107
  29. package/dist/discover.js +0 -212
  30. package/dist/economy.js +0 -155
  31. package/dist/expand-store.js +0 -44
  32. package/dist/file-cache.js +0 -72
  33. package/dist/file-index.js +0 -62
  34. package/dist/history.js +0 -62
  35. package/dist/lazy-executor.js +0 -54
  36. package/dist/line-dedup.js +0 -59
  37. package/dist/loop-detector.js +0 -75
  38. package/dist/mcp/install.js +0 -189
  39. package/dist/mcp/server.js +0 -1375
  40. package/dist/noise-filter.js +0 -94
  41. package/dist/output-processor.js +0 -233
  42. package/dist/output-router.js +0 -41
  43. package/dist/output-store.js +0 -111
  44. package/dist/parsers/base.js +0 -2
  45. package/dist/parsers/build.js +0 -64
  46. package/dist/parsers/errors.js +0 -101
  47. package/dist/parsers/files.js +0 -78
  48. package/dist/parsers/git.js +0 -99
  49. package/dist/parsers/index.js +0 -48
  50. package/dist/parsers/tests.js +0 -89
  51. package/dist/providers/anthropic.js +0 -43
  52. package/dist/providers/base.js +0 -4
  53. package/dist/providers/cerebras.js +0 -8
  54. package/dist/providers/groq.js +0 -8
  55. package/dist/providers/index.js +0 -142
  56. package/dist/providers/openai-compat.js +0 -93
  57. package/dist/providers/xai.js +0 -8
  58. package/dist/recipes/model.js +0 -20
  59. package/dist/recipes/storage.js +0 -153
  60. package/dist/search/content-search.js +0 -70
  61. package/dist/search/file-search.js +0 -61
  62. package/dist/search/filters.js +0 -34
  63. package/dist/search/index.js +0 -5
  64. package/dist/search/semantic.js +0 -346
  65. package/dist/session-boot.js +0 -59
  66. package/dist/session-context.js +0 -55
  67. package/dist/sessions-db.js +0 -231
  68. package/dist/smart-display.js +0 -286
  69. package/dist/snapshots.js +0 -51
  70. package/dist/supervisor.js +0 -112
  71. package/dist/test-watchlist.js +0 -131
  72. package/dist/tokens.js +0 -17
  73. package/dist/tool-profiles.js +0 -129
  74. package/dist/tree.js +0 -94
  75. package/dist/usage-cache.js +0 -65
@@ -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
- }
@@ -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
- }
@@ -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 };