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
|
@@ -583,48 +583,45 @@ const grep = (args) => {
|
|
|
583
583
|
scan.push(' $fx_mleft--');
|
|
584
584
|
if (onlyMatch && !inv) {
|
|
585
585
|
if (fixed) {
|
|
586
|
+
// GNU -o: emit leftmost-longest matches in input order, not per-needle.
|
|
586
587
|
if (ci)
|
|
587
588
|
scan.push(' $lx = $fx_l.ToLower()');
|
|
588
589
|
const hay = ci ? '$lx' : '$fx_l';
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
needle +
|
|
611
|
-
'.Length)) }');
|
|
612
|
-
}
|
|
613
|
-
else {
|
|
614
|
-
scan.push(indent + ' fx-emitline $fx_i (' + hay + '.Substring($p, ' + needle + '.Length))');
|
|
615
|
-
}
|
|
616
|
-
scan.push(indent + ' $p = ' + hay + '.IndexOf(' + needle + ', $p + 1)');
|
|
617
|
-
scan.push(indent + '}');
|
|
618
|
-
};
|
|
619
|
-
if (multiFixed) {
|
|
620
|
-
const arr = ci ? '$fx_needles_ll' : '$fx_needles';
|
|
621
|
-
scan.push(' foreach ($fx_needle in ' + arr + ') {');
|
|
622
|
-
emitFixedHits('$fx_needle', ' ');
|
|
623
|
-
scan.push(' }');
|
|
590
|
+
const needleArr = multiFixed
|
|
591
|
+
? ci
|
|
592
|
+
? '$fx_needles_ll'
|
|
593
|
+
: '$fx_needles'
|
|
594
|
+
: '@(' + (ci ? '$fx_needle_ll' : '$fx_needle') + ')';
|
|
595
|
+
scan.push(' $fx_cands = New-Object System.Collections.Generic.List[object]');
|
|
596
|
+
scan.push(' foreach ($fx_needle in ' + needleArr + ') {');
|
|
597
|
+
scan.push(' if ($fx_needle.Length -lt 1) { continue }');
|
|
598
|
+
scan.push(' $p = ' + hay + '.IndexOf($fx_needle)');
|
|
599
|
+
scan.push(' while ($p -ge 0) {');
|
|
600
|
+
if (word) {
|
|
601
|
+
scan.push(' $ok = $true');
|
|
602
|
+
scan.push(" if ($p -gt 0) { $c = " +
|
|
603
|
+
hay +
|
|
604
|
+
"[$p - 1]; $ok = -not ([char]::IsLetterOrDigit($c) -or $c -eq '_') }");
|
|
605
|
+
scan.push(' if ($ok) { $e = $p + $fx_needle.Length; if ($e -lt ' +
|
|
606
|
+
hay +
|
|
607
|
+
'.Length) { $c = ' +
|
|
608
|
+
hay +
|
|
609
|
+
"[$e]; $ok = -not ([char]::IsLetterOrDigit($c) -or $c -eq '_') } }");
|
|
610
|
+
scan.push(' if ($ok) { [void]$fx_cands.Add([pscustomobject]@{ Start = $p; Len = $fx_needle.Length }) }');
|
|
624
611
|
}
|
|
625
612
|
else {
|
|
626
|
-
|
|
613
|
+
scan.push(' [void]$fx_cands.Add([pscustomobject]@{ Start = $p; Len = $fx_needle.Length })');
|
|
627
614
|
}
|
|
615
|
+
scan.push(' $p = ' + hay + '.IndexOf($fx_needle, $p + 1)');
|
|
616
|
+
scan.push(' }');
|
|
617
|
+
scan.push(' }');
|
|
618
|
+
scan.push(' $fx_end = 0');
|
|
619
|
+
scan.push(' foreach ($fx_c in @($fx_cands | Sort-Object Start, @{ Expression = { $_.Len }; Descending = $true })) {');
|
|
620
|
+
scan.push(' if ($fx_c.Start -ge $fx_end) {');
|
|
621
|
+
scan.push(' fx-emitline $fx_i (' + hay + '.Substring($fx_c.Start, $fx_c.Len))');
|
|
622
|
+
scan.push(' $fx_end = $fx_c.Start + $fx_c.Len');
|
|
623
|
+
scan.push(' }');
|
|
624
|
+
scan.push(' }');
|
|
628
625
|
}
|
|
629
626
|
else {
|
|
630
627
|
scan.push(' foreach ($fx_m in $fx_re.Matches($fx_l)) { fx-emitline $fx_i $fx_m.Value }');
|
|
@@ -2292,8 +2289,9 @@ function parseCutList(list) {
|
|
|
2292
2289
|
const cut = (args) => {
|
|
2293
2290
|
const { flags, longs, values, operandWords } = parseWords(args, ['d', 'f', 'c', 'b'], []);
|
|
2294
2291
|
const complement = longs.has('--complement');
|
|
2295
|
-
|
|
2296
|
-
const
|
|
2292
|
+
// -f/-c/-b are value-taking; parseWords records them in `values`, not `flags`.
|
|
2293
|
+
const charsMode = values.has('-c') || values.has('-b');
|
|
2294
|
+
const fieldsMode = values.has('-f');
|
|
2297
2295
|
const suppress = flags.has('s');
|
|
2298
2296
|
if (charsMode && fieldsMode) {
|
|
2299
2297
|
throw new FauxnixParseError('fauxnix: cut only one type of list may be specified');
|
|
@@ -2530,7 +2528,67 @@ export const specs = [
|
|
|
2530
2528
|
usageExit: 2,
|
|
2531
2529
|
handler: grep,
|
|
2532
2530
|
},
|
|
2531
|
+
{
|
|
2532
|
+
names: ['sort'],
|
|
2533
|
+
options: [
|
|
2534
|
+
{ short: 'r', long: '--reverse', support: 'implemented' },
|
|
2535
|
+
{ short: 'n', long: '--numeric-sort', support: 'implemented' },
|
|
2536
|
+
{ short: 'u', long: '--unique', support: 'implemented' },
|
|
2537
|
+
{ short: 'f', long: '--ignore-case', support: 'implemented' },
|
|
2538
|
+
{ short: 'b', long: '--ignore-leading-blanks', support: 'implemented' },
|
|
2539
|
+
{ short: 't', takesValue: true, support: 'implemented' },
|
|
2540
|
+
{ short: 'k', takesValue: true, support: 'implemented' },
|
|
2541
|
+
{ short: 'z', long: '--zero-terminated', support: 'unsupported', reason: 'NUL-terminated records' },
|
|
2542
|
+
],
|
|
2543
|
+
effects: ['read'],
|
|
2544
|
+
platform: 'windows-ps51',
|
|
2545
|
+
dispatch: 'translated',
|
|
2546
|
+
usageExit: 2,
|
|
2547
|
+
handler: sort,
|
|
2548
|
+
},
|
|
2549
|
+
{
|
|
2550
|
+
names: ['uniq'],
|
|
2551
|
+
options: [
|
|
2552
|
+
{ short: 'c', support: 'implemented' },
|
|
2553
|
+
{ short: 'd', support: 'implemented' },
|
|
2554
|
+
{ short: 'u', support: 'implemented' },
|
|
2555
|
+
{ short: 'i', support: 'implemented' },
|
|
2556
|
+
],
|
|
2557
|
+
effects: ['read'],
|
|
2558
|
+
platform: 'windows-ps51',
|
|
2559
|
+
dispatch: 'translated',
|
|
2560
|
+
handler: uniq,
|
|
2561
|
+
},
|
|
2562
|
+
{
|
|
2563
|
+
names: ['cut'],
|
|
2564
|
+
options: [
|
|
2565
|
+
{ short: 'd', takesValue: true, support: 'implemented' },
|
|
2566
|
+
{ short: 'f', takesValue: true, support: 'implemented' },
|
|
2567
|
+
{ short: 'c', takesValue: true, support: 'implemented' },
|
|
2568
|
+
{ short: 'b', takesValue: true, support: 'implemented' },
|
|
2569
|
+
{ short: 's', support: 'implemented' },
|
|
2570
|
+
{ long: '--complement', support: 'implemented' },
|
|
2571
|
+
],
|
|
2572
|
+
effects: ['read'],
|
|
2573
|
+
platform: 'windows-ps51',
|
|
2574
|
+
dispatch: 'translated',
|
|
2575
|
+
handler: cut,
|
|
2576
|
+
},
|
|
2577
|
+
{
|
|
2578
|
+
names: ['tr'],
|
|
2579
|
+
options: [
|
|
2580
|
+
{ short: 'd', support: 'implemented' },
|
|
2581
|
+
{ short: 's', support: 'implemented' },
|
|
2582
|
+
{ short: 'c', long: '--complement', support: 'unsupported', reason: 'complement' },
|
|
2583
|
+
{ short: 'C', support: 'unsupported', reason: 'complement' },
|
|
2584
|
+
],
|
|
2585
|
+
effects: ['read'],
|
|
2586
|
+
platform: 'windows-ps51',
|
|
2587
|
+
dispatch: 'translated',
|
|
2588
|
+
handler: tr,
|
|
2589
|
+
},
|
|
2533
2590
|
];
|
|
2591
|
+
/** sed/awk stay unspec'd (custom script parsers). egrep injects -E and stays its own handler. */
|
|
2534
2592
|
export const handlers = {
|
|
2535
2593
|
egrep: (args, ctx) => grep([[{ kind: 'Text', text: '-E' }], ...args], ctx), // egrep = grep -E
|
|
2536
2594
|
sed,
|
package/dist/commands/text-io.js
CHANGED
|
@@ -1093,6 +1093,9 @@ const xargs = (args) => {
|
|
|
1093
1093
|
noRunIfEmpty = true;
|
|
1094
1094
|
else if (ch === 't')
|
|
1095
1095
|
trace = true;
|
|
1096
|
+
else if (ch === '0') {
|
|
1097
|
+
return psErrExpr(psStr('xargs: -0 is not supported by fauxnix'));
|
|
1098
|
+
}
|
|
1096
1099
|
else if (ch === 'n' || ch === 'I' || ch === 'L') {
|
|
1097
1100
|
const restv = body.slice(c + 1);
|
|
1098
1101
|
let val;
|
|
@@ -1140,12 +1143,12 @@ const xargs = (args) => {
|
|
|
1140
1143
|
}
|
|
1141
1144
|
const n = chunkN !== null && Number.isFinite(chunkN) && chunkN > 0 ? chunkN : 0;
|
|
1142
1145
|
const replExpr = repl !== null ? psStr(repl) : null;
|
|
1146
|
+
// fx-native already records $script:fx_exit from ExitCode.
|
|
1143
1147
|
const invoke = [
|
|
1144
1148
|
' if (' +
|
|
1145
1149
|
pb(trace) +
|
|
1146
1150
|
") { [Console]::Error.WriteLine(((@($fx_cmd) + @($fx_argv)) -join ' ')) }",
|
|
1147
|
-
'
|
|
1148
|
-
' if ($LASTEXITCODE -ne 0 -and $script:fx_exit -eq 0) { $script:fx_exit = $LASTEXITCODE }',
|
|
1151
|
+
' fx-native $fx_cmd $fx_argv',
|
|
1149
1152
|
];
|
|
1150
1153
|
let dispatch;
|
|
1151
1154
|
const guard = ' if (' + pb(noRunIfEmpty) + ' -and $fx_args.Count -eq 0) { }' + '\n' + ' else {';
|
|
@@ -1154,7 +1157,7 @@ const xargs = (args) => {
|
|
|
1154
1157
|
dispatch = [
|
|
1155
1158
|
guard,
|
|
1156
1159
|
' foreach ($fx_l in $fx_args) {',
|
|
1157
|
-
' $fx_argv = @()',
|
|
1160
|
+
' $fx_argv = [object[]]@()',
|
|
1158
1161
|
' $fx_hit = $false',
|
|
1159
1162
|
' foreach ($fx_a in $fx_base) {',
|
|
1160
1163
|
' if ($fx_a.Contains(' + replExpr + ')) {',
|
|
@@ -1175,7 +1178,7 @@ const xargs = (args) => {
|
|
|
1175
1178
|
' $fx_i = 0',
|
|
1176
1179
|
' $fx_ran = $false',
|
|
1177
1180
|
' while ($fx_i -lt $fx_args.Count) {',
|
|
1178
|
-
' $fx_argv = @($fx_base)',
|
|
1181
|
+
' $fx_argv = [object[]]@($fx_base)',
|
|
1179
1182
|
' $fx_j = 0',
|
|
1180
1183
|
' while ($fx_j -lt ' + n + ' -and $fx_i -lt $fx_args.Count) {',
|
|
1181
1184
|
' $fx_argv += $fx_args[$fx_i]',
|
|
@@ -1185,10 +1188,8 @@ const xargs = (args) => {
|
|
|
1185
1188
|
...invoke,
|
|
1186
1189
|
' }',
|
|
1187
1190
|
' if (-not $fx_ran) {',
|
|
1188
|
-
' $fx_argv = @($fx_base)',
|
|
1189
|
-
'
|
|
1190
|
-
' ' + invoke[1],
|
|
1191
|
-
' ' + invoke[2],
|
|
1191
|
+
' $fx_argv = [object[]]@($fx_base)',
|
|
1192
|
+
...invoke.map((line) => ' ' + line),
|
|
1192
1193
|
' }',
|
|
1193
1194
|
' }',
|
|
1194
1195
|
];
|
|
@@ -1196,17 +1197,27 @@ const xargs = (args) => {
|
|
|
1196
1197
|
else {
|
|
1197
1198
|
dispatch = [
|
|
1198
1199
|
guard,
|
|
1199
|
-
' $fx_argv = @($fx_base) + @($fx_args)',
|
|
1200
|
+
' $fx_argv = [object[]](@($fx_base) + @($fx_args))',
|
|
1200
1201
|
...invoke,
|
|
1201
1202
|
' }',
|
|
1202
1203
|
];
|
|
1203
1204
|
}
|
|
1205
|
+
// Default GNU xargs splits on blanks; -I keeps whole lines as one item.
|
|
1206
|
+
const collectArgs = replExpr !== null
|
|
1207
|
+
? "$fx_args = @($fx_in | Where-Object { $_ -ne '' })"
|
|
1208
|
+
: [
|
|
1209
|
+
'$fx_args = @()',
|
|
1210
|
+
'foreach ($fx_l in $fx_in) {',
|
|
1211
|
+
" if ($fx_l -eq '') { continue }",
|
|
1212
|
+
" $fx_args += @($fx_l -split '[ \\t]+' | Where-Object { $_ -ne '' })",
|
|
1213
|
+
'}',
|
|
1214
|
+
].join('\n');
|
|
1204
1215
|
return [
|
|
1205
1216
|
PS_SPLITLINES_FN,
|
|
1206
1217
|
STDIN_INLINES,
|
|
1207
1218
|
'$fx_tg = ' + argListExpr(target, exprOfWord),
|
|
1208
1219
|
"if ($fx_tg.Count -eq 0) { $fx_cmd = ''; $fx_base = @() } else { $fx_cmd = [string]$fx_tg[0]; $fx_base = $(if ($fx_tg.Count -gt 1) { @($fx_tg[1..($fx_tg.Count - 1)]) } else { @() }) }",
|
|
1209
|
-
|
|
1220
|
+
collectArgs,
|
|
1210
1221
|
...dispatch,
|
|
1211
1222
|
].join('\n');
|
|
1212
1223
|
};
|
|
@@ -1236,6 +1247,74 @@ export const specs = [
|
|
|
1236
1247
|
dispatch: 'translated',
|
|
1237
1248
|
handler: head,
|
|
1238
1249
|
},
|
|
1250
|
+
{
|
|
1251
|
+
names: ['echo'],
|
|
1252
|
+
options: [
|
|
1253
|
+
{ short: 'n', support: 'implemented' },
|
|
1254
|
+
{ short: 'e', support: 'implemented' },
|
|
1255
|
+
{ short: 'E', support: 'implemented' },
|
|
1256
|
+
],
|
|
1257
|
+
effects: [],
|
|
1258
|
+
platform: 'windows-ps51',
|
|
1259
|
+
dispatch: 'translated',
|
|
1260
|
+
usageExit: 2,
|
|
1261
|
+
leadingOptions: true,
|
|
1262
|
+
handler: echo,
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
names: ['printf'],
|
|
1266
|
+
options: [],
|
|
1267
|
+
effects: [],
|
|
1268
|
+
platform: 'windows-ps51',
|
|
1269
|
+
dispatch: 'translated',
|
|
1270
|
+
usageExit: 2,
|
|
1271
|
+
leadingOptions: true,
|
|
1272
|
+
handler: printf,
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
names: ['cat'],
|
|
1276
|
+
options: [
|
|
1277
|
+
{ short: 'n', support: 'implemented' },
|
|
1278
|
+
{ short: 'b', support: 'implemented' },
|
|
1279
|
+
{ short: 's', support: 'implemented' },
|
|
1280
|
+
{ short: 'E', support: 'implemented' },
|
|
1281
|
+
{ short: 'T', support: 'implemented' },
|
|
1282
|
+
{ short: 'A', support: 'implemented' },
|
|
1283
|
+
],
|
|
1284
|
+
effects: ['read'],
|
|
1285
|
+
platform: 'windows-ps51',
|
|
1286
|
+
dispatch: 'translated',
|
|
1287
|
+
handler: cat,
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
names: ['tail'],
|
|
1291
|
+
options: [
|
|
1292
|
+
{ short: 'n', long: '--lines', takesValue: true, support: 'implemented' },
|
|
1293
|
+
{ short: 'c', long: '--bytes', takesValue: true, support: 'implemented' },
|
|
1294
|
+
{ short: 'q', long: '--quiet', support: 'implemented' },
|
|
1295
|
+
{ long: '--silent', support: 'implemented' },
|
|
1296
|
+
{ short: 'v', long: '--verbose', support: 'implemented' },
|
|
1297
|
+
{ short: 'f', support: 'unsupported', reason: 'no persistent tty' },
|
|
1298
|
+
{ short: 'F', support: 'unsupported', reason: 'no persistent tty' },
|
|
1299
|
+
],
|
|
1300
|
+
effects: ['read'],
|
|
1301
|
+
platform: 'windows-ps51',
|
|
1302
|
+
dispatch: 'translated',
|
|
1303
|
+
handler: tail,
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
names: ['wc'],
|
|
1307
|
+
options: [
|
|
1308
|
+
{ short: 'l', support: 'implemented' },
|
|
1309
|
+
{ short: 'w', support: 'implemented' },
|
|
1310
|
+
{ short: 'c', support: 'implemented' },
|
|
1311
|
+
{ short: 'm', support: 'implemented' },
|
|
1312
|
+
],
|
|
1313
|
+
effects: ['read'],
|
|
1314
|
+
platform: 'windows-ps51',
|
|
1315
|
+
dispatch: 'translated',
|
|
1316
|
+
handler: wc,
|
|
1317
|
+
},
|
|
1239
1318
|
];
|
|
1240
1319
|
export const handlers = {
|
|
1241
1320
|
echo,
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type DoctorOptions = {
|
|
2
|
+
home?: string;
|
|
3
|
+
cwd?: string;
|
|
4
|
+
env?: NodeJS.ProcessEnv;
|
|
5
|
+
nodeVersion?: string;
|
|
6
|
+
/** Injected MCP-module loader. Default: dynamic import of ./mcp.js (does not start the server). */
|
|
7
|
+
loadMcp?: () => Promise<unknown>;
|
|
8
|
+
};
|
|
9
|
+
export type DoctorReport = {
|
|
10
|
+
lines: string[];
|
|
11
|
+
ok: boolean;
|
|
12
|
+
};
|
|
13
|
+
export declare function collectDoctorReport(opts?: DoctorOptions): Promise<DoctorReport>;
|
|
14
|
+
export declare function claudeUserConfigPath(home: string, env: NodeJS.ProcessEnv): string;
|
|
15
|
+
export declare function codexConfigPath(home: string, env: NodeJS.ProcessEnv): string;
|
|
16
|
+
export declare function openCodeConfigPath(home: string, env: NodeJS.ProcessEnv): string;
|
|
17
|
+
export declare function hasCodexFauxnix(text: string): boolean;
|
|
18
|
+
export declare function hasOpenCodeFauxnix(data: unknown): boolean;
|
|
19
|
+
export declare function isServerMap(value: unknown): boolean;
|
|
20
|
+
export declare function serverMapHasFauxnix(value: unknown): boolean;
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const VALUE_INDENT = ' ';
|
|
5
|
+
export async function collectDoctorReport(opts = {}) {
|
|
6
|
+
const home = opts.home ?? homedir();
|
|
7
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
8
|
+
const env = opts.env ?? process.env;
|
|
9
|
+
const nodeVersion = opts.nodeVersion ?? process.version;
|
|
10
|
+
const lines = [''];
|
|
11
|
+
lines.push(...encodingLines(env));
|
|
12
|
+
lines.push('');
|
|
13
|
+
lines.push(field('claude', detectClaude(home, cwd, env)));
|
|
14
|
+
lines.push(field('codex', detectCodex(home, env)));
|
|
15
|
+
lines.push(field('opencode', detectOpenCode(home, env)));
|
|
16
|
+
lines.push('');
|
|
17
|
+
const mcp = await mcpLines(nodeVersion, opts.loadMcp);
|
|
18
|
+
lines.push(...mcp.lines);
|
|
19
|
+
return { lines, ok: mcp.ok };
|
|
20
|
+
}
|
|
21
|
+
function field(label, value) {
|
|
22
|
+
return `${label.padEnd(10)} : ${value}`;
|
|
23
|
+
}
|
|
24
|
+
function encodingLines(env) {
|
|
25
|
+
const raw = env.FAUXNIX_NATIVE_ENCODING;
|
|
26
|
+
const current = raw === undefined || raw === ''
|
|
27
|
+
? 'unset → utf8 (default)'
|
|
28
|
+
: raw === 'ansi'
|
|
29
|
+
? 'ansi → GBK-native admin tools'
|
|
30
|
+
: `${raw} → utf8 (only ansi selects GBK)`;
|
|
31
|
+
return [
|
|
32
|
+
field('encoding', 'UTF-8 default for native-tool pipelines'),
|
|
33
|
+
VALUE_INDENT + `current FAUXNIX_NATIVE_ENCODING=${current}`,
|
|
34
|
+
VALUE_INDENT + 'set FAUXNIX_NATIVE_ENCODING=ansi for GBK-native admin tools (ipconfig, tasklist)',
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
function detectClaude(home, cwd, env) {
|
|
38
|
+
const userPath = claudeUserConfigPath(home, env);
|
|
39
|
+
const projectPath = join(cwd, '.mcp.json');
|
|
40
|
+
const userExists = existsSync(userPath);
|
|
41
|
+
const projectExists = existsSync(projectPath);
|
|
42
|
+
let user;
|
|
43
|
+
let project;
|
|
44
|
+
if (userExists)
|
|
45
|
+
user = inspectClaudeJson(userPath);
|
|
46
|
+
if (projectExists) {
|
|
47
|
+
const inspected = inspectClaudeJson(projectPath);
|
|
48
|
+
// Project-scope Claude MCP is always a top-level mcpServers object.
|
|
49
|
+
if (!inspected.parseError && inspected.hasTopLevelMcpServers)
|
|
50
|
+
project = inspected;
|
|
51
|
+
}
|
|
52
|
+
if (!userExists && !project)
|
|
53
|
+
return 'not detected — see README';
|
|
54
|
+
if (user?.hasFauxnix)
|
|
55
|
+
return `fauxnix MCP configured (${userPath})`;
|
|
56
|
+
if (project?.hasFauxnix)
|
|
57
|
+
return `fauxnix MCP configured (${projectPath})`;
|
|
58
|
+
if (userExists && user?.parseError) {
|
|
59
|
+
return `found ${userPath} (unreadable JSON) — see README`;
|
|
60
|
+
}
|
|
61
|
+
if (userExists) {
|
|
62
|
+
return `found ${userPath}, fauxnix MCP not listed — run: claude mcp add fauxnix -- fauxnix mcp`;
|
|
63
|
+
}
|
|
64
|
+
return `found ${projectPath}, fauxnix MCP not listed — see README`;
|
|
65
|
+
}
|
|
66
|
+
export function claudeUserConfigPath(home, env) {
|
|
67
|
+
const dir = env.CLAUDE_CONFIG_DIR?.trim();
|
|
68
|
+
if (dir)
|
|
69
|
+
return join(dir, '.claude.json');
|
|
70
|
+
return join(home, '.claude.json');
|
|
71
|
+
}
|
|
72
|
+
export function codexConfigPath(home, env) {
|
|
73
|
+
const codexHome = env.CODEX_HOME?.trim() || join(home, '.codex');
|
|
74
|
+
return join(codexHome, 'config.toml');
|
|
75
|
+
}
|
|
76
|
+
export function openCodeConfigPath(home, env) {
|
|
77
|
+
const xdg = env.XDG_CONFIG_HOME?.trim() || join(home, '.config');
|
|
78
|
+
return join(xdg, 'opencode', 'opencode.json');
|
|
79
|
+
}
|
|
80
|
+
function inspectClaudeJson(path) {
|
|
81
|
+
const text = readText(path);
|
|
82
|
+
if (text === undefined) {
|
|
83
|
+
return { parseError: true, hasTopLevelMcpServers: false, hasFauxnix: false };
|
|
84
|
+
}
|
|
85
|
+
let data;
|
|
86
|
+
try {
|
|
87
|
+
data = JSON.parse(stripBom(text));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return { parseError: true, hasTopLevelMcpServers: false, hasFauxnix: false };
|
|
91
|
+
}
|
|
92
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
93
|
+
return { parseError: true, hasTopLevelMcpServers: false, hasFauxnix: false };
|
|
94
|
+
}
|
|
95
|
+
const rec = data;
|
|
96
|
+
const hasTopLevelMcpServers = isServerMap(rec.mcpServers);
|
|
97
|
+
let hasFauxnix = hasTopLevelMcpServers && serverMapHasFauxnix(rec.mcpServers);
|
|
98
|
+
const projects = rec.projects;
|
|
99
|
+
if (projects && typeof projects === 'object' && !Array.isArray(projects)) {
|
|
100
|
+
for (const proj of Object.values(projects)) {
|
|
101
|
+
if (!proj || typeof proj !== 'object' || Array.isArray(proj))
|
|
102
|
+
continue;
|
|
103
|
+
const servers = proj.mcpServers;
|
|
104
|
+
if (isServerMap(servers) && serverMapHasFauxnix(servers))
|
|
105
|
+
hasFauxnix = true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { parseError: false, hasTopLevelMcpServers, hasFauxnix };
|
|
109
|
+
}
|
|
110
|
+
function detectCodex(home, env) {
|
|
111
|
+
const path = codexConfigPath(home, env);
|
|
112
|
+
if (!existsSync(path))
|
|
113
|
+
return 'not detected — see README';
|
|
114
|
+
const text = readText(path);
|
|
115
|
+
if (text === undefined)
|
|
116
|
+
return `found ${path} (unreadable) — see README`;
|
|
117
|
+
if (hasCodexFauxnix(stripBom(text)))
|
|
118
|
+
return `fauxnix MCP configured (${path})`;
|
|
119
|
+
return `found ${path}, fauxnix MCP not listed — run: codex mcp add fauxnix -- fauxnix mcp`;
|
|
120
|
+
}
|
|
121
|
+
export function hasCodexFauxnix(text) {
|
|
122
|
+
if (/^\s*\[mcp_servers\.(?:fauxnix|"fauxnix"|'fauxnix')\]/im.test(text))
|
|
123
|
+
return true;
|
|
124
|
+
const tables = text.split(/^\s*\[/m);
|
|
125
|
+
for (const table of tables) {
|
|
126
|
+
if (!/^mcp_servers\./i.test(table))
|
|
127
|
+
continue;
|
|
128
|
+
const header = (table.split(/[\]\r\n]/, 1)[0] ?? '').trim();
|
|
129
|
+
if (/^mcp_servers\.(?:fauxnix|"fauxnix"|'fauxnix')$/i.test(header))
|
|
130
|
+
return true;
|
|
131
|
+
const cmd = /^\s*command\s*=\s*(?:"([^"]*)"|'([^']*)')/im.exec(table);
|
|
132
|
+
const command = cmd?.[1] ?? cmd?.[2];
|
|
133
|
+
if (command && isFauxnixExecutable(command))
|
|
134
|
+
return true;
|
|
135
|
+
const args = /^\s*args\s*=\s*\[([^\]]*)\]/im.exec(table);
|
|
136
|
+
if (args) {
|
|
137
|
+
const items = [...args[1].matchAll(/"([^"]*)"|'([^']*)'/g)].map((m) => m[1] ?? m[2] ?? '');
|
|
138
|
+
if (items.some(isFauxnixExecutable))
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
function detectOpenCode(home, env) {
|
|
145
|
+
const path = openCodeConfigPath(home, env);
|
|
146
|
+
if (!existsSync(path))
|
|
147
|
+
return 'not detected — see README';
|
|
148
|
+
const text = readText(path);
|
|
149
|
+
if (text === undefined)
|
|
150
|
+
return `found ${path} (unreadable) — see README`;
|
|
151
|
+
let data;
|
|
152
|
+
try {
|
|
153
|
+
data = JSON.parse(stripBom(text));
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return `found ${path} (unreadable JSON) — see README`;
|
|
157
|
+
}
|
|
158
|
+
if (hasOpenCodeFauxnix(data))
|
|
159
|
+
return `fauxnix MCP configured (${path})`;
|
|
160
|
+
return `found ${path}, fauxnix MCP not listed — add mcp.fauxnix (see README)`;
|
|
161
|
+
}
|
|
162
|
+
export function hasOpenCodeFauxnix(data) {
|
|
163
|
+
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
164
|
+
return false;
|
|
165
|
+
const mcp = data.mcp;
|
|
166
|
+
if (!isServerMap(mcp))
|
|
167
|
+
return false;
|
|
168
|
+
if (serverMapHasFauxnix(mcp))
|
|
169
|
+
return true;
|
|
170
|
+
const nested = mcp.servers;
|
|
171
|
+
return isServerMap(nested) && serverMapHasFauxnix(nested);
|
|
172
|
+
}
|
|
173
|
+
export function isServerMap(value) {
|
|
174
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
175
|
+
}
|
|
176
|
+
export function serverMapHasFauxnix(value) {
|
|
177
|
+
if (!isServerMap(value))
|
|
178
|
+
return false;
|
|
179
|
+
for (const [name, cfg] of Object.entries(value)) {
|
|
180
|
+
if (name === 'servers')
|
|
181
|
+
continue;
|
|
182
|
+
if (looksLikeFauxnixServer(name, cfg))
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
function looksLikeFauxnixServer(name, cfg) {
|
|
188
|
+
if (/^fauxnix(-cli)?$/i.test(name))
|
|
189
|
+
return true;
|
|
190
|
+
if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg))
|
|
191
|
+
return false;
|
|
192
|
+
const rec = cfg;
|
|
193
|
+
const chunks = [];
|
|
194
|
+
if (typeof rec.command === 'string')
|
|
195
|
+
chunks.push(rec.command);
|
|
196
|
+
if (Array.isArray(rec.command)) {
|
|
197
|
+
for (const part of rec.command)
|
|
198
|
+
if (typeof part === 'string')
|
|
199
|
+
chunks.push(part);
|
|
200
|
+
}
|
|
201
|
+
if (Array.isArray(rec.args)) {
|
|
202
|
+
for (const part of rec.args)
|
|
203
|
+
if (typeof part === 'string')
|
|
204
|
+
chunks.push(part);
|
|
205
|
+
}
|
|
206
|
+
return chunks.some(isFauxnixExecutable);
|
|
207
|
+
}
|
|
208
|
+
function isFauxnixExecutable(s) {
|
|
209
|
+
const base = s.replace(/\\/g, '/').split('/').pop()?.trim() ?? '';
|
|
210
|
+
return /^fauxnix(-cli)?(\.cmd|\.exe)?$/i.test(base);
|
|
211
|
+
}
|
|
212
|
+
async function mcpLines(nodeVersion, loadMcp) {
|
|
213
|
+
const major = nodeMajor(nodeVersion);
|
|
214
|
+
const nodeOk = major >= 18;
|
|
215
|
+
let moduleOk = false;
|
|
216
|
+
let moduleDetail = '';
|
|
217
|
+
try {
|
|
218
|
+
const mod = await (loadMcp ?? defaultLoadMcp)();
|
|
219
|
+
moduleOk =
|
|
220
|
+
!!mod && typeof mod.startMcpServer === 'function';
|
|
221
|
+
if (!moduleOk)
|
|
222
|
+
moduleDetail = 'startMcpServer export missing';
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
moduleDetail = e instanceof Error ? e.message : String(e);
|
|
226
|
+
}
|
|
227
|
+
const lines = [
|
|
228
|
+
field('node', `${nodeVersion.startsWith('v') ? nodeVersion : 'v' + nodeVersion}${nodeOk ? ' (>=18 required)' : ' FAILED (requires >=18)'}`),
|
|
229
|
+
field('mcp', moduleOk ? 'module loads' : `FAILED to load${moduleDetail ? ': ' + moduleDetail : ''}`),
|
|
230
|
+
VALUE_INDENT + 'start with: fauxnix mcp',
|
|
231
|
+
];
|
|
232
|
+
return { lines, ok: nodeOk && moduleOk };
|
|
233
|
+
}
|
|
234
|
+
async function defaultLoadMcp() {
|
|
235
|
+
return import('./mcp.js');
|
|
236
|
+
}
|
|
237
|
+
function nodeMajor(version) {
|
|
238
|
+
const m = /^v?(\d+)/.exec(version);
|
|
239
|
+
return m ? Number(m[1]) : 0;
|
|
240
|
+
}
|
|
241
|
+
function stripBom(text) {
|
|
242
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
243
|
+
}
|
|
244
|
+
function readText(path) {
|
|
245
|
+
try {
|
|
246
|
+
return readFileSync(path, 'utf8');
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -2,4 +2,8 @@
|
|
|
2
2
|
* Error normalization — make PowerShell failures look like bash failures
|
|
3
3
|
* so agents can pattern-match on familiar Linux error styles.
|
|
4
4
|
*/
|
|
5
|
+
/** Missing `python3` on Windows (Mac/Linux agents emit this; do not alias). */
|
|
6
|
+
export declare const PYTHON3_WINDOWS_HINT = " (fauxnix: try `python` or `py` on Windows)";
|
|
7
|
+
/** `.sh` cannot be CreateProcess'd; fx-native never emits the PS not-recognized line. */
|
|
8
|
+
export declare const SH_SCRIPT_WINDOWS_HINT = " (fauxnix: .sh scripts cannot run natively on Windows)";
|
|
5
9
|
export declare function normalizeStderr(stderr: string): string;
|
package/dist/errors.js
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
* Error normalization — make PowerShell failures look like bash failures
|
|
3
3
|
* so agents can pattern-match on familiar Linux error styles.
|
|
4
4
|
*/
|
|
5
|
+
/** Missing `python3` on Windows (Mac/Linux agents emit this; do not alias). */
|
|
6
|
+
export const PYTHON3_WINDOWS_HINT = ' (fauxnix: try `python` or `py` on Windows)';
|
|
7
|
+
/** `.sh` cannot be CreateProcess'd; fx-native never emits the PS not-recognized line. */
|
|
8
|
+
export const SH_SCRIPT_WINDOWS_HINT = ' (fauxnix: .sh scripts cannot run natively on Windows)';
|
|
9
|
+
function commandNotFound(name) {
|
|
10
|
+
const msg = 'bash: ' + name + ': command not found';
|
|
11
|
+
const base = name.replace(/^.*[/\\]/, '');
|
|
12
|
+
if (/^python3(\.exe)?$/i.test(base))
|
|
13
|
+
return msg + PYTHON3_WINDOWS_HINT;
|
|
14
|
+
if (/\.sh$/i.test(name))
|
|
15
|
+
return msg + SH_SCRIPT_WINDOWS_HINT;
|
|
16
|
+
return msg;
|
|
17
|
+
}
|
|
5
18
|
/** Lines produced by PowerShell error formatting that bash would never show. */
|
|
6
19
|
const PS_NOISE = [
|
|
7
20
|
/^\s*\+ CategoryInfo\s*:/,
|
|
@@ -62,15 +75,15 @@ export function normalizeStderr(stderr) {
|
|
|
62
75
|
// "The term 'x' is not recognized as a name of a cmdlet, function, ..."
|
|
63
76
|
let m = line.match(/^The term '(.+?)' is not recognized/);
|
|
64
77
|
if (m)
|
|
65
|
-
return
|
|
78
|
+
return commandNotFound(m[1]);
|
|
66
79
|
// zh-CN: 无法将"x"项识别为 cmdlet、函数、脚本文件或可运行程序的名称
|
|
67
80
|
m = line.match(/^无法将["'”]?([^"'”]+)["'”]?项识别为/);
|
|
68
81
|
if (m)
|
|
69
|
-
return
|
|
82
|
+
return commandNotFound(m[1]);
|
|
70
83
|
// "x : The term 'y' is not recognized ..." (with source prefix)
|
|
71
84
|
m = line.match(/^(\S+)\s*:\s*The term '(.+?)' is not recognized/);
|
|
72
85
|
if (m)
|
|
73
|
-
return
|
|
86
|
+
return commandNotFound(m[2]);
|
|
74
87
|
// "cat : Cannot find path 'D:\x' because it does not exist."
|
|
75
88
|
m = line.match(/^(\S+)\s*:\s*Cannot find path '(.+?)' because it does not exist\.?$/);
|
|
76
89
|
if (m) {
|
|
@@ -93,9 +106,9 @@ export function normalizeStderr(stderr) {
|
|
|
93
106
|
m = line.match(/^(\S+)\s*:\s*(.*)Access to the path '(.+?)' is denied\.?$/);
|
|
94
107
|
if (m)
|
|
95
108
|
return m[1].toLowerCase() + ': cannot remove \'' + m[3] + '\': Permission denied';
|
|
96
|
-
//
|
|
109
|
+
// leftover PS not-recognized lines that the rewrites above did not catch
|
|
97
110
|
if (/\.sh'?/.test(line) && /is not recognized/.test(line)) {
|
|
98
|
-
return line +
|
|
111
|
+
return line + SH_SCRIPT_WINDOWS_HINT;
|
|
99
112
|
}
|
|
100
113
|
return line;
|
|
101
114
|
});
|