fauxnix-cli 0.7.1 → 0.8.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.
@@ -77,3 +77,53 @@ export interface WordArgs {
77
77
  * shortValues: single-char options that consume a value (e.g. ['n']).
78
78
  */
79
79
  export declare function parseWords(args: Word[], shortValues?: string[], longValues?: string[]): WordArgs;
80
+ export type CommandEffect = 'read' | 'write' | 'delete' | 'network' | 'process';
81
+ export type OptionSupport = 'implemented' | 'unsupported';
82
+ /** One short/long alias group. Unknown options on a spec'd command fail loud. */
83
+ export interface OptionSpec {
84
+ /** Short flag letter without dash (e.g. `'n'`). */
85
+ short?: string;
86
+ /** Long option including dashes (e.g. `'--no-clobber'`). */
87
+ long?: string;
88
+ /** Consumes a following argument (`-n 5`, `--lines=5`). */
89
+ takesValue?: boolean;
90
+ support: OptionSupport;
91
+ /** Extra phrase for unsupported options (`interactive prompt`). */
92
+ reason?: string;
93
+ }
94
+ export interface CommandSpec {
95
+ names: string[];
96
+ options: OptionSpec[];
97
+ effects: CommandEffect[];
98
+ platform?: 'windows-ps51' | 'portable-translate';
99
+ dispatch?: 'translated' | 'native' | 'dynamic';
100
+ handler: Handler;
101
+ }
102
+ /** Register a spec'd command. Unknown/unsupported options become GNU-style usage errors. */
103
+ export declare function registerSpec(spec: CommandSpec): void;
104
+ export declare function registerSpecs(list: CommandSpec[]): void;
105
+ export declare function lookupSpec(name: string): CommandSpec | undefined;
106
+ /** Unique specs in registration order. */
107
+ export declare function registeredSpecs(): CommandSpec[];
108
+ export interface ListedCommand {
109
+ name: string;
110
+ spec: null | {
111
+ options: Array<{
112
+ short?: string;
113
+ long?: string;
114
+ takesValue: boolean;
115
+ support: OptionSupport;
116
+ reason?: string;
117
+ }>;
118
+ effects: CommandEffect[];
119
+ platform: 'windows-ps51' | 'portable-translate';
120
+ dispatch: 'translated' | 'native' | 'dynamic';
121
+ };
122
+ }
123
+ /** Capability dump for `fauxnix list --json` / MCP introspection. */
124
+ export declare function listCommandsJson(): ListedCommand[];
125
+ /**
126
+ * Walk argv against a CommandSpec. Returns a PowerShell error script, or
127
+ * null when every option is recognized and implemented.
128
+ */
129
+ export declare function specOptionError(spec: CommandSpec, args: Word[], cmdName: string): string | null;
package/dist/registry.js CHANGED
@@ -153,3 +153,145 @@ export function parseWords(args, shortValues = [], longValues = []) {
153
153
  }
154
154
  return { flags, longs, values, missingValue, operandWords };
155
155
  }
