@hasna/terminal 2.3.1 → 3.0.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 (99) hide show
  1. package/dist/App.js +404 -0
  2. package/dist/Browse.js +79 -0
  3. package/dist/FuzzyPicker.js +47 -0
  4. package/dist/Onboarding.js +51 -0
  5. package/dist/Spinner.js +12 -0
  6. package/dist/StatusBar.js +49 -0
  7. package/dist/ai.js +296 -0
  8. package/dist/cache.js +42 -0
  9. package/dist/cli.js +1 -1
  10. package/dist/command-rewriter.js +64 -0
  11. package/dist/command-validator.js +86 -0
  12. package/dist/compression.js +85 -0
  13. package/dist/context-hints.js +285 -0
  14. package/dist/diff-cache.js +107 -0
  15. package/dist/discover.js +212 -0
  16. package/dist/economy.js +155 -0
  17. package/dist/expand-store.js +44 -0
  18. package/dist/file-cache.js +72 -0
  19. package/dist/file-index.js +62 -0
  20. package/dist/history.js +62 -0
  21. package/dist/lazy-executor.js +54 -0
  22. package/dist/line-dedup.js +59 -0
  23. package/dist/loop-detector.js +75 -0
  24. package/dist/mcp/install.js +98 -0
  25. package/dist/mcp/server.js +545 -0
  26. package/dist/noise-filter.js +86 -0
  27. package/dist/output-processor.js +132 -0
  28. package/dist/output-router.js +41 -0
  29. package/dist/output-store.js +111 -0
  30. package/dist/parsers/base.js +2 -0
  31. package/dist/parsers/build.js +64 -0
  32. package/dist/parsers/errors.js +101 -0
  33. package/dist/parsers/files.js +78 -0
  34. package/dist/parsers/git.js +99 -0
  35. package/dist/parsers/index.js +48 -0
  36. package/dist/parsers/tests.js +89 -0
  37. package/dist/providers/anthropic.js +43 -0
  38. package/dist/providers/base.js +4 -0
  39. package/dist/providers/cerebras.js +8 -0
  40. package/dist/providers/groq.js +8 -0
  41. package/dist/providers/index.js +122 -0
  42. package/dist/providers/openai-compat.js +93 -0
  43. package/dist/providers/xai.js +8 -0
  44. package/dist/recipes/model.js +20 -0
  45. package/dist/recipes/storage.js +136 -0
  46. package/dist/search/content-search.js +68 -0
  47. package/dist/search/file-search.js +61 -0
  48. package/dist/search/filters.js +34 -0
  49. package/dist/search/index.js +5 -0
  50. package/dist/search/semantic.js +320 -0
  51. package/dist/session-boot.js +59 -0
  52. package/dist/session-context.js +55 -0
  53. package/dist/sessions-db.js +173 -0
  54. package/dist/smart-display.js +286 -0
  55. package/dist/snapshots.js +51 -0
  56. package/dist/supervisor.js +112 -0
  57. package/dist/test-watchlist.js +131 -0
  58. package/dist/tokens.js +17 -0
  59. package/dist/tool-profiles.js +129 -0
  60. package/dist/tree.js +94 -0
  61. package/dist/usage-cache.js +65 -0
  62. package/package.json +8 -1
  63. package/src/ai.ts +60 -90
  64. package/src/cache.ts +3 -2
  65. package/src/cli.tsx +1 -1
  66. package/src/compression.ts +8 -35
  67. package/src/context-hints.ts +20 -10
  68. package/src/diff-cache.ts +1 -1
  69. package/src/discover.ts +1 -1
  70. package/src/economy.ts +37 -5
  71. package/src/expand-store.ts +8 -1
  72. package/src/mcp/server.ts +45 -73
  73. package/src/output-processor.ts +11 -8
  74. package/src/providers/anthropic.ts +6 -2
  75. package/src/providers/base.ts +2 -0
  76. package/src/providers/cerebras.ts +6 -105
  77. package/src/providers/groq.ts +6 -105
  78. package/src/providers/index.ts +84 -33
  79. package/src/providers/openai-compat.ts +109 -0
  80. package/src/providers/xai.ts +6 -105
  81. package/src/tokens.ts +18 -0
  82. package/src/tool-profiles.ts +9 -2
  83. package/.claude/scheduled_tasks.lock +0 -1
  84. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
  85. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
  86. package/CONTRIBUTING.md +0 -80
  87. package/benchmarks/benchmark.mjs +0 -115
  88. package/imported_modules.txt +0 -0
  89. package/src/compression.test.ts +0 -49
  90. package/src/output-router.ts +0 -56
  91. package/src/parsers/base.ts +0 -72
  92. package/src/parsers/build.ts +0 -73
  93. package/src/parsers/errors.ts +0 -107
  94. package/src/parsers/files.ts +0 -91
  95. package/src/parsers/git.ts +0 -101
  96. package/src/parsers/index.ts +0 -66
  97. package/src/parsers/parsers.test.ts +0 -153
  98. package/src/parsers/tests.ts +0 -98
  99. package/tsconfig.json +0 -15
