@blogic-cz/agent-tools 1.10.1 → 1.12.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 (106) hide show
  1. package/README.md +21 -44
  2. package/dist/credential-guard/bash.d.ts +3 -0
  3. package/dist/credential-guard/bash.d.ts.map +1 -0
  4. package/dist/credential-guard/claude-hook.d.ts.map +1 -1
  5. package/dist/credential-guard/engine.test.d.ts +5 -0
  6. package/dist/credential-guard/engine.test.d.ts.map +1 -0
  7. package/dist/credential-guard/index.d.ts +8 -35
  8. package/dist/credential-guard/index.d.ts.map +1 -1
  9. package/dist/credential-guard/ir.d.ts +150 -0
  10. package/dist/credential-guard/ir.d.ts.map +1 -0
  11. package/dist/credential-guard/literal-output.d.ts +2 -0
  12. package/dist/credential-guard/literal-output.d.ts.map +1 -0
  13. package/dist/credential-guard/passive-awk.d.ts +2 -0
  14. package/dist/credential-guard/passive-awk.d.ts.map +1 -0
  15. package/dist/credential-guard/passive-jq.d.ts.map +1 -1
  16. package/dist/credential-guard/passive-sed.d.ts +2 -0
  17. package/dist/credential-guard/passive-sed.d.ts.map +1 -0
  18. package/dist/credential-guard/passive.test.d.ts +2 -0
  19. package/dist/credential-guard/passive.test.d.ts.map +1 -0
  20. package/dist/credential-guard/paths.d.ts +14 -0
  21. package/dist/credential-guard/paths.d.ts.map +1 -0
  22. package/dist/credential-guard/policy.d.ts +47 -0
  23. package/dist/credential-guard/policy.d.ts.map +1 -0
  24. package/dist/credential-guard/powershell.d.ts +3 -0
  25. package/dist/credential-guard/powershell.d.ts.map +1 -0
  26. package/dist/credential-guard/registry.d.ts +89 -0
  27. package/dist/credential-guard/registry.d.ts.map +1 -0
  28. package/dist/credential-guard/review4.test.d.ts +2 -0
  29. package/dist/credential-guard/review4.test.d.ts.map +1 -0
  30. package/dist/credential-guard/review7.test.d.ts +2 -0
  31. package/dist/credential-guard/review7.test.d.ts.map +1 -0
  32. package/dist/credential-guard/shell-oracle.d.ts +2 -0
  33. package/dist/credential-guard/shell-oracle.d.ts.map +1 -0
  34. package/dist/credential-guard/xargs.d.ts +7 -0
  35. package/dist/credential-guard/xargs.d.ts.map +1 -0
  36. package/dist/db-tool/service.d.ts +2 -1
  37. package/dist/db-tool/service.d.ts.map +1 -1
  38. package/dist/db-tool/sql-client.d.ts +1 -0
  39. package/dist/db-tool/sql-client.d.ts.map +1 -1
  40. package/dist/gh-tool/pr/commands.d.ts.map +1 -1
  41. package/dist/gh-tool/read/api.d.ts +8 -0
  42. package/dist/gh-tool/read/api.d.ts.map +1 -1
  43. package/dist/gh-tool/read/cache.d.ts.map +1 -1
  44. package/dist/gh-tool/read/commands.d.ts +5 -0
  45. package/dist/gh-tool/read/commands.d.ts.map +1 -1
  46. package/dist/gh-tool/read/reads.d.ts +38 -0
  47. package/dist/gh-tool/read/reads.d.ts.map +1 -1
  48. package/dist/gh-tool/read/telemetry-command.d.ts +7 -0
  49. package/dist/gh-tool/read/telemetry-command.d.ts.map +1 -0
  50. package/dist/gh-tool/read/telemetry-store.d.ts +3 -0
  51. package/dist/gh-tool/read/telemetry-store.d.ts.map +1 -0
  52. package/dist/gh-tool/read/telemetry.d.ts +59 -0
  53. package/dist/gh-tool/read/telemetry.d.ts.map +1 -0
  54. package/dist/gh-tool/read/transport.d.ts.map +1 -1
  55. package/dist/observability-tool/logs.d.ts +1 -1
  56. package/dist/observability-tool/logs.d.ts.map +1 -1
  57. package/dist/observability-tool/metrics.d.ts +1 -1
  58. package/dist/observability-tool/metrics.d.ts.map +1 -1
  59. package/dist/observability-tool/shared.d.ts +2 -1
  60. package/dist/observability-tool/shared.d.ts.map +1 -1
  61. package/dist/observability-tool/trace.d.ts +1 -1
  62. package/dist/observability-tool/trace.d.ts.map +1 -1
  63. package/dist/shared/content-security.d.ts +3 -1
  64. package/dist/shared/content-security.d.ts.map +1 -1
  65. package/dist/shared/secret-env.d.ts +22 -0
  66. package/dist/shared/secret-env.d.ts.map +1 -0
  67. package/package.json +2 -1
  68. package/schemas/agent-tools.schema.json +1 -1
  69. package/src/credential-guard/bash.ts +769 -0
  70. package/src/credential-guard/claude-hook.ts +14 -14
  71. package/src/credential-guard/engine.test.ts +2050 -0
  72. package/src/credential-guard/index.ts +285 -4268
  73. package/src/credential-guard/ir.ts +139 -0
  74. package/src/credential-guard/literal-output.ts +8 -0
  75. package/src/credential-guard/passive-awk.ts +239 -0
  76. package/src/credential-guard/passive-jq.ts +183 -24
  77. package/src/credential-guard/passive-sed.ts +78 -0
  78. package/src/credential-guard/passive.test.ts +141 -0
  79. package/src/credential-guard/paths.ts +108 -0
  80. package/src/credential-guard/policy.ts +1576 -0
  81. package/src/credential-guard/powershell.ts +220 -0
  82. package/src/credential-guard/registry.ts +4720 -0
  83. package/src/credential-guard/review4.test.ts +83 -0
  84. package/src/credential-guard/review7.test.ts +51 -0
  85. package/src/credential-guard/rg-types.json +389 -0
  86. package/src/credential-guard/shell-oracle.ts +187 -0
  87. package/src/credential-guard/xargs.ts +65 -0
  88. package/src/db-tool/main.ts +2 -0
  89. package/src/db-tool/service.ts +122 -140
  90. package/src/db-tool/sql-client.ts +7 -4
  91. package/src/gh-tool/main.ts +30 -6
  92. package/src/gh-tool/pr/commands.ts +9 -2
  93. package/src/gh-tool/pr/core.ts +2 -2
  94. package/src/gh-tool/read/api.ts +73 -6
  95. package/src/gh-tool/read/cache.ts +13 -4
  96. package/src/gh-tool/read/commands.ts +17 -1
  97. package/src/gh-tool/read/reads.ts +84 -1
  98. package/src/gh-tool/read/telemetry-command.ts +45 -0
  99. package/src/gh-tool/read/telemetry-store.ts +85 -0
  100. package/src/gh-tool/read/telemetry.ts +191 -0
  101. package/src/gh-tool/read/transport.ts +10 -5
  102. package/src/gh-tool/workflow.ts +2 -2
  103. package/src/observability-tool/main.ts +7 -1
  104. package/src/observability-tool/shared.ts +20 -14
  105. package/src/shared/content-security.ts +7 -2
  106. package/src/shared/secret-env.ts +230 -0
