@taliesinb/mash 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +275 -2
  3. package/bin/mash.ts +253 -0
  4. package/dist/bin/mash.d.ts +39 -0
  5. package/dist/bin/mash.d.ts.map +1 -0
  6. package/dist/bin/mash.js +281 -0
  7. package/dist/bin/mash.js.map +1 -0
  8. package/dist/src/analysis/analyze.d.ts +97 -0
  9. package/dist/src/analysis/analyze.d.ts.map +1 -0
  10. package/dist/src/analysis/analyze.js +1406 -0
  11. package/dist/src/analysis/analyze.js.map +1 -0
  12. package/dist/src/analysis/fs.d.ts +39 -0
  13. package/dist/src/analysis/fs.d.ts.map +1 -0
  14. package/dist/src/analysis/fs.js +117 -0
  15. package/dist/src/analysis/fs.js.map +1 -0
  16. package/dist/src/analysis/outputs.d.ts +16 -0
  17. package/dist/src/analysis/outputs.d.ts.map +1 -0
  18. package/dist/src/analysis/outputs.js +277 -0
  19. package/dist/src/analysis/outputs.js.map +1 -0
  20. package/dist/src/analysis/value.d.ts +76 -0
  21. package/dist/src/analysis/value.d.ts.map +1 -0
  22. package/dist/src/analysis/value.js +248 -0
  23. package/dist/src/analysis/value.js.map +1 -0
  24. package/dist/src/behaviors/defs/core.d.ts +15 -0
  25. package/dist/src/behaviors/defs/core.d.ts.map +1 -0
  26. package/dist/src/behaviors/defs/core.js +1084 -0
  27. package/dist/src/behaviors/defs/core.js.map +1 -0
  28. package/dist/src/behaviors/defs/devtools.d.ts +9 -0
  29. package/dist/src/behaviors/defs/devtools.d.ts.map +1 -0
  30. package/dist/src/behaviors/defs/devtools.js +432 -0
  31. package/dist/src/behaviors/defs/devtools.js.map +1 -0
  32. package/dist/src/behaviors/defs/macos.d.ts +12 -0
  33. package/dist/src/behaviors/defs/macos.d.ts.map +1 -0
  34. package/dist/src/behaviors/defs/macos.js +454 -0
  35. package/dist/src/behaviors/defs/macos.js.map +1 -0
  36. package/dist/src/behaviors/defs/tools.d.ts +9 -0
  37. package/dist/src/behaviors/defs/tools.d.ts.map +1 -0
  38. package/dist/src/behaviors/defs/tools.js +639 -0
  39. package/dist/src/behaviors/defs/tools.js.map +1 -0
  40. package/dist/src/behaviors/dsl.d.ts +95 -0
  41. package/dist/src/behaviors/dsl.d.ts.map +1 -0
  42. package/dist/src/behaviors/dsl.js +267 -0
  43. package/dist/src/behaviors/dsl.js.map +1 -0
  44. package/dist/src/behaviors/index.d.ts +12 -0
  45. package/dist/src/behaviors/index.d.ts.map +1 -0
  46. package/dist/src/behaviors/index.js +18 -0
  47. package/dist/src/behaviors/index.js.map +1 -0
  48. package/dist/src/behaviors/registry.d.ts +45 -0
  49. package/dist/src/behaviors/registry.d.ts.map +1 -0
  50. package/dist/src/behaviors/registry.js +266 -0
  51. package/dist/src/behaviors/registry.js.map +1 -0
  52. package/dist/src/behaviors/roles.d.ts +29 -0
  53. package/dist/src/behaviors/roles.d.ts.map +1 -0
  54. package/dist/src/behaviors/roles.js +198 -0
  55. package/dist/src/behaviors/roles.js.map +1 -0
  56. package/dist/src/behaviors/types.d.ts +120 -0
  57. package/dist/src/behaviors/types.d.ts.map +1 -0
  58. package/dist/src/behaviors/types.js +12 -0
  59. package/dist/src/behaviors/types.js.map +1 -0
  60. package/dist/src/index.d.ts +27 -0
  61. package/dist/src/index.d.ts.map +1 -0
  62. package/dist/src/index.js +24 -0
  63. package/dist/src/index.js.map +1 -0
  64. package/dist/src/integrations/deepsea.d.ts +121 -0
  65. package/dist/src/integrations/deepsea.d.ts.map +1 -0
  66. package/dist/src/integrations/deepsea.js +199 -0
  67. package/dist/src/integrations/deepsea.js.map +1 -0
  68. package/dist/src/machine.d.ts +139 -0
  69. package/dist/src/machine.d.ts.map +1 -0
  70. package/dist/src/machine.js +400 -0
  71. package/dist/src/machine.js.map +1 -0
  72. package/dist/src/parser/ast.d.ts +369 -0
  73. package/dist/src/parser/ast.d.ts.map +1 -0
  74. package/dist/src/parser/ast.js +17 -0
  75. package/dist/src/parser/ast.js.map +1 -0
  76. package/dist/src/parser/parser.d.ts +48 -0
  77. package/dist/src/parser/parser.d.ts.map +1 -0
  78. package/dist/src/parser/parser.js +1950 -0
  79. package/dist/src/parser/parser.js.map +1 -0
  80. package/dist/src/policy/effects.d.ts +86 -0
  81. package/dist/src/policy/effects.d.ts.map +1 -0
  82. package/dist/src/policy/effects.js +53 -0
  83. package/dist/src/policy/effects.js.map +1 -0
  84. package/dist/src/policy/policy.d.ts +130 -0
  85. package/dist/src/policy/policy.d.ts.map +1 -0
  86. package/dist/src/policy/policy.js +532 -0
  87. package/dist/src/policy/policy.js.map +1 -0
  88. package/dist/src/printer/doc.d.ts +151 -0
  89. package/dist/src/printer/doc.d.ts.map +1 -0
  90. package/dist/src/printer/doc.js +409 -0
  91. package/dist/src/printer/doc.js.map +1 -0
  92. package/dist/src/printer/highlight.d.ts +53 -0
  93. package/dist/src/printer/highlight.d.ts.map +1 -0
  94. package/dist/src/printer/highlight.js +339 -0
  95. package/dist/src/printer/highlight.js.map +1 -0
  96. package/dist/src/printer/html.d.ts +72 -0
  97. package/dist/src/printer/html.d.ts.map +1 -0
  98. package/dist/src/printer/html.js +423 -0
  99. package/dist/src/printer/html.js.map +1 -0
  100. package/dist/src/printer/print.d.ts +63 -0
  101. package/dist/src/printer/print.d.ts.map +1 -0
  102. package/dist/src/printer/print.js +484 -0
  103. package/dist/src/printer/print.js.map +1 -0
  104. package/dist/src/printer/summary.d.ts +50 -0
  105. package/dist/src/printer/summary.d.ts.map +1 -0
  106. package/dist/src/printer/summary.js +621 -0
  107. package/dist/src/printer/summary.js.map +1 -0
  108. package/dist/src/trace/events.d.ts +183 -0
  109. package/dist/src/trace/events.d.ts.map +1 -0
  110. package/dist/src/trace/events.js +9 -0
  111. package/dist/src/trace/events.js.map +1 -0
  112. package/dist/src/trace/progress.d.ts +58 -0
  113. package/dist/src/trace/progress.d.ts.map +1 -0
  114. package/dist/src/trace/progress.js +83 -0
  115. package/dist/src/trace/progress.js.map +1 -0
  116. package/dist/src/vm/arith.d.ts +29 -0
  117. package/dist/src/vm/arith.d.ts.map +1 -0
  118. package/dist/src/vm/arith.js +258 -0
  119. package/dist/src/vm/arith.js.map +1 -0
  120. package/dist/src/vm/builtins.d.ts +31 -0
  121. package/dist/src/vm/builtins.d.ts.map +1 -0
  122. package/dist/src/vm/builtins.js +1773 -0
  123. package/dist/src/vm/builtins.js.map +1 -0
  124. package/dist/src/vm/emulate.d.ts +75 -0
  125. package/dist/src/vm/emulate.d.ts.map +1 -0
  126. package/dist/src/vm/emulate.js +380 -0
  127. package/dist/src/vm/emulate.js.map +1 -0
  128. package/dist/src/vm/expand.d.ts +68 -0
  129. package/dist/src/vm/expand.d.ts.map +1 -0
  130. package/dist/src/vm/expand.js +744 -0
  131. package/dist/src/vm/expand.js.map +1 -0
  132. package/dist/src/vm/external.d.ts +34 -0
  133. package/dist/src/vm/external.d.ts.map +1 -0
  134. package/dist/src/vm/external.js +228 -0
  135. package/dist/src/vm/external.js.map +1 -0
  136. package/dist/src/vm/fifo.d.ts +66 -0
  137. package/dist/src/vm/fifo.d.ts.map +1 -0
  138. package/dist/src/vm/fifo.js +232 -0
  139. package/dist/src/vm/fifo.js.map +1 -0
  140. package/dist/src/vm/interp.d.ts +209 -0
  141. package/dist/src/vm/interp.d.ts.map +1 -0
  142. package/dist/src/vm/interp.js +1641 -0
  143. package/dist/src/vm/interp.js.map +1 -0
  144. package/dist/src/vm/io.d.ts +180 -0
  145. package/dist/src/vm/io.d.ts.map +1 -0
  146. package/dist/src/vm/io.js +502 -0
  147. package/dist/src/vm/io.js.map +1 -0
  148. package/dist/src/vm/pattern.d.ts +46 -0
  149. package/dist/src/vm/pattern.d.ts.map +1 -0
  150. package/dist/src/vm/pattern.js +498 -0
  151. package/dist/src/vm/pattern.js.map +1 -0
  152. package/dist/src/vm/proc.d.ts +94 -0
  153. package/dist/src/vm/proc.d.ts.map +1 -0
  154. package/dist/src/vm/proc.js +130 -0
  155. package/dist/src/vm/proc.js.map +1 -0
  156. package/dist/src/vm/state.d.ts +104 -0
  157. package/dist/src/vm/state.d.ts.map +1 -0
  158. package/dist/src/vm/state.js +233 -0
  159. package/dist/src/vm/state.js.map +1 -0
  160. package/dist/src/vm/test.d.ts +11 -0
  161. package/dist/src/vm/test.d.ts.map +1 -0
  162. package/dist/src/vm/test.js +277 -0
  163. package/dist/src/vm/test.js.map +1 -0
  164. package/package.json +63 -4
  165. package/src/analysis/analyze.ts +1395 -0
  166. package/src/analysis/fs.ts +121 -0
  167. package/src/analysis/outputs.ts +242 -0
  168. package/src/analysis/value.ts +264 -0
  169. package/src/behaviors/defs/core.ts +970 -0
  170. package/src/behaviors/defs/devtools.ts +400 -0
  171. package/src/behaviors/defs/macos.ts +426 -0
  172. package/src/behaviors/defs/tools.ts +596 -0
  173. package/src/behaviors/dsl.ts +293 -0
  174. package/src/behaviors/index.ts +19 -0
  175. package/src/behaviors/registry.ts +261 -0
  176. package/src/behaviors/roles.ts +187 -0
  177. package/src/behaviors/types.ts +127 -0
  178. package/src/index.ts +27 -0
  179. package/src/integrations/deepsea.ts +259 -0
  180. package/src/machine.ts +483 -0
  181. package/src/parser/ast.ts +472 -0
  182. package/src/parser/parser.ts +1826 -0
  183. package/src/policy/effects.ts +102 -0
  184. package/src/policy/policy.ts +578 -0
  185. package/src/printer/doc.ts +454 -0
  186. package/src/printer/highlight.ts +283 -0
  187. package/src/printer/html.ts +441 -0
  188. package/src/printer/print.ts +528 -0
  189. package/src/printer/summary.ts +601 -0
  190. package/src/trace/events.ts +102 -0
  191. package/src/trace/progress.ts +119 -0
  192. package/src/vm/arith.ts +236 -0
  193. package/src/vm/builtins.ts +1590 -0
  194. package/src/vm/emulate.ts +337 -0
  195. package/src/vm/expand.ts +721 -0
  196. package/src/vm/external.ts +265 -0
  197. package/src/vm/fifo.ts +226 -0
  198. package/src/vm/interp.ts +1623 -0
  199. package/src/vm/io.ts +570 -0
  200. package/src/vm/pattern.ts +477 -0
  201. package/src/vm/proc.ts +147 -0
  202. package/src/vm/state.ts +267 -0
  203. package/src/vm/test.ts +253 -0
