fauxnix-cli 0.9.2 → 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 +54 -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 +85 -3
- package/dist/commands/files.js +4 -2
- package/dist/commands/install-all.js +2 -1
- package/dist/commands/net.js +3 -4
- package/dist/commands/sysinfo.js +48 -12
- package/dist/commands/text-filters.d.ts +1 -0
- package/dist/commands/text-filters.js +96 -38
- package/dist/commands/text-io.js +89 -10
- package/dist/doctor.d.ts +20 -0
- package/dist/doctor.js +251 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +18 -5
- package/dist/executor.js +92 -102
- 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/registry.d.ts +2 -0
- package/dist/registry.js +4 -1
- package/dist/translator.d.ts +29 -3
- package/dist/translator.js +320 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,14 @@ Linux command line — file ops, text processing, process management, archives,
|
|
|
58
58
|
maps cleanly onto PowerShell + .NET. fauxnix implements that subset faithfully and *fails loudly
|
|
59
59
|
and helpfully* on what it can't translate, so the agent never gets silently-wrong results.
|
|
60
60
|
|
|
61
|
+
Labs now train computer-use agents on fleets of real desktops. Reporting in 2026 (*The
|
|
62
|
+
Information*, widely repeated) has OpenAI buying tens of thousands of Mac mini / Mac Studio
|
|
63
|
+
boxes — no screen, no keyboard — to reinforcement-learn agents that click, edit, test, and
|
|
64
|
+
run bash workflows, and Anthropic renting Mac minis through AWS for the same class of work.
|
|
65
|
+
That scoring environment is macOS. Windows users should not have to install a guest Unix to
|
|
66
|
+
keep up: the agent keeps writing bash; fauxnix makes the Windows box answer like the box the
|
|
67
|
+
agent was trained on. See [`docs/rfc-computer-use-windows.md`](docs/rfc-computer-use-windows.md).
|
|
68
|
+
|
|
61
69
|
## Install
|
|
62
70
|
|
|
63
71
|
```bash
|
|
@@ -90,6 +98,10 @@ fauxnix translate "find . -name '*.log' -mtime +7 -delete"
|
|
|
90
98
|
|
|
91
99
|
# check your environment
|
|
92
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
|
|
93
105
|
|
|
94
106
|
# run the MCP stdio server (what agent harnesses connect to)
|
|
95
107
|
fauxnix mcp
|
|
@@ -101,7 +113,11 @@ with argv-style quoting — no string re-parsing, no quoting bugs.
|
|
|
101
113
|
## Use with your agent harness
|
|
102
114
|
|
|
103
115
|
fauxnix ships an MCP stdio server exposing a `bash` tool (plus `fauxnix_translate` and
|
|
104
|
-
`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/)
|
|
105
121
|
|
|
106
122
|
**Claude Code**
|
|
107
123
|
```bash
|
|
@@ -137,12 +153,23 @@ TOML config: `~/.kimi-code/mcp.json`
|
|
|
137
153
|
}
|
|
138
154
|
```
|
|
139
155
|
|
|
156
|
+
**Qwen Code** (`~/.qwen/settings.json`)
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"mcpServers": {
|
|
160
|
+
"fauxnix": { "command": "fauxnix", "args": ["mcp"] }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
140
165
|
**Any MCP client** — stdio server: `fauxnix mcp`. The tool name is `bash` (override with
|
|
141
166
|
`FAUXNIX_TOOL_NAME`). Tool description already teaches the model the supported subset, so no
|
|
142
167
|
system-prompt changes are required.
|
|
143
168
|
|
|
144
|
-
The MCP session persists `cwd`, environment variables, `export`/`unset
|
|
145
|
-
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.
|
|
146
173
|
|
|
147
174
|
## What's translated
|
|
148
175
|
|
|
@@ -156,20 +183,24 @@ development:
|
|
|
156
183
|
|
|
157
184
|
`cp` / `mv` / `rm` / `touch` / `du` / `ls` / `ll` / `mkdir` / `rmdir` / `mktemp` / `ln` /
|
|
158
185
|
`readlink` / `realpath` / `basename` / `dirname` / `stat` / `file` / `df` / `chmod` / `chown` /
|
|
159
|
-
`diff` / `tee` / `grep` / `head`
|
|
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
|
|
160
188
|
usage error instead of being ignored (`find` stays unspec'd so predicates like `-name` still
|
|
161
|
-
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` /
|
|
162
191
|
`head --lines` / `du --max-depth`. `fauxnix list --json` and `docs/command-specs.md` dump the
|
|
163
192
|
same metadata.
|
|
164
193
|
- **shell/system**: `cd pwd export unset env printenv ps kill pkill pgrep sleep which type whoami
|
|
165
194
|
id groups date uname hostname uptime free nproc clear true false test [ [[ : pushd popd dirs sudo
|
|
166
|
-
timeout man history less more source . eval exit alias set`
|
|
195
|
+
timeout man history less more source . eval exit alias set shift`
|
|
167
196
|
- **network**: `curl wget ping netstat ss ip ifconfig nslookup dig host`
|
|
168
197
|
- **archives**: `tar gzip gunzip zcat zip unzip`
|
|
169
198
|
|
|
170
199
|
Plus shell syntax: pipes, `&&` / `||` / `;`, redirections (`> >> 2> 2>&1 < &>`, `/dev/null`),
|
|
171
|
-
quoting, `$VAR` `$
|
|
172
|
-
|
|
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`).
|
|
173
204
|
|
|
174
205
|
Exit codes follow bash conventions: 0 ok, 1 fail, 2 usage/serious, 127 command not found,
|
|
175
206
|
124 timeout.
|
|
@@ -203,11 +234,15 @@ fauxnix optimizes for the commands agents actually run. Documented deviations:
|
|
|
203
234
|
word expansion precedes the temporary environment).
|
|
204
235
|
- `yes` is capped at 65,536 lines — PS 5.1 pipelines cannot signal upstream producers to stop, so
|
|
205
236
|
an unbounded `yes | head` would hang.
|
|
206
|
-
- `tail -f`, `eval`, `alias`, heredocs,
|
|
237
|
+
- `tail -f`, `eval`, `alias`, heredocs,
|
|
207
238
|
`env -i`/`--ignore-environment`,
|
|
208
|
-
|
|
209
|
-
(`
|
|
210
|
-
|
|
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.)
|
|
211
246
|
- `command -v <builtin>` prints `/usr/bin/<name>` where bash prints the bare builtin name;
|
|
212
247
|
exit codes and empty-result semantics match.
|
|
213
248
|
- `chmod` maps only the read-only bit; exec bits are no-ops on Windows. `chown` is a silent no-op
|
|
@@ -237,6 +272,8 @@ npm run build
|
|
|
237
272
|
npx tsx scratch/run.mjs "any bash command" # quick live check
|
|
238
273
|
```
|
|
239
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
|
+
|
|
240
277
|
Architecture map: `src/parser.ts` (bash subset → AST) · `src/translator.ts` (AST → PowerShell +
|
|
241
278
|
executor wrapper) · `src/executor.ts` (spawn, redirects, session persistence) ·
|
|
242
279
|
`src/commands/*.ts` (per-command generators) · `src/mcp.ts` (MCP server) · `src/cli.ts`.
|
|
@@ -244,6 +281,11 @@ executor wrapper) · `src/executor.ts` (spawn, redirects, session persistence)
|
|
|
244
281
|
Roadmap: [docs/rfc-roadmap-to-1.0.md](docs/rfc-roadmap-to-1.0.md) — tracks, milestones,
|
|
245
282
|
and the RFC process for proposing waves.
|
|
246
283
|
|
|
284
|
+
## Security
|
|
285
|
+
|
|
286
|
+
Trust model, host protocol, kill semantics, network guard, and reporting:
|
|
287
|
+
[SECURITY.md](SECURITY.md).
|
|
288
|
+
|
|
247
289
|
## License
|
|
248
290
|
|
|
249
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 [
|
|
@@ -214,8 +216,9 @@ const tar = (args) => {
|
|
|
214
216
|
' if ($fx_c) { $fx_tar = $fx_c.Source } else { $fx_tar = $null }',
|
|
215
217
|
'}',
|
|
216
218
|
'if ($fx_tar) {',
|
|
217
|
-
|
|
218
|
-
|
|
219
|
+
// fx-native captures stdout as pipeline strings and sets fx_exit.
|
|
220
|
+
// [object[]]@(...) keeps an empty argv from unwrapping to $null on PS 5.1.
|
|
221
|
+
' fx-native $fx_tar ([object[]]@($fx_args))',
|
|
219
222
|
'} else {',
|
|
220
223
|
" [Console]::Error.WriteLine('tar: fauxnix: tar.exe not found (Windows 10+ ships bsdtar as tar.exe)')",
|
|
221
224
|
' $script:fx_exit = 1',
|
|
@@ -373,8 +376,87 @@ const unzip = (args) => {
|
|
|
373
376
|
].join('\n');
|
|
374
377
|
};
|
|
375
378
|
/* ------------------------------------------------------------------ */
|
|
376
|
-
/*
|
|
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. */
|
|
377
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
|
+
];
|
|
378
460
|
export const handlers = {
|
|
379
461
|
tar,
|
|
380
462
|
gzip,
|
package/dist/commands/files.js
CHANGED
|
@@ -55,7 +55,7 @@ function psIsLink(it) {
|
|
|
55
55
|
/* ls */
|
|
56
56
|
/* ------------------------------------------------------------------ */
|
|
57
57
|
const ls = (args) => {
|
|
58
|
-
const { flags, longs, values, operandWords } = parseWords(args, [], ['--format']);
|
|
58
|
+
const { flags, longs, values, operandWords } = parseWords(args, [], ['--format', '--color']);
|
|
59
59
|
const long = flags.has('l') || longs.has('--long') || values.get('--format') === 'long';
|
|
60
60
|
const all = flags.has('a') || longs.has('--all');
|
|
61
61
|
const almost = flags.has('A') || longs.has('--almost-all');
|
|
@@ -725,7 +725,7 @@ const find = (args) => {
|
|
|
725
725
|
const preds = raw.slice(pathEnd);
|
|
726
726
|
if (preds.includes('-exec') || preds.includes('-execdir')) {
|
|
727
727
|
return ('[Console]::Error.WriteLine(' +
|
|
728
|
-
psStr('find: -exec is not supported by fauxnix;
|
|
728
|
+
psStr('find: -exec is not supported by fauxnix; use `find . -name "*.log" -delete` or grep -r instead') +
|
|
729
729
|
'); $script:fx_exit = 1');
|
|
730
730
|
}
|
|
731
731
|
const plan = parseFindPreds(preds);
|
|
@@ -967,6 +967,8 @@ export const specs = [
|
|
|
967
967
|
opt('S', undefined),
|
|
968
968
|
opt('r', undefined),
|
|
969
969
|
opt('R', '--recursive', 'unsupported', { reason: 'recursive listing' }),
|
|
970
|
+
// GNU optional WHEN; takesValue so --color=auto is valid. No ANSI.
|
|
971
|
+
opt(undefined, '--color', 'implemented', { takesValue: true }),
|
|
970
972
|
], ls),
|
|
971
973
|
fileSpec(['mkdir'], ['write'], [opt('p', '--parents'), opt('v', '--verbose')], mkdir),
|
|
972
974
|
fileSpec(['rmdir'], ['delete'], [], rmdir),
|
|
@@ -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/net.js
CHANGED
|
@@ -79,10 +79,9 @@ function synthWord(text) {
|
|
|
79
79
|
}
|
|
80
80
|
/** A native-exe invocation obeying the fauxnix contract (string lines + exit code). */
|
|
81
81
|
function nativeCall(exe, argArray) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
].join('\n');
|
|
82
|
+
// fx-native captures stdout as pipeline strings and sets fx_exit.
|
|
83
|
+
// [object[]]@(...) keeps an empty argv from unwrapping to $null on PS 5.1.
|
|
84
|
+
return 'fx-native ' + psStr(exe) + ' ([object[]]@(' + argArray + '))';
|
|
86
85
|
}
|
|
87
86
|
/* ------------------------------------------------------------------ */
|
|
88
87
|
/* curl */
|
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
|
};
|