@@ -1,66 +0,0 @@
1
- // Output parser registry — auto-detect command output type and parse to structured JSON
2
-
3
- import type { Parser } from "./base.js";
4
- import { lsParser, findParser } from "./files.js";
5
- import { testParser } from "./tests.js";
6
- import { gitLogParser, gitStatusParser } from "./git.js";
7
- import { buildParser, npmInstallParser } from "./build.js";
8
- import { errorParser } from "./errors.js";
9
-
10
- export type { Parser } from "./base.js";
11
- export type {
12
- FileEntry, TestResult, GitLogEntry, GitStatus,
13
- BuildResult, NpmInstallResult, ErrorInfo, SearchResult,
14
- } from "./base.js";
15
-
16
- // Ordered by specificity — more specific parsers first
17
- const parsers: Parser[] = [
18
- npmInstallParser,
19
- testParser,
20
- gitLogParser,
21
- gitStatusParser,
22
- buildParser,
23
- findParser,
24
- lsParser,
25
- errorParser, // fallback for error detection
26
- ];
27
-
28
- export interface ParseResult {
29
- parser: string;
30
- data: unknown;
31
- raw: string;
32
- }
33
-
34
- /** Try to parse command output with the best matching parser */
35
- export function parseOutput(command: string, output: string): ParseResult | null {
36
- for (const parser of parsers) {
37
- if (parser.detect(command, output)) {
38
- try {
39
- const data = parser.parse(command, output);
40
- return { parser: parser.name, data, raw: output };
41
- } catch {
42
- continue;
43
- }
44
- }
45
- }
46
- return null;
47
- }
48
-
49
- /** Get all parsers that match (for debugging/info) */
50
- export function detectParsers(command: string, output: string): string[] {
51
- return parsers.filter(p => p.detect(command, output)).map(p => p.name);
52
- }
53
-
54
- /** Estimate token count for a string (rough: ~4 chars per token) */
55
- export function estimateTokens(text: string): number {
56
- return Math.ceil(text.length / 4);
57
- }
58
-
59
- /** Calculate token savings between raw output and parsed JSON */
60
- export function tokenSavings(raw: string, parsed: unknown): { rawTokens: number; parsedTokens: number; saved: number; percent: number } {
61
- const rawTokens = estimateTokens(raw);
62
- const parsedTokens = estimateTokens(JSON.stringify(parsed));
63
- const saved = Math.max(0, rawTokens - parsedTokens);
64
- const percent = rawTokens > 0 ? Math.round((saved / rawTokens) * 100) : 0;
65
- return { rawTokens, parsedTokens, saved, percent };
66
- }
@@ -1,153 +0,0 @@
1
- import { describe, it, expect } from "bun:test";
2
- import { parseOutput, tokenSavings, estimateTokens } from "./index.js";
3
-
4
- describe("parseOutput", () => {
5
- it("parses ls -la output", () => {
6
- const output = `total 32
7
- drwxr-xr-x 5 user staff 160 Mar 10 09:00 src
8
- -rw-r--r-- 1 user staff 450 Mar 10 09:00 package.json
9
- lrwxr-xr-x 1 user staff 20 Mar 10 09:00 link -> target`;
10
-
11
- const result = parseOutput("ls -la", output);
12
- expect(result).not.toBeNull();
13
- expect(result!.parser).toBe("ls");
14
- const data = result!.data as any[];
15
- expect(data.length).toBe(3);
16
- expect(data[0].name).toBe("src");
17
- expect(data[0].type).toBe("dir");
18
- expect(data[1].name).toBe("package.json");
19
- expect(data[1].type).toBe("file");
20
- expect(data[2].type).toBe("symlink");
21
- });
22
-
23
- it("parses find output and filters node_modules", () => {
24
- const output = `./src/lib/webhooks.ts
25
- ./node_modules/@types/node/async_hooks.d.ts
26
- ./node_modules/@types/node/perf_hooks.d.ts
27
- ./dist/lib/webhooks.d.ts
28
- ./src/routes/api.ts`;
29
-
30
- const result = parseOutput("find . -name '*hooks*' -type f", output);
31
- expect(result).not.toBeNull();
32
- expect(result!.parser).toBe("find");
33
- const data = result!.data as any;
34
- expect(data.source.length).toBe(2); // webhooks.ts and api.ts
35
- expect(data.filtered.length).toBeGreaterThan(0);
36
- expect(data.filtered.find((f: any) => f.reason === "node_modules")?.count).toBe(2);
37
- });
38
-
39
- it("parses test output (jest style)", () => {
40
- const output = `PASS src/auth.test.ts
41
- FAIL src/db.test.ts
42
- ✗ should connect to database
43
- Error: Connection refused
44
- Tests: 5 passed, 1 failed, 1 skipped, 7 total
45
- Time: 3.2s`;
46
-
47
- const result = parseOutput("npm test", output);
48
- expect(result).not.toBeNull();
49
- expect(result!.parser).toBe("test");
50
- const data = result!.data as any;
51
- expect(data.passed).toBe(5);
52
- expect(data.failed).toBe(1);
53
- expect(data.skipped).toBe(1);
54
- expect(data.total).toBe(7);
55
- });
56
-
57
- it("parses git status", () => {
58
- const output = `On branch main
59
- Changes to be committed:
60
- new file: src/mcp/server.ts
61
- modified: src/ai.ts
62
-
63
- Changes not staged for commit:
64
- modified: package.json
65
-
66
- Untracked files:
67
- src/tree.ts`;
68
-
69
- const result = parseOutput("git status", output);
70
- expect(result).not.toBeNull();
71
- expect(result!.parser).toBe("git-status");
72
- const data = result!.data as any;
73
- expect(data.branch).toBe("main");
74
- expect(data.staged.length).toBe(2);
75
- expect(data.unstaged.length).toBe(1);
76
- expect(data.untracked.length).toBe(1);
77
- });
78
-
79
- it("parses git log", () => {
80
- const output = `commit af19ce3456789
81
- Author: Andrei Hasna <andrei@hasna.com>
82
- Date: Sat Mar 15 10:00:00 2026
83
-
84
- feat: add MCP server
85
-
86
- commit 3963db5123456
87
- Author: Andrei Hasna <andrei@hasna.com>
88
- Date: Fri Mar 14 09:00:00 2026
89
-
90
- feat: tabs and browse mode`;
91
-
92
- const result = parseOutput("git log", output);
93
- expect(result).not.toBeNull();
94
- expect(result!.parser).toBe("git-log");
95
- const data = result!.data as any[];
96
- expect(data.length).toBe(2);
97
- expect(data[0].hash).toBe("af19ce34");
98
- expect(data[0].message).toBe("feat: add MCP server");
99
- });
100
-
101
- it("parses npm install output", () => {
102
- const output = `added 47 packages in 3.2s
103
- 2 vulnerabilities found`;
104
-
105
- const result = parseOutput("npm install", output);
106
- expect(result).not.toBeNull();
107
- expect(result!.parser).toBe("npm-install");
108
- const data = result!.data as any;
109
- expect(data.installed).toBe(47);
110
- expect(data.duration).toBe("3.2s");
111
- expect(data.vulnerabilities).toBe(2);
112
- });
113
-
114
- it("parses build output", () => {
115
- const output = `Compiling...
116
- 1 warning
117
- Found 0 errors
118
- Done in 2.5s`;
119
-
120
- const result = parseOutput("npm run build", output);
121
- expect(result).not.toBeNull();
122
- expect(result!.parser).toBe("build");
123
- const data = result!.data as any;
124
- expect(data.status).toBe("success");
125
- expect(data.warnings).toBe(1);
126
- });
127
-
128
- it("detects errors", () => {
129
- const output = `Error: EADDRINUSE: address already in use :3000`;
130
- const result = parseOutput("node server.js", output);
131
- expect(result).not.toBeNull();
132
- expect(result!.parser).toBe("error");
133
- const data = result!.data as any;
134
- expect(data.type).toBe("port_in_use");
135
- });
136
- });
137
-
138
- describe("estimateTokens", () => {
139
- it("estimates roughly 4 chars per token", () => {
140
- expect(estimateTokens("hello world")).toBe(3); // 11 chars / 4 = 2.75 → 3
141
- });
142
- });
143
-
144
- describe("tokenSavings", () => {
145
- it("calculates savings correctly", () => {
146
- const raw = "a".repeat(400); // 100 tokens
147
- const parsed = { status: "ok" };
148
- const result = tokenSavings(raw, parsed);
149
- expect(result.rawTokens).toBe(100);
150
- expect(result.saved).toBeGreaterThan(0);
151
- expect(result.percent).toBeGreaterThan(0);
152
- });
153
- });
@@ -1,98 +0,0 @@
1
- // Parser for test runner output (jest, vitest, bun test, pytest, go test)
2
-
3
- import type { Parser, TestResult } from "./base.js";
4
-
5
- export const testParser: Parser<TestResult> = {
6
- name: "test",
7
-
8
- detect(command: string, output: string): boolean {
9
- if (/\b(jest|vitest|bun\s+test|pytest|go\s+test|mocha|ava|tap)\b/.test(command)) return true;
10
- if (/\b(npm|bun|pnpm|yarn)\s+(run\s+)?test\b/.test(command)) return true;
11
- // Detect by output patterns
12
- return /Tests:\s+\d+/.test(output) || /\d+\s+(passing|passed|failed)/.test(output) || /PASS|FAIL/.test(output);
13
- },
14
-
15
- parse(_command: string, output: string): TestResult {
16
- const failures: { test: string; error: string }[] = [];
17
- let passed = 0, failed = 0, skipped = 0, duration: string | undefined;
18
-
19
- // Jest/Vitest style: Tests: 5 passed, 2 failed, 7 total
20
- const jestMatch = output.match(/Tests:\s+(?:(\d+)\s+passed)?[,\s]*(?:(\d+)\s+failed)?[,\s]*(?:(\d+)\s+skipped)?[,\s]*(\d+)\s+total/);
21
- if (jestMatch) {
22
- passed = parseInt(jestMatch[1] ?? "0");
23
- failed = parseInt(jestMatch[2] ?? "0");
24
- skipped = parseInt(jestMatch[3] ?? "0");
25
- }
26
-
27
- // Bun test style: 5 pass, 2 fail
28
- const bunMatch = output.match(/(\d+)\s+pass.*?(\d+)\s+fail/);
29
- if (!jestMatch && bunMatch) {
30
- passed = parseInt(bunMatch[1]);
31
- failed = parseInt(bunMatch[2]);
32
- }
33
-
34
- // Pytest style: 5 passed, 2 failed
35
- const pytestMatch = output.match(/(\d+)\s+passed(?:.*?(\d+)\s+failed)?/);
36
- if (!jestMatch && !bunMatch && pytestMatch) {
37
- passed = parseInt(pytestMatch[1]);
38
- failed = parseInt(pytestMatch[2] ?? "0");
39
- }
40
-
41
- // Go test: ok/FAIL + count
42
- const goPassMatch = output.match(/ok\s+\S+\s+([\d.]+s)/);
43
- const goFailMatch = output.match(/FAIL\s+\S+/);
44
- if (!jestMatch && !bunMatch && !pytestMatch && (goPassMatch || goFailMatch)) {
45
- const passLines = (output.match(/--- PASS/g) || []).length;
46
- const failLines = (output.match(/--- FAIL/g) || []).length;
47
- passed = passLines;
48
- failed = failLines;
49
- if (goPassMatch) duration = goPassMatch[1];
50
- }
51
-
52
- // Duration
53
- const timeMatch = output.match(/Time:\s+([\d.]+\s*(?:s|ms|m))/i) || output.match(/in\s+([\d.]+\s*(?:s|ms|m))/i);
54
- if (timeMatch) duration = timeMatch[1];
55
-
56
- // Extract failure details: lines starting with FAIL or ✗ or ×
57
- const lines = output.split("\n");
58
- let capturingFailure = false;
59
- let currentTest = "";
60
- let currentError: string[] = [];
61
-
62
- for (const line of lines) {
63
- const failMatch = line.match(/(?:FAIL|✗|×|✕)\s+(.+)/);
64
- if (failMatch) {
65
- if (capturingFailure && currentTest) {
66
- failures.push({ test: currentTest, error: currentError.join("\n").trim() });
67
- }
68
- currentTest = failMatch[1].trim();
69
- currentError = [];
70
- capturingFailure = true;
71
- continue;
72
- }
73
-
74
- if (capturingFailure) {
75
- if (line.match(/^(PASS|✓|✔|FAIL|✗|×|✕)\s/) || line.match(/^Tests:|^\d+ pass/)) {
76
- failures.push({ test: currentTest, error: currentError.join("\n").trim() });
77
- capturingFailure = false;
78
- currentTest = "";
79
- currentError = [];
80
- } else {
81
- currentError.push(line);
82
- }
83
- }
84
- }
85
- if (capturingFailure && currentTest) {
86
- failures.push({ test: currentTest, error: currentError.join("\n").trim() });
87
- }
88
-
89
- return {
90
- passed,
91
- failed,
92
- skipped,
93
- total: passed + failed + skipped,
94
- duration,
95
- failures,
96
- };
97
- },
98
- };
package/tsconfig.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "jsx": "react-jsx",
7
- "outDir": "./dist",
8
- "rootDir": "./src",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true
12
- },
13
- "include": ["src/**/*"],
14
- "exclude": ["src/**/*.test.ts"]
15
- }