@@ -0,0 +1,4720 @@
1
+ import rgTypes from "./rg-types.json";
2
+ import type { XargsInput } from "./xargs";
3
+ import { isPassiveAwkProgram } from "./passive-awk";
4
+ import { isPassiveSedProgram } from "./passive-sed";
5
+ import { isPassiveJqFilter } from "./passive-jq";
6
+
7
+ export type Language = "bash" | "powershell" | "jq" | "sed" | "awk" | "runtime";
8
+ export type OperandRole =
9
+ | "data"
10
+ | "path"
11
+ | "content"
12
+ | "path-pattern"
13
+ | "search-filter"
14
+ | "names"
15
+ | "env-name"
16
+ | "script"
17
+ | "test-filter"
18
+ | "member"
19
+ | "rg-type"
20
+ | "write"
21
+ | { readonly code: Language };
22
+ export type Issue = {
23
+ readonly kind: "violation" | "missing-knowledge";
24
+ readonly rule:
25
+ | "secret-source"
26
+ | "wrapper-required"
27
+ | "no-analyzer"
28
+ | "opaque-code"
29
+ | "opaque-executable"
30
+ | "opaque-env-name"
31
+ | "startup-mutation"
32
+ | "unsupported-option";
33
+ readonly detail: string;
34
+ };
35
+ export type Option = {
36
+ readonly roles: readonly OperandRole[];
37
+ readonly issue?: Issue;
38
+ readonly attachedValue?: RegExp;
39
+ };
40
+ export type ExecutableEntry = {
41
+ readonly name: string;
42
+ readonly readsContent?: boolean;
43
+ readonly readsStdinCode?: boolean;
44
+ readonly builtinOnly?: boolean;
45
+ readonly pipelineData?: boolean;
46
+ readonly aliases?: readonly string[];
47
+ readonly numericOption?: boolean;
48
+ readonly optionsBeforeOperands?: boolean;
49
+ readonly optionGrammar?: "echo" | "printf";
50
+ readonly informationFlags?: readonly string[];
51
+ readonly namePattern?: RegExp;
52
+ readonly operands: readonly OperandRole[];
53
+ readonly options?: Readonly<Record<string, Option>>;
54
+ readonly subcommands?: Readonly<Record<string, Omit<ExecutableEntry, "name">>>;
55
+ readonly effect?: Issue | "none";
56
+ readonly bare?: Issue;
57
+ readonly startup?: readonly string[];
58
+ readonly startupFiles?: readonly RegExp[];
59
+ readonly semantics?:
60
+ | "joined-shell"
61
+ | "remote-shell"
62
+ | "ordinary"
63
+ | "shell"
64
+ | "runtime"
65
+ | "python"
66
+ | "env"
67
+ | "forward"
68
+ | "timeout"
69
+ | "xargs"
70
+ | "find"
71
+ | "gh"
72
+ | "git"
73
+ | "ps"
74
+ | "sed"
75
+ | "awk"
76
+ | "jq"
77
+ | "herdr"
78
+ | "copy"
79
+ | "cd"
80
+ | "seq"
81
+ | "listing"
82
+ | "eval"
83
+ | "env-provider"
84
+ | "curl";
85
+ readonly stdout?: "opaque" | "arguments" | "stdin";
86
+ };
87
+ export type Registry = {
88
+ readonly entries: ReadonlyMap<string, ExecutableEntry>;
89
+ readonly startupVariables: ReadonlySet<string>;
90
+ readonly blocked: ReadonlyMap<string, string>;
91
+ };
92
+ export type InvocationPlan = {
93
+ readonly remote: boolean;
94
+ readonly xargsInput: XargsInput | null;
95
+ readonly roles: readonly {
96
+ readonly index: number;
97
+ readonly role: OperandRole;
98
+ readonly literal?: string;
99
+ }[];
100
+ readonly issues: readonly Issue[];
101
+ readonly children: readonly {
102
+ readonly start: number;
103
+ readonly end: number;
104
+ readonly appendInput: boolean;
105
+ readonly placeholder: string | null;
106
+ readonly findRoots: readonly string[] | null;
107
+ readonly runtimeWrapper?: boolean;
108
+ }[];
109
+ readonly assignments: readonly {
110
+ readonly name: string;
111
+ readonly index: number;
112
+ readonly value: string;
113
+ }[];
114
+ readonly stdout: "opaque" | "arguments" | "stdin";
115
+ readonly delta: "none" | "cd" | "assign";
116
+ readonly stdinCode: Language | null;
117
+ readonly refuseSuppliedStdin: boolean;
118
+ readonly pipelinePaths: boolean;
119
+ readonly startupFiles: readonly RegExp[];
120
+ readonly copyDestination: boolean;
121
+ readonly inPlaceBackupSuffix: string | null;
122
+ readonly scriptLanguage: Language;
123
+ readonly codePositionals: readonly number[];
124
+ readonly cwdOperands: readonly { readonly index: number; readonly literal?: string }[];
125
+ };
126
+ const data: OperandRole = "data";
127
+ const path: OperandRole = "path";
128
+ const flag: Option = { roles: [] };
129
+ const arg = (...roles: OperandRole[]): Option => ({ roles });
130
+ const code = (language: Language): OperandRole => ({ code: language });
131
+ const source = (detail: string): Issue => ({ kind: "violation", rule: "secret-source", detail });
132
+ const unknown = (detail: string): Issue => ({
133
+ kind: "missing-knowledge",
134
+ rule: "no-analyzer",
135
+ detail,
136
+ });
137
+ const wrapper = (name: string, suggestion: string): Issue => ({
138
+ kind: "violation",
139
+ rule: "wrapper-required",
140
+ detail: `${name}: ${suggestion}`,
141
+ });
142
+ const flags = (...names: string[]): Readonly<Record<string, Option>> =>
143
+ Object.fromEntries(names.map((name) => [name, flag]));
144
+ const readerOptions = {
145
+ ...flags("-q", "-v", "--quiet", "--verbose"),
146
+ "-n": arg(data),
147
+ "-c": arg(data),
148
+ };
149
+ const shellOptions = {
150
+ ...flags("-e", "-u", "-x", "-s", "--noprofile", "--norc"),
151
+ "-c": arg(code("bash")),
152
+ "--rcfile": { roles: ["script"], issue: unknown("shell startup file") },
153
+ "--init-file": { roles: ["script"], issue: unknown("shell startup file") },
154
+ "-o": arg(data),
155
+ "-l": { roles: [], issue: unknown("login shell startup") },
156
+ "-i": { roles: [], issue: unknown("interactive shell startup") },
157
+ } satisfies Readonly<Record<string, Option>>;
158
+ const nodeOptions = {
159
+ ...flags("--version", "-v", "--help"),
160
+ "-e": arg(code("runtime")),
161
+ "--eval": arg(code("runtime")),
162
+ "-p": arg(code("runtime")),
163
+ "--print": arg(code("runtime")),
164
+ "-r": { roles: [path], issue: unknown("runtime preload") },
165
+ "--require": { roles: [path], issue: unknown("runtime preload") },
166
+ "--import": { roles: [path], issue: unknown("runtime import") },
167
+ "--env-file": arg(path),
168
+ };
169
+ const pythonOptions = {
170
+ ...flags("-I", "-S", "-B", "-E", "-u", "--version", "-V", "--help"),
171
+ "-c": arg(code("runtime")),
172
+ "-m": arg(data),
173
+ "-W": arg(data),
174
+ "--check-hash-based-pycs": arg(data),
175
+ "-X": arg(data),
176
+ };
177
+
178
+ const gitDiffOptions = {
179
+ ...Object.fromEntries(
180
+ ["-U", "-C", "-M"].map((name) => [name, { roles: [], attachedValue: /^[0-9]+%?$/ }]),
181
+ ),
182
+ "--unified": flag,
183
+ "--dirstat": flag,
184
+ "--ignore-submodules": arg(data),
185
+ ...flags(
186
+ "--stat",
187
+ "--shortstat",
188
+ "--numstat",
189
+ "--name-only",
190
+ "--name-status",
191
+ "--check",
192
+ "--cached",
193
+ "--staged",
194
+ "--no-color",
195
+ "--color",
196
+ "--no-ext-diff",
197
+ "--no-textconv",
198
+ "--exit-code",
199
+ "--quiet",
200
+ "--patch",
201
+ "--no-patch",
202
+ "--raw",
203
+ "--summary",
204
+ "--compact-summary",
205
+ "--ignore-space-change",
206
+ "--ignore-all-space",
207
+ "--ignore-space-at-eol",
208
+ "--ignore-cr-at-eol",
209
+ "--word-diff",
210
+ "--relative",
211
+ "--binary",
212
+ "--full-index",
213
+ "--cc",
214
+ "-c",
215
+ "--no-renames",
216
+ "-p",
217
+ "-s",
218
+ ),
219
+ "--diff-filter": arg(data),
220
+ "--stat-width": arg(data),
221
+ "--stat-name-width": arg(data),
222
+ "--word-diff-regex": arg(data),
223
+ "--output-indicator-new": arg(data),
224
+ "--output-indicator-old": arg(data),
225
+ "--output-indicator-context": arg(data),
226
+ };
227
+ const gitLogOptions = {
228
+ ...gitDiffOptions,
229
+ ...flags(
230
+ "--oneline",
231
+ "--decorate",
232
+ "--graph",
233
+ "--all",
234
+ "--reverse",
235
+ "--first-parent",
236
+ "--no-merges",
237
+ "--merges",
238
+ "--follow",
239
+ "--abbrev-commit",
240
+ "--date-order",
241
+ "--topo-order",
242
+ "--no-decorate",
243
+ "--branches",
244
+ "--tags",
245
+ "--remotes",
246
+ "--not",
247
+ "--reflog",
248
+ "--timestamp",
249
+ "-g",
250
+ ),
251
+ "--format": arg(data),
252
+ "--pretty": arg(data),
253
+ "--author": arg(data),
254
+ "--committer": arg(data),
255
+ "--since": arg(data),
256
+ "--until": arg(data),
257
+ "--before": arg(data),
258
+ "--after": arg(data),
259
+ "--date": arg(data),
260
+ "--max-count": arg(data),
261
+ "-n": arg(data),
262
+ "--skip": arg(data),
263
+ "--max-parents": arg(data),
264
+ "--min-parents": arg(data),
265
+ };
266
+
267
+ export const EXECUTABLES: readonly ExecutableEntry[] = [
268
+ {
269
+ name: "git",
270
+ informationFlags: ["--help", "--version"],
271
+ startupFiles: [/(?:^|\/)\.git\/config$/],
272
+ operands: [data],
273
+ semantics: "git",
274
+ startup: [
275
+ "GIT_CONFIG",
276
+ "GIT_CONFIG_COUNT",
277
+ "GIT_CONFIG_GLOBAL",
278
+ "GIT_CONFIG_SYSTEM",
279
+ "GIT_EDITOR",
280
+ "GIT_SEQUENCE_EDITOR",
281
+ "GIT_PAGER",
282
+ "GIT_SSH_COMMAND",
283
+ "GIT_SSH",
284
+ "GIT_PROXY_COMMAND",
285
+ "GIT_EXEC_PATH",
286
+ "GIT_ASKPASS",
287
+ "SSH_ASKPASS",
288
+ "EDITOR",
289
+ "VISUAL",
290
+ "GIT_CONFIG_PARAMETERS",
291
+ "GIT_EXTERNAL_DIFF",
292
+ "PAGER",
293
+ "EDITOR",
294
+ "VISUAL",
295
+ ],
296
+ options: {
297
+ "-C": arg("names"),
298
+ "-c": arg(data),
299
+ "--config-env": { roles: [data], issue: unknown("git config override") },
300
+ ...flags(
301
+ "--help",
302
+ "--version",
303
+ "--no-optional-locks",
304
+ "--literal-pathspecs",
305
+ "--no-replace-objects",
306
+ ),
307
+ "--git-dir": arg("names"),
308
+ "--exec-path": { roles: [data], issue: unknown("git executable directory") },
309
+ "--textconv": { roles: [], issue: unknown("git text converter") },
310
+ "--ext-diff": { roles: [], issue: unknown("git external diff") },
311
+ "--upload-pack": { roles: [data], issue: unknown("git upload program") },
312
+ "--receive-pack": { roles: [data], issue: unknown("git receive program") },
313
+ "--work-tree": arg("names"),
314
+ ...flags("--no-pager", "--paginate", "-P", "--bare"),
315
+ },
316
+ subcommands: {
317
+ switch: {
318
+ operands: [data],
319
+ options: { "-c": arg(data), "-C": arg(data), ...flags("-q", "--detach", "--force", "-f") },
320
+ },
321
+ bisect: { operands: [data], subcommands: { run: { operands: [data] } } },
322
+ submodule: {
323
+ operands: [data],
324
+ subcommands: {
325
+ foreach: { operands: [data], options: flags("--recursive", "--quiet", "-q") },
326
+ },
327
+ options: {
328
+ ...flags(
329
+ "--init",
330
+ "--recursive",
331
+ "--force",
332
+ "--remote",
333
+ "--checkout",
334
+ "--merge",
335
+ "--rebase",
336
+ "--quiet",
337
+ "-q",
338
+ ),
339
+ "--reference": arg("names"),
340
+ "--depth": arg(data),
341
+ "--jobs": arg(data),
342
+ },
343
+ },
344
+ "ls-remote": {
345
+ operands: [data],
346
+ options: flags("--heads", "--tags", "--refs", "--exit-code", "-q"),
347
+ },
348
+ "merge-tree": {
349
+ operands: [data],
350
+ options: flags(
351
+ "--write-tree",
352
+ "--name-only",
353
+ "--no-messages",
354
+ "--allow-unrelated-histories",
355
+ ),
356
+ },
357
+ apply: {
358
+ operands: ["content"],
359
+ options: flags(
360
+ "--3way",
361
+ "--check",
362
+ "--stat",
363
+ "--numstat",
364
+ "--cached",
365
+ "--index",
366
+ "--reverse",
367
+ "-R",
368
+ ),
369
+ },
370
+ "update-index": {
371
+ operands: ["names"],
372
+ options: {
373
+ "--cacheinfo": arg(data),
374
+ ...flags("--add", "--remove", "--force-remove", "--refresh"),
375
+ },
376
+ },
377
+ tag: {
378
+ operands: [data],
379
+ options: {
380
+ ...flags("--list", "-l", "-d", "-f", "-a", "-s"),
381
+ "-m": arg(data),
382
+ "--format": arg(data),
383
+ "--sort": arg(data),
384
+ "-F": arg("content"),
385
+ },
386
+ },
387
+ stash: {
388
+ operands: ["names"],
389
+ options: {
390
+ ...flags(
391
+ "-u",
392
+ "--include-untracked",
393
+ "--all",
394
+ "-a",
395
+ "-q",
396
+ "--quiet",
397
+ "--keep-index",
398
+ "--staged",
399
+ ),
400
+ "-m": arg(data),
401
+ },
402
+ },
403
+ clone: {
404
+ operands: ["names"],
405
+ options: {
406
+ "--depth": arg(data),
407
+ "--reference": arg("names"),
408
+ "-b": arg(data),
409
+ "--branch": arg(data),
410
+ ...flags("--recursive", "--recurse-submodules", "--bare", "--mirror", "--quiet", "-q"),
411
+ },
412
+ },
413
+ status: {
414
+ operands: ["names"],
415
+ options: {
416
+ ...flags(
417
+ "--short",
418
+ "-s",
419
+ "--branch",
420
+ "-b",
421
+ "--porcelain",
422
+ "--ignored",
423
+ "--untracked-files",
424
+ "-u",
425
+ ),
426
+ "--ignore-submodules": arg(data),
427
+ },
428
+ },
429
+ branch: {
430
+ operands: [data],
431
+ options: {
432
+ ...flags(
433
+ "--show-current",
434
+ "-q",
435
+ "--quiet",
436
+ "--merged",
437
+ "--no-merged",
438
+ "-d",
439
+ "-D",
440
+ "-a",
441
+ "-r",
442
+ "-v",
443
+ "-l",
444
+ "-m",
445
+ "-M",
446
+ "--list",
447
+ "--contains",
448
+ "-f",
449
+ "--force",
450
+ "--no-track",
451
+ ),
452
+ "--sort": arg(data),
453
+ "-c": flag,
454
+ "-C": flag,
455
+ "--format": arg(data),
456
+ "--set-upstream-to": arg(data),
457
+ "--track": flag,
458
+ },
459
+ },
460
+ checkout: {
461
+ operands: ["names"],
462
+ options: {
463
+ ...flags("-q", "-f", "--detach", "--ours", "--theirs"),
464
+ "-b": arg(data),
465
+ "-B": arg(data),
466
+ },
467
+ },
468
+ difftool: {
469
+ operands: [path],
470
+ options: {
471
+ "-x": arg(code("bash")),
472
+ "--extcmd": arg(code("bash")),
473
+ "-t": arg(data),
474
+ "--tool": arg(data),
475
+ ...flags("--tool-help", "-y", "--no-prompt"),
476
+ },
477
+ },
478
+ rebase: {
479
+ operands: [data],
480
+ options: {
481
+ ...flags(
482
+ "--continue",
483
+ "--abort",
484
+ "--skip",
485
+ "-i",
486
+ "--interactive",
487
+ "--autosquash",
488
+ "--autostash",
489
+ "--keep-base",
490
+ "--rebase-merges",
491
+ "-q",
492
+ "--quiet",
493
+ "--show-current-patch",
494
+ ),
495
+ "--onto": arg(data),
496
+ "-x": arg(code("bash")),
497
+ "--exec": arg(code("bash")),
498
+ },
499
+ },
500
+ push: {
501
+ operands: [data],
502
+ options: flags(
503
+ "-u",
504
+ "--set-upstream",
505
+ "--force-with-lease",
506
+ "--force-if-includes",
507
+ "-f",
508
+ "--force",
509
+ "--tags",
510
+ "--follow-tags",
511
+ "--delete",
512
+ "--dry-run",
513
+ "-q",
514
+ "--quiet",
515
+ "--porcelain",
516
+ "--all",
517
+ ),
518
+ },
519
+ fetch: {
520
+ operands: [data],
521
+ options: {
522
+ ...flags("-u", "--update-head-ok"),
523
+ ...flags(
524
+ "-q",
525
+ "--quiet",
526
+ "--all",
527
+ "--prune",
528
+ "-p",
529
+ "--tags",
530
+ "--no-tags",
531
+ "--force",
532
+ "-f",
533
+ ),
534
+ "--depth": arg(data),
535
+ },
536
+ },
537
+ pull: {
538
+ operands: [data],
539
+ options: flags("--ff-only", "--rebase", "--no-rebase", "--autostash", "-q", "--quiet"),
540
+ },
541
+ merge: {
542
+ operands: [data],
543
+ options: {
544
+ ...flags(
545
+ "--ff-only",
546
+ "--no-ff",
547
+ "--no-commit",
548
+ "--abort",
549
+ "--continue",
550
+ "--squash",
551
+ "--no-edit",
552
+ "-q",
553
+ ),
554
+ "-m": arg(data),
555
+ },
556
+ },
557
+ reset: {
558
+ operands: ["names"],
559
+ options: flags("--hard", "--soft", "--mixed", "--keep", "--merge", "-q"),
560
+ },
561
+ restore: {
562
+ operands: ["names"],
563
+ options: {
564
+ ...flags("--staged", "--worktree", "--ours", "--theirs"),
565
+ "--source": arg(data),
566
+ "-s": arg(data),
567
+ },
568
+ },
569
+ remote: {
570
+ operands: [data],
571
+ options: flags("-v", "--verbose", "-a", "--auto", "--all", "--push", "--add", "--delete"),
572
+ },
573
+ worktree: {
574
+ operands: ["names"],
575
+ options: {
576
+ ...flags("--porcelain", "--force", "-f", "--detach", "-q", "--quiet"),
577
+ "-b": arg(data),
578
+ "-B": arg(data),
579
+ },
580
+ },
581
+ "ls-files": {
582
+ operands: ["names"],
583
+ options: {
584
+ ...flags(
585
+ "-z",
586
+ "--cached",
587
+ "--others",
588
+ "--exclude-standard",
589
+ "--modified",
590
+ "--deleted",
591
+ "--stage",
592
+ "-s",
593
+ "--unmerged",
594
+ "-u",
595
+ ),
596
+ "--exclude": arg(data),
597
+ },
598
+ },
599
+ "ls-tree": {
600
+ operands: [data],
601
+ options: flags(
602
+ "-r",
603
+ "-t",
604
+ "-d",
605
+ "--name-only",
606
+ "--name-status",
607
+ "--full-tree",
608
+ "--long",
609
+ "-l",
610
+ "-z",
611
+ ),
612
+ },
613
+ "merge-base": {
614
+ operands: [data],
615
+ options: flags("--is-ancestor", "--fork-point", "--all", "--octopus"),
616
+ },
617
+ "rev-list": {
618
+ operands: [data],
619
+ numericOption: true,
620
+ options: { ...gitLogOptions, ...flags("--count", "--left-right", "--boundary") },
621
+ },
622
+ show: {
623
+ numericOption: true,
624
+ readsContent: true,
625
+ operands: [path],
626
+ options: {
627
+ ...gitLogOptions,
628
+ "--textconv": { roles: [], issue: unknown("git text converter") },
629
+ "--ext-diff": { roles: [], issue: unknown("git external diff") },
630
+ },
631
+ },
632
+ "rev-parse": {
633
+ operands: ["names"],
634
+ options: {
635
+ ...flags(
636
+ "--git-dir",
637
+ "--absolute-git-dir",
638
+ "--git-common-dir",
639
+ "--show-toplevel",
640
+ "--show-prefix",
641
+ "--is-inside-work-tree",
642
+ "--is-bare-repository",
643
+ "--short",
644
+ "--verify",
645
+ "--abbrev-ref",
646
+ "--symbolic-full-name",
647
+ "-q",
648
+ "--quiet",
649
+ "--branches",
650
+ "--tags",
651
+ "--remotes",
652
+ "--all",
653
+ "--not",
654
+ ),
655
+ "--git-path": arg("names"),
656
+ },
657
+ },
658
+ diff: {
659
+ operands: [path],
660
+ readsContent: true,
661
+ options: {
662
+ ...gitDiffOptions,
663
+ "-S": arg(data),
664
+ "-G": arg(data),
665
+ "-I": arg(data),
666
+ "--grep": arg(data),
667
+ "--dst-prefix": arg(data),
668
+ "--src-prefix": arg(data),
669
+ "-l": arg(data),
670
+ "--line-prefix": arg(data),
671
+ "--ws-error-highlight": arg(data),
672
+ "--color-moved-ws": arg(data),
673
+ "--color": flag,
674
+ ...flags("-p", "-u", "-R", "-w", "-b"),
675
+ "--no-index": flag,
676
+ "--output": arg("write"),
677
+ "-O": arg(path),
678
+ "--ext-diff": { roles: [], issue: unknown("git external diff") },
679
+ "--textconv": { roles: [], issue: unknown("git text converter") },
680
+ },
681
+ },
682
+ log: {
683
+ numericOption: true,
684
+ operands: [path],
685
+ options: {
686
+ ...gitLogOptions,
687
+ "-S": arg(data),
688
+ "-G": arg(data),
689
+ "-I": arg(data),
690
+ "--grep": arg(data),
691
+ "--dst-prefix": arg(data),
692
+ "--src-prefix": arg(data),
693
+ "-l": arg(data),
694
+ "--line-prefix": arg(data),
695
+ "--ws-error-highlight": arg(data),
696
+ "--color-moved-ws": arg(data),
697
+ "--color": flag,
698
+ "--textconv": { roles: [], issue: unknown("git text converter") },
699
+ "--ext-diff": { roles: [], issue: unknown("git external diff") },
700
+ },
701
+ },
702
+ blame: {
703
+ readsContent: true,
704
+ operands: [path],
705
+ options: {
706
+ "-L": arg(data),
707
+ "--contents": arg("content"),
708
+ "--ignore-revs-file": arg("content"),
709
+ "-S": arg("content"),
710
+ ...flags("-p", "--porcelain", "--line-porcelain", "-l", "-n", "-w"),
711
+ },
712
+ },
713
+ "for-each-ref": {
714
+ operands: [data],
715
+ options: {
716
+ "--format": arg(data),
717
+ "--sort": arg(data),
718
+ "--count": arg(data),
719
+ "--points-at": arg(data),
720
+ "--contains": arg(data),
721
+ "--no-contains": arg(data),
722
+ "--merged": arg(data),
723
+ "--no-merged": arg(data),
724
+ ...flags(
725
+ "--shell",
726
+ "-s",
727
+ "--perl",
728
+ "-p",
729
+ "--python",
730
+ "--tcl",
731
+ "--stdin",
732
+ "--ignore-case",
733
+ ),
734
+ },
735
+ },
736
+ "commit-tree": {
737
+ operands: [data],
738
+ options: { "-p": arg(data), "-m": arg(data), "-F": arg("content") },
739
+ },
740
+ "check-ignore": {
741
+ operands: ["names"],
742
+ options: flags("-v", "--verbose", "-q", "--quiet", "--stdin", "-z", "--no-index"),
743
+ },
744
+ "symbolic-ref": {
745
+ operands: [data],
746
+ options: {
747
+ "-m": arg(data),
748
+ ...flags("--short", "-q", "--quiet", "--no-recurse", "-d", "--delete"),
749
+ },
750
+ },
751
+ init: {
752
+ operands: ["write"],
753
+ options: {
754
+ "-b": arg(data),
755
+ "--initial-branch": arg(data),
756
+ ...flags("-q", "--quiet", "--bare"),
757
+ },
758
+ },
759
+ replay: {
760
+ operands: [data],
761
+ options: { "--onto": arg(data), "--advance": arg(data), "--contained": flag },
762
+ },
763
+ reflog: { operands: [data], options: gitLogOptions },
764
+ cherry: { operands: [data], options: flags("-v") },
765
+ "cherry-pick": {
766
+ operands: [data],
767
+ options: {
768
+ "-m": arg(data),
769
+ ...flags("--no-commit", "-n", "--no-edit", "--continue", "--abort", "--skip"),
770
+ },
771
+ },
772
+ revert: {
773
+ operands: [data],
774
+ options: {
775
+ "-m": arg(data),
776
+ ...flags("--no-edit", "--no-commit", "-n", "--continue", "--abort", "--skip"),
777
+ },
778
+ },
779
+ "diff-tree": {
780
+ operands: [path],
781
+ options: { ...gitDiffOptions, ...flags("--no-commit-id", "-r", "-t", "--root") },
782
+ },
783
+ "cat-file": {
784
+ readsContent: true,
785
+ operands: [path],
786
+ options: {
787
+ ...flags("-p", "-t", "-s", "-e"),
788
+ ...Object.fromEntries(
789
+ ["--batch", "--batch-check", "--batch-command", "--batch-all-objects"].map((option) => [
790
+ option,
791
+ { roles: [], issue: unknown("git batch object input") },
792
+ ]),
793
+ ),
794
+ },
795
+ },
796
+ archive: {
797
+ readsContent: true,
798
+ operands: [data, path],
799
+ options: {
800
+ "-o": arg("write"),
801
+ "--output": arg("write"),
802
+ "--format": arg(data),
803
+ "--prefix": arg(data),
804
+ ...flags("-v", "--verbose", "--list"),
805
+ },
806
+ },
807
+ grep: {
808
+ readsContent: true,
809
+ operands: [data, path],
810
+ options: {
811
+ ...flags("-c", "-r", "-P", "-v", "-o", "--only-matching"),
812
+ "-A": arg(data),
813
+ "-B": arg(data),
814
+ "-C": arg(data),
815
+ ...flags(
816
+ "--no-index",
817
+ "--untracked",
818
+ "--no-exclude-standard",
819
+ "-n",
820
+ "-i",
821
+ "-l",
822
+ "-I",
823
+ "-w",
824
+ "-E",
825
+ "-F",
826
+ "-q",
827
+ "-h",
828
+ ),
829
+ "-e": arg(data),
830
+ "-f": arg(path),
831
+ "-O": { roles: [], issue: unknown("git pager execution") },
832
+ "--open-files-in-pager": { roles: [], issue: unknown("git pager execution") },
833
+ "--textconv": { roles: [], issue: unknown("git text converter") },
834
+ },
835
+ },
836
+ commit: {
837
+ operands: ["names"],
838
+ options: {
839
+ "-m": arg(data),
840
+ "--message": arg(data),
841
+ "--fixup": arg(data),
842
+ "-F": arg(path),
843
+ "--file": arg(path),
844
+ ...flags(
845
+ "--amend",
846
+ "--no-edit",
847
+ "--no-verify",
848
+ "--allow-empty",
849
+ "-a",
850
+ "-q",
851
+ "-v",
852
+ "-s",
853
+ "--signoff",
854
+ ),
855
+ },
856
+ },
857
+ add: {
858
+ operands: ["names"],
859
+ options: flags(
860
+ "-A",
861
+ "--all",
862
+ "-u",
863
+ "-f",
864
+ "-v",
865
+ "--update",
866
+ "--force",
867
+ "--intent-to-add",
868
+ "-N",
869
+ ),
870
+ },
871
+ rm: { operands: ["names"], options: flags("-r", "-f", "-q", "--cached", "--ignore-unmatch") },
872
+ mv: { operands: ["names"] },
873
+ credential: { operands: [data], effect: source("git credential") },
874
+ config: {
875
+ operands: [data],
876
+ options: flags("--get", "--get-all", "--local", "--global", "--show-origin"),
877
+ effect: source("git config can contain credentials"),
878
+ },
879
+ },
880
+ },
881
+ {
882
+ name: "grep",
883
+ aliases: ["egrep", "fgrep", "ggrep", "rg"],
884
+ operands: [data, path],
885
+ options: {
886
+ "-e": arg(data),
887
+ "--regexp": arg(data),
888
+ "-f": arg(path),
889
+ "--file": arg(path),
890
+ "-g": arg(path),
891
+ "--glob": arg(path),
892
+ "--iglob": arg(path),
893
+ "--include": arg(path),
894
+ "--exclude": arg(data),
895
+ "--exclude-dir": arg(data),
896
+ "-A": arg(data),
897
+ "-B": arg(data),
898
+ "-C": arg(data),
899
+ "-m": arg(data),
900
+ "--max-count": arg(data),
901
+ "-t": arg("rg-type"),
902
+ "--type": arg("rg-type"),
903
+ "--type-add": arg(data),
904
+ "--type-not": arg(data),
905
+ "-T": arg(data),
906
+ ...flags(
907
+ "--files",
908
+ "--hidden",
909
+ "--no-ignore",
910
+ "--no-ignore-vcs",
911
+ "-u",
912
+ "-U",
913
+ "-c",
914
+ "--count",
915
+ "--count-matches",
916
+ "--files-with-matches",
917
+ "--files-without-match",
918
+ "--line-number",
919
+ "--no-heading",
920
+ "--no-filename",
921
+ "--fixed-strings",
922
+ "-N",
923
+ "-L",
924
+ "--heading",
925
+ "--smart-case",
926
+ "-S",
927
+ "-I",
928
+ "--text",
929
+ "-a",
930
+ "--multiline",
931
+ "--multiline-dotall",
932
+ "--pcre2",
933
+ "-P",
934
+ "--null",
935
+ "-0",
936
+ "--null-data",
937
+ "-z",
938
+ ),
939
+ "--max-columns": arg(data),
940
+ "--max-columns-preview": flag,
941
+ "--color": arg(data),
942
+ "--threads": arg(data),
943
+ "-j": arg(data),
944
+ "--max-depth": arg(data),
945
+ "--sort": arg(data),
946
+ "--sortr": arg(data),
947
+ "--max-filesize": arg(data),
948
+ "--context": arg(data),
949
+ "--after-context": arg(data),
950
+ "--before-context": arg(data),
951
+ "--replace": arg(data),
952
+ "--pre": { roles: [data], issue: unknown("rg preprocessor") },
953
+ "--hostname-bin": { roles: [data], issue: unknown("rg hostname program") },
954
+ ...flags(
955
+ "-n",
956
+ "-i",
957
+ "-v",
958
+ "-r",
959
+ "-R",
960
+ "-l",
961
+ "-h",
962
+ "-H",
963
+ "-o",
964
+ "-E",
965
+ "-F",
966
+ "-s",
967
+ "-q",
968
+ "-w",
969
+ "-x",
970
+ ),
971
+ },
972
+ },
973
+ {
974
+ name: "cd",
975
+ builtinOnly: true,
976
+ aliases: ["Set-Location", "Push-Location", "sl"],
977
+ operands: ["names"],
978
+ semantics: "cd",
979
+ startup: ["CDPATH"],
980
+ options: flags("-L", "-P", "-e"),
981
+ },
982
+ {
983
+ name: "head",
984
+ numericOption: true,
985
+ readsContent: true,
986
+ operands: [path],
987
+ options: readerOptions,
988
+ stdout: "opaque",
989
+ },
990
+ {
991
+ name: "tail",
992
+ numericOption: true,
993
+ readsContent: true,
994
+ operands: [path],
995
+ options: { ...readerOptions, ...flags("-f", "-F") },
996
+ stdout: "opaque",
997
+ },
998
+ {
999
+ name: "echo",
1000
+ optionGrammar: "echo",
1001
+ optionsBeforeOperands: true,
1002
+ operands: [data],
1003
+ stdout: "arguments",
1004
+ options: flags("-n", "-e", "-E"),
1005
+ },
1006
+ {
1007
+ name: "printf",
1008
+ optionGrammar: "printf",
1009
+ optionsBeforeOperands: true,
1010
+ operands: [data],
1011
+ stdout: "opaque",
1012
+ options: { "-v": { roles: [data], issue: unknown("printf variable write") } },
1013
+ },
1014
+ {
1015
+ name: "bun",
1016
+ informationFlags: ["--help", "--version"],
1017
+ operands: ["script", path],
1018
+ options: {
1019
+ ...flags("--no-env-file", "--bun", "--silent", "--check", "--version", "-v", "--help", "-h"),
1020
+ "--filter": arg(data),
1021
+ "--cwd": arg("names"),
1022
+ "-e": arg(code("runtime")),
1023
+ "--eval": arg(code("runtime")),
1024
+ "-p": arg(code("runtime")),
1025
+ "--print": arg(code("runtime")),
1026
+ "-r": { roles: ["script"], issue: unknown("runtime preload") },
1027
+ "--preload": { roles: ["script"], issue: unknown("runtime preload") },
1028
+ "-c": { roles: ["script"], issue: unknown("runtime configuration") },
1029
+ "--config": { roles: ["script"], issue: unknown("runtime configuration") },
1030
+ "--install": arg(data),
1031
+ },
1032
+ semantics: "runtime",
1033
+ startup: ["BUN_OPTIONS", "NODE_OPTIONS"],
1034
+ },
1035
+ {
1036
+ name: "node",
1037
+ aliases: ["nodejs"],
1038
+ informationFlags: ["--help", "--version", "-v"],
1039
+ operands: ["script", path],
1040
+ options: {
1041
+ ...nodeOptions,
1042
+ ...flags("--check", "--test", "--experimental-strip-types"),
1043
+ "-c": { roles: ["script"], issue: unknown("runtime syntax check") },
1044
+ },
1045
+ semantics: "runtime",
1046
+ startup: ["NODE_OPTIONS", "NODE_PATH"],
1047
+ },
1048
+ {
1049
+ name: "python3",
1050
+ informationFlags: ["--help", "--version", "-V"],
1051
+ namePattern: /^python(?:[0-9]+(?:\.[0-9]+)*t?)?$/,
1052
+ aliases: ["python", "pypy", "pypy3"],
1053
+ operands: ["script", path],
1054
+ options: pythonOptions,
1055
+ semantics: "python",
1056
+ startup: ["PYTHONPATH", "PYTHONHOME", "PYTHONSTARTUP"],
1057
+ },
1058
+ {
1059
+ name: "perl",
1060
+ informationFlags: ["--help", "--version", "-v"],
1061
+ operands: ["script", path],
1062
+ semantics: "runtime",
1063
+ options: {
1064
+ ...flags("--help", "--version", "-v", "-w", "-n", "-p"),
1065
+ "-e": arg(code("runtime")),
1066
+ "-E": arg(code("runtime")),
1067
+ "-I": arg(path),
1068
+ "-M": { roles: [data], issue: unknown("Perl preload") },
1069
+ },
1070
+ startup: ["PERL5OPT", "PERL5LIB", "PERLLIB"],
1071
+ },
1072
+ {
1073
+ name: "ruby",
1074
+ informationFlags: ["--help", "--version", "-v"],
1075
+ operands: ["script", path],
1076
+ semantics: "runtime",
1077
+ options: {
1078
+ ...flags("--help", "--version", "-v", "-w", "-n", "-p"),
1079
+ "-e": arg(code("runtime")),
1080
+ "-I": arg(path),
1081
+ "-r": { roles: [path], issue: unknown("Ruby preload") },
1082
+ },
1083
+ startup: ["RUBYOPT", "RUBYLIB", "GEM_HOME", "GEM_PATH"],
1084
+ },
1085
+ {
1086
+ name: "php",
1087
+ operands: ["script", path],
1088
+ semantics: "runtime",
1089
+ options: {
1090
+ ...flags("--help", "--version", "-v", "-n"),
1091
+ "-r": arg(code("runtime")),
1092
+ "-B": arg(code("runtime")),
1093
+ "-R": arg(code("runtime")),
1094
+ "-E": arg(code("runtime")),
1095
+ "-F": arg(code("runtime")),
1096
+ "-f": arg("script"),
1097
+ },
1098
+ },
1099
+ {
1100
+ name: "lua",
1101
+ aliases: ["luajit", "lua5.4", "lua5.3"],
1102
+ informationFlags: ["--help", "--version", "-v"],
1103
+ operands: ["script", path],
1104
+ semantics: "runtime",
1105
+ options: {
1106
+ ...flags("--help", "--version", "-v"),
1107
+ "-e": arg(code("runtime")),
1108
+ "-l": { roles: [data], issue: unknown("Lua preload") },
1109
+ },
1110
+ },
1111
+ {
1112
+ name: "osascript",
1113
+ operands: ["script", path],
1114
+ semantics: "runtime",
1115
+ options: { "-e": arg(code("runtime")), "-l": arg(data), "-s": arg(data) },
1116
+ },
1117
+ {
1118
+ name: "deno",
1119
+ operands: ["script", path],
1120
+ semantics: "runtime",
1121
+ options: flags("--help", "--version"),
1122
+ subcommands: {
1123
+ repl: { operands: [data], effect: unknown("runtime REPL code") },
1124
+ eval: { operands: [code("runtime"), data] },
1125
+ run: { operands: ["script", path] },
1126
+ },
1127
+ },
1128
+ {
1129
+ name: "sqlite3",
1130
+ operands: [path, data],
1131
+ readsStdinCode: true,
1132
+ options: flags(
1133
+ "-readonly",
1134
+ "-readwrite",
1135
+ "-batch",
1136
+ "-header",
1137
+ "-noheader",
1138
+ "-column",
1139
+ "-csv",
1140
+ "-json",
1141
+ ),
1142
+ },
1143
+ ...["expect", "wish", "swift", "ocaml", "erl", "clj"].map((name): ExecutableEntry => ({
1144
+ name,
1145
+ operands: ["script", path],
1146
+ semantics: "runtime",
1147
+ })),
1148
+ {
1149
+ name: "java",
1150
+ options: {
1151
+ ...flags("--version", "-version", "--help", "-help"),
1152
+ "-jar": arg("script"),
1153
+ "-cp": arg(path),
1154
+ "-classpath": arg(path),
1155
+ },
1156
+ operands: [path],
1157
+ startup: ["JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "CLASSPATH", "_JAVA_OPTIONS", "JAVA_HOME"],
1158
+ },
1159
+ {
1160
+ name: "herdr",
1161
+ options: {
1162
+ "--format": arg(data),
1163
+ "--env": arg(data),
1164
+ ...flags("--focus"),
1165
+ "--label": arg(data),
1166
+ "--until": arg(data),
1167
+ "--kind": arg(data),
1168
+ "--pane": arg(data),
1169
+ "--source": arg(data),
1170
+ "--lines": arg(data),
1171
+ "--match": arg(data),
1172
+ "--regex": arg(data),
1173
+ "--timeout": arg(data),
1174
+ "--file": arg("content"),
1175
+ "--cwd": arg("names"),
1176
+ "--command": arg(code("bash")),
1177
+ "--name": arg(data),
1178
+ "--direction": arg(data),
1179
+ "--workspace": arg(data),
1180
+ "--tab": arg(data),
1181
+ ...flags("--help", "--all", "--json", "--no-focus", "--wait", "--current"),
1182
+ },
1183
+ informationFlags: ["--help"],
1184
+ operands: [data],
1185
+ semantics: "herdr",
1186
+ subcommands: {
1187
+ agent: { operands: [data] },
1188
+ workspace: { operands: [data] },
1189
+ tab: { operands: [data] },
1190
+ notification: { operands: [data] },
1191
+ session: { operands: [data] },
1192
+ pane: {
1193
+ operands: [data],
1194
+ subcommands: {
1195
+ layout: { operands: [data] },
1196
+ close: { operands: [data] },
1197
+ list: { operands: [data] },
1198
+ get: { operands: [data] },
1199
+ read: { operands: [data] },
1200
+ current: { operands: [data] },
1201
+ split: { operands: [data] },
1202
+ run: { operands: [data] },
1203
+ "send-keys": { operands: [data] },
1204
+ "send-text": { operands: [data] },
1205
+ },
1206
+ },
1207
+ },
1208
+ },
1209
+ {
1210
+ name: "cat",
1211
+ options: flags("-n", "-b", "-s", "-v", "-e", "-t", "-u", "-A", "-E", "-T"),
1212
+ readsContent: true,
1213
+ operands: [path],
1214
+ stdout: "stdin",
1215
+ },
1216
+ {
1217
+ name: "shasum",
1218
+ readsContent: true,
1219
+ aliases: ["sha1sum", "sha224sum", "sha256sum", "sha384sum", "sha512sum", "md5sum", "b2sum"],
1220
+ operands: [path],
1221
+ options: {
1222
+ ...flags("-b", "-t", "--binary", "--text", "--tag"),
1223
+ "-a": arg(data),
1224
+ "--algorithm": arg(data),
1225
+ "-c": { roles: [], issue: unknown("checksum indirect file list") },
1226
+ "--check": { roles: [], issue: unknown("checksum indirect file list") },
1227
+ },
1228
+ },
1229
+ {
1230
+ name: "tee",
1231
+ operands: ["write"],
1232
+ options: flags("-a", "--append", "-i", "--ignore-interrupts", "-p"),
1233
+ },
1234
+ {
1235
+ name: "base64",
1236
+ readsContent: true,
1237
+ operands: [path],
1238
+ options: {
1239
+ ...flags("-d", "-D", "--decode", "-i", "--ignore-garbage"),
1240
+ "-w": arg(data),
1241
+ "-b": arg(data),
1242
+ "--wrap": arg(data),
1243
+ "--input": arg("content"),
1244
+ "-o": arg("write"),
1245
+ "--output": arg("write"),
1246
+ },
1247
+ },
1248
+ {
1249
+ name: "diff",
1250
+ readsContent: true,
1251
+ operands: [path],
1252
+ options: {
1253
+ ...flags("-u", "-c", "-r", "-q", "-s", "-w", "-b", "-B", "-i", "-N", "--no-index"),
1254
+ "-U": arg(data),
1255
+ "-C": arg(data),
1256
+ "-I": arg(data),
1257
+ "--exclude": arg(data),
1258
+ },
1259
+ },
1260
+ {
1261
+ name: "cmp",
1262
+ readsContent: true,
1263
+ operands: [path],
1264
+ options: { ...flags("-s", "-l"), "-n": arg(data), "-i": arg(data) },
1265
+ },
1266
+ { name: "dd", operands: [data] },
1267
+ {
1268
+ name: "gzip",
1269
+ aliases: ["gunzip", "zcat"],
1270
+ readsContent: true,
1271
+ operands: [path],
1272
+ options: flags(
1273
+ "-c",
1274
+ "--stdout",
1275
+ "-d",
1276
+ "--decompress",
1277
+ "-f",
1278
+ "-k",
1279
+ "-n",
1280
+ "-q",
1281
+ "-r",
1282
+ "-t",
1283
+ "-v",
1284
+ "-1",
1285
+ "-9",
1286
+ ),
1287
+ },
1288
+ {
1289
+ name: "openssl",
1290
+ operands: [data],
1291
+ options: {
1292
+ "-in": arg("content"),
1293
+ "-out": arg("write"),
1294
+ ...flags("-a", "-A", "-d", "-e", "-base64"),
1295
+ },
1296
+ },
1297
+ {
1298
+ name: "iconv",
1299
+ readsContent: true,
1300
+ operands: [path],
1301
+ options: {
1302
+ "-f": arg(data),
1303
+ "-t": arg(data),
1304
+ "--from-code": arg(data),
1305
+ "--to-code": arg(data),
1306
+ "-o": arg("write"),
1307
+ ...flags("-c", "-s", "-l"),
1308
+ },
1309
+ },
1310
+ {
1311
+ name: "expand",
1312
+ readsContent: true,
1313
+ operands: [path],
1314
+ options: { "-t": arg(data), "--tabs": arg(data), ...flags("-i", "--initial") },
1315
+ },
1316
+ {
1317
+ name: "pr",
1318
+ readsContent: true,
1319
+ operands: [path],
1320
+ options: {
1321
+ "-h": arg(data),
1322
+ "-l": arg(data),
1323
+ "-w": arg(data),
1324
+ "-o": arg(data),
1325
+ "-s": arg(data),
1326
+ ...flags("-t", "-T", "-d", "-m", "-n"),
1327
+ },
1328
+ },
1329
+ {
1330
+ name: "fmt",
1331
+ readsContent: true,
1332
+ operands: [path],
1333
+ options: { "-w": arg(data), "--width": arg(data), ...flags("-s", "-u") },
1334
+ },
1335
+ {
1336
+ name: "column",
1337
+ readsContent: true,
1338
+ operands: [path],
1339
+ options: {
1340
+ "-s": arg(data),
1341
+ "-o": arg(data),
1342
+ "-c": arg(data),
1343
+ ...flags("-t", "-x", "-e", "-n"),
1344
+ },
1345
+ },
1346
+ { name: "uuencode", readsContent: true, operands: [path, data], options: flags("-m") },
1347
+ {
1348
+ name: "yq",
1349
+ readsContent: true,
1350
+ operands: [code("jq"), path],
1351
+ options: {
1352
+ ...flags("-r", "-c", "-y", "-Y", "-n", "-e"),
1353
+ "-f": { roles: [path], issue: unknown("yq program file") },
1354
+ },
1355
+ },
1356
+ {
1357
+ name: "plutil",
1358
+ readsContent: true,
1359
+ operands: [path],
1360
+ options: { ...flags("-p", "-lint", "-s"), "-convert": arg(data), "-o": arg("write") },
1361
+ },
1362
+ {
1363
+ name: "textutil",
1364
+ readsContent: true,
1365
+ operands: [path],
1366
+ options: {
1367
+ "-cat": arg(data),
1368
+ "-convert": arg(data),
1369
+ "-output": arg("write"),
1370
+ ...flags("-stdout", "-info"),
1371
+ },
1372
+ },
1373
+ {
1374
+ name: "install",
1375
+ readsContent: true,
1376
+ operands: [path],
1377
+ options: {
1378
+ "-m": arg(data),
1379
+ "-o": arg(data),
1380
+ "-g": arg(data),
1381
+ ...flags("-d", "-v", "-C", "-p", "-s"),
1382
+ },
1383
+ },
1384
+ {
1385
+ name: "sed",
1386
+ readsContent: true,
1387
+ aliases: ["gsed"],
1388
+ operands: [code("sed"), path],
1389
+ semantics: "sed",
1390
+ options: {
1391
+ "-e": arg(code("sed")),
1392
+ "--expression": arg(code("sed")),
1393
+ "-f": { roles: [path], issue: unknown("sed program file") },
1394
+ "--file": { roles: [path], issue: unknown("sed program file") },
1395
+ ...flags("-n", "-E", "-r"),
1396
+ ...flags("-i", "--in-place"),
1397
+ },
1398
+ },
1399
+ {
1400
+ name: "awk",
1401
+ readsContent: true,
1402
+ aliases: ["gawk", "mawk"],
1403
+ operands: [code("awk"), path],
1404
+ semantics: "awk",
1405
+ startup: ["AWKPATH", "AWKLIBPATH"],
1406
+ options: {
1407
+ "-F": arg(data),
1408
+ "-v": arg(data),
1409
+ "-f": { roles: [path], issue: unknown("awk program file") },
1410
+ "--file": { roles: [path], issue: unknown("awk program file") },
1411
+ "-e": { roles: [code("awk")], issue: unknown("additional awk program") },
1412
+ "--source": { roles: [code("awk")], issue: unknown("additional awk program") },
1413
+ "-l": { roles: ["script"], issue: unknown("awk extension") },
1414
+ "--load": { roles: ["script"], issue: unknown("awk extension") },
1415
+ },
1416
+ },
1417
+ {
1418
+ name: "jq",
1419
+ readsContent: true,
1420
+ startupFiles: [/(?:^|\/)\.jq$/],
1421
+ operands: [code("jq"), path],
1422
+ semantics: "jq",
1423
+ startup: ["JQ_LIBRARY_PATH"],
1424
+ options: {
1425
+ ...flags(
1426
+ "-n",
1427
+ "-r",
1428
+ "-c",
1429
+ "-s",
1430
+ "-e",
1431
+ "-R",
1432
+ "-S",
1433
+ "-M",
1434
+ "-C",
1435
+ "--null-input",
1436
+ "--raw-output",
1437
+ "--compact-output",
1438
+ "--slurp",
1439
+ "--exit-status",
1440
+ ),
1441
+ "--arg": arg(data, data),
1442
+ "--argjson": arg(data, data),
1443
+ "--rawfile": arg(data, path),
1444
+ "--slurpfile": arg(data, path),
1445
+ "-f": { roles: [path], issue: unknown("jq program file") },
1446
+ "--from-file": { roles: [path], issue: unknown("jq program file") },
1447
+ "-L": { roles: [path], issue: unknown("jq library override") },
1448
+ },
1449
+ },
1450
+ {
1451
+ name: "cut",
1452
+ readsContent: true,
1453
+ operands: [path],
1454
+ stdout: "opaque",
1455
+ options: { "-d": arg(data), "-f": arg(data), "-c": arg(data), "-b": arg(data), ...flags("-s") },
1456
+ },
1457
+ {
1458
+ name: "less",
1459
+ aliases: ["more", "most"],
1460
+ readsContent: true,
1461
+ operands: [path],
1462
+ options: flags("-R", "-S", "-F", "-X", "-N", "-n", "-r", "-s", "-f"),
1463
+ },
1464
+ {
1465
+ name: "bat",
1466
+ readsContent: true,
1467
+ operands: [path],
1468
+ startup: ["BAT_PAGER"],
1469
+ options: {
1470
+ ...flags("-n", "-p", "--plain", "--number"),
1471
+ "--paging": arg(data),
1472
+ "--style": arg(data),
1473
+ "--language": arg(data),
1474
+ "--pager": arg(code("runtime")),
1475
+ },
1476
+ },
1477
+ {
1478
+ name: "view",
1479
+ aliases: ["vim", "vi"],
1480
+ readsContent: true,
1481
+ operands: [path],
1482
+ options: {
1483
+ "-c": arg(code("runtime")),
1484
+ "--cmd": arg(code("runtime")),
1485
+ "-S": arg(code("runtime")),
1486
+ "-u": arg("script"),
1487
+ "-U": arg("script"),
1488
+ ...flags("-e", "-s", "-R", "-n", "-N", "-M", "-m", "-b", "-f", "-d"),
1489
+ },
1490
+ },
1491
+ {
1492
+ name: "nl",
1493
+ readsContent: true,
1494
+ operands: [path],
1495
+ options: {
1496
+ "-b": arg(data),
1497
+ "-n": arg(data),
1498
+ "-w": arg(data),
1499
+ "-s": arg(data),
1500
+ "-v": arg(data),
1501
+ "-i": arg(data),
1502
+ },
1503
+ },
1504
+ {
1505
+ name: "tac",
1506
+ readsContent: true,
1507
+ operands: [path],
1508
+ options: { ...flags("-b", "-r"), "-s": arg(data) },
1509
+ },
1510
+ { name: "rev", readsContent: true, operands: [path] },
1511
+ {
1512
+ name: "fold",
1513
+ readsContent: true,
1514
+ operands: [path],
1515
+ options: { ...flags("-b", "-s"), "-w": arg(data) },
1516
+ },
1517
+ {
1518
+ name: "paste",
1519
+ readsContent: true,
1520
+ operands: [path],
1521
+ options: { ...flags("-s"), "-d": arg(data) },
1522
+ },
1523
+ {
1524
+ name: "md5",
1525
+ readsContent: true,
1526
+ operands: [path],
1527
+ options: { ...flags("-q", "-r"), "-s": arg(data) },
1528
+ },
1529
+ {
1530
+ name: "xxd",
1531
+ readsContent: true,
1532
+ operands: [path],
1533
+ options: {
1534
+ ...flags("-p", "-r", "-i", "-b", "-a", "-u"),
1535
+ "-l": arg(data),
1536
+ "-s": arg(data),
1537
+ "-c": arg(data),
1538
+ "-g": arg(data),
1539
+ },
1540
+ },
1541
+ {
1542
+ name: "hexdump",
1543
+ readsContent: true,
1544
+ operands: [path],
1545
+ options: {
1546
+ ...flags("-C", "-b", "-c", "-d", "-o", "-x", "-v"),
1547
+ "-n": arg(data),
1548
+ "-s": arg(data),
1549
+ "-e": arg(data),
1550
+ "-f": arg("content"),
1551
+ },
1552
+ },
1553
+ {
1554
+ name: "od",
1555
+ readsContent: true,
1556
+ operands: [path],
1557
+ options: {
1558
+ ...flags("-c", "-x", "-b", "-v"),
1559
+ "-A": arg(data),
1560
+ "-t": arg(data),
1561
+ "-N": arg(data),
1562
+ "-j": arg(data),
1563
+ },
1564
+ },
1565
+ {
1566
+ name: "strings",
1567
+ readsContent: true,
1568
+ operands: [path],
1569
+ options: { ...flags("-a", "-f", "-o"), "-n": arg(data), "-t": arg(data), "-e": arg(data) },
1570
+ },
1571
+ {
1572
+ name: "nano",
1573
+ readsContent: true,
1574
+ operands: [path],
1575
+ options: flags("-v", "-w", "-l", "-m", "-i", "-B"),
1576
+ },
1577
+ {
1578
+ name: "emacs",
1579
+ readsContent: true,
1580
+ operands: [path],
1581
+ options: {
1582
+ "--eval": arg(code("runtime")),
1583
+ "-l": arg(code("runtime")),
1584
+ "--load": arg(code("runtime")),
1585
+ "--batch": { roles: [], issue: unknown("editor batch code") },
1586
+ ...flags("-nw", "-Q", "-q"),
1587
+ },
1588
+ },
1589
+ {
1590
+ name: "man",
1591
+ operands: [data],
1592
+ startup: ["MANPAGER"],
1593
+ options: {
1594
+ "-P": arg(code("runtime")),
1595
+ "--pager": arg(code("runtime")),
1596
+ "-H": arg(code("runtime")),
1597
+ "-l": arg("content"),
1598
+ "--local-file": arg("content"),
1599
+ "-M": arg("names"),
1600
+ "-S": arg(data),
1601
+ ...flags("-a", "-k", "-f", "-w", "--help", "--version"),
1602
+ },
1603
+ },
1604
+ {
1605
+ name: "info",
1606
+ operands: [data],
1607
+ options: {
1608
+ "-f": arg("content"),
1609
+ "--file": arg("content"),
1610
+ "-n": arg(data),
1611
+ "--node": arg(data),
1612
+ "-d": arg("names"),
1613
+ ...flags("--help", "--version", "-w"),
1614
+ },
1615
+ },
1616
+ {
1617
+ name: "tldr",
1618
+ operands: [data],
1619
+ options: {
1620
+ "--render": arg("content"),
1621
+ "-p": arg(data),
1622
+ "--platform": arg(data),
1623
+ ...flags("--help", "--version", "-l", "--list", "-u", "--update"),
1624
+ },
1625
+ },
1626
+ {
1627
+ name: "whatis",
1628
+ aliases: ["apropos"],
1629
+ operands: [data],
1630
+ options: {
1631
+ "-P": arg(code("runtime")),
1632
+ "--pager": arg(code("runtime")),
1633
+ ...flags("-a", "-e", "-r", "-w", "--help", "--version"),
1634
+ },
1635
+ },
1636
+ {
1637
+ name: "which",
1638
+ aliases: ["whereis"],
1639
+ operands: [data],
1640
+ options: flags("-a", "-s", "-b", "-m", "-u"),
1641
+ },
1642
+ {
1643
+ name: "type",
1644
+ builtinOnly: true,
1645
+ operands: [data],
1646
+ options: flags("-a", "-t", "-p", "-P", "-f"),
1647
+ },
1648
+ {
1649
+ name: "brew",
1650
+ operands: [data],
1651
+ options: {
1652
+ ...flags(
1653
+ "--formula",
1654
+ "--cask",
1655
+ "--installed",
1656
+ "--analytics",
1657
+ "--desc",
1658
+ "--versions",
1659
+ "-1",
1660
+ "-l",
1661
+ "--version",
1662
+ "--help",
1663
+ ),
1664
+ "--json": arg(data),
1665
+ "-e": arg(code("runtime")),
1666
+ "--eval": arg(code("runtime")),
1667
+ "-c": arg(code("runtime")),
1668
+ "--command": arg(code("runtime")),
1669
+ },
1670
+ subcommands: {
1671
+ ...Object.fromEntries(
1672
+ [
1673
+ "sh",
1674
+ "ruby",
1675
+ "irb",
1676
+ "exec",
1677
+ "command",
1678
+ "test",
1679
+ "livecheck",
1680
+ "style",
1681
+ "typecheck",
1682
+ "tests",
1683
+ "debugger",
1684
+ ].map((name) => [name, { operands: [data], effect: unknown("brew code execution") }]),
1685
+ ),
1686
+ bundle: {
1687
+ operands: [data],
1688
+ subcommands: { exec: { operands: [data], effect: unknown("brew bundle execution") } },
1689
+ },
1690
+ },
1691
+ },
1692
+ {
1693
+ name: "pip",
1694
+ options: flags("--version", "--help", "-q", "-v"),
1695
+ operands: [path],
1696
+ subcommands: { show: { operands: [data] } },
1697
+ },
1698
+ {
1699
+ name: "cargo",
1700
+ options: {
1701
+ "--bin": arg(data),
1702
+ "--example": arg(data),
1703
+ "--limit": arg(data),
1704
+ ...flags("--bins"),
1705
+ ...flags(
1706
+ "--version",
1707
+ "--help",
1708
+ "-q",
1709
+ "--quiet",
1710
+ "--release",
1711
+ "--all-targets",
1712
+ "--workspace",
1713
+ "--locked",
1714
+ "--offline",
1715
+ "--all-features",
1716
+ "--lib",
1717
+ "--all",
1718
+ "--no-default-features",
1719
+ "--no-deps",
1720
+ ),
1721
+ "-p": arg(data),
1722
+ "--package": arg(data),
1723
+ "--target": arg(data),
1724
+ "--features": arg(data),
1725
+ "--test": arg(data),
1726
+ "--manifest-path": arg("content"),
1727
+ },
1728
+ operands: [path],
1729
+ subcommands: { search: { operands: [data] }, test: { operands: [data] } },
1730
+ },
1731
+ {
1732
+ name: "make",
1733
+ startupFiles: [/(?:^|\/)(?:GNUmakefile|makefile|Makefile)$/i],
1734
+ operands: [data],
1735
+ options: {
1736
+ "-f": arg("script"),
1737
+ "--file": arg("script"),
1738
+ "--makefile": arg("script"),
1739
+ "-C": arg("names"),
1740
+ "--directory": arg("names"),
1741
+ "--eval": arg(code("runtime")),
1742
+ "-p": { roles: [], issue: source("make variable listing") },
1743
+ "--print-data-base": { roles: [], issue: source("make variable listing") },
1744
+ ...flags("-n", "--dry-run", "-s", "--silent", "-k", "-B", "-r", "-R", "--help", "--version"),
1745
+ "-j": arg(data),
1746
+ "--jobs": arg(data),
1747
+ },
1748
+ },
1749
+ {
1750
+ name: "just",
1751
+ startupFiles: [/(?:^|\/)(?:\.?justfile)$/i],
1752
+ operands: [data],
1753
+ options: {
1754
+ ...flags("--verbose", "-v", "--quiet", "-q"),
1755
+ "-f": arg("script"),
1756
+ "--justfile": arg("script"),
1757
+ "--command": arg(code("runtime")),
1758
+ "-c": arg(code("runtime")),
1759
+ "-d": arg("names"),
1760
+ "--working-directory": arg("names"),
1761
+ ...flags("--list", "-l", "--summary", "--dry-run", "-n", "--help", "--version"),
1762
+ },
1763
+ },
1764
+ {
1765
+ name: "task",
1766
+ startupFiles: [/(?:^|\/)Taskfile(?:\.dist)?\.ya?ml$/i],
1767
+ operands: [data],
1768
+ options: {
1769
+ "-t": arg("script"),
1770
+ "--taskfile": arg("script"),
1771
+ "-d": arg("names"),
1772
+ "--dir": arg("names"),
1773
+ ...flags("--list", "-l", "--list-all", "--dry", "-n", "--help", "--version"),
1774
+ },
1775
+ },
1776
+ {
1777
+ name: "ConvertFrom-Json",
1778
+ builtinOnly: true,
1779
+ aliases: [
1780
+ "ConvertTo-Json",
1781
+ "Select-Object",
1782
+ "Where-Object",
1783
+ "Sort-Object",
1784
+ "Measure-Object",
1785
+ "Group-Object",
1786
+ "Format-Table",
1787
+ "Format-List",
1788
+ "Out-String",
1789
+ "Out-Null",
1790
+ ],
1791
+ operands: [data],
1792
+ pipelineData: true,
1793
+ options: {
1794
+ ...flags(
1795
+ "-Line",
1796
+ "-Word",
1797
+ "-Character",
1798
+ "-Unique",
1799
+ "-Descending",
1800
+ "-Compress",
1801
+ "-AsHashtable",
1802
+ ),
1803
+ "-First": arg(data),
1804
+ "-Last": arg(data),
1805
+ "-Skip": arg(data),
1806
+ "-Property": arg(data),
1807
+ "-ExpandProperty": arg(data),
1808
+ "-Like": arg(data),
1809
+ "-NotLike": arg(data),
1810
+ "-Match": arg(data),
1811
+ "-EQ": arg(data),
1812
+ "-Depth": arg(data),
1813
+ "-Width": arg(data),
1814
+ },
1815
+ },
1816
+ {
1817
+ name: "ForEach-Object",
1818
+ builtinOnly: true,
1819
+ aliases: ["%", "foreach"],
1820
+ pipelineData: true,
1821
+ operands: ["member"],
1822
+ options: {
1823
+ "-MemberName": arg("member"),
1824
+ "-ArgumentList": { roles: [data], issue: unknown("PowerShell method arguments") },
1825
+ },
1826
+ },
1827
+ ...[
1828
+ "irb",
1829
+ "pry",
1830
+ "jshell",
1831
+ "julia",
1832
+ "ghci",
1833
+ "racket",
1834
+ "guile",
1835
+ "scheme",
1836
+ "clojure",
1837
+ "scala",
1838
+ "R",
1839
+ "Rscript",
1840
+ "ipython",
1841
+ "ipython3",
1842
+ "bpython",
1843
+ ].map((name): ExecutableEntry => ({
1844
+ name,
1845
+ operands: ["script", path],
1846
+ semantics: "runtime",
1847
+ options: {
1848
+ ...flags("--help", "--version"),
1849
+ "-e": arg(code("runtime")),
1850
+ "--eval": arg(code("runtime")),
1851
+ },
1852
+ })),
1853
+ {
1854
+ name: "pkill",
1855
+ aliases: ["pgrep"],
1856
+ operands: [data],
1857
+ options: {
1858
+ ...flags("-f", "-l", "-a", "-x", "-n", "-o", "-v", "-i", "-9", "-TERM", "-KILL"),
1859
+ "-u": arg(data),
1860
+ "-U": arg(data),
1861
+ "-P": arg(data),
1862
+ "-g": arg(data),
1863
+ "-s": arg(data),
1864
+ },
1865
+ },
1866
+ {
1867
+ name: "killall",
1868
+ aliases: ["kill"],
1869
+ operands: [data],
1870
+ options: {
1871
+ ...flags("-9", "-15", "-TERM", "-KILL", "-HUP", "-INT", "-0", "-q", "-v", "-l", "-w"),
1872
+ "-s": arg(data),
1873
+ "-u": arg(data),
1874
+ },
1875
+ },
1876
+ {
1877
+ name: "lsof",
1878
+ operands: [data],
1879
+ options: {
1880
+ "-F": arg(data),
1881
+ "-s": arg(data),
1882
+ ...flags("-n", "-P", "-t", "-a", "-l"),
1883
+ "-i": { roles: [], attachedValue: /^[A-Za-z0-9:]+$/ },
1884
+ "-c": arg(data),
1885
+ "-p": arg(data),
1886
+ "-u": arg(data),
1887
+ "-d": arg(data),
1888
+ },
1889
+ },
1890
+ ...["nvm", "fnm", "volta", "asdf", "pyenv"].map((name): ExecutableEntry => ({
1891
+ name,
1892
+ operands: [data],
1893
+ effect: unknown("unsupported version manager command"),
1894
+ options: flags("--help", "--version"),
1895
+ subcommands: Object.fromEntries(
1896
+ ["list", "ls", "current", "alias", "use", "versions", "global", "local"].map((command) => [
1897
+ command,
1898
+ { operands: [data], effect: "none" },
1899
+ ]),
1900
+ ),
1901
+ })),
1902
+ {
1903
+ name: "tree",
1904
+ options: { ...flags("-a", "-d", "-f", "-i", "-I", "--dirsfirst"), "-L": arg(data) },
1905
+ operands: ["names"],
1906
+ semantics: "listing",
1907
+ },
1908
+ { name: "basename", aliases: ["dirname"], operands: [data] },
1909
+ {
1910
+ name: "ls",
1911
+ options: {
1912
+ "--time-style": arg(data),
1913
+ ...flags(
1914
+ "-l",
1915
+ "-O",
1916
+ "-T",
1917
+ "-a",
1918
+ "-A",
1919
+ "-h",
1920
+ "-t",
1921
+ "-r",
1922
+ "-S",
1923
+ "-d",
1924
+ "-R",
1925
+ "-F",
1926
+ "-1",
1927
+ "-n",
1928
+ "-i",
1929
+ "-s",
1930
+ "--color",
1931
+ "--all",
1932
+ "--directory",
1933
+ "--recursive",
1934
+ ),
1935
+ },
1936
+ operands: ["names"],
1937
+ semantics: "listing",
1938
+ },
1939
+ {
1940
+ name: "fd",
1941
+ operands: ["names"],
1942
+ semantics: "listing",
1943
+ options: {
1944
+ ...flags(
1945
+ "-H",
1946
+ "--hidden",
1947
+ "-I",
1948
+ "--no-ignore",
1949
+ "-u",
1950
+ "--unrestricted",
1951
+ "-s",
1952
+ "--case-sensitive",
1953
+ "-i",
1954
+ "--ignore-case",
1955
+ "-g",
1956
+ "--glob",
1957
+ "-F",
1958
+ "--fixed-strings",
1959
+ "-a",
1960
+ "--absolute-path",
1961
+ "-L",
1962
+ "--follow",
1963
+ "-l",
1964
+ "--list-details",
1965
+ "-0",
1966
+ "--print0",
1967
+ "-q",
1968
+ "--quiet",
1969
+ "--show-errors",
1970
+ "--one-file-system",
1971
+ "--no-ignore-vcs",
1972
+ "--no-ignore-parent",
1973
+ "--help",
1974
+ "--version",
1975
+ ),
1976
+ "-t": arg(data),
1977
+ "--type": arg(data),
1978
+ "-e": arg(data),
1979
+ "--extension": arg(data),
1980
+ "-E": arg(data),
1981
+ "--exclude": arg(data),
1982
+ "-d": arg(data),
1983
+ "--max-depth": arg(data),
1984
+ "--min-depth": arg(data),
1985
+ "--exact-depth": arg(data),
1986
+ "-j": arg(data),
1987
+ "--threads": arg(data),
1988
+ "--base-directory": arg("names"),
1989
+ "--search-path": arg("names"),
1990
+ "-p": flag,
1991
+ "--full-path": flag,
1992
+ "-S": arg(data),
1993
+ "--size": arg(data),
1994
+ "--changed-within": arg(data),
1995
+ "--changed-before": arg(data),
1996
+ "-c": arg(data),
1997
+ "--color": arg(data),
1998
+ "--path-separator": arg(data),
1999
+ },
2000
+ },
2001
+ {
2002
+ name: "wc",
2003
+ optionsBeforeOperands: false,
2004
+ readsContent: true,
2005
+ operands: [path],
2006
+ options: {
2007
+ ...flags("-l", "-w", "-c", "-m", "-L"),
2008
+ "--files0-from": { roles: [path], issue: unknown("indirect file list") },
2009
+ },
2010
+ },
2011
+ {
2012
+ name: "sort",
2013
+ readsContent: true,
2014
+ operands: [path],
2015
+ stdout: "opaque",
2016
+ options: {
2017
+ "-T": arg("names"),
2018
+ "--temporary-directory": arg("names"),
2019
+ "-o": arg("write"),
2020
+ "--output": arg("write"),
2021
+ "-k": arg(data),
2022
+ "-t": arg(data),
2023
+ ...flags("-n", "-r", "-u", "-f", "-h", "-V", "-b", "-s", "-g", "-M", "--unique"),
2024
+ "--compress-program": { roles: [data], issue: unknown("sort compression program") },
2025
+ "--files0-from": { roles: [path], issue: unknown("indirect file list") },
2026
+ "--random-source": arg(path),
2027
+ },
2028
+ },
2029
+ {
2030
+ name: "sh",
2031
+ namePattern: /^bash[0-9]+(?:\.[0-9]+)*$/,
2032
+ startupFiles: [/(?:^|\/)\.(?:bashrc|bash_profile|profile|zshrc)$/],
2033
+ aliases: ["bash", "dash"],
2034
+ operands: ["script", path],
2035
+ options: shellOptions,
2036
+ semantics: "shell",
2037
+ startup: ["BASH_ENV", "ENV", "SHELLOPTS", "BASHOPTS", "PROMPT_COMMAND", "IFS", "BASH_XTRACEFD"],
2038
+ },
2039
+ {
2040
+ name: "zsh",
2041
+ aliases: ["rbash", "rksh", "ksh", "ksh93", "zsh5"],
2042
+ operands: [data],
2043
+ effect: unknown("unsupported shell dialect"),
2044
+ },
2045
+ {
2046
+ name: "csh",
2047
+ aliases: [
2048
+ "tcsh",
2049
+ "fish",
2050
+ "tclsh",
2051
+ "ksh88",
2052
+ "mksh",
2053
+ "pdksh",
2054
+ "oksh",
2055
+ "elvish",
2056
+ "nu",
2057
+ "nushell",
2058
+ "xonsh",
2059
+ "ash",
2060
+ "yash",
2061
+ "oil",
2062
+ "osh",
2063
+ ],
2064
+ informationFlags: ["--help", "--version"],
2065
+ operands: ["script", path],
2066
+ options: { "-c": arg(code("runtime")), ...flags("--help", "--version") },
2067
+ semantics: "runtime",
2068
+ },
2069
+ {
2070
+ name: "turbo",
2071
+ operands: [data],
2072
+ options: {
2073
+ "--filter": arg(data),
2074
+ "--cwd": arg("names"),
2075
+ ...flags("--help", "--version", "--dry-run", "--no-cache", "--force"),
2076
+ },
2077
+ },
2078
+ {
2079
+ name: "direnv",
2080
+ operands: [data],
2081
+ effect: unknown("direnv state change"),
2082
+ subcommands: { exec: { operands: ["names", data] } },
2083
+ },
2084
+ { name: "parallel", operands: [data], effect: unknown("parallel executes operands and stdin") },
2085
+ {
2086
+ name: "New-PSDrive",
2087
+ builtinOnly: true,
2088
+ aliases: ["ndr"],
2089
+ operands: [data],
2090
+ effect: unknown("PowerShell drive aliases"),
2091
+ },
2092
+ {
2093
+ name: "dotnet",
2094
+ options: {
2095
+ "--report": arg(data),
2096
+ "--output-dir": arg("write"),
2097
+ ...flags("--locked-mode", "--force", "-nologo"),
2098
+ "-v": arg(data),
2099
+ "-f": arg(data),
2100
+ "--context": arg(data),
2101
+ "--startup-project": arg(path),
2102
+ "--file": arg("content"),
2103
+ ...flags("--nologo", "--no-launch-profile"),
2104
+ ...flags(
2105
+ "--no-build",
2106
+ "--no-restore",
2107
+ "--help",
2108
+ "--version",
2109
+ "--list-sdks",
2110
+ "--list-runtimes",
2111
+ ),
2112
+ "--project": arg(path),
2113
+ "--configuration": arg(data),
2114
+ "-c": arg(data),
2115
+ "--filter": arg(data),
2116
+ "--framework": arg(data),
2117
+ "--verbosity": arg(data),
2118
+ "--include": arg(path),
2119
+ "--severity": arg(data),
2120
+ },
2121
+ informationFlags: ["--help", "--version"],
2122
+ operands: [path],
2123
+ subcommands: {
2124
+ fsi: { operands: [data], effect: unknown("FSharp code has no analyzer") },
2125
+ restore: { operands: [path], options: flags("--force-evaluate") },
2126
+ csharpier: {
2127
+ operands: ["content"],
2128
+ options: flags("--write-stdout"),
2129
+ subcommands: { format: { operands: ["content"], options: flags("--write-stdout") } },
2130
+ },
2131
+ },
2132
+ },
2133
+ { name: "busybox", operands: [data], effect: unknown("applet execution has no analyzer") },
2134
+ { name: "eval", builtinOnly: true, operands: [code("bash")], semantics: "eval" },
2135
+ {
2136
+ name: "alias",
2137
+ builtinOnly: true,
2138
+ aliases: [
2139
+ "unalias",
2140
+ "unset",
2141
+ "pushd",
2142
+ "popd",
2143
+ "shopt",
2144
+ "enable",
2145
+ "trap",
2146
+ "read",
2147
+ "mapfile",
2148
+ "readarray",
2149
+ ],
2150
+ operands: [data],
2151
+ effect: unknown("shell state mutation has no analyzer"),
2152
+ },
2153
+ {
2154
+ name: "source",
2155
+ builtinOnly: true,
2156
+ aliases: ["."],
2157
+ operands: [path],
2158
+ effect: unknown("source file contents are not visible"),
2159
+ },
2160
+ {
2161
+ name: "env",
2162
+ operands: [data],
2163
+ semantics: "env",
2164
+ bare: source("environment listing"),
2165
+ options: {
2166
+ ...flags("-i", "--ignore-environment", "-0"),
2167
+ "-u": arg(data),
2168
+ "--unset": arg(data),
2169
+ "-C": arg("names"),
2170
+ "--chdir": arg("names"),
2171
+ "-S": arg(code("bash")),
2172
+ "--split-string": arg(code("bash")),
2173
+ },
2174
+ },
2175
+ {
2176
+ name: "rtk",
2177
+ operands: [data],
2178
+ semantics: "ordinary",
2179
+ },
2180
+ {
2181
+ name: "command",
2182
+ builtinOnly: true,
2183
+ aliases: ["builtin", "exec"],
2184
+ operands: [data],
2185
+ semantics: "forward",
2186
+ options: {
2187
+ ...flags("-p", "-c", "-l"),
2188
+ "-a": arg(data),
2189
+ "-n": arg(data),
2190
+ "-v": flag,
2191
+ "-V": flag,
2192
+ },
2193
+ },
2194
+ {
2195
+ name: "nohup",
2196
+ aliases: ["time", "nice"],
2197
+ operands: [data],
2198
+ semantics: "forward",
2199
+ options: {
2200
+ ...flags("-p", "-c", "-l"),
2201
+ "-a": arg(data),
2202
+ "-n": arg(data),
2203
+ "-v": flag,
2204
+ "-V": flag,
2205
+ },
2206
+ },
2207
+ {
2208
+ name: "sudo",
2209
+ aliases: ["doas"],
2210
+ operands: [data],
2211
+ semantics: "forward",
2212
+ options: {
2213
+ ...flags("-n", "-E", "-H", "-S"),
2214
+ "-u": arg(data),
2215
+ "-g": arg(data),
2216
+ "-h": arg(data),
2217
+ },
2218
+ },
2219
+ {
2220
+ name: "watch",
2221
+ operands: [data],
2222
+ semantics: "joined-shell",
2223
+ options: { "-n": arg(data), ...flags("-d", "-t") },
2224
+ },
2225
+ {
2226
+ name: "ssh",
2227
+ operands: [data],
2228
+ semantics: "remote-shell",
2229
+ options: {
2230
+ ...flags("-T"),
2231
+ "-i": arg(path),
2232
+ "-F": arg(path),
2233
+ "-p": arg(data),
2234
+ "-l": arg(data),
2235
+ "-o": { roles: [data], issue: unknown("SSH execution configuration") },
2236
+ },
2237
+ },
2238
+ {
2239
+ name: "timeout",
2240
+ operands: [data],
2241
+ semantics: "timeout",
2242
+ options: {
2243
+ "-s": arg(data),
2244
+ "-k": arg(data),
2245
+ "--signal": arg(data),
2246
+ "--kill-after": arg(data),
2247
+ ...flags("--foreground", "--preserve-status", "-v"),
2248
+ },
2249
+ },
2250
+ {
2251
+ name: "xargs",
2252
+ operands: [data],
2253
+ semantics: "xargs",
2254
+ options: {
2255
+ "-I": arg(data),
2256
+ "-n": arg(data),
2257
+ "-P": arg(data),
2258
+ "-L": arg(data),
2259
+ ...flags("-r", "-t", "-o", "--open-tty", "--no-run-if-empty"),
2260
+ },
2261
+ },
2262
+ {
2263
+ name: "stdbuf",
2264
+ operands: [data],
2265
+ semantics: "forward",
2266
+ options: {
2267
+ "-i": arg(data),
2268
+ "-o": arg(data),
2269
+ "-e": arg(data),
2270
+ "--input": arg(data),
2271
+ "--output": arg(data),
2272
+ "--error": arg(data),
2273
+ },
2274
+ },
2275
+ {
2276
+ name: "setsid",
2277
+ operands: [data],
2278
+ semantics: "forward",
2279
+ options: flags("-f", "-w", "-c", "--fork", "--wait", "--ctty"),
2280
+ },
2281
+ {
2282
+ name: "caffeinate",
2283
+ operands: [data],
2284
+ semantics: "forward",
2285
+ options: { ...flags("-d", "-i", "-m", "-s", "-u"), "-t": arg(data), "-w": arg(data) },
2286
+ },
2287
+ {
2288
+ name: "chronic",
2289
+ aliases: ["unbuffer"],
2290
+ operands: [data],
2291
+ semantics: "forward",
2292
+ options: flags("-v", "-e"),
2293
+ },
2294
+ {
2295
+ name: "ionice",
2296
+ operands: [data],
2297
+ semantics: "forward",
2298
+ options: { "-c": arg(data), "-n": arg(data), "-p": arg(data), ...flags("-t") },
2299
+ },
2300
+ {
2301
+ name: "flock",
2302
+ operands: ["names"],
2303
+ semantics: "timeout",
2304
+ options: {
2305
+ ...flags("-s", "-x", "-u", "-n", "-o", "-F"),
2306
+ "-w": arg(data),
2307
+ "-E": arg(data),
2308
+ "-c": arg(code("bash")),
2309
+ "--command": arg(code("bash")),
2310
+ },
2311
+ },
2312
+ {
2313
+ name: "script",
2314
+ operands: ["names"],
2315
+ semantics: "timeout",
2316
+ options: {
2317
+ ...flags("-q", "-a", "-e", "-f"),
2318
+ "-c": arg(code("bash")),
2319
+ "--command": arg(code("bash")),
2320
+ },
2321
+ },
2322
+ {
2323
+ name: "uv",
2324
+ aliases: ["poetry"],
2325
+ operands: [data],
2326
+ options: {
2327
+ "--directory": arg("names"),
2328
+ "--project": arg("names"),
2329
+ "--config-file": arg("script"),
2330
+ "--with": arg(data),
2331
+ "--with-editable": arg("script"),
2332
+ "--python": arg(data),
2333
+ ...flags("--no-project"),
2334
+ },
2335
+ subcommands: { run: { operands: [data] } },
2336
+ },
2337
+ {
2338
+ name: "conda",
2339
+ aliases: ["mamba", "micromamba"],
2340
+ operands: [data],
2341
+ subcommands: {
2342
+ run: {
2343
+ operands: [data],
2344
+ options: {
2345
+ "-n": arg(data),
2346
+ "--name": arg(data),
2347
+ "-p": arg("names"),
2348
+ "--prefix": arg("names"),
2349
+ ...flags("--no-capture-output", "--live-stream"),
2350
+ },
2351
+ },
2352
+ },
2353
+ },
2354
+ { name: "find", operands: ["names"], semantics: "find" },
2355
+ {
2356
+ name: "bunx",
2357
+ operands: [data],
2358
+ semantics: "forward",
2359
+ options: {
2360
+ ...flags("--bun", "--no-install", "--verbose", "--silent", "--help"),
2361
+ "-p": arg(data),
2362
+ "--package": arg(data),
2363
+ },
2364
+ },
2365
+ {
2366
+ name: "rustup",
2367
+ operands: [data],
2368
+ effect: unknown("unsupported toolchain command"),
2369
+ options: flags("--help", "--version", "-h", "-V"),
2370
+ subcommands: {
2371
+ run: { operands: [data] },
2372
+ show: { operands: [data], effect: "none" },
2373
+ which: { operands: [data], effect: "none", options: { "--toolchain": arg("names") } },
2374
+ toolchain: {
2375
+ operands: [data],
2376
+ subcommands: {
2377
+ list: {
2378
+ operands: [data],
2379
+ effect: "none",
2380
+ options: flags("-v", "--verbose", "-q", "--quiet"),
2381
+ },
2382
+ },
2383
+ },
2384
+ },
2385
+ },
2386
+ {
2387
+ name: "gh-tool",
2388
+ aliases: ["agent-tools-gh"],
2389
+ operands: [path],
2390
+ options: {
2391
+ "--pr": arg(data),
2392
+ "--prs": arg(data),
2393
+ "--repo": arg(data),
2394
+ "--format": arg(data),
2395
+ "--strategy": arg(data),
2396
+ "--match-head-commit": arg(data),
2397
+ "--author": arg(data),
2398
+ "--body-contains": arg(data),
2399
+ "--title": arg(data),
2400
+ "--body": arg(data),
2401
+ "--head": arg(data),
2402
+ "--base": arg(data),
2403
+ "--comment": arg(data),
2404
+ "--comment-id": arg(data),
2405
+ "--body-file": arg("content"),
2406
+ "--comment-file": arg("content"),
2407
+ "--notes-file": arg("content"),
2408
+ "-F": arg("content"),
2409
+ "--run": arg(data),
2410
+ "--job": arg(data),
2411
+ "--limit": arg(data),
2412
+ "--state": arg(data),
2413
+ "--timeout": arg(data),
2414
+ "--interval": arg(data),
2415
+ "--branch": arg(data),
2416
+ ...flags(
2417
+ "--help",
2418
+ "--version",
2419
+ "--confirm",
2420
+ "--delete-branch",
2421
+ "--watch",
2422
+ "--body-stdin",
2423
+ "--failed-steps-only",
2424
+ ),
2425
+ },
2426
+ },
2427
+ { name: "jest", operands: [path] },
2428
+ { name: "vitest", operands: [path] },
2429
+ { name: "pytest", operands: [path] },
2430
+ {
2431
+ name: "playwright",
2432
+ informationFlags: ["--help", "--version", "-h", "-V"],
2433
+ operands: [data],
2434
+ effect: unknown("unsupported test runner command"),
2435
+ options: flags("--help", "--version", "-h", "-V"),
2436
+ startupFiles: [/(?:^|\/)playwright\.config\.[cm]?[jt]s$/],
2437
+ subcommands: {
2438
+ install: { operands: [data], effect: "none", options: flags("--with-deps") },
2439
+ "show-report": { operands: [data], effect: "none" },
2440
+ "show-trace": { operands: [path], effect: "none" },
2441
+ "install-deps": { operands: [data], effect: "none" },
2442
+ codegen: { operands: [data], effect: "none" },
2443
+ test: {
2444
+ operands: ["script"],
2445
+ effect: "none",
2446
+ options: {
2447
+ "-c": arg("script"),
2448
+ "--config": arg("script"),
2449
+ "--reporter": arg(code("runtime")),
2450
+ "-j": arg(data),
2451
+ "--workers": arg(data),
2452
+ "--project": arg(data),
2453
+ "-g": arg(data),
2454
+ "--grep": arg(data),
2455
+ "--timeout": arg(data),
2456
+ "--output": arg("write"),
2457
+ ...flags(
2458
+ "--headed",
2459
+ "--list",
2460
+ "--quiet",
2461
+ "--forbid-only",
2462
+ "--fully-parallel",
2463
+ "--pass-with-no-tests",
2464
+ ),
2465
+ },
2466
+ },
2467
+ },
2468
+ },
2469
+ {
2470
+ name: "acli",
2471
+ operands: [data],
2472
+ effect: unknown("unsupported application CLI command"),
2473
+ options: flags("--help", "-h", "--version"),
2474
+ subcommands: {
2475
+ jira: {
2476
+ operands: [data],
2477
+ subcommands: {
2478
+ workitem: {
2479
+ operands: [data],
2480
+ subcommands: {
2481
+ view: {
2482
+ operands: [data],
2483
+ effect: "none",
2484
+ options: {
2485
+ "-f": arg(data),
2486
+ "--fields": arg(data),
2487
+ ...flags("--json", "--web", "-w"),
2488
+ },
2489
+ },
2490
+ edit: {
2491
+ operands: [data],
2492
+ effect: "none",
2493
+ options: {
2494
+ "-k": arg(data),
2495
+ "--key": arg(data),
2496
+ "-a": arg(data),
2497
+ "--assignee": arg(data),
2498
+ "--description": arg(data),
2499
+ "-d": arg(data),
2500
+ "--summary": arg(data),
2501
+ "-s": arg(data),
2502
+ "--description-file": arg("content"),
2503
+ "--from-json": arg("content"),
2504
+ "--jql": arg(data),
2505
+ "--filter": arg(data),
2506
+ "--labels": arg(data),
2507
+ "-l": arg(data),
2508
+ "--remove-labels": arg(data),
2509
+ "--type": arg(data),
2510
+ "-t": arg(data),
2511
+ ...flags(
2512
+ "--json",
2513
+ "--yes",
2514
+ "-y",
2515
+ "--generate-json",
2516
+ "--ignore-errors",
2517
+ "--remove-assignee",
2518
+ ),
2519
+ },
2520
+ },
2521
+ search: {
2522
+ operands: [data],
2523
+ effect: "none",
2524
+ options: {
2525
+ "--fields": arg(data),
2526
+ "-f": arg(data),
2527
+ "--filter": arg(data),
2528
+ "--jql": arg(data),
2529
+ "-j": arg(data),
2530
+ "--limit": arg(data),
2531
+ "-l": arg(data),
2532
+ ...flags("--json", "--csv", "--count", "--paginate", "--web", "-w"),
2533
+ },
2534
+ },
2535
+ comment: {
2536
+ operands: [data],
2537
+ subcommands: {
2538
+ list: {
2539
+ operands: [data],
2540
+ effect: "none",
2541
+ options: {
2542
+ "--key": arg(data),
2543
+ "--limit": arg(data),
2544
+ "--order": arg(data),
2545
+ ...flags("--json", "--paginate"),
2546
+ },
2547
+ },
2548
+ create: {
2549
+ operands: [data],
2550
+ effect: "none",
2551
+ options: {
2552
+ "--key": arg(data),
2553
+ "-k": arg(data),
2554
+ "--body": arg(data),
2555
+ "-b": arg(data),
2556
+ "--body-file": arg("content"),
2557
+ "-F": arg("content"),
2558
+ "--jql": arg(data),
2559
+ "--filter": arg(data),
2560
+ ...flags("--json", "--edit-last", "-e", "--ignore-errors"),
2561
+ },
2562
+ },
2563
+ },
2564
+ },
2565
+ },
2566
+ },
2567
+ },
2568
+ },
2569
+ confluence: {
2570
+ operands: [data],
2571
+ subcommands: {
2572
+ page: {
2573
+ operands: [data],
2574
+ subcommands: {
2575
+ view: {
2576
+ operands: [data],
2577
+ effect: "none",
2578
+ options: {
2579
+ "--id": arg(data),
2580
+ "--body-format": arg(data),
2581
+ "--status": arg(data),
2582
+ "--version": arg(data),
2583
+ ...flags(
2584
+ "--json",
2585
+ "--get-draft",
2586
+ "--include-collaborators",
2587
+ "--include-direct-children",
2588
+ "--include-favorited-by-current-user-status",
2589
+ "--include-labels",
2590
+ "--include-likes",
2591
+ "--include-operations",
2592
+ "--include-properties",
2593
+ "--include-version",
2594
+ "--include-versions",
2595
+ "--include-webresources",
2596
+ ),
2597
+ },
2598
+ },
2599
+ },
2600
+ },
2601
+ },
2602
+ },
2603
+ },
2604
+ },
2605
+ {
2606
+ name: "npx",
2607
+ operands: [data],
2608
+ semantics: "forward",
2609
+ options: {
2610
+ "-c": arg(code("bash")),
2611
+ "--call": arg(code("bash")),
2612
+ "-p": arg(data),
2613
+ "--package": arg(data),
2614
+ ...flags("-y", "--yes", "--no-install"),
2615
+ },
2616
+ },
2617
+ {
2618
+ name: "printenv",
2619
+ options: flags("-0", "--null"),
2620
+ operands: ["env-name"],
2621
+ bare: source("environment listing"),
2622
+ },
2623
+ {
2624
+ name: "set",
2625
+ builtinOnly: true,
2626
+ operands: [data],
2627
+ bare: source("shell variable listing"),
2628
+ effect: unknown("shell options or positional mutation"),
2629
+ },
2630
+ {
2631
+ name: "export",
2632
+ builtinOnly: true,
2633
+ aliases: ["declare", "typeset", "readonly", "local"],
2634
+ operands: [data],
2635
+ bare: source("shell variable listing"),
2636
+ options: {
2637
+ "-p": { roles: [], issue: source("variable listing") },
2638
+ "-r": { roles: [], issue: unknown("readonly binding state") },
2639
+ ...flags("-x"),
2640
+ },
2641
+ },
2642
+ {
2643
+ name: "security",
2644
+ operands: [data],
2645
+ subcommands: {
2646
+ "find-generic-password": {
2647
+ operands: [data],
2648
+ options: {
2649
+ "-w": { roles: [], issue: source("keychain password") },
2650
+ "-g": { roles: [], issue: source("keychain password") },
2651
+ "-s": arg(data),
2652
+ "-a": arg(data),
2653
+ "-l": arg(data),
2654
+ },
2655
+ },
2656
+ "find-internet-password": {
2657
+ operands: [data],
2658
+ options: {
2659
+ "-w": { roles: [], issue: source("keychain password") },
2660
+ "-g": { roles: [], issue: source("keychain password") },
2661
+ },
2662
+ },
2663
+ "dump-keychain": { operands: [data], effect: source("keychain dump") },
2664
+ },
2665
+ },
2666
+ {
2667
+ name: "ps",
2668
+ operands: [data],
2669
+ semantics: "ps",
2670
+ options: {
2671
+ "-E": { roles: [], issue: source("process environments") },
2672
+ ...flags("-a", "-A", "-x", "-u", "-w", "-r", "-f", "-l", "-j", "-H", "--forest"),
2673
+ "-o": arg(data),
2674
+ "-O": arg(data),
2675
+ "--format": arg(data),
2676
+ "-p": arg(data),
2677
+ "--pid": arg(data),
2678
+ "-t": arg(data),
2679
+ "-U": arg(data),
2680
+ "-G": arg(data),
2681
+ },
2682
+ },
2683
+ {
2684
+ name: "launchctl",
2685
+ operands: [data],
2686
+ subcommands: { getenv: { operands: [data], effect: source("launchd environment") } },
2687
+ },
2688
+ {
2689
+ name: "docker",
2690
+ informationFlags: ["--help", "--version"],
2691
+ aliases: ["podman"],
2692
+ operands: [data],
2693
+ startup: ["DOCKER_HOST", "DOCKER_CONFIG"],
2694
+ subcommands: {
2695
+ volume: {
2696
+ operands: [data],
2697
+ subcommands: {
2698
+ ls: {
2699
+ operands: [data],
2700
+ options: {
2701
+ "--format": arg(data),
2702
+ "--filter": arg(data),
2703
+ "-f": arg(data),
2704
+ ...flags("-q", "--quiet"),
2705
+ },
2706
+ },
2707
+ list: {
2708
+ operands: [data],
2709
+ options: {
2710
+ "--format": arg(data),
2711
+ "--filter": arg(data),
2712
+ "-f": arg(data),
2713
+ ...flags("-q", "--quiet"),
2714
+ },
2715
+ },
2716
+ },
2717
+ },
2718
+ buildx: {
2719
+ operands: [data],
2720
+ subcommands: {
2721
+ history: {
2722
+ operands: [data],
2723
+ subcommands: {
2724
+ ls: {
2725
+ operands: [data],
2726
+ options: {
2727
+ "--format": arg(data),
2728
+ "--filter": arg(data),
2729
+ "--builder": arg(data),
2730
+ ...flags("--local", "--no-trunc", "-D", "--debug"),
2731
+ },
2732
+ },
2733
+ },
2734
+ },
2735
+ },
2736
+ },
2737
+ version: { operands: [data], options: { "--format": arg(data), "-f": arg(data) } },
2738
+ info: { operands: [data], options: { "--format": arg(data), "-f": arg(data) } },
2739
+ stats: {
2740
+ operands: [data],
2741
+ options: { "--format": arg(data), ...flags("--no-stream", "--no-trunc", "-a", "--all") },
2742
+ },
2743
+ ps: {
2744
+ operands: [data],
2745
+ options: {
2746
+ ...flags("-q", "-a", "--quiet", "--all", "--no-trunc"),
2747
+ "--format": arg(data),
2748
+ "--filter": arg(data),
2749
+ "-f": arg(data),
2750
+ },
2751
+ },
2752
+ logs: {
2753
+ operands: [data],
2754
+ options: {
2755
+ ...flags("-f", "--follow", "-t", "--timestamps"),
2756
+ "--since": arg(data),
2757
+ "--until": arg(data),
2758
+ "--tail": arg(data),
2759
+ },
2760
+ },
2761
+ stop: { operands: [data], options: { "-t": arg(data), "--time": arg(data) } },
2762
+ rm: { operands: [data], options: flags("-f", "-v", "--force", "--volumes") },
2763
+ build: {
2764
+ operands: [path],
2765
+ options: {
2766
+ "-f": arg("script"),
2767
+ "--file": arg("script"),
2768
+ "-t": arg(data),
2769
+ "--tag": arg(data),
2770
+ "--target": arg(data),
2771
+ "--build-arg": arg(data),
2772
+ ...flags("--no-cache", "--pull", "--quiet", "-q"),
2773
+ },
2774
+ },
2775
+
2776
+ inspect: { operands: [data], effect: source("docker inspect environment") },
2777
+ exec: { operands: [data], effect: unknown("docker exec program") },
2778
+ run: { operands: [data], effect: unknown("docker run program") },
2779
+ compose: {
2780
+ operands: [data],
2781
+ options: {
2782
+ "--profile": arg(data),
2783
+ "-p": arg(data),
2784
+ "--project-name": arg(data),
2785
+ "-f": arg("script"),
2786
+ "--file": arg("script"),
2787
+ "--env-file": arg("content"),
2788
+ },
2789
+ subcommands: {
2790
+ rm: {
2791
+ operands: [data],
2792
+ options: flags("-f", "--force", "-s", "--stop", "-v", "--volumes", "--dry-run"),
2793
+ },
2794
+ ps: {
2795
+ operands: [data],
2796
+ options: {
2797
+ "--format": arg(data),
2798
+ "--filter": arg(data),
2799
+ "--status": arg(data),
2800
+ ...flags("-a", "--all", "-q", "--quiet", "--services", "--no-trunc"),
2801
+ },
2802
+ },
2803
+ config: {
2804
+ operands: [data],
2805
+ options: flags("--get", "--get-all", "--local", "--global", "--show-origin"),
2806
+ effect: source("docker compose resolved environment"),
2807
+ },
2808
+ exec: { operands: [data], effect: unknown("docker compose exec") },
2809
+ run: { operands: [data], effect: unknown("docker compose run entrypoint") },
2810
+ },
2811
+ },
2812
+ },
2813
+ },
2814
+ {
2815
+ name: "gh",
2816
+ options: {
2817
+ "-R": arg(data),
2818
+ "--repo": arg(data),
2819
+ "--json": arg(data),
2820
+ "--jq": arg(code("jq")),
2821
+ "-q": arg(code("jq")),
2822
+ "--template": arg(code("runtime")),
2823
+ "-t": arg(code("runtime")),
2824
+ "--limit": arg(data),
2825
+ "-L": arg(data),
2826
+ "--state": arg(data),
2827
+ "--search": arg(data),
2828
+ "--author": arg(data),
2829
+ "--assignee": arg(data),
2830
+ "--label": arg(data),
2831
+ "--head": arg(data),
2832
+ "--base": arg(data),
2833
+ "--topic": arg(data),
2834
+ ...flags("--help", "--version", "--web", "--comments", "--watch", "--required"),
2835
+ },
2836
+ informationFlags: ["--help", "--version"],
2837
+ operands: [data],
2838
+ semantics: "gh",
2839
+ effect: wrapper("gh", "agent-tools-gh"),
2840
+ startup: ["GH_HOST", "GH_CONFIG_DIR", "GH_BROWSER", "GH_PAGER"],
2841
+ subcommands: {
2842
+ issue: {
2843
+ operands: [data],
2844
+ subcommands: {
2845
+ list: { operands: [data], effect: "none" },
2846
+ view: { operands: [data], effect: "none" },
2847
+ search: { operands: [data], effect: "none" },
2848
+ },
2849
+ },
2850
+ pr: {
2851
+ operands: [data],
2852
+ subcommands: {
2853
+ list: { operands: [data], effect: "none" },
2854
+ view: { operands: [data], effect: "none" },
2855
+ diff: { operands: [data], effect: "none" },
2856
+ checks: { operands: [data], effect: "none" },
2857
+ },
2858
+ },
2859
+ release: {
2860
+ operands: [data],
2861
+ subcommands: {
2862
+ list: { operands: [data], effect: "none" },
2863
+ view: { operands: [data], effect: "none" },
2864
+ },
2865
+ },
2866
+ repo: { operands: [data], subcommands: { view: { operands: [data], effect: "none" } } },
2867
+ search: {
2868
+ operands: [data],
2869
+ subcommands: {
2870
+ issues: { operands: [data], effect: "none" },
2871
+ prs: { operands: [data], effect: "none" },
2872
+ repos: { operands: [data], effect: "none" },
2873
+ },
2874
+ },
2875
+ api: { operands: [data], effect: "none" },
2876
+ auth: { operands: [data], effect: source("gh authentication credentials") },
2877
+ },
2878
+ },
2879
+ {
2880
+ name: "kubectl",
2881
+ informationFlags: ["--help", "--version"],
2882
+ operands: [data],
2883
+ effect: wrapper("kubectl", "agent-tools-k8s"),
2884
+ },
2885
+ {
2886
+ name: "psql",
2887
+ informationFlags: ["--help", "--version"],
2888
+ operands: [data],
2889
+ effect: wrapper("psql", "agent-tools-db"),
2890
+ },
2891
+ {
2892
+ name: "az",
2893
+ informationFlags: ["--help", "--version"],
2894
+ operands: [data],
2895
+ effect: wrapper("az", "agent-tools-az"),
2896
+ subcommands: Object.fromEntries(
2897
+ ["devops", "pipelines", "repos", "boards", "artifacts"].map((name) => [
2898
+ name,
2899
+ { operands: [data], effect: wrapper("az (Azure DevOps)", "agent-tools-azdo") },
2900
+ ]),
2901
+ ),
2902
+ },
2903
+ {
2904
+ name: "curl",
2905
+ readsContent: true,
2906
+ operands: [data],
2907
+ semantics: "curl",
2908
+ startup: ["CURL_HOME"],
2909
+ options: {
2910
+ ...flags("--insecure"),
2911
+ ...flags(
2912
+ "-s",
2913
+ "-S",
2914
+ "-f",
2915
+ "-L",
2916
+ "-I",
2917
+ "-i",
2918
+ "-k",
2919
+ "-v",
2920
+ "--silent",
2921
+ "--show-error",
2922
+ "--fail",
2923
+ "--location",
2924
+ "--head",
2925
+ "--compressed",
2926
+ "--globoff",
2927
+ "-g",
2928
+ ),
2929
+ "-X": arg(data),
2930
+ "--request": arg(data),
2931
+ "--max-time": arg(data),
2932
+ "-m": arg(data),
2933
+ "--connect-timeout": arg(data),
2934
+ "--retry": arg(data),
2935
+ "--write-out": arg(data),
2936
+ "--data-raw": arg(data),
2937
+
2938
+ "-d": arg(data),
2939
+ "--data": arg(data),
2940
+ "--data-binary": arg(data),
2941
+ "-F": arg(data),
2942
+ "--form": arg(data),
2943
+ "-T": arg(path),
2944
+ "--upload-file": arg(path),
2945
+ "-K": arg(path),
2946
+ "--config": arg(path),
2947
+ "--netrc-file": arg(path),
2948
+ "--key": arg(path),
2949
+ "--cert": arg(path),
2950
+ "-b": arg(path),
2951
+ "--cookie": arg(path),
2952
+ "-o": arg("write"),
2953
+ "--output": arg("write"),
2954
+ "-H": arg(data),
2955
+ "--header": arg(data),
2956
+ "-w": arg(data),
2957
+ },
2958
+ },
2959
+ {
2960
+ name: "cp",
2961
+ operands: [path],
2962
+ semantics: "copy",
2963
+ options: {
2964
+ "-t": { roles: ["write"], issue: unknown("copy target directory") },
2965
+ "--target-directory": { roles: ["write"], issue: unknown("copy target directory") },
2966
+ ...flags(
2967
+ "-f",
2968
+ "-n",
2969
+ "-v",
2970
+ "-p",
2971
+ "-r",
2972
+ "-R",
2973
+ "-a",
2974
+ "--recursive",
2975
+ "--archive",
2976
+ "--no-clobber",
2977
+ "--verbose",
2978
+ ),
2979
+ },
2980
+ },
2981
+ { name: "seq", operands: [data], semantics: "seq" },
2982
+ { name: "tr", operands: [data], stdout: "opaque", options: flags("-c", "-C", "-d", "-s", "-t") },
2983
+ {
2984
+ name: "uniq",
2985
+ readsContent: true,
2986
+ operands: [path, "write"],
2987
+ stdout: "opaque",
2988
+ options: {
2989
+ ...flags("-c", "-d", "-u", "-i"),
2990
+ "-f": arg(data),
2991
+ "-s": arg(data),
2992
+ "-w": arg(data),
2993
+ "--skip-fields": arg(data),
2994
+ "--skip-chars": arg(data),
2995
+ "--check-chars": arg(data),
2996
+ },
2997
+ },
2998
+ { name: "sleep", operands: [data] },
2999
+ {
3000
+ name: "mkdir",
3001
+ operands: ["names"],
3002
+ options: { "-m": arg(data), ...flags("-p", "-v", "--parents") },
3003
+ },
3004
+ {
3005
+ name: "rm",
3006
+ aliases: ["touch", "du", "stat", "chown", "df"],
3007
+ options: {
3008
+ ...flags("-f", "-r", "-R", "-v", "-n", "-i", "-p", "-a", "-m", "-c", "-h", "-s"),
3009
+ "-t": arg(data),
3010
+ "-d": arg(data),
3011
+ },
3012
+ operands: ["names"],
3013
+ },
3014
+ { name: "mv", operands: ["write"] },
3015
+ { name: "chmod", operands: [data, "names"], options: flags("-R", "-f", "-v", "-c") },
3016
+ {
3017
+ name: "break",
3018
+ builtinOnly: true,
3019
+ aliases: ["continue", "return", "exit"],
3020
+ operands: [data],
3021
+ effect: unknown("control transfer outside modeled status flow"),
3022
+ },
3023
+ {
3024
+ name: "test",
3025
+ aliases: ["["],
3026
+ operands: ["names"],
3027
+ options: {
3028
+ ...flags("-a", "-o", "-eq", "-ne", "-lt", "-le", "-gt", "-ge", "-nt", "-ot", "-ef"),
3029
+ ...Object.fromEntries(
3030
+ [
3031
+ "-e",
3032
+ "-f",
3033
+ "-d",
3034
+ "-L",
3035
+ "-h",
3036
+ "-r",
3037
+ "-w",
3038
+ "-x",
3039
+ "-s",
3040
+ "-b",
3041
+ "-c",
3042
+ "-p",
3043
+ "-S",
3044
+ "-t",
3045
+ "-n",
3046
+ "-z",
3047
+ ].map((option) => [option, arg("names")]),
3048
+ ),
3049
+ },
3050
+ },
3051
+ {
3052
+ name: "Get-ChildItem",
3053
+ builtinOnly: true,
3054
+ pipelineData: true,
3055
+ aliases: ["gci", "dir"],
3056
+ operands: ["names"],
3057
+ semantics: "env-provider",
3058
+ options: {
3059
+ "-Path": arg("names"),
3060
+ "-LiteralPath": arg("names"),
3061
+ "-Filter": arg("path-pattern"),
3062
+ "-Include": arg("path-pattern"),
3063
+ "-Exclude": arg("path-pattern"),
3064
+ ...flags("-Force", "-Recurse", "-File", "-Directory", "-Name", "-Hidden"),
3065
+ },
3066
+ },
3067
+ {
3068
+ name: "Get-Content",
3069
+ builtinOnly: true,
3070
+ readsContent: true,
3071
+ aliases: ["gc"],
3072
+ operands: [path],
3073
+ options: {
3074
+ "-Path": arg(path),
3075
+ "-LiteralPath": arg(path),
3076
+ "-TotalCount": arg(data),
3077
+ ...flags("-Raw"),
3078
+ },
3079
+ },
3080
+ {
3081
+ name: "Write-Output",
3082
+ builtinOnly: true,
3083
+ pipelineData: true,
3084
+ aliases: ["Write-Host"],
3085
+ operands: [data],
3086
+ stdout: "arguments",
3087
+ },
3088
+ {
3089
+ name: "Get-StoredCredential",
3090
+ aliases: ["vaultcmd"],
3091
+ operands: [data],
3092
+ effect: source("Windows credentials"),
3093
+ },
3094
+ {
3095
+ name: "op",
3096
+ informationFlags: ["--help", "--version"],
3097
+ operands: [data],
3098
+ effect: source("password manager secret"),
3099
+ subcommands: { whoami: { operands: [data], effect: "none" } },
3100
+ },
3101
+ { name: "gcloud", operands: [path] },
3102
+ { name: "aws", operands: [path] },
3103
+ { name: "bw", operands: [path] },
3104
+ { name: "pass", operands: [path] },
3105
+ {
3106
+ name: "npm",
3107
+ operands: [path],
3108
+ options: {
3109
+ "--package": arg(data),
3110
+ "-p": arg(data),
3111
+ ...flags("--yes", "-y", "-g", "--global"),
3112
+ ...flags("--version", "--help", "--silent", "-s", "--json"),
3113
+ "--prefix": arg("names"),
3114
+ },
3115
+ subcommands: {
3116
+ view: { operands: [data], options: flags("--prefer-online", "--prefer-offline") },
3117
+ run: { operands: [data] },
3118
+ "run-script": { operands: [data] },
3119
+ exec: {
3120
+ operands: [data],
3121
+ options: { "-c": arg(code("runtime")), "--call": arg(code("runtime")) },
3122
+ },
3123
+ },
3124
+ },
3125
+ { name: "git-credential-helper", namePattern: /^(?:git|docker)-credential-/, operands: [path] },
3126
+ { name: "cmdkey", operands: [data], effect: source("Windows stored credentials") },
3127
+ { name: "cmd", operands: [data], effect: unknown("cmd command language") },
3128
+ {
3129
+ name: "powershell",
3130
+ aliases: ["pwsh"],
3131
+ operands: [data],
3132
+ effect: unknown("PowerShell command invocation"),
3133
+ },
3134
+ {
3135
+ name: "Start-Process",
3136
+ builtinOnly: true,
3137
+ aliases: ["saps", "start"],
3138
+ operands: [data],
3139
+ effect: unknown("Start-Process code"),
3140
+ },
3141
+ {
3142
+ name: "Invoke-Expression",
3143
+ builtinOnly: true,
3144
+ aliases: ["iex"],
3145
+ operands: [data],
3146
+ effect: unknown("Invoke-Expression code"),
3147
+ },
3148
+ ];
3149
+
3150
+ export function isStdinScript(value: string): boolean {
3151
+ return /^(?:-|\/dev\/stdin|\/dev\/fd\/0|\/proc\/(?:self|[0-9]+)\/fd\/0)$/.test(value);
3152
+ }
3153
+
3154
+ export function executableName(name: string): string {
3155
+ return (name.replace(/\\/g, "/").split("/").at(-1) ?? name)
3156
+ .replace(/\.(?:exe|cmd|bat|ps1)$/i, "")
3157
+ .toLowerCase();
3158
+ }
3159
+ export function buildRegistry(
3160
+ blocked: readonly { readonly tool: string; readonly suggestion: string }[] = [],
3161
+ ): Registry {
3162
+ const entries = new Map<string, ExecutableEntry>();
3163
+ const startupVariables = new Set([
3164
+ "PATH",
3165
+ "HOME",
3166
+ "XDG_CONFIG_HOME",
3167
+ "LD_PRELOAD",
3168
+ "LD_AUDIT",
3169
+ "DYLD_INSERT_LIBRARIES",
3170
+ "LESSOPEN",
3171
+ "LESSCLOSE",
3172
+ "MANPAGER",
3173
+ "BAT_PAGER",
3174
+ "GIT_PAGER",
3175
+ "VISUAL",
3176
+ "EDITOR",
3177
+ "FPATH",
3178
+ "ZDOTDIR",
3179
+ "GLOBIGNORE",
3180
+ "LIBPATH",
3181
+ "SHLIB_PATH",
3182
+ "BUN_PRELOAD",
3183
+ "PS0",
3184
+ "PS1",
3185
+ "PS2",
3186
+ "PS3",
3187
+ "PS4",
3188
+ ]);
3189
+ for (const entry of EXECUTABLES) {
3190
+ for (const name of [entry.name, ...(entry.aliases ?? [])]) {
3191
+ const key = executableName(name);
3192
+ if (entries.has(key)) throw new Error(`duplicate executable ${key}`);
3193
+ entries.set(key, entry);
3194
+ }
3195
+ for (const variable of entry.startup ?? []) startupVariables.add(variable);
3196
+ }
3197
+ return {
3198
+ entries,
3199
+ startupVariables,
3200
+ blocked: new Map(blocked.map((item) => [executableName(item.tool), item.suggestion])),
3201
+ };
3202
+ }
3203
+
3204
+ function inertGitConfigKey(key: string): boolean {
3205
+ return /^(?:user\.(?:name|email)|color\.[A-Za-z0-9.-]+|core\.(?:quotepath|autocrlf)|advice\.[A-Za-z0-9.-]+|init\.defaultbranch|(?:commit|tag)\.gpgsign|pull\.rebase)$/i.test(
3206
+ key,
3207
+ );
3208
+ }
3209
+
3210
+ function analyzeUnknownOperands(
3211
+ words: readonly (string | undefined)[],
3212
+ registry: Registry,
3213
+ dialect: "bash" | "powershell",
3214
+ ): Pick<InvocationPlan, "issues" | "refuseSuppliedStdin" | "roles"> {
3215
+ const issues: Issue[] = [];
3216
+ let refuseSuppliedStdin = false;
3217
+ const roles: Array<InvocationPlan["roles"][number]> = [];
3218
+ for (let index = 0; index < words.length; index++) {
3219
+ const operand = words[index] ?? "";
3220
+ const wordName =
3221
+ operand.startsWith("-") && operand.includes("=")
3222
+ ? operand.slice(operand.indexOf("=") + 1)
3223
+ : operand;
3224
+ const name = executableName(wordName);
3225
+ const previous = words[index - 1];
3226
+ const optionValue =
3227
+ operand.startsWith("-") ||
3228
+ (previous?.startsWith("-") && previous !== "--" && !previous.includes("="));
3229
+ const sourceWord =
3230
+ (!optionValue || name !== "env") &&
3231
+ (dialect === "powershell" || wordName.replace(/\\/g, "/").split("/").at(-1) === name);
3232
+ const entry =
3233
+ registry.entries.get(name) ??
3234
+ [...registry.entries.values()].find((candidate) => candidate.namePattern?.test(name));
3235
+ if (!entry || ["export", "set"].includes(entry.name)) continue;
3236
+ let mode: Omit<ExecutableEntry, "name"> = entry;
3237
+ let position = index + 1;
3238
+ while (words[position] !== undefined && mode.subcommands?.[words[position] ?? ""]) {
3239
+ mode = mode.subcommands[words[position] ?? ""] ?? mode;
3240
+ position++;
3241
+ }
3242
+ refuseSuppliedStdin ||=
3243
+ Boolean(mode.readsStdinCode ?? entry.readsStdinCode) ||
3244
+ ["shell", "runtime", "python"].includes(entry.semantics ?? "");
3245
+ const effect = mode.effect ?? entry.effect;
3246
+ if (sourceWord && effect && effect !== "none" && effect.rule === "secret-source")
3247
+ issues.push(effect);
3248
+ const environment = entry.semantics === "env";
3249
+ const environmentNames = entry.operands.includes("env-name");
3250
+ if (
3251
+ sourceWord &&
3252
+ entry.bare?.rule === "secret-source" &&
3253
+ !environment &&
3254
+ !environmentNames &&
3255
+ position === words.length
3256
+ )
3257
+ issues.push(entry.bare);
3258
+ let envCommand = false;
3259
+ let envNameSeen = false;
3260
+ const options = { ...entry.options, ...mode.options };
3261
+ let awkProgramSeen = false;
3262
+ let endOptions = false;
3263
+ for (; position < words.length; position++) {
3264
+ const word = words[position];
3265
+ if (word === "--" && !endOptions) {
3266
+ endOptions = true;
3267
+ continue;
3268
+ }
3269
+ if (endOptions || !word?.startsWith("-")) {
3270
+ if (environment && (word === undefined || !/^[A-Za-z_][A-Za-z0-9_]*=/.test(word)))
3271
+ envCommand = true;
3272
+ if (sourceWord && environmentNames) {
3273
+ envNameSeen = true;
3274
+ if (word === undefined)
3275
+ issues.push({
3276
+ kind: "missing-knowledge",
3277
+ rule: "opaque-env-name",
3278
+ detail: "environment name is not literal",
3279
+ });
3280
+ else roles.push({ index: 0, role: "env-name", literal: word });
3281
+ }
3282
+ if (entry.semantics === "awk" && !awkProgramSeen) {
3283
+ issues.push(
3284
+ ...(word === undefined
3285
+ ? [unknown("awk program is not literal")]
3286
+ : analyzeCode("awk", word)),
3287
+ );
3288
+ awkProgramSeen = true;
3289
+ }
3290
+ continue;
3291
+ }
3292
+ const equal = word.indexOf("=");
3293
+ const exact = options[equal < 0 ? word : word.slice(0, equal)];
3294
+ const matched: { option: Option; attached?: string }[] = [];
3295
+ if (exact)
3296
+ matched.push({ option: exact, ...(equal < 0 ? {} : { attached: word.slice(equal + 1) }) });
3297
+ else if (/^-[^-].+/.test(word)) {
3298
+ for (let offset = 1; offset < word.length; offset++) {
3299
+ const option = options[`-${word[offset]}`];
3300
+ if (!option) break;
3301
+ matched.push({
3302
+ option,
3303
+ ...(option.roles.length && offset + 1 < word.length
3304
+ ? { attached: word.slice(offset + 1) }
3305
+ : {}),
3306
+ });
3307
+ if (option.roles.length) break;
3308
+ }
3309
+ }
3310
+ for (const { option, attached } of matched) {
3311
+ if (sourceWord && option.issue?.rule === "secret-source") issues.push(option.issue);
3312
+ if (
3313
+ ["shell", "runtime", "python", "env"].includes(entry.semantics ?? "") &&
3314
+ option.roles.some((role) => typeof role !== "string")
3315
+ )
3316
+ issues.push(unknown("inline code in unknown command operands"));
3317
+ if (environment || environmentNames)
3318
+ position += Math.max(0, option.roles.length - (attached === undefined ? 0 : 1));
3319
+ if (entry.semantics === "awk") {
3320
+ if (option === options["-f"] || option === options["--file"]) awkProgramSeen = true;
3321
+ for (const role of option.roles) {
3322
+ const value = attached ?? words[++position];
3323
+ if (typeof role !== "string" && role.code === "awk") {
3324
+ issues.push(
3325
+ ...(value === undefined
3326
+ ? [unknown("awk program is not literal")]
3327
+ : analyzeCode("awk", value)),
3328
+ );
3329
+ awkProgramSeen = true;
3330
+ }
3331
+ }
3332
+ }
3333
+ }
3334
+ }
3335
+ if (
3336
+ sourceWord &&
3337
+ entry.bare?.rule === "secret-source" &&
3338
+ ((environment && !envCommand) || (environmentNames && !envNameSeen))
3339
+ )
3340
+ issues.push(entry.bare);
3341
+ }
3342
+ return { issues, refuseSuppliedStdin, roles };
3343
+ }
3344
+
3345
+ export function classify(
3346
+ argv: readonly (string | undefined)[],
3347
+ registry: Registry,
3348
+ dialect: "bash" | "powershell" = "bash",
3349
+ runtimeWrapper = false,
3350
+ ): InvocationPlan {
3351
+ return classifyInvocation(argv, registry, dialect, runtimeWrapper, new Set(), { remaining: 32 });
3352
+ }
3353
+
3354
+ function classifyInvocation(
3355
+ argv: readonly (string | undefined)[],
3356
+ registry: Registry,
3357
+ dialect: "bash" | "powershell",
3358
+ runtimeWrapper: boolean,
3359
+ skippedValues: ReadonlySet<number>,
3360
+ budget: { remaining: number },
3361
+ ): InvocationPlan {
3362
+ const roles: { index: number; role: OperandRole; literal?: string }[] = [];
3363
+ const issues: Issue[] = [];
3364
+ const children: Array<InvocationPlan["children"][number]> = [];
3365
+ const assignments: { name: string; index: number; value: string }[] = [];
3366
+ let stdout: InvocationPlan["stdout"] = "opaque";
3367
+ let stdinCode: Language | null = null;
3368
+ let unknownStdinCode = false;
3369
+ const unknownOperandIssues = (words: readonly (string | undefined)[]): readonly Issue[] => {
3370
+ const analysis = analyzeUnknownOperands(words, registry, dialect);
3371
+ roles.push(...analysis.roles);
3372
+ unknownStdinCode ||= analysis.refuseSuppliedStdin;
3373
+ return analysis.issues;
3374
+ };
3375
+ let remote = false;
3376
+ let delta: InvocationPlan["delta"] = "none";
3377
+ let codePositionals: number[] = [];
3378
+ let inPlaceBackupSuffix: string | null = null;
3379
+ let sedEmptySuffix = false;
3380
+ let maxArgs: number | null = null;
3381
+ let maxLines: number | null = null;
3382
+ let replacement: string | null = null;
3383
+ const cwdOperands: { index: number; literal?: string }[] = [];
3384
+ let entry: ExecutableEntry | undefined;
3385
+ let pythonModule: { index: number; name: string | undefined } | null = null;
3386
+ const result = (): InvocationPlan => ({
3387
+ roles,
3388
+ xargsInput: entry?.semantics === "xargs" ? { maxArgs, maxLines, replacement } : null,
3389
+ issues,
3390
+ children,
3391
+ assignments,
3392
+ stdout,
3393
+ delta,
3394
+ stdinCode,
3395
+ refuseSuppliedStdin:
3396
+ unknownStdinCode ||
3397
+ Boolean(entry?.readsStdinCode) ||
3398
+ (runtimeWrapper && pythonModule !== null),
3399
+ pipelinePaths:
3400
+ dialect === "powershell" &&
3401
+ (/^[a-z]+-[a-z]+$/i.test(executable ?? "") ||
3402
+ ["gc", "cat", "type", "sls", "cp", "copy", "mv"].includes(
3403
+ executableName(executable ?? ""),
3404
+ )) &&
3405
+ !entry?.pipelineData,
3406
+ remote,
3407
+ codePositionals,
3408
+ cwdOperands,
3409
+ inPlaceBackupSuffix,
3410
+ startupFiles: entry?.startupFiles ?? [],
3411
+ copyDestination: entry?.semantics === "copy",
3412
+ scriptLanguage: entry?.semantics === "shell" ? "bash" : "runtime",
3413
+ });
3414
+ const executable = argv[0];
3415
+ if (executable === undefined) {
3416
+ issues.push({
3417
+ kind: "missing-knowledge",
3418
+ rule: "opaque-executable",
3419
+ detail: "executable is not literal",
3420
+ });
3421
+ return result();
3422
+ }
3423
+ const name = executableName(executable);
3424
+ const lookupName = dialect === "powershell" && name === "type" ? "get-content" : name;
3425
+ const configured = registry.blocked.get(name);
3426
+
3427
+ entry =
3428
+ registry.entries.get(lookupName) ??
3429
+ [...registry.entries.values()].find((candidate) => candidate.namePattern?.test(name));
3430
+ let informationMode: Omit<ExecutableEntry, "name"> | undefined = entry;
3431
+ for (const segment of argv.slice(1, -1))
3432
+ informationMode = segment === undefined ? undefined : informationMode?.subcommands?.[segment];
3433
+ if (
3434
+ (entry?.informationFlags?.includes(argv.at(-1) ?? "") && informationMode) ||
3435
+ (configured && argv.length === 2 && ["--help", "--version"].includes(argv[1] ?? ""))
3436
+ )
3437
+ return result();
3438
+ if (configured) issues.push(wrapper(name, configured));
3439
+ if (!entry) {
3440
+ issues.push(...unknownOperandIssues(argv.slice(1)));
3441
+ argv.slice(1).forEach((_, i) => roles.push({ index: i + 1, role: path }));
3442
+ return result();
3443
+ }
3444
+ if (name === "rustup" && argv[1] === "run") {
3445
+ const toolchainIndex = argv[2] === "--install" ? 3 : 2;
3446
+ const toolchain = argv[toolchainIndex];
3447
+ if (
3448
+ toolchain === undefined ||
3449
+ toolchain.startsWith("-") ||
3450
+ argv[toolchainIndex + 1] === undefined
3451
+ )
3452
+ issues.push(unknown("toolchain and command must be literal"));
3453
+ else {
3454
+ roles.push({ index: toolchainIndex, role: "names" });
3455
+ children.push({
3456
+ start: toolchainIndex + 1,
3457
+ end: argv.length,
3458
+ appendInput: false,
3459
+ placeholder: null,
3460
+ findRoots: null,
3461
+ runtimeWrapper: true,
3462
+ });
3463
+ }
3464
+ return result();
3465
+ }
3466
+ if (
3467
+ ["nvm", "fnm", "volta", "asdf", "pyenv"].includes(name) &&
3468
+ ["exec", "run"].includes(argv[1] ?? "")
3469
+ ) {
3470
+ let start = 2;
3471
+ if (name === "nvm" && /^[0-9v]/.test(argv[start] ?? "")) start++;
3472
+ if (name === "fnm" && argv[start]?.startsWith("--using=")) start++;
3473
+ else if (name === "fnm" && argv[start] === "--using") start += 2;
3474
+ if (argv[start]?.startsWith("-"))
3475
+ issues.push(unknown("unsupported version manager execution option"));
3476
+ else
3477
+ children.push({
3478
+ start,
3479
+ end: argv.length,
3480
+ appendInput: false,
3481
+ placeholder: null,
3482
+ findRoots: null,
3483
+ runtimeWrapper: true,
3484
+ });
3485
+ return result();
3486
+ }
3487
+ const words = argv.slice(1);
3488
+ if (
3489
+ name === "set" &&
3490
+ words.length > 0 &&
3491
+ words.every((word) => word !== undefined && !word.startsWith("-") && !word.startsWith("+"))
3492
+ )
3493
+ return result();
3494
+ if (name === "brew" && words.length === 1 && ["--help", "--version"].includes(words[0] ?? ""))
3495
+ return result();
3496
+
3497
+ if (
3498
+ name === "git" &&
3499
+ argv.some((word, index) => word?.startsWith("credential-") && argv[index + 1] === "get")
3500
+ )
3501
+ issues.push(source("git credential helper output"));
3502
+ if (
3503
+ (name === "gcloud" &&
3504
+ words[0] === "auth" &&
3505
+ ["print-access-token", "print-identity-token"].includes(words[1] ?? "")) ||
3506
+ (name.startsWith("git-credential-") && words.includes("get")) ||
3507
+ (name.startsWith("docker-credential-") && words.includes("get")) ||
3508
+ (name === "npm" &&
3509
+ words[0] === "config" &&
3510
+ words[1] === "get" &&
3511
+ /auth|token/i.test(words[2] ?? "")) ||
3512
+ (name === "bw" && words[0] === "get") ||
3513
+ (name === "pass" && (words.length === 0 || words[0] === "show")) ||
3514
+ (name === "aws" &&
3515
+ ((words[0] === "sts" && words[1] === "get-session-token") ||
3516
+ (words[0] === "configure" && words[1] === "get") ||
3517
+ (words[0] === "ecr" && words[1] === "get-login-password"))) ||
3518
+ (name === "security" && /^find-.*-password$/.test(words[0] ?? ""))
3519
+ )
3520
+ issues.push(source("credential command output"));
3521
+ if (name === "op" && argv[1] !== "whoami") {
3522
+ issues.push(source("password manager secret"));
3523
+ return result();
3524
+ }
3525
+ let mode: Omit<ExecutableEntry, "name"> = entry;
3526
+ let unknownSubcommandStart: number | null = null;
3527
+ let effect = entry.effect;
3528
+ let position = 1;
3529
+ const positional: number[] = [];
3530
+ let codeSeen = false;
3531
+ let shellStdin = false;
3532
+ let testRunner = false;
3533
+ const optionRoles: { index: number; role: OperandRole; literal?: string }[] = [];
3534
+ let filterOption = false;
3535
+ let endOptions = false;
3536
+ const child = (
3537
+ start: number,
3538
+ end = argv.length,
3539
+ appendInput = false,
3540
+ placeholder: string | null = null,
3541
+ findRoots: readonly string[] | null = null,
3542
+ ) => children.push({ start, end, appendInput, placeholder, findRoots });
3543
+ if (name === "docker" && (argv[1] === "exec" || (argv[1] === "compose" && argv[2] === "exec"))) {
3544
+ remote = true;
3545
+ cwdOperands.push({ index: 0, literal: "/" });
3546
+ const compose = argv[1] === "compose";
3547
+ let index = compose ? 3 : 2;
3548
+ while (argv[index]?.startsWith("-")) {
3549
+ const word = argv[index] ?? "";
3550
+ if (word === "--") {
3551
+ index++;
3552
+ break;
3553
+ }
3554
+ if (["--help", "-h"].includes(word)) return result();
3555
+ if (
3556
+ /^-[itd]+$/.test(word) ||
3557
+ ["--interactive", "--tty", "--detach", ...(compose ? ["-T", "--no-TTY"] : [])].includes(
3558
+ word,
3559
+ )
3560
+ ) {
3561
+ index++;
3562
+ continue;
3563
+ }
3564
+ const match = /^(--[^=]+)=(.*)$/.exec(word) ?? /^(-[uwe])(.+)$/.exec(word);
3565
+ const option = match?.[1] ?? word;
3566
+ const valueIndex = match ? index : index + 1;
3567
+ const value = match?.[2] ?? argv[valueIndex];
3568
+ if (
3569
+ ![
3570
+ "-u",
3571
+ "--user",
3572
+ "-w",
3573
+ "--workdir",
3574
+ "-e",
3575
+ "--env",
3576
+ "--detach-keys",
3577
+ ...(compose ? ["--index"] : []),
3578
+ ].includes(option) ||
3579
+ value === undefined
3580
+ ) {
3581
+ issues.push(unknown("unsupported Docker exec option or nonliteral value"));
3582
+ return result();
3583
+ }
3584
+ if (option === "-w" || option === "--workdir")
3585
+ cwdOperands.push({ index: valueIndex, literal: value });
3586
+ if (option === "-e" || option === "--env") {
3587
+ const binding = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s.exec(value);
3588
+ if (!binding) {
3589
+ issues.push(unknown("Docker exec environment must have a literal value"));
3590
+ return result();
3591
+ }
3592
+ assignments.push({ name: binding[1] ?? "", value: binding[2] ?? "", index: valueIndex });
3593
+ }
3594
+ index += match ? 1 : 2;
3595
+ }
3596
+ if (argv[index] === undefined || argv[index + 1] === undefined)
3597
+ issues.push(unknown("Docker exec container and executable must be literal"));
3598
+ else child(index + 1);
3599
+ return result();
3600
+ }
3601
+ if (name === "direnv" && argv[1] === "exec") {
3602
+ if (argv[2] === undefined || argv[2].startsWith("-") || argv[3] === undefined)
3603
+ issues.push(unknown("direnv exec directory and command are required"));
3604
+ roles.push({ index: 2, role: "names" });
3605
+ children.push({
3606
+ start: 3,
3607
+ end: argv.length,
3608
+ appendInput: false,
3609
+ placeholder: null,
3610
+ findRoots: null,
3611
+ runtimeWrapper: true,
3612
+ });
3613
+ return result();
3614
+ }
3615
+ if (name === "rtk") {
3616
+ let start = 1;
3617
+ while (/^(?:-v+|--verbose|--skip-env)$/.test(argv[start] ?? "")) start++;
3618
+ const subcommand = argv[start];
3619
+ if (subcommand?.startsWith("-") && !["--help", "--version"].includes(subcommand)) {
3620
+ issues.push(...unknownOperandIssues(argv.slice(start)));
3621
+ argv.slice(start).forEach((word, offset) =>
3622
+ roles.push({
3623
+ index: start + offset,
3624
+ role: path,
3625
+ ...(word?.includes("=") ? { literal: word.slice(word.indexOf("=") + 1) } : {}),
3626
+ }),
3627
+ );
3628
+ return result();
3629
+ }
3630
+ if (["proxy", "run", "err", "test", "summary", "bypass", "pipe"].includes(subcommand ?? "")) {
3631
+ let index = start + 1;
3632
+ let shell: string | undefined;
3633
+ let commandIndex: number | undefined;
3634
+ while (argv[index]?.startsWith("-")) {
3635
+ const option = argv[index];
3636
+ if (option === "--") {
3637
+ index++;
3638
+ break;
3639
+ }
3640
+ if (option === "--help" || option === "-h") return result();
3641
+ if (option === "--skip-env" || (subcommand === "pipe" && option === "--passthrough")) {
3642
+ index++;
3643
+ continue;
3644
+ }
3645
+ if (
3646
+ option === "--shell" ||
3647
+ option === "--command" ||
3648
+ option === "-c" ||
3649
+ (subcommand === "pipe" && (option === "--filter" || option === "-f"))
3650
+ ) {
3651
+ if (argv[index + 1] === undefined) {
3652
+ issues.push(unknown("missing rtk option value"));
3653
+ return result();
3654
+ }
3655
+ if (option === "--shell") shell = argv[index + 1];
3656
+ else if (option === "--command" || option === "-c") commandIndex = index + 1;
3657
+ index += 2;
3658
+ continue;
3659
+ }
3660
+ issues.push(...unknownOperandIssues([option, ...argv.slice(index + 1)]));
3661
+ roles.push({
3662
+ index,
3663
+ role: path,
3664
+ ...(option?.includes("=") ? { literal: option.slice(option.indexOf("=") + 1) } : {}),
3665
+ });
3666
+ index++;
3667
+ }
3668
+ if (shell !== undefined || commandIndex !== undefined) {
3669
+ const language = ["sh", "bash", "zsh"].includes(executableName(shell ?? "sh"))
3670
+ ? "bash"
3671
+ : ["pwsh", "powershell"].includes(executableName(shell ?? ""))
3672
+ ? "powershell"
3673
+ : null;
3674
+ if (!language) issues.push(unknown("unsupported rtk shell"));
3675
+ else roles.push({ index: commandIndex ?? index, role: code(language) });
3676
+ if (commandIndex === undefined ? index + 1 !== argv.length : index !== argv.length)
3677
+ issues.push(unknown("ambiguous rtk shell arguments"));
3678
+ } else if (subcommand === "pipe" && index === argv.length) stdout = "stdin";
3679
+ else child(index);
3680
+ return result();
3681
+ } else if (["read", "smart", "json", "log", "deps"].includes(subcommand ?? "")) {
3682
+ mode = {
3683
+ operands: ["content"],
3684
+ options: {
3685
+ "-n": arg(data),
3686
+ "--lines": arg(data),
3687
+ "--max-lines": arg(data),
3688
+ ...flags("--help", "--raw", "--smart"),
3689
+ },
3690
+ };
3691
+ position = start + 1;
3692
+ } else if (
3693
+ [
3694
+ "gain",
3695
+ "methods",
3696
+ "discover",
3697
+ "session",
3698
+ "recall",
3699
+ "cc-economics",
3700
+ "hook-audit",
3701
+ "help",
3702
+ "--help",
3703
+ "--version",
3704
+ ].includes(subcommand ?? "")
3705
+ ) {
3706
+ if (
3707
+ argv
3708
+ .slice(start + 1)
3709
+ .some((value) => value === "--reset" || value === "--write" || value === "--apply")
3710
+ )
3711
+ issues.push(unknown("rtk management mutation"));
3712
+ mode = {
3713
+ operands: [data],
3714
+ options: {
3715
+ ...flags(
3716
+ "--help",
3717
+ "-h",
3718
+ "--json",
3719
+ "--all",
3720
+ "--list",
3721
+ "--history",
3722
+ "--daily",
3723
+ "--weekly",
3724
+ "--monthly",
3725
+ ),
3726
+ "--days": arg(data),
3727
+ "--since": arg(data),
3728
+ "--limit": arg(data),
3729
+ "--format": arg(data),
3730
+ },
3731
+ };
3732
+ position = start + 1;
3733
+ } else if (
3734
+ [
3735
+ "init",
3736
+ "config",
3737
+ "trust",
3738
+ "untrust",
3739
+ "plugins",
3740
+ "learn",
3741
+ "llm-proxy",
3742
+ "telemetry",
3743
+ "verify",
3744
+ "pipeline",
3745
+ "tasks",
3746
+ ].includes(subcommand ?? "")
3747
+ ) {
3748
+ if (!argv.slice(start + 1).some((value) => value === "--help" || value === "-h"))
3749
+ issues.push(unknown("rtk management command"));
3750
+ mode = { operands: [data], options: flags("--help", "-h") };
3751
+ position = start + 1;
3752
+ } else {
3753
+ child(start);
3754
+ return result();
3755
+ }
3756
+ }
3757
+ if (name === "command") {
3758
+ let index = 1;
3759
+ let lookup = false;
3760
+ while (/^-[pvV]+$/.test(argv[index] ?? "")) {
3761
+ lookup ||= /[vV]/.test(argv[index] ?? "");
3762
+ index++;
3763
+ }
3764
+ if (lookup && (argv[index] === "--" || !argv[index]?.startsWith("-"))) return result();
3765
+ }
3766
+ if (["export", "declare", "typeset", "readonly", "local"].includes(name)) {
3767
+ delta = "assign";
3768
+ if (name === "readonly") issues.push(unknown("readonly binding state"));
3769
+ if (argv.slice(1).some((word) => /^-[^-]*[in]/.test(word ?? "")))
3770
+ issues.push(unknown("indirect or arithmetic declaration binding"));
3771
+ for (let index = 1; index < argv.length; index++) {
3772
+ const value = argv[index];
3773
+ if (
3774
+ value &&
3775
+ /^[A-Za-z_][A-Za-z0-9_]*(?:\[[^\]]*\])?\+?=/.test(value) &&
3776
+ !/^[A-Za-z_][A-Za-z0-9_]*=/.test(value)
3777
+ )
3778
+ issues.push(unknown("append or indexed declaration binding"));
3779
+ const match = value && /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s.exec(value);
3780
+ if (match) assignments.push({ name: match[1] ?? "", index, value: match[2] ?? "" });
3781
+ }
3782
+ }
3783
+ if (name === "printf") {
3784
+ const format = argv[1] === "--" ? argv[2] : argv[1];
3785
+ if (
3786
+ format === undefined ||
3787
+ [...format.matchAll(/%(?:%|[-+ #0]*(?:[0-9]+|\*)?(?:\.(?:[0-9]+|\*))?n)/g)].some(
3788
+ ([directive]) => directive !== "%%",
3789
+ )
3790
+ )
3791
+ issues.push(unknown("printf format can write a shell binding"));
3792
+ }
3793
+ if (
3794
+ entry.semantics === "git" &&
3795
+ argv.some((a) => /^-(?:[^-]*O|[^-]*f)/.test(a ?? "")) &&
3796
+ argv.includes("grep")
3797
+ )
3798
+ issues.push(unknown("git grep executable or file option"));
3799
+ if (entry.semantics === "gh") {
3800
+ argv.forEach((a, index) => {
3801
+ if (a?.startsWith("--jq=")) roles.push({ index, role: code("jq"), literal: a.slice(5) });
3802
+ if (a?.startsWith("-q") && a.length > 2)
3803
+ roles.push({ index, role: code("jq"), literal: a.slice(2) });
3804
+ });
3805
+ const jq = argv.findIndex((a) => a === "--jq" || a === "-q");
3806
+ if (jq >= 0) roles.push({ index: jq + 1, role: code("jq") });
3807
+ if (argv.some((a) => a === "--template" || a === "-t" || a?.startsWith("--template=")))
3808
+ issues.push(unknown("gh template code"));
3809
+ if (argv[1] === "api") {
3810
+ issues.push(wrapper("gh", "agent-tools-gh"));
3811
+ return result();
3812
+ }
3813
+ let selected = mode;
3814
+ for (const value of argv.slice(1, 3)) {
3815
+ if (value && selected.subcommands?.[value]) selected = selected.subcommands[value];
3816
+ else break;
3817
+ }
3818
+ if (
3819
+ !(
3820
+ selected.effect === "none" &&
3821
+ argv.some(
3822
+ (a, i) =>
3823
+ ((a === "-R" || a === "--repo") && Boolean(argv[i + 1])) ||
3824
+ (a?.startsWith("--repo=") && a.length > 7),
3825
+ )
3826
+ )
3827
+ )
3828
+ issues.push(
3829
+ selected.effect && selected.effect !== "none"
3830
+ ? selected.effect
3831
+ : wrapper("gh", "agent-tools-gh"),
3832
+ );
3833
+ }
3834
+ if (name === "fd") {
3835
+ const execution = argv.findIndex((word) =>
3836
+ ["-x", "-X", "--exec", "--exec-batch"].includes(word ?? ""),
3837
+ );
3838
+ if (execution >= 0) {
3839
+ if (execution === argv.length - 1) issues.push(unknown("fd execution has no executable"));
3840
+ else {
3841
+ const prefix = classify(argv.slice(0, execution), registry, dialect);
3842
+ roles.push(...prefix.roles);
3843
+ issues.push(...prefix.issues);
3844
+ child(
3845
+ execution + 1,
3846
+ argv.at(-1) === ";" ? argv.length - 1 : argv.length,
3847
+ !argv.slice(execution + 1).some((word) => word?.includes("{}")),
3848
+ "{}",
3849
+ [],
3850
+ );
3851
+ }
3852
+ return result();
3853
+ }
3854
+ }
3855
+ if (entry.semantics === "find") {
3856
+ const roots: string[] = [];
3857
+ for (let i = 1; i < argv.length; i++) {
3858
+ const a = argv[i];
3859
+ if (a && ["-exec", "-execdir", "-ok", "-okdir"].includes(a)) {
3860
+ const end = argv.findIndex((v, n) => n > i && (v === ";" || v === "+"));
3861
+ if (end < 0) issues.push(unknown("unterminated find execution"));
3862
+ else {
3863
+ child(i + 1, end, false, "{}", roots);
3864
+ i = end;
3865
+ }
3866
+ } else if (
3867
+ a &&
3868
+ [
3869
+ "-name",
3870
+ "-iname",
3871
+ "-path",
3872
+ "-regex",
3873
+ "-type",
3874
+ "-maxdepth",
3875
+ "-mindepth",
3876
+ "-newer",
3877
+ "-newermt",
3878
+ "-mmin",
3879
+ "-mtime",
3880
+ "-size",
3881
+ ].includes(a)
3882
+ )
3883
+ i++;
3884
+ else if (a && ["-fprint", "-fls", "-fprintf"].includes(a)) {
3885
+ roles.push({ index: ++i, role: "write" });
3886
+ } else if (
3887
+ a &&
3888
+ [
3889
+ "-print",
3890
+ "-print0",
3891
+ "-delete",
3892
+ "-empty",
3893
+ "-depth",
3894
+ "-prune",
3895
+ "-a",
3896
+ "-o",
3897
+ "-and",
3898
+ "-or",
3899
+ "-not",
3900
+ "-L",
3901
+ "-H",
3902
+ "-P",
3903
+ "-x",
3904
+ "-s",
3905
+ ].includes(a)
3906
+ )
3907
+ continue;
3908
+ else if (a?.startsWith("-")) {
3909
+ issues.push(...unknownOperandIssues([a, argv[i + 1]]));
3910
+ roles.push({
3911
+ index: i,
3912
+ role: path,
3913
+ ...(a.includes("=") ? { literal: a.slice(a.indexOf("=") + 1) } : {}),
3914
+ });
3915
+ if (!a.includes("=") && argv[i + 1] !== undefined && !argv[i + 1]?.startsWith("-"))
3916
+ roles.push({ index: i + 1, role: path });
3917
+ } else if (a) roots.push(a);
3918
+ }
3919
+ return result();
3920
+ }
3921
+ while (position < argv.length) {
3922
+ if (skippedValues.has(position)) {
3923
+ position++;
3924
+ continue;
3925
+ }
3926
+ const a = argv[position];
3927
+ if (a === undefined) {
3928
+ if (["env", "forward", "timeout", "xargs"].includes(entry.semantics ?? ""))
3929
+ issues.push({
3930
+ kind: "missing-knowledge",
3931
+ rule: "opaque-executable",
3932
+ detail: "wrapper boundary is not literal",
3933
+ });
3934
+ positional.push(position++);
3935
+ if (
3936
+ entry.optionsBeforeOperands ||
3937
+ ["runtime", "python", "shell"].includes(entry.semantics ?? "")
3938
+ )
3939
+ endOptions = true;
3940
+ continue;
3941
+ }
3942
+ if (
3943
+ dialect === "bash" &&
3944
+ ((entry.optionGrammar === "echo" && !/^-[neE]+$/.test(a)) ||
3945
+ (entry.optionGrammar === "printf" && a !== "--" && !a.startsWith("-v")))
3946
+ )
3947
+ endOptions = true;
3948
+ if (a === "--" && !endOptions) {
3949
+ endOptions = true;
3950
+ position++;
3951
+ continue;
3952
+ }
3953
+ const modeName =
3954
+ entry.name === "npm" && a === "x" && mode === entry
3955
+ ? "exec"
3956
+ : entry.name === "git" && mode === entry
3957
+ ? a === "annotate"
3958
+ ? "blame"
3959
+ : ["range-diff", "format-patch"].includes(a)
3960
+ ? "log"
3961
+ : a
3962
+ : a;
3963
+ if (!endOptions && mode.subcommands?.[modeName]) {
3964
+ mode = mode.subcommands[modeName];
3965
+ effect = mode.effect ?? effect;
3966
+ if (entry.semantics === "git" && a === "config") {
3967
+ const query = argv
3968
+ .slice(position + 1)
3969
+ .filter(
3970
+ (word) =>
3971
+ !["--local", "--global", "--show-origin", "--get", "--get-all"].includes(word ?? ""),
3972
+ );
3973
+ const key = query[0];
3974
+ if (
3975
+ query.length === 1 &&
3976
+ key !== undefined &&
3977
+ (inertGitConfigKey(key) ||
3978
+ /^(?:credential\.helper|core\.(?:editor|pager)|remote\.[^.]+\.url)$/i.test(key))
3979
+ )
3980
+ effect = "none";
3981
+ }
3982
+ position++;
3983
+ continue;
3984
+ }
3985
+ const options =
3986
+ entry.semantics === "git" && mode !== entry
3987
+ ? {
3988
+ ...Object.fromEntries(
3989
+ Object.entries(entry.options ?? {}).filter(
3990
+ ([key, option]) =>
3991
+ key.startsWith("--") &&
3992
+ (option.issue ||
3993
+ option.roles.some(
3994
+ (role) =>
3995
+ typeof role !== "string" ||
3996
+ ["path", "content", "script", "env-name"].includes(role),
3997
+ )),
3998
+ ),
3999
+ ),
4000
+ ...mode.options,
4001
+ }
4002
+ : { ...entry.options, ...mode.options };
4003
+ if (entry.semantics === "sed" && !endOptions && /^-[^-]/.test(a)) {
4004
+ const inPlaceIndex = a.indexOf("i", 1);
4005
+ if (
4006
+ inPlaceIndex > 0 &&
4007
+ a
4008
+ .slice(1, inPlaceIndex)
4009
+ .split("")
4010
+ .every((letter) => options[`-${letter}`]?.roles.length === 0)
4011
+ ) {
4012
+ for (const letter of a.slice(1, inPlaceIndex)) {
4013
+ const finding = options[`-${letter}`]?.issue;
4014
+ if (finding) issues.push(finding);
4015
+ }
4016
+ inPlaceBackupSuffix = a.slice(inPlaceIndex + 1);
4017
+ if (a === "-i" && argv[position + 1] === "") {
4018
+ sedEmptySuffix = true;
4019
+ position++;
4020
+ }
4021
+ position++;
4022
+ continue;
4023
+ }
4024
+ }
4025
+ if (entry.semantics === "shell" && !endOptions && /^-[euxs]*s/.test(a)) shellStdin = true;
4026
+ let optionName = a;
4027
+ let attached: string | undefined;
4028
+ const equal = dialect === "powershell" && /^-[^:]+:/.test(a) ? a.indexOf(":") : a.indexOf("=");
4029
+ if (a.startsWith("-") && equal > 0) {
4030
+ optionName = a.slice(0, equal);
4031
+ attached = a.slice(equal + 1);
4032
+ }
4033
+ if (!(optionName in options) && /^-[^-].+/.test(a)) {
4034
+ let offset = 1;
4035
+ while (offset < a.length && options[`-${a[offset]}`]?.roles.length === 0) {
4036
+ const finding = options[`-${a[offset]}`]?.issue;
4037
+ if (finding) issues.push(finding);
4038
+ offset++;
4039
+ }
4040
+ if (offset > 1 && offset < a.length && options[`-${a[offset]}`]?.roles.length) {
4041
+ optionName = `-${a[offset]}`;
4042
+ attached = offset + 1 < a.length ? a.slice(offset + 1) : undefined;
4043
+ }
4044
+ }
4045
+ if (!(optionName in options) && /^-[^-].+/.test(a)) {
4046
+ const prefix = a.slice(0, 2);
4047
+ if (options[prefix]?.roles.length || options[prefix]?.attachedValue?.test(a.slice(2))) {
4048
+ optionName = prefix;
4049
+ attached = a.slice(2);
4050
+ } else if (
4051
+ a
4052
+ .slice(1)
4053
+ .split("")
4054
+ .every((c) => options[`-${c}`]?.roles.length === 0)
4055
+ ) {
4056
+ for (const c of a.slice(1)) {
4057
+ const issue = options[`-${c}`]?.issue;
4058
+ if (issue) issues.push(issue);
4059
+ }
4060
+ position++;
4061
+ continue;
4062
+ }
4063
+ }
4064
+ const option =
4065
+ options[optionName] ??
4066
+ (optionName.startsWith("--")
4067
+ ? Object.entries(options).find(
4068
+ ([key, value]) =>
4069
+ key.startsWith(optionName) &&
4070
+ (value.issue || value.roles.some((role) => typeof role !== "string")),
4071
+ )?.[1]
4072
+ : undefined) ??
4073
+ (dialect === "powershell"
4074
+ ? Object.entries(options).find(
4075
+ ([key]) => key.toLowerCase() === optionName.toLowerCase(),
4076
+ )?.[1]
4077
+ : undefined);
4078
+ if (entry.semantics === "git" && mode === entry && !endOptions && optionName === "-c") {
4079
+ const setting = attached ?? argv[position + 1];
4080
+ const settingEqual = setting?.indexOf("=") ?? -1;
4081
+ if (
4082
+ setting === undefined ||
4083
+ settingEqual < 1 ||
4084
+ !(
4085
+ inertGitConfigKey(setting.slice(0, settingEqual)) ||
4086
+ (/^(?:core|sequence)\.editor$/i.test(setting.slice(0, settingEqual)) &&
4087
+ ["true", ":", "false"].includes(setting.slice(settingEqual + 1)))
4088
+ )
4089
+ )
4090
+ issues.push(unknown("git config override"));
4091
+ }
4092
+ if (!endOptions && option && a.startsWith("-")) {
4093
+ if (
4094
+ entry.name === "bun" &&
4095
+ ["--filter", "--cwd"].includes(optionName) &&
4096
+ (attached ?? argv[position + 1]) === undefined
4097
+ )
4098
+ issues.push(unknown("Bun option operand is not literal"));
4099
+ if (entry.name === "bun" && optionName === "--cwd") {
4100
+ if (cwdOperands.length) issues.push(unknown("repeated Bun working directory"));
4101
+ cwdOperands.push({
4102
+ index: attached === undefined ? position + 1 : position,
4103
+ ...(attached !== undefined ? { literal: attached } : {}),
4104
+ });
4105
+ }
4106
+ if (entry.name === "node" && optionName === "--test") testRunner = true;
4107
+ if (entry.semantics === "sed" && option === options["--in-place"])
4108
+ inPlaceBackupSuffix = attached ?? "";
4109
+ if (option.issue) issues.push(option.issue);
4110
+ if (["-e", "--expression", "--regexp", "-f", "--file"].includes(optionName))
4111
+ filterOption = true;
4112
+ for (let i = 0; i < option.roles.length; i++) {
4113
+ const role = option.roles[i] ?? data;
4114
+ const index = i === 0 && attached !== undefined ? position : ++position;
4115
+ if (
4116
+ index >= argv.length &&
4117
+ !(
4118
+ role === data &&
4119
+ ["--ws-error-highlight", "--color-moved-ws", "--line-prefix", "-l"].includes(optionName)
4120
+ )
4121
+ )
4122
+ issues.push(unknown(`missing ${optionName} argument`));
4123
+ if (
4124
+ entry.semantics === "awk" &&
4125
+ optionName === "-v" &&
4126
+ (attached ?? argv[index]) === undefined
4127
+ )
4128
+ issues.push(unknown("awk variable assignment is not literal"));
4129
+ if (
4130
+ ["git", "env"].includes(entry.semantics ?? "") &&
4131
+ ["-C", "--chdir", "--git-dir", "--work-tree"].includes(optionName) &&
4132
+ (entry.semantics !== "git" || mode === entry)
4133
+ )
4134
+ cwdOperands.push({ index, ...(attached !== undefined ? { literal: attached } : {}) });
4135
+ optionRoles.push({
4136
+ index,
4137
+ role,
4138
+ ...(i === 0 && attached !== undefined ? { literal: attached } : {}),
4139
+ });
4140
+ if (entry.semantics === "python" && optionName === "-m") {
4141
+ pythonModule = { index, name: attached ?? argv[index] };
4142
+ if (runtimeWrapper) endOptions = true;
4143
+ }
4144
+ if (typeof role !== "string") codeSeen = true;
4145
+ if (entry.semantics === "xargs") {
4146
+ const value = attached ?? argv[index];
4147
+ if (optionName === "-I") {
4148
+ replacement = value ?? null;
4149
+ if (!replacement) issues.push(unknown("xargs replacement is not literal"));
4150
+ }
4151
+ if (["-n", "-L", "-P"].includes(optionName)) {
4152
+ if (value === undefined || !/^[1-9][0-9]*$/.test(value) || Number(value) > 64)
4153
+ issues.push(unknown("xargs bound is not supported"));
4154
+ else if (optionName === "-n") maxArgs = Number(value);
4155
+ else if (optionName === "-L") maxLines = Number(value);
4156
+ }
4157
+ if (
4158
+ [replacement !== null, maxArgs !== null, maxLines !== null].filter(Boolean).length > 1
4159
+ )
4160
+ issues.push(unknown("combined xargs input modes"));
4161
+ }
4162
+ if (entry.semantics === "shell" && typeof role !== "string") {
4163
+ codePositionals = argv.slice(position + 1).map((_, n) => position + 1 + n);
4164
+ position = argv.length;
4165
+ break;
4166
+ }
4167
+ }
4168
+ position++;
4169
+ continue;
4170
+ }
4171
+ if (!endOptions && mode.numericOption && /^-[0-9]+$/.test(a)) {
4172
+ position++;
4173
+ continue;
4174
+ }
4175
+ if (!endOptions && a.startsWith("-") && a !== "-") {
4176
+ optionRoles.push({
4177
+ index: position,
4178
+ role: path,
4179
+ ...(attached !== undefined ? { literal: attached } : {}),
4180
+ });
4181
+ issues.push(...unknownOperandIssues([a, ...argv.slice(position + 1)]));
4182
+ const next = argv[position + 1];
4183
+ if (attached === undefined && next !== undefined && !next.startsWith("-")) {
4184
+ optionRoles.push({ index: position + 1, role: path });
4185
+ if (
4186
+ (mode.subcommands ||
4187
+ ["forward", "env", "timeout", "xargs"].includes(entry.semantics ?? "")) &&
4188
+ !skippedValues.has(position + 1)
4189
+ ) {
4190
+ if (budget.remaining-- <= 0) issues.push(unknown("option arity analysis limit"));
4191
+ else {
4192
+ const alternative = classifyInvocation(
4193
+ argv,
4194
+ registry,
4195
+ dialect,
4196
+ runtimeWrapper,
4197
+ new Set([...skippedValues, position + 1]),
4198
+ budget,
4199
+ );
4200
+ roles.push(...alternative.roles);
4201
+ issues.push(...alternative.issues);
4202
+ children.push(...alternative.children);
4203
+ assignments.push(...alternative.assignments);
4204
+ cwdOperands.push(...alternative.cwdOperands);
4205
+ if (alternative.stdinCode) stdinCode = alternative.stdinCode;
4206
+ unknownStdinCode ||= alternative.refuseSuppliedStdin;
4207
+ if (alternative.delta !== "none" && alternative.delta !== delta)
4208
+ issues.push(unknown("ambiguous option state change"));
4209
+ }
4210
+ }
4211
+ }
4212
+ position++;
4213
+ continue;
4214
+ }
4215
+ if (entry.name === "git" && mode === entry.subcommands?.bisect?.subcommands?.run) {
4216
+ child(position);
4217
+ break;
4218
+ }
4219
+ if (entry.name === "git" && mode === entry.subcommands?.submodule?.subcommands?.foreach) {
4220
+ const body = argv.slice(position);
4221
+ if (body.some((word) => word === undefined))
4222
+ issues.push(unknown("submodule shell body is not literal"));
4223
+ else roles.push({ index: position, role: code("bash"), literal: body.join(" ") });
4224
+ break;
4225
+ }
4226
+ if (entry.semantics === "env" && /^[A-Za-z_][A-Za-z0-9_]*=/.test(a)) {
4227
+ const eq = a.indexOf("=");
4228
+ assignments.push({ name: a.slice(0, eq), index: position, value: a.slice(eq + 1) });
4229
+ position++;
4230
+ continue;
4231
+ }
4232
+ if (
4233
+ ["forward", "env", "xargs"].includes(entry.semantics ?? "") ||
4234
+ (["uv", "conda"].includes(entry.name) && mode === entry.subcommands?.run)
4235
+ ) {
4236
+ children.push({
4237
+ start: position,
4238
+ end: argv.length,
4239
+ appendInput: entry.semantics === "xargs",
4240
+ placeholder: replacement,
4241
+ findRoots: null,
4242
+ runtimeWrapper: true,
4243
+ });
4244
+ position = argv.length;
4245
+ break;
4246
+ }
4247
+ if (entry.semantics === "timeout" && positional.length === 1) {
4248
+ children.push({
4249
+ start: position,
4250
+ end: argv.length,
4251
+ appendInput: false,
4252
+ placeholder: null,
4253
+ findRoots: null,
4254
+ runtimeWrapper: true,
4255
+ });
4256
+ position = argv.length;
4257
+ break;
4258
+ }
4259
+ if (testRunner && endOptions && a.startsWith("-"))
4260
+ issues.push(unknown("test runner option after file operand"));
4261
+ if (mode.subcommands && entry.name !== "brew") {
4262
+ unknownSubcommandStart = position;
4263
+ mode = { operands: [path], options: { ...entry.options, ...mode.options } };
4264
+ }
4265
+ if (entry.name === "brew" && mode === entry) mode = { operands: [data] };
4266
+ positional.push(position++);
4267
+ if (
4268
+ entry.optionsBeforeOperands ||
4269
+ ["runtime", "python", "shell"].includes(entry.semantics ?? "")
4270
+ )
4271
+ endOptions = true;
4272
+ }
4273
+ if (unknownSubcommandStart !== null) {
4274
+ const previous = argv[unknownSubcommandStart - 1];
4275
+ const start =
4276
+ previous?.startsWith("-") && previous !== "--" && !previous.includes("=")
4277
+ ? unknownSubcommandStart - 1
4278
+ : unknownSubcommandStart;
4279
+ issues.push(
4280
+ ...unknownOperandIssues(
4281
+ argv
4282
+ .slice(start)
4283
+ .filter(
4284
+ (_, offset) =>
4285
+ !optionRoles.some(
4286
+ (operand) => operand.index === start + offset && operand.role === data,
4287
+ ),
4288
+ ),
4289
+ ),
4290
+ );
4291
+ }
4292
+ if (
4293
+ entry.name === "docker" &&
4294
+ mode === entry.subcommands?.compose?.subcommands?.config &&
4295
+ argv.some((word) =>
4296
+ ["--services", "--volumes", "--profiles", "--images", "-q", "--quiet"].includes(word ?? ""),
4297
+ )
4298
+ )
4299
+ effect = "none";
4300
+ if (effect && effect !== "none") issues.push(effect);
4301
+ if (!positional.length && !children.length && !codeSeen && entry.bare) issues.push(entry.bare);
4302
+ roles.push(...optionRoles);
4303
+ const operands =
4304
+ (filterOption && ["sed", "awk"].includes(entry.semantics ?? "")) ||
4305
+ (filterOption &&
4306
+ (entry.name === "grep" || (entry.semantics === "git" && argv.includes("grep"))))
4307
+ ? [path]
4308
+ : mode.operands;
4309
+ for (let i = 0; i < positional.length; i++)
4310
+ roles.push({
4311
+ index: positional[i] ?? 0,
4312
+ role: shellStdin
4313
+ ? data
4314
+ : testRunner
4315
+ ? "script"
4316
+ : (operands[Math.min(i, operands.length - 1)] ?? path),
4317
+ });
4318
+ if (entry.name === "dd")
4319
+ for (const operand of roles) {
4320
+ if (optionRoles.includes(operand)) continue;
4321
+ const value = argv[operand.index] ?? "";
4322
+ if (value.startsWith("if=")) {
4323
+ operand.role = "content";
4324
+ operand.literal = value.slice(3);
4325
+ } else if (value.startsWith("of=")) {
4326
+ operand.role = "write";
4327
+ operand.literal = value.slice(3);
4328
+ } else if (!/^(?:bs|ibs|obs|count|skip|seek|conv|iflag|oflag|status)=/.test(value))
4329
+ issues.push(unknown("unsupported dd operand"));
4330
+ }
4331
+ if (
4332
+ entry.name === "npm" &&
4333
+ (mode === entry.subcommands?.run || mode === entry.subcommands?.["run-script"]) &&
4334
+ argv[positional[0] ?? -1] === "env"
4335
+ )
4336
+ issues.push(source("npm environment listing"));
4337
+ if (
4338
+ entry.name === "npm" &&
4339
+ mode === entry.subcommands?.exec &&
4340
+ !codeSeen &&
4341
+ positional[0] !== undefined
4342
+ )
4343
+ child(positional[0]);
4344
+ if (["man", "info", "tldr"].includes(entry.name))
4345
+ for (const operand of roles)
4346
+ if (operand.role === data && /[/]/.test(argv[operand.index] ?? "")) operand.role = "content";
4347
+ if (["view", "less"].includes(entry.name))
4348
+ for (const operand of roles)
4349
+ if ((argv[operand.index] ?? "").startsWith("+")) operand.role = code("runtime");
4350
+ if (entry.name === "make")
4351
+ for (const index of positional) {
4352
+ const assignment = /^([^\s=:+?!]+)(?:::?|\+|\?|!)?=(.*)$/s.exec(argv[index] ?? "");
4353
+ if (!assignment) continue;
4354
+ const value = assignment[2] ?? "";
4355
+ if (
4356
+ ["SHELL", ".SHELLFLAGS", "MAKESHELL"].includes(assignment[1] ?? "") ||
4357
+ (argv[index] ?? "").includes("!=")
4358
+ )
4359
+ roles.push({ index, role: code("runtime") });
4360
+ else {
4361
+ roles.push({ index, role: path, literal: value });
4362
+ issues.push(...unknownOperandIssues(value.split(/\s+/)));
4363
+ }
4364
+ }
4365
+ if (name === "rg" && argv.includes("--files")) {
4366
+ for (const operand of roles)
4367
+ if (
4368
+ operand.role === "path" &&
4369
+ !optionRoles.some(
4370
+ (option) =>
4371
+ option.index === operand.index &&
4372
+ !["-g", "--glob", "--iglob"].some(
4373
+ (flagName) =>
4374
+ argv[operand.index - 1] === flagName || argv[operand.index]?.startsWith(flagName),
4375
+ ),
4376
+ )
4377
+ )
4378
+ operand.role = "names";
4379
+ }
4380
+ if (entry.name === "bun") {
4381
+ const commandIndex = positional[0];
4382
+ const command = commandIndex === undefined ? undefined : argv[commandIndex];
4383
+ if (commandIndex !== undefined && command === "exec") {
4384
+ const start = argv[commandIndex + 1] === "-c" ? commandIndex + 2 : commandIndex + 1;
4385
+ if (argv.length === start + 1) roles.push({ index: start, role: code("bash") });
4386
+ else issues.push(unknown("bun exec shell body"));
4387
+ } else if (command === "repl") {
4388
+ issues.push(unknown("interactive runtime code"));
4389
+ } else if (commandIndex !== undefined && command === "test") {
4390
+ for (const operand of roles)
4391
+ if (operand.index > commandIndex && (operand.role === "path" || operand.role === "script"))
4392
+ operand.role = "test-filter";
4393
+ } else if (commandIndex !== undefined && command === "run") {
4394
+ for (let index = commandIndex + 1; index < argv.length; index++)
4395
+ roles.push({ index, role: "script" });
4396
+ }
4397
+ const scriptIndex = command === "run" ? (commandIndex ?? 0) + 1 : commandIndex;
4398
+ const script = scriptIndex === undefined ? "" : (argv[scriptIndex] ?? "");
4399
+ const wrapperName =
4400
+ /^(?:agent-tools-(gh|db|k8s|vault|az|logs)|(gh|db|k8s|vault|az|logs)-tool)$/.exec(
4401
+ executableName(script),
4402
+ );
4403
+ if (wrapperName && scriptIndex !== undefined) {
4404
+ const projectTool = wrapperName[1] ?? wrapperName[2];
4405
+ const dataOptions: Readonly<Record<string, readonly string[]>> = {
4406
+ gh: ["--title", "--body", "--comment", "--head", "--base"],
4407
+ db: ["--sql"],
4408
+ k8s: ["--cmd"],
4409
+ az: ["--cmd"],
4410
+ logs: [],
4411
+ vault: [],
4412
+ };
4413
+ const options = dataOptions[projectTool ?? ""] ?? [];
4414
+ const fileOptions =
4415
+ projectTool === "gh" ? ["--body-file", "--comment-file", "--notes-file", "-F"] : [];
4416
+ for (let index = scriptIndex + 1; index < argv.length; index++) {
4417
+ const word = argv[index];
4418
+ const optionKey = word?.split("=")[0] ?? "";
4419
+ if (options.includes(optionKey) || fileOptions.includes(optionKey)) {
4420
+ const valueIndex = word?.includes("=") ? index : ++index;
4421
+ for (let n = roles.length - 1; n >= 0; n--)
4422
+ if (roles[n]?.index === valueIndex) roles.splice(n, 1);
4423
+ roles.push({
4424
+ index: valueIndex,
4425
+ role: fileOptions.includes(optionKey) ? "content" : data,
4426
+ ...(word?.includes("=") ? { literal: word.slice(word.indexOf("=") + 1) } : {}),
4427
+ });
4428
+ }
4429
+ }
4430
+ if (
4431
+ projectTool === "vault" &&
4432
+ argv[scriptIndex + 1] === "kv" &&
4433
+ argv[scriptIndex + 2] === "keys"
4434
+ ) {
4435
+ for (const operand of roles) if (operand.index > scriptIndex) operand.role = "names";
4436
+ }
4437
+ }
4438
+ if (command?.startsWith("-")) issues.push(unknown("Bun entry point begins with an option"));
4439
+ }
4440
+ if (inPlaceBackupSuffix !== null) {
4441
+ for (const operand of roles) if (operand.role === "path") operand.role = "write";
4442
+ if (sedEmptySuffix && !filterOption && positional[0] !== undefined)
4443
+ roles.push({ index: positional[0], role: "write" });
4444
+ }
4445
+ if (entry.semantics === "copy" && positional.length) {
4446
+ const destination = positional.at(-1);
4447
+ if (destination !== undefined) {
4448
+ const role = roles.find((item) => item.index === destination);
4449
+ if (role) roles.splice(roles.indexOf(role), 1);
4450
+ roles.push({ index: destination, role: "write" });
4451
+ }
4452
+ }
4453
+ stdout = mode.stdout ?? entry.stdout ?? "opaque";
4454
+ if (stdout === "stdin" && (entry.name !== "cat" || argv.length !== 1)) stdout = "opaque";
4455
+ if (
4456
+ stdout === "arguments" &&
4457
+ argv.slice(1).some((value) => value?.startsWith("-") || value?.includes("\\"))
4458
+ )
4459
+ stdout = "opaque";
4460
+ if (entry.semantics === "cd") {
4461
+ delta = "cd";
4462
+ if (positional.some((index) => argv[index] === "-"))
4463
+ issues.push(unknown("cd previous directory is not known"));
4464
+ cwdOperands.push({ index: positional[0] ?? 1 });
4465
+ }
4466
+
4467
+ if (
4468
+ entry.semantics === "ps" &&
4469
+ argv.slice(1).some((a, i) => {
4470
+ const operand = optionRoles.find((role) => role.index === i + 1);
4471
+ return operand
4472
+ ? /(?:^|[,=])(?:env|environ|environment)(?:[,=]|$)/i.test(operand.literal ?? a ?? "")
4473
+ : a !== undefined && /^(?:-?[A-Za-z]*e[A-Za-z]*|--?environ.*)$/.test(a);
4474
+ })
4475
+ )
4476
+ issues.push(source("process environments"));
4477
+ if (entry.semantics === "listing" || entry.semantics === "env-provider") {
4478
+ if (argv.some((a) => /^env:/i.test(a ?? "")))
4479
+ issues.push(source("PowerShell environment provider"));
4480
+ }
4481
+ if (entry.semantics === "herdr") {
4482
+ if (["tab", "window"].includes(argv[1] ?? "") && ["create", "split"].includes(argv[2] ?? "")) {
4483
+ const separator = argv.indexOf("--");
4484
+ if (separator >= 0) child(separator + 1);
4485
+ const commandIndex = argv.findIndex((value) => value === "--command");
4486
+ if (commandIndex >= 0) roles.push({ index: commandIndex + 1, role: code("bash") });
4487
+ }
4488
+
4489
+ if (argv[1] === "pane" && ["run", "send-keys", "send-text"].includes(argv[2] ?? ""))
4490
+ issues.push(unknown("Herdr execution in unknown terminal"));
4491
+ }
4492
+ if (entry.semantics === "python") {
4493
+ if (argv.some((value) => value?.startsWith("-W") || value?.startsWith("-X")))
4494
+ issues.push(unknown("Python startup module option"));
4495
+ const module = pythonModule;
4496
+ if (module !== null) {
4497
+ if (module.name === "py_compile") {
4498
+ roles.splice(
4499
+ 0,
4500
+ roles.length,
4501
+ ...argv
4502
+ .slice(module.index + 1)
4503
+ .map((_, n) => ({ index: module.index + 1 + n, role: path })),
4504
+ );
4505
+ } else if (
4506
+ !runtimeWrapper ||
4507
+ module.name === undefined ||
4508
+ [
4509
+ "timeit",
4510
+ "code",
4511
+ "pdb",
4512
+ "trace",
4513
+ "cProfile",
4514
+ "profile",
4515
+ "runpy",
4516
+ "idlelib",
4517
+ "doctest",
4518
+ "IPython",
4519
+ "bpython",
4520
+ "ptpython",
4521
+ "xonsh",
4522
+ "asyncio",
4523
+ "sqlite3",
4524
+ ].includes(module.name)
4525
+ )
4526
+ issues.push(unknown("Python module code"));
4527
+ } else if (
4528
+ !codeSeen &&
4529
+ !argv.includes("--version") &&
4530
+ !argv.includes("--help") &&
4531
+ (!positional.length || positional.some((i) => isStdinScript(argv[i] ?? "")))
4532
+ )
4533
+ stdinCode = "runtime";
4534
+ }
4535
+ if (
4536
+ entry.semantics === "runtime" &&
4537
+ ((!positional.length && !testRunner) || positional.some((i) => isStdinScript(argv[i] ?? ""))) &&
4538
+ !argv.includes("--version") &&
4539
+ !argv.includes("--help") &&
4540
+ !codeSeen
4541
+ )
4542
+ stdinCode = "runtime";
4543
+ if (entry.semantics === "shell" && !codeSeen && (shellStdin || !positional.length))
4544
+ stdinCode = "bash";
4545
+ if (entry.semantics === "joined-shell" || entry.semantics === "remote-shell") {
4546
+ const indexes = positional.slice(entry.semantics === "remote-shell" ? 1 : 0);
4547
+ const parts = indexes.map((i) => argv[i]);
4548
+ roles.splice(0, roles.length, ...optionRoles);
4549
+ const connectionOnly =
4550
+ entry.semantics === "remote-shell" &&
4551
+ positional.length === 1 &&
4552
+ argv[positional[0] ?? 0] !== undefined;
4553
+ if (!connectionOnly) {
4554
+ if (!parts.length || parts.some((part) => part === undefined))
4555
+ issues.push(unknown("joined shell command is not literal"));
4556
+ else roles.push({ index: indexes[0] ?? 0, role: code("bash"), literal: parts.join(" ") });
4557
+ }
4558
+ }
4559
+ if (entry.semantics === "eval") {
4560
+ roles.splice(0);
4561
+ issues.push(unknown("eval joins and reparses shell words"));
4562
+ }
4563
+ if (entry.semantics === "curl") {
4564
+ if (argv.some((a) => a?.includes("dev.azure.com")))
4565
+ issues.push(wrapper("curl (Azure DevOps)", "agent-tools-azdo"));
4566
+ const fileSelectors = [
4567
+ ...optionRoles.map((item) => ({ index: item.index, text: item.literal ?? argv[item.index] })),
4568
+ ...argv.slice(1).map((text, i) => ({ index: i + 1, text })),
4569
+ ];
4570
+ for (const selector of fileSelectors) {
4571
+ if (selector.text === undefined) {
4572
+ issues.push(unknown("curl operand is not literal"));
4573
+ continue;
4574
+ }
4575
+ const match = /(?:^|=)@([^;]+)|=<([^;]+)/.exec(selector.text);
4576
+ if (match)
4577
+ roles.push({ index: selector.index, role: path, literal: match[1] ?? match[2] ?? "" });
4578
+ if (/^file:/i.test(selector.text))
4579
+ roles.push({ index: selector.index, role: path, literal: selector.text });
4580
+ }
4581
+ }
4582
+ if (entry.name === "grep") {
4583
+ const types = new Map<string, readonly string[]>(Object.entries(rgTypes));
4584
+ for (const operand of optionRoles) {
4585
+ if (
4586
+ argv[operand.index - 1] === "--type-add" ||
4587
+ argv[operand.index]?.startsWith("--type-add=")
4588
+ ) {
4589
+ const spec = operand.literal ?? argv[operand.index];
4590
+ const colon = spec?.indexOf(":") ?? -1;
4591
+ if (spec === undefined || colon < 1 || spec.slice(colon + 1).startsWith("include:"))
4592
+ issues.push(unknown("unsupported rg type definition"));
4593
+ else {
4594
+ const key = spec.slice(0, colon);
4595
+ const pattern = spec.slice(colon + 1);
4596
+ types.set(key, [...(types.get(key) ?? []), pattern]);
4597
+ if (!argv.includes("--files"))
4598
+ roles.push({ index: operand.index, role: "path-pattern", literal: pattern });
4599
+ }
4600
+ }
4601
+ }
4602
+ for (const operand of roles)
4603
+ if (operand.role === "rg-type") {
4604
+ operand.role = data;
4605
+ const patterns = types.get(operand.literal ?? argv[operand.index] ?? "");
4606
+ if (!patterns) issues.push(unknown("unknown rg type"));
4607
+ }
4608
+ }
4609
+ if (entry.name === "grep" && !(name === "rg" && argv.includes("--files"))) {
4610
+ for (const operand of roles) {
4611
+ const word = argv[operand.index] ?? "";
4612
+ if (
4613
+ ["-g", "--glob", "--iglob", "--include"].includes(argv[operand.index - 1] ?? "") ||
4614
+ /^(?:--glob|--iglob|--include)=/.test(word) ||
4615
+ /^-g./.test(word)
4616
+ )
4617
+ operand.role =
4618
+ name !== "rg" &&
4619
+ (argv[operand.index - 1] === "--include" || word.startsWith("--include="))
4620
+ ? "search-filter"
4621
+ : "path-pattern";
4622
+ }
4623
+ }
4624
+ if (entry.name === "grep") {
4625
+ const recursive =
4626
+ name === "rg" ||
4627
+ argv.some((word) => /^-[^-]*[rR]/.test(word ?? "") || word === "--recursive");
4628
+ for (const operand of roles)
4629
+ if (operand.role === "path" && recursive && name !== "rg" && !optionRoles.includes(operand))
4630
+ operand.role = "search-filter";
4631
+ else if (
4632
+ operand.role === "path" &&
4633
+ (!recursive || /\.[^/]+$/.test(operand.literal ?? argv[operand.index] ?? ""))
4634
+ )
4635
+ operand.role = "content";
4636
+ }
4637
+ if (entry.name === "git" && mode === entry.subcommands?.grep) {
4638
+ const pathspecs = roles.filter((operand) => operand.role === path);
4639
+ if (
4640
+ argv.some((word) => word === "--no-index" || word === "--untracked") &&
4641
+ !pathspecs.some((operand) => {
4642
+ const value = operand.literal ?? argv[operand.index];
4643
+ return value !== undefined && value !== "." && !/[*?[{:]/.test(value);
4644
+ })
4645
+ )
4646
+ issues.push(unknown("git grep unrestricted content search"));
4647
+ for (const operand of pathspecs) {
4648
+ const value = operand.literal ?? argv[operand.index];
4649
+ if (/^:[!^]/.test(value ?? "")) {
4650
+ operand.role = data;
4651
+ continue;
4652
+ }
4653
+ if (value?.startsWith(":(")) {
4654
+ const magic = /^:\(([^)]+)\)(.*)$/.exec(value);
4655
+ if (
4656
+ !magic ||
4657
+ !(magic[1] ?? "")
4658
+ .split(",")
4659
+ .every((modifier) =>
4660
+ ["glob", "icase", "top", "literal", "exclude", "!", "^"].includes(modifier),
4661
+ )
4662
+ )
4663
+ issues.push(unknown("unsupported git pathspec magic"));
4664
+ else if (
4665
+ (magic[1] ?? "").split(",").some((modifier) => ["exclude", "!", "^"].includes(modifier))
4666
+ ) {
4667
+ operand.role = data;
4668
+ continue;
4669
+ } else operand.literal = magic[2] ?? "";
4670
+ }
4671
+ const pattern = operand.literal ?? value;
4672
+ if (pattern !== undefined && /(?:^|\/)\.[^/]*[*?[]/.test(pattern))
4673
+ operand.role = "path-pattern";
4674
+ else if (!argv.some((word) => word === "--untracked" || word === "--no-index"))
4675
+ operand.role = "search-filter";
4676
+ else if (pattern !== undefined && /[*?[{}]/.test(pattern)) operand.role = "path-pattern";
4677
+ else if (pattern !== undefined && /\.[^/]+$/.test(pattern)) operand.role = "content";
4678
+ }
4679
+ }
4680
+ if (entry.name === "git" && mode === entry.subcommands?.diff && argv.includes("--no-index"))
4681
+ for (const operand of roles) if (operand.role === path) operand.role = "content";
4682
+ const contentReader =
4683
+ mode.readsContent ??
4684
+ entry.readsContent ??
4685
+ (entry.semantics === "copy" &&
4686
+ !argv.some(
4687
+ (word) =>
4688
+ /^-[^-]*[rRa]/.test(word ?? "") || ["--recursive", "--archive"].includes(word ?? ""),
4689
+ ));
4690
+ if (contentReader)
4691
+ for (const operand of roles)
4692
+ if (
4693
+ operand.role === "path" &&
4694
+ (entry.semantics !== "git" ||
4695
+ !argv.includes("grep") ||
4696
+ /\.[^/]+$/.test(operand.literal ?? argv[operand.index] ?? "")) &&
4697
+ (entry.semantics !== "git" ||
4698
+ !["show", "cat-file"].some((command) => argv.includes(command)) ||
4699
+ (operand.literal ?? argv[operand.index] ?? "").includes(":"))
4700
+ )
4701
+ operand.role = "content";
4702
+ return result();
4703
+ }
4704
+
4705
+ export function analyzeCode(
4706
+ language: Exclude<Language, "bash" | "powershell">,
4707
+ text: string,
4708
+ ): readonly Issue[] {
4709
+ if (language === "runtime") return [unknown("inline runtime code has no analyzer")];
4710
+ if (language === "jq") {
4711
+ if (isPassiveJqFilter(text)) return [];
4712
+ if (/(?:\$ENV\b|\benv\b)/.test(text)) return [source("jq environment")];
4713
+ return [unknown("jq syntax outside passive grammar")];
4714
+ }
4715
+ if (language === "awk") {
4716
+ if (/\bENVIRON\b/.test(text)) return [source("awk environment")];
4717
+ return isPassiveAwkProgram(text) ? [] : [unknown("awk syntax outside passive grammar")];
4718
+ }
4719
+ return isPassiveSedProgram(text) ? [] : [unknown("sed syntax outside passive grammar")];
4720
+ }