@howaboua/pi-codex-conversion 1.0.8 → 1.0.9

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.
@@ -1,5 +1,5 @@
1
1
  import type { ShellAction } from "./types.ts";
2
- import { isAbsoluteLike, joinPaths, shortDisplayPath } from "./tokenize.ts";
2
+ import { isAbsoluteLike, joinCommandTokens, joinPaths, shortDisplayPath } from "./tokenize.ts";
3
3
 
4
4
  export function parseShellPart(tokens: string[], cwd?: string): ShellAction | null {
5
5
  if (tokens.length === 0) return null;
@@ -18,6 +18,13 @@ export function parseShellPart(tokens: string[], cwd?: string): ShellAction | nu
18
18
  path: joinPaths(cwd, parsed.path),
19
19
  };
20
20
  }
21
+ if (parsed.kind === "list" && cwd && !parsed.path) {
22
+ return {
23
+ ...parsed,
24
+ path: shortDisplayPath(cwd),
25
+ };
26
+ }
27
+
21
28
  return parsed;
22
29
  }
23
30
 
@@ -28,30 +35,97 @@ export function nextCwd(currentCwd: string | undefined, tokens: string[]): strin
28
35
  return currentCwd ? joinPaths(currentCwd, target) : target;
29
36
  }
30
37
 
