pi-rtk-optimizer 0.3.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.
@@ -0,0 +1,343 @@
1
+ import {
2
+ aggregateLinterOutput,
3
+ aggregateTestOutput,
4
+ compactGitOutput,
5
+ detectLanguage,
6
+ filterBuildOutput,
7
+ filterSourceCode,
8
+ groupSearchResults,
9
+ smartTruncate,
10
+ stripAnsiFast,
11
+ truncate,
12
+ } from "./techniques/index.js";
13
+ import { trackOutputSavings } from "./output-metrics.js";
14
+ import type { RtkIntegrationConfig } from "./types.js";
15
+
16
+ interface ContentBlock {
17
+ type: string;
18
+ text?: string;
19
+ [key: string]: unknown;
20
+ }
21
+
22
+ interface ToolResultLikeEvent {
23
+ toolName: string;
24
+ input?: unknown;
25
+ content?: unknown;
26
+ }
27
+
28
+ export interface ToolResultCompactionMetadata {
29
+ applied: boolean;
30
+ techniques: string[];
31
+ truncated: boolean;
32
+ originalCharCount: number;
33
+ compactedCharCount: number;
34
+ originalLineCount: number;
35
+ compactedLineCount: number;
36
+ }
37
+
38
+ export interface ToolResultCompactionOutcome {
39
+ changed: boolean;
40
+ content?: unknown[];
41
+ techniques: string[];
42
+ metadata?: ToolResultCompactionMetadata;
43
+ }
44
+
45
+ const LOSSY_TECHNIQUE_PREFIXES = [
46
+ "build",
47
+ "test",
48
+ "git",
49
+ "linter",
50
+ "search",
51
+ "truncate",
52
+ "smart-truncate",
53
+ "source:",
54
+ ] as const;
55
+
56
+ const READ_EXACT_OUTPUT_LINE_THRESHOLD = 80;
57
+ const READ_COMPACTION_BANNER_PREFIX = "[RTK compacted output:";
58
+
59
+ function toRecord(value: unknown): Record<string, unknown> {
60
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
61
+ return {};
62
+ }
63
+ return value as Record<string, unknown>;
64
+ }
65
+
66
+ function toArray(value: unknown): unknown[] {
67
+ return Array.isArray(value) ? value : [];
68
+ }
69
+
70
+ function normalizeCommand(input: Record<string, unknown>): string | undefined {
71
+ const raw = input.command;
72
+ if (typeof raw === "string" && raw.trim()) {
73
+ return raw;
74
+ }
75
+ return undefined;
76
+ }
77
+
78
+ function normalizePath(input: Record<string, unknown>): string {
79
+ const raw = input.path;
80
+ if (typeof raw === "string") {
81
+ return raw;
82
+ }
83
+ return "";
84
+ }
85
+
86
+ function hasExplicitReadRange(input: Record<string, unknown>): boolean {
87
+ return input.offset !== undefined || input.limit !== undefined;
88
+ }
89
+
90
+ function shouldPreserveExactReadOutput(text: string, input: Record<string, unknown>): boolean {
91
+ if (hasExplicitReadRange(input)) {
92
+ return true;
93
+ }
94
+
95
+ return countLines(text) <= READ_EXACT_OUTPUT_LINE_THRESHOLD;
96
+ }
97
+
98
+ function formatReadCompactionBanner(techniques: string[]): string {
99
+ return `${READ_COMPACTION_BANNER_PREFIX} ${techniques.join(", ")}]`;
100
+ }
101
+
102
+ function countLines(text: string): number {
103
+ if (!text) {
104
+ return 0;
105
+ }
106
+ return text.split("\n").length;
107
+ }
108
+
109
+ function hasLossyCompaction(techniques: string[]): boolean {
110
+ return techniques.some((technique) =>
111
+ LOSSY_TECHNIQUE_PREFIXES.some((prefix) =>
112
+ prefix.endsWith(":") ? technique.startsWith(prefix) : technique === prefix,
113
+ ),
114
+ );
115
+ }
116
+
117
+ function compactBashText(
118
+ text: string,
119
+ command: string | undefined,
120
+ config: RtkIntegrationConfig,
121
+ ): { text: string; techniques: string[] } {
122
+ let nextText = text;
123
+ const techniques: string[] = [];
124
+ const compaction = config.outputCompaction;
125
+
126
+ if (compaction.stripAnsi) {
127
+ const stripped = stripAnsiFast(nextText);
128
+ if (stripped !== nextText) {
129
+ nextText = stripped;
130
+ techniques.push("ansi");
131
+ }
132
+ }
133
+
134
+ if (compaction.filterBuildOutput) {
135
+ const compacted = filterBuildOutput(nextText, command);
136
+ if (compacted !== null && compacted !== nextText) {
137
+ nextText = compacted;
138
+ techniques.push("build");
139
+ }
140
+ }
141
+
142
+ if (compaction.aggregateTestOutput) {
143
+ const compacted = aggregateTestOutput(nextText, command);
144
+ if (compacted !== null && compacted !== nextText) {
145
+ nextText = compacted;
146
+ techniques.push("test");
147
+ }
148
+ }
149
+
150
+ if (compaction.compactGitOutput) {
151
+ const compacted = compactGitOutput(nextText, command);
152
+ if (compacted !== null && compacted !== nextText) {
153
+ nextText = compacted;
154
+ techniques.push("git");
155
+ }
156
+ }
157
+
158
+ if (compaction.aggregateLinterOutput) {
159
+ const compacted = aggregateLinterOutput(nextText, command);
160
+ if (compacted !== null && compacted !== nextText) {
161
+ nextText = compacted;
162
+ techniques.push("linter");
163
+ }
164
+ }
165
+
166
+ if (compaction.truncate.enabled && nextText.length > compaction.truncate.maxChars) {
167
+ nextText = truncate(nextText, compaction.truncate.maxChars);
168
+ techniques.push("truncate");
169
+ }
170
+
171
+ return { text: nextText, techniques };
172
+ }
173
+
174
+ function compactReadText(
175
+ text: string,
176
+ filePath: string,
177
+ config: RtkIntegrationConfig,
178
+ preserveExactReadOutput: boolean,
179
+ ): { text: string; techniques: string[] } {
180
+ if (preserveExactReadOutput) {
181
+ return { text, techniques: [] };
182
+ }
183
+
184
+ let nextText = text;
185
+ const techniques: string[] = [];
186
+ const compaction = config.outputCompaction;
187
+
188
+ if (compaction.stripAnsi) {
189
+ const stripped = stripAnsiFast(nextText);
190
+ if (stripped !== nextText) {
191
+ nextText = stripped;
192
+ techniques.push("ansi");
193
+ }
194
+ }
195
+
196
+ const language = detectLanguage(filePath);
197
+ if (compaction.sourceCodeFilteringEnabled && compaction.sourceCodeFiltering !== "none") {
198
+ const filtered = filterSourceCode(nextText, language, compaction.sourceCodeFiltering);
199
+ if (filtered !== nextText) {
200
+ nextText = filtered;
201
+ techniques.push(`source:${compaction.sourceCodeFiltering}`);
202
+ }
203
+ }
204
+
205
+ if (compaction.smartTruncate.enabled) {
206
+ const lineCount = nextText.split("\n").length;
207
+ if (lineCount > compaction.smartTruncate.maxLines) {
208
+ const compacted = smartTruncate(nextText, compaction.smartTruncate.maxLines, language);
209
+ if (compacted !== nextText) {
210
+ nextText = compacted;
211
+ techniques.push("smart-truncate");
212
+ }
213
+ }
214
+ }
215
+
216
+ if (compaction.truncate.enabled && nextText.length > compaction.truncate.maxChars) {
217
+ nextText = truncate(nextText, compaction.truncate.maxChars);
218
+ techniques.push("truncate");
219
+ }
220
+
221
+ if (techniques.length > 0 && !nextText.startsWith(READ_COMPACTION_BANNER_PREFIX)) {
222
+ nextText = `${formatReadCompactionBanner(techniques)}\n${nextText}`;
223
+ }
224
+
225
+ return { text: nextText, techniques };
226
+ }
227
+
228
+ function compactGrepText(text: string, config: RtkIntegrationConfig): { text: string; techniques: string[] } {
229
+ let nextText = text;
230
+ const techniques: string[] = [];
231
+ const compaction = config.outputCompaction;
232
+
233
+ if (compaction.stripAnsi) {
234
+ const stripped = stripAnsiFast(nextText);
235
+ if (stripped !== nextText) {
236
+ nextText = stripped;
237
+ techniques.push("ansi");
238
+ }
239
+ }
240
+
241
+ if (compaction.groupSearchOutput) {
242
+ const grouped = groupSearchResults(nextText);
243
+ if (grouped !== null && grouped !== nextText) {
244
+ nextText = grouped;
245
+ techniques.push("search");
246
+ }
247
+ }
248
+
249
+ if (compaction.truncate.enabled && nextText.length > compaction.truncate.maxChars) {
250
+ nextText = truncate(nextText, compaction.truncate.maxChars);
251
+ techniques.push("truncate");
252
+ }
253
+
254
+ return { text: nextText, techniques };
255
+ }
256
+
257
+ export function compactToolResult(
258
+ event: ToolResultLikeEvent,
259
+ config: RtkIntegrationConfig,
260
+ ): ToolResultCompactionOutcome {
261
+ if (!config.outputCompaction.enabled) {
262
+ return { changed: false, techniques: [] };
263
+ }
264
+
265
+ const input = toRecord(event.input);
266
+ const sourceContent = toArray(event.content);
267
+ if (sourceContent.length === 0) {
268
+ return { changed: false, techniques: [] };
269
+ }
270
+
271
+ let changed = false;
272
+ const allTechniques = new Set<string>();
273
+ const originalChunks: string[] = [];
274
+ const filteredChunks: string[] = [];
275
+
276
+ const nextContent = sourceContent.map((block) => {
277
+ if (!block || typeof block !== "object" || Array.isArray(block)) {
278
+ return block;
279
+ }
280
+
281
+ const contentBlock = block as ContentBlock;
282
+ if (contentBlock.type !== "text" || typeof contentBlock.text !== "string") {
283
+ return block;
284
+ }
285
+
286
+ let transformed = { text: contentBlock.text, techniques: [] as string[] };
287
+ if (event.toolName === "bash") {
288
+ transformed = compactBashText(contentBlock.text, normalizeCommand(input), config);
289
+ } else if (event.toolName === "read") {
290
+ transformed = compactReadText(
291
+ contentBlock.text,
292
+ normalizePath(input),
293
+ config,
294
+ shouldPreserveExactReadOutput(contentBlock.text, input),
295
+ );
296
+ } else if (event.toolName === "grep") {
297
+ transformed = compactGrepText(contentBlock.text, config);
298
+ }
299
+
300
+ for (const technique of transformed.techniques) {
301
+ allTechniques.add(technique);
302
+ }
303
+
304
+ originalChunks.push(contentBlock.text);
305
+ filteredChunks.push(transformed.text);
306
+
307
+ if (transformed.text !== contentBlock.text) {
308
+ changed = true;
309
+ return { ...contentBlock, text: transformed.text };
310
+ }
311
+
312
+ return block;
313
+ });
314
+
315
+ if (!changed) {
316
+ return { changed: false, techniques: [] };
317
+ }
318
+
319
+ const techniques = Array.from(allTechniques);
320
+ const originalText = originalChunks.join("\n");
321
+ const compactedText = filteredChunks.join("\n");
322
+
323
+ if (config.outputCompaction.trackSavings) {
324
+ trackOutputSavings(originalText, compactedText, event.toolName, techniques);
325
+ }
326
+
327
+ const metadata: ToolResultCompactionMetadata = {
328
+ applied: true,
329
+ techniques,
330
+ truncated: hasLossyCompaction(techniques),
331
+ originalCharCount: originalText.length,
332
+ compactedCharCount: compactedText.length,
333
+ originalLineCount: countLines(originalText),
334
+ compactedLineCount: countLines(compactedText),
335
+ };
336
+
337
+ return {
338
+ changed: true,
339
+ content: nextContent,
340
+ techniques,
341
+ metadata,
342
+ };
343
+ }
@@ -0,0 +1,69 @@
1
+ export interface OutputMetricRecord {
2
+ timestamp: string;
3
+ tool: string;
4
+ techniques: string;
5
+ originalChars: number;
6
+ filteredChars: number;
7
+ savingsPercent: number;
8
+ }
9
+
10
+ const outputMetrics: OutputMetricRecord[] = [];
11
+
12
+ export function trackOutputSavings(
13
+ original: string,
14
+ filtered: string,
15
+ tool: string,
16
+ techniques: string[],
17
+ ): OutputMetricRecord {
18
+ const originalChars = original.length;
19
+ const filteredChars = filtered.length;
20
+ const savingsPercent =
21
+ originalChars > 0 ? Math.round((((originalChars - filteredChars) / originalChars) * 100) * 100) / 100 : 0;
22
+
23
+ const record: OutputMetricRecord = {
24
+ timestamp: new Date().toISOString(),
25
+ tool,
26
+ techniques: techniques.join(",") || "none",
27
+ originalChars,
28
+ filteredChars,
29
+ savingsPercent,
30
+ };
31
+
32
+ outputMetrics.push(record);
33
+ return record;
34
+ }
35
+
36
+ export function clearOutputMetrics(): void {
37
+ outputMetrics.length = 0;
38
+ }
39
+
40
+ export function getOutputMetricsSummary(): string {
41
+ if (outputMetrics.length === 0) {
42
+ return "RTK output compaction metrics: no data yet.";
43
+ }
44
+
45
+ const totalOriginal = outputMetrics.reduce((sum, metric) => sum + metric.originalChars, 0);
46
+ const totalFiltered = outputMetrics.reduce((sum, metric) => sum + metric.filteredChars, 0);
47
+ const totalSaved = totalOriginal - totalFiltered;
48
+ const savingsPercent = totalOriginal > 0 ? (totalSaved / totalOriginal) * 100 : 0;
49
+
50
+ const byTool = new Map<string, { count: number; originalChars: number; filteredChars: number }>();
51
+ for (const metric of outputMetrics) {
52
+ const existing = byTool.get(metric.tool) ?? { count: 0, originalChars: 0, filteredChars: 0 };
53
+ existing.count += 1;
54
+ existing.originalChars += metric.originalChars;
55
+ existing.filteredChars += metric.filteredChars;
56
+ byTool.set(metric.tool, existing);
57
+ }
58
+
59
+ let result = "RTK output compaction metrics\n";
60
+ result += `calls=${outputMetrics.length}, saved=${totalSaved.toLocaleString()} chars (${savingsPercent.toFixed(1)}%)\n`;
61
+
62
+ for (const [tool, stats] of byTool.entries()) {
63
+ const toolSaved = stats.originalChars - stats.filteredChars;
64
+ const toolSavingsPercent = stats.originalChars > 0 ? (toolSaved / stats.originalChars) * 100 : 0;
65
+ result += `- ${tool}: ${stats.count} calls, saved ${toolSaved.toLocaleString()} chars (${toolSavingsPercent.toFixed(1)}%)\n`;
66
+ }
67
+
68
+ return result.trimEnd();
69
+ }
@@ -0,0 +1,248 @@
1
+ export type RtkRewriteCategory =
2
+ | "gitGithub"
3
+ | "filesystem"
4
+ | "rust"
5
+ | "javascript"
6
+ | "python"
7
+ | "go"
8
+ | "containers"
9
+ | "network"
10
+ | "packageManagers";
11
+
12
+ export interface RtkRewriteRule {
13
+ id: string;
14
+ category: RtkRewriteCategory;
15
+ matcher: RegExp;
16
+ replacement: string;
17
+ description: string;
18
+ }
19
+
20
+ export const RTK_REWRITE_RULES: RtkRewriteRule[] = [
21
+ // Git / GitHub
22
+ {
23
+ id: "git-leading-flags-proxy",
24
+ category: "gitGithub",
25
+ matcher: /^git\s+-(.+)$/,
26
+ replacement: "rtk proxy git -$1",
27
+ description: "git with leading flags (e.g. -C, --no-pager) -> proxy",
28
+ },
29
+ {
30
+ id: "git-any",
31
+ category: "gitGithub",
32
+ matcher: /^git\s+(.+)$/,
33
+ replacement: "rtk git $1",
34
+ description: "git <args> -> rtk git",
35
+ },
36
+ {
37
+ id: "gh-leading-flags-proxy",
38
+ category: "gitGithub",
39
+ matcher: /^gh\s+-(.+)$/,
40
+ replacement: "rtk proxy gh -$1",
41
+ description: "gh with leading flags -> proxy",
42
+ },
43
+ {
44
+ id: "gh-any",
45
+ category: "gitGithub",
46
+ matcher: /^gh\s+(.+)$/,
47
+ replacement: "rtk gh $1",
48
+ description: "gh <args> -> rtk gh",
49
+ },
50
+
51
+ // Filesystem / shell helpers
52
+ { id: "cat", category: "filesystem", matcher: /^cat\s+/, replacement: "rtk read ", description: "cat" },
53
+ {
54
+ id: "head-short-lines",
55
+ category: "filesystem",
56
+ matcher: /^head\s+-([0-9]+)\s+(.+)$/,
57
+ replacement: "rtk read $2 --max-lines $1",
58
+ description: "head -N <file>",
59
+ },
60
+ {
61
+ id: "head-long-lines",
62
+ category: "filesystem",
63
+ matcher: /^head\s+--lines=([0-9]+)\s+(.+)$/,
64
+ replacement: "rtk read $2 --max-lines $1",
65
+ description: "head --lines=N <file>",
66
+ },
67
+ {
68
+ id: "tail-short-lines",
69
+ category: "filesystem",
70
+ matcher: /^tail\s+-n\s*([0-9]+)\s+(.+)$/,
71
+ replacement: "rtk read $2 --max-lines $1",
72
+ description: "tail -n N <file>",
73
+ },
74
+ {
75
+ id: "tail-long-lines",
76
+ category: "filesystem",
77
+ matcher: /^tail\s+--lines=([0-9]+)\s+(.+)$/,
78
+ replacement: "rtk read $2 --max-lines $1",
79
+ description: "tail --lines=N <file>",
80
+ },
81
+ { id: "grep", category: "filesystem", matcher: /^(rg|grep)\s+/, replacement: "rtk grep ", description: "rg/grep" },
82
+ { id: "ls", category: "filesystem", matcher: /^ls\b/, replacement: "rtk ls", description: "ls" },
83
+ { id: "tree", category: "filesystem", matcher: /^tree\b/, replacement: "rtk tree", description: "tree" },
84
+ { id: "find", category: "filesystem", matcher: /^find\s+/, replacement: "rtk find ", description: "find" },
85
+ { id: "diff", category: "filesystem", matcher: /^diff\s+/, replacement: "rtk diff ", description: "diff" },
86
+ { id: "wc", category: "filesystem", matcher: /^wc\b/, replacement: "rtk wc", description: "wc" },
87
+ {
88
+ id: "powershell-proxy",
89
+ category: "filesystem",
90
+ matcher: /^(powershell(?:\.exe)?)\s+(.+)$/i,
91
+ replacement: "rtk proxy $1 $2",
92
+ description: "powershell -> proxy",
93
+ },
94
+ {
95
+ id: "cmd-proxy",
96
+ category: "filesystem",
97
+ matcher: /^(cmd(?:\.exe)?)\s+(.+)$/i,
98
+ replacement: "rtk proxy $1 $2",
99
+ description: "cmd -> proxy",
100
+ },
101
+ { id: "bash-proxy", category: "filesystem", matcher: /^bash\s+(.+)$/i, replacement: "rtk proxy bash $1", description: "bash -> proxy" },
102
+
103
+ // Rust
104
+ {
105
+ id: "cargo-any",
106
+ category: "rust",
107
+ matcher: /^cargo\s+(.+)$/,
108
+ replacement: "rtk cargo $1",
109
+ description: "cargo <args> -> rtk cargo",
110
+ },
111
+
112
+ // JavaScript / TypeScript ecosystem
113
+ { id: "vitest", category: "javascript", matcher: /^vitest(?:\s+run)?\b/, replacement: "rtk vitest run", description: "vitest" },
114
+ { id: "npx-vitest", category: "javascript", matcher: /^npx\s+vitest(?:\s+run)?\b/, replacement: "rtk vitest run", description: "npx vitest" },
115
+ { id: "bunx-vitest", category: "javascript", matcher: /^bunx\s+vitest(?:\s+run)?\b/, replacement: "rtk vitest run", description: "bunx vitest" },
116
+ { id: "pnpm-vitest", category: "javascript", matcher: /^pnpm\s+(?:exec\s+)?vitest(?:\s+run)?\b/, replacement: "rtk vitest run", description: "pnpm vitest" },
117
+ {
118
+ id: "npm-run",
119
+ category: "javascript",
120
+ matcher: /^npm\s+run\s+(.+)$/,
121
+ replacement: "rtk proxy npm run $1",
122
+ description: "npm run <script> -> proxy",
123
+ },
124
+ {
125
+ id: "npm-script-shorthand",
126
+ category: "javascript",
127
+ matcher: /^npm\s+(test|start|build|lint)\b(.*)$/,
128
+ replacement: "rtk proxy npm $1$2",
129
+ description: "npm test/start/build/lint -> proxy",
130
+ },
131
+ { id: "tsc", category: "javascript", matcher: /^tsc\b/, replacement: "rtk tsc", description: "tsc" },
132
+ { id: "npx-tsc", category: "javascript", matcher: /^npx\s+tsc\b/, replacement: "rtk tsc", description: "npx tsc" },
133
+ { id: "bunx-tsc", category: "javascript", matcher: /^bunx\s+tsc\b/, replacement: "rtk tsc", description: "bunx tsc" },
134
+ { id: "pnpm-tsc", category: "javascript", matcher: /^pnpm\s+(?:exec\s+)?tsc\b/, replacement: "rtk tsc", description: "pnpm tsc" },
135
+ { id: "pnpm-typecheck", category: "javascript", matcher: /^pnpm\s+typecheck\b/, replacement: "rtk tsc", description: "pnpm typecheck" },
136
+ { id: "eslint", category: "javascript", matcher: /^eslint\b/, replacement: "rtk lint", description: "eslint" },
137
+ { id: "npx-eslint", category: "javascript", matcher: /^npx\s+eslint\b/, replacement: "rtk lint", description: "npx eslint" },
138
+ { id: "bunx-eslint", category: "javascript", matcher: /^bunx\s+eslint\b/, replacement: "rtk lint", description: "bunx eslint" },
139
+ { id: "pnpm-lint", category: "javascript", matcher: /^pnpm\s+lint\b/, replacement: "rtk lint", description: "pnpm lint" },
140
+ { id: "next", category: "javascript", matcher: /^next\b/, replacement: "rtk next", description: "next" },
141
+ { id: "npx-next", category: "javascript", matcher: /^npx\s+next\b/, replacement: "rtk next", description: "npx next" },
142
+ { id: "bunx-next", category: "javascript", matcher: /^bunx\s+next\b/, replacement: "rtk next", description: "bunx next" },
143
+ { id: "prettier", category: "javascript", matcher: /^prettier\b/, replacement: "rtk prettier", description: "prettier" },
144
+ { id: "npx-prettier", category: "javascript", matcher: /^npx\s+prettier\b/, replacement: "rtk prettier", description: "npx prettier" },
145
+ { id: "bunx-prettier", category: "javascript", matcher: /^bunx\s+prettier\b/, replacement: "rtk prettier", description: "bunx prettier" },
146
+ { id: "playwright", category: "javascript", matcher: /^playwright\b/, replacement: "rtk playwright", description: "playwright" },
147
+ { id: "npx-playwright", category: "javascript", matcher: /^npx\s+playwright\b/, replacement: "rtk playwright", description: "npx playwright" },
148
+ { id: "bunx-playwright", category: "javascript", matcher: /^bunx\s+playwright\b/, replacement: "rtk playwright", description: "bunx playwright" },
149
+ { id: "pnpm-playwright", category: "javascript", matcher: /^pnpm\s+playwright\b/, replacement: "rtk playwright", description: "pnpm playwright" },
150
+ { id: "prisma", category: "javascript", matcher: /^prisma\b/, replacement: "rtk prisma", description: "prisma" },
151
+ { id: "npx-prisma", category: "javascript", matcher: /^npx\s+prisma\b/, replacement: "rtk prisma", description: "npx prisma" },
152
+ { id: "bunx-prisma", category: "javascript", matcher: /^bunx\s+prisma\b/, replacement: "rtk prisma", description: "bunx prisma" },
153
+ { id: "pnpm-prisma", category: "javascript", matcher: /^pnpm\s+prisma\b/, replacement: "rtk prisma", description: "pnpm prisma" },
154
+ { id: "bun-proxy", category: "javascript", matcher: /^bun\s+(.+)$/, replacement: "rtk proxy bun $1", description: "bun -> proxy" },
155
+ { id: "node-proxy", category: "javascript", matcher: /^node\s+(.+)$/, replacement: "rtk proxy node $1", description: "node -> proxy" },
156
+ { id: "tsx-proxy", category: "javascript", matcher: /^tsx\s+(.+)$/, replacement: "rtk proxy tsx $1", description: "tsx -> proxy" },
157
+ { id: "npx-proxy", category: "javascript", matcher: /^npx\s+(.+)$/, replacement: "rtk proxy npx $1", description: "npx fallback -> proxy" },
158
+ { id: "bunx-proxy", category: "javascript", matcher: /^bunx\s+(.+)$/, replacement: "rtk proxy bunx $1", description: "bunx fallback -> proxy" },
159
+
160
+ // Python
161
+ { id: "pytest", category: "python", matcher: /^pytest\b/, replacement: "rtk pytest", description: "pytest" },
162
+ { id: "python-pytest", category: "python", matcher: /^python(?:3(?:\.\d+)?)?\s+-m\s+pytest\b/, replacement: "rtk pytest", description: "python -m pytest" },
163
+ { id: "ruff", category: "python", matcher: /^ruff\b/, replacement: "rtk ruff", description: "ruff" },
164
+ { id: "python-ruff", category: "python", matcher: /^python(?:3(?:\.\d+)?)?\s+-m\s+ruff\b/, replacement: "rtk ruff", description: "python -m ruff" },
165
+ {
166
+ id: "pip",
167
+ category: "python",
168
+ matcher: /^pip\s+(list|outdated|install|uninstall|show)\b/,
169
+ replacement: "rtk pip $1",
170
+ description: "pip supported commands",
171
+ },
172
+ {
173
+ id: "uv-pip",
174
+ category: "python",
175
+ matcher: /^uv\s+pip\s+(list|outdated|install|uninstall|show)\b/,
176
+ replacement: "rtk pip $1",
177
+ description: "uv pip supported commands",
178
+ },
179
+ {
180
+ id: "python-pip",
181
+ category: "python",
182
+ matcher: /^python(?:3(?:\.\d+)?)?\s+-m\s+pip\s+(list|outdated|install|uninstall|show)\b/,
183
+ replacement: "rtk pip $1",
184
+ description: "python -m pip supported commands",
185
+ },
186
+ {
187
+ id: "python-proxy",
188
+ category: "python",
189
+ matcher: /^python(?:3(?:\.\d+)?)?\s+(.+)$/,
190
+ replacement: "rtk proxy python $1",
191
+ description: "python fallback -> proxy",
192
+ },
193
+ { id: "poetry-proxy", category: "python", matcher: /^poetry\s+(.+)$/, replacement: "rtk proxy poetry $1", description: "poetry -> proxy" },
194
+
195
+ // Go
196
+ { id: "go-any", category: "go", matcher: /^go\s+(.+)$/, replacement: "rtk go $1", description: "go <args> -> rtk go" },
197
+ { id: "golangci", category: "go", matcher: /^golangci-lint\b/, replacement: "rtk golangci-lint", description: "golangci-lint" },
198
+
199
+ // Containers
200
+ {
201
+ id: "docker-compose",
202
+ category: "containers",
203
+ matcher: /^docker\s+compose\s+(.+)$/,
204
+ replacement: "rtk docker compose $1",
205
+ description: "docker compose",
206
+ },
207
+ {
208
+ id: "docker",
209
+ category: "containers",
210
+ matcher: /^docker\s+(.+)$/,
211
+ replacement: "rtk docker $1",
212
+ description: "docker",
213
+ },
214
+ {
215
+ id: "kubectl",
216
+ category: "containers",
217
+ matcher: /^kubectl\s+(.+)$/,
218
+ replacement: "rtk kubectl $1",
219
+ description: "kubectl",
220
+ },
221
+
222
+ // Network
223
+ { id: "curl", category: "network", matcher: /^curl\s+/, replacement: "rtk curl ", description: "curl" },
224
+ { id: "wget", category: "network", matcher: /^wget\s+/, replacement: "rtk wget ", description: "wget" },
225
+
226
+ // Package managers
227
+ {
228
+ id: "npm-proxy",
229
+ category: "packageManagers",
230
+ matcher: /^npm\s+(.+)$/,
231
+ replacement: "rtk proxy npm $1",
232
+ description: "npm fallback -> proxy",
233
+ },
234
+ {
235
+ id: "pnpm-leading-flags-proxy",
236
+ category: "packageManagers",
237
+ matcher: /^pnpm\s+-(.+)$/,
238
+ replacement: "rtk proxy pnpm -$1",
239
+ description: "pnpm with leading flags -> proxy",
240
+ },
241
+ {
242
+ id: "pnpm-any",
243
+ category: "packageManagers",
244
+ matcher: /^pnpm\s+(.+)$/,
245
+ replacement: "rtk pnpm $1",
246
+ description: "pnpm <args> -> rtk pnpm",
247
+ },
248
+ ];
@@ -0,0 +1,13 @@
1
+ export function stripAnsi(text: string): string {
2
+ return text
3
+ .replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "")
4
+ .replace(/\x1b\][0-9;]*(?:\x07|\x1b\\)/g, "")
5
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "");
6
+ }
7
+
8
+ export function stripAnsiFast(text: string): string {
9
+ if (!text.includes("\x1b")) {
10
+ return text;
11
+ }
12
+ return stripAnsi(text);
13
+ }