pi-rtk-optimizer 0.3.2 → 0.3.3

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,173 @@
1
+ import type { RtkRewriteRule } from "./rewrite-rules.js";
2
+
3
+ const COMMAND_WORD_PATTERN = /"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|`(?:\\.|[^`])*`|[^\s]+/g;
4
+ const BYPASSED_CARGO_SUBCOMMANDS = new Set(["help", "install", "publish"]);
5
+ const GH_STRUCTURED_OUTPUT_FLAGS = ["--json", "--jq", "--template"] as const;
6
+ const INTERACTIVE_CONTAINER_SHELLS = new Set([
7
+ "ash",
8
+ "bash",
9
+ "cmd",
10
+ "cmd.exe",
11
+ "fish",
12
+ "powershell",
13
+ "powershell.exe",
14
+ "pwsh",
15
+ "pwsh.exe",
16
+ "sh",
17
+ "zsh",
18
+ ]);
19
+
20
+ function splitCommandWords(commandBody: string): string[] {
21
+ return commandBody.match(COMMAND_WORD_PATTERN) ?? [];
22
+ }
23
+
24
+ function shouldBypassCargoRewrite(tokens: string[]): boolean {
25
+ let index = 1;
26
+
27
+ while (index < tokens.length && tokens[index].startsWith("+")) {
28
+ index += 1;
29
+ }
30
+
31
+ while (index < tokens.length && tokens[index].startsWith("-")) {
32
+ index += 1;
33
+ }
34
+
35
+ const subcommand = tokens[index]?.toLowerCase();
36
+ if (!subcommand) {
37
+ return true;
38
+ }
39
+
40
+ return BYPASSED_CARGO_SUBCOMMANDS.has(subcommand);
41
+ }
42
+
43
+ function normalizeCommandWord(token: string): string {
44
+ const unwrapped = token.replace(/^(?:["'`])|(?:["'`])$/g, "");
45
+ const lastPathSeparator = Math.max(unwrapped.lastIndexOf("/"), unwrapped.lastIndexOf("\\"));
46
+ const basename = lastPathSeparator >= 0 ? unwrapped.slice(lastPathSeparator + 1) : unwrapped;
47
+ return basename.toLowerCase();
48
+ }
49
+
50
+ function findInteractiveShellIndex(tokens: string[], startIndex: number, endIndex: number): number {
51
+ for (let index = startIndex; index < endIndex; index += 1) {
52
+ if (INTERACTIVE_CONTAINER_SHELLS.has(normalizeCommandWord(tokens[index] ?? ""))) {
53
+ return index;
54
+ }
55
+ }
56
+
57
+ return -1;
58
+ }
59
+
60
+ function hasTrailingArguments(tokens: string[], startIndex: number, endIndex: number): boolean {
61
+ return startIndex >= 0 && startIndex < endIndex - 1;
62
+ }
63
+
64
+ function hasStructuredGhOutputFlag(tokens: string[]): boolean {
65
+ return tokens.some((token) => {
66
+ const normalized = token.toLowerCase();
67
+ return GH_STRUCTURED_OUTPUT_FLAGS.some((flag) => normalized === flag || normalized.startsWith(`${flag}=`));
68
+ });
69
+ }
70
+
71
+ function hasShortInteractiveFlag(token: string, flag: "i" | "t"): boolean {
72
+ if (!token.startsWith("-") || token.startsWith("--")) {
73
+ return false;
74
+ }
75
+
76
+ return token.slice(1).includes(flag);
77
+ }
78
+
79
+ function hasInteractiveFlagPair(tokens: string[], startIndex: number, endIndex: number): boolean {
80
+ let interactive = false;
81
+ let tty = false;
82
+
83
+ for (let index = startIndex; index < endIndex; index += 1) {
84
+ const token = tokens[index] ?? "";
85
+ if (token === "--interactive") {
86
+ interactive = true;
87
+ continue;
88
+ }
89
+ if (token === "--tty") {
90
+ tty = true;
91
+ continue;
92
+ }
93
+ if (hasShortInteractiveFlag(token, "i")) {
94
+ interactive = true;
95
+ }
96
+ if (hasShortInteractiveFlag(token, "t")) {
97
+ tty = true;
98
+ }
99
+ }
100
+
101
+ return interactive && tty;
102
+ }
103
+
104
+ function shouldBypassInteractiveContainerRewrite(tokens: string[]): boolean {
105
+ const command = tokens[0]?.toLowerCase();
106
+ if (!command) {
107
+ return false;
108
+ }
109
+
110
+ if (command === "docker" || command === "podman") {
111
+ const subcommand = tokens[1]?.toLowerCase();
112
+ if (subcommand === "run" || subcommand === "exec") {
113
+ const interactiveShellIndex = findInteractiveShellIndex(tokens, 2, tokens.length);
114
+ return (
115
+ interactiveShellIndex >= 0 &&
116
+ !hasTrailingArguments(tokens, interactiveShellIndex, tokens.length) &&
117
+ !hasInteractiveFlagPair(tokens, 2, interactiveShellIndex)
118
+ );
119
+ }
120
+
121
+ if (subcommand === "compose") {
122
+ const composeSubcommand = tokens[2]?.toLowerCase();
123
+ if (composeSubcommand === "run" || composeSubcommand === "exec") {
124
+ const interactiveShellIndex = findInteractiveShellIndex(tokens, 3, tokens.length);
125
+ return (
126
+ interactiveShellIndex >= 0 &&
127
+ !hasTrailingArguments(tokens, interactiveShellIndex, tokens.length) &&
128
+ !hasInteractiveFlagPair(tokens, 3, interactiveShellIndex)
129
+ );
130
+ }
131
+ }
132
+ }
133
+
134
+ if (command === "kubectl" && tokens[1]?.toLowerCase() === "exec") {
135
+ const separatorIndex = tokens.indexOf("--");
136
+ if (separatorIndex === -1 || separatorIndex >= tokens.length - 1) {
137
+ return false;
138
+ }
139
+
140
+ const interactiveShellIndex = findInteractiveShellIndex(tokens, separatorIndex + 1, tokens.length);
141
+ if (interactiveShellIndex === -1) {
142
+ return false;
143
+ }
144
+
145
+ return !hasTrailingArguments(tokens, interactiveShellIndex, tokens.length) && !hasInteractiveFlagPair(tokens, 2, separatorIndex);
146
+ }
147
+
148
+ return false;
149
+ }
150
+
151
+ /**
152
+ * Skips RTK rewrites for command shapes that do not map cleanly to RTK wrappers.
153
+ */
154
+ export function shouldBypassRewriteForCommand(commandBody: string, rule: RtkRewriteRule): boolean {
155
+ const tokens = splitCommandWords(commandBody.trim());
156
+ if (tokens.length === 0) {
157
+ return false;
158
+ }
159
+
160
+ if (tokens[0]?.toLowerCase() === "gh" && hasStructuredGhOutputFlag(tokens)) {
161
+ return true;
162
+ }
163
+
164
+ if (rule.category === "rust" && tokens[0]?.toLowerCase() === "cargo") {
165
+ return shouldBypassCargoRewrite(tokens);
166
+ }
167
+
168
+ if (rule.category === "containers") {
169
+ return shouldBypassInteractiveContainerRewrite(tokens);
170
+ }
171
+
172
+ return false;
173
+ }
@@ -238,6 +238,13 @@ export const RTK_REWRITE_RULES: RtkRewriteRule[] = [
238
238
  replacement: "rtk proxy pnpm -$1",
239
239
  description: "pnpm with leading flags -> proxy",
240
240
  },
241
+ {
242
+ id: "pnpm-dlx-proxy",
243
+ category: "packageManagers",
244
+ matcher: /^pnpm\s+dlx\b(.*)$/,
245
+ replacement: "rtk proxy pnpm dlx$1",
246
+ description: "pnpm dlx -> proxy",
247
+ },
241
248
  {
242
249
  id: "pnpm-any",
243
250
  category: "packageManagers",
@@ -0,0 +1,50 @@
1
+ import assert from "node:assert/strict";
2
+
3
+ import {
4
+ shouldRequireRtkAvailabilityForCommandHandling,
5
+ shouldSkipCommandHandlingWhenRtkMissing,
6
+ } from "./runtime-guard.ts";
7
+ import { DEFAULT_RTK_INTEGRATION_CONFIG, type RtkIntegrationConfig, type RuntimeStatus } from "./types.ts";
8
+
9
+ function runTest(name: string, testFn: () => void): void {
10
+ testFn();
11
+ console.log(`[PASS] ${name}`);
12
+ }
13
+
14
+ function cloneConfig(): RtkIntegrationConfig {
15
+ return structuredClone(DEFAULT_RTK_INTEGRATION_CONFIG);
16
+ }
17
+
18
+ function runtimeStatus(rtkAvailable: boolean): RuntimeStatus {
19
+ return { rtkAvailable };
20
+ }
21
+
22
+ runTest("rewrite mode still requires RTK availability when guard is enabled", () => {
23
+ const config = cloneConfig();
24
+ config.mode = "rewrite";
25
+ config.guardWhenRtkMissing = true;
26
+
27
+ assert.equal(shouldRequireRtkAvailabilityForCommandHandling(config), true);
28
+ assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(false)), true);
29
+ assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(true)), false);
30
+ });
31
+
32
+ runTest("suggest mode does not suppress suggestions when RTK is missing", () => {
33
+ const config = cloneConfig();
34
+ config.mode = "suggest";
35
+ config.guardWhenRtkMissing = true;
36
+
37
+ assert.equal(shouldRequireRtkAvailabilityForCommandHandling(config), false);
38
+ assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(false)), false);
39
+ });
40
+
41
+ runTest("guard disabled never blocks command handling", () => {
42
+ const config = cloneConfig();
43
+ config.mode = "rewrite";
44
+ config.guardWhenRtkMissing = false;
45
+
46
+ assert.equal(shouldRequireRtkAvailabilityForCommandHandling(config), false);
47
+ assert.equal(shouldSkipCommandHandlingWhenRtkMissing(config, runtimeStatus(false)), false);
48
+ });
49
+
50
+ console.log("All runtime-guard tests passed.");
@@ -0,0 +1,14 @@
1
+ import type { RtkIntegrationConfig, RuntimeStatus } from "./types.js";
2
+
3
+ export function shouldRequireRtkAvailabilityForCommandHandling(
4
+ config: Pick<RtkIntegrationConfig, "mode" | "guardWhenRtkMissing">,
5
+ ): boolean {
6
+ return config.mode === "rewrite" && config.guardWhenRtkMissing;
7
+ }
8
+
9
+ export function shouldSkipCommandHandlingWhenRtkMissing(
10
+ config: Pick<RtkIntegrationConfig, "mode" | "guardWhenRtkMissing">,
11
+ runtimeStatus: Pick<RuntimeStatus, "rtkAvailable">,
12
+ ): boolean {
13
+ return shouldRequireRtkAvailabilityForCommandHandling(config) && !runtimeStatus.rtkAvailable;
14
+ }
@@ -66,9 +66,32 @@ export function filterMinimal(content: string, language: Language): string {
66
66
  const result: string[] = [];
67
67
  let inBlockComment = false;
68
68
  let inDocstring = false;
69
+ let inUserscriptMetadataBlock = false;
70
+ const userscriptMetadataStartPattern = /^\/\/\s*==\s*userscript\s*==$/i;
71
+ const userscriptMetadataContentPattern = /^\/\/\s*@\w+/;
72
+ const userscriptMetadataEndPattern = /^\/\/\s*==\s*\/userscript\s*==$/i;
69
73
 
70
74
  for (const line of lines) {
71
75
  const trimmed = line.trim();
76
+ const isUserscriptMetadataStart = userscriptMetadataStartPattern.test(trimmed);
77
+ const isUserscriptMetadataContent = userscriptMetadataContentPattern.test(trimmed);
78
+ const isUserscriptMetadataEnd = userscriptMetadataEndPattern.test(trimmed);
79
+
80
+ if (isUserscriptMetadataStart) {
81
+ inUserscriptMetadataBlock = true;
82
+ result.push(line);
83
+ continue;
84
+ }
85
+
86
+ if (inUserscriptMetadataBlock) {
87
+ result.push(line);
88
+ if (isUserscriptMetadataEnd) {
89
+ inUserscriptMetadataBlock = false;
90
+ } else if (isUserscriptMetadataContent) {
91
+ // Preserve metadata key/value lines (e.g. // @name) within the userscript block.
92
+ }
93
+ continue;
94
+ }
72
95
 
73
96
  if (patterns.blockStart && patterns.blockEnd) {
74
97
  if (
@@ -12,6 +12,35 @@ declare module "@mariozechner/pi-tui" {
12
12
  label: string;
13
13
  description?: string;
14
14
  }
15
+
16
+ export class Box {
17
+ constructor(...args: unknown[]);
18
+ addChild(child: unknown): void;
19
+ }
20
+
21
+ export class Container {
22
+ constructor(...args: unknown[]);
23
+ addChild(child: unknown): void;
24
+ render(width: number): string[];
25
+ invalidate(): void;
26
+ }
27
+
28
+ export class SettingsList {
29
+ constructor(...args: unknown[]);
30
+ handleInput(data: string): void;
31
+ updateValue(id: string, value: string): void;
32
+ }
33
+
34
+ export class Spacer {
35
+ constructor(...args: unknown[]);
36
+ }
37
+
38
+ export class Text {
39
+ constructor(...args: unknown[]);
40
+ }
41
+
42
+ export function truncateToWidth(text: string, width: number, suffix?: string, pad?: boolean): string;
43
+ export function visibleWidth(text: string): number;
15
44
  }
16
45
 
17
46
  declare module "@mariozechner/pi-coding-agent" {
@@ -20,7 +49,7 @@ declare module "@mariozechner/pi-coding-agent" {
20
49
  custom<T>(
21
50
  renderer: (
22
51
  tui: { requestRender(): void },
23
- theme: unknown,
52
+ theme: Theme,
24
53
  keybindings: unknown,
25
54
  done: () => void,
26
55
  ) => {
@@ -54,6 +83,14 @@ declare module "@mariozechner/pi-coding-agent" {
54
83
 
55
84
  type MaybePromise<T> = T | Promise<T>;
56
85
 
86
+ export interface Theme {
87
+ fg(color: string, text: string): string;
88
+ bold(text: string): string;
89
+ getFgAnsi?(name: string): string;
90
+ }
91
+
92
+ export function getSettingsListTheme(): unknown;
93
+
57
94
  export interface ExtensionAPI {
58
95
  exec(
59
96
  command: string,
@@ -112,6 +149,21 @@ declare module "@mariozechner/pi-coding-agent" {
112
149
  }
113
150
 
114
151
 
152
+ declare module "node:assert/strict" {
153
+ const assert: {
154
+ equal(actual: unknown, expected: unknown, message?: string): void;
155
+ deepEqual(actual: unknown, expected: unknown, message?: string): void;
156
+ ok(value: unknown, message?: string): void;
157
+ };
158
+
159
+ export default assert;
160
+ }
161
+
162
+ declare const process: {
163
+ platform: string;
164
+ env: Record<string, string | undefined>;
165
+ };
166
+
115
167
  declare module "node:os" {
116
168
  export function homedir(): string;
117
169
  }