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/dist/install.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { claudeUserConfigPath, codexConfigPath, fauxnixServerNames, hasCodexFauxnix, hasOpenCodeFauxnix, isServerMap, openCodeConfigPath, serverMapHasFauxnix, } from './doctor.js';
|
|
5
|
+
import { resolveQwenLaunchTuple, sameQwenLaunchTuple } from './qwen-launch.js';
|
|
6
|
+
export const INSTALL_FLAGS = ['claude', 'codex', 'opencode', 'kimi', 'qwen'];
|
|
7
|
+
const STDIO = { command: 'fauxnix', args: ['mcp'] };
|
|
8
|
+
const OPENCODE_STDIO = { type: 'local', command: ['fauxnix', 'mcp'] };
|
|
9
|
+
export function kimiConfigPath(home, env) {
|
|
10
|
+
const root = env.KIMI_CODE_HOME?.trim() || join(home, '.kimi-code');
|
|
11
|
+
return join(root, 'mcp.json');
|
|
12
|
+
}
|
|
13
|
+
export function qwenConfigPath(home, env) {
|
|
14
|
+
return join(home, '.qwen', 'settings.json');
|
|
15
|
+
}
|
|
16
|
+
export function runInstall(argv, opts = {}) {
|
|
17
|
+
const parsed = parseHarnessFlags(argv);
|
|
18
|
+
if (parsed.help)
|
|
19
|
+
return { lines: installUsageLines(), ok: true };
|
|
20
|
+
if (parsed.error)
|
|
21
|
+
return { lines: [parsed.error, ...installUsageLines()], ok: false };
|
|
22
|
+
const ctx = {
|
|
23
|
+
home: opts.home ?? homedir(),
|
|
24
|
+
env: opts.env ?? process.env,
|
|
25
|
+
};
|
|
26
|
+
const lines = [];
|
|
27
|
+
let ok = true;
|
|
28
|
+
for (const name of parsed.harnesses) {
|
|
29
|
+
const one = installHarness(name, ctx);
|
|
30
|
+
lines.push(one.line);
|
|
31
|
+
if (!one.ok)
|
|
32
|
+
ok = false;
|
|
33
|
+
}
|
|
34
|
+
return { lines, ok };
|
|
35
|
+
}
|
|
36
|
+
function parseHarnessFlags(argv) {
|
|
37
|
+
if (argv.some((a) => a === '--help' || a === '-h'))
|
|
38
|
+
return { help: true, harnesses: [] };
|
|
39
|
+
if (argv.length === 0) {
|
|
40
|
+
return { error: 'select a harness: --claude --codex --opencode --kimi --qwen', harnesses: [] };
|
|
41
|
+
}
|
|
42
|
+
const harnesses = [];
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
for (const a of argv) {
|
|
45
|
+
if (!a.startsWith('--') || a === '--') {
|
|
46
|
+
return { error: `unknown argument: ${a}`, harnesses: [] };
|
|
47
|
+
}
|
|
48
|
+
const name = a.slice(2);
|
|
49
|
+
if (!isHarness(name))
|
|
50
|
+
return { error: `unknown harness: ${a}`, harnesses: [] };
|
|
51
|
+
if (!seen.has(name)) {
|
|
52
|
+
seen.add(name);
|
|
53
|
+
harnesses.push(name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { harnesses };
|
|
57
|
+
}
|
|
58
|
+
function isHarness(s) {
|
|
59
|
+
return INSTALL_FLAGS.includes(s);
|
|
60
|
+
}
|
|
61
|
+
function installUsageLines() {
|
|
62
|
+
return ['Usage:', ' fauxnix install --claude|--codex|--opencode|--kimi|--qwen'];
|
|
63
|
+
}
|
|
64
|
+
function installHarness(name, ctx) {
|
|
65
|
+
switch (name) {
|
|
66
|
+
case 'claude':
|
|
67
|
+
return patchMcpServers(claudeUserConfigPath(ctx.home, ctx.env), 'claude');
|
|
68
|
+
case 'codex':
|
|
69
|
+
return patchCodex(codexConfigPath(ctx.home, ctx.env));
|
|
70
|
+
case 'opencode':
|
|
71
|
+
return patchOpenCode(openCodeConfigPath(ctx.home, ctx.env));
|
|
72
|
+
case 'kimi':
|
|
73
|
+
return patchMcpServers(kimiConfigPath(ctx.home, ctx.env), 'kimi');
|
|
74
|
+
case 'qwen':
|
|
75
|
+
return patchQwen(qwenConfigPath(ctx.home, ctx.env));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function patchQwen(path) {
|
|
79
|
+
const launch = resolveQwenLaunchTuple();
|
|
80
|
+
if (!launch.ok)
|
|
81
|
+
return { ok: false, line: `qwen: ${launch.reason} — not modified` };
|
|
82
|
+
const read = readJsonObject(path);
|
|
83
|
+
if (read.state === 'invalid') {
|
|
84
|
+
return { ok: false, line: `qwen: ${path} is ${read.reason} — not modified` };
|
|
85
|
+
}
|
|
86
|
+
const existed = read.state !== 'missing';
|
|
87
|
+
const data = read.state === 'ok' ? read.data : {};
|
|
88
|
+
if (data.mcpServers != null && !isServerMap(data.mcpServers)) {
|
|
89
|
+
return { ok: false, line: `qwen: ${path} mcpServers is not an object — not modified` };
|
|
90
|
+
}
|
|
91
|
+
if (!isServerMap(data.mcpServers))
|
|
92
|
+
data.mcpServers = {};
|
|
93
|
+
const servers = data.mcpServers;
|
|
94
|
+
const extraNames = fauxnixServerNames(servers).filter((name) => name !== 'fauxnix');
|
|
95
|
+
if (extraNames.length) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
line: `qwen: ${path} has another fauxnix MCP entry (${extraNames.join(', ')}) — remove the extra entry, then retry; not modified`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const current = servers.fauxnix;
|
|
102
|
+
if (current != null && !isServerMap(current)) {
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
line: `qwen: ${path} mcpServers.fauxnix is not an object — not modified`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (sameQwenLaunchTuple(current, launch.value)) {
|
|
109
|
+
return { ok: true, line: `qwen: already configured with an absolute launcher (${path})` };
|
|
110
|
+
}
|
|
111
|
+
servers.fauxnix = {
|
|
112
|
+
...(isServerMap(current) ? current : {}),
|
|
113
|
+
command: launch.value.command,
|
|
114
|
+
args: [...launch.value.args],
|
|
115
|
+
};
|
|
116
|
+
return writeJson(path, data, 'qwen', existed, current == null ? 'added mcpServers.fauxnix' : 'updated mcpServers.fauxnix launcher');
|
|
117
|
+
}
|
|
118
|
+
function patchMcpServers(path, harness) {
|
|
119
|
+
const read = readJsonObject(path);
|
|
120
|
+
if (read.state === 'invalid') {
|
|
121
|
+
return { ok: false, line: `${harness}: ${path} is ${read.reason} — not modified` };
|
|
122
|
+
}
|
|
123
|
+
const existed = read.state !== 'missing';
|
|
124
|
+
const data = read.state === 'ok' ? read.data : {};
|
|
125
|
+
if (serverMapHasFauxnix(data.mcpServers)) {
|
|
126
|
+
return { ok: true, line: `${harness}: already configured (${path})` };
|
|
127
|
+
}
|
|
128
|
+
if (data.mcpServers != null && !isServerMap(data.mcpServers)) {
|
|
129
|
+
return { ok: false, line: `${harness}: ${path} mcpServers is not an object — not modified` };
|
|
130
|
+
}
|
|
131
|
+
if (!isServerMap(data.mcpServers))
|
|
132
|
+
data.mcpServers = {};
|
|
133
|
+
data.mcpServers.fauxnix = {
|
|
134
|
+
command: STDIO.command,
|
|
135
|
+
args: [...STDIO.args],
|
|
136
|
+
};
|
|
137
|
+
return writeJson(path, data, harness, existed, 'added mcpServers.fauxnix');
|
|
138
|
+
}
|
|
139
|
+
function patchOpenCode(path) {
|
|
140
|
+
const read = readJsonObject(path);
|
|
141
|
+
if (read.state === 'invalid') {
|
|
142
|
+
return { ok: false, line: `opencode: ${path} is ${read.reason} — not modified` };
|
|
143
|
+
}
|
|
144
|
+
const existed = read.state !== 'missing';
|
|
145
|
+
const data = read.state === 'ok' ? read.data : {};
|
|
146
|
+
if (hasOpenCodeFauxnix(data)) {
|
|
147
|
+
return { ok: true, line: `opencode: already configured (${path})` };
|
|
148
|
+
}
|
|
149
|
+
if (data.mcp != null && !isServerMap(data.mcp)) {
|
|
150
|
+
return { ok: false, line: `opencode: ${path} mcp is not an object — not modified` };
|
|
151
|
+
}
|
|
152
|
+
if (!isServerMap(data.mcp))
|
|
153
|
+
data.mcp = {};
|
|
154
|
+
const mcp = data.mcp;
|
|
155
|
+
const payload = { type: OPENCODE_STDIO.type, command: [...OPENCODE_STDIO.command] };
|
|
156
|
+
if (isServerMap(mcp.servers)) {
|
|
157
|
+
mcp.servers.fauxnix = payload;
|
|
158
|
+
return writeJson(path, data, 'opencode', existed, 'added mcp.servers.fauxnix');
|
|
159
|
+
}
|
|
160
|
+
mcp.fauxnix = payload;
|
|
161
|
+
return writeJson(path, data, 'opencode', existed, 'added mcp.fauxnix');
|
|
162
|
+
}
|
|
163
|
+
function patchCodex(path) {
|
|
164
|
+
const existed = existsSync(path);
|
|
165
|
+
if (!existed) {
|
|
166
|
+
const written = writeText(path, tomlTable('\n'));
|
|
167
|
+
if (!written.ok)
|
|
168
|
+
return { ok: false, line: `codex: failed to write ${path}: ${written.error}` };
|
|
169
|
+
return { ok: true, line: `codex: created ${path}` };
|
|
170
|
+
}
|
|
171
|
+
const text = readText(path);
|
|
172
|
+
if (text === undefined) {
|
|
173
|
+
return { ok: false, line: `codex: ${path} is unreadable — not modified` };
|
|
174
|
+
}
|
|
175
|
+
if (hasCodexFauxnix(stripBom(text))) {
|
|
176
|
+
return { ok: true, line: `codex: already configured (${path})` };
|
|
177
|
+
}
|
|
178
|
+
const nl = text.includes('\r\n') ? '\r\n' : '\n';
|
|
179
|
+
if (stripBom(text).trim() === '') {
|
|
180
|
+
const written = writeText(path, tomlTable(nl));
|
|
181
|
+
if (!written.ok)
|
|
182
|
+
return { ok: false, line: `codex: failed to write ${path}: ${written.error}` };
|
|
183
|
+
return { ok: true, line: `codex: patched ${path} (appended [mcp_servers.fauxnix])` };
|
|
184
|
+
}
|
|
185
|
+
let body = text;
|
|
186
|
+
if (!body.endsWith('\n'))
|
|
187
|
+
body += nl;
|
|
188
|
+
if (!body.endsWith(nl + nl))
|
|
189
|
+
body += nl;
|
|
190
|
+
const written = writeText(path, body + tomlTable(nl));
|
|
191
|
+
if (!written.ok)
|
|
192
|
+
return { ok: false, line: `codex: failed to write ${path}: ${written.error}` };
|
|
193
|
+
return { ok: true, line: `codex: patched ${path} (appended [mcp_servers.fauxnix])` };
|
|
194
|
+
}
|
|
195
|
+
function tomlTable(nl) {
|
|
196
|
+
return `[mcp_servers.fauxnix]${nl}command = "fauxnix"${nl}args = ["mcp"]${nl}`;
|
|
197
|
+
}
|
|
198
|
+
function writeJson(path, data, harness, existed, change) {
|
|
199
|
+
const written = writeText(path, JSON.stringify(data, null, 2) + '\n');
|
|
200
|
+
if (!written.ok) {
|
|
201
|
+
return { ok: false, line: `${harness}: failed to write ${path}: ${written.error}` };
|
|
202
|
+
}
|
|
203
|
+
if (existed)
|
|
204
|
+
return { ok: true, line: `${harness}: patched ${path} (${change})` };
|
|
205
|
+
return { ok: true, line: `${harness}: created ${path}` };
|
|
206
|
+
}
|
|
207
|
+
function writeText(path, contents) {
|
|
208
|
+
try {
|
|
209
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
210
|
+
writeFileSync(path, contents, 'utf8');
|
|
211
|
+
return { ok: true };
|
|
212
|
+
}
|
|
213
|
+
catch (e) {
|
|
214
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function readJsonObject(path) {
|
|
218
|
+
if (!existsSync(path))
|
|
219
|
+
return { state: 'missing' };
|
|
220
|
+
const text = readText(path);
|
|
221
|
+
if (text === undefined)
|
|
222
|
+
return { state: 'invalid', reason: 'unreadable' };
|
|
223
|
+
const stripped = stripBom(text).trim();
|
|
224
|
+
if (stripped === '')
|
|
225
|
+
return { state: 'empty' };
|
|
226
|
+
try {
|
|
227
|
+
const data = JSON.parse(stripped);
|
|
228
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
229
|
+
return { state: 'invalid', reason: 'not a JSON object' };
|
|
230
|
+
}
|
|
231
|
+
return { state: 'ok', data: data };
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return { state: 'invalid', reason: 'not valid JSON' };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function stripBom(text) {
|
|
238
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
239
|
+
}
|
|
240
|
+
function readText(path) {
|
|
241
|
+
try {
|
|
242
|
+
return readFileSync(path, 'utf8');
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
}
|
package/dist/mcp.d.ts
CHANGED
|
@@ -19,5 +19,24 @@ export declare function bashToolResult(r: ExecResult, sessionId: string, infra:
|
|
|
19
19
|
sessionId: string;
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
|
+
export declare function translateToolResult(command: string): {
|
|
23
|
+
content: {
|
|
24
|
+
type: "text";
|
|
25
|
+
text: string;
|
|
26
|
+
}[];
|
|
27
|
+
isError?: undefined;
|
|
28
|
+
} | {
|
|
29
|
+
content: {
|
|
30
|
+
type: "text";
|
|
31
|
+
text: string;
|
|
32
|
+
}[];
|
|
33
|
+
isError: true;
|
|
34
|
+
};
|
|
35
|
+
export declare function positionalCountFromEnv(env: Record<string, string>): number;
|
|
36
|
+
export declare function formatSessionStatus(session: {
|
|
37
|
+
cwd: string | null;
|
|
38
|
+
env: Record<string, string>;
|
|
39
|
+
id: string;
|
|
40
|
+
}): string;
|
|
22
41
|
export declare function startMcpServer(): Promise<void>;
|
|
23
42
|
export { translatePipelineBody };
|
package/dist/mcp.js
CHANGED
|
@@ -3,7 +3,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { FauxnixSession } from './executor.js';
|
|
5
5
|
import { parseCommand } from './parser.js';
|
|
6
|
-
import { translateCommandList, wrapScript, translatePipelineBody } from './translator.js';
|
|
6
|
+
import { EXECUTE_TRANSLATION, PURE_TRANSLATION, translateCommandList, wrapScript, translatePipelineBody, } from './translator.js';
|
|
7
7
|
import { registeredNames } from './registry.js';
|
|
8
8
|
import { packageVersion } from './version.js';
|
|
9
9
|
import './commands/install-all.js';
|
|
@@ -31,11 +31,10 @@ const TOOL_DESCRIPTION = `Execute a Linux/bash-style command on this Windows mac
|
|
|
31
31
|
Commands are deterministically translated to PowerShell and executed natively — no WSL or VM.
|
|
32
32
|
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.
|
|
33
33
|
|
|
34
|
-
Supported: pipes (|), && / || / ;, redirections (> >> 2> 2>&1 < /dev/null), variables ($VAR $HOME ~), command substitution $(...), and ${registeredNames().length}+ coreutils-style commands (${registeredNames().slice(0, 18).join(', ')}...).
|
|
34
|
+
Supported: pipes (|), && / || / ;, redirections (> >> 2> 2>&1 < /dev/null), variables ($VAR $HOME $1 $# "$@" ~), set -- / shift, array assignment A=(x y z), \${name[n]} \${#name[@]} \${name//pat/str} \${name:off:len}, command substitution $(...), and ${registeredNames().length}+ coreutils-style commands (${registeredNames().slice(0, 18).join(', ')}...).
|
|
35
35
|
Unknown commands (git, node, npm, python, cargo...) are passed through and executed natively with argv-style quoting.
|
|
36
|
-
Not supported: heredocs,
|
|
37
|
-
|
|
38
|
-
CWD, environment variables, export/unset and cd persist across calls within this session — a resident PowerShell 5.1 host is started when the MCP session begins (and after reset), so the first bash tool call is already warm.
|
|
36
|
+
Not supported: heredocs, env -i/--ignore-environment, background jobs. if/then/elif/else/fi, for-in loops, while/until, case ... esac, and word-level \$((...)) arithmetic expansion are supported.
|
|
37
|
+
CWD, environment variables, export/unset, cd, and positional parameters (set -- / $1 / "$@") persist across calls within this session — a resident PowerShell 5.1 host is started when the MCP session begins (and after reset), so the first bash tool call is already warm.
|
|
39
38
|
Exit codes follow bash conventions (0 ok, 1 fail, 2 usage/serious, 127 command not found, 124 timeout, 130 cancelled). The tool also returns structuredContent (schemaVersion 1) with stdout/stderr/exitCode/timedOut/cancelled/truncated/sessionId.
|
|
40
39
|
|
|
41
40
|
Platform requirement: the execution backend is native Windows PowerShell 5.1+. On hosts without PowerShell on PATH (e.g. Linux containers/sandboxes), the bash tool returns exit code 127 with an actionable error instead of running the command.`;
|
|
@@ -70,7 +69,47 @@ export function bashToolResult(r, sessionId, infra) {
|
|
|
70
69
|
...(infra ? { isError: true } : {}),
|
|
71
70
|
};
|
|
72
71
|
}
|
|
72
|
+
export function translateToolResult(command) {
|
|
73
|
+
try {
|
|
74
|
+
const list = parseCommand(command);
|
|
75
|
+
const plans = translateCommandList(list, PURE_TRANSLATION);
|
|
76
|
+
const script = wrapScript(plans.map((p) => p.script).join('\n# ---- next segment ----\n'));
|
|
77
|
+
return { content: [{ type: 'text', text: script }] };
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
81
|
+
return { content: [{ type: 'text', text: msg }], isError: true };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Packed FAUXNIX_POS uses char-30 separators (same as array sidecar). */
|
|
85
|
+
const POS_SEP = '\x1e';
|
|
86
|
+
export function positionalCountFromEnv(env) {
|
|
87
|
+
let packed;
|
|
88
|
+
for (const [k, v] of Object.entries(env)) {
|
|
89
|
+
if (k.toUpperCase() === 'FAUXNIX_POS') {
|
|
90
|
+
packed = v;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (packed == null || packed === '')
|
|
95
|
+
return 0;
|
|
96
|
+
return packed.split(POS_SEP).length;
|
|
97
|
+
}
|
|
98
|
+
export function formatSessionStatus(session) {
|
|
99
|
+
const envKeys = Object.keys(session.env).sort();
|
|
100
|
+
return ('cwd: ' +
|
|
101
|
+
(session.cwd ?? '(inherit from server start)') +
|
|
102
|
+
'\nenv keys: ' +
|
|
103
|
+
(envKeys.length ? envKeys.join(', ') : '(none tracked)') +
|
|
104
|
+
'\npositionals: ' +
|
|
105
|
+
positionalCountFromEnv(session.env) +
|
|
106
|
+
'\nsession: ' +
|
|
107
|
+
session.id +
|
|
108
|
+
'\ncommands registered: ' +
|
|
109
|
+
registeredNames().length);
|
|
110
|
+
}
|
|
73
111
|
export async function startMcpServer() {
|
|
112
|
+
process.env.FAUXNIX_ARG0 = TOOL_NAME;
|
|
74
113
|
const server = new McpServer({ name: 'fauxnix', version: packageVersion }, { capabilities: { tools: {} } });
|
|
75
114
|
const session = new FauxnixSession();
|
|
76
115
|
await session.prewarm();
|
|
@@ -85,7 +124,7 @@ export async function startMcpServer() {
|
|
|
85
124
|
.describe('Timeout in milliseconds (default 120000)'),
|
|
86
125
|
}, EXEC_ANNOTATIONS, async ({ command, timeout_ms }, extra) => {
|
|
87
126
|
try {
|
|
88
|
-
const plans = translateCommandList(parseCommand(command));
|
|
127
|
+
const plans = translateCommandList(parseCommand(command), EXECUTE_TRANSLATION);
|
|
89
128
|
const result = await session.run(plans, {
|
|
90
129
|
timeoutMs: timeout_ms,
|
|
91
130
|
signal: extra.signal,
|
|
@@ -105,34 +144,18 @@ export async function startMcpServer() {
|
|
|
105
144
|
}, session.id, true);
|
|
106
145
|
}
|
|
107
146
|
});
|
|
108
|
-
server.tool('fauxnix_translate', 'Translate a bash-style command into the equivalent PowerShell script WITHOUT executing it. Useful for learning/debugging what fauxnix does under the hood.', { command: z.string().describe('The bash-style command line to translate (never executed)') }, TRANSLATE_ANNOTATIONS, async ({ command }) =>
|
|
109
|
-
|
|
110
|
-
const list = parseCommand(command);
|
|
111
|
-
const plans = translateCommandList(list);
|
|
112
|
-
const script = wrapScript(plans.map((p) => p.script).join('\n# ---- next segment ----\n'));
|
|
113
|
-
return { content: [{ type: 'text', text: script }] };
|
|
114
|
-
}
|
|
115
|
-
catch (e) {
|
|
116
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
117
|
-
return { content: [{ type: 'text', text: msg }], isError: true };
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
server.tool('fauxnix_session', 'Inspect or reset the persistent fauxnix shell session (current directory, environment, session id). Actions: "status" (default) or "reset".', {
|
|
147
|
+
server.tool('fauxnix_translate', 'Translate a bash-style command into the equivalent PowerShell script WITHOUT executing it. Useful for learning/debugging what fauxnix does under the hood.', { command: z.string().describe('The bash-style command line to translate (never executed)') }, TRANSLATE_ANNOTATIONS, async ({ command }) => translateToolResult(command));
|
|
148
|
+
server.tool('fauxnix_session', 'Inspect or reset the persistent fauxnix shell session (current directory, environment, positional count, session id). Actions: "status" (default) or "reset".', {
|
|
121
149
|
action: z
|
|
122
150
|
.enum(['status', 'reset'])
|
|
123
151
|
.default('status')
|
|
124
|
-
.describe('"status" shows the session state (cwd, tracked env keys); "reset" clears it back to a fresh shell'),
|
|
152
|
+
.describe('"status" shows the session state (cwd, tracked env keys, positional count); "reset" clears it back to a fresh shell'),
|
|
125
153
|
}, SESSION_ANNOTATIONS, async ({ action }) => {
|
|
126
154
|
if (action === 'reset') {
|
|
127
155
|
await session.reset();
|
|
128
156
|
return { content: [{ type: 'text', text: 'fauxnix: session reset' }] };
|
|
129
157
|
}
|
|
130
|
-
|
|
131
|
-
const text = 'cwd: ' + (session.cwd ?? '(inherit from server start)') +
|
|
132
|
-
'\nenv keys: ' + (envKeys.length ? envKeys.join(', ') : '(none tracked)') +
|
|
133
|
-
'\nsession: ' + session.id +
|
|
134
|
-
'\ncommands registered: ' + registeredNames().length;
|
|
135
|
-
return { content: [{ type: 'text', text }] };
|
|
158
|
+
return { content: [{ type: 'text', text: formatSessionStatus(session) }] };
|
|
136
159
|
});
|
|
137
160
|
const transport = new StdioServerTransport();
|
|
138
161
|
let shuttingDown = false;
|