fauxnix-cli 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.
@@ -0,0 +1,2 @@
1
+ /** Register every built-in Linux command translator. Side-effectful import. */
2
+ export declare function installAll(): void;
@@ -0,0 +1,17 @@
1
+ import { registerAll } from '../registry.js';
2
+ import { handlers as files } from './files.js';
3
+ import { handlers as textFilters } from './text-filters.js';
4
+ import { handlers as textIo } from './text-io.js';
5
+ import { handlers as sysinfo } from './sysinfo.js';
6
+ import { handlers as net } from './net.js';
7
+ import { handlers as archive } from './archive.js';
8
+ /** Register every built-in Linux command translator. Side-effectful import. */
9
+ export function installAll() {
10
+ registerAll(files);
11
+ registerAll(textFilters);
12
+ registerAll(textIo);
13
+ registerAll(sysinfo);
14
+ registerAll(net);
15
+ registerAll(archive);
16
+ }
17
+ installAll();
@@ -0,0 +1,2 @@
1
+ import { Handler } from '../registry.js';
2
+ export declare const handlers: Record<string, Handler>;
@@ -0,0 +1,533 @@
1
+ import { wordToString } from '../ast.js';
2
+ import { parseWords, psErr, psStr } from '../registry.js';
3
+ import { exprOfWord, literalOfWord, operandExpr } from '../translator.js';
4
+ /* ------------------------------------------------------------------ */
5
+ /* Shared PS snippets */
6
+ /* ------------------------------------------------------------------ */
7
+ /**
8
+ * Mimosa security guard for HTTP(S) client commands (curl/wget): refuse
9
+ * loopback / private / reserved destinations before any request is made.
10
+ * Applied to every argv element that starts with http:// or https://.
11
+ */
12
+ const PS_NETGUARD_FNS = [
13
+ 'function fx-badhost($h) {',
14
+ ' $h = ([string]$h).Trim(\'[]\').ToLower()',
15
+ " if ($h -eq '') { return $false }",
16
+ " if ($h -eq 'localhost' -or $h -eq '::1') { return $true }",
17
+ " if ($h.StartsWith('127.') -or $h.StartsWith('10.') -or $h.StartsWith('192.168.') -or $h.StartsWith('169.254.')) { return $true }",
18
+ " if ($h.StartsWith('172.')) {",
19
+ " $fx_p = $h.Split('.')",
20
+ ' if ($fx_p.Count -ge 2) { $fx_n = 0; if ([int]::TryParse($fx_p[1], [ref]$fx_n)) { if ($fx_n -ge 16 -and $fx_n -le 31) { return $true } } }',
21
+ ' }',
22
+ ' return $false',
23
+ '}',
24
+ 'function fx-urlhost($u) {',
25
+ ' $fx_r = [string]$u',
26
+ " $fx_i = $fx_r.IndexOf('://')",
27
+ ' if ($fx_i -lt 0) { return \'\' }',
28
+ ' $fx_r = $fx_r.Substring($fx_i + 3)',
29
+ " foreach ($fx_c in @('/', '?', '#')) { $fx_i = $fx_r.IndexOf($fx_c); if ($fx_i -ge 0) { $fx_r = $fx_r.Substring(0, $fx_i) } }",
30
+ " $fx_i = $fx_r.LastIndexOf('@')",
31
+ ' if ($fx_i -ge 0) { $fx_r = $fx_r.Substring($fx_i + 1) }',
32
+ " if ($fx_r.StartsWith('[')) {",
33
+ " $fx_j = $fx_r.IndexOf(']')",
34
+ ' if ($fx_j -ge 0) { $fx_r = $fx_r.Substring(0, $fx_j + 1) }',
35
+ ' } else {',
36
+ " $fx_i = $fx_r.IndexOf(':')",
37
+ ' if ($fx_i -ge 0) { $fx_r = $fx_r.Substring(0, $fx_i) }',
38
+ ' }',
39
+ " return $fx_r.Trim('[]')",
40
+ '}',
41
+ 'function fx-netguard($cmd, $a) {',
42
+ ' $fx_s = [string]$a',
43
+ " if ($fx_s.StartsWith('http://', [System.StringComparison]::OrdinalIgnoreCase) -or $fx_s.StartsWith('https://', [System.StringComparison]::OrdinalIgnoreCase)) {",
44
+ ' $fx_h = fx-urlhost $fx_s',
45
+ ' if (fx-badhost $fx_h) {',
46
+ " [Console]::Error.WriteLine($cmd + ': fauxnix refused private/loopback address ' + $fx_h)",
47
+ ' return $true',
48
+ ' }',
49
+ ' }',
50
+ ' return $false',
51
+ '}',
52
+ ].join('\n');
53
+ /** IPv4 netmask math helpers (prefix → mask/broadcast), shared by ip/ifconfig. */
54
+ const PS_IP_MASK_FNS = [
55
+ 'function fx-mask($len, $oct) {',
56
+ ' $fx_rem = $len - ($oct * 8)',
57
+ ' if ($fx_rem -le 0) { return 0 }',
58
+ ' if ($fx_rem -ge 8) { return 255 }',
59
+ ' return ((255 -shl (8 - $fx_rem)) -band 255)',
60
+ '}',
61
+ 'function fx-nm($len) {',
62
+ ' $fx_o = @()',
63
+ ' for ($fx_i = 0; $fx_i -lt 4; $fx_i++) { $fx_o += [string](fx-mask $len $fx_i) }',
64
+ " return ($fx_o -join '.')",
65
+ '}',
66
+ 'function fx-brd($ip, $len) {',
67
+ " $fx_b = ([string]$ip).Split('.')",
68
+ ' $fx_o = @()',
69
+ ' for ($fx_i = 0; $fx_i -lt 4; $fx_i++) {',
70
+ ' $fx_m = fx-mask $len $fx_i',
71
+ ' $fx_o += [string](([int]$fx_b[$fx_i]) -bor (255 -bxor $fx_m))',
72
+ ' }',
73
+ " return ($fx_o -join '.')",
74
+ '}',
75
+ ].join('\n');
76
+ /** Synthetic literal Word (for values extracted out of a larger argument). */
77
+ function synthWord(text) {
78
+ return [{ kind: 'Text', text }];
79
+ }
80
+ /** A native-exe invocation obeying the fauxnix contract (string lines + exit code). */
81
+ function nativeCall(exe, argArray) {
82
+ return [
83
+ '& ' + psStr(exe) + ' @(' + argArray + ') | ForEach-Object { [string]$_ }',
84
+ 'if ($LASTEXITCODE -gt 0) { $script:fx_exit = $LASTEXITCODE }',
85
+ ].join('\n');
86
+ }
87
+ /* ------------------------------------------------------------------ */
88
+ /* curl */
89
+ /* ------------------------------------------------------------------ */
90
+ const curl = (args) => {
91
+ // CRITICAL: in PS 5.1 `curl` is an alias for Invoke-WebRequest — the real
92
+ // curl must be invoked explicitly as curl.exe. All args pass through
93
+ // untouched (Windows curl.exe is real curl); the runtime host guard below
94
+ // refuses private/loopback URLs before the process is even started.
95
+ return [
96
+ PS_NETGUARD_FNS,
97
+ "$fx_args = @(" + args.map(exprOfWord).join(', ') + ')',
98
+ '$fx_bad = $false',
99
+ "foreach ($fx_a in $fx_args) { if (fx-netguard 'curl' $fx_a) { $fx_bad = $true } }",
100
+ 'if ($fx_bad) { $script:fx_exit = 1 }',
101
+ 'else {',
102
+ " " + nativeCall('curl.exe', '$fx_args'),
103
+ '}',
104
+ ].join('\n');
105
+ };
106
+ /* ------------------------------------------------------------------ */
107
+ /* wget */
108
+ /* ------------------------------------------------------------------ */
109
+ /** GNU wget default output filename derived from a literal URL (index.html when bare). */
110
+ function urlFileName(u) {
111
+ // keep only the path component (drop scheme, host, query, fragment)
112
+ let p = u.split('#')[0].split('?')[0];
113
+ const sm = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.exec(p);
114
+ if (sm)
115
+ p = p.slice(sm[0].length);
116
+ const slash = p.indexOf('/');
117
+ if (slash >= 0)
118
+ p = p.slice(slash + 1);
119
+ else
120
+ p = '';
121
+ if (p === '')
122
+ return 'index.html'; // no path (or trailing /) → GNU default
123
+ return p;
124
+ }
125
+ /** Map GNU wget argv → curl.exe argv (only literal words are rewritten). */
126
+ function mapWgetArgs(args) {
127
+ const margs = [];
128
+ const urls = [];
129
+ let sawOutput = false;
130
+ const raw = args.map(wordToString);
131
+ let i = 0;
132
+ while (i < args.length) {
133
+ const lit = literalOfWord(args[i]);
134
+ if (lit === null) {
135
+ margs.push(exprOfWord(args[i]));
136
+ i++;
137
+ continue;
138
+ }
139
+ // bundles like -q, -qO-, -O-
140
+ const m = /^-(q*)O(.*)$/.exec(lit);
141
+ if (m) {
142
+ if (m[1].length > 0)
143
+ margs.push("'-s'");
144
+ const rest = m[2];
145
+ if (rest === '') {
146
+ if (i + 1 < args.length) {
147
+ const v = wordToString(args[i + 1]);
148
+ if (v === '-') {
149
+ sawOutput = true; // -O - → curl writes to stdout by default
150
+ }
151
+ else {
152
+ margs.push("'-o'", operandExpr(args[i + 1]));
153
+ sawOutput = true;
154
+ }
155
+ i++;
156
+ }
157
+ }
158
+ else if (rest === '-') {
159
+ sawOutput = true; // -O- → stdout passthrough (curl default)
160
+ }
161
+ else {
162
+ margs.push("'-o'", operandExpr(synthWord(rest)));
163
+ sawOutput = true;
164
+ }
165
+ i++;
166
+ continue;
167
+ }
168
+ if (lit === '--output-document' && i + 1 < args.length) {
169
+ const v = wordToString(args[i + 1]);
170
+ if (v === '-') {
171
+ sawOutput = true;
172
+ }
173
+ else {
174
+ margs.push("'-o'", operandExpr(args[i + 1]));
175
+ sawOutput = true;
176
+ }
177
+ i += 2;
178
+ continue;
179
+ }
180
+ const eq = /^--output-document=(.*)$/.exec(lit);
181
+ if (eq) {
182
+ if (eq[1] === '') {
183
+ sawOutput = true;
184
+ }
185
+ else {
186
+ margs.push("'-o'", operandExpr(synthWord(eq[1])));
187
+ sawOutput = true;
188
+ }
189
+ i++;
190
+ continue;
191
+ }
192
+ if (lit === '-q' || lit === '--quiet') {
193
+ margs.push("'-s'");
194
+ i++;
195
+ continue;
196
+ }
197
+ if (lit === '--spider') {
198
+ margs.push("'-I'");
199
+ i++;
200
+ continue;
201
+ }
202
+ if (/^https?:\/\//i.test(lit))
203
+ urls.push(lit);
204
+ margs.push(exprOfWord(args[i]));
205
+ i++;
206
+ }
207
+ // GNU wget saves the document in the current directory when -O is absent;
208
+ // curl would dump it on stdout, so derive the filename from the URL.
209
+ if (!sawOutput && urls.length === 1) {
210
+ margs.push("'-o'", operandExpr(synthWord(urlFileName(urls[0]))));
211
+ }
212
+ return { margs, sawOutput, urls };
213
+ }
214
+ const wget = (args) => {
215
+ const orig = args.map(exprOfWord);
216
+ const mapped = mapWgetArgs(args);
217
+ return [
218
+ PS_NETGUARD_FNS,
219
+ '$fx_args = @(' + orig.join(', ') + ')',
220
+ '$fx_margs = @(' + mapped.margs.join(', ') + ')',
221
+ '$fx_bad = $false',
222
+ "foreach ($fx_a in $fx_args) { if (fx-netguard 'wget' $fx_a) { $fx_bad = $true } }",
223
+ 'if ($fx_bad) { $script:fx_exit = 1 }',
224
+ // real GNU wget on PATH (Git Bash etc.) → use it natively with the original argv
225
+ "elseif (Get-Command 'wget.exe' -ErrorAction SilentlyContinue) {",
226
+ " " + nativeCall('wget.exe', '$fx_args'),
227
+ '}',
228
+ // otherwise map onto curl.exe
229
+ 'else {',
230
+ " " + nativeCall('curl.exe', '$fx_margs'),
231
+ '}',
232
+ ].join('\n');
233
+ };
234
+ /* ------------------------------------------------------------------ */
235
+ /* ping */
236
+ /* ------------------------------------------------------------------ */
237
+ const ping = (args) => {
238
+ const raw = args.map(wordToString);
239
+ const out = [];
240
+ let hasCount = false;
241
+ let i = 0;
242
+ while (i < args.length) {
243
+ const t = raw[i];
244
+ let handled = false;
245
+ let m = /^-c(\d+)$/.exec(t);
246
+ if (m) {
247
+ out.push("'-n'", psStr(m[1]));
248
+ hasCount = true;
249
+ handled = true;
250
+ }
251
+ if (!handled && t === '-c' && i + 1 < args.length) {
252
+ out.push("'-n'", exprOfWord(args[i + 1]));
253
+ hasCount = true;
254
+ i++;
255
+ handled = true;
256
+ }
257
+ m = /^-W(\d+(?:\.\d+)?)$/.exec(t);
258
+ if (!handled && m) {
259
+ out.push("'-w'", psStr(String(Math.round(parseFloat(m[1]) * 1000))));
260
+ handled = true;
261
+ }
262
+ if (!handled && t === '-W' && i + 1 < args.length && /^\d+(\.\d+)?$/.test(raw[i + 1])) {
263
+ out.push("'-w'", psStr(String(Math.round(parseFloat(raw[i + 1]) * 1000))));
264
+ i++;
265
+ handled = true;
266
+ }
267
+ // -i interval: no equivalent — dropped (with its value)
268
+ if (!handled && /^-i\d+(\.\d+)?$/.test(t)) {
269
+ handled = true;
270
+ }
271
+ if (!handled && t === '-i' && i + 1 < args.length && /^\d+(\.\d+)?$/.test(raw[i + 1])) {
272
+ i++;
273
+ handled = true;
274
+ }
275
+ if (!handled)
276
+ out.push(exprOfWord(args[i]));
277
+ i++;
278
+ }
279
+ // Linux ping defaults to endless echoing → Windows needs -t
280
+ if (!hasCount)
281
+ out.unshift("'-t'");
282
+ return nativeCall('ping.exe', out.join(', '));
283
+ };
284
+ /* ------------------------------------------------------------------ */
285
+ /* netstat / ss */
286
+ /* ------------------------------------------------------------------ */
287
+ const netstat = (args) => {
288
+ const raw = args.map(wordToString);
289
+ const out = [];
290
+ const notes = [];
291
+ let i = 0;
292
+ while (i < args.length) {
293
+ const t = raw[i];
294
+ // listening-socket combos (-tlnp -tlpn -tulpn -tln -tl ...) → -ano
295
+ const body = t.slice(1);
296
+ if (/^-[tulnp]+$/.test(t) &&
297
+ body.includes('l') &&
298
+ (body.includes('n') || body.includes('p'))) {
299
+ if (!out.includes("'-ano'"))
300
+ out.push("'-ano'");
301
+ i++;
302
+ continue;
303
+ }
304
+ if (t === '-an' || t === '-na') {
305
+ if (!out.includes("'-ano'"))
306
+ out.push("'-ano'");
307
+ i++;
308
+ continue;
309
+ }
310
+ if (t === '-p') {
311
+ if (i + 1 < args.length && !raw[i + 1].startsWith('-'))
312
+ i++;
313
+ notes.push('netstat: fauxnix: -p is not supported on Windows (all sockets shown)');
314
+ i++;
315
+ continue;
316
+ }
317
+ out.push(exprOfWord(args[i]));
318
+ i++;
319
+ }
320
+ // if -p swallowed everything, still list sockets as -ano — a bare
321
+ // `netstat` (no flags) reverse-DNSes every address and takes minutes
322
+ if (out.length === 0)
323
+ out.push("'-ano'");
324
+ return [
325
+ ...notes.map((n) => '[Console]::Error.WriteLine(' + psStr(n) + ')'),
326
+ nativeCall('netstat.exe', out.join(', ')),
327
+ ].join('\n');
328
+ };
329
+ const ss = () => {
330
+ // Windows has no ss; netstat -ano is the closest (all sockets + PIDs).
331
+ return [
332
+ '[Console]::Error.WriteLine(' + psStr('ss: fauxnix maps ss → netstat -ano (Windows)') + ')',
333
+ nativeCall('netstat.exe', "'-ano'"),
334
+ ].join('\n');
335
+ };
336
+ /* ------------------------------------------------------------------ */
337
+ /* ip / ifconfig */
338
+ /* ------------------------------------------------------------------ */
339
+ /** PS: unique interface alias list, loopback first (GNU puts lo first). */
340
+ const PS_IFLIST = [
341
+ "$fx_addrs = @(Get-NetIPAddress -ErrorAction SilentlyContinue |",
342
+ " Sort-Object -Property @{e={ if ($_.InterfaceAlias -like '*Loopback*') { 0 } else { 1 } }}, InterfaceAlias, AddressFamily)",
343
+ "$fx_ifs = @($fx_addrs | Select-Object -ExpandProperty InterfaceAlias -Unique)",
344
+ ].join('\n');
345
+ const ip = (args) => {
346
+ const raw = args.map(wordToString);
347
+ let sub = '';
348
+ for (const t of raw) {
349
+ if (!t.startsWith('-')) {
350
+ sub = t;
351
+ break;
352
+ }
353
+ }
354
+ const fam = raw.includes('-6') ? 'IPv6' : raw.includes('-4') ? 'IPv4' : '';
355
+ const famFilter = fam ? " | Where-Object { $_.AddressFamily -eq '" + fam + "' }" : '';
356
+ if (sub === 'addr' || sub === 'a' || sub === 'address') {
357
+ return [
358
+ PS_IP_MASK_FNS,
359
+ '$fx_addrs = @(Get-NetIPAddress -ErrorAction SilentlyContinue' + famFilter + ' |',
360
+ " Sort-Object -Property @{e={ if ($_.InterfaceAlias -like '*Loopback*') { 0 } else { 1 } }}, InterfaceAlias, AddressFamily)",
361
+ "$fx_ifs = @($fx_addrs | Select-Object -ExpandProperty InterfaceAlias -Unique)",
362
+ '$fx_n = 0',
363
+ 'foreach ($fx_name in $fx_ifs) {',
364
+ ' $fx_n = $fx_n + 1',
365
+ " $fx_lo = ($fx_name -like '*Loopback*')",
366
+ " if ($fx_lo) { ('' + $fx_n + ': ' + $fx_name + ': <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000') }",
367
+ " else { ('' + $fx_n + ': ' + $fx_name + ': <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000') }",
368
+ ' foreach ($fx_a in @($fx_addrs | Where-Object { $_.InterfaceAlias -eq $fx_name })) {',
369
+ " if ($fx_a.AddressFamily -eq 'IPv4') {",
370
+ " $fx_scope = 'global'",
371
+ " if ($fx_lo) { $fx_scope = 'host' } elseif ($fx_a.IPAddress.StartsWith('169.254.')) { $fx_scope = 'link' }",
372
+ " (' inet ' + $fx_a.IPAddress + '/' + $fx_a.PrefixLength + ' brd ' + (fx-brd $fx_a.IPAddress $fx_a.PrefixLength) + ' scope ' + $fx_scope + ' ' + $fx_name)",
373
+ ' } else {',
374
+ " $fx_scope = 'global'",
375
+ " if ($fx_a.IPAddress -eq '::1') { $fx_scope = 'host' } elseif ($fx_a.IPAddress.ToLower().StartsWith('fe80:')) { $fx_scope = 'link' }",
376
+ " (' inet6 ' + $fx_a.IPAddress + '/' + $fx_a.PrefixLength + ' scope ' + $fx_scope + ' ' + $fx_name)",
377
+ ' }',
378
+ ' }',
379
+ '}',
380
+ ].join('\n');
381
+ }
382
+ if (sub === 'link' || sub === 'l') {
383
+ return [
384
+ PS_IFLIST,
385
+ '$fx_mac = @{}',
386
+ 'foreach ($fx_ad in @(Get-NetAdapter -ErrorAction SilentlyContinue)) { if ($fx_ad.MacAddress) { $fx_mac[$fx_ad.Name] = $fx_ad.MacAddress.Replace(\'-\', \':\').ToLower() } }',
387
+ '$fx_n = 0',
388
+ 'foreach ($fx_name in $fx_ifs) {',
389
+ ' $fx_n = $fx_n + 1',
390
+ " $fx_lo = ($fx_name -like '*Loopback*')",
391
+ " if ($fx_lo) { ('' + $fx_n + ': ' + $fx_name + ': <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000') }",
392
+ " else { ('' + $fx_n + ': ' + $fx_name + ': <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000') }",
393
+ ' if ($fx_lo) {',
394
+ " ' link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00'",
395
+ ' } else {',
396
+ ' $fx_m = $fx_mac[$fx_name]',
397
+ " if (-not $fx_m) { $fx_m = '00:00:00:00:00:00' }",
398
+ " (' link/ether ' + $fx_m + ' brd ff:ff:ff:ff:ff:ff')",
399
+ ' }',
400
+ '}',
401
+ ].join('\n');
402
+ }
403
+ if (sub === 'route' || sub === 'r') {
404
+ return [
405
+ '$fx_a4 = @(Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue)',
406
+ '$fx_rts = @(Get-NetRoute -ErrorAction SilentlyContinue |',
407
+ " Where-Object { $_.DestinationPrefix -match '\\.' -and $_.DestinationPrefix -ne '255.255.255.255/32' } |",
408
+ ' Sort-Object -Property DestinationPrefix)',
409
+ 'foreach ($fx_r in @($fx_rts | Where-Object { $_.DestinationPrefix -eq \'0.0.0.0/0\' })) {',
410
+ " $fx_l = 'default'",
411
+ " if ($null -ne $fx_r.NextHop -and $fx_r.NextHop -ne '' -and $fx_r.NextHop -ne '0.0.0.0') { $fx_l = $fx_l + ' via ' + $fx_r.NextHop }",
412
+ " $fx_l = $fx_l + ' dev ' + $fx_r.InterfaceAlias",
413
+ " $fx_c = @($fx_a4 | Where-Object { $_.InterfaceAlias -eq $fx_r.InterfaceAlias })",
414
+ " if ($fx_c.Count -gt 0) { $fx_l = $fx_l + ' proto dhcp src ' + $fx_c[0].IPAddress }",
415
+ ' $fx_l',
416
+ '}',
417
+ 'foreach ($fx_r in @($fx_rts | Where-Object { $_.DestinationPrefix -ne \'0.0.0.0/0\' })) {',
418
+ ' $fx_l = $fx_r.DestinationPrefix',
419
+ " if ($null -ne $fx_r.NextHop -and $fx_r.NextHop -ne '' -and $fx_r.NextHop -ne '0.0.0.0') { $fx_l = $fx_l + ' via ' + $fx_r.NextHop }",
420
+ " $fx_l = $fx_l + ' dev ' + $fx_r.InterfaceAlias + ' proto kernel scope link'",
421
+ " $fx_c = @($fx_a4 | Where-Object { $_.InterfaceAlias -eq $fx_r.InterfaceAlias })",
422
+ " if ($fx_c.Count -gt 0) { $fx_l = $fx_l + ' src ' + $fx_c[0].IPAddress }",
423
+ ' $fx_l',
424
+ '}',
425
+ ].join('\n');
426
+ }
427
+ return psErr('ip', 'fauxnix does not support "ip ' + sub + '"');
428
+ };
429
+ const ifconfig = (args) => {
430
+ const { operandWords } = parseWords(args);
431
+ const filter = operandWords.length
432
+ ? "$fx_ifs = @($fx_ifs | Where-Object { $_ -like ('*' + (" + exprOfWord(operandWords[0]) + ") + '*') })\n"
433
+ : '';
434
+ return [
435
+ PS_IP_MASK_FNS,
436
+ PS_IFLIST,
437
+ filter,
438
+ 'foreach ($fx_name in $fx_ifs) {',
439
+ " $fx_lo = ($fx_name -like '*Loopback*')",
440
+ " if ($fx_lo) { ($fx_name + ': flags=73<UP,LOOPBACK,RUNNING> mtu 65536') } else { ($fx_name + ': flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500') }",
441
+ ' foreach ($fx_a in @($fx_addrs | Where-Object { $_.InterfaceAlias -eq $fx_name })) {',
442
+ " if ($fx_a.AddressFamily -eq 'IPv4') {",
443
+ " $fx_l = ' inet addr:' + $fx_a.IPAddress + ' netmask:' + (fx-nm $fx_a.PrefixLength)",
444
+ " if (-not $fx_lo) { $fx_l = $fx_l + ' broadcast:' + (fx-brd $fx_a.IPAddress $fx_a.PrefixLength) }",
445
+ ' $fx_l',
446
+ ' } else {',
447
+ " $fx_sc = 'Scope:Global'",
448
+ " if ($fx_a.IPAddress -eq '::1') { $fx_sc = 'Scope:Host' } elseif ($fx_a.IPAddress.ToLower().StartsWith('fe80:')) { $fx_sc = 'Scope:Link' }",
449
+ " ' inet6 addr: ' + $fx_a.IPAddress + '/' + $fx_a.PrefixLength + ' ' + $fx_sc",
450
+ ' }',
451
+ ' }',
452
+ " ''",
453
+ '}',
454
+ ].join('\n');
455
+ };
456
+ /* ------------------------------------------------------------------ */
457
+ /* nslookup / dig / host */
458
+ /* ------------------------------------------------------------------ */
459
+ const nslookup = (args) => nativeCall('nslookup.exe', args.map(exprOfWord).join(', '));
460
+ const dig = (args) => {
461
+ const raw = args.map(wordToString);
462
+ let short = false;
463
+ const ops = [];
464
+ for (let i = 0; i < raw.length; i++) {
465
+ const t = raw[i];
466
+ if (t === '+short') {
467
+ short = true;
468
+ continue;
469
+ }
470
+ if (t.startsWith('-') || t.startsWith('+'))
471
+ continue; // other dig options dropped
472
+ if (t.startsWith('@'))
473
+ continue; // @server dropped
474
+ ops.push(args[i]);
475
+ }
476
+ const note = '[Console]::Error.WriteLine(' + psStr('dig: fauxnix maps dig → nslookup') + ')';
477
+ if (short) {
478
+ // dig +short HOST → only the answer addresses, no server preamble
479
+ if (ops.length === 0) {
480
+ return note + '; ' + psErr('dig', 'fauxnix: no host given');
481
+ }
482
+ return [
483
+ note,
484
+ '$fx_h = ' + exprOfWord(ops[0]),
485
+ 'try {',
486
+ " foreach ($fx_a in @([System.Net.Dns]::GetHostAddresses($fx_h) | Sort-Object -Property @{e={ if ($_.AddressFamily -eq 'InterNetwork') { 0 } else { 1 } }})) { $fx_a.IPAddressToString }",
487
+ '} catch { }',
488
+ ].join('\n');
489
+ }
490
+ const nsArgs = [];
491
+ if (ops.length >= 2)
492
+ nsArgs.push(psStr('-type=' + wordToString(ops[1])));
493
+ if (ops.length >= 1)
494
+ nsArgs.push(exprOfWord(ops[0]));
495
+ return [
496
+ note,
497
+ nativeCall('nslookup.exe', nsArgs.join(', ')),
498
+ ].join('\n');
499
+ };
500
+ const host = (args) => {
501
+ const { operandWords } = parseWords(args);
502
+ if (operandWords.length === 0) {
503
+ return psErr('host', 'you must specify a host name (usage: host NAME)');
504
+ }
505
+ return [
506
+ '$fx_name = ' + exprOfWord(operandWords[0]),
507
+ 'try {',
508
+ " $fx_addrs = @([System.Net.Dns]::GetHostAddresses($fx_name) | Sort-Object -Property @{e={ if ($_.AddressFamily -eq 'InterNetwork') { 0 } else { 1 } }})",
509
+ ' foreach ($fx_a in $fx_addrs) {',
510
+ " if ($fx_a.AddressFamily -eq 'InterNetwork') { ($fx_name + ' has address ' + $fx_a.IPAddressToString) }",
511
+ " else { ($fx_name + ' has IPv6 address ' + $fx_a.IPAddressToString) }",
512
+ ' }',
513
+ '} catch {',
514
+ " [Console]::Error.WriteLine('Host ' + $fx_name + ' not found: 3(NXDOMAIN)')",
515
+ ' $script:fx_exit = 1',
516
+ '}',
517
+ ].join('\n');
518
+ };
519
+ /* ------------------------------------------------------------------ */
520
+ /* exports */
521
+ /* ------------------------------------------------------------------ */
522
+ export const handlers = {
523
+ curl,
524
+ wget,
525
+ ping,
526
+ netstat,
527
+ ss,
528
+ ip,
529
+ ifconfig,
530
+ nslookup,
531
+ dig,
532
+ host,
533
+ };
@@ -0,0 +1,2 @@
1
+ import { Handler } from '../registry.js';
2
+ export declare const handlers: Record<string, Handler>;