fauxnix-cli 0.9.3 → 0.11.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.
- package/README.md +46 -12
- package/dist/ast.d.ts +38 -3
- package/dist/ast.js +22 -2
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +25 -1
- package/dist/commands/archive.d.ts +2 -1
- package/dist/commands/archive.js +82 -1
- package/dist/commands/install-all.js +2 -1
- package/dist/commands/sysinfo.js +48 -12
- package/dist/commands/text-filters.d.ts +1 -0
- package/dist/commands/text-filters.js +63 -2
- package/dist/doctor.d.ts +20 -0
- package/dist/doctor.js +251 -0
- package/dist/install.d.ts +15 -0
- package/dist/install.js +206 -0
- package/dist/mcp.d.ts +6 -0
- package/dist/mcp.js +34 -12
- package/dist/parser.js +422 -33
- package/dist/translator.d.ts +29 -3
- package/dist/translator.js +302 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,6 +98,10 @@ fauxnix translate "find . -name '*.log' -mtime +7 -delete"
|
|
|
98
98
|
|
|
99
99
|
# check your environment
|
|
100
100
|
fauxnix check
|
|
101
|
+
fauxnix doctor # check + encoding, harness config, MCP
|
|
102
|
+
|
|
103
|
+
# write user-level MCP config (idempotent; also --codex/--opencode/--kimi/--qwen)
|
|
104
|
+
fauxnix install --claude
|
|
101
105
|
|
|
102
106
|
# run the MCP stdio server (what agent harnesses connect to)
|
|
103
107
|
fauxnix mcp
|
|
@@ -109,7 +113,11 @@ with argv-style quoting — no string re-parsing, no quoting bugs.
|
|
|
109
113
|
## Use with your agent harness
|
|
110
114
|
|
|
111
115
|
fauxnix ships an MCP stdio server exposing a `bash` tool (plus `fauxnix_translate` and
|
|
112
|
-
`fauxnix_session`). Point any MCP-capable harness at it
|
|
116
|
+
`fauxnix_session`). Point any MCP-capable harness at it with
|
|
117
|
+
`fauxnix install --claude` (or `--codex` / `--opencode` / `--kimi` / `--qwen`).
|
|
118
|
+
Idempotent; prints what changed. Manual configs below.
|
|
119
|
+
|
|
120
|
+
- **Quickstarts** — copy-paste config + a 10-command smoke: [`docs/examples/`](docs/examples/)
|
|
113
121
|
|
|
114
122
|
**Claude Code**
|
|
115
123
|
```bash
|
|
@@ -145,12 +153,23 @@ TOML config: `~/.kimi-code/mcp.json`
|
|
|
145
153
|
}
|
|
146
154
|
```
|
|
147
155
|
|
|
156
|
+
**Qwen Code** (`~/.qwen/settings.json`)
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"mcpServers": {
|
|
160
|
+
"fauxnix": { "command": "fauxnix", "args": ["mcp"] }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
148
165
|
**Any MCP client** — stdio server: `fauxnix mcp`. The tool name is `bash` (override with
|
|
149
166
|
`FAUXNIX_TOOL_NAME`). Tool description already teaches the model the supported subset, so no
|
|
150
167
|
system-prompt changes are required.
|
|
151
168
|
|
|
152
|
-
The MCP session persists `cwd`, environment variables, `export`/`unset
|
|
153
|
-
tool calls — it behaves like a logged-in
|
|
169
|
+
The MCP session persists `cwd`, environment variables, `export`/`unset`, `cd -`/OLDPWD, and
|
|
170
|
+
positional parameters (`set --` / `$1` / `"$@"`) across tool calls — it behaves like a logged-in
|
|
171
|
+
shell, not a stateless `exec`. `$0` is the MCP tool name (`bash` / `FAUXNIX_TOOL_NAME`), not a
|
|
172
|
+
Windows path.
|
|
154
173
|
|
|
155
174
|
## What's translated
|
|
156
175
|
|
|
@@ -164,20 +183,24 @@ development:
|
|
|
164
183
|
|
|
165
184
|
`cp` / `mv` / `rm` / `touch` / `du` / `ls` / `ll` / `mkdir` / `rmdir` / `mktemp` / `ln` /
|
|
166
185
|
`readlink` / `realpath` / `basename` / `dirname` / `stat` / `file` / `df` / `chmod` / `chown` /
|
|
167
|
-
`diff` / `tee` / `grep` / `head` / `echo` / `printf` / `cat` / `tail` / `wc`
|
|
186
|
+
`diff` / `tee` / `grep` / `head` / `echo` / `printf` / `cat` / `tail` / `wc` / `sort` / `uniq` /
|
|
187
|
+
`cut` / `tr` / `gzip` / `gunzip` / `zcat` / `zip` / `unzip` carry a `CommandSpec`: unknown options fail with a GNU-style
|
|
168
188
|
usage error instead of being ignored (`find` stays unspec'd so predicates like `-name` still
|
|
169
|
-
compile
|
|
189
|
+
compile; `sed`/`awk`/`egrep` stay unspec'd; `tar` stays unspec'd because it is fx-native to
|
|
190
|
+
`tar.exe` and unknown GNU flags must reach bsdtar). Implemented GNU holes: `cp -n` / `mv -n` / `touch -c` / `tee --append` / `grep -m` /
|
|
170
191
|
`head --lines` / `du --max-depth`. `fauxnix list --json` and `docs/command-specs.md` dump the
|
|
171
192
|
same metadata.
|
|
172
193
|
- **shell/system**: `cd pwd export unset env printenv ps kill pkill pgrep sleep which type whoami
|
|
173
194
|
id groups date uname hostname uptime free nproc clear true false test [ [[ : pushd popd dirs sudo
|
|
174
|
-
timeout man history less more source . eval exit alias set`
|
|
195
|
+
timeout man history less more source . eval exit alias set shift`
|
|
175
196
|
- **network**: `curl wget ping netstat ss ip ifconfig nslookup dig host`
|
|
176
197
|
- **archives**: `tar gzip gunzip zcat zip unzip`
|
|
177
198
|
|
|
178
199
|
Plus shell syntax: pipes, `&&` / `||` / `;`, redirections (`> >> 2> 2>&1 < &>`, `/dev/null`),
|
|
179
|
-
quoting, `$VAR` `$
|
|
180
|
-
|
|
200
|
+
quoting, `$VAR` `$1` `$#` `"$@"` `set --` `shift`, `${name:-word}` `${name//pat/str}`
|
|
201
|
+
`${name:off:len}` `${name[n]}` `${#name[@]}`, `A=(x y z)` array assignment, `$(...)` command
|
|
202
|
+
substitution, `VAR=x cmd` prefixes, `~` expansion, and POSIX-style path normalization
|
|
203
|
+
(`/tmp`, `/d/foo` → `D:\foo`).
|
|
181
204
|
|
|
182
205
|
Exit codes follow bash conventions: 0 ok, 1 fail, 2 usage/serious, 127 command not found,
|
|
183
206
|
124 timeout.
|
|
@@ -211,11 +234,15 @@ fauxnix optimizes for the commands agents actually run. Documented deviations:
|
|
|
211
234
|
word expansion precedes the temporary environment).
|
|
212
235
|
- `yes` is capped at 65,536 lines — PS 5.1 pipelines cannot signal upstream producers to stop, so
|
|
213
236
|
an unbounded `yes | head` would hang.
|
|
214
|
-
- `tail -f`, `eval`, `alias`, heredocs,
|
|
237
|
+
- `tail -f`, `eval`, `alias`, heredocs,
|
|
215
238
|
`env -i`/`--ignore-environment`,
|
|
216
|
-
|
|
217
|
-
(`
|
|
218
|
-
|
|
239
|
+
background `&`, and stdout redirects (`>` `>>` `&>` `&>>`) on a non-last
|
|
240
|
+
pipeline stage (`echo hi >f | cat`) are rejected with actionable error
|
|
241
|
+
messages instead of misbehaving.
|
|
242
|
+
(`if/then/elif/else/fi`, `for x in ...`, `while`/`until`, `case ... esac` (`;;` only; `;&`/`;;&` fail loud),
|
|
243
|
+
backtick substitution, `command -v`, pipeline `read`,
|
|
244
|
+
dotenv-style `source`, word-level `$((...))` arithmetic expansion, `A=(x y z)` arrays, and
|
|
245
|
+
`${name//pat/str}` / `${name:off:len}` are supported.)
|
|
219
246
|
- `command -v <builtin>` prints `/usr/bin/<name>` where bash prints the bare builtin name;
|
|
220
247
|
exit codes and empty-result semantics match.
|
|
221
248
|
- `chmod` maps only the read-only bit; exec bits are no-ops on Windows. `chown` is a silent no-op
|
|
@@ -245,6 +272,8 @@ npm run build
|
|
|
245
272
|
npx tsx scratch/run.mjs "any bash command" # quick live check
|
|
246
273
|
```
|
|
247
274
|
|
|
275
|
+
Differential vs Git Bash is opt-in (`FAUXNIX_DIFF_ORACLE=1`; skips if unset or `bash.exe` is missing — Git Bash is not required). See [`test/differential/README.md`](test/differential/README.md). This is grown from the 40-case RFC C-7 scaffold, not the 200-case 1.0 gate. The weekly oracle is opt-in via the scheduled workflow (`.github/workflows/differential.yml`) when Git Bash is on the runner.
|
|
276
|
+
|
|
248
277
|
Architecture map: `src/parser.ts` (bash subset → AST) · `src/translator.ts` (AST → PowerShell +
|
|
249
278
|
executor wrapper) · `src/executor.ts` (spawn, redirects, session persistence) ·
|
|
250
279
|
`src/commands/*.ts` (per-command generators) · `src/mcp.ts` (MCP server) · `src/cli.ts`.
|
|
@@ -252,6 +281,11 @@ executor wrapper) · `src/executor.ts` (spawn, redirects, session persistence)
|
|
|
252
281
|
Roadmap: [docs/rfc-roadmap-to-1.0.md](docs/rfc-roadmap-to-1.0.md) — tracks, milestones,
|
|
253
282
|
and the RFC process for proposing waves.
|
|
254
283
|
|
|
284
|
+
## Security
|
|
285
|
+
|
|
286
|
+
Trust model, host protocol, kill semantics, network guard, and reporting:
|
|
287
|
+
[SECURITY.md](SECURITY.md).
|
|
288
|
+
|
|
255
289
|
## License
|
|
256
290
|
|
|
257
291
|
MIT © 20000419
|
package/dist/ast.d.ts
CHANGED
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
* - lists: cmd1 ; cmd2 && cmd3 || cmd4 (newlines act as ';')
|
|
7
7
|
* - redirections: > >> 2> 2>> &> 2>&1 1>&2 <
|
|
8
8
|
* - quoting: 'literal' "interp $VAR $(cmd)"
|
|
9
|
-
* - variables: $VAR ${VAR} ${VAR[n]}
|
|
9
|
+
* - variables: $VAR ${VAR} ${VAR[n]} ${name//pat/str} ${name:off:len}
|
|
10
|
+
* plus special cases ($HOME $USER $PATH $1 $# $@ $0 ...)
|
|
10
11
|
* - command substitution: $(...) and `...` (recursively translated)
|
|
11
12
|
* - arithmetic expansion: $((...)) (existing fx-arith engine)
|
|
12
13
|
* - env assignment prefix: VAR=value cmd
|
|
14
|
+
* - control: if/then/elif/else/fi, for-in, while/until, case
|
|
15
|
+
* - array assignment: A=(x y z) (sidecar FAUXNIX_ARRS)
|
|
13
16
|
*
|
|
14
17
|
* Explicitly unsupported (parser throws a helpful FauxnixError):
|
|
15
|
-
* heredocs, subshells (...), background &,
|
|
18
|
+
* heredocs, subshells (...), background &,
|
|
16
19
|
* globs inside quotes, process substitution <(...).
|
|
17
20
|
*/
|
|
18
21
|
export interface CommandList {
|
|
@@ -25,7 +28,7 @@ export interface ListSegment {
|
|
|
25
28
|
/** ';' for the first segment, otherwise the operator seen before this one. */
|
|
26
29
|
op: ';' | '&&' | '||';
|
|
27
30
|
}
|
|
28
|
-
export type ShellCommand = SimpleCommand | IfCommand | ForCommand;
|
|
31
|
+
export type ShellCommand = SimpleCommand | IfCommand | ForCommand | WhileCommand | CaseCommand;
|
|
29
32
|
export interface Pipeline {
|
|
30
33
|
kind: 'Pipeline';
|
|
31
34
|
commands: ShellCommand[];
|
|
@@ -44,6 +47,24 @@ export interface ForCommand {
|
|
|
44
47
|
body: CommandList;
|
|
45
48
|
redirects: Redirect[];
|
|
46
49
|
}
|
|
50
|
+
export interface WhileCommand {
|
|
51
|
+
kind: 'While';
|
|
52
|
+
/** true for `until TEST; do BODY; done`. */
|
|
53
|
+
until: boolean;
|
|
54
|
+
test: CommandList;
|
|
55
|
+
body: CommandList;
|
|
56
|
+
redirects: Redirect[];
|
|
57
|
+
}
|
|
58
|
+
export interface CaseArm {
|
|
59
|
+
patterns: Word[];
|
|
60
|
+
body: CommandList;
|
|
61
|
+
}
|
|
62
|
+
export interface CaseCommand {
|
|
63
|
+
kind: 'Case';
|
|
64
|
+
word: Word;
|
|
65
|
+
arms: CaseArm[];
|
|
66
|
+
redirects: Redirect[];
|
|
67
|
+
}
|
|
47
68
|
export interface SimpleCommand {
|
|
48
69
|
kind: 'SimpleCommand';
|
|
49
70
|
/** `VAR=value` prefixes before the command name. */
|
|
@@ -55,7 +76,10 @@ export interface SimpleCommand {
|
|
|
55
76
|
}
|
|
56
77
|
export interface Assignment {
|
|
57
78
|
name: string;
|
|
79
|
+
/** Scalar value; for arrays, first element (or empty Word) so export paths stay valid. */
|
|
58
80
|
value: Word;
|
|
81
|
+
/** Set ⇒ bash array assignment `A=(x y z)`. */
|
|
82
|
+
values?: Word[];
|
|
59
83
|
}
|
|
60
84
|
export type RedirectOp = '>' | '>>' | '2>' | '2>>' | '&>' | '&>>' | '2>&1' | '1>&2' | '<';
|
|
61
85
|
export interface Redirect {
|
|
@@ -86,6 +110,17 @@ export type WordPart = {
|
|
|
86
110
|
};
|
|
87
111
|
/** `${#name}` / `${#name[@]}` — string/array length expansion. */
|
|
88
112
|
length?: boolean;
|
|
113
|
+
/** `${name/pat/str}` (first) / `${name//pat/str}` (global). Pattern is a bash glob. */
|
|
114
|
+
replace?: {
|
|
115
|
+
global: boolean;
|
|
116
|
+
pat: string;
|
|
117
|
+
repl: string;
|
|
118
|
+
};
|
|
119
|
+
/** `${name:offset}` / `${name:offset:length}` — scalar substring. */
|
|
120
|
+
slice?: {
|
|
121
|
+
offset: string;
|
|
122
|
+
length?: string;
|
|
123
|
+
};
|
|
89
124
|
} | {
|
|
90
125
|
kind: 'CmdSub';
|
|
91
126
|
cmd: string;
|
package/dist/ast.js
CHANGED
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
* - lists: cmd1 ; cmd2 && cmd3 || cmd4 (newlines act as ';')
|
|
7
7
|
* - redirections: > >> 2> 2>> &> 2>&1 1>&2 <
|
|
8
8
|
* - quoting: 'literal' "interp $VAR $(cmd)"
|
|
9
|
-
* - variables: $VAR ${VAR} ${VAR[n]}
|
|
9
|
+
* - variables: $VAR ${VAR} ${VAR[n]} ${name//pat/str} ${name:off:len}
|
|
10
|
+
* plus special cases ($HOME $USER $PATH $1 $# $@ $0 ...)
|
|
10
11
|
* - command substitution: $(...) and `...` (recursively translated)
|
|
11
12
|
* - arithmetic expansion: $((...)) (existing fx-arith engine)
|
|
12
13
|
* - env assignment prefix: VAR=value cmd
|
|
14
|
+
* - control: if/then/elif/else/fi, for-in, while/until, case
|
|
15
|
+
* - array assignment: A=(x y z) (sidecar FAUXNIX_ARRS)
|
|
13
16
|
*
|
|
14
17
|
* Explicitly unsupported (parser throws a helpful FauxnixError):
|
|
15
|
-
* heredocs, subshells (...), background &,
|
|
18
|
+
* heredocs, subshells (...), background &,
|
|
16
19
|
* globs inside quotes, process substitution <(...).
|
|
17
20
|
*/
|
|
18
21
|
export function wordToString(w) {
|
|
@@ -37,6 +40,23 @@ function partToString(p) {
|
|
|
37
40
|
case 'DoubleQuoted':
|
|
38
41
|
return p.parts.map(partToString).join('');
|
|
39
42
|
case 'Var':
|
|
43
|
+
if (p.replace) {
|
|
44
|
+
const sep = p.replace.global ? '//' : '/';
|
|
45
|
+
return `\${${p.name}${sep}${p.replace.pat}/${p.replace.repl}}`;
|
|
46
|
+
}
|
|
47
|
+
if (p.slice) {
|
|
48
|
+
return p.slice.length !== undefined
|
|
49
|
+
? `\${${p.name}:${p.slice.offset}:${p.slice.length}}`
|
|
50
|
+
: `\${${p.name}:${p.slice.offset}}`;
|
|
51
|
+
}
|
|
52
|
+
if (p.length) {
|
|
53
|
+
return p.index !== undefined
|
|
54
|
+
? `\${#${p.name}[${p.index}]}`
|
|
55
|
+
: `\${#${p.name}}`;
|
|
56
|
+
}
|
|
57
|
+
if (p.param) {
|
|
58
|
+
return `\${${p.name}${p.param.op}${p.param.word}}`;
|
|
59
|
+
}
|
|
40
60
|
return p.index !== undefined ? `\${${p.name}[${p.index}]}` : `$${p.name}`;
|
|
41
61
|
case 'CmdSub':
|
|
42
62
|
return '$(' + p.cmd + ')';
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import './commands/install-all.js';
|
|
2
|
+
export declare const USAGE = "fauxnix \u2014 run Linux-style commands on Windows via PowerShell translation\n\nUsage:\n fauxnix \"ls -la | head -5\" translate + execute a bash-style command\n fauxnix -c \"cmd\" same as above\n fauxnix translate \"cmd\" show the PowerShell translation only\n fauxnix mcp start the MCP stdio server (for agent harnesses)\n fauxnix list list translated commands\n fauxnix list --json same list as machine-readable capability metadata\n fauxnix list --markdown CommandSpec tables (same text as docs/command-specs.md)\n fauxnix check verify the local PowerShell environment\n fauxnix doctor check + encoding, harness config, MCP readiness\n fauxnix install --claude|--codex|--opencode|--kimi|--qwen\n fauxnix --version\n\nNotes:\n Unknown commands (git, node, npm, python, cargo, ...) pass through and run natively.";
|
|
2
3
|
export declare function runCli(argv: string[]): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,11 @@ import { translateCommandList } from './translator.js';
|
|
|
5
5
|
import { listCommandsJson, registeredNames, specsMarkdown } from './registry.js';
|
|
6
6
|
import { encodeCommand } from './encoding.js';
|
|
7
7
|
import { startMcpServer } from './mcp.js';
|
|
8
|
+
import { collectDoctorReport } from './doctor.js';
|
|
9
|
+
import { runInstall } from './install.js';
|
|
8
10
|
import { packageVersion } from './version.js';
|
|
9
11
|
import './commands/install-all.js';
|
|
10
|
-
const USAGE = `fauxnix — run Linux-style commands on Windows via PowerShell translation
|
|
12
|
+
export const USAGE = `fauxnix — run Linux-style commands on Windows via PowerShell translation
|
|
11
13
|
|
|
12
14
|
Usage:
|
|
13
15
|
fauxnix "ls -la | head -5" translate + execute a bash-style command
|
|
@@ -18,6 +20,8 @@ Usage:
|
|
|
18
20
|
fauxnix list --json same list as machine-readable capability metadata
|
|
19
21
|
fauxnix list --markdown CommandSpec tables (same text as docs/command-specs.md)
|
|
20
22
|
fauxnix check verify the local PowerShell environment
|
|
23
|
+
fauxnix doctor check + encoding, harness config, MCP readiness
|
|
24
|
+
fauxnix install --claude|--codex|--opencode|--kimi|--qwen
|
|
21
25
|
fauxnix --version
|
|
22
26
|
|
|
23
27
|
Notes:
|
|
@@ -52,6 +56,18 @@ export async function runCli(argv) {
|
|
|
52
56
|
await runCheck();
|
|
53
57
|
return;
|
|
54
58
|
}
|
|
59
|
+
if (verb === 'doctor') {
|
|
60
|
+
await runDoctor();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (verb === 'install') {
|
|
64
|
+
const result = runInstall(rest);
|
|
65
|
+
for (const line of result.lines)
|
|
66
|
+
console.log(line);
|
|
67
|
+
if (!result.ok)
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
55
71
|
if (verb === 'mcp') {
|
|
56
72
|
await startMcpServer();
|
|
57
73
|
return;
|
|
@@ -80,6 +96,14 @@ export async function runCli(argv) {
|
|
|
80
96
|
process.exit(result.exitCode);
|
|
81
97
|
}
|
|
82
98
|
const PS_ARGS = ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass'];
|
|
99
|
+
async function runDoctor() {
|
|
100
|
+
await runCheck();
|
|
101
|
+
const report = await collectDoctorReport();
|
|
102
|
+
for (const line of report.lines)
|
|
103
|
+
console.log(line);
|
|
104
|
+
if (!report.ok)
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
}
|
|
83
107
|
async function runCheck() {
|
|
84
108
|
console.log('powershell : powershell.exe (Windows built-in)');
|
|
85
109
|
const probeCmd = '$PSVersionTable.PSVersion.ToString()';
|
package/dist/commands/archive.js
CHANGED
|
@@ -201,6 +201,8 @@ const gunzip = (args, ctx) => gzBlock(args, ctx, { decompress: true });
|
|
|
201
201
|
const zcat = (args, ctx) => gzBlock(args, ctx, { decompress: true, stdout: true });
|
|
202
202
|
/* ------------------------------------------------------------------ */
|
|
203
203
|
/* tar — Windows 10+ ships bsdtar as tar.exe, pass everything through */
|
|
204
|
+
/* Intentionally unspec'd: registerSpec fail-loud would reject GNU */
|
|
205
|
+
/* flags bsdtar accepts (--numeric-owner, …). Same class as find. */
|
|
204
206
|
/* ------------------------------------------------------------------ */
|
|
205
207
|
const tar = (args) => {
|
|
206
208
|
return [
|
|
@@ -374,8 +376,87 @@ const unzip = (args) => {
|
|
|
374
376
|
].join('\n');
|
|
375
377
|
};
|
|
376
378
|
/* ------------------------------------------------------------------ */
|
|
377
|
-
/*
|
|
379
|
+
/* CommandSpec (C-5 archive slice) */
|
|
380
|
+
/* tar stays unspec'd: fx-native to tar.exe; unknown GNU flags must */
|
|
381
|
+
/* reach bsdtar. gzip -f is a no-op today and the stdin-from-terminal */
|
|
382
|
+
/* message advertises it, so it is unsupported (force from terminal) */
|
|
383
|
+
/* rather than a silent ignore. */
|
|
378
384
|
/* ------------------------------------------------------------------ */
|
|
385
|
+
const gzipOptions = [
|
|
386
|
+
{ short: 'd', long: '--decompress', support: 'implemented' },
|
|
387
|
+
{ long: '--uncompress', support: 'implemented' },
|
|
388
|
+
{ short: 'k', long: '--keep', support: 'implemented' },
|
|
389
|
+
{ short: 'c', long: '--stdout', support: 'implemented' },
|
|
390
|
+
{ long: '--to-stdout', support: 'implemented' },
|
|
391
|
+
{ short: 't', long: '--test', support: 'implemented' },
|
|
392
|
+
{ short: '1', long: '--fast', support: 'implemented' },
|
|
393
|
+
{ short: '2', support: 'implemented' },
|
|
394
|
+
{ short: '3', support: 'implemented' },
|
|
395
|
+
{ short: '4', support: 'implemented' },
|
|
396
|
+
{ short: '5', support: 'implemented' },
|
|
397
|
+
{ short: '6', support: 'implemented' },
|
|
398
|
+
{ short: '7', support: 'implemented' },
|
|
399
|
+
{ short: '8', support: 'implemented' },
|
|
400
|
+
{ short: '9', long: '--best', support: 'implemented' },
|
|
401
|
+
{ short: 'f', long: '--force', support: 'unsupported', reason: 'force from terminal' },
|
|
402
|
+
{ short: 'q', long: '--quiet', support: 'unsupported', reason: 'quiet' },
|
|
403
|
+
{ short: 'v', long: '--verbose', support: 'unsupported', reason: 'verbose' },
|
|
404
|
+
{ short: 'n', long: '--no-name', support: 'unsupported', reason: 'no-name' },
|
|
405
|
+
{ short: 'r', long: '--recursive', support: 'unsupported', reason: 'recursive' },
|
|
406
|
+
{ short: 'S', long: '--suffix', takesValue: true, support: 'unsupported', reason: 'suffix' },
|
|
407
|
+
];
|
|
408
|
+
export const specs = [
|
|
409
|
+
{
|
|
410
|
+
names: ['gzip'],
|
|
411
|
+
options: gzipOptions,
|
|
412
|
+
effects: ['read', 'write', 'delete'],
|
|
413
|
+
platform: 'windows-ps51',
|
|
414
|
+
dispatch: 'translated',
|
|
415
|
+
handler: gzip,
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
names: ['gunzip'],
|
|
419
|
+
options: gzipOptions,
|
|
420
|
+
effects: ['read', 'write', 'delete'],
|
|
421
|
+
platform: 'windows-ps51',
|
|
422
|
+
dispatch: 'translated',
|
|
423
|
+
handler: gunzip,
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
names: ['zcat'],
|
|
427
|
+
options: gzipOptions,
|
|
428
|
+
effects: ['read'],
|
|
429
|
+
platform: 'windows-ps51',
|
|
430
|
+
dispatch: 'translated',
|
|
431
|
+
handler: zcat,
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
names: ['zip'],
|
|
435
|
+
options: [
|
|
436
|
+
// Compress-Archive always recurses directory inputs; -q is already quiet.
|
|
437
|
+
{ short: 'r', support: 'implemented' },
|
|
438
|
+
{ short: 'q', support: 'implemented' },
|
|
439
|
+
{ short: 'x', long: '--exclude', takesValue: true, support: 'unsupported', reason: 'exclude patterns' },
|
|
440
|
+
],
|
|
441
|
+
effects: ['read', 'write'],
|
|
442
|
+
platform: 'windows-ps51',
|
|
443
|
+
dispatch: 'translated',
|
|
444
|
+
handler: zip,
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
names: ['unzip'],
|
|
448
|
+
options: [
|
|
449
|
+
{ short: 'l', support: 'implemented' },
|
|
450
|
+
{ short: 'o', support: 'implemented' },
|
|
451
|
+
{ short: 'q', support: 'implemented' },
|
|
452
|
+
{ short: 'd', long: '--directory', takesValue: true, support: 'implemented' },
|
|
453
|
+
],
|
|
454
|
+
effects: ['read', 'write'],
|
|
455
|
+
platform: 'windows-ps51',
|
|
456
|
+
dispatch: 'translated',
|
|
457
|
+
handler: unzip,
|
|
458
|
+
},
|
|
459
|
+
];
|
|
379
460
|
export const handlers = {
|
|
380
461
|
tar,
|
|
381
462
|
gzip,
|
|
@@ -4,7 +4,7 @@ import { handlers as textFilters, specs as textFilterSpecs } from './text-filter
|
|
|
4
4
|
import { handlers as textIo, specs as textIoSpecs } from './text-io.js';
|
|
5
5
|
import { handlers as sysinfo } from './sysinfo.js';
|
|
6
6
|
import { handlers as net } from './net.js';
|
|
7
|
-
import { handlers as archive } from './archive.js';
|
|
7
|
+
import { handlers as archive, specs as archiveSpecs } from './archive.js';
|
|
8
8
|
/** Register every built-in Linux command translator. Side-effectful import. */
|
|
9
9
|
export function installAll() {
|
|
10
10
|
registerAll(files);
|
|
@@ -16,5 +16,6 @@ export function installAll() {
|
|
|
16
16
|
registerSpecs(fileSpecs);
|
|
17
17
|
registerSpecs(textIoSpecs);
|
|
18
18
|
registerSpecs(textFilterSpecs);
|
|
19
|
+
registerSpecs(archiveSpecs);
|
|
19
20
|
}
|
|
20
21
|
installAll();
|
package/dist/commands/sysinfo.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FauxnixParseError, isUnquotedLiteral, wordToString } from '../ast.js';
|
|
2
2
|
import { lookup, parseWords, psStr, registeredNames } from '../registry.js';
|
|
3
|
-
import { argListExpr, exprOfWord, operandExpr, translateSimple, wrapTempEnv, encodeSetValExpr, escapeDq, translateCmdSub, normalizeLiteralPath, pathExpr, paramExpr, varExpr, arithExpr, setArithHelperPreamble, } from '../translator.js';
|
|
3
|
+
import { argListExpr, exprOfWord, operandExpr, translateSimple, wrapTempEnv, encodeSetValExpr, escapeDq, translateCmdSub, normalizeLiteralPath, pathExpr, paramExpr, varExpr, varExtraOf, arithExpr, setArithHelperPreamble, isSpecialShellVar, } from '../translator.js';
|
|
4
4
|
import { handlers as textIoHandlers } from './text-io.js';
|
|
5
5
|
/* ------------------------------------------------------------------ */
|
|
6
6
|
/* Shared TS helpers */
|
|
@@ -210,7 +210,7 @@ const env = (args, ctx) => {
|
|
|
210
210
|
}
|
|
211
211
|
if (t === '-i' || t === '--ignore-environment') {
|
|
212
212
|
return ('[Console]::Error.WriteLine(' +
|
|
213
|
-
psStr('fauxnix: env -i/--ignore-environment is not supported (would silently keep inherited secrets)') +
|
|
213
|
+
psStr('fauxnix: env -i/--ignore-environment is not supported (would silently keep inherited secrets). Use env -u NAME or unset first instead.') +
|
|
214
214
|
'); $script:fx_exit = 2');
|
|
215
215
|
}
|
|
216
216
|
if (t === '-u' || t === '--unset') {
|
|
@@ -1398,6 +1398,10 @@ function kshExprOfWord(w) {
|
|
|
1398
1398
|
return arithExpr(expanded[0].parts);
|
|
1399
1399
|
}
|
|
1400
1400
|
if (!tilde && expanded.length === 1 && expanded[0].kind === 'Var') {
|
|
1401
|
+
const extra = varExtraOf(expanded[0]);
|
|
1402
|
+
if (extra && (extra.replace || extra.slice)) {
|
|
1403
|
+
return varExpr(expanded[0].name, expanded[0].index, expanded[0].param, expanded[0].length === true, extra);
|
|
1404
|
+
}
|
|
1401
1405
|
if (expanded[0].param) {
|
|
1402
1406
|
return paramExpr(expanded[0].name, expanded[0].param.op, expanded[0].param.word);
|
|
1403
1407
|
}
|
|
@@ -1407,6 +1411,8 @@ function kshExprOfWord(w) {
|
|
|
1407
1411
|
if (expanded[0].index !== undefined) {
|
|
1408
1412
|
return '(fx-subget ' + psStr(expanded[0].name) + ' ' + psStr(expanded[0].index) + ')';
|
|
1409
1413
|
}
|
|
1414
|
+
if (isSpecialShellVar(expanded[0].name))
|
|
1415
|
+
return varExpr(expanded[0].name);
|
|
1410
1416
|
return '(fx-envget ' + psStr(expanded[0].name) + ')';
|
|
1411
1417
|
}
|
|
1412
1418
|
const literal = !tilde && expanded.every((p) => p.kind === 'Text' || p.kind === 'SingleQuoted');
|
|
@@ -1427,14 +1433,28 @@ function kshExprOfWord(w) {
|
|
|
1427
1433
|
for (const q of p.parts)
|
|
1428
1434
|
emitPart(q);
|
|
1429
1435
|
break;
|
|
1430
|
-
case 'Var':
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1436
|
+
case 'Var': {
|
|
1437
|
+
const extra = varExtraOf(p);
|
|
1438
|
+
if (extra && (extra.replace || extra.slice)) {
|
|
1439
|
+
out += '$(' + varExpr(p.name, p.index, p.param, p.length === true, extra) + ')';
|
|
1440
|
+
}
|
|
1441
|
+
else if (p.param) {
|
|
1442
|
+
out += '$(' + paramExpr(p.name, p.param.op, p.param.word) + ')';
|
|
1443
|
+
}
|
|
1444
|
+
else if (p.length) {
|
|
1445
|
+
out += '$(' + varExpr(p.name, p.index, undefined, true) + ')';
|
|
1446
|
+
}
|
|
1447
|
+
else if (p.index !== undefined) {
|
|
1448
|
+
out += '$(fx-subget ' + psStr(p.name) + ' ' + psStr(p.index) + ')';
|
|
1449
|
+
}
|
|
1450
|
+
else if (isSpecialShellVar(p.name)) {
|
|
1451
|
+
out += '$(' + varExpr(p.name) + ')';
|
|
1452
|
+
}
|
|
1453
|
+
else {
|
|
1454
|
+
out += '$(fx-envget ' + psStr(p.name) + ')';
|
|
1455
|
+
}
|
|
1437
1456
|
break;
|
|
1457
|
+
}
|
|
1438
1458
|
case 'CmdSub':
|
|
1439
1459
|
// [[ ]] does not IFS-split, so keep the newline contract.
|
|
1440
1460
|
out += '$(' + translateCmdSub(p.cmd, true) + ')';
|
|
@@ -2478,7 +2498,7 @@ const source = (args) => {
|
|
|
2478
2498
|
].join('\n');
|
|
2479
2499
|
};
|
|
2480
2500
|
const evalCmd = () => {
|
|
2481
|
-
return ("[Console]::Error.WriteLine('fauxnix: eval is not supported; pass the command itself'); $script:fx_exit = 1");
|
|
2501
|
+
return ("[Console]::Error.WriteLine('fauxnix: eval is not supported; pass the command itself instead'); $script:fx_exit = 1");
|
|
2482
2502
|
};
|
|
2483
2503
|
const exitCmd = (args) => {
|
|
2484
2504
|
const w = args[0];
|
|
@@ -2510,9 +2530,15 @@ const alias = (args) => {
|
|
|
2510
2530
|
});
|
|
2511
2531
|
if (!has)
|
|
2512
2532
|
return '';
|
|
2513
|
-
return "[Console]::Error.WriteLine('fauxnix: alias is not supported'); $script:fx_exit = 1";
|
|
2533
|
+
return "[Console]::Error.WriteLine('fauxnix: alias is not supported. Invoke the real command instead.'); $script:fx_exit = 1";
|
|
2514
2534
|
};
|
|
2515
2535
|
const set = (args) => {
|
|
2536
|
+
if (args.length > 0 && wordToString(args[0]) === '--') {
|
|
2537
|
+
return [
|
|
2538
|
+
'$fx_pv = [object[]](' + argListExpr(args.slice(1)) + ')',
|
|
2539
|
+
'fx-posset $fx_pv',
|
|
2540
|
+
].join('\n');
|
|
2541
|
+
}
|
|
2516
2542
|
const raw = args.map(wordToString);
|
|
2517
2543
|
const unsupported = raw.filter((t) => t === '-e' ||
|
|
2518
2544
|
t === '-u' ||
|
|
@@ -2529,11 +2555,20 @@ const set = (args) => {
|
|
|
2529
2555
|
/^-.*[eux]/.test(t));
|
|
2530
2556
|
if (unsupported.length > 0) {
|
|
2531
2557
|
return ('[Console]::Error.WriteLine(' +
|
|
2532
|
-
psStr('fauxnix: set -e/-u/-x is not supported (would silently lie); use explicit || exit') +
|
|
2558
|
+
psStr('fauxnix: set -e/-u/-x is not supported (would silently lie); use explicit || exit instead') +
|
|
2533
2559
|
'); $script:fx_exit = 2');
|
|
2534
2560
|
}
|
|
2535
2561
|
return '';
|
|
2536
2562
|
};
|
|
2563
|
+
const shiftCmd = (args) => {
|
|
2564
|
+
if (args.length === 0)
|
|
2565
|
+
return 'fx-posshift 1';
|
|
2566
|
+
return [
|
|
2567
|
+
'$fx_sn = [string](' + exprOfWord(args[0]) + ')',
|
|
2568
|
+
"if ($fx_sn -notmatch '^-?[0-9]+$') { [Console]::Error.WriteLine('bash: shift: ' + $fx_sn + ': numeric argument required'); $script:fx_exit = 1 }",
|
|
2569
|
+
'else { fx-posshift $fx_sn }',
|
|
2570
|
+
].join('\n');
|
|
2571
|
+
};
|
|
2537
2572
|
/* ------------------------------------------------------------------ */
|
|
2538
2573
|
export const handlers = {
|
|
2539
2574
|
cd,
|
|
@@ -2582,4 +2617,5 @@ export const handlers = {
|
|
|
2582
2617
|
exit: exitCmd,
|
|
2583
2618
|
alias,
|
|
2584
2619
|
set,
|
|
2620
|
+
shift: shiftCmd,
|
|
2585
2621
|
};
|
|
@@ -2289,8 +2289,9 @@ function parseCutList(list) {
|
|
|
2289
2289
|
const cut = (args) => {
|
|
2290
2290
|
const { flags, longs, values, operandWords } = parseWords(args, ['d', 'f', 'c', 'b'], []);
|
|
2291
2291
|
const complement = longs.has('--complement');
|
|
2292
|
-
|
|
2293
|
-
const
|
|
2292
|
+
// -f/-c/-b are value-taking; parseWords records them in `values`, not `flags`.
|
|
2293
|
+
const charsMode = values.has('-c') || values.has('-b');
|
|
2294
|
+
const fieldsMode = values.has('-f');
|
|
2294
2295
|
const suppress = flags.has('s');
|
|
2295
2296
|
if (charsMode && fieldsMode) {
|
|
2296
2297
|
throw new FauxnixParseError('fauxnix: cut only one type of list may be specified');
|
|
@@ -2527,7 +2528,67 @@ export const specs = [
|
|
|
2527
2528
|
usageExit: 2,
|
|
2528
2529
|
handler: grep,
|
|
2529
2530
|
},
|
|
2531
|
+
{
|
|
2532
|
+
names: ['sort'],
|
|
2533
|
+
options: [
|
|
2534
|
+
{ short: 'r', long: '--reverse', support: 'implemented' },
|
|
2535
|
+
{ short: 'n', long: '--numeric-sort', support: 'implemented' },
|
|
2536
|
+
{ short: 'u', long: '--unique', support: 'implemented' },
|
|
2537
|
+
{ short: 'f', long: '--ignore-case', support: 'implemented' },
|
|
2538
|
+
{ short: 'b', long: '--ignore-leading-blanks', support: 'implemented' },
|
|
2539
|
+
{ short: 't', takesValue: true, support: 'implemented' },
|
|
2540
|
+
{ short: 'k', takesValue: true, support: 'implemented' },
|
|
2541
|
+
{ short: 'z', long: '--zero-terminated', support: 'unsupported', reason: 'NUL-terminated records' },
|
|
2542
|
+
],
|
|
2543
|
+
effects: ['read'],
|
|
2544
|
+
platform: 'windows-ps51',
|
|
2545
|
+
dispatch: 'translated',
|
|
2546
|
+
usageExit: 2,
|
|
2547
|
+
handler: sort,
|
|
2548
|
+
},
|
|
2549
|
+
{
|
|
2550
|
+
names: ['uniq'],
|
|
2551
|
+
options: [
|
|
2552
|
+
{ short: 'c', support: 'implemented' },
|
|
2553
|
+
{ short: 'd', support: 'implemented' },
|
|
2554
|
+
{ short: 'u', support: 'implemented' },
|
|
2555
|
+
{ short: 'i', support: 'implemented' },
|
|
2556
|
+
],
|
|
2557
|
+
effects: ['read'],
|
|
2558
|
+
platform: 'windows-ps51',
|
|
2559
|
+
dispatch: 'translated',
|
|
2560
|
+
handler: uniq,
|
|
2561
|
+
},
|
|
2562
|
+
{
|
|
2563
|
+
names: ['cut'],
|
|
2564
|
+
options: [
|
|
2565
|
+
{ short: 'd', takesValue: true, support: 'implemented' },
|
|
2566
|
+
{ short: 'f', takesValue: true, support: 'implemented' },
|
|
2567
|
+
{ short: 'c', takesValue: true, support: 'implemented' },
|
|
2568
|
+
{ short: 'b', takesValue: true, support: 'implemented' },
|
|
2569
|
+
{ short: 's', support: 'implemented' },
|
|
2570
|
+
{ long: '--complement', support: 'implemented' },
|
|
2571
|
+
],
|
|
2572
|
+
effects: ['read'],
|
|
2573
|
+
platform: 'windows-ps51',
|
|
2574
|
+
dispatch: 'translated',
|
|
2575
|
+
handler: cut,
|
|
2576
|
+
},
|
|
2577
|
+
{
|
|
2578
|
+
names: ['tr'],
|
|
2579
|
+
options: [
|
|
2580
|
+
{ short: 'd', support: 'implemented' },
|
|
2581
|
+
{ short: 's', support: 'implemented' },
|
|
2582
|
+
{ short: 'c', long: '--complement', support: 'unsupported', reason: 'complement' },
|
|
2583
|
+
{ short: 'C', support: 'unsupported', reason: 'complement' },
|
|
2584
|
+
],
|
|
2585
|
+
effects: ['read'],
|
|
2586
|
+
platform: 'windows-ps51',
|
|
2587
|
+
dispatch: 'translated',
|
|
2588
|
+
handler: tr,
|
|
2589
|
+
},
|
|
2530
2590
|
];
|
|
2591
|
+
/** sed/awk stay unspec'd (custom script parsers). egrep injects -E and stays its own handler. */
|
|
2531
2592
|
export const handlers = {
|
|
2532
2593
|
egrep: (args, ctx) => grep([[{ kind: 'Text', text: '-E' }], ...args], ctx), // egrep = grep -E
|
|
2533
2594
|
sed,
|