shrinker-ai 0.1.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.
Files changed (35) hide show
  1. package/.copilot-instructions.md +2 -0
  2. package/CLAUDE.md +2 -0
  3. package/README.md +295 -0
  4. package/dist/src/cli.js +265 -0
  5. package/dist/src/execution/raw-output-store.js +132 -0
  6. package/dist/src/execution/run-command.js +172 -0
  7. package/dist/src/filters/cat.js +13 -0
  8. package/dist/src/filters/docker.js +42 -0
  9. package/dist/src/filters/find.js +63 -0
  10. package/dist/src/filters/generic-log.js +38 -0
  11. package/dist/src/filters/gh.js +38 -0
  12. package/dist/src/filters/git-diff.js +56 -0
  13. package/dist/src/filters/git-list.js +25 -0
  14. package/dist/src/filters/git-log.js +256 -0
  15. package/dist/src/filters/git-status.js +67 -0
  16. package/dist/src/filters/kubectl.js +76 -0
  17. package/dist/src/filters/npm.js +44 -0
  18. package/dist/src/filters/rg.js +64 -0
  19. package/dist/src/filters/select-filter.js +119 -0
  20. package/dist/src/filters/table.js +27 -0
  21. package/dist/src/filters/tail.js +6 -0
  22. package/dist/src/filters/test-output.js +54 -0
  23. package/dist/src/filters/types.js +2 -0
  24. package/dist/src/formatting/ansi.js +13 -0
  25. package/dist/src/formatting/limits.js +17 -0
  26. package/dist/src/metrics/dashboard.js +138 -0
  27. package/dist/src/metrics/measure.js +29 -0
  28. package/dist/src/metrics/stats-store.js +166 -0
  29. package/integrations/install-shrinker.ps1 +166 -0
  30. package/integrations/install.ps1 +46 -0
  31. package/integrations/shrinker-profile.ps1 +136 -0
  32. package/integrations/uninstall-shrinker.ps1 +79 -0
  33. package/integrations/uninstall.ps1 +35 -0
  34. package/package.json +31 -0
  35. package/templates/agent-rules.md +19 -0