156
+ const specs = new Map();
157
+ /** Register a spec'd command. Unknown/unsupported options become GNU-style usage errors. */
158
+ export function registerSpec(spec) {
159
+ for (const name of spec.names) {
160
+ const wrapped = (args, ctx) => {
161
+ const err = specOptionError(spec, args, name);
162
+ if (err)
163
+ return err;
164
+ return spec.handler(args, ctx);
165
+ };
166
+ registry.set(name, wrapped);
167
+ specs.set(name, spec);
168
+ }
169
+ }
170
+ export function registerSpecs(list) {
171
+ for (const spec of list)
172
+ registerSpec(spec);
173
+ }
174
+ export function lookupSpec(name) {
175
+ return specs.get(name);
176
+ }
177
+ /** Unique specs in registration order. */
178
+ export function registeredSpecs() {
179
+ const seen = new Set();
180
+ const out = [];
181
+ for (const spec of specs.values()) {
182
+ if (seen.has(spec))
183
+ continue;
184
+ seen.add(spec);
185
+ out.push(spec);
186
+ }
187
+ return out;
188
+ }
189
+ /** Capability dump for `fauxnix list --json` / MCP introspection. */
190
+ export function listCommandsJson() {
191
+ return registeredNames().map((name) => {
192
+ const spec = lookupSpec(name);
193
+ if (!spec)
194
+ return { name, spec: null };
195
+ return {
196
+ name,
197
+ spec: {
198
+ options: spec.options.map((o) => ({
199
+ ...(o.short ? { short: o.short } : {}),
200
+ ...(o.long ? { long: o.long } : {}),
201
+ takesValue: o.takesValue === true,
202
+ support: o.support,
203
+ ...(o.reason ? { reason: o.reason } : {}),
204
+ })),
205
+ effects: spec.effects,
206
+ platform: spec.platform ?? 'windows-ps51',
207
+ dispatch: spec.dispatch ?? 'translated',
208
+ },
209
+ };
210
+ });
211
+ }
212
+ /**
213
+ * Walk argv against a CommandSpec. Returns a PowerShell error script, or
214
+ * null when every option is recognized and implemented.
215
+ */
216
+ export function specOptionError(spec, args, cmdName) {
217
+ const shorts = new Map();
218
+ const longs = new Map();
219
+ for (const o of spec.options) {
220
+ if (o.short)
221
+ shorts.set(o.short, o);
222
+ if (o.long)
223
+ longs.set(o.long, o);
224
+ }
225
+ let i = 0;
226
+ let onlyOperands = false;
227
+ while (i < args.length) {
228
+ const t = wordToString(args[i]);
229
+ if (onlyOperands) {
230
+ i++;
231
+ continue;
232
+ }
233
+ if (t === '--') {
234
+ onlyOperands = true;
235
+ i++;
236
+ continue;
237
+ }
238
+ if (t.startsWith('--')) {
239
+ const eq = t.indexOf('=');
240
+ const name = eq >= 0 ? t.slice(0, eq) : t;
241
+ const opt = longs.get(name);
242
+ if (!opt)
243
+ return optionFail(cmdName, "unrecognized option '" + name + "'");
244
+ if (opt.support === 'unsupported') {
245
+ return optionFail(cmdName, unsupportedMsg(opt, name));
246
+ }
247
+ if (!opt.takesValue && eq >= 0) {
248
+ return optionFail(cmdName, "option '" + name + "' doesn't allow an argument");
249
+ }
250
+ if (opt.takesValue && eq < 0) {
251
+ if (i + 1 < args.length)
252
+ i++;
253
+ else
254
+ return optionFail(cmdName, "option '" + name + "' requires an argument");
255
+ }
256
+ i++;
257
+ continue;
258
+ }
259
+ if (t.startsWith('-') && t.length > 1 && !/^-?\d/.test(t.slice(1, 2))) {
260
+ const body = t.slice(1);
261
+ for (let c = 0; c < body.length; c++) {
262
+ const ch = body[c];
263
+ const opt = shorts.get(ch);
264
+ if (!opt)
265
+ return optionFail(cmdName, "invalid option -- '" + ch + "'");
266
+ if (opt.support === 'unsupported') {
267
+ return optionFail(cmdName, unsupportedMsg(opt, '-' + ch));
268
+ }
269
+ if (opt.takesValue) {
270
+ const rest = body.slice(c + 1);
271
+ if (!rest) {
272
+ if (i + 1 < args.length)
273
+ i++;
274
+ else
275
+ return optionFail(cmdName, "option requires an argument -- '" + ch + "'");
276
+ }
277
+ break;
278
+ }
279
+ }
280
+ i++;
281
+ continue;
282
+ }
283
+ i++;
284
+ }
285
+ return null;
286
+ }
287
+ function unsupportedMsg(opt, shown) {
288
+ const reason = opt.reason ? ' (' + opt.reason + ')' : '';
289
+ return "option '" + shown + "' is not supported by fauxnix" + reason;
290
+ }
291
+ function optionFail(cmd, msg) {
292
+ return ('[Console]::Error.WriteLine(' +
293
+ psStr(cmd + ': ' + msg) +
294
+ '); [Console]::Error.WriteLine(' +
295
+ psStr("Try '" + cmd + " --help' for more information.") +
296
+ '); $script:fx_exit = 1');
297
+ }
@@ -1268,6 +1268,13 @@ while ($true) {
1268
1268
  $fx_code = 0
1269
1269
  try { $fx_code = [int]$script:fx_exit } catch { $fx_code = 1 }
1270
1270
  $fx_res = @{ id = $fx_id; stdoutB64 = $fx_outB64; stderrB64 = $fx_errB64; exitCode = $fx_code }
1271
- $fx_proto.WriteLine(($fx_res | ConvertTo-Json -Compress))
1271
+ try {
1272
+ $fx_json = $fx_res | ConvertTo-Json -Compress
1273
+ } catch {
1274
+ $fx_msg = 'fauxnix: host result exceeded ConvertTo-Json MaxJsonLength (~2MB)'
1275
+ $fx_res = @{ id = $fx_id; stdoutB64 = ''; stderrB64 = [Convert]::ToBase64String($fx_utf8.GetBytes($fx_msg)); exitCode = 1 }
1276
+ $fx_json = $fx_res | ConvertTo-Json -Compress
1277
+ }
1278
+ $fx_proto.WriteLine($fx_json)
1272
1279
  }
1273
1280
  `.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fauxnix-cli",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Fauxnix — run Linux-style commands on Windows via deterministic PowerShell translation. No VM, no WSL. MCP server + CLI for AI agents.",
5
5
  "type": "module",
6
6
  "bin": {