@hasna/terminal 3.7.2 → 3.7.4

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.
@@ -436,7 +436,13 @@ export function createServer() {
436
436
  // ── boot: session start context (replaces first 5 agent commands) ──────────
437
437
  server.tool("boot", "Get everything an agent needs on session start in ONE call — git state, project info, source structure. Replaces: git status + git log + cat package.json + ls src/. Cached for the session.", async () => {
438
438
  const ctx = await getBootContext(process.cwd());
439
- return { content: [{ type: "text", text: JSON.stringify(ctx) }] };
439
+ return { content: [{ type: "text", text: JSON.stringify({
440
+ ...ctx,
441
+ hints: {
442
+ cwd: process.cwd(),
443
+ tip: "All terminal tools support relative paths. Use 'src/foo.ts' not the full absolute path. Use commit({message, push:true}) instead of raw git commands. Use run({task:'test'}) instead of bun/npm test. Use lookup({file, items}) instead of grep pipelines.",
444
+ },
445
+ }) }] };
440
446
  });
441
447
  // ── project_overview: orient agent in one call ─────────────────────────────
442
448
  server.tool("project_overview", "Get project overview in one call — package.json info, source structure, config files. Replaces: cat package.json + ls src/ + cat tsconfig.json.", {
@@ -578,12 +584,14 @@ export function createServer() {
578
584
  return { content: [{ type: "text", text: JSON.stringify({ error: `Cannot read ${filePath}` }) }] };
579
585
  }
580
586
  // AI extracts symbols — works for ANY language
581
- const provider = getOutputProvider();
582
- const outputModel = provider.name === "groq" ? "llama-3.1-8b-instant" : undefined;
583
- const content = result.content.length > 6000 ? result.content.slice(0, 6000) : result.content;
584
- const summary = await provider.complete(`File: ${filePath}\n\n${content}`, {
585
- model: outputModel,
586
- system: `Extract all symbols from this source file. Return ONLY a JSON array, no explanation.
587
+ let symbols = [];
588
+ try {
589
+ const provider = getOutputProvider();
590
+ const outputModel = provider.name === "groq" ? "llama-3.1-8b-instant" : undefined;
591
+ const content = result.content.length > 8000 ? result.content.slice(0, 8000) : result.content;
592
+ const summary = await provider.complete(`File: ${filePath}\n\n${content}`, {
593
+ model: outputModel,
594
+ system: `Extract all symbols from this source file. Return ONLY a JSON array, no explanation.
587
595
 
588
596
  Each symbol: {"name": "symbolName", "kind": "function|class|method|interface|type|variable|export", "line": lineNumber, "signature": "brief signature"}
589
597
 
@@ -591,17 +599,17 @@ For class methods, use "ClassName.methodName" as name with kind "method".
591
599
  Include: functions, classes, methods, interfaces, types, exported constants.
592
600
  Exclude: imports, local variables, comments.
593
601
  Line numbers must be accurate (count from 1).`,
594
- maxTokens: 1000,
595
- temperature: 0,
596
- });
597
- // Parse AI response
598
- let symbols = [];
599
- try {
602
+ maxTokens: 2000,
603
+ temperature: 0,
604
+ });
600
605
  const jsonMatch = summary.match(/\[[\s\S]*\]/);
601
606
  if (jsonMatch)
602
607
  symbols = JSON.parse(jsonMatch[0]);
603
608
  }
604
- catch { }
609
+ catch (err) {
610
+ // Surface the error instead of silently returning []
611
+ return { content: [{ type: "text", text: JSON.stringify({ error: `AI symbol extraction failed: ${err.message?.slice(0, 200)}`, file: filePath }) }] };
612
+ }
605
613
  const outputTokens = estimateTokens(result.content);
606
614
  const symbolTokens = estimateTokens(JSON.stringify(symbols));
607
615
  logCall("symbols", { command: filePath, outputTokens, tokensSaved: Math.max(0, outputTokens - symbolTokens), durationMs: Date.now() - start, aiProcessed: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/terminal",
3
- "version": "3.7.2",
3
+ "version": "3.7.4",
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/mcp/server.ts CHANGED
@@ -615,7 +615,13 @@ export function createServer(): McpServer {
615
615
  "Get everything an agent needs on session start in ONE call — git state, project info, source structure. Replaces: git status + git log + cat package.json + ls src/. Cached for the session.",
616
616
  async () => {
617
617
  const ctx = await getBootContext(process.cwd());
618
- return { content: [{ type: "text" as const, text: JSON.stringify(ctx) }] };
618
+ return { content: [{ type: "text" as const, text: JSON.stringify({
619
+ ...ctx,
620
+ hints: {
621
+ cwd: process.cwd(),
622
+ tip: "All terminal tools support relative paths. Use 'src/foo.ts' not the full absolute path. Use commit({message, push:true}) instead of raw git commands. Use run({task:'test'}) instead of bun/npm test. Use lookup({file, items}) instead of grep pipelines.",
623
+ },
624
+ }) }] };
619
625
  }
620
626
  );
621
627
 
@@ -800,14 +806,16 @@ export function createServer(): McpServer {
800
806
  }
801
807
 
802
808
  // AI extracts symbols — works for ANY language
803
- const provider = getOutputProvider();
804
- const outputModel = provider.name === "groq" ? "llama-3.1-8b-instant" : undefined;
805
- const content = result.content.length > 6000 ? result.content.slice(0, 6000) : result.content;
806
- const summary = await provider.complete(
807
- `File: ${filePath}\n\n${content}`,
808
- {
809
- model: outputModel,
810
- system: `Extract all symbols from this source file. Return ONLY a JSON array, no explanation.
809
+ let symbols: any[] = [];
810
+ try {
811
+ const provider = getOutputProvider();
812
+ const outputModel = provider.name === "groq" ? "llama-3.1-8b-instant" : undefined;
813
+ const content = result.content.length > 8000 ? result.content.slice(0, 8000) : result.content;
814
+ const summary = await provider.complete(
815
+ `File: ${filePath}\n\n${content}`,
816
+ {
817
+ model: outputModel,
818
+ system: `Extract all symbols from this source file. Return ONLY a JSON array, no explanation.
811
819
 
812
820
  Each symbol: {"name": "symbolName", "kind": "function|class|method|interface|type|variable|export", "line": lineNumber, "signature": "brief signature"}
813
821
 
@@ -815,17 +823,17 @@ For class methods, use "ClassName.methodName" as name with kind "method".
815
823
  Include: functions, classes, methods, interfaces, types, exported constants.
816
824
  Exclude: imports, local variables, comments.
817
825
  Line numbers must be accurate (count from 1).`,
818
- maxTokens: 1000,
819
- temperature: 0,
820
- }
821
- );
826
+ maxTokens: 2000,
827
+ temperature: 0,
828
+ }
829
+ );
822
830
 
823
- // Parse AI response
824
- let symbols: any[] = [];
825
- try {
826
831
  const jsonMatch = summary.match(/\[[\s\S]*\]/);
827
832
  if (jsonMatch) symbols = JSON.parse(jsonMatch[0]);
828
- } catch {}
833
+ } catch (err: any) {
834
+ // Surface the error instead of silently returning []
835
+ return { content: [{ type: "text" as const, text: JSON.stringify({ error: `AI symbol extraction failed: ${err.message?.slice(0, 200)}`, file: filePath }) }] };
836
+ }
829
837
 
830
838
  const outputTokens = estimateTokens(result.content);
831
839
  const symbolTokens = estimateTokens(JSON.stringify(symbols));