@@ -0,0 +1,639 @@
1
+ /**
2
+ * Behavior definitions for common command-line tools beyond coreutils:
3
+ * databases, document and media tools, downloaders, crypto, and local
4
+ * service CLIs. Most are table definitions (see ../dsl.ts); tools whose
5
+ * argument syntax the table cannot express (ffmpeg, sqlite3) are written out.
6
+ */
7
+ import { concat, concrete, hole, known, knownPrefix } from "../../analysis/value.js";
8
+ import { define } from "../dsl.js";
9
+ const args = (inv) => inv.argv.slice(1);
10
+ const isDash = (v) => concrete(v) === '-';
11
+ const isUrl = (v) => /^[a-z][a-z0-9+.-]*:\/\//i.test(knownPrefix(v));
12
+ /** Accepts any flags and operands; no effects of its own (query subcommands). */
13
+ const QUERY = { anyFlags: true, operands: 'arg*', stdin: false };
14
+ /** Changes state the definition cannot describe. */
15
+ const opaque = (detail) => ({ anyFlags: true, operands: 'arg*', opaque: detail, stdin: 'maybe' });
16
+ /** The directory part of a path value (`dir/` for `dir/x`), or undefined. */
17
+ function dirOf(p) {
18
+ const s = concrete(p);
19
+ if (s === undefined)
20
+ return undefined;
21
+ const i = s.lastIndexOf('/');
22
+ return known(i <= 0 ? (i === 0 ? '/' : '.') : s.slice(0, i));
23
+ }
24
+ /** `x.typ` → `x.<ext>`: a sibling with another extension (unknown if the name is). */
25
+ function sibling(p, ext) {
26
+ const s = concrete(p);
27
+ if (s !== undefined)
28
+ return known(s.replace(/\.[^./]*$/, '') + ext);
29
+ const d = dirOf(p);
30
+ return d ? concat(d, known('/'), hole('filename', 'output name')) : hole('path', 'output file');
31
+ }
32
+ // ---------------------------------------------------------------------------
33
+ // sqlite3
34
+ // ---------------------------------------------------------------------------
35
+ /** Dot commands that only print or set display options. */
36
+ const SQLITE_SAFE_DOT = /^\.(tables|schema|indexes|indices|headers?|mode|width|timer|timeout|nullvalue|separator|print|quit|exit|help|databases|changes|eqp|stats|bail|echo|show|dbinfo|fullschema|lint|selftrace|parameter|limits?|explain|columns|prompt|dump|version|vfsinfo|vfsname|vfslist|sha3sum|binary|crnl|expert|scanstats)\b/;
37
+ function sqlTexts(v) {
38
+ const out = [];
39
+ for (const alt of v.alts) {
40
+ let s = '';
41
+ for (const seg of alt) {
42
+ if (typeof seg === 'string')
43
+ s += seg;
44
+ else if (seg.type === 'int')
45
+ s += '0';
46
+ // a string inside quotes can only be data if it has no quote of its own: not knowable
47
+ else
48
+ return undefined;
49
+ }
50
+ out.push(s);
51
+ }
52
+ return out;
53
+ }
54
+ const SQL_MODIFIES = /\b(insert|update|delete|replace|upsert|create|drop|alter|vacuum|reindex|analyze|begin|commit|savepoint|release|restore)\b|\bpragma\s+\w+(\.\w+)?\s*=|^\s*\.(restore|import)\b/im;
55
+ const unquote = (w) => w.replace(/^(['"])(.*)\1$/, '$2');
56
+ /**
57
+ * The files this SQL (with dot commands) touches beyond the database, or
58
+ * undefined when it can reach outside what can be described: other databases,
59
+ * extensions, the shell's file functions, `.read` scripts, pipes to commands.
60
+ */
61
+ function sqlFiles(text) {
62
+ const files = [];
63
+ for (const line of text.split('\n')) {
64
+ const t = line.trim();
65
+ if (!t.startsWith('.') || SQLITE_SAFE_DOT.test(t))
66
+ continue;
67
+ const [cmd, ...words] = t.split(/\s+/).map(unquote);
68
+ const ops = words.filter((w) => !w.startsWith('-'));
69
+ const last = ops[ops.length - 1];
70
+ if (/^\.(backup|save|output|once)$/.test(cmd)) {
71
+ if (cmd === '.backup' || cmd === '.save' ? !last : false)
72
+ return undefined;
73
+ if (last === undefined)
74
+ continue; // `.output` alone: back to stdout
75
+ if (last.startsWith('|') || words.some((w) => /^-[ex]$/.test(w)))
76
+ return undefined; // to a command or an editor
77
+ files.push({ kind: 'write', file: last });
78
+ }
79
+ else if (cmd === '.import' && ops[0] && !ops[0].startsWith('|'))
80
+ files.push({ kind: 'read', file: ops[0] });
81
+ else
82
+ return undefined;
83
+ }
84
+ if (/\battach\b|\bvacuum\s+into\b|\b(load_extension|writefile|readfile|edit|fsdir|zipfile|sqlar_compress|sqlar_uncompress|lsmode)\s*\(/i.test(text.split('\n').filter((l) => !l.trim().startsWith('.')).join('\n')))
85
+ return undefined;
86
+ return files;
87
+ }
88
+ const sqlite3 = {
89
+ names: ['sqlite3', 'sqlite'],
90
+ summary: 'SQLite shell (sqlite3 [opts] DB [SQL...])',
91
+ analyze(inv, h) {
92
+ const p = h.parseArgs(args(inv), {
93
+ longSingle: true,
94
+ long: {
95
+ readonly: false, header: false, noheader: false, column: false, csv: false, json: false, line: false, list: false, box: false, markdown: false, table: false, html: false, tabs: false, quote: false, ascii: false,
96
+ batch: false, bail: false, echo: false, interactive: false, safe: false, nofollow: false, stats: false, version: false, help: false, memtrace: false, unsafe_testing: false,
97
+ separator: true, newline: true, nullvalue: true, cmd: true, init: true, vfs: true, mmap: true, append: false, deserialize: false, maxsize: true, nonce: true, A: true, zip: false,
98
+ },
99
+ });
100
+ if (p.flags.has('version') || p.flags.has('help'))
101
+ return { effects: [], readsStdin: false };
102
+ const [db, ...sql] = p.operands;
103
+ const effects = [];
104
+ let complete = p.unknownFlags.length === 0 && !p.flags.has('A') && !p.flags.has('zip');
105
+ const init = p.flags.get('init');
106
+ if (init && init !== true) {
107
+ effects.push({ kind: 'read', path: h.resolve(init) });
108
+ complete = false;
109
+ }
110
+ const programs = [...sql, ...(p.multi.get('cmd') ?? [])];
111
+ for (const s of programs) {
112
+ h.role(s, 'code');
113
+ for (const text of sqlTexts(s) ?? [undefined]) {
114
+ const files = text === undefined ? undefined : sqlFiles(text);
115
+ if (!files)
116
+ complete = false;
117
+ for (const f of files ?? [])
118
+ effects.push({ kind: f.kind, path: h.resolve(known(f.file)) });
119
+ }
120
+ }
121
+ const name = db === undefined ? '' : concrete(db);
122
+ if (db !== undefined && name !== '' && name !== ':memory:') {
123
+ if (knownPrefix(db).startsWith('file:'))
124
+ complete = false;
125
+ const path = h.resolve(db);
126
+ effects.push({ kind: 'read', path });
127
+ if (!p.flags.has('readonly')) {
128
+ // statements that change the database write it; unknown or stdin SQL may
129
+ const texts = programs.map((v) => sqlTexts(v));
130
+ const modifies = texts.some((t) => t?.some((x) => SQL_MODIFIES.test(x)));
131
+ const unknown = texts.some((t) => t === undefined) || sql.length === 0;
132
+ if (modifies || unknown)
133
+ effects.push({ kind: 'write', path, ...(modifies ? {} : { maybe: true }) });
134
+ // even reads may create the -wal/-shm/-journal siblings
135
+ effects.push({ kind: 'write', path: { alts: ['-wal', '-shm', '-journal'].flatMap((sfx) => path.alts.map((a) => [...a, sfx])) }, maybe: true });
136
+ }
137
+ }
138
+ // no SQL arguments: statements come from stdin
139
+ if (sql.length === 0) {
140
+ if (programs.length === 0)
141
+ complete = false;
142
+ return { effects, readsStdin: true, complete };
143
+ }
144
+ return { effects, readsStdin: false, complete };
145
+ },
146
+ };
147
+ // ---------------------------------------------------------------------------
148
+ // typst
149
+ // ---------------------------------------------------------------------------
150
+ const TYPST_GLOBAL = { short: 'hV', long: { help: false, version: false, color: true, cert: true } };
151
+ const TYPST_WORLD = {
152
+ root: true, input: true, 'font-path': true, 'ignore-system-fonts': false, 'ignore-embedded-fonts': false, 'package-path': true, 'package-cache-path': true,
153
+ 'creation-timestamp': true, features: true, 'diagnostic-format': true, jobs: '=j', format: '=f', ppi: true, pages: true, 'pdf-standard': true, 'no-pdf-tags': false,
154
+ 'make-deps': true, deps: true, 'deps-format': true, open: true, timings: true, field: true, one: false, target: true, pretty: false, 'no-serve': false, 'no-reload': false, port: true,
155
+ };
156
+ /** typst compile/watch/query: reads the project (input's directory or --root), may fetch packages. */
157
+ function typstCompile(withOutput) {
158
+ return {
159
+ spec: { shortWithArg: 'jf', long: TYPST_WORLD },
160
+ operands: withOutput ? 'read write?' : 'read pattern?',
161
+ flags: { 'font-path': 'read:dir', 'make-deps': 'write', deps: 'write', timings: 'write' },
162
+ extra: (r, { inv, h, p, operands }) => {
163
+ const effects = [...r.effects];
164
+ const [input, output] = operands;
165
+ const rootFlag = p.flags.get('root');
166
+ const root = rootFlag && rootFlag !== true ? h.resolve(rootFlag) : input && !isDash(input) ? dirOf(h.resolve(input)) : inv.cwd;
167
+ if (root)
168
+ effects.push({ kind: 'read', path: root, recursive: true });
169
+ if (withOutput && input && !output && !isDash(input)) {
170
+ const fmt = concrete(p.flags.get('f') ?? known('pdf')) ?? 'pdf';
171
+ effects.push({ kind: 'write', path: sibling(h.resolve(input), '.' + fmt) });
172
+ }
173
+ // `@preview` packages are downloaded into the cache on first use
174
+ effects.push({ kind: 'network', detail: 'typst packages', maybe: true });
175
+ const cache = p.flags.get('package-cache-path');
176
+ effects.push(cache && cache !== true
177
+ ? { kind: 'write', path: h.resolve(cache), recursive: true, maybe: true }
178
+ : { kind: 'write', path: concat(h.home() ?? known('~'), known(process.platform === 'darwin' ? '/Library/Caches/typst' : '/.cache/typst')), recursive: true, maybe: true });
179
+ const complete = r.complete !== false && !p.flags.has('open');
180
+ return { ...r, effects, complete, readsStdin: input !== undefined && isDash(input) };
181
+ },
182
+ };
183
+ }
184
+ const typst = define({
185
+ names: ['typst'], summary: 'typesetting', spec: TYPST_GLOBAL,
186
+ subcommands: {
187
+ 'compile|c': typstCompile(true),
188
+ 'watch|w': typstCompile(true),
189
+ 'query|q': { ...typstCompile(false), stdout: { type: 'string' } },
190
+ fonts: { spec: { long: { 'font-path': true, 'ignore-system-fonts': false, 'ignore-embedded-fonts': false, variants: false } }, flags: { 'font-path': 'read:dir' }, stdin: false },
191
+ 'init|update': opaque('typst downloads templates or updates itself'),
192
+ help: QUERY,
193
+ },
194
+ });
195
+ // ---------------------------------------------------------------------------
196
+ // ffmpeg / ffprobe
197
+ // ---------------------------------------------------------------------------
198
+ const FFMPEG_BOOL = new Set([
199
+ '-y', '-n', '-nostdin', '-stdin', '-hide_banner', '-stats', '-nostats', '-an', '-vn', '-sn', '-dn', '-shortest', '-re', '-benchmark', '-benchmark_all', '-copyts', '-start_at_zero',
200
+ '-accurate_seek', '-noaccurate_seek', '-autorotate', '-noautorotate', '-ignore_unknown', '-copy_unknown', '-dump', '-hex', '-xerror', '-bitexact', '-debug_ts', '-version', '-h', '-help',
201
+ '-formats', '-codecs', '-encoders', '-decoders', '-filters', '-pix_fmts', '-layouts', '-sample_fmts', '-protocols', '-muxers', '-demuxers', '-devices', '-bsfs', '-buildconf', '-hwaccels',
202
+ '-show_format', '-show_streams', '-show_packets', '-show_frames', '-show_chapters', '-show_programs', '-show_error', '-show_data', '-show_private_data', '-show_program_version',
203
+ '-show_library_versions', '-show_versions', '-show_pixel_formats', '-count_frames', '-count_packets', '-pretty', '-unit', '-prefix', '-byte_binary_prefix', '-sexagesimal', '-show_optional_fields',
204
+ '-shortest_buf_duration', '-dts_delta_threshold', '-vsync', '-fix_sub_duration', '-apad', '-nostdin',
205
+ ]);
206
+ /** Options whose value names a file read or written. */
207
+ const FFMPEG_READS = new Set(['-filter_complex_script', '-filter_script', '-attach', '-i']);
208
+ const FFMPEG_WRITES = new Set(['-vstats_file', '-passlogfile', '-progress', '-o']);
209
+ const FFMPEG_PATH_VALUE = new Set([...FFMPEG_READS, ...FFMPEG_WRITES, '-dump_attachment']);
210
+ const ffmpeg = {
211
+ names: ['ffmpeg', 'ffprobe', 'ffplay'],
212
+ summary: 'audio/video conversion',
213
+ analyze(inv, h) {
214
+ const a = args(inv);
215
+ const probe = inv.name !== 'ffmpeg';
216
+ const effects = [];
217
+ const inputs = [];
218
+ const outputs = [];
219
+ let complete = true;
220
+ let nostdin = probe;
221
+ let stdinInput = false;
222
+ for (let i = 0; i < a.length; i++) {
223
+ const s = concrete(a[i]);
224
+ if (s === undefined) {
225
+ // an unknown word that may be an option (and then perhaps take a value): not known
226
+ if (knownPrefix(a[i]).startsWith('-'))
227
+ complete = false;
228
+ else
229
+ (probe ? inputs : outputs).push(a[i]);
230
+ continue;
231
+ }
232
+ if (s === '-' || !s.startsWith('-')) {
233
+ ;
234
+ (probe ? inputs : outputs).push(a[i]);
235
+ continue;
236
+ }
237
+ if (s === '-nostdin')
238
+ nostdin = true;
239
+ if (FFMPEG_BOOL.has(s) || (/^-(no)?(show|count)_/.test(s) && s !== '-show_entries' && s !== '-show_optional_fields'))
240
+ continue;
241
+ if (s === '-report') {
242
+ effects.push({ kind: 'write', path: concat(inv.cwd, known('/'), hole('filename', 'ffmpeg report')) });
243
+ continue;
244
+ }
245
+ const v = a[++i];
246
+ if (v === undefined)
247
+ break;
248
+ const name = s.replace(/:.*$/, '');
249
+ // a path-like value for an option not known to take one: maybe a boolean option we do not know, then an output file
250
+ if (!FFMPEG_PATH_VALUE.has(name) && /\/|\.[A-Za-z0-9]{2,4}$/.test(concrete(v) ?? '') && !/^-(metadata|vf|af|filter|filter_complex|lavfi|map|ss|t|to|sseof|f|c|codec|tag)$/.test(name))
251
+ complete = false;
252
+ if (name === '-i')
253
+ inputs.push(v);
254
+ else if (FFMPEG_READS.has(name))
255
+ effects.push({ kind: 'read', path: h.resolve(v) });
256
+ else if (FFMPEG_WRITES.has(name)) {
257
+ if (!isDash(v) && !isUrl(v) && concrete(v) !== 'pipe:1')
258
+ effects.push({ kind: 'write', path: h.resolve(v) });
259
+ }
260
+ else if (/^-(vf|af|filter|filter_complex|lavfi)$/.test(name)) {
261
+ h.role(v, 'code');
262
+ // filters can open (movie=, subtitles=) or write (file=, stats_file=) files
263
+ const t = concrete(v);
264
+ if (t === undefined || /\b(movie|amovie|subtitles|ass|lut3d|lut1d|haldclut|sendcmd|asendcmd|zmq|azmq|file|filename|stats_file|result|f|ocr)\s*=/.test(t) || /\bf=/.test(t))
265
+ complete = false;
266
+ }
267
+ else if (name === '-f' && /^(concat|lavfi|tee|segment|hls|dash|image2pipe|fifo)$/.test(concrete(v) ?? '')) {
268
+ // inputs listing other files, filter-graph inputs, several outputs
269
+ if (!/^(segment|hls|dash)$/.test(concrete(v)))
270
+ complete = false;
271
+ }
272
+ }
273
+ for (const v of inputs) {
274
+ const t = concrete(v);
275
+ if (t === '-' || t === 'pipe:' || t === 'pipe:0')
276
+ stdinInput = true;
277
+ else if (isUrl(v))
278
+ effects.push({ kind: 'network', target: v, detail: inv.name });
279
+ else if (t !== undefined && /^[a-z]+:/.test(t) && !/^file:/.test(t))
280
+ complete = false; // concat:, subfile:, … protocols
281
+ else
282
+ effects.push({ kind: 'read', path: h.resolve(v) });
283
+ }
284
+ for (const v of outputs) {
285
+ const t = concrete(v);
286
+ if (t === '-' || t === 'pipe:' || t === 'pipe:1')
287
+ continue;
288
+ if (isUrl(v))
289
+ effects.push({ kind: 'network', target: v, detail: inv.name });
290
+ else
291
+ effects.push({ kind: 'write', path: h.resolve(v) });
292
+ }
293
+ // ffmpeg reads keyboard commands from stdin unless -nostdin
294
+ return { effects, readsStdin: stdinInput || (nostdin ? false : 'maybe'), complete };
295
+ },
296
+ };
297
+ // ---------------------------------------------------------------------------
298
+ // yt-dlp
299
+ // ---------------------------------------------------------------------------
300
+ const ytdlp = define({
301
+ names: ['yt-dlp', 'youtube-dl'], summary: 'download videos',
302
+ spec: {
303
+ short: 'FqvsjJgeixwciUN', shortWithArg: 'foPNraRIS',
304
+ long: {
305
+ format: '=f', 'list-formats': '=F', output: '=o', paths: '=P', 'concurrent-fragments': '=N', quiet: '=q', verbose: '=v', 'no-progress': false, progress: false, 'no-part': false,
306
+ 'cookies-from-browser': true, cookies: true, 'extractor-args': true, 'skip-download': false, print: true, 'print-to-file': 2, 'flat-playlist': false, 'dump-json': '=j', 'dump-single-json': '=J',
307
+ simulate: '=s', 'no-simulate': false, version: false, help: false, 'get-url': '=g', 'get-title': '=e', 'extract-audio': '=x', 'audio-format': true, 'audio-quality': true, 'merge-output-format': true,
308
+ 'remux-video': true, 'recode-video': true, 'write-subs': false, 'write-auto-subs': false, 'sub-langs': true, 'sub-format': true, 'embed-subs': false, 'write-thumbnail': false, 'embed-thumbnail': false,
309
+ 'embed-metadata': false, 'no-playlist': false, 'yes-playlist': false, 'playlist-items': '=I', 'limit-rate': '=r', retries: '=R', 'batch-file': '=a', 'no-warnings': false, 'no-overwrites': '=w',
310
+ continue: '=c', newline: false, 'restrict-filenames': false, impersonate: true, 'user-agent': true, referer: true, 'add-header': true, 'socket-timeout': true, 'sleep-interval': true,
311
+ 'ignore-errors': '=i', 'abort-on-error': false, 'download-sections': true, 'force-overwrites': false, 'no-mtime': false, 'write-info-json': false, 'write-description': false, 'list-subs': false,
312
+ 'list-thumbnails': false, 'get-filename': false, 'no-cache-dir': false, 'cache-dir': true, proxy: true, 'geo-bypass': false, 'match-filters': true, 'match-filter': true, 'download-archive': true,
313
+ 'js-runtimes': true, 'remote-components': true, 'format-sort': '=S', 'no-color': false, 'max-downloads': true, 'min-filesize': true, 'max-filesize': true, 'date': true, 'dateafter': true, 'datebefore': true,
314
+ 'exec': true, 'update': '=U', 'external-downloader': true, 'downloader': true, 'postprocessor-args': true, 'ppa': true, 'ffmpeg-location': true, 'split-chapters': false, 'trim-filenames': true, 'windows-filenames': false,
315
+ },
316
+ },
317
+ operands: 'url*', stdin: false,
318
+ flags: { a: 'read', cookies: 'read', 'download-archive': 'append', 'print-to-file': 'append', P: 'write:dir' },
319
+ extra: (r, { inv, h, p }) => {
320
+ const effects = [...r.effects];
321
+ const noDownload = ['F', 'j', 'J', 's', 'g', 'e', 'skip-download', 'list-subs', 'list-thumbnails', 'get-filename', 'version', 'help', 'print'].some((k) => p.flags.has(k)) && !p.flags.has('no-simulate');
322
+ if (p.flags.has('version') || p.flags.has('help'))
323
+ return { effects: [], readsStdin: false };
324
+ if (!noDownload) {
325
+ const o = p.flags.get('o');
326
+ const P = p.flags.get('P');
327
+ const base = P && P !== true ? h.resolve(P) : inv.cwd;
328
+ // the output template (and the merged/temporary files next to it); a template may add directories
329
+ const out = o && o !== true && !isDash(o) ? (knownPrefix(o).startsWith('/') ? o : concat(base, known('/'), o)) : concat(base, known('/'), hole('filename', 'video title'));
330
+ if (!(o && o !== true && isDash(o)))
331
+ effects.push({ kind: 'write', path: out });
332
+ }
333
+ const cookies = p.flags.get('cookies');
334
+ if (cookies && cookies !== true)
335
+ effects.push({ kind: 'write', path: h.resolve(cookies), maybe: true });
336
+ // the browser's profile (cookie store)
337
+ if (p.flags.has('cookies-from-browser'))
338
+ effects.push({ kind: 'read', path: concat(h.home() ?? known('~'), known(process.platform === 'darwin' ? '/Library' : '/.config')), recursive: true, maybe: true });
339
+ if (!p.flags.has('no-cache-dir'))
340
+ effects.push({ kind: 'write', path: concat(h.home() ?? known('~'), known('/.cache/yt-dlp')), recursive: true, maybe: true });
341
+ // --exec runs commands; --update replaces the program; external downloaders run other programs
342
+ const complete = r.complete !== false && !['exec', 'U', 'external-downloader', 'downloader', 'postprocessor-args', 'ppa'].some((k) => p.flags.has(k));
343
+ // URLs from a batch file (or none given): still network
344
+ if (!effects.some((e) => e.kind === 'network'))
345
+ effects.unshift({ kind: 'network', detail: inv.name });
346
+ return { ...r, effects, complete, readsStdin: p.flags.has('a') && isDash(p.flags.get('a')) };
347
+ },
348
+ });
349
+ // ---------------------------------------------------------------------------
350
+ // PDF tools (poppler), ImageMagick identify
351
+ // ---------------------------------------------------------------------------
352
+ const POPPLER_SHORT = { short: 'qvh', shortWithArg: 'flrxyWH' };
353
+ const POPPLER_LONG = { opw: true, upw: true, q: false, v: false, h: false, help: false, enc: true, f: true, l: true, r: true, x: true, y: true, W: true, H: true, cropbox: false, nodiag: false };
354
+ const pdftools = [
355
+ define({
356
+ names: ['pdftotext'], summary: 'PDF to text (pdftotext in.pdf [out.txt|-])',
357
+ spec: { ...POPPLER_SHORT, longSingle: true, long: { ...POPPLER_LONG, layout: false, fixed: true, raw: false, htmlmeta: false, bbox: false, 'bbox-layout': false, tsv: false, eol: true, nopgbrk: false, colspacing: true } },
358
+ operands: 'read write?', stdin: false,
359
+ extra: (r, { h, operands }) => {
360
+ const [input, output] = operands;
361
+ if (output && isDash(output))
362
+ return { ...r, effects: r.effects.filter((e) => e.kind !== 'write') };
363
+ if (input && !output && !isDash(input))
364
+ return { ...r, effects: [...r.effects, { kind: 'write', path: sibling(h.resolve(input), '.txt') }] };
365
+ return r;
366
+ },
367
+ }),
368
+ define({
369
+ names: ['pdfinfo', 'pdffonts', 'pdfsig', 'pdfdetach'], summary: 'inspect PDFs',
370
+ spec: { ...POPPLER_SHORT, longSingle: true, long: { ...POPPLER_LONG, box: false, meta: false, js: false, struct: false, 'struct-text': false, rawdates: false, isodates: false, dests: false, url: false, listenc: false, subst: false, list: false, nodump: false } },
371
+ operands: 'read*', stdin: false,
372
+ when: { saveall: { opaque: 'pdfdetach writes attachments' }, save: { opaque: 'pdfdetach writes attachments' } },
373
+ }),
374
+ define({
375
+ names: ['pdftoppm', 'pdftocairo', 'pdfimages'], summary: 'render PDF pages to images (… in.pdf ROOT)',
376
+ spec: { ...POPPLER_SHORT, longSingle: true, long: { ...POPPLER_LONG, png: false, jpeg: false, tiff: false, ppm: false, pdf: false, ps: false, eps: false, svg: false, j: false, all: false, list: false, p: false, print: false, 'scale-to': true, 'scale-to-x': true, 'scale-to-y': true, singlefile: false, gray: false, mono: false, jpegopt: true, aa: true, aaVector: true, rx: true, ry: true, sep: true, forcenum: false, 'transp': false, 'hide-annotations': false, 'o': false, 'e': false, 'struct': false } },
377
+ operands: 'read arg?', stdin: false,
378
+ // pages are written as ROOT-N.ext (or ROOT.ext): a name starting with ROOT
379
+ extra: (r, { h, operands }) => {
380
+ const root = operands[1];
381
+ if (!root || isDash(root))
382
+ return r;
383
+ const base = h.resolve(root);
384
+ return { ...r, effects: [...r.effects, { kind: 'write', path: { alts: base.alts.map((a) => [...a, hole('filename', 'page suffix').alts[0][0]]) } }] };
385
+ },
386
+ }),
387
+ define({ names: ['pdfunite'], summary: 'merge PDFs (pdfunite in... out)', spec: { short: 'vh', longSingle: true, long: { help: false } }, operands: 'read* write', stdin: false }),
388
+ define({ names: ['pdfseparate'], summary: 'split a PDF (pdfseparate in out-%d.pdf)', spec: { short: 'vh', shortWithArg: 'fl', longSingle: true }, operands: 'read write', stdin: false }),
389
+ define({
390
+ names: ['identify'], summary: 'describe images (ImageMagick)',
391
+ spec: { longSingle: true, long: { format: true, verbose: false, ping: false, quiet: false, 'regard-warnings': false, density: true, units: true, precision: true, define: true, moments: false, features: true, unique: false, size: true, depth: true, list: true, version: false, help: false, colorspace: true, channel: true, alpha: true, auto_orient: false, debug: true, limit: 2, monitor: false } },
392
+ operands: 'read*', stdin: false,
393
+ // `@list` reads file names from a file; coders like `msl:` run scripts
394
+ extra: (r, { operands }) => (operands.some((o) => /^@|^[a-z]{2,}:/i.test(knownPrefix(o)) || concrete(o) === undefined && knownPrefix(o) === '') ? { ...r, complete: false } : r),
395
+ }),
396
+ ];
397
+ // ---------------------------------------------------------------------------
398
+ // crypto and keys
399
+ // ---------------------------------------------------------------------------
400
+ /** openssl SUBCOMMAND with single-dash options: -in/-key/… read, -out/-keyout write. */
401
+ const OPENSSL_READ = new Set(['-in', '-key', '-inkey', '-CAfile', '-CA', '-CAkey', '-cert', '-certfile', '-config', '-signature', '-extfile', '-paramfile', '-kfile', '-verify', '-prverify', '-peerkey', '-sigfile', '-untrusted', '-chain', '-CRLfile', '-passin', '-dcert', '-dkey', '-reqin', '-signkey', '-CAserial', '-rand']);
402
+ const OPENSSL_WRITE = new Set(['-out', '-keyout', '-sessout', '-msgfile', '-signer', '-pubout_file']);
403
+ const OPENSSL_READONLY = new Set(['version', 'rand', 'x509', 'dgst', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'base64', 'enc', 'req', 'rsa', 'ec', 'pkey', 'pkeyutl', 'verify', 'asn1parse', 'crl', 'pkcs7', 'pkcs12', 'cms', 'smime', 'genrsa', 'genpkey', 'ecparam', 'dhparam', 'prime', 'passwd', 'list', 'help', 'speed', 'errstr', 'ciphers', 'kdf', 'mac', 'storeutl', 'nseq', 'spkac', 'ts', 'rsautl', 'gendsa', 'dsa', 'dsaparam', 'crl2pkcs7', 'sess_id', 'info']);
404
+ const openssl = {
405
+ names: ['openssl'],
406
+ summary: 'cryptography toolkit',
407
+ analyze(inv, h) {
408
+ const a = args(inv);
409
+ const sub = concrete(a[0] ?? known('')) ?? '';
410
+ if (a[0])
411
+ h.role(a[0], 'subcommand');
412
+ const effects = [];
413
+ if (sub === 's_client' || sub === 's_time' || sub === 'ocsp') {
414
+ return { effects: [{ kind: 'network', detail: `openssl ${sub}`, target: a.slice(1).find((_, k) => concrete(a[k]) === '-connect') }], readsStdin: true, complete: true };
415
+ }
416
+ if (!OPENSSL_READONLY.has(sub))
417
+ return { effects: [{ kind: 'unknown', detail: `openssl ${sub || '(interactive)'}` }], readsStdin: 'maybe', complete: false };
418
+ let complete = true;
419
+ let readsIn = false;
420
+ let explicitIn = false;
421
+ const operands = [];
422
+ for (let i = 1; i < a.length; i++) {
423
+ const s = concrete(a[i]);
424
+ if (s === undefined) {
425
+ complete = false;
426
+ continue;
427
+ }
428
+ if (!s.startsWith('-') || s === '-') {
429
+ operands.push(a[i]);
430
+ continue;
431
+ }
432
+ const v = a[i + 1];
433
+ if (OPENSSL_READ.has(s) && v) {
434
+ i++;
435
+ if (s === '-in')
436
+ explicitIn = true;
437
+ const t = concrete(v);
438
+ if (s === '-passin') {
439
+ if (t?.startsWith('file:'))
440
+ effects.push({ kind: 'read', path: h.resolve(known(t.slice(5))) });
441
+ continue;
442
+ }
443
+ if (t === '-' || t === 'stdin')
444
+ readsIn = true;
445
+ else
446
+ effects.push({ kind: 'read', path: h.resolve(v) });
447
+ }
448
+ else if (OPENSSL_WRITE.has(s) && v) {
449
+ i++;
450
+ if (!isDash(v))
451
+ effects.push({ kind: 'write', path: h.resolve(v) });
452
+ }
453
+ else if (s === '-engine' || s === '-provider-path' || s === '-provider') {
454
+ // loads code
455
+ complete = false;
456
+ i++;
457
+ }
458
+ else if (s === '-sign' || s === '-hmac' || s === '-mac' || s === '-pass' || s === '-passout' || s === '-subj' || s === '-days' || s === '-newkey' || s === '-name' || s === '-digest' || s === '-cipher' || s === '-pkeyopt' || s === '-sigopt' || s === '-nameopt' || s === '-certopt' || s === '-set_serial' || s === '-addext' || s === '-extensions' || s === '-section' || s === '-K' || s === '-iv' || s === '-S' || s === '-md' || s === '-iter' || s === '-inform' || s === '-outform' || s === '-keyform' || s === '-certform' || s === '-algorithm' || s === '-macopt' || s === '-kdfopt' || s === '-keylen' || s === '-bits' || s === '-group' || s === '-servername' || s === '-connect') {
459
+ if (s === '-sign' && v)
460
+ effects.push({ kind: 'read', path: h.resolve(v) });
461
+ if (s === '-passout' && concrete(v ?? known(''))?.startsWith('file:'))
462
+ effects.push({ kind: 'write', path: h.resolve(known(concrete(v).slice(5))) });
463
+ i++;
464
+ }
465
+ }
466
+ // dgst/sha*/x509 … take files as operands; rand/prime take numbers
467
+ const fileOps = ['dgst', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'asn1parse'].includes(sub);
468
+ if (fileOps)
469
+ for (const o of operands)
470
+ if (!isDash(o))
471
+ effects.push({ kind: 'read', path: h.resolve(o) });
472
+ const usesStdin = !explicitIn && !['version', 'rand', 'help', 'list', 'genrsa', 'genpkey', 'ecparam', 'prime', 'speed', 'errstr', 'ciphers', 'kdf', 'passwd', 'dhparam', 'gendsa', 'dsaparam'].includes(sub) && !(fileOps && operands.length > 0);
473
+ return { effects, readsStdin: readsIn || usesStdin, complete };
474
+ },
475
+ };
476
+ const sshKeygen = {
477
+ names: ['ssh-keygen'],
478
+ summary: 'create and inspect SSH keys',
479
+ analyze(inv, h) {
480
+ const p = h.parseArgs(args(inv), { short: 'ABcDeEHiklLmMpqQrRuUvyYgK', shortWithArg: 'abCEfFgIJjmNnOPrRstVwYzZDTGSM' });
481
+ const effects = [];
482
+ const home = h.home() ?? known('~');
483
+ const f = p.flags.get('f');
484
+ const file = f && f !== true ? h.resolve(f) : undefined;
485
+ const knownHosts = file ?? concat(home, known('/.ssh/known_hosts'));
486
+ const query = ['l', 'y', 'B', 'e', 'i', 'D', 'k', 'Q', 'L'].some((k) => p.flags.has(k));
487
+ let complete = p.unknownFlags.length === 0;
488
+ if (p.flags.has('R') || p.flags.has('H'))
489
+ effects.push({ kind: 'write', path: knownHosts });
490
+ else if (p.flags.has('F'))
491
+ effects.push({ kind: 'read', path: knownHosts });
492
+ else if (p.flags.has('Y')) {
493
+ const mode = concrete(p.flags.get('Y')) ?? '';
494
+ if (file)
495
+ effects.push({ kind: 'read', path: file });
496
+ for (const o of p.operands) {
497
+ effects.push({ kind: 'read', path: h.resolve(o) });
498
+ if (mode === 'sign')
499
+ effects.push({ kind: 'write', path: concat(h.resolve(o), known('.sig')) });
500
+ }
501
+ if (mode !== 'sign' && mode !== 'verify' && mode !== 'check-novalidate' && mode !== 'find-principals')
502
+ complete = false;
503
+ }
504
+ else if (query || p.flags.has('p') || p.flags.has('c')) {
505
+ if (file)
506
+ effects.push({ kind: (p.flags.has('p') || p.flags.has('c')) ? 'write' : 'read', path: file });
507
+ else
508
+ complete = false; // prompts for the file
509
+ }
510
+ else {
511
+ // key generation: FILE and FILE.pub (default ~/.ssh/id_TYPE, asked interactively)
512
+ if (file)
513
+ effects.push({ kind: 'write', path: file }, { kind: 'write', path: concat(file, known('.pub')) });
514
+ else {
515
+ effects.push({ kind: 'write', path: concat(home, known('/.ssh/'), hole('filename', 'key name')) });
516
+ complete = false;
517
+ }
518
+ }
519
+ return { effects, readsStdin: !p.flags.has('N') || !file ? 'maybe' : false, complete };
520
+ },
521
+ };
522
+ // ---------------------------------------------------------------------------
523
+ // developer tools that do not run project code
524
+ // ---------------------------------------------------------------------------
525
+ /** tsc: reads the project; emits only without --noEmit (outDir comes from tsconfig: not known). */
526
+ const TSC_VALUE = new Set(['-p', '--project', '--module', '-m', '--moduleResolution', '--target', '-t', '--lib', '--jsx', '--types', '--typeRoots', '--outDir', '--rootDir', '--rootDirs', '--baseUrl', '--declarationDir', '--outFile', '--tsBuildInfoFile', '--generateTrace', '--locale', '--maxNodeModuleJsDepth', '--ignoreDeprecations', '--moduleDetection', '--newLine', '--paths', '--customConditions', '--watchFile', '--watchDirectory', '--fallbackPolling', '--excludeDirectories', '--excludeFiles', '--jsxFactory', '--jsxFragmentFactory', '--jsxImportSource', '--importsNotUsedAsValues', '--moduleSuffixes', '--plugins', '--extendedDiagnostics', '--generateCpuProfile', '--explainFiles', '--listEmittedFiles', '--diagnostics']);
527
+ const tsc = {
528
+ names: ['tsc'],
529
+ summary: 'TypeScript compiler',
530
+ analyze(inv, h) {
531
+ const a = args(inv);
532
+ const s = a.map((x) => concrete(x));
533
+ if (s.length === 1 && ['-v', '--version', '-h', '--help', '--all'].includes(s[0] ?? ''))
534
+ return { effects: [], readsStdin: false };
535
+ let dir = inv.cwd;
536
+ const effects = [];
537
+ const files = [];
538
+ let noEmit = false;
539
+ let complete = true;
540
+ for (let i = 0; i < a.length; i++) {
541
+ const t = s[i];
542
+ if (t === undefined) {
543
+ complete = false;
544
+ continue;
545
+ }
546
+ if (t === '--noEmit' && s[i + 1] !== 'false')
547
+ noEmit = true;
548
+ if (TSC_VALUE.has(t) && a[i + 1]) {
549
+ const v = a[++i];
550
+ if (t === '-p' || t === '--project')
551
+ dir = h.resolve(v);
552
+ else if (t === '--generateTrace' || t === '--tsBuildInfoFile' || t === '--generateCpuProfile')
553
+ effects.push({ kind: 'write', path: h.resolve(v), recursive: t === '--generateTrace' });
554
+ else if (t === '--outDir' || t === '--declarationDir' || t === '--outFile')
555
+ effects.push({ kind: 'write', path: h.resolve(v), recursive: t !== '--outFile', maybe: true });
556
+ continue;
557
+ }
558
+ if (t.startsWith('-')) {
559
+ if (s[i + 1] === 'true' || s[i + 1] === 'false')
560
+ i++;
561
+ continue;
562
+ }
563
+ files.push(a[i]);
564
+ }
565
+ for (const f of files)
566
+ effects.push({ kind: 'read', path: h.resolve(f) });
567
+ effects.unshift({ kind: 'read', path: dir, recursive: true });
568
+ if (!noEmit) {
569
+ // emits next to the sources or into tsconfig's outDir
570
+ effects.push({ kind: 'write', path: dir, recursive: true, maybe: true });
571
+ complete = false;
572
+ }
573
+ return { effects, readsStdin: false, complete };
574
+ },
575
+ };
576
+ /** Linters and formatters: read their operands (or the cwd); fix/write flags rewrite them. */
577
+ const LINT = {
578
+ anyFlags: true, operands: 'read*', cwdIfNone: true, recursive: true, stdin: false,
579
+ spec: { long: { fix: false, write: false, unsafe: false, 'unsafe-fixes': false, 'fix-only': false, 'fix-dangerously': false, apply: false, 'apply-unsafe': false, diff: false, check: false, config: true, 'config-path': true, format: true, 'output-format': true, select: true, ignore: true, 'extend-select': true, 'target-version': true, 'line-length': true, 'max-diagnostics': true, reporter: true, severity: true, shell: true, exclude: true, include: true } },
580
+ flags: { config: 'read', 'config-path': 'read' },
581
+ when: { 'fix|write|unsafe|unsafe-fixes|fix-only|fix-dangerously|apply|apply-unsafe': { operands: 'write*' } },
582
+ };
583
+ const lintTools = [
584
+ define({ names: ['oxlint', 'shellcheck', 'markdownlint', 'markdownlint-cli2', 'actionlint', 'hadolint', 'yamllint', 'eslint_d'], summary: 'linters (read files; --fix rewrites them)', ...LINT }),
585
+ define({
586
+ names: ['ruff', 'biome', 'taplo'], summary: 'linters / formatters', spec: { long: { version: false, help: false } },
587
+ subcommands: {
588
+ 'check|lint|ci': LINT,
589
+ // `ruff format` and `taplo fmt` rewrite files unless --check/--diff; `biome format` only with --write
590
+ 'format|fmt': { ...LINT, extra: (r, { inv, p }) => (inv.name !== 'biome' && !p.flags.has('check') && !p.flags.has('diff') ? { ...r, effects: r.effects.map((e) => (e.kind === 'read' ? { kind: 'write', path: e.path, recursive: true } : e)) } : r) },
591
+ 'version|help|rule|explain|rage|config|linter|analyze': QUERY,
592
+ '*': opaque('linter subcommand'),
593
+ },
594
+ }),
595
+ ];
596
+ // ---------------------------------------------------------------------------
597
+ // local daemons and services
598
+ // ---------------------------------------------------------------------------
599
+ const lms = define({
600
+ names: ['lms'], summary: 'LM Studio CLI', spec: { short: 'h', long: { help: false, version: false } },
601
+ subcommands: {
602
+ 'ps|ls|status|version|help|log': QUERY,
603
+ 'load|unload|server': { ...QUERY, effects: [{ kind: 'process', detail: 'LM Studio server state' }] },
604
+ get: { ...QUERY, effects: [{ kind: 'network', detail: 'lms get' }, { kind: 'write', path: '~/.lmstudio', recursive: true, maybe: true }] },
605
+ '*': opaque('lms'),
606
+ },
607
+ });
608
+ const tailscale = define({
609
+ names: ['tailscale'], summary: 'Tailscale CLI', spec: { long: { socket: true, help: false, version: false } },
610
+ subcommands: {
611
+ 'status|ip|version|whoami|dns|exit-node|metrics|licenses|bugreport|help|completion': QUERY,
612
+ 'ping|netcheck|nc': { ...QUERY, effects: [{ kind: 'network', detail: 'tailscale' }] },
613
+ 'switch': { ...QUERY, when: { list: QUERY }, opaque: 'tailscale switch changes the account' },
614
+ 'serve|funnel': { ...opaque('tailscale serve changes the node configuration'), when: { json: QUERY } },
615
+ '*': opaque('tailscale changes the node configuration'),
616
+ },
617
+ });
618
+ const netlify = define({
619
+ names: ['netlify', 'ntl'], summary: 'Netlify CLI', spec: { long: { version: false, help: false, debug: false, auth: true } },
620
+ subcommands: {
621
+ 'status|sites:list|sites:create|api|env:list|env:get|open:admin|watch|logs|logs:function|functions:list|teams:list|help|status:hooks|unlink|switch': { ...QUERY, effects: [{ kind: 'network', detail: 'netlify' }] },
622
+ // builds run project code unless --no-build
623
+ deploy: { anyFlags: true, operands: 'arg*', stdin: false, spec: { long: { dir: true, functions: true, 'no-build': false, prod: false, site: true, message: true, alias: true, json: false } }, flags: { dir: 'read:dir', functions: 'read:dir' }, effects: [{ kind: 'network', detail: 'netlify deploy' }, { kind: 'write', path: '.netlify', recursive: true, maybe: true }], recursive: true, opaque: 'netlify deploy may run the build', when: { 'no-build': { opaque: undefined } } },
624
+ '*': opaque('netlify runs project scripts or changes the site'),
625
+ },
626
+ });
627
+ const hf = define({
628
+ names: ['hf', 'huggingface-cli'], summary: 'Hugging Face CLI', spec: { long: { help: false, version: false, token: true } },
629
+ subcommands: {
630
+ 'whoami|env|version|help': { ...QUERY, effects: [{ kind: 'network', detail: 'hf', maybe: true }] },
631
+ download: { ...QUERY, spec: { long: { 'local-dir': true, 'cache-dir': true } }, flags: { 'local-dir': 'write:dir', 'cache-dir': 'write:dir' }, effects: [{ kind: 'network', detail: 'hf download' }, { kind: 'write', path: '~/.cache/huggingface', recursive: true, maybe: true }] },
632
+ upload: { anyFlags: true, operands: 'arg read*', stdin: false, recursive: true, effects: [{ kind: 'network', detail: 'hf upload' }] },
633
+ '*': opaque('hf'),
634
+ },
635
+ });
636
+ export const toolBehaviors = [
637
+ sqlite3, typst, ffmpeg, ytdlp, ...pdftools, openssl, sshKeygen, tsc, ...lintTools, lms, tailscale, netlify, hf,
638
+ ];
639
+ //# sourceMappingURL=tools.js.map