@@ -0,0 +1,256 @@
1
+ import { cleanText } from "../formatting/ansi.js";
2
+ import { limitLines } from "../formatting/limits.js";
3
+ const ONELINE_PATTERN = /^[0-9a-f]{7,40}(?:\s+\([^)]+\))?\s+\S/i;
4
+ const BODY_LIMIT = 3;
5
+ const TRAILER_PATTERN = /^(?:Signed-off-by|Co-authored-by):/i;
6
+ const RAW_SHAPE_FLAGS = new Set([
7
+ "-L",
8
+ "-c",
9
+ "-p",
10
+ "-u",
11
+ "--cc",
12
+ "--dirstat",
13
+ "--name-only",
14
+ "--name-status",
15
+ "--numstat",
16
+ "--patch",
17
+ "--patch-with-raw",
18
+ "--patch-with-stat",
19
+ "--raw",
20
+ "--remerge-diff",
21
+ "--shortstat",
22
+ "--show-signature",
23
+ "--stat",
24
+ "--summary",
25
+ ]);
26
+ const VALUE_FLAGS = new Set([
27
+ "-G",
28
+ "-L",
29
+ "-O",
30
+ "-S",
31
+ "-n",
32
+ "--after",
33
+ "--author",
34
+ "--before",
35
+ "--committer",
36
+ "--date",
37
+ "--encoding",
38
+ "--grep",
39
+ "--max-count",
40
+ "--since",
41
+ "--skip",
42
+ "--until",
43
+ ]);
44
+ const SAFE_COMPACT_FLAGS = new Set([
45
+ "-E",
46
+ "-F",
47
+ "-i",
48
+ "-n",
49
+ "--all",
50
+ "--all-match",
51
+ "--ancestry-path",
52
+ "--author",
53
+ "--before",
54
+ "--branches",
55
+ "--committer",
56
+ "--date-order",
57
+ "--decorate",
58
+ "--exclude",
59
+ "--extended-regexp",
60
+ "--first-parent",
61
+ "--fixed-strings",
62
+ "--glob",
63
+ "--grep",
64
+ "--invert-grep",
65
+ "--max-count",
66
+ "--merges",
67
+ "--no-color",
68
+ "--no-decorate",
69
+ "--no-merges",
70
+ "--not",
71
+ "--remotes",
72
+ "--reverse",
73
+ "--since",
74
+ "--tags",
75
+ "--topo-order",
76
+ "--until",
77
+ ]);
78
+ function gitLogFlags(command) {
79
+ const logIndex = command.findIndex((part) => part.toLowerCase() === "log");
80
+ if (logIndex < 0)
81
+ return [];
82
+ const flags = [];
83
+ for (let index = logIndex + 1; index < command.length; index += 1) {
84
+ const part = command[index];
85
+ if (!part || part === "--")
86
+ break;
87
+ if (part.startsWith("-")) {
88
+ flags.push(part);
89
+ if (VALUE_FLAGS.has(part))
90
+ index += 1;
91
+ }
92
+ }
93
+ return flags;
94
+ }
95
+ function requestsRawShape(flags) {
96
+ return flags.some((flag) => RAW_SHAPE_FLAGS.has(flag) ||
97
+ flag.startsWith("--stat=") ||
98
+ flag.startsWith("--dirstat="));
99
+ }
100
+ function requestsCustomFormat(flags) {
101
+ return flags.some((flag) => flag === "--oneline" ||
102
+ flag.startsWith("--pretty") ||
103
+ flag.startsWith("--format"));
104
+ }
105
+ function isSafeCompactFlag(flag) {
106
+ if (/^-\d+$/.test(flag))
107
+ return true;
108
+ if (flag.startsWith("--max-count=") ||
109
+ flag.startsWith("--author=") ||
110
+ flag.startsWith("--committer=") ||
111
+ flag.startsWith("--grep=") ||
112
+ flag.startsWith("--since=") ||
113
+ flag.startsWith("--after=") ||
114
+ flag.startsWith("--until=") ||
115
+ flag.startsWith("--before=") ||
116
+ flag.startsWith("--branches=") ||
117
+ flag.startsWith("--tags=") ||
118
+ flag.startsWith("--remotes=") ||
119
+ flag.startsWith("--glob=") ||
120
+ flag.startsWith("--exclude=") ||
121
+ flag.startsWith("--min-parents=") ||
122
+ flag.startsWith("--max-parents=") ||
123
+ flag.startsWith("--decorate=")) {
124
+ return true;
125
+ }
126
+ return SAFE_COMPACT_FLAGS.has(flag);
127
+ }
128
+ function hasStructuredLogDetails(lines) {
129
+ let afterSubject = false;
130
+ for (const line of lines) {
131
+ if (/^commit\s+[0-9a-f]{7,40}/i.test(line)) {
132
+ afterSubject = false;
133
+ continue;
134
+ }
135
+ if (/^Date:\s+/.test(line))
136
+ continue;
137
+ if (/^\s{4}\S/.test(line) && !afterSubject) {
138
+ afterSubject = true;
139
+ continue;
140
+ }
141
+ if (/^\s+\S.*\|\s+\d+/.test(line) ||
142
+ /^\s*\d+\s+files?\s+changed/.test(line) ||
143
+ /^\d+\s+\d+\s+\S/.test(line) ||
144
+ /^[ACDMRTUXB]\d*\s+\S/.test(line) ||
145
+ (afterSubject &&
146
+ line.trim() &&
147
+ !/^\s/.test(line) &&
148
+ !/^(?:Author|Merge):\s+/.test(line))) {
149
+ return true;
150
+ }
151
+ }
152
+ return false;
153
+ }
154
+ export function filterGitLog(input, options) {
155
+ const cleaned = cleanText(input);
156
+ const lines = cleaned.split("\n");
157
+ const flags = gitLogFlags(options.command ?? []);
158
+ if (requestsRawShape(flags) ||
159
+ requestsCustomFormat(flags) ||
160
+ flags.some((flag) => !isSafeCompactFlag(flag)) ||
161
+ lines.some((line) => line.startsWith("diff --git ")) ||
162
+ hasStructuredLogDetails(lines)) {
163
+ return {
164
+ output: cleaned,
165
+ kind: "git-log",
166
+ omitted: false,
167
+ notes: ["explicit Git output format preserved"],
168
+ };
169
+ }
170
+ if (lines.length > 0 && lines.every((line) => !line.trim() || ONELINE_PATTERN.test(line))) {
171
+ const limited = limitLines(lines.filter(Boolean), options.maxLines);
172
+ return {
173
+ output: limited.lines.join("\n"),
174
+ kind: "git-log",
175
+ omitted: limited.omitted > 0,
176
+ recovery: "always",
177
+ notes: limited.omitted > 0 ? [`omitted ${limited.omitted} commits`] : [],
178
+ };
179
+ }
180
+ const commits = [];
181
+ let current;
182
+ let phase = "metadata";
183
+ for (const rawLine of lines) {
184
+ const commitMatch = rawLine.match(/^commit\s+([0-9a-f]{7,40})(?:\s+\(([^)]+)\))?/i);
185
+ if (commitMatch) {
186
+ current = {
187
+ hash: (commitMatch[1] ?? "").slice(0, 10),
188
+ ...(commitMatch[2] ? { refs: commitMatch[2] } : {}),
189
+ body: [],
190
+ };
191
+ commits.push(current);
192
+ phase = "metadata";
193
+ continue;
194
+ }
195
+ if (!current)
196
+ continue;
197
+ const authorMatch = rawLine.match(/^Author:\s+(.+?)(?:\s+<[^>]+>)?$/);
198
+ const author = authorMatch?.[1];
199
+ if (author) {
200
+ current.author = author.trim();
201
+ continue;
202
+ }
203
+ const dateMatch = rawLine.match(/^Date:\s+(.+)$/);
204
+ const date = dateMatch?.[1];
205
+ if (date) {
206
+ const parsed = new Date(date);
207
+ current.date = Number.isNaN(parsed.valueOf()) ? date.trim() : parsed.toISOString().slice(0, 10);
208
+ phase = "subject";
209
+ continue;
210
+ }
211
+ const content = rawLine.trim();
212
+ if (phase === "subject" && content) {
213
+ current.subject = content;
214
+ phase = "body";
215
+ }
216
+ else if (phase === "body" && content && !TRAILER_PATTERN.test(content)) {
217
+ current.body.push(content);
218
+ }
219
+ }
220
+ if (commits.length === 0 || commits.some((commit) => !commit.subject)) {
221
+ const limited = limitLines(lines, options.maxLines);
222
+ return {
223
+ output: limited.lines.join("\n"),
224
+ kind: "git-log",
225
+ omitted: limited.omitted > 0,
226
+ recovery: "always",
227
+ notes: ["unrecognized log format; returned cleaned output"],
228
+ };
229
+ }
230
+ let omittedBodyLines = 0;
231
+ const compact = commits.flatMap((commit) => {
232
+ const refs = commit.refs ? ` (${commit.refs})` : "";
233
+ const details = [commit.author, commit.date].filter(Boolean).join(", ");
234
+ const header = `${commit.hash}${refs} ${commit.subject}${details ? ` — ${details}` : ""}`;
235
+ const body = commit.body.slice(0, BODY_LIMIT).map((line) => ` ${line}`);
236
+ const omitted = Math.max(0, commit.body.length - BODY_LIMIT);
237
+ omittedBodyLines += omitted;
238
+ if (omitted > 0)
239
+ body.push(` [+${omitted} body ${omitted === 1 ? "line" : "lines"} omitted]`);
240
+ return [header, ...body];
241
+ });
242
+ const limited = limitLines(compact, options.maxLines);
243
+ const meaningfulOmission = omittedBodyLines > 0 || limited.omitted > 0;
244
+ return {
245
+ output: limited.lines.join("\n"),
246
+ kind: "git-log",
247
+ omitted: true,
248
+ recovery: meaningfulOmission ? "always" : "threshold",
249
+ notes: limited.omitted > 0
250
+ ? [`omitted verbose metadata and ${limited.omitted} output lines`]
251
+ : omittedBodyLines > 0
252
+ ? [`omitted verbose metadata and ${omittedBodyLines} body lines`]
253
+ : ["omitted verbose metadata"],
254
+ };
255
+ }
256
+ //# sourceMappingURL=git-log.js.map
@@ -0,0 +1,67 @@
1
+ import { cleanText } from "../formatting/ansi.js";
2
+ import { limitLines } from "../formatting/limits.js";
3
+ const SECTION_MAP = [
4
+ [/^Changes to be committed:/i, "staged"],
5
+ [/^Changes not staged for commit:/i, "unstaged"],
6
+ [/^Untracked files:/i, "untracked"],
7
+ [/^Unmerged paths:/i, "conflicts"],
8
+ ];
9
+ export function filterGitStatus(input, options) {
10
+ const lines = cleanText(input).split("\n");
11
+ const branch = [];
12
+ const groups = new Map();
13
+ let currentSection;
14
+ for (const rawLine of lines) {
15
+ const line = rawLine.trim();
16
+ if (!line)
17
+ continue;
18
+ if (line.startsWith("(") ||
19
+ line.startsWith("no changes added") ||
20
+ line.startsWith("nothing to commit")) {
21
+ continue;
22
+ }
23
+ const section = SECTION_MAP.find(([pattern]) => pattern.test(line));
24
+ if (section) {
25
+ currentSection = section[1];
26
+ continue;
27
+ }
28
+ if (line.startsWith("On branch ") ||
29
+ line.startsWith("Your branch ") ||
30
+ line.startsWith("HEAD detached")) {
31
+ branch.push(line.replace(/^On branch /, "branch: "));
32
+ continue;
33
+ }
34
+ const fileMatch = line.match(/^(modified|new file|deleted|renamed|copied|both modified|added by us|deleted by them):\s+(.+)$/i);
35
+ if (fileMatch) {
36
+ const state = fileMatch[1] ?? "changed";
37
+ const path = fileMatch[2] ?? line;
38
+ const group = currentSection ?? "changed";
39
+ groups.set(group, [...(groups.get(group) ?? []), `${state}: ${path}`]);
40
+ continue;
41
+ }
42
+ if (currentSection === "untracked" && !line.startsWith("(")) {
43
+ groups.set("untracked", [...(groups.get("untracked") ?? []), line]);
44
+ }
45
+ }
46
+ const output = [...branch];
47
+ for (const [name, files] of groups) {
48
+ output.push(`${name} (${files.length}):`);
49
+ output.push(...files.map((file) => ` ${file}`));
50
+ }
51
+ if (output.length === 0) {
52
+ return {
53
+ output: lines.join("\n"),
54
+ kind: "git-status",
55
+ omitted: false,
56
+ notes: ["unrecognized status format; returned cleaned output"],
57
+ };
58
+ }
59
+ const limited = limitLines(output, options.maxLines);
60
+ return {
61
+ output: limited.lines.join("\n"),
62
+ kind: "git-status",
63
+ omitted: limited.omitted > 0 || output.join("\n").length < cleanText(input).length,
64
+ notes: [],
65
+ };
66
+ }
67
+ //# sourceMappingURL=git-status.js.map
@@ -0,0 +1,76 @@
1
+ import { cleanText } from "../formatting/ansi.js";
2
+ import { limitLines } from "../formatting/limits.js";
3
+ import { filterGenericLog } from "./generic-log.js";
4
+ import { compactTable } from "./table.js";
5
+ const IMPORTANT_PATTERN = /\b(error|failed|failure|fatal|warn(?:ing)?|exception|panic)\b/i;
6
+ function detectSubcommand(command) {
7
+ const index = command.findIndex((part) => /(?:^|[\\/])kubectl(?:\.exe)?$/i.test(part));
8
+ if (index < 0)
9
+ return undefined;
10
+ return command[index + 1]?.toLowerCase();
11
+ }
12
+ function getOutputFormat(command) {
13
+ for (let index = 0; index < command.length; index += 1) {
14
+ const part = command[index];
15
+ if (part === "-o" || part === "--output") {
16
+ return command[index + 1]?.toLowerCase();
17
+ }
18
+ if (part?.startsWith("--output=")) {
19
+ return part.slice("--output=".length).toLowerCase();
20
+ }
21
+ }
22
+ return undefined;
23
+ }
24
+ export function filterKubectlOutput(input, options) {
25
+ const command = options.command ?? [];
26
+ const subcommand = detectSubcommand(command);
27
+ const outputFormat = getOutputFormat(command);
28
+ if (outputFormat && ["json", "yaml", "name", "go-template", "go-template-file", "jsonpath", "jsonpath-as-json"].includes(outputFormat)) {
29
+ return {
30
+ output: cleanText(input),
31
+ kind: "kubectl",
32
+ omitted: false,
33
+ notes: ["explicit kubectl output format preserved"],
34
+ };
35
+ }
36
+ if (subcommand === "logs") {
37
+ const result = filterGenericLog(input, options);
38
+ return { ...result, kind: "kubectl" };
39
+ }
40
+ if (subcommand === "get") {
41
+ const table = compactTable(input, options);
42
+ if (table.parsed) {
43
+ return {
44
+ output: table.output,
45
+ kind: "kubectl",
46
+ omitted: table.omitted,
47
+ notes: table.notes,
48
+ };
49
+ }
50
+ }
51
+ if (subcommand === "describe") {
52
+ const lines = cleanText(input).split("\n");
53
+ const kept = lines.filter((line) => {
54
+ const trimmed = line.trim();
55
+ return (trimmed.startsWith("Name:") ||
56
+ trimmed.startsWith("Namespace:") ||
57
+ trimmed.startsWith("Status:") ||
58
+ trimmed.startsWith("Containers:") ||
59
+ trimmed.startsWith("Conditions:") ||
60
+ trimmed.startsWith("Events:") ||
61
+ IMPORTANT_PATTERN.test(trimmed));
62
+ });
63
+ if (kept.length > 0) {
64
+ const limited = limitLines(kept, options.maxLines);
65
+ return {
66
+ output: limited.lines.join("\n"),
67
+ kind: "kubectl",
68
+ omitted: kept.length < lines.length || limited.omitted > 0,
69
+ notes: ["collapsed kubectl describe details"],
70
+ };
71
+ }
72
+ }
73
+ const fallback = filterGenericLog(input, options);
74
+ return { ...fallback, kind: "kubectl" };
75
+ }
76
+ //# sourceMappingURL=kubectl.js.map
@@ -0,0 +1,44 @@
1
+ import { cleanText } from "../formatting/ansi.js";
2
+ import { limitLines } from "../formatting/limits.js";
3
+ const IMPORTANT_PATTERN = /(?:npm\s+ERR!|\berror\b|\bfailed\b|\bfailure\b|\bfatal\b|\bwarn(?:ing)?\b|\bexception\b|\bpanic\b)/i;
4
+ const SUMMARY_PATTERN = /\b(added|removed|changed|audited|vulnerabilities?|packages?|dependencies|found\s+0\s+vulnerabilities)\b/i;
5
+ const NOISE_PATTERN = /^(?:npm\s+notice\s+|npm\s+timing\s+|npm\s+http\s+|npm\s+verb\s+|\s*[\|/\\-]+\s*$|\s*\d+%\s*$)/i;
6
+ export function filterNpmOutput(input, options) {
7
+ const lines = cleanText(input).split("\n");
8
+ const kept = [];
9
+ let noiseLines = 0;
10
+ for (const line of lines) {
11
+ if (!line.trim())
12
+ continue;
13
+ if (IMPORTANT_PATTERN.test(line)) {
14
+ kept.push(line);
15
+ continue;
16
+ }
17
+ if (SUMMARY_PATTERN.test(line)) {
18
+ kept.push(line);
19
+ continue;
20
+ }
21
+ if (NOISE_PATTERN.test(line)) {
22
+ noiseLines += 1;
23
+ }
24
+ }
25
+ if (noiseLines > 0) {
26
+ kept.unshift(`[${noiseLines} npm noise lines collapsed]`);
27
+ }
28
+ if (kept.length === 0) {
29
+ return {
30
+ output: lines.join("\n"),
31
+ kind: "npm",
32
+ omitted: false,
33
+ notes: ["unrecognized npm format; returned cleaned output"],
34
+ };
35
+ }
36
+ const limited = limitLines(kept, options.maxLines);
37
+ return {
38
+ output: limited.lines.join("\n"),
39
+ kind: "npm",
40
+ omitted: noiseLines > 0 || limited.omitted > 0 || kept.length < lines.length,
41
+ notes: noiseLines > 0 ? [`collapsed ${noiseLines} npm noise lines`] : [],
42
+ };
43
+ }
44
+ //# sourceMappingURL=npm.js.map
@@ -0,0 +1,64 @@
1
+ import { cleanText } from "../formatting/ansi.js";
2
+ import { limitLines } from "../formatting/limits.js";
3
+ function requestsStructuredOutput(command) {
4
+ return command.some((part) => part === "--json" || part.startsWith("--json="));
5
+ }
6
+ export function filterRgOutput(input, options) {
7
+ const cleaned = cleanText(input);
8
+ const lines = cleaned.split("\n");
9
+ if (requestsStructuredOutput(options.command ?? [])) {
10
+ return {
11
+ output: cleaned,
12
+ kind: "rg",
13
+ omitted: false,
14
+ notes: ["explicit rg structured output preserved"],
15
+ };
16
+ }
17
+ const byFile = new Map();
18
+ let parseable = 0;
19
+ for (const line of lines) {
20
+ const match = line.match(/^(.+?):(\d+):(.*)$/);
21
+ if (!match)
22
+ continue;
23
+ const filePath = match[1] ?? "";
24
+ const lineNumber = Number.parseInt(match[2] ?? "0", 10);
25
+ const body = match[3] ?? "";
26
+ if (!filePath || !Number.isFinite(lineNumber) || lineNumber <= 0)
27
+ continue;
28
+ const list = byFile.get(filePath) ?? [];
29
+ list.push({ line: lineNumber, text: body.trim() });
30
+ byFile.set(filePath, list);
31
+ parseable += 1;
32
+ }
33
+ if (parseable === 0) {
34
+ return {
35
+ output: cleaned,
36
+ kind: "rg",
37
+ omitted: false,
38
+ notes: ["unrecognized rg format; returned cleaned output"],
39
+ };
40
+ }
41
+ const files = [...byFile.entries()].sort((left, right) => right[1].length - left[1].length);
42
+ const output = [`matches: ${parseable} in ${files.length} files`];
43
+ let omittedMatches = 0;
44
+ for (const [filePath, matches] of files) {
45
+ output.push(`${filePath} (${matches.length})`);
46
+ const visible = matches.slice(0, Math.max(1, options.perFileLines));
47
+ for (const item of visible) {
48
+ output.push(` ${item.line}: ${item.text}`);
49
+ }
50
+ if (matches.length > visible.length) {
51
+ const hidden = matches.length - visible.length;
52
+ omittedMatches += hidden;
53
+ output.push(` ... ${hidden} matches omitted ...`);
54
+ }
55
+ }
56
+ const limited = limitLines(output, options.maxLines);
57
+ return {
58
+ output: limited.lines.join("\n"),
59
+ kind: "rg",
60
+ omitted: omittedMatches > 0 || limited.omitted > 0,
61
+ notes: omittedMatches > 0 ? [`omitted ${omittedMatches} rg matches`] : [],
62
+ };
63
+ }
64
+ //# sourceMappingURL=rg.js.map
@@ -0,0 +1,119 @@
1
+ import { filterGenericLog } from "./generic-log.js";
2
+ import { filterCatOutput } from "./cat.js";
3
+ import { filterDockerOutput } from "./docker.js";
4
+ import { filterFindOutput } from "./find.js";
5
+ import { filterGitDiff } from "./git-diff.js";
6
+ import { filterGitList } from "./git-list.js";
7
+ import { filterGitLog } from "./git-log.js";
8
+ import { filterGitStatus } from "./git-status.js";
9
+ import { filterGhOutput } from "./gh.js";
10
+ import { filterKubectlOutput } from "./kubectl.js";
11
+ import { filterNpmOutput } from "./npm.js";
12
+ import { filterRgOutput } from "./rg.js";
13
+ import { filterTailOutput } from "./tail.js";
14
+ import { filterTestOutput } from "./test-output.js";
15
+ import { cleanText } from "../formatting/ansi.js";
16
+ import { measure } from "../metrics/measure.js";
17
+ const FILTERS = {
18
+ "git-status": filterGitStatus,
19
+ "git-diff": filterGitDiff,
20
+ "git-log": filterGitLog,
21
+ "git-list": filterGitList,
22
+ npm: filterNpmOutput,
23
+ tail: filterTailOutput,
24
+ find: filterFindOutput,
25
+ rg: filterRgOutput,
26
+ docker: filterDockerOutput,
27
+ kubectl: filterKubectlOutput,
28
+ cat: filterCatOutput,
29
+ gh: filterGhOutput,
30
+ test: filterTestOutput,
31
+ log: filterGenericLog,
32
+ };
33
+ export function detectFilter(command) {
34
+ const executable = detectExecutable(command);
35
+ const gitSubcommand = detectGitSubcommand(command);
36
+ if (gitSubcommand === "status")
37
+ return "git-status";
38
+ if (gitSubcommand === "log")
39
+ return "git-log";
40
+ if (gitSubcommand === "reflog")
41
+ return "git-log";
42
+ if (gitSubcommand === "diff" || gitSubcommand === "show")
43
+ return "git-diff";
44
+ if (gitSubcommand)
45
+ return "git-list";
46
+ if (executable === "npm" || executable === "pnpm" || executable === "yarn") {
47
+ if (isTestRunnerCommand(command))
48
+ return "test";
49
+ return "npm";
50
+ }
51
+ if (executable === "tail")
52
+ return "tail";
53
+ if (executable === "find")
54
+ return "find";
55
+ if (executable === "rg" || executable === "ripgrep")
56
+ return "rg";
57
+ if (executable === "docker")
58
+ return "docker";
59
+ if (executable === "kubectl")
60
+ return "kubectl";
61
+ if (executable === "cat")
62
+ return "cat";
63
+ if (executable === "gh")
64
+ return "gh";
65
+ if (isTestRunnerCommand(command)) {
66
+ return "test";
67
+ }
68
+ return "log";
69
+ }
70
+ function detectExecutable(command) {
71
+ const first = command[0];
72
+ if (!first)
73
+ return undefined;
74
+ return first
75
+ .split(/[\\/]/)
76
+ .pop()
77
+ ?.replace(/\.exe$/i, "")
78
+ .toLowerCase();
79
+ }
80
+ function isTestRunnerCommand(command) {
81
+ const normalized = command.join(" ").toLowerCase();
82
+ return /\b(test|pytest|jest|vitest|cargo test|go test|dotnet test|rspec|npm test|pnpm test|yarn test)\b/.test(normalized);
83
+ }
84
+ function detectGitSubcommand(command) {
85
+ const gitIndex = command.findIndex((part) => /(?:^|[\\/])git(?:\.exe)?$/i.test(part));
86
+ if (gitIndex < 0)
87
+ return undefined;
88
+ const optionsWithValues = new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace"]);
89
+ for (let index = gitIndex + 1; index < command.length; index += 1) {
90
+ const part = command[index];
91
+ if (!part)
92
+ continue;
93
+ if (optionsWithValues.has(part)) {
94
+ index += 1;
95
+ continue;
96
+ }
97
+ if (part.startsWith("-"))
98
+ continue;
99
+ return part.toLowerCase();
100
+ }
101
+ return undefined;
102
+ }
103
+ export function applyFilter(input, requestedKind, command, options) {
104
+ const kind = requestedKind === "auto" ? detectFilter(command) : requestedKind;
105
+ const result = FILTERS[kind](input, { ...options, command });
106
+ const cleanedRaw = cleanText(input);
107
+ const comparison = measure(cleanedRaw, result.output);
108
+ if (comparison.outputBytes >= comparison.rawBytes ||
109
+ comparison.outputEstimatedTokens >= comparison.rawEstimatedTokens) {
110
+ return {
111
+ output: cleanedRaw,
112
+ kind,
113
+ omitted: false,
114
+ notes: [...result.notes, "compact output was not smaller; returned cleaned raw output"],
115
+ };
116
+ }
117
+ return result;
118
+ }
119
+ //# sourceMappingURL=select-filter.js.map
@@ -0,0 +1,27 @@
1
+ import { cleanText } from "../formatting/ansi.js";
2
+ import { limitLines } from "../formatting/limits.js";
3
+ function hasWideSpacing(line) {
4
+ return /\S\s{2,}\S/.test(line);
5
+ }
6
+ export function compactTable(input, options) {
7
+ const lines = cleanText(input).split("\n").filter((line) => line.trim());
8
+ if (lines.length < 2 || !hasWideSpacing(lines[0] ?? "")) {
9
+ return { output: cleanText(input), omitted: false, notes: [], parsed: false };
10
+ }
11
+ const header = lines[0] ?? "";
12
+ const rows = lines.slice(1);
13
+ const rowLimit = Math.max(1, options.maxLines - 2);
14
+ const visibleRows = rows.slice(0, rowLimit);
15
+ const output = [header, ...visibleRows];
16
+ if (rows.length > visibleRows.length) {
17
+ output.push(`... ${rows.length - visibleRows.length} rows omitted ...`);
18
+ }
19
+ const limited = limitLines(output, options.maxLines);
20
+ return {
21
+ output: limited.lines.join("\n"),
22
+ omitted: rows.length > visibleRows.length || limited.omitted > 0,
23
+ notes: rows.length > visibleRows.length ? [`omitted ${rows.length - visibleRows.length} rows`] : [],
24
+ parsed: true,
25
+ };
26
+ }
27
+ //# sourceMappingURL=table.js.map
@@ -0,0 +1,6 @@
1
+ import { filterGenericLog } from "./generic-log.js";
2
+ export function filterTailOutput(input, options) {
3
+ const result = filterGenericLog(input, options);
4
+ return { ...result, kind: "tail" };
5
+ }
6
+ //# sourceMappingURL=tail.js.map