38
+ export function isSmallFormattingCommand(tokens: string[]): boolean {
39
+ if (tokens.length === 0) return false;
40
+ const [head, ...tail] = tokens;
41
+ if (
42
+ head === "wc" ||
43
+ head === "tr" ||
44
+ head === "cut" ||
45
+ head === "sort" ||
46
+ head === "uniq" ||
47
+ head === "tee" ||
48
+ head === "column" ||
49
+ head === "yes" ||
50
+ head === "printf"
51
+ ) {
52
+ return true;
53
+ }
54
+ if (head === "xargs") {
55
+ return !xargsIsMutatingSubcommand(tail);
56
+ }
57
+ if (head === "awk") {
58
+ return awkDataFileOperand(tail) === undefined;
59
+ }
60
+ if (head === "head") {
61
+ if (tail.length === 0) return true;
62
+ if (tail.length === 1) return tail[0].startsWith("-");
63
+ if (tail.length === 2 && (tail[0] === "-n" || tail[0] === "-c") && /^\d+$/.test(tail[1])) return true;
64
+ return false;
65
+ }
66
+ if (head === "tail") {
67
+ if (tail.length === 0) return true;
68
+ if (tail.length === 1) return tail[0].startsWith("-");
69
+ if (tail.length === 2 && (tail[0] === "-n" || tail[0] === "-c")) {
70
+ const value = tail[1]?.startsWith("+") ? tail[1].slice(1) : tail[1];
71
+ if (value && /^\d+$/.test(value)) return true;
72
+ }
73
+ return false;
74
+ }
75
+ if (head === "sed") {
76
+ return sedReadPath(tail) === undefined;
77
+ }
78
+ return false;
79
+ }
80
+
31
81
  function parseMainTokens(tokens: string[]): ShellAction | null {
32
82
  const [head, ...tail] = tokens;
33
83
  if (!head) return null;
34
-
35
- if (head === "echo" || head === "true") {
84
+ const command = joinCommandTokens(tokens);
85
+
86
+ if (
87
+ head === "echo" ||
88
+ head === "true" ||
89
+ head === "printf" ||
90
+ head === "wc" ||
91
+ head === "tr" ||
92
+ head === "cut" ||
93
+ head === "sort" ||
94
+ head === "uniq" ||
95
+ head === "tee" ||
96
+ head === "column" ||
97
+ head === "yes"
98
+ ) {
36
99
  return null;
37
100
  }
38
101
 
39
- if (head === "ls" || head === "eza" || head === "exa" || head === "tree" || head === "du") {
102
+ if (head === "xargs") {
103
+ return xargsIsMutatingSubcommand(tail) ? { kind: "run", command } : null;
104
+ }
105
+
106
+ if (head === "ls" || head === "eza" || head === "exa") {
40
107
  const flagsWithValues =
41
- head === "tree"
42
- ? ["-L", "-P", "-I", "--charset", "--filelimit", "--sort"]
43
- : head === "du"
44
- ? ["-d", "--max-depth", "-B", "--block-size", "--exclude", "--time-style"]
45
- : head === "ls"
46
- ? ["-I", "--ignore", "-w", "--block-size", "--format", "--time-style", "--color"]
47
- : ["-I", "--ignore-glob", "--color", "--sort", "--time-style", "--time"];
108
+ head === "ls"
109
+ ? ["-I", "-w", "--block-size", "--format", "--time-style", "--color", "--quoting-style"]
110
+ : ["-I", "--ignore-glob", "--color", "--sort", "--time-style", "--time"];
48
111
  const path = firstNonFlagOperand(tail, flagsWithValues);
49
- return { kind: "list", command: tokens.join(" "), path: path ? shortDisplayPath(path) : undefined };
112
+ return { kind: "list", command, path: path ? shortDisplayPath(path) : undefined };
113
+ }
114
+
115
+ if (head === "tree") {
116
+ const path = firstNonFlagOperand(tail, ["-L", "-P", "-I", "--charset", "--filelimit", "--sort"]);
117
+ return { kind: "list", command, path: path ? shortDisplayPath(path) : undefined };
118
+ }
119
+
120
+ if (head === "du") {
121
+ const path = firstNonFlagOperand(tail, ["-d", "--max-depth", "-B", "--block-size", "--exclude", "--time-style"]);
122
+ return { kind: "list", command, path: path ? shortDisplayPath(path) : undefined };
50
123
  }
51
124
 
52
125
  if (head === "rg" || head === "rga" || head === "ripgrep-all") {
53
- const hasFilesFlag = tail.includes("--files");
54
- const candidates = skipFlagValues(tail, [
126
+ const args = trimAtConnector(tail);
127
+ const hasFilesFlag = args.includes("--files");
128
+ const candidates = skipFlagValues(args, [
55
129
  "-g",
56
130
  "--glob",
57
131
  "--iglob",
@@ -70,63 +144,63 @@ function parseMainTokens(tokens: string[]): ShellAction | null {
70
144
  const nonFlags = candidates.filter((token) => !token.startsWith("-"));
71
145
  if (hasFilesFlag) {
72
146
  const path = nonFlags[0];
73
- return { kind: "list", command: tokens.join(" "), path: path ? shortDisplayPath(path) : undefined };
147
+ return { kind: "list", command, path: path ? shortDisplayPath(path) : undefined };
74
148
  }
75
149
  return {
76
150
  kind: "search",
77
- command: tokens.join(" "),
151
+ command,
78
152
  query: nonFlags[0],
79
153
  path: nonFlags[1] ? shortDisplayPath(nonFlags[1]) : undefined,
80
154
  };
81
155
  }
82
156
 
83
157
  if (head === "git" && tail[0] === "grep") {
84
- return parseGrepLike(tokens.join(" "), tail.slice(1));
158
+ return parseGrepLike(command, tail.slice(1));
85
159
  }
86
160
 
87
161
  if (head === "git" && tail[0] === "ls-files") {
88
162
  const path = firstNonFlagOperand(tail.slice(1), ["--exclude", "--exclude-from", "--pathspec-from-file"]);
89
- return { kind: "list", command: tokens.join(" "), path: path ? shortDisplayPath(path) : undefined };
163
+ return { kind: "list", command, path: path ? shortDisplayPath(path) : undefined };
90
164
  }
91
165
 
92
166
  if (head === "fd") {
93
- const nonFlags = skipFlagValues(tail, ["-g", "--glob", "-e", "--extension", "-E", "--exclude"]).filter(
94
- (token) => !token.startsWith("-"),
95
- );
96
- if (nonFlags.length === 0) {
97
- return { kind: "list", command: tokens.join(" ") };
98
- }
99
- if (nonFlags.length === 1) {
100
- return { kind: "list", command: tokens.join(" "), path: shortDisplayPath(nonFlags[0]) };
167
+ const [query, path] = parseFdQueryAndPath(tail);
168
+ if (query) {
169
+ return { kind: "search", command, query, path };
101
170
  }
102
- return {
103
- kind: "search",
104
- command: tokens.join(" "),
105
- query: nonFlags[0],
106
- path: shortDisplayPath(nonFlags[1]),
107
- };
171
+ return { kind: "list", command, path };
108
172
  }
109
173
 
110
174
  if (head === "find") {
111
- const path = tail.find((token) => !token.startsWith("-"));
112
- const nameIndex = tail.findIndex((token) => token === "-name" || token === "-iname");
113
- const query = nameIndex !== -1 ? tail[nameIndex + 1] : undefined;
175
+ const [query, path] = parseFindQueryAndPath(tail);
114
176
  if (query) {
115
- return {
116
- kind: "search",
117
- command: tokens.join(" "),
118
- query,
119
- path: path ? shortDisplayPath(path) : undefined,
120
- };
177
+ return { kind: "search", command, query, path };
121
178
  }
122
- return { kind: "list", command: tokens.join(" "), path: path ? shortDisplayPath(path) : undefined };
179
+ return { kind: "list", command, path };
123
180
  }
124
181
 
125
- if (head === "grep" || head === "egrep" || head === "fgrep" || head === "ag" || head === "ack" || head === "pt") {
126
- return parseGrepLike(tokens.join(" "), tail);
182
+ if (head === "grep" || head === "egrep" || head === "fgrep") {
183
+ return parseGrepLike(command, tail);
127
184
  }
128
185
 
129
- if (head === "cat" || head === "bat" || head === "batcat" || head === "less" || head === "more") {
186
+ if (head === "ag" || head === "ack" || head === "pt") {
187
+ const args = trimAtConnector(tail);
188
+ const candidates = skipFlagValues(args, ["-G", "-g", "--file-search-regex", "--ignore-dir", "--ignore-file", "--path-to-ignore"]);
189
+ const nonFlags = candidates.filter((token) => !token.startsWith("-"));
190
+ return {
191
+ kind: "search",
192
+ command,
193
+ query: nonFlags[0],
194
+ path: nonFlags[1] ? shortDisplayPath(nonFlags[1]) : undefined,
195
+ };
196
+ }
197
+
198
+ if (head === "cat") {
199
+ const path = singleNonFlagOperand(tail, []);
200
+ return path ? readAction(command, path) : { kind: "run", command };
201
+ }
202
+
203
+ if (head === "bat" || head === "batcat") {
130
204
  const path = singleNonFlagOperand(tail, [
131
205
  "--theme",
132
206
  "--language",
@@ -135,6 +209,12 @@ function parseMainTokens(tokens: string[]): ShellAction | null {
135
209
  "--tabs",
136
210
  "--line-range",
137
211
  "--map-syntax",
212
+ ]);
213
+ return path ? readAction(command, path) : { kind: "run", command };
214
+ }
215
+
216
+ if (head === "less") {
217
+ const path = singleNonFlagOperand(tail, [
138
218
  "-p",
139
219
  "-P",
140
220
  "-x",
@@ -143,56 +223,136 @@ function parseMainTokens(tokens: string[]): ShellAction | null {
143
223
  "-j",
144
224
  "--pattern",
145
225
  "--prompt",
226
+ "--tabs",
146
227
  "--shift",
147
228
  "--jump-target",
148
229
  ]);
149
- return path ? readAction(tokens.join(" "), path) : { kind: "run", command: tokens.join(" ") };
230
+ return path ? readAction(command, path) : { kind: "run", command };
231
+ }
232
+
233
+ if (head === "more") {
234
+ const path = singleNonFlagOperand(tail, []);
235
+ return path ? readAction(command, path) : { kind: "run", command };
150
236
  }
151
237
 
152
238
  if (head === "head") {
153
239
  const path = readPathFromHeadTail(tail, "head");
154
- return path ? readAction(tokens.join(" "), path) : null;
240
+ return path ? readAction(command, path) : null;
155
241
  }
156
242
 
157
243
  if (head === "tail") {
158
244
  const path = readPathFromHeadTail(tail, "tail");
159
- return path ? readAction(tokens.join(" "), path) : null;
245
+ return path ? readAction(command, path) : null;
246
+ }
247
+
248
+ if (head === "awk") {
249
+ const path = awkDataFileOperand(tail);
250
+ return path ? readAction(command, path) : { kind: "run", command };
160
251
  }
161
252
 
162
253
  if (head === "nl") {
163
254
  const candidates = skipFlagValues(tail, ["-s", "-w", "-v", "-i", "-b"]);
164
255
  const path = candidates.find((token) => !token.startsWith("-"));
165
- return path ? readAction(tokens.join(" "), path) : null;
256
+ return path ? readAction(command, path) : null;
166
257
  }
167
258
 
168
259
  if (head === "sed") {
169
260
  const path = sedReadPath(tail);
170
- return path ? readAction(tokens.join(" "), path) : null;
261
+ return path ? readAction(command, path) : null;
262
+ }
263
+
264
+ if (isPythonCommand(head)) {
265
+ return pythonWalksFiles(tail) ? { kind: "list", command } : { kind: "run", command };
171
266
  }
172
267
 
173
- return { kind: "run", command: tokens.join(" ") };
268
+ return { kind: "run", command };
174
269
  }
175
270
 
176
271
  function parseGrepLike(command: string, tail: string[]): ShellAction {
177
- const candidates = skipFlagValues(tail, [
178
- "-e",
179
- "-f",
180
- "-g",
181
- "-G",
182
- "--glob",
183
- "--include",
184
- "--exclude",
185
- "--exclude-dir",
186
- "--exclude-from",
187
- "--ignore-dir",
188
- ]);
272
+ const args = trimAtConnector(tail);
273
+ const operands: string[] = [];
274
+ let pattern: string | undefined;
275
+ let afterDoubleDash = false;
276
+
277
+ for (let index = 0; index < args.length; index++) {
278
+ const arg = args[index];
279
+ if (afterDoubleDash) {
280
+ operands.push(arg);
281
+ continue;
282
+ }
283
+ if (arg === "--") {
284
+ afterDoubleDash = true;
285
+ continue;
286
+ }
287
+ if (arg === "-e" || arg === "--regexp") {
288
+ if (!pattern) pattern = args[index + 1];
289
+ index += 1;
290
+ continue;
291
+ }
292
+ if (arg === "-f" || arg === "--file") {
293
+ if (!pattern) pattern = args[index + 1];
294
+ index += 1;
295
+ continue;
296
+ }
297
+ if (
298
+ arg === "-m" ||
299
+ arg === "--max-count" ||
300
+ arg === "-C" ||
301
+ arg === "--context" ||
302
+ arg === "-A" ||
303
+ arg === "--after-context" ||
304
+ arg === "-B" ||
305
+ arg === "--before-context"
306
+ ) {
307
+ index += 1;
308
+ continue;
309
+ }
310
+ if (arg.startsWith("-")) continue;
311
+ operands.push(arg);
312
+ }
313
+
314
+ const hasPattern = pattern !== undefined;
315
+ const query = pattern ?? operands[0];
316
+ const pathIndex = hasPattern ? 0 : 1;
317
+ const path = operands[pathIndex] ? shortDisplayPath(operands[pathIndex]) : undefined;
318
+
319
+ return { kind: "search", command, query, path };
320
+ }
321
+
322
+ function parseFdQueryAndPath(tail: string[]): [string | undefined, string | undefined] {
323
+ const args = trimAtConnector(tail);
324
+ const candidates = skipFlagValues(args, ["-t", "--type", "-e", "--extension", "-E", "--exclude", "--search-path"]);
189
325
  const nonFlags = candidates.filter((token) => !token.startsWith("-"));
190
- return {
191
- kind: "search",
192
- command,
193
- query: nonFlags[0],
194
- path: nonFlags[1] ? shortDisplayPath(nonFlags[1]) : undefined,
195
- };
326
+ if (nonFlags.length === 1) {
327
+ if (isPathish(nonFlags[0])) return [undefined, shortDisplayPath(nonFlags[0])];
328
+ return [nonFlags[0], undefined];
329
+ }
330
+ if (nonFlags.length >= 2) {
331
+ return [nonFlags[0], shortDisplayPath(nonFlags[1])];
332
+ }
333
+ return [undefined, undefined];
334
+ }
335
+
336
+ function parseFindQueryAndPath(tail: string[]): [string | undefined, string | undefined] {
337
+ const args = trimAtConnector(tail);
338
+ let path: string | undefined;
339
+ for (const arg of args) {
340
+ if (!arg.startsWith("-") && arg !== "!" && arg !== "(" && arg !== ")") {
341
+ path = shortDisplayPath(arg);
342
+ break;
343
+ }
344
+ }
345
+
346
+ let query: string | undefined;
347
+ for (let index = 0; index < args.length; index++) {
348
+ const arg = args[index];
349
+ if (arg === "-name" || arg === "-iname" || arg === "-path" || arg === "-regex") {
350
+ query = args[index + 1];
351
+ break;
352
+ }
353
+ }
354
+
355
+ return [query, path];
196
356
  }
197
357
 
198
358
  function readAction(command: string, path: string): ShellAction {
@@ -209,7 +369,7 @@ function readPathFromHeadTail(args: string[], tool: "head" | "tail"): string | u
209
369
  return args[0];
210
370
  }
211
371
 
212
- const tokens = [...args];
372
+ const tokens = trimAtConnector(args);
213
373
  let index = 0;
214
374
  while (index < tokens.length) {
215
375
  const token = tokens[index];
@@ -232,12 +392,13 @@ function readPathFromHeadTail(args: string[], tool: "head" | "tail"): string | u
232
392
  }
233
393
 
234
394
  function sedReadPath(args: string[]): string | undefined {
235
- if (!args.includes("-n")) return undefined;
395
+ const tokens = trimAtConnector(args);
396
+ if (!tokens.includes("-n")) return undefined;
236
397
 
237
398
  let hasRangeScript = false;
238
- for (let index = 0; index < args.length; index++) {
239
- const token = args[index];
240
- if ((token === "-e" || token === "--expression") && isValidSedRange(args[index + 1])) {
399
+ for (let index = 0; index < tokens.length; index++) {
400
+ const token = tokens[index];
401
+ if ((token === "-e" || token === "--expression") && isValidSedRange(tokens[index + 1])) {
241
402
  hasRangeScript = true;
242
403
  }
243
404
  if (!token.startsWith("-") && isValidSedRange(token)) {
@@ -246,12 +407,10 @@ function sedReadPath(args: string[]): string | undefined {
246
407
  }
247
408
  if (!hasRangeScript) return undefined;
248
409
 
249
- const candidates = skipFlagValues(args, ["-e", "-f", "--expression", "--file"]);
410
+ const candidates = skipFlagValues(tokens, ["-e", "-f", "--expression", "--file"]);
250
411
  const nonFlags = candidates.filter((token) => !token.startsWith("-"));
251
412
  if (nonFlags.length === 0) return undefined;
252
- if (isValidSedRange(nonFlags[0])) {
253
- return nonFlags[1];
254
- }
413
+ if (isValidSedRange(nonFlags[0])) return nonFlags[1];
255
414
  return nonFlags[0];
256
415
  }
257
416
 
@@ -262,36 +421,154 @@ function isValidSedRange(value: string | undefined): boolean {
262
421
  return parts.length >= 1 && parts.length <= 2 && parts.every((part) => part.length > 0 && /^\d+$/.test(part));
263
422
  }
264
423
 
265
- function firstNonFlagOperand(args: string[], flagsWithValues: string[]): string | undefined {
266
- return skipFlagValues(args, flagsWithValues).find((token) => !token.startsWith("-"));
267
- }
268
-
269
- function singleNonFlagOperand(args: string[], flagsWithValues: string[]): string | undefined {
270
- const nonFlags = skipFlagValues(args, flagsWithValues).filter((token) => !token.startsWith("-"));
271
- return nonFlags.length === 1 ? nonFlags[0] : undefined;
424
+ function trimAtConnector(tokens: string[]): string[] {
425
+ const index = tokens.findIndex((token) => token === "|" || token === "&&" || token === "||" || token === ";");
426
+ return index === -1 ? [...tokens] : tokens.slice(0, index);
272
427
  }
273
428
 
274
429
  function skipFlagValues(args: string[], flagsWithValues: string[]): string[] {
275
430
  const out: string[] = [];
431
+ let skipNext = false;
276
432
  for (let index = 0; index < args.length; index++) {
277
433
  const token = args[index];
278
- out.push(token);
279
- if (flagsWithValues.includes(token) && index + 1 < args.length) {
280
- index += 1;
434
+ if (skipNext) {
435
+ skipNext = false;
436
+ continue;
437
+ }
438
+ if (token === "--") {
439
+ out.push(...args.slice(index + 1));
440
+ break;
441
+ }
442
+ if (token.startsWith("--") && token.includes("=")) {
443
+ continue;
444
+ }
445
+ if (flagsWithValues.includes(token)) {
446
+ if (index + 1 < args.length) skipNext = true;
447
+ continue;
281
448
  }
449
+ out.push(token);
282
450
  }
283
451
  return out;
284
452
  }
285
453
 
286
- function cdTarget(args: string[]): string | undefined {
454
+ function positionalOperands(args: string[], flagsWithValues: string[]): string[] {
455
+ const out: string[] = [];
456
+ let afterDoubleDash = false;
457
+ let skipNext = false;
287
458
  for (let index = 0; index < args.length; index++) {
288
- const token = args[index];
289
- if (token === "--") {
459
+ const arg = args[index];
460
+ if (skipNext) {
461
+ skipNext = false;
290
462
  continue;
291
463
  }
292
- if (!token.startsWith("-")) {
293
- return token;
464
+ if (afterDoubleDash) {
465
+ out.push(arg);
466
+ continue;
294
467
  }
468
+ if (arg === "--") {
469
+ afterDoubleDash = true;
470
+ continue;
471
+ }
472
+ if (arg.startsWith("--") && arg.includes("=")) {
473
+ continue;
474
+ }
475
+ if (flagsWithValues.includes(arg)) {
476
+ if (index + 1 < args.length) skipNext = true;
477
+ continue;
478
+ }
479
+ if (arg.startsWith("-")) continue;
480
+ out.push(arg);
481
+ }
482
+ return out;
483
+ }
484
+
485
+ function firstNonFlagOperand(args: string[], flagsWithValues: string[]): string | undefined {
486
+ return positionalOperands(args, flagsWithValues)[0];
487
+ }
488
+
489
+ function singleNonFlagOperand(args: string[], flagsWithValues: string[]): string | undefined {
490
+ const operands = positionalOperands(args, flagsWithValues);
491
+ return operands.length === 1 ? operands[0] : undefined;
492
+ }
493
+
494
+ function awkDataFileOperand(args: string[]): string | undefined {
495
+ if (args.length === 0) return undefined;
496
+ const tokens = trimAtConnector(args);
497
+ const hasScriptFile = tokens.some((arg) => arg === "-f" || arg === "--file");
498
+ const candidates = skipFlagValues(tokens, ["-F", "-v", "-f", "--field-separator", "--assign", "--file"]);
499
+ const nonFlags = candidates.filter((arg) => !arg.startsWith("-"));
500
+ if (hasScriptFile) return nonFlags[0];
501
+ return nonFlags.length >= 2 ? nonFlags[1] : undefined;
502
+ }
503
+
504
+ function pythonWalksFiles(args: string[]): boolean {
505
+ const tokens = trimAtConnector(args);
506
+ for (let index = 0; index < tokens.length; index++) {
507
+ if (tokens[index] !== "-c") continue;
508
+ const script = tokens[index + 1];
509
+ if (!script) continue;
510
+ return (
511
+ script.includes("os.walk") ||
512
+ script.includes("os.listdir") ||
513
+ script.includes("os.scandir") ||
514
+ script.includes("glob.glob") ||
515
+ script.includes("glob.iglob") ||
516
+ script.includes("pathlib.Path") ||
517
+ script.includes(".rglob(")
518
+ );
519
+ }
520
+ return false;
521
+ }
522
+
523
+ function isPythonCommand(command: string): boolean {
524
+ return (
525
+ command === "python" ||
526
+ command === "python2" ||
527
+ command === "python3" ||
528
+ command.startsWith("python2.") ||
529
+ command.startsWith("python3.")
530
+ );
531
+ }
532
+
533
+ function isPathish(value: string): boolean {
534
+ return value === "." || value === ".." || value.startsWith("./") || value.startsWith("../") || value.includes("/") || value.includes("\\");
535
+ }
536
+
537
+ function xargsIsMutatingSubcommand(tokens: string[]): boolean {
538
+ const subcommand = xargsSubcommand(tokens);
539
+ if (!subcommand || subcommand.length === 0) return false;
540
+ const [head, ...tail] = subcommand;
541
+ if (head === "perl" || head === "ruby") return xargsHasInPlaceFlag(tail);
542
+ if (head === "sed") return xargsHasInPlaceFlag(tail) || tail.includes("--in-place");
543
+ if (head === "rg") return tail.includes("--replace");
544
+ return false;
545
+ }
546
+
547
+ function xargsSubcommand(tokens: string[]): string[] | undefined {
548
+ let index = 0;
549
+ while (index < tokens.length) {
550
+ const token = tokens[index];
551
+ if (token === "--") return tokens.slice(index + 1);
552
+ if (!token.startsWith("-")) return tokens.slice(index);
553
+ const takesValue = token === "-E" || token === "-e" || token === "-I" || token === "-L" || token === "-n" || token === "-P" || token === "-s";
554
+ index += takesValue && token.length === 2 ? 2 : 1;
295
555
  }
296
556
  return undefined;
297
557
  }
558
+
559
+ function xargsHasInPlaceFlag(tokens: string[]): boolean {
560
+ return tokens.some((token) => token === "-i" || token.startsWith("-i") || token === "-pi" || token.startsWith("-pi"));
561
+ }
562
+
563
+ function cdTarget(args: string[]): string | undefined {
564
+ if (args.length === 0) return undefined;
565
+ let target: string | undefined;
566
+ for (let index = 0; index < args.length; index++) {
567
+ const arg = args[index];
568
+ if (arg === "--") return args[index + 1];
569
+ if (arg === "-L" || arg === "-P") continue;
570
+ if (arg.startsWith("-")) continue;
571
+ target = arg;
572
+ }
573
+ return target;
574
+ }