fauxnix-cli 0.9.3 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -26
- package/dist/ast.d.ts +38 -3
- package/dist/ast.js +22 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +84 -20
- package/dist/commands/archive.d.ts +2 -1
- package/dist/commands/archive.js +169 -25
- package/dist/commands/install-all.js +4 -2
- package/dist/commands/net.d.ts +9 -0
- package/dist/commands/net.js +51 -15
- package/dist/commands/sysinfo.d.ts +2 -1
- package/dist/commands/sysinfo.js +610 -82
- package/dist/commands/text-filters.d.ts +1 -0
- package/dist/commands/text-filters.js +99 -13
- package/dist/commands/text-io.js +72 -43
- package/dist/doctor.d.ts +21 -0
- package/dist/doctor.js +292 -0
- package/dist/errors.js +14 -4
- package/dist/executor.d.ts +4 -1
- package/dist/executor.js +194 -51
- package/dist/install.d.ts +15 -0
- package/dist/install.js +247 -0
- package/dist/mcp.d.ts +19 -0
- package/dist/mcp.js +49 -26
- package/dist/parser.js +432 -35
- package/dist/powershell.d.ts +22 -0
- package/dist/powershell.js +129 -0
- package/dist/ps-host.d.ts +41 -13
- package/dist/ps-host.js +302 -40
- package/dist/qwen-launch.d.ts +12 -0
- package/dist/qwen-launch.js +29 -0
- package/dist/registry.d.ts +22 -0
- package/dist/registry.js +109 -1
- package/dist/translator.d.ts +42 -9
- package/dist/translator.js +697 -95
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -85,6 +85,20 @@ npm install -g .
|
|
|
85
85
|
|
|
86
86
|
Requires: Windows with PowerShell 5.1+ (built-in) and Node.js ≥ 18.
|
|
87
87
|
|
|
88
|
+
PowerShell 7 is an opt-in, CI-tested tier:
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
$env:FAUXNIX_PS = 'pwsh'
|
|
92
|
+
fauxnix check # edition: Core
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Set the variable before starting `fauxnix` or its MCP harness; restart the
|
|
96
|
+
harness after changing it. Windows PowerShell 5.1 remains the default. Invalid
|
|
97
|
+
values and a missing selected executable fail loudly rather than falling back.
|
|
98
|
+
The default is pinned below `SystemRoot`; `pwsh.exe` is resolved once from
|
|
99
|
+
absolute `PATH` directories, excluding the current working directory.
|
|
100
|
+
See [PowerShell 7 support](docs/powershell-7.md).
|
|
101
|
+
|
|
88
102
|
## Quick start
|
|
89
103
|
|
|
90
104
|
```bash
|
|
@@ -98,18 +112,32 @@ fauxnix translate "find . -name '*.log' -mtime +7 -delete"
|
|
|
98
112
|
|
|
99
113
|
# check your environment
|
|
100
114
|
fauxnix check
|
|
115
|
+
fauxnix doctor # check + encoding, harness config, MCP
|
|
116
|
+
|
|
117
|
+
# write user-level MCP config (idempotent; also --codex/--opencode/--kimi/--qwen)
|
|
118
|
+
fauxnix install --claude
|
|
101
119
|
|
|
102
120
|
# run the MCP stdio server (what agent harnesses connect to)
|
|
103
121
|
fauxnix mcp
|
|
104
122
|
```
|
|
105
123
|
|
|
124
|
+
`translate` only renders a script and does not read command operands. Because
|
|
125
|
+
`sed -f` needs a script file, translate-only mode asks you to use `-e` with
|
|
126
|
+
inline script text; normal command execution continues to support `sed -f`.
|
|
127
|
+
|
|
106
128
|
Unknown commands (git, node, npm, python, cargo, gh, docker, ...) are **passed through natively**
|
|
107
|
-
with argv-style quoting
|
|
129
|
+
with argv-style quoting. Windows `.cmd`/`.bat` shims necessarily pass through `cmd.exe`; fauxnix
|
|
130
|
+
preserves its supported punctuation and fails loudly for `%`, embedded double quotes, NUL, and
|
|
131
|
+
line breaks rather than passing a different argument.
|
|
108
132
|
|
|
109
133
|
## Use with your agent harness
|
|
110
134
|
|
|
111
135
|
fauxnix ships an MCP stdio server exposing a `bash` tool (plus `fauxnix_translate` and
|
|
112
|
-
`fauxnix_session`). Point any MCP-capable harness at it
|
|
136
|
+
`fauxnix_session`). Point any MCP-capable harness at it with
|
|
137
|
+
`fauxnix install --claude` (or `--codex` / `--opencode` / `--kimi` / `--qwen`).
|
|
138
|
+
Idempotent; prints what changed. Manual configs below.
|
|
139
|
+
|
|
140
|
+
- **Quickstarts** — copy-paste config + a 10-command smoke: [`docs/examples/`](docs/examples/)
|
|
113
141
|
|
|
114
142
|
**Claude Code**
|
|
115
143
|
```bash
|
|
@@ -145,12 +173,23 @@ TOML config: `~/.kimi-code/mcp.json`
|
|
|
145
173
|
}
|
|
146
174
|
```
|
|
147
175
|
|
|
176
|
+
**Qwen Code** (`~/.qwen/settings.json`)
|
|
177
|
+
```bash
|
|
178
|
+
fauxnix install --qwen
|
|
179
|
+
```
|
|
180
|
+
The installer preserves the rest of `settings.json` and writes an absolute
|
|
181
|
+
Node + package-entry launcher so Qwen startup does not depend on its working
|
|
182
|
+
directory or `PATH` order. See [the Qwen example](docs/examples/qwen.md) for
|
|
183
|
+
the generated JSON shape.
|
|
184
|
+
|
|
148
185
|
**Any MCP client** — stdio server: `fauxnix mcp`. The tool name is `bash` (override with
|
|
149
186
|
`FAUXNIX_TOOL_NAME`). Tool description already teaches the model the supported subset, so no
|
|
150
187
|
system-prompt changes are required.
|
|
151
188
|
|
|
152
|
-
The MCP session persists `cwd`, environment variables, `export`/`unset
|
|
153
|
-
tool calls — it behaves like a logged-in
|
|
189
|
+
The MCP session persists `cwd`, environment variables, `export`/`unset`, `cd -`/OLDPWD, and
|
|
190
|
+
positional parameters (`set --` / `$1` / `"$@"`) across tool calls — it behaves like a logged-in
|
|
191
|
+
shell, not a stateless `exec`. `$0` is the MCP tool name (`bash` / `FAUXNIX_TOOL_NAME`), not a
|
|
192
|
+
Windows path.
|
|
154
193
|
|
|
155
194
|
## What's translated
|
|
156
195
|
|
|
@@ -158,26 +197,30 @@ tool calls — it behaves like a logged-in shell, not a stateless `exec`.
|
|
|
158
197
|
development:
|
|
159
198
|
|
|
160
199
|
- **files**: `ls cp mv rm mkdir rmdir touch mktemp ln readlink realpath basename dirname stat file du df find chmod chown diff`
|
|
161
|
-
- **text filters**: `grep egrep sed awk sort uniq cut tr` — sed/awk scripts are parsed
|
|
162
|
-
|
|
200
|
+
- **text filters**: `grep egrep sed awk sort uniq cut tr` — sed/awk scripts are parsed while
|
|
201
|
+
preparing an executable plan (unsupported constructs throw named errors, never silently
|
|
202
|
+
misbehave); inspect-only `translate` keeps `sed -f` file reads out of that path
|
|
163
203
|
- **text I/O**: `echo printf cat head tail wc tee nl tac md5sum sha1sum sha256sum base64 seq yes xargs`
|
|
164
204
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
compile
|
|
170
|
-
`
|
|
171
|
-
|
|
205
|
+
The curated **agent-daily 60** carry a `CommandSpec`: unknown options fail with a GNU-style
|
|
206
|
+
usage error instead of being ignored. The generated [`docs/command-specs.md`](docs/command-specs.md)
|
|
207
|
+
is the exact list, coverage count, option table, and exclusion rationale; `fauxnix list --json`
|
|
208
|
+
exposes the same per-command metadata. `find` stays unspec'd so predicates like `-name` still
|
|
209
|
+
compile; `sed`/`awk`/`egrep` keep their command-specific parsers; `tar` remains native to
|
|
210
|
+
`tar.exe` so supported bsdtar options reach the executable. Implemented GNU holes include
|
|
211
|
+
`cp -n` / `mv -n` / `touch -c` / `tee --append` / `grep -m` / `head --lines` /
|
|
212
|
+
`du --max-depth` / `env -u` / `ps -f` / `command -V` / `date --date=@SECONDS`.
|
|
172
213
|
- **shell/system**: `cd pwd export unset env printenv ps kill pkill pgrep sleep which type whoami
|
|
173
214
|
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`
|
|
215
|
+
timeout man history less more source . eval exit alias set shift`
|
|
175
216
|
- **network**: `curl wget ping netstat ss ip ifconfig nslookup dig host`
|
|
176
217
|
- **archives**: `tar gzip gunzip zcat zip unzip`
|
|
177
218
|
|
|
178
219
|
Plus shell syntax: pipes, `&&` / `||` / `;`, redirections (`> >> 2> 2>&1 < &>`, `/dev/null`),
|
|
179
|
-
quoting, `$VAR` `$
|
|
180
|
-
|
|
220
|
+
quoting, `$VAR` `$1` `$#` `"$@"` `set --` `shift`, `${name:-word}` `${name//pat/str}`
|
|
221
|
+
`${name:off:len}` `${name[n]}` `${#name[@]}`, `A=(x y z)` array assignment, `$(...)` command
|
|
222
|
+
substitution, `VAR=x cmd` prefixes, `~` expansion, and POSIX-style path normalization
|
|
223
|
+
(`/tmp`, `/d/foo` → `D:\foo`).
|
|
181
224
|
|
|
182
225
|
Exit codes follow bash conventions: 0 ok, 1 fail, 2 usage/serious, 127 command not found,
|
|
183
226
|
124 timeout.
|
|
@@ -185,9 +228,9 @@ Exit codes follow bash conventions: 0 ok, 1 fail, 2 usage/serious, 127 command n
|
|
|
185
228
|
## How it works
|
|
186
229
|
|
|
187
230
|
```
|
|
188
|
-
bash command ──parser──▶ AST ──translator──▶ PowerShell script ──executor──▶
|
|
231
|
+
bash command ──parser──▶ AST ──translator──▶ PowerShell script ──executor──▶ selected PowerShell
|
|
189
232
|
│
|
|
190
|
-
agent ◀── GNU-style output, bash-style errors ◀──
|
|
233
|
+
agent ◀── GNU-style output, bash-style errors ◀── UTF-8 framed host protocol ◀┘
|
|
191
234
|
```
|
|
192
235
|
|
|
193
236
|
- **Deterministic translation, zero LLM calls** at runtime.
|
|
@@ -195,9 +238,10 @@ agent ◀── GNU-style output, bash-style errors ◀── decoder (UTF-8 →
|
|
|
195
238
|
"Fauxnix contract": string-per-line stdout, `[Console]::Error.WriteLine` for bash-style
|
|
196
239
|
stderr, `$script:fx_exit` for exit codes, `$input` for stdin.
|
|
197
240
|
- The executor wraps every script with UTF-8 enforcement (`[Console]::OutputEncoding`,
|
|
198
|
-
`$OutputEncoding`, `chcp 65001`), decodes output
|
|
199
|
-
|
|
200
|
-
common PowerShell errors (including zh-CN locale
|
|
241
|
+
`$OutputEncoding`, `chcp 65001`), decodes native output at the process boundary (UTF-8 by
|
|
242
|
+
default or GBK(936) in `ansi` mode), keeps host frames UTF-8, strips CLIXML serialization and
|
|
243
|
+
PowerShell noise from stderr, and rewrites common PowerShell errors (including zh-CN locale
|
|
244
|
+
messages) into bash phrasing.
|
|
201
245
|
- Scripts run via `-EncodedCommand` (UTF-16LE) and transparently fall back to a temp `.ps1` file
|
|
202
246
|
when the 32 KB command-line limit would be exceeded.
|
|
203
247
|
|
|
@@ -211,11 +255,17 @@ fauxnix optimizes for the commands agents actually run. Documented deviations:
|
|
|
211
255
|
word expansion precedes the temporary environment).
|
|
212
256
|
- `yes` is capped at 65,536 lines — PS 5.1 pipelines cannot signal upstream producers to stop, so
|
|
213
257
|
an unbounded `yes | head` would hang.
|
|
214
|
-
- `tail -f`, `eval`, `alias`, heredocs,
|
|
258
|
+
- `tail -f`, `eval`, `alias`, heredocs,
|
|
215
259
|
`env -i`/`--ignore-environment`,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
260
|
+
background `&`, and output/fd redirects (`>` `>>` `2>` `2>>` `&>` `&>>`
|
|
261
|
+
`2>&1` `1>&2`) on a non-last pipeline stage (`echo hi >f | cat`) are
|
|
262
|
+
rejected with operation-specific, actionable error messages instead of
|
|
263
|
+
misbehaving. Per-stage `<` remains supported; fully routed per-stage output
|
|
264
|
+
fds are still tracked by #157.
|
|
265
|
+
(`if/then/elif/else/fi`, `for x in ...`, `while`/`until`, `case ... esac` (`;;` only; `;&`/`;;&` fail loud),
|
|
266
|
+
backtick substitution, `command -v`, pipeline `read`,
|
|
267
|
+
dotenv-style `source`, word-level `$((...))` arithmetic expansion, `A=(x y z)` arrays, and
|
|
268
|
+
`${name//pat/str}` / `${name:off:len}` are supported.)
|
|
219
269
|
- `command -v <builtin>` prints `/usr/bin/<name>` where bash prints the bare builtin name;
|
|
220
270
|
exit codes and empty-result semantics match.
|
|
221
271
|
- `chmod` maps only the read-only bit; exec bits are no-ops on Windows. `chown` is a silent no-op
|
|
@@ -238,13 +288,16 @@ fauxnix optimizes for the commands agents actually run. Documented deviations:
|
|
|
238
288
|
|
|
239
289
|
## Development
|
|
240
290
|
|
|
241
|
-
```
|
|
291
|
+
```powershell
|
|
242
292
|
npm install
|
|
243
293
|
npm test # unit + real-PowerShell integration suite (Windows only, auto-skipped elsewhere)
|
|
294
|
+
$env:FAUXNIX_PS = 'pwsh'; npm test # same suite through PowerShell 7
|
|
244
295
|
npm run build
|
|
245
296
|
npx tsx scratch/run.mjs "any bash command" # quick live check
|
|
246
297
|
```
|
|
247
298
|
|
|
299
|
+
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). The 253-case corpus enforces the RFC C-7 minimum of 200 cases and a 95% identity gate. The weekly oracle runs from `.github/workflows/differential.yml`; two consecutive green **scheduled** runs are still required release evidence after this gate lands.
|
|
300
|
+
|
|
248
301
|
Architecture map: `src/parser.ts` (bash subset → AST) · `src/translator.ts` (AST → PowerShell +
|
|
249
302
|
executor wrapper) · `src/executor.ts` (spawn, redirects, session persistence) ·
|
|
250
303
|
`src/commands/*.ts` (per-command generators) · `src/mcp.ts` (MCP server) · `src/cli.ts`.
|
|
@@ -252,6 +305,11 @@ executor wrapper) · `src/executor.ts` (spawn, redirects, session persistence)
|
|
|
252
305
|
Roadmap: [docs/rfc-roadmap-to-1.0.md](docs/rfc-roadmap-to-1.0.md) — tracks, milestones,
|
|
253
306
|
and the RFC process for proposing waves.
|
|
254
307
|
|
|
308
|
+
## Security
|
|
309
|
+
|
|
310
|
+
Trust model, host protocol, kill semantics, network guard, and reporting:
|
|
311
|
+
[SECURITY.md](SECURITY.md).
|
|
312
|
+
|
|
255
313
|
## License
|
|
256
314
|
|
|
257
315
|
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,4 @@
|
|
|
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 FAUXNIX_PS=pwsh selects the opt-in PowerShell 7 host (5.1 is the default).\n Unknown commands (git, node, npm, python, cargo, ...) pass through and run natively.";
|
|
2
3
|
export declare function runCli(argv: string[]): Promise<void>;
|
|
4
|
+
export declare function runCheck(): Promise<boolean>;
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { FauxnixSession } from './executor.js';
|
|
3
3
|
import { parseCommand } from './parser.js';
|
|
4
|
-
import { translateCommandList } from './translator.js';
|
|
4
|
+
import { EXECUTE_TRANSLATION, PURE_TRANSLATION, 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';
|
|
11
|
+
import { POWERSHELL_ARGS, powerShellDisplay, powerShellMissingMessage, resolvePowerShell, } from './powershell.js';
|
|
9
12
|
import './commands/install-all.js';
|
|
10
|
-
const USAGE = `fauxnix — run Linux-style commands on Windows via PowerShell translation
|
|
13
|
+
export const USAGE = `fauxnix — run Linux-style commands on Windows via PowerShell translation
|
|
11
14
|
|
|
12
15
|
Usage:
|
|
13
16
|
fauxnix "ls -la | head -5" translate + execute a bash-style command
|
|
@@ -18,9 +21,12 @@ Usage:
|
|
|
18
21
|
fauxnix list --json same list as machine-readable capability metadata
|
|
19
22
|
fauxnix list --markdown CommandSpec tables (same text as docs/command-specs.md)
|
|
20
23
|
fauxnix check verify the local PowerShell environment
|
|
24
|
+
fauxnix doctor check + encoding, harness config, MCP readiness
|
|
25
|
+
fauxnix install --claude|--codex|--opencode|--kimi|--qwen
|
|
21
26
|
fauxnix --version
|
|
22
27
|
|
|
23
28
|
Notes:
|
|
29
|
+
FAUXNIX_PS=pwsh selects the opt-in PowerShell 7 host (5.1 is the default).
|
|
24
30
|
Unknown commands (git, node, npm, python, cargo, ...) pass through and run natively.`;
|
|
25
31
|
export async function runCli(argv) {
|
|
26
32
|
if (argv.length === 0) {
|
|
@@ -52,6 +58,18 @@ export async function runCli(argv) {
|
|
|
52
58
|
await runCheck();
|
|
53
59
|
return;
|
|
54
60
|
}
|
|
61
|
+
if (verb === 'doctor') {
|
|
62
|
+
await runDoctor();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (verb === 'install') {
|
|
66
|
+
const result = runInstall(rest);
|
|
67
|
+
for (const line of result.lines)
|
|
68
|
+
console.log(line);
|
|
69
|
+
if (!result.ok)
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
55
73
|
if (verb === 'mcp') {
|
|
56
74
|
await startMcpServer();
|
|
57
75
|
return;
|
|
@@ -59,7 +77,7 @@ export async function runCli(argv) {
|
|
|
59
77
|
if (verb === 'translate') {
|
|
60
78
|
const cmd = rest.join(' ');
|
|
61
79
|
const list = parseCommand(cmd);
|
|
62
|
-
const plans = translateCommandList(list);
|
|
80
|
+
const plans = translateCommandList(list, PURE_TRANSLATION);
|
|
63
81
|
console.log(plans.map((p) => p.script).join('\n# ---- next segment ----\n'));
|
|
64
82
|
return;
|
|
65
83
|
}
|
|
@@ -69,7 +87,7 @@ export async function runCli(argv) {
|
|
|
69
87
|
return;
|
|
70
88
|
}
|
|
71
89
|
const list = parseCommand(cmd);
|
|
72
|
-
const plans = translateCommandList(list);
|
|
90
|
+
const plans = translateCommandList(list, EXECUTE_TRANSLATION);
|
|
73
91
|
const session = new FauxnixSession();
|
|
74
92
|
const result = await session.run(plans);
|
|
75
93
|
if (result.stdout)
|
|
@@ -79,28 +97,74 @@ export async function runCli(argv) {
|
|
|
79
97
|
await session.dispose();
|
|
80
98
|
process.exit(result.exitCode);
|
|
81
99
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
100
|
+
async function runDoctor() {
|
|
101
|
+
const checkOk = await runCheck();
|
|
102
|
+
const report = await collectDoctorReport();
|
|
103
|
+
for (const line of report.lines)
|
|
104
|
+
console.log(line);
|
|
105
|
+
if (!checkOk || !report.ok)
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
}
|
|
108
|
+
export async function runCheck() {
|
|
109
|
+
const selection = resolvePowerShell();
|
|
110
|
+
console.log('powershell : ' + powerShellDisplay(selection));
|
|
111
|
+
if (selection.error) {
|
|
112
|
+
console.error('status : FAILED');
|
|
113
|
+
console.error(selection.error);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const probeCmd = '[Console]::Out.WriteLine($PSVersionTable.PSVersion.ToString()); ' +
|
|
118
|
+
'[Console]::Out.WriteLine([string]$PSVersionTable.PSEdition)';
|
|
119
|
+
const probe = spawn(selection.executable, [...POWERSHELL_ARGS, '-EncodedCommand', encodeCommand(probeCmd)], {
|
|
87
120
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
88
121
|
windowsHide: true,
|
|
89
122
|
});
|
|
90
|
-
probe.on('error', (e) => {
|
|
91
|
-
console.error('status : FAILED to run powershell.exe: ' + e.message);
|
|
92
|
-
process.exit(1);
|
|
93
|
-
});
|
|
94
123
|
let out = '';
|
|
124
|
+
let err = '';
|
|
95
125
|
probe.stdout.on('data', (d) => (out += d.toString('utf8')));
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
126
|
+
probe.stderr.on('data', (d) => (err += d.toString('utf8')));
|
|
127
|
+
const outcome = await new Promise((resolve) => {
|
|
128
|
+
let settled = false;
|
|
129
|
+
const done = (value) => {
|
|
130
|
+
if (settled)
|
|
131
|
+
return;
|
|
132
|
+
settled = true;
|
|
133
|
+
resolve(value);
|
|
134
|
+
};
|
|
135
|
+
probe.once('error', (error) => done({ code: 127, error }));
|
|
136
|
+
probe.once('close', (code) => done({ code: code ?? 1 }));
|
|
137
|
+
});
|
|
138
|
+
if (outcome.error) {
|
|
139
|
+
console.error('status : FAILED');
|
|
140
|
+
if (outcome.error.code === 'ENOENT') {
|
|
141
|
+
console.error(powerShellMissingMessage(selection).trimEnd());
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
console.error(`fauxnix: failed to start ${selection.executable}: ${outcome.error.message}`);
|
|
145
|
+
}
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
if (outcome.code !== 0) {
|
|
150
|
+
console.error(`status : FAILED to run ${selection.executable}`);
|
|
151
|
+
if (err.trim())
|
|
152
|
+
console.error(err.trim());
|
|
153
|
+
process.exitCode = 1;
|
|
154
|
+
return false;
|
|
101
155
|
}
|
|
102
|
-
|
|
103
|
-
|
|
156
|
+
const lines = out.trim().split(/\r?\n/);
|
|
157
|
+
const version = lines[0] ?? '';
|
|
158
|
+
const edition = lines[1] ?? '';
|
|
159
|
+
console.log('version : ' + version);
|
|
160
|
+
console.log('edition : ' + edition);
|
|
161
|
+
if (edition !== selection.expectedEdition) {
|
|
162
|
+
console.error(`status : FAILED: ${selection.executable} reported ${edition || 'no edition'}; ` +
|
|
163
|
+
`expected ${selection.expectedEdition}`);
|
|
104
164
|
process.exitCode = 1;
|
|
165
|
+
return false;
|
|
105
166
|
}
|
|
167
|
+
console.log('commands : ' + registeredNames().length + ' translated, others pass through');
|
|
168
|
+
console.log('status : OK');
|
|
169
|
+
return true;
|
|
106
170
|
}
|