@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,115 +0,0 @@
1
- #!/usr/bin/env bun
2
- // Reproducible benchmark: measures token savings across real commands
3
- // Run: bun benchmarks/benchmark.mjs
4
-
5
- import { compress, stripAnsi } from "../dist/compression.js";
6
- import { parseOutput, estimateTokens, tokenSavings } from "../dist/parsers/index.js";
7
- import { searchContent } from "../dist/search/index.js";
8
- import { diffOutput, clearDiffCache } from "../dist/diff-cache.js";
9
- import { smartDisplay } from "../dist/smart-display.js";
10
- import { stripNoise } from "../dist/noise-filter.js";
11
- import { rewriteCommand } from "../dist/command-rewriter.js";
12
- import { execSync } from "child_process";
13
-
14
- const cwd = process.cwd();
15
- const run = (cmd) => { try { return execSync(cmd, { encoding: "utf8", cwd, maxBuffer: 10*1024*1024 }).trim(); } catch(e) { return e.stdout?.trim() ?? ""; } };
16
-
17
- let totalRaw = 0, totalSaved = 0;
18
- const rows = [];
19
-
20
- function track(name, rawText, compressedText) {
21
- const raw = estimateTokens(rawText);
22
- const comp = estimateTokens(compressedText);
23
- const saved = Math.max(0, raw - comp);
24
- totalRaw += raw;
25
- totalSaved += saved;
26
- rows.push({ name, raw, comp, saved, pct: raw > 0 ? Math.round(saved/raw*100) : 0 });
27
- }
28
-
29
- console.log("open-terminal benchmark — measuring real token savings\n");
30
-
31
- // 1. Noise filter on npm install-like output
32
- const npmSim = "added 847 packages in 12s\n\n143 packages are looking for funding\n run `npm fund` for details\n\nfound 0 vulnerabilities\n";
33
- const npmClean = stripNoise(npmSim).cleaned;
34
- track("npm install (noise filter)", npmSim, npmClean);
35
-
36
- // 2. Command rewriting
37
- const rwTests = [
38
- ["find . -name '*.ts' | grep -v node_modules", "find pipe→filter"],
39
- ["cat package.json | grep name", "cat pipe→grep"],
40
- ["git log", "git log→oneline"],
41
- ["npm ls", "npm ls→depth0"],
42
- ];
43
- for (const [cmd, label] of rwTests) {
44
- const rw = rewriteCommand(cmd);
45
- if (rw.changed) {
46
- const rawOut = run(cmd) || cmd;
47
- const rwOut = run(rw.rewritten) || rw.rewritten;
48
- track(`rewrite: ${label}`, rawOut, rwOut);
49
- }
50
- }
51
-
52
- // 3. Structured parsing
53
- const gitStatus = run("git status");
54
- const gsParsed = parseOutput("git status", gitStatus);
55
- if (gsParsed) track("git status (structured)", gitStatus, JSON.stringify(gsParsed.data));
56
-
57
- const gitLog = run("git log -15");
58
- const glParsed = parseOutput("git log -15", gitLog);
59
- if (glParsed) track("git log -15 (structured)", gitLog, JSON.stringify(glParsed.data));
60
-
61
- // 4. Token budget compression
62
- const bigLs = run("ls -laR src/");
63
- const c1 = compress("ls -laR src/", bigLs, { maxTokens: 150 });
64
- track("ls -laR src/ (budget 150)", bigLs, c1.content);
65
-
66
- // 5. Search overflow guard
67
- const rawGrep = run("grep -rn export src/ | head -200");
68
- const search = await searchContent("export", cwd, { maxResults: 10 });
69
- track("grep export (overflow guard)", rawGrep, JSON.stringify(search));
70
-
71
- // 6. Smart display on paths
72
- const findPng = run("find . -name '*.png' -not -path '*/node_modules/*' 2>/dev/null | head -50");
73
- if (findPng) {
74
- const display = smartDisplay(findPng.split("\n"));
75
- track("find *.png (smart display)", findPng, display.join("\n"));
76
- }
77
-
78
- // 7. Diff caching (identical re-run)
79
- clearDiffCache();
80
- const testOut = run("bun test 2>&1");
81
- diffOutput("bun test", cwd, testOut);
82
- const d2 = diffOutput("bun test", cwd, testOut);
83
- track("bun test (identical re-run)", testOut, d2.diffSummary);
84
-
85
- // 8. Diff caching (fuzzy — simulated 95% similar)
86
- clearDiffCache();
87
- const testA = "PASS test1\nPASS test2\nPASS test3\nPASS test4\nPASS test5\nPASS test6\nPASS test7\nPASS test8\nPASS test9\nFAIL test10\nTests: 9 passed, 1 failed";
88
- const testB = "PASS test1\nPASS test2\nPASS test3\nPASS test4\nPASS test5\nPASS test6\nPASS test7\nPASS test8\nPASS test9\nPASS test10\nTests: 10 passed, 0 failed";
89
- diffOutput("test", "/tmp", testA);
90
- const fuzzyDiff = diffOutput("test", "/tmp", testB);
91
- track("test (fuzzy diff, 1 change)", testA, fuzzyDiff.added.join("\n") + "\n" + fuzzyDiff.removed.join("\n"));
92
-
93
- // 9. Budget compression on large ls
94
- const bigLs2 = run("ls -laR . 2>/dev/null | head -300");
95
- const c2 = compress("ls -laR .", bigLs2, { maxTokens: 100 });
96
- track("ls -laR . (budget 100, 300 lines)", bigLs2, c2.content);
97
-
98
- // Print results
99
- console.log("┌─────────────────────────────────────────────┬──────┬──────┬───────┬──────┐");
100
- console.log("│ Scenario │ Raw │ Comp │ Saved │ % │");
101
- console.log("├─────────────────────────────────────────────┼──────┼──────┼───────┼──────┤");
102
- for (const r of rows) {
103
- console.log("│ " + r.name.padEnd(43) + " │ " + String(r.raw).padStart(4) + " │ " + String(r.comp).padStart(4) + " │ " + String(r.saved).padStart(5) + " │ " + (r.pct + "%").padStart(4) + " │");
104
- }
105
- console.log("├─────────────────────────────────────────────┼──────┼──────┼───────┼──────┤");
106
- const pct = Math.round(totalSaved/totalRaw*100);
107
- console.log("│ " + "TOTAL".padEnd(43) + " │ " + String(totalRaw).padStart(4) + " │ " + String(totalRaw-totalSaved).padStart(4) + " │ " + String(totalSaved).padStart(5) + " │ " + (pct + "%").padStart(4) + " │");
108
- console.log("└─────────────────────────────────────────────┴──────┴──────┴───────┴──────┘");
109
-
110
- // Cost analysis
111
- const sonnetRate = 3.0;
112
- const cerebrasInputRate = 0.60;
113
- const savingsUsd = totalSaved * sonnetRate / 1_000_000;
114
- console.log(`\nAt Claude Sonnet $3/M: ${totalSaved} tokens saved = $${savingsUsd.toFixed(6)}`);
115
- console.log(`At 500 commands/day: ~$${(savingsUsd * 50).toFixed(2)}/day, $${(savingsUsd * 50 * 30).toFixed(0)}/month saved`);
File without changes
@@ -1,49 +0,0 @@
1
- import { describe, it, expect } from "bun:test";
2
- import { compress, stripAnsi } from "./compression.js";
3
-
4
- describe("stripAnsi", () => {
5
- it("removes ANSI escape codes", () => {
6
- expect(stripAnsi("\x1b[31mred\x1b[0m")).toBe("red");
7
- expect(stripAnsi("\x1b[1;32mbold green\x1b[0m")).toBe("bold green");
8
- });
9
-
10
- it("leaves clean text unchanged", () => {
11
- expect(stripAnsi("hello world")).toBe("hello world");
12
- });
13
- });
14
-
15
- describe("compress", () => {
16
- it("strips ANSI by default", () => {
17
- const result = compress("ls", "\x1b[32mfile.ts\x1b[0m");
18
- expect(result.content).not.toContain("\x1b");
19
- });
20
-
21
- it("uses structured parser when format=json", () => {
22
- const output = `total 16
23
- -rw-r--r-- 1 user staff 450 Mar 10 09:00 package.json
24
- drwxr-xr-x 5 user staff 160 Mar 10 09:00 src`;
25
-
26
- const result = compress("ls -la", output, { format: "json" });
27
- // Parser may skip JSON if it's larger than raw — just check it returned something
28
- expect(result.content).toBeTruthy();
29
- expect(result.compressedTokens).toBeGreaterThan(0);
30
- });
31
-
32
- it("respects maxTokens budget", () => {
33
- const longOutput = Array.from({ length: 100 }, (_, i) => `Line ${i}: some output text here`).join("\n");
34
- const result = compress("some-command", longOutput, { maxTokens: 50 });
35
- expect(result.compressedTokens).toBeLessThanOrEqual(60); // allow some slack
36
- });
37
-
38
- it("deduplicates similar lines", () => {
39
- const output = Array.from({ length: 20 }, (_, i) => `Compiling module ${i}...`).join("\n");
40
- const result = compress("build", output);
41
- expect(result.compressedTokens).toBeLessThan(result.originalTokens);
42
- });
43
-
44
- it("tracks savings on large output", () => {
45
- const output = Array.from({ length: 100 }, (_, i) => `Line ${i}: some long output text here that takes tokens`).join("\n");
46
- const result = compress("cmd", output, { maxTokens: 50 });
47
- expect(result.compressedTokens).toBeLessThan(result.originalTokens);
48
- });
49
- });
@@ -1,56 +0,0 @@
1
- // Output intelligence router — auto-detect command type and optimize output
2
-
3
- import { parseOutput, estimateTokens } from "./parsers/index.js";
4
- import { compress, stripAnsi } from "./compression.js";
5
- import { recordSaving } from "./economy.js";
6
-
7
- export interface RouterResult {
8
- raw: string;
9
- structured?: unknown;
10
- compressed?: string;
11
- parser?: string;
12
- tokensSaved: number;
13
- format: "raw" | "json" | "compressed";
14
- }
15
-
16
- /** Route command output through the best optimization path */
17
- export function routeOutput(command: string, output: string, maxTokens?: number): RouterResult {
18
- const clean = stripAnsi(output);
19
- const rawTokens = estimateTokens(clean);
20
-
21
- // Try structured parsing first
22
- const parsed = parseOutput(command, clean);
23
- if (parsed) {
24
- const json = JSON.stringify(parsed.data);
25
- const jsonTokens = estimateTokens(json);
26
- const saved = rawTokens - jsonTokens;
27
-
28
- if (saved > 0) {
29
- recordSaving("structured", saved);
30
- return {
31
- raw: clean,
32
- structured: parsed.data,
33
- parser: parsed.parser,
34
- tokensSaved: saved,
35
- format: "json",
36
- };
37
- }
38
- }
39
-
40
- // Try compression if structured didn't save enough
41
- if (maxTokens || rawTokens > 200) {
42
- const compressed = compress(command, clean, { maxTokens, format: "text" });
43
- if (compressed.tokensSaved > 0) {
44
- recordSaving("compressed", compressed.tokensSaved);
45
- return {
46
- raw: clean,
47
- compressed: compressed.content,
48
- tokensSaved: compressed.tokensSaved,
49
- format: "compressed",
50
- };
51
- }
52
- }
53
-
54
- // Return raw if no optimization helps
55
- return { raw: clean, tokensSaved: 0, format: "raw" };
56
- }
@@ -1,72 +0,0 @@
1
- // Base types for output parsers
2
-
3
- export interface Parser<T = unknown> {
4
- /** Name of this parser */
5
- readonly name: string;
6
-
7
- /** Test if this parser can handle the given command/output */
8
- detect(command: string, output: string): boolean;
9
-
10
- /** Parse the output into structured data */
11
- parse(command: string, output: string): T;
12
- }
13
-
14
- export interface FileEntry {
15
- name: string;
16
- type: "file" | "dir" | "symlink" | "other";
17
- size?: number;
18
- modified?: string;
19
- permissions?: string;
20
- }
21
-
22
- export interface TestResult {
23
- passed: number;
24
- failed: number;
25
- skipped: number;
26
- total: number;
27
- duration?: string;
28
- failures: { test: string; error: string }[];
29
- }
30
-
31
- export interface GitLogEntry {
32
- hash: string;
33
- author: string;
34
- date: string;
35
- message: string;
36
- }
37
-
38
- export interface GitStatus {
39
- branch: string;
40
- staged: string[];
41
- unstaged: string[];
42
- untracked: string[];
43
- }
44
-
45
- export interface BuildResult {
46
- status: "success" | "failure";
47
- warnings: number;
48
- errors: number;
49
- duration?: string;
50
- output?: string;
51
- }
52
-
53
- export interface NpmInstallResult {
54
- installed: number;
55
- duration?: string;
56
- vulnerabilities: number;
57
- }
58
-
59
- export interface ErrorInfo {
60
- type: string;
61
- message: string;
62
- file?: string;
63
- line?: number;
64
- suggestion?: string;
65
- }
66
-
67
- export interface SearchResult {
68
- total: number;
69
- source: FileEntry[];
70
- other: FileEntry[];
71
- filtered: { count: number; reason: string }[];
72
- }
@@ -1,73 +0,0 @@
1
- // Parser for build output (npm/bun/pnpm build, tsc, webpack, vite, etc.)
2
-
3
- import type { Parser, BuildResult, NpmInstallResult } from "./base.js";
4
-
5
- export const buildParser: Parser<BuildResult> = {
6
- name: "build",
7
-
8
- detect(command: string, output: string): boolean {
9
- if (/\b(npm|bun|pnpm|yarn)\s+(run\s+)?build\b/.test(command)) return true;
10
- if (/\btsc\b/.test(command)) return true;
11
- if (/\b(webpack|vite|esbuild|rollup|turbo)\b/.test(command)) return true;
12
- return /\b(compiled|bundled|built)\b/i.test(output) && /\b(success|error|warning)\b/i.test(output);
13
- },
14
-
15
- parse(_command: string, output: string): BuildResult {
16
- const lines = output.split("\n");
17
- let warnings = 0, errors = 0, duration: string | undefined;
18
-
19
- // Count warnings and errors
20
- for (const line of lines) {
21
- if (/\bwarning\b/i.test(line)) warnings++;
22
- if (/\berror\b/i.test(line) && !/0 errors/.test(line)) errors++;
23
- }
24
-
25
- // Specific patterns
26
- const tscErrors = output.match(/Found (\d+) error/);
27
- if (tscErrors) errors = parseInt(tscErrors[1]);
28
-
29
- const warningCount = output.match(/(\d+)\s+warning/);
30
- if (warningCount) warnings = parseInt(warningCount[1]);
31
-
32
- // Duration
33
- const timeMatch = output.match(/(?:in|took)\s+([\d.]+\s*(?:s|ms|m))/i) ||
34
- output.match(/Done in ([\d.]+s)/);
35
- if (timeMatch) duration = timeMatch[1];
36
-
37
- const status: "success" | "failure" = errors > 0 ? "failure" : "success";
38
-
39
- return { status, warnings, errors, duration };
40
- },
41
- };
42
-
43
- export const npmInstallParser: Parser<NpmInstallResult> = {
44
- name: "npm-install",
45
-
46
- detect(command: string, _output: string): boolean {
47
- return /\b(npm|bun|pnpm|yarn)\s+(install|add|i)\b/.test(command);
48
- },
49
-
50
- parse(_command: string, output: string): NpmInstallResult {
51
- let installed = 0, vulnerabilities = 0, duration: string | undefined;
52
-
53
- // npm: added 47 packages in 3.2s
54
- const npmMatch = output.match(/added\s+(\d+)\s+packages?\s+in\s+([\d.]+s)/);
55
- if (npmMatch) {
56
- installed = parseInt(npmMatch[1]);
57
- duration = npmMatch[2];
58
- }
59
-
60
- // bun: 47 packages installed [1.2s]
61
- const bunMatch = output.match(/(\d+)\s+packages?\s+installed.*?\[([\d.]+[ms]*s)\]/);
62
- if (!npmMatch && bunMatch) {
63
- installed = parseInt(bunMatch[1]);
64
- duration = bunMatch[2];
65
- }
66
-
67
- // Vulnerabilities
68
- const vulnMatch = output.match(/(\d+)\s+vulnerabilit/);
69
- if (vulnMatch) vulnerabilities = parseInt(vulnMatch[1]);
70
-
71
- return { installed, vulnerabilities, duration };
72
- },
73
- };
@@ -1,107 +0,0 @@
1
- // Parser for common error patterns
2
-
3
- import type { Parser, ErrorInfo } from "./base.js";
4
-
5
- const ERROR_PATTERNS: { type: string; pattern: RegExp; extract: (m: RegExpMatchArray, output: string) => ErrorInfo }[] = [
6
- {
7
- type: "port_in_use",
8
- pattern: /EADDRINUSE.*?(?::(\d+))|port\s+(\d+)\s+(?:is\s+)?(?:already\s+)?in\s+use/i,
9
- extract: (m) => ({
10
- type: "port_in_use",
11
- message: m[0],
12
- suggestion: `Kill the process: lsof -i :${m[1] ?? m[2]} -t | xargs kill`,
13
- }),
14
- },
15
- {
16
- type: "file_not_found",
17
- pattern: /ENOENT.*?'([^']+)'|No such file or directory:\s*(.+)/,
18
- extract: (m) => ({
19
- type: "file_not_found",
20
- message: m[0],
21
- file: m[1] ?? m[2]?.trim(),
22
- suggestion: "Check the file path exists",
23
- }),
24
- },
25
- {
26
- type: "permission_denied",
27
- pattern: /EACCES.*?'([^']+)'|Permission denied:\s*(.+)/,
28
- extract: (m) => ({
29
- type: "permission_denied",
30
- message: m[0],
31
- file: m[1] ?? m[2]?.trim(),
32
- suggestion: "Check file permissions or run with sudo",
33
- }),
34
- },
35
- {
36
- type: "command_not_found",
37
- pattern: /command not found:\s*(\S+)|(\S+):\s*not found/,
38
- extract: (m) => ({
39
- type: "command_not_found",
40
- message: m[0],
41
- suggestion: `Install ${m[1] ?? m[2]} or check your PATH`,
42
- }),
43
- },
44
- {
45
- type: "dependency_missing",
46
- pattern: /Cannot find module\s+'([^']+)'|Module not found.*?'([^']+)'/,
47
- extract: (m) => ({
48
- type: "dependency_missing",
49
- message: m[0],
50
- suggestion: `Install: npm install ${m[1] ?? m[2]}`,
51
- }),
52
- },
53
- {
54
- type: "syntax_error",
55
- pattern: /SyntaxError:\s*(.+)|error TS\d+:\s*(.+)/,
56
- extract: (m, output) => {
57
- const fileMatch = output.match(/(\S+\.\w+):(\d+)/);
58
- return {
59
- type: "syntax_error",
60
- message: m[1] ?? m[2] ?? m[0],
61
- file: fileMatch?.[1],
62
- line: fileMatch ? parseInt(fileMatch[2]) : undefined,
63
- suggestion: "Fix the syntax error in the referenced file",
64
- };
65
- },
66
- },
67
- {
68
- type: "out_of_memory",
69
- pattern: /ENOMEM|JavaScript heap out of memory|Killed/,
70
- extract: (m) => ({
71
- type: "out_of_memory",
72
- message: m[0],
73
- suggestion: "Increase memory: NODE_OPTIONS=--max-old-space-size=4096",
74
- }),
75
- },
76
- {
77
- type: "network_error",
78
- pattern: /ECONNREFUSED|ENOTFOUND|ETIMEDOUT|fetch failed/,
79
- extract: (m) => ({
80
- type: "network_error",
81
- message: m[0],
82
- suggestion: "Check network connection and target URL/host",
83
- }),
84
- },
85
- ];
86
-
87
- export const errorParser: Parser<ErrorInfo> = {
88
- name: "error",
89
-
90
- detect(_command: string, output: string): boolean {
91
- return ERROR_PATTERNS.some(({ pattern }) => pattern.test(output));
92
- },
93
-
94
- parse(_command: string, output: string): ErrorInfo {
95
- for (const { pattern, extract } of ERROR_PATTERNS) {
96
- const match = output.match(pattern);
97
- if (match) return extract(match, output);
98
- }
99
-
100
- // Generic error fallback
101
- const errorLine = output.split("\n").find(l => /error/i.test(l));
102
- return {
103
- type: "unknown",
104
- message: errorLine?.trim() ?? "Unknown error",
105
- };
106
- },
107
- };
@@ -1,91 +0,0 @@
1
- // Parser for file listing output (ls -la, find, etc.)
2
-
3
- import type { Parser, FileEntry, SearchResult } from "./base.js";
4
-
5
- const NODE_MODULES_RE = /node_modules/;
6
- const DIST_RE = /\b(dist|build|\.next|__pycache__|coverage|\.git)\b/;
7
- const SOURCE_EXTS = /\.(ts|tsx|js|jsx|py|go|rs|java|rb|sh|c|cpp|h|css|scss|html|vue|svelte|md|json|yaml|yml|toml)$/;
8
-
9
- export const lsParser: Parser<FileEntry[]> = {
10
- name: "ls",
11
-
12
- detect(command: string, output: string): boolean {
13
- return /^\s*(ls|ll|la)\b/.test(command) && output.includes(" ");
14
- },
15
-
16
- parse(_command: string, output: string): FileEntry[] {
17
- const lines = output.split("\n").filter(l => l.trim());
18
- const entries: FileEntry[] = [];
19
-
20
- for (const line of lines) {
21
- // ls -la format: drwxr-xr-x 5 user group 160 Mar 10 09:00 dirname
22
- const match = line.match(/^([dlcbps-])([rwxsStT-]{9})\s+\d+\s+\S+\s+\S+\s+(\d+)\s+(\w+\s+\d+\s+[\d:]+)\s+(.+)$/);
23
- if (match) {
24
- const typeChar = match[1];
25
- entries.push({
26
- name: match[5],
27
- type: typeChar === "d" ? "dir" : typeChar === "l" ? "symlink" : "file",
28
- size: parseInt(match[3]),
29
- modified: match[4],
30
- permissions: match[1] + match[2],
31
- });
32
- } else if (line.trim() && !line.startsWith("total ")) {
33
- // Simple ls output — just filenames
34
- entries.push({ name: line.trim(), type: "file" });
35
- }
36
- }
37
-
38
- return entries;
39
- },
40
- };
41
-
42
- export const findParser: Parser<SearchResult> = {
43
- name: "find",
44
-
45
- detect(command: string, _output: string): boolean {
46
- return /^\s*(find|fd)\b/.test(command);
47
- },
48
-
49
- parse(_command: string, output: string): SearchResult {
50
- const lines = output.split("\n").filter(l => l.trim());
51
- const source: FileEntry[] = [];
52
- const other: FileEntry[] = [];
53
- let nodeModulesCount = 0;
54
- let distCount = 0;
55
-
56
- for (const line of lines) {
57
- const path = line.trim();
58
- if (!path) continue;
59
-
60
- if (NODE_MODULES_RE.test(path)) {
61
- nodeModulesCount++;
62
- continue;
63
- }
64
-
65
- if (DIST_RE.test(path)) {
66
- distCount++;
67
- continue;
68
- }
69
-
70
- const name = path.split("/").pop() ?? path;
71
- const entry: FileEntry = { name: path, type: SOURCE_EXTS.test(name) ? "file" : "other" };
72
-
73
- if (SOURCE_EXTS.test(name)) {
74
- source.push(entry);
75
- } else {
76
- other.push(entry);
77
- }
78
- }
79
-
80
- const filtered: { count: number; reason: string }[] = [];
81
- if (nodeModulesCount > 0) filtered.push({ count: nodeModulesCount, reason: "node_modules" });
82
- if (distCount > 0) filtered.push({ count: distCount, reason: "dist/build" });
83
-
84
- return {
85
- total: lines.length,
86
- source,
87
- other,
88
- filtered,
89
- };
90
- },
91
- };
@@ -1,101 +0,0 @@
1
- // Parsers for git output (log, status, diff)
2
-
3
- import type { Parser, GitLogEntry, GitStatus } from "./base.js";
4
-
5
- export const gitLogParser: Parser<GitLogEntry[]> = {
6
- name: "git-log",
7
-
8
- detect(command: string, _output: string): boolean {
9
- return /\bgit\s+log\b/.test(command);
10
- },
11
-
12
- parse(_command: string, output: string): GitLogEntry[] {
13
- const entries: GitLogEntry[] = [];
14
- const lines = output.split("\n");
15
-
16
- // Detect oneline format: "abc1234 commit message"
17
- const firstLine = lines[0]?.trim() ?? "";
18
- const isOneline = /^[a-f0-9]{7,12}\s+/.test(firstLine) && !firstLine.startsWith("commit ");
19
-
20
- if (isOneline) {
21
- for (const line of lines) {
22
- const match = line.trim().match(/^([a-f0-9]{7,12})\s+(.+)$/);
23
- if (match) {
24
- entries.push({ hash: match[1], author: "", date: "", message: match[2] });
25
- }
26
- }
27
- return entries;
28
- }
29
-
30
- // Verbose format
31
- let hash = "", author = "", date = "", message: string[] = [];
32
-
33
- for (const line of lines) {
34
- const commitMatch = line.match(/^commit\s+([a-f0-9]+)/);
35
- if (commitMatch) {
36
- if (hash) {
37
- entries.push({ hash: hash.slice(0, 8), author, date, message: message.join(" ").trim() });
38
- }
39
- hash = commitMatch[1];
40
- author = ""; date = ""; message = [];
41
- continue;
42
- }
43
-
44
- const authorMatch = line.match(/^Author:\s+(.+)/);
45
- if (authorMatch) { author = authorMatch[1]; continue; }
46
-
47
- const dateMatch = line.match(/^Date:\s+(.+)/);
48
- if (dateMatch) { date = dateMatch[1].trim(); continue; }
49
-
50
- if (line.startsWith(" ")) {
51
- message.push(line.trim());
52
- }
53
- }
54
-
55
- if (hash) {
56
- entries.push({ hash: hash.slice(0, 8), author, date, message: message.join(" ").trim() });
57
- }
58
-
59
- return entries;
60
- },
61
- };
62
-
63
- export const gitStatusParser: Parser<GitStatus> = {
64
- name: "git-status",
65
-
66
- detect(command: string, _output: string): boolean {
67
- return /\bgit\s+status\b/.test(command);
68
- },
69
-
70
- parse(_command: string, output: string): GitStatus {
71
- const lines = output.split("\n");
72
- let branch = "";
73
- const staged: string[] = [];
74
- const unstaged: string[] = [];
75
- const untracked: string[] = [];
76
-
77
- const branchMatch = output.match(/On branch\s+(\S+)/);
78
- if (branchMatch) branch = branchMatch[1];
79
-
80
- let section = "";
81
- for (const line of lines) {
82
- if (line.includes("Changes to be committed")) { section = "staged"; continue; }
83
- if (line.includes("Changes not staged")) { section = "unstaged"; continue; }
84
- if (line.includes("Untracked files")) { section = "untracked"; continue; }
85
-
86
- const fileMatch = line.match(/^\s+(?:new file|modified|deleted|renamed):\s+(.+)/);
87
- if (fileMatch) {
88
- if (section === "staged") staged.push(fileMatch[1].trim());
89
- else if (section === "unstaged") unstaged.push(fileMatch[1].trim());
90
- continue;
91
- }
92
-
93
- // Untracked files are just indented filenames
94
- if (section === "untracked" && line.match(/^\s+\S/) && !line.includes("(use ")) {
95
- untracked.push(line.trim());
96
- }
97
- }
98
-
99
- return { branch, staged, unstaged, untracked };
100
- },
101
- };