@hasna/terminal 3.4.0 → 3.5.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/mcp/server.js +63 -22
- package/package.json +1 -1
- package/src/mcp/server.ts +72 -23
package/dist/mcp/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import { compress, stripAnsi } from "../compression.js";
|
|
|
7
7
|
import { stripNoise } from "../noise-filter.js";
|
|
8
8
|
import { estimateTokens } from "../tokens.js";
|
|
9
9
|
import { processOutput } from "../output-processor.js";
|
|
10
|
+
import { getOutputProvider } from "../providers/index.js";
|
|
10
11
|
import { searchFiles, searchContent, semanticSearch } from "../search/index.js";
|
|
11
12
|
import { listRecipes, listCollections, getRecipe, createRecipe } from "../recipes/storage.js";
|
|
12
13
|
import { substituteVariables } from "../recipes/model.js";
|
|
@@ -545,17 +546,44 @@ export function createServer() {
|
|
|
545
546
|
};
|
|
546
547
|
});
|
|
547
548
|
// ── symbols: file structure outline ───────────────────────────────────────
|
|
548
|
-
server.tool("symbols", "Get a structured outline of
|
|
549
|
+
server.tool("symbols", "Get a structured outline of any source file — functions, classes, methods, interfaces, exports with line numbers. Works for ALL languages (TypeScript, Python, Go, Rust, Java, C#, Ruby, PHP, etc.). AI-powered, not regex.", {
|
|
549
550
|
path: z.string().describe("File path to extract symbols from"),
|
|
550
551
|
}, async ({ path: filePath }) => {
|
|
551
|
-
const
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
552
|
+
const start = Date.now();
|
|
553
|
+
const result = cachedRead(filePath, {});
|
|
554
|
+
if (!result.content || result.content.startsWith("Error:")) {
|
|
555
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `Cannot read ${filePath}` }) }] };
|
|
556
|
+
}
|
|
557
|
+
// AI extracts symbols — works for ANY language
|
|
558
|
+
const provider = getOutputProvider();
|
|
559
|
+
const outputModel = provider.name === "groq" ? "llama-3.1-8b-instant" : undefined;
|
|
560
|
+
const content = result.content.length > 6000 ? result.content.slice(0, 6000) : result.content;
|
|
561
|
+
const summary = await provider.complete(`File: ${filePath}\n\n${content}`, {
|
|
562
|
+
model: outputModel,
|
|
563
|
+
system: `Extract all symbols from this source file. Return ONLY a JSON array, no explanation.
|
|
564
|
+
|
|
565
|
+
Each symbol: {"name": "symbolName", "kind": "function|class|method|interface|type|variable|export", "line": lineNumber, "signature": "brief signature"}
|
|
566
|
+
|
|
567
|
+
For class methods, use "ClassName.methodName" as name with kind "method".
|
|
568
|
+
Include: functions, classes, methods, interfaces, types, exported constants.
|
|
569
|
+
Exclude: imports, local variables, comments.
|
|
570
|
+
Line numbers must be accurate (count from 1).`,
|
|
571
|
+
maxTokens: 1000,
|
|
572
|
+
temperature: 0,
|
|
573
|
+
});
|
|
574
|
+
// Parse AI response
|
|
575
|
+
let symbols = [];
|
|
576
|
+
try {
|
|
577
|
+
const jsonMatch = summary.match(/\[[\s\S]*\]/);
|
|
578
|
+
if (jsonMatch)
|
|
579
|
+
symbols = JSON.parse(jsonMatch[0]);
|
|
580
|
+
}
|
|
581
|
+
catch { }
|
|
582
|
+
const outputTokens = estimateTokens(result.content);
|
|
583
|
+
const symbolTokens = estimateTokens(JSON.stringify(symbols));
|
|
584
|
+
logCall("symbols", { command: filePath, outputTokens, tokensSaved: Math.max(0, outputTokens - symbolTokens), durationMs: Date.now() - start, aiProcessed: true });
|
|
557
585
|
return {
|
|
558
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
586
|
+
content: [{ type: "text", text: JSON.stringify(symbols) }],
|
|
559
587
|
};
|
|
560
588
|
});
|
|
561
589
|
// ── read_symbol: read a function/class by name ─────────────────────────────
|
|
@@ -563,21 +591,34 @@ export function createServer() {
|
|
|
563
591
|
path: z.string().describe("Source file path"),
|
|
564
592
|
name: z.string().describe("Symbol name (function, class, interface)"),
|
|
565
593
|
}, async ({ path: filePath, name }) => {
|
|
566
|
-
const
|
|
567
|
-
const
|
|
568
|
-
if (!
|
|
569
|
-
|
|
570
|
-
const symbols = extractSymbolsFromFile(filePath);
|
|
571
|
-
const names = symbols.filter(s => s.kind !== "import").map(s => `${s.kind}: ${s.name} (L${s.line})`);
|
|
572
|
-
return { content: [{ type: "text", text: JSON.stringify({
|
|
573
|
-
error: `Symbol '${name}' not found`,
|
|
574
|
-
available: names.slice(0, 20),
|
|
575
|
-
}) }] };
|
|
594
|
+
const start = Date.now();
|
|
595
|
+
const result = cachedRead(filePath, {});
|
|
596
|
+
if (!result.content || result.content.startsWith("Error:")) {
|
|
597
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `Cannot read ${filePath}` }) }] };
|
|
576
598
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
599
|
+
// AI extracts the specific symbol — works for ANY language
|
|
600
|
+
const provider = getOutputProvider();
|
|
601
|
+
const outputModel = provider.name === "groq" ? "llama-3.1-8b-instant" : undefined;
|
|
602
|
+
const summary = await provider.complete(`File: ${filePath}\nSymbol to extract: ${name}\n\n${result.content.slice(0, 8000)}`, {
|
|
603
|
+
model: outputModel,
|
|
604
|
+
system: `Extract the complete code block for the symbol "${name}" from this file. Return ONLY a JSON object:
|
|
605
|
+
{"name": "${name}", "code": "the complete code block", "startLine": N, "endLine": N}
|
|
606
|
+
|
|
607
|
+
If the symbol is not found, return: {"error": "not found", "available": ["list", "of", "symbol", "names"]}
|
|
608
|
+
|
|
609
|
+
Match by function name, class name, method name (including ClassName.method), interface, type, or variable name.`,
|
|
610
|
+
maxTokens: 2000,
|
|
611
|
+
temperature: 0,
|
|
612
|
+
});
|
|
613
|
+
let parsed = {};
|
|
614
|
+
try {
|
|
615
|
+
const jsonMatch = summary.match(/\{[\s\S]*\}/);
|
|
616
|
+
if (jsonMatch)
|
|
617
|
+
parsed = JSON.parse(jsonMatch[0]);
|
|
618
|
+
}
|
|
619
|
+
catch { }
|
|
620
|
+
logCall("read_symbol", { command: `${filePath}:${name}`, outputTokens: estimateTokens(result.content), tokensSaved: Math.max(0, estimateTokens(result.content) - estimateTokens(JSON.stringify(parsed))), durationMs: Date.now() - start, aiProcessed: true });
|
|
621
|
+
return { content: [{ type: "text", text: JSON.stringify(parsed) }] };
|
|
581
622
|
});
|
|
582
623
|
return server;
|
|
583
624
|
}
|
package/package.json
CHANGED
package/src/mcp/server.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { compress, stripAnsi } from "../compression.js";
|
|
|
8
8
|
import { stripNoise } from "../noise-filter.js";
|
|
9
9
|
import { estimateTokens } from "../tokens.js";
|
|
10
10
|
import { processOutput } from "../output-processor.js";
|
|
11
|
+
import { getOutputProvider } from "../providers/index.js";
|
|
11
12
|
import { searchFiles, searchContent, semanticSearch } from "../search/index.js";
|
|
12
13
|
import { listRecipes, listCollections, getRecipe, createRecipe } from "../recipes/storage.js";
|
|
13
14
|
import { substituteVariables } from "../recipes/model.js";
|
|
@@ -762,21 +763,51 @@ export function createServer(): McpServer {
|
|
|
762
763
|
|
|
763
764
|
server.tool(
|
|
764
765
|
"symbols",
|
|
765
|
-
"Get a structured outline of
|
|
766
|
+
"Get a structured outline of any source file — functions, classes, methods, interfaces, exports with line numbers. Works for ALL languages (TypeScript, Python, Go, Rust, Java, C#, Ruby, PHP, etc.). AI-powered, not regex.",
|
|
766
767
|
{
|
|
767
768
|
path: z.string().describe("File path to extract symbols from"),
|
|
768
769
|
},
|
|
769
770
|
async ({ path: filePath }) => {
|
|
770
|
-
const
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
771
|
+
const start = Date.now();
|
|
772
|
+
const result = cachedRead(filePath, {});
|
|
773
|
+
if (!result.content || result.content.startsWith("Error:")) {
|
|
774
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({ error: `Cannot read ${filePath}` }) }] };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// AI extracts symbols — works for ANY language
|
|
778
|
+
const provider = getOutputProvider();
|
|
779
|
+
const outputModel = provider.name === "groq" ? "llama-3.1-8b-instant" : undefined;
|
|
780
|
+
const content = result.content.length > 6000 ? result.content.slice(0, 6000) : result.content;
|
|
781
|
+
const summary = await provider.complete(
|
|
782
|
+
`File: ${filePath}\n\n${content}`,
|
|
783
|
+
{
|
|
784
|
+
model: outputModel,
|
|
785
|
+
system: `Extract all symbols from this source file. Return ONLY a JSON array, no explanation.
|
|
786
|
+
|
|
787
|
+
Each symbol: {"name": "symbolName", "kind": "function|class|method|interface|type|variable|export", "line": lineNumber, "signature": "brief signature"}
|
|
788
|
+
|
|
789
|
+
For class methods, use "ClassName.methodName" as name with kind "method".
|
|
790
|
+
Include: functions, classes, methods, interfaces, types, exported constants.
|
|
791
|
+
Exclude: imports, local variables, comments.
|
|
792
|
+
Line numbers must be accurate (count from 1).`,
|
|
793
|
+
maxTokens: 1000,
|
|
794
|
+
temperature: 0,
|
|
795
|
+
}
|
|
777
796
|
);
|
|
797
|
+
|
|
798
|
+
// Parse AI response
|
|
799
|
+
let symbols: any[] = [];
|
|
800
|
+
try {
|
|
801
|
+
const jsonMatch = summary.match(/\[[\s\S]*\]/);
|
|
802
|
+
if (jsonMatch) symbols = JSON.parse(jsonMatch[0]);
|
|
803
|
+
} catch {}
|
|
804
|
+
|
|
805
|
+
const outputTokens = estimateTokens(result.content);
|
|
806
|
+
const symbolTokens = estimateTokens(JSON.stringify(symbols));
|
|
807
|
+
logCall("symbols", { command: filePath, outputTokens, tokensSaved: Math.max(0, outputTokens - symbolTokens), durationMs: Date.now() - start, aiProcessed: true });
|
|
808
|
+
|
|
778
809
|
return {
|
|
779
|
-
content: [{ type: "text" as const, text: JSON.stringify(
|
|
810
|
+
content: [{ type: "text" as const, text: JSON.stringify(symbols) }],
|
|
780
811
|
};
|
|
781
812
|
}
|
|
782
813
|
);
|
|
@@ -791,21 +822,39 @@ export function createServer(): McpServer {
|
|
|
791
822
|
name: z.string().describe("Symbol name (function, class, interface)"),
|
|
792
823
|
},
|
|
793
824
|
async ({ path: filePath, name }) => {
|
|
794
|
-
const
|
|
795
|
-
const
|
|
796
|
-
if (!
|
|
797
|
-
|
|
798
|
-
const symbols = extractSymbolsFromFile(filePath);
|
|
799
|
-
const names = symbols.filter(s => s.kind !== "import").map(s => `${s.kind}: ${s.name} (L${s.line})`);
|
|
800
|
-
return { content: [{ type: "text" as const, text: JSON.stringify({
|
|
801
|
-
error: `Symbol '${name}' not found`,
|
|
802
|
-
available: names.slice(0, 20),
|
|
803
|
-
}) }] };
|
|
825
|
+
const start = Date.now();
|
|
826
|
+
const result = cachedRead(filePath, {});
|
|
827
|
+
if (!result.content || result.content.startsWith("Error:")) {
|
|
828
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({ error: `Cannot read ${filePath}` }) }] };
|
|
804
829
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
830
|
+
|
|
831
|
+
// AI extracts the specific symbol — works for ANY language
|
|
832
|
+
const provider = getOutputProvider();
|
|
833
|
+
const outputModel = provider.name === "groq" ? "llama-3.1-8b-instant" : undefined;
|
|
834
|
+
const summary = await provider.complete(
|
|
835
|
+
`File: ${filePath}\nSymbol to extract: ${name}\n\n${result.content.slice(0, 8000)}`,
|
|
836
|
+
{
|
|
837
|
+
model: outputModel,
|
|
838
|
+
system: `Extract the complete code block for the symbol "${name}" from this file. Return ONLY a JSON object:
|
|
839
|
+
{"name": "${name}", "code": "the complete code block", "startLine": N, "endLine": N}
|
|
840
|
+
|
|
841
|
+
If the symbol is not found, return: {"error": "not found", "available": ["list", "of", "symbol", "names"]}
|
|
842
|
+
|
|
843
|
+
Match by function name, class name, method name (including ClassName.method), interface, type, or variable name.`,
|
|
844
|
+
maxTokens: 2000,
|
|
845
|
+
temperature: 0,
|
|
846
|
+
}
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
let parsed: any = {};
|
|
850
|
+
try {
|
|
851
|
+
const jsonMatch = summary.match(/\{[\s\S]*\}/);
|
|
852
|
+
if (jsonMatch) parsed = JSON.parse(jsonMatch[0]);
|
|
853
|
+
} catch {}
|
|
854
|
+
|
|
855
|
+
logCall("read_symbol", { command: `${filePath}:${name}`, outputTokens: estimateTokens(result.content), tokensSaved: Math.max(0, estimateTokens(result.content) - estimateTokens(JSON.stringify(parsed))), durationMs: Date.now() - start, aiProcessed: true });
|
|
856
|
+
|
|
857
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(parsed) }] };
|
|
809
858
|
}
|
|
810
859
|
);
|
|
811
860
|
|