fauxnix-cli 0.1.0 → 0.2.1
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 +21 -1
- package/dist/cli.js +1 -1
- package/dist/encoding.d.ts +14 -3
- package/dist/encoding.js +13 -3
- package/dist/executor.js +16 -14
- package/dist/mcp.js +11 -11
- package/dist/translator.js +9 -2
- package/package.json +58 -57
package/README.md
CHANGED
|
@@ -78,12 +78,15 @@ fauxnix ships an MCP stdio server exposing a `bash` tool (plus `fauxnix_translat
|
|
|
78
78
|
claude mcp add fauxnix -- fauxnix mcp
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
**Codex** (`~/.codex/config.toml`)
|
|
81
|
+
**Codex** (`~/.codex/config.toml` or `codex mcp add fauxnix -- fauxnix mcp`)
|
|
82
82
|
```toml
|
|
83
83
|
[mcp_servers.fauxnix]
|
|
84
84
|
command = "fauxnix"
|
|
85
85
|
args = ["mcp"]
|
|
86
86
|
```
|
|
87
|
+
Note: in non-interactive `codex exec` mode, MCP tool calls are auto-denied by
|
|
88
|
+
the approval layer; pass `--dangerously-bypass-approvals-and-sandbox` (or run
|
|
89
|
+
interactively and approve once).
|
|
87
90
|
|
|
88
91
|
**OpenCode** (`opencode.json`)
|
|
89
92
|
```json
|
|
@@ -94,6 +97,16 @@ args = ["mcp"]
|
|
|
94
97
|
}
|
|
95
98
|
```
|
|
96
99
|
|
|
100
|
+
**Kimi Code** — unlike the others, MCP servers live in a JSON file, not the
|
|
101
|
+
TOML config: `~/.kimi-code/mcp.json`
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"mcpServers": {
|
|
105
|
+
"fauxnix": { "command": "fauxnix", "args": ["mcp"] }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
97
110
|
**Any MCP client** — stdio server: `fauxnix mcp`. The tool name is `bash` (override with
|
|
98
111
|
`FAUXNIX_TOOL_NAME`). Tool description already teaches the model the supported subset, so no
|
|
99
112
|
system-prompt changes are required.
|
|
@@ -160,6 +173,13 @@ fauxnix optimizes for the commands agents actually run. Documented deviations:
|
|
|
160
173
|
"not supported" errors at translate time.
|
|
161
174
|
- `curl`/`wget` refuse loopback/private/reserved addresses (localhost, 127.x, ::1, 10.x,
|
|
162
175
|
172.16–31.x, 192.168.x, 169.254.x) as a safety default for agent-driven HTTP.
|
|
176
|
+
- **Native-tool pipelines vs encoding**: PS 5.1 has a single console-encoding knob, so
|
|
177
|
+
piping localized admin tools (ipconfig, tasklist — GBK on zh-CN) and UTF-8-native dev
|
|
178
|
+
tools (node, curl) cannot both decode cleanly mid-pipeline. Default favors UTF-8 dev
|
|
179
|
+
tools; set `FAUXNIX_NATIVE_ENCODING=ansi` when your agents grep Chinese output of
|
|
180
|
+
native Windows admin tools. **File reads are always sniffed per file** (UTF-8 strict →
|
|
181
|
+
GBK fallback), so grep/sed/awk over GBK *files* works in either mode — unlike Git Bash,
|
|
182
|
+
which only matches the encoding its locale assumes.
|
|
163
183
|
|
|
164
184
|
## Development
|
|
165
185
|
|
package/dist/cli.js
CHANGED
package/dist/encoding.d.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* How PowerShell decodes native tool output mid-pipeline. PS 5.1 has a single
|
|
3
|
+
* console-encoding knob, so GBK-native admin tools (ipconfig, tasklist, ...)
|
|
4
|
+
* and UTF-8-native dev tools (node, curl) cannot both decode cleanly:
|
|
5
|
+
* utf8 (default) — dev tools exact; localized admin tools mojibake
|
|
6
|
+
* ansi — admin tools exact; dev tools' non-ASCII mojibake
|
|
7
|
+
* File reads are unaffected (fx-read byte-sniffs per file).
|
|
4
8
|
*/
|
|
5
|
-
export
|
|
9
|
+
export type NativeEncodingPref = 'utf8' | 'gbk';
|
|
10
|
+
export declare function resolveNativePref(): NativeEncodingPref;
|
|
11
|
+
/**
|
|
12
|
+
* Decode process output per the resolved preference. UTF-8 mode sniffs
|
|
13
|
+
* strictly first (so genuine UTF-8 never falls back); GBK mode trusts the
|
|
14
|
+
* setting (GBK decoding is lenient and cannot be validity-tested).
|
|
15
|
+
*/
|
|
16
|
+
export declare function decodeOutput(buf: Buffer, prefer?: NativeEncodingPref): string;
|
|
6
17
|
/** Encode a PowerShell script for -EncodedCommand (UTF-16LE base64). */
|
|
7
18
|
export declare function encodeCommand(script: string): string;
|
package/dist/encoding.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import iconv from 'iconv-lite';
|
|
2
2
|
const strictUtf8 = new TextDecoder('utf-8', { fatal: true });
|
|
3
|
+
export function resolveNativePref() {
|
|
4
|
+
return process.env.FAUXNIX_NATIVE_ENCODING === 'ansi' ? 'gbk' : 'utf8';
|
|
5
|
+
}
|
|
3
6
|
/**
|
|
4
|
-
* Decode process output
|
|
5
|
-
*
|
|
7
|
+
* Decode process output per the resolved preference. UTF-8 mode sniffs
|
|
8
|
+
* strictly first (so genuine UTF-8 never falls back); GBK mode trusts the
|
|
9
|
+
* setting (GBK decoding is lenient and cannot be validity-tested).
|
|
6
10
|
*/
|
|
7
|
-
export function decodeOutput(buf) {
|
|
11
|
+
export function decodeOutput(buf, prefer = 'utf8') {
|
|
8
12
|
if (buf.length === 0)
|
|
9
13
|
return '';
|
|
14
|
+
if (prefer === 'gbk') {
|
|
15
|
+
let s = iconv.decode(buf, 'gbk');
|
|
16
|
+
if (s.charCodeAt(0) === 0xfeff)
|
|
17
|
+
s = s.slice(1);
|
|
18
|
+
return s;
|
|
19
|
+
}
|
|
10
20
|
try {
|
|
11
21
|
let s = strictUtf8.decode(buf);
|
|
12
22
|
if (s.charCodeAt(0) === 0xfeff)
|
package/dist/executor.js
CHANGED
|
@@ -4,7 +4,7 @@ import { promises as fs, readFileSync, writeFileSync, existsSync } from 'node:fs
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { normalizeLiteralPath } from './translator.js';
|
|
7
|
-
import { decodeOutput, encodeCommand } from './encoding.js';
|
|
7
|
+
import { decodeOutput, encodeCommand, resolveNativePref } from './encoding.js';
|
|
8
8
|
import { normalizeStderr } from './errors.js';
|
|
9
9
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
10
10
|
const PS_ARGS = ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass'];
|
|
@@ -160,16 +160,6 @@ export class FauxnixSession {
|
|
|
160
160
|
return runPlans(plans, this, opts, () => this.syncFromDisk(), this.scriptFile);
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
-
function killTree(pid) {
|
|
164
|
-
if (!pid)
|
|
165
|
-
return;
|
|
166
|
-
try {
|
|
167
|
-
spawn('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
/* best effort */
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
163
|
async function runPlans(plans, session, opts, afterSegment, scriptFile) {
|
|
174
164
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
175
165
|
let stdout = '';
|
|
@@ -224,7 +214,15 @@ async function runPlans(plans, session, opts, afterSegment, scriptFile) {
|
|
|
224
214
|
child.stdin.end();
|
|
225
215
|
const timer = setTimeout(() => {
|
|
226
216
|
running.killed = true;
|
|
227
|
-
|
|
217
|
+
// Node-native termination — no external kill process, nothing injectable.
|
|
218
|
+
// Grandchildren of a timed-out script may survive; the `kill -9`/`pkill`
|
|
219
|
+
// builtins remain available for explicit Windows tree kills.
|
|
220
|
+
try {
|
|
221
|
+
child.kill();
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
/* best effort */
|
|
225
|
+
}
|
|
228
226
|
}, timeoutMs);
|
|
229
227
|
const code = await new Promise((resolve) => {
|
|
230
228
|
child.on('error', (e) => {
|
|
@@ -235,8 +233,12 @@ async function runPlans(plans, session, opts, afterSegment, scriptFile) {
|
|
|
235
233
|
});
|
|
236
234
|
clearTimeout(timer);
|
|
237
235
|
afterSegment();
|
|
238
|
-
|
|
239
|
-
|
|
236
|
+
const decodePref = resolveNativePref();
|
|
237
|
+
// GNU line discipline: PowerShell's console layer terminates every line
|
|
238
|
+
// with CRLF; bash tools expect LF (redirect-written files and byte counts
|
|
239
|
+
// must match coreutils, e.g. `head -2 f > out.txt; wc -c out.txt`)
|
|
240
|
+
let segOut = decodeOutput(Buffer.concat(outBufs), decodePref).replace(/\r\n/g, '\n');
|
|
241
|
+
let segErr = normalizeStderr(decodeOutput(Buffer.concat(errBufs), decodePref)).replace(/\r\n/g, '\n');
|
|
240
242
|
if (running.killed) {
|
|
241
243
|
segErr += '\nbash: command timed out after ' + Math.round(timeoutMs / 1000) + 's';
|
|
242
244
|
}
|
package/dist/mcp.js
CHANGED
|
@@ -7,19 +7,19 @@ import { translateCommandList, wrapScript, translatePipelineBody } from './trans
|
|
|
7
7
|
import { registeredNames } from './registry.js';
|
|
8
8
|
import './commands/install-all.js';
|
|
9
9
|
const TOOL_NAME = process.env.FAUXNIX_TOOL_NAME || 'bash';
|
|
10
|
-
const TOOL_DESCRIPTION = `Execute a Linux/bash-style command on this Windows machine.
|
|
11
|
-
|
|
12
|
-
Commands are deterministically translated to PowerShell and executed natively — no WSL or VM.
|
|
13
|
-
Output is formatted to look like GNU/Linux tooling (ls -l, ps aux, df -h ...), errors look like bash errors, and text encoding (UTF-8/GBK) is handled automatically.
|
|
14
|
-
|
|
15
|
-
Supported: pipes (|), && / || / ;, redirections (> >> 2> 2>&1 < /dev/null), variables ($VAR $HOME ~), command substitution $(...), and ${registeredNames().length}+ coreutils-style commands (${registeredNames().slice(0, 18).join(', ')}...).
|
|
16
|
-
Unknown commands (git, node, npm, python, cargo...) are passed through and executed natively with argv-style quoting.
|
|
17
|
-
Not supported: heredocs, backticks, control flow (if/for/while), background jobs.
|
|
18
|
-
|
|
19
|
-
CWD, environment variables, export/unset and cd persist across calls within this session.
|
|
10
|
+
const TOOL_DESCRIPTION = `Execute a Linux/bash-style command on this Windows machine.
|
|
11
|
+
|
|
12
|
+
Commands are deterministically translated to PowerShell and executed natively — no WSL or VM.
|
|
13
|
+
Output is formatted to look like GNU/Linux tooling (ls -l, ps aux, df -h ...), errors look like bash errors, and text encoding (UTF-8/GBK) is handled automatically.
|
|
14
|
+
|
|
15
|
+
Supported: pipes (|), && / || / ;, redirections (> >> 2> 2>&1 < /dev/null), variables ($VAR $HOME ~), command substitution $(...), and ${registeredNames().length}+ coreutils-style commands (${registeredNames().slice(0, 18).join(', ')}...).
|
|
16
|
+
Unknown commands (git, node, npm, python, cargo...) are passed through and executed natively with argv-style quoting.
|
|
17
|
+
Not supported: heredocs, backticks, control flow (if/for/while), background jobs.
|
|
18
|
+
|
|
19
|
+
CWD, environment variables, export/unset and cd persist across calls within this session — but prefer COMBINING related commands in one call with ; or && (e.g. 'cd src && ls | wc -l'); each call is a fresh translation+process, so batching is faster than many tiny calls.
|
|
20
20
|
Exit codes follow bash conventions (0 ok, 1 fail, 2 usage/serious, 127 command not found, 124 timeout).`;
|
|
21
21
|
export async function startMcpServer() {
|
|
22
|
-
const server = new McpServer({ name: 'fauxnix', version: '0.1
|
|
22
|
+
const server = new McpServer({ name: 'fauxnix', version: '0.2.1' }, { capabilities: { tools: {} } });
|
|
23
23
|
const session = new FauxnixSession();
|
|
24
24
|
server.tool(TOOL_NAME, TOOL_DESCRIPTION, {
|
|
25
25
|
command: z.string().describe('The bash-style command line to run'),
|
package/dist/translator.js
CHANGED
|
@@ -272,9 +272,16 @@ export function wrapScript(body) {
|
|
|
272
272
|
'$fx_exit = 0',
|
|
273
273
|
'$fx_prev = 0',
|
|
274
274
|
'if ($env:FAUXNIX_PREV_EXIT) { try { $fx_prev = [int]$env:FAUXNIX_PREV_EXIT } catch { $fx_prev = 0 } }',
|
|
275
|
-
|
|
275
|
+
// single console-encoding knob in PS 5.1: ansi mode decodes GBK-native
|
|
276
|
+
// admin tools correctly, utf8 mode decodes UTF-8-native dev tools
|
|
277
|
+
// (see encoding.ts — file reads sniff per file and are always right)
|
|
278
|
+
"if ($env:FAUXNIX_NATIVE_ENCODING -eq 'ansi') {",
|
|
279
|
+
" try { [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding(936) } catch {}",
|
|
280
|
+
'} else {',
|
|
281
|
+
' try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}',
|
|
282
|
+
' try { chcp 65001 > $null } catch {}',
|
|
283
|
+
'}',
|
|
276
284
|
'$OutputEncoding = [System.Text.Encoding]::UTF8',
|
|
277
|
-
'try { chcp 65001 > $null } catch {}',
|
|
278
285
|
'if ($env:FAUXNIX_CWD) { try { Set-Location -LiteralPath $env:FAUXNIX_CWD } catch {} }',
|
|
279
286
|
// capture AFTER the session cwd is applied — OLDPWD must refer to the
|
|
280
287
|
// shell's previous directory, not the host process' startup directory
|
package/package.json
CHANGED
|
@@ -1,57 +1,58 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "fauxnix-cli",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Fauxnix — run Linux-style commands on Windows via deterministic PowerShell translation. No VM, no WSL. MCP server + CLI for AI agents.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"fauxnix": "dist/index.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"dist",
|
|
11
|
-
"README.md",
|
|
12
|
-
"LICENSE"
|
|
13
|
-
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "tsc",
|
|
16
|
-
"test": "vitest run",
|
|
17
|
-
"test:watch": "vitest",
|
|
18
|
-
"typecheck": "tsc --noEmit",
|
|
19
|
-
"dev": "tsx src/index.ts"
|
|
20
|
-
},
|
|
21
|
-
"keywords": [
|
|
22
|
-
"bash",
|
|
23
|
-
"powershell",
|
|
24
|
-
"translate",
|
|
25
|
-
"translator",
|
|
26
|
-
"linux",
|
|
27
|
-
"windows",
|
|
28
|
-
"mcp",
|
|
29
|
-
"mcp-server",
|
|
30
|
-
"ai-agent",
|
|
31
|
-
"claude-code",
|
|
32
|
-
"codex",
|
|
33
|
-
"opencode",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "fauxnix-cli",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Fauxnix — run Linux-style commands on Windows via deterministic PowerShell translation. No VM, no WSL. MCP server + CLI for AI agents.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"fauxnix": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"dev": "tsx src/index.ts"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"bash",
|
|
23
|
+
"powershell",
|
|
24
|
+
"translate",
|
|
25
|
+
"translator",
|
|
26
|
+
"linux",
|
|
27
|
+
"windows",
|
|
28
|
+
"mcp",
|
|
29
|
+
"mcp-server",
|
|
30
|
+
"ai-agent",
|
|
31
|
+
"claude-code",
|
|
32
|
+
"codex",
|
|
33
|
+
"opencode",
|
|
34
|
+
"kimi-code",
|
|
35
|
+
"shell",
|
|
36
|
+
"gbk",
|
|
37
|
+
"utf8"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/20000419/fauxnix.git"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
49
|
+
"iconv-lite": "^0.6.3",
|
|
50
|
+
"zod": "^3.24.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^20.14.0",
|
|
54
|
+
"tsx": "^4.19.0",
|
|
55
|
+
"typescript": "^5.5.0",
|
|
56
|
+
"vitest": "^2.1.0"
|
|
57
|
+
}
|
|
58
|
+
}
|