@routerlab/cli 0.0.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/LICENSE +201 -0
- package/README.md +171 -0
- package/dist/commands/eval.d.ts +21 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/eval.js +104 -0
- package/dist/commands/eval.js.map +1 -0
- package/dist/commands/frontier.d.ts +29 -0
- package/dist/commands/frontier.d.ts.map +1 -0
- package/dist/commands/frontier.js +212 -0
- package/dist/commands/frontier.js.map +1 -0
- package/dist/commands/help.d.ts +3 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +55 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/models.d.ts +7 -0
- package/dist/commands/models.d.ts.map +1 -0
- package/dist/commands/models.js +71 -0
- package/dist/commands/models.js.map +1 -0
- package/dist/commands/route.d.ts +7 -0
- package/dist/commands/route.d.ts.map +1 -0
- package/dist/commands/route.js +173 -0
- package/dist/commands/route.js.map +1 -0
- package/dist/commands/version.d.ts +8 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +21 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +32 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/io.d.ts +33 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +47 -0
- package/dist/io.js.map +1 -0
- package/dist/main.d.ts +14 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +64 -0
- package/dist/main.js.map +1 -0
- package/dist/parse.d.ts +44 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +125 -0
- package/dist/parse.js.map +1 -0
- package/package.json +30 -0
package/dist/errors.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// errors.ts — canonical CLI exit codes + a typed error for graceful failures.
|
|
2
|
+
//
|
|
3
|
+
// Exit-code semantics (mirrored in the CLI's README so users can script
|
|
4
|
+
// against them):
|
|
5
|
+
//
|
|
6
|
+
// 0 — success
|
|
7
|
+
// 1 — no candidates pass the filters (engine ran, decided nothing routable)
|
|
8
|
+
// 2 — invalid input (bad flag, malformed value, missing required arg)
|
|
9
|
+
// 3 — downstream error (calibration file malformed, missing file on disk,
|
|
10
|
+
// provider runner failure, etc.)
|
|
11
|
+
//
|
|
12
|
+
// The CLI never throws raw `Error` to the user. Every user-visible failure
|
|
13
|
+
// is funneled through `CliError`, which carries the exit code and a clean
|
|
14
|
+
// message. `main()` catches and renders.
|
|
15
|
+
export const EXIT_SUCCESS = 0;
|
|
16
|
+
export const EXIT_NO_CANDIDATES = 1;
|
|
17
|
+
export const EXIT_INVALID_INPUT = 2;
|
|
18
|
+
export const EXIT_DOWNSTREAM = 3;
|
|
19
|
+
/**
|
|
20
|
+
* The one error type the CLI throws for expected, user-facing failures.
|
|
21
|
+
* Unknown exceptions still bubble up to `main()` and are mapped to
|
|
22
|
+
* `EXIT_DOWNSTREAM` with the underlying message preserved.
|
|
23
|
+
*/
|
|
24
|
+
export class CliError extends Error {
|
|
25
|
+
code;
|
|
26
|
+
constructor(code, message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "CliError";
|
|
29
|
+
this.code = code;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,EAAE;AACF,wEAAwE;AACxE,iBAAiB;AACjB,EAAE;AACF,gBAAgB;AAChB,8EAA8E;AAC9E,wEAAwE;AACxE,4EAA4E;AAC5E,uCAAuC;AACvC,EAAE;AACF,2EAA2E;AAC3E,0EAA0E;AAC1E,yCAAyC;AAEzC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAC9B,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AACpC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AACpC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC;AAQjC;;;;GAIG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,IAAI,CAAc;IAE3B,YAAY,IAAiB,EAAE,OAAe;QAC5C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @routerlab/cli — `route` command entrypoint.
|
|
3
|
+
//
|
|
4
|
+
// This file is the binary's argv shim. Real CLI logic lives in `main.ts`
|
|
5
|
+
// (and the per-subcommand modules) so it can be imported by tests without
|
|
6
|
+
// the side effect of consuming `process.argv`.
|
|
7
|
+
//
|
|
8
|
+
// We exit with `main()`'s return code so shell pipelines see the right
|
|
9
|
+
// status (0 success, 1 no candidates, 2 invalid input, 3 downstream error).
|
|
10
|
+
// See `./errors.ts` for the canonical exit-code definitions.
|
|
11
|
+
import { main } from "./main.js";
|
|
12
|
+
const code = await main({
|
|
13
|
+
argv: process.argv.slice(2),
|
|
14
|
+
stdout: process.stdout,
|
|
15
|
+
stderr: process.stderr,
|
|
16
|
+
stdin: process.stdin,
|
|
17
|
+
env: process.env,
|
|
18
|
+
cwd: process.cwd(),
|
|
19
|
+
});
|
|
20
|
+
process.exit(code);
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,+CAA+C;AAC/C,EAAE;AACF,yEAAyE;AACzE,0EAA0E;AAC1E,+CAA+C;AAC/C,EAAE;AACF,uEAAuE;AACvE,4EAA4E;AAC5E,6DAA6D;AAE7D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC,MAAM;IACtB,MAAM,EAAE,OAAO,CAAC,MAAM;IACtB,KAAK,EAAE,OAAO,CAAC,KAAK;IACpB,GAAG,EAAE,OAAO,CAAC,GAAG;IAChB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;CACnB,CAAC,CAAC;AAEH,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC"}
|
package/dist/io.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
export interface CliContext {
|
|
3
|
+
/** argv slice with the binary name and node entry already removed. */
|
|
4
|
+
argv: readonly string[];
|
|
5
|
+
/** Where normal output goes. Tests inject a `MemoryStream`. */
|
|
6
|
+
stdout: Writable;
|
|
7
|
+
/** Where error and diagnostic output goes. */
|
|
8
|
+
stderr: Writable;
|
|
9
|
+
/**
|
|
10
|
+
* Stdin handle. The `route` subcommand reads from this when `--input`
|
|
11
|
+
* is not supplied. Tests inject a primed `Readable` to simulate piping.
|
|
12
|
+
*/
|
|
13
|
+
stdin: Readable;
|
|
14
|
+
/** Environment snapshot. Used to read `ROUTERLAB_*` overrides. */
|
|
15
|
+
env: NodeJS.ProcessEnv;
|
|
16
|
+
/** Working directory. Used to resolve relative `--input` paths. */
|
|
17
|
+
cwd: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Read stdin to completion as a UTF-8 string.
|
|
21
|
+
*
|
|
22
|
+
* Returns an empty string if stdin is a TTY (interactive shell, no pipe)
|
|
23
|
+
* — that way `route --task=qa --quality-bar=0.85` without `--input` and
|
|
24
|
+
* without a piped stdin doesn't hang forever waiting on the user.
|
|
25
|
+
*/
|
|
26
|
+
export declare function readStdinToString(stdin: Readable): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Convenience: write a string + newline to a stream. Centralizing this
|
|
29
|
+
* keeps the subcommands free of `\n` boilerplate and lets us swap the
|
|
30
|
+
* underlying writer (e.g. if we ever want to color-tag stderr).
|
|
31
|
+
*/
|
|
32
|
+
export declare function writeLine(stream: Writable, line: string): void;
|
|
33
|
+
//# sourceMappingURL=io.d.ts.map
|
package/dist/io.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,+DAA+D;IAC/D,MAAM,EAAE,QAAQ,CAAC;IACjB,8CAA8C;IAC9C,MAAM,EAAE,QAAQ,CAAC;IACjB;;;OAGG;IACH,KAAK,EAAE,QAAQ,CAAC;IAChB,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBxE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9D"}
|
package/dist/io.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// io.ts — IO seam for the CLI.
|
|
2
|
+
//
|
|
3
|
+
// Every subcommand takes a `CliContext` instead of poking at `process.*`
|
|
4
|
+
// globals directly. This is the seam that makes the CLI fully unit-testable
|
|
5
|
+
// from `bun test` without spawning a child process: tests instantiate a
|
|
6
|
+
// `CliContext` with in-memory streams, pass in argv, and read the captured
|
|
7
|
+
// stdout/stderr after the call returns.
|
|
8
|
+
/**
|
|
9
|
+
* Read stdin to completion as a UTF-8 string.
|
|
10
|
+
*
|
|
11
|
+
* Returns an empty string if stdin is a TTY (interactive shell, no pipe)
|
|
12
|
+
* — that way `route --task=qa --quality-bar=0.85` without `--input` and
|
|
13
|
+
* without a piped stdin doesn't hang forever waiting on the user.
|
|
14
|
+
*/
|
|
15
|
+
export async function readStdinToString(stdin) {
|
|
16
|
+
// Bun's stdin exposes `isTTY` (matches Node), so a TTY-attached caller
|
|
17
|
+
// doesn't block. Real pipes (`echo "hi" | route ...`) and injected
|
|
18
|
+
// memory streams in tests both have `isTTY` falsy.
|
|
19
|
+
const maybeTty = stdin;
|
|
20
|
+
if (maybeTty.isTTY === true) {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
const chunks = [];
|
|
24
|
+
for await (const chunk of stdin) {
|
|
25
|
+
if (typeof chunk === "string") {
|
|
26
|
+
chunks.push(Buffer.from(chunk, "utf8"));
|
|
27
|
+
}
|
|
28
|
+
else if (chunk instanceof Uint8Array) {
|
|
29
|
+
chunks.push(Buffer.from(chunk));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Defensive: shouldn't happen on Node/Bun stdin, but keeps the
|
|
33
|
+
// function total-typed under `noImplicitAny`.
|
|
34
|
+
chunks.push(Buffer.from(String(chunk), "utf8"));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Convenience: write a string + newline to a stream. Centralizing this
|
|
41
|
+
* keeps the subcommands free of `\n` boilerplate and lets us swap the
|
|
42
|
+
* underlying writer (e.g. if we ever want to color-tag stderr).
|
|
43
|
+
*/
|
|
44
|
+
export function writeLine(stream, line) {
|
|
45
|
+
stream.write(line + "\n");
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=io.js.map
|
package/dist/io.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.js","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,yEAAyE;AACzE,4EAA4E;AAC5E,wEAAwE;AACxE,2EAA2E;AAC3E,wCAAwC;AAsBxC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAe;IACrD,uEAAuE;IACvE,mEAAmE;IACnE,mDAAmD;IACnD,MAAM,QAAQ,GAAG,KAAuC,CAAC;IACzD,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAChC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1C,CAAC;aAAM,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;YACvC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,+DAA+D;YAC/D,8CAA8C;YAC9C,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,MAAgB,EAAE,IAAY;IACtD,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;AAC5B,CAAC"}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type CliContext } from "./io.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Top-level entry point. Always resolves with an exit code; never throws.
|
|
4
|
+
*
|
|
5
|
+
* Lifecycle:
|
|
6
|
+
* 1. Extract first positional as the subcommand name.
|
|
7
|
+
* 2. Dispatch to that subcommand's `run*` function with a shifted argv.
|
|
8
|
+
* 3. Any `CliError` thrown by a subcommand is rendered to stderr with
|
|
9
|
+
* its embedded exit code returned verbatim.
|
|
10
|
+
* 4. Any other exception is mapped to `EXIT_DOWNSTREAM` with the
|
|
11
|
+
* original message preserved.
|
|
12
|
+
*/
|
|
13
|
+
export declare function main(ctx: CliContext): Promise<number>;
|
|
14
|
+
//# sourceMappingURL=main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,UAAU,EAAa,MAAM,SAAS,CAAC;AAErD;;;;;;;;;;GAUG;AACH,wBAAsB,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwC3D"}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// main.ts — top-level CLI dispatcher.
|
|
2
|
+
//
|
|
3
|
+
// Reads the first argv positional, routes it to the matching subcommand
|
|
4
|
+
// module, and converts thrown errors into exit codes. Tests import this
|
|
5
|
+
// function directly (rather than spawning a subprocess) by passing a
|
|
6
|
+
// `CliContext` with in-memory streams.
|
|
7
|
+
//
|
|
8
|
+
// Exit codes are defined in `./errors.ts` and mirrored in the CLI README.
|
|
9
|
+
import { CliError, EXIT_DOWNSTREAM, EXIT_INVALID_INPUT } from "./errors.js";
|
|
10
|
+
import { runEval } from "./commands/eval.js";
|
|
11
|
+
import { runFrontier } from "./commands/frontier.js";
|
|
12
|
+
import { runHelp } from "./commands/help.js";
|
|
13
|
+
import { runModels } from "./commands/models.js";
|
|
14
|
+
import { runRoute } from "./commands/route.js";
|
|
15
|
+
import { runVersion } from "./commands/version.js";
|
|
16
|
+
import { writeLine } from "./io.js";
|
|
17
|
+
/**
|
|
18
|
+
* Top-level entry point. Always resolves with an exit code; never throws.
|
|
19
|
+
*
|
|
20
|
+
* Lifecycle:
|
|
21
|
+
* 1. Extract first positional as the subcommand name.
|
|
22
|
+
* 2. Dispatch to that subcommand's `run*` function with a shifted argv.
|
|
23
|
+
* 3. Any `CliError` thrown by a subcommand is rendered to stderr with
|
|
24
|
+
* its embedded exit code returned verbatim.
|
|
25
|
+
* 4. Any other exception is mapped to `EXIT_DOWNSTREAM` with the
|
|
26
|
+
* original message preserved.
|
|
27
|
+
*/
|
|
28
|
+
export async function main(ctx) {
|
|
29
|
+
const [first, ...rest] = ctx.argv;
|
|
30
|
+
// No subcommand → show help and exit 0. This is the same behavior as
|
|
31
|
+
// `git` (interactive shells) and is friendlier than a usage error.
|
|
32
|
+
if (first === undefined || first === "--help" || first === "-h" || first === "help") {
|
|
33
|
+
return runHelp({ ...ctx, argv: rest });
|
|
34
|
+
}
|
|
35
|
+
const subCtx = { ...ctx, argv: rest };
|
|
36
|
+
try {
|
|
37
|
+
switch (first) {
|
|
38
|
+
case "route":
|
|
39
|
+
return await runRoute(subCtx);
|
|
40
|
+
case "frontier":
|
|
41
|
+
return runFrontier(subCtx);
|
|
42
|
+
case "models":
|
|
43
|
+
return runModels(subCtx);
|
|
44
|
+
case "eval":
|
|
45
|
+
return await runEval(subCtx);
|
|
46
|
+
case "version":
|
|
47
|
+
case "--version":
|
|
48
|
+
case "-v":
|
|
49
|
+
return runVersion(subCtx);
|
|
50
|
+
default:
|
|
51
|
+
throw new CliError(EXIT_INVALID_INPUT, `unknown subcommand "${first}". Run \`route help\` for usage.`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
if (e instanceof CliError) {
|
|
56
|
+
writeLine(ctx.stderr, `error: ${e.message}`);
|
|
57
|
+
return e.code;
|
|
58
|
+
}
|
|
59
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
60
|
+
writeLine(ctx.stderr, `error: ${msg}`);
|
|
61
|
+
return EXIT_DOWNSTREAM;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,wEAAwE;AACxE,wEAAwE;AACxE,qEAAqE;AACrE,uCAAuC;AACvC,EAAE;AACF,0EAA0E;AAE1E,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAmB,SAAS,EAAE,MAAM,SAAS,CAAC;AAErD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,GAAe;IACxC,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;IAElC,qEAAqE;IACrE,mEAAmE;IACnE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACpF,OAAO,OAAO,CAAC,EAAE,GAAG,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,MAAM,GAAe,EAAE,GAAG,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAElD,IAAI,CAAC;QACH,QAAQ,KAAK,EAAE,CAAC;YACd,KAAK,OAAO;gBACV,OAAO,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;YAChC,KAAK,UAAU;gBACb,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC;YAC7B,KAAK,QAAQ;gBACX,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC;YAC3B,KAAK,MAAM;gBACT,OAAO,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;YAC/B,KAAK,SAAS,CAAC;YACf,KAAK,WAAW,CAAC;YACjB,KAAK,IAAI;gBACP,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;YAC5B;gBACE,MAAM,IAAI,QAAQ,CAChB,kBAAkB,EAClB,uBAAuB,KAAK,kCAAkC,CAC/D,CAAC;QACN,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,QAAQ,EAAE,CAAC;YAC1B,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7C,OAAO,CAAC,CAAC,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACvD,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,GAAG,EAAE,CAAC,CAAC;QACvC,OAAO,eAAe,CAAC;IACzB,CAAC;AACH,CAAC"}
|
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Provider, TaskClass } from "@routerlab/core";
|
|
2
|
+
/**
|
|
3
|
+
* The value type `node:util`'s `parseArgs` returns per flag. We accept the
|
|
4
|
+
* full union (including the array case from `multiple: true`, which the
|
|
5
|
+
* CLI doesn't currently use) so we can swap a flag to multi-value later
|
|
6
|
+
* without rewriting validators. The validators below all reject arrays
|
|
7
|
+
* because none of the current flags are multi-value.
|
|
8
|
+
*/
|
|
9
|
+
export type ParsedFlagValue = string | boolean | (string | boolean)[] | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Require a string-typed flag value and return it, else throw.
|
|
12
|
+
*/
|
|
13
|
+
export declare function requireString(flag: string, value: ParsedFlagValue): string;
|
|
14
|
+
/**
|
|
15
|
+
* Parse a `--task=...` value into a `TaskClass`, else throw.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseTask(value: ParsedFlagValue): TaskClass;
|
|
18
|
+
/**
|
|
19
|
+
* Parse a `--provider=...` value into a `Provider`, else throw.
|
|
20
|
+
* Unlike `parseTask` this one is optional because `models` accepts no filter.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseProviderOptional(value: ParsedFlagValue): Provider | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Parse a `--quality-bar=...` value into a [0, 1] number, else throw.
|
|
25
|
+
*
|
|
26
|
+
* The engine itself also validates this, but doing it here lets us return
|
|
27
|
+
* exit code 2 (invalid input) consistently rather than depending on the
|
|
28
|
+
* engine's exception bubbling up to the catch-all that returns 3.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseQualityBar(value: ParsedFlagValue): number;
|
|
31
|
+
/**
|
|
32
|
+
* Parse an optional numeric flag like `--max-cost-usd=0.005`. Returns
|
|
33
|
+
* `undefined` if the flag is absent.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseNonNegativeFloatOptional(flag: string, value: ParsedFlagValue): number | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Parse an optional positive integer flag like `--n=20`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function parsePositiveIntOptional(flag: string, value: ParsedFlagValue): number | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Parse a `--format=table|json` value with a sensible default.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseFormat(value: ParsedFlagValue, defaultValue?: "table" | "json"): "table" | "json";
|
|
44
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAwB3D;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,EAAE,GAAG,SAAS,CAAC;AAElF;;GAEG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,eAAe,GACrB,MAAM,CAQR;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,CAS3D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,eAAe,GACrB,QAAQ,GAAG,SAAS,CAYtB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAgB9D;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,eAAe,GACrB,MAAM,GAAG,SAAS,CAmBpB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,eAAe,GACrB,MAAM,GAAG,SAAS,CAapB;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,eAAe,EACtB,YAAY,GAAE,OAAO,GAAG,MAAgB,GACvC,OAAO,GAAG,MAAM,CASlB"}
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// parse.ts — shared argument validation helpers.
|
|
2
|
+
//
|
|
3
|
+
// Every subcommand parses flags with `node:util` `parseArgs`. The helpers
|
|
4
|
+
// here turn the raw `string | undefined` values into validated, typed
|
|
5
|
+
// values, throwing `CliError(EXIT_INVALID_INPUT, ...)` on bad input.
|
|
6
|
+
//
|
|
7
|
+
// Centralizing the validation keeps the subcommands focused on rendering
|
|
8
|
+
// and ensures the user gets the same error message for the same kind of
|
|
9
|
+
// mistake regardless of which subcommand they're running.
|
|
10
|
+
import { CliError, EXIT_INVALID_INPUT } from "./errors.js";
|
|
11
|
+
const VALID_TASKS = new Set([
|
|
12
|
+
"qa",
|
|
13
|
+
"codegen",
|
|
14
|
+
"summarization",
|
|
15
|
+
"classification",
|
|
16
|
+
"reasoning",
|
|
17
|
+
]);
|
|
18
|
+
const VALID_PROVIDERS = new Set([
|
|
19
|
+
"anthropic",
|
|
20
|
+
"openai",
|
|
21
|
+
"google",
|
|
22
|
+
"groq",
|
|
23
|
+
"together",
|
|
24
|
+
"hf",
|
|
25
|
+
"openrouter",
|
|
26
|
+
]);
|
|
27
|
+
const VALID_FORMATS = new Set(["table", "json"]);
|
|
28
|
+
/**
|
|
29
|
+
* Require a string-typed flag value and return it, else throw.
|
|
30
|
+
*/
|
|
31
|
+
export function requireString(flag, value) {
|
|
32
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
33
|
+
throw new CliError(EXIT_INVALID_INPUT, `missing required flag --${flag}`);
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parse a `--task=...` value into a `TaskClass`, else throw.
|
|
39
|
+
*/
|
|
40
|
+
export function parseTask(value) {
|
|
41
|
+
const raw = requireString("task", value);
|
|
42
|
+
if (!VALID_TASKS.has(raw)) {
|
|
43
|
+
throw new CliError(EXIT_INVALID_INPUT, `invalid --task "${raw}". Expected one of: ${[...VALID_TASKS].join(", ")}`);
|
|
44
|
+
}
|
|
45
|
+
return raw;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse a `--provider=...` value into a `Provider`, else throw.
|
|
49
|
+
* Unlike `parseTask` this one is optional because `models` accepts no filter.
|
|
50
|
+
*/
|
|
51
|
+
export function parseProviderOptional(value) {
|
|
52
|
+
if (value === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
55
|
+
throw new CliError(EXIT_INVALID_INPUT, `--provider expects a value`);
|
|
56
|
+
}
|
|
57
|
+
if (!VALID_PROVIDERS.has(value)) {
|
|
58
|
+
throw new CliError(EXIT_INVALID_INPUT, `invalid --provider "${value}". Expected one of: ${[...VALID_PROVIDERS].join(", ")}`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parse a `--quality-bar=...` value into a [0, 1] number, else throw.
|
|
64
|
+
*
|
|
65
|
+
* The engine itself also validates this, but doing it here lets us return
|
|
66
|
+
* exit code 2 (invalid input) consistently rather than depending on the
|
|
67
|
+
* engine's exception bubbling up to the catch-all that returns 3.
|
|
68
|
+
*/
|
|
69
|
+
export function parseQualityBar(value) {
|
|
70
|
+
const raw = requireString("quality-bar", value);
|
|
71
|
+
const n = Number(raw);
|
|
72
|
+
if (!Number.isFinite(n)) {
|
|
73
|
+
throw new CliError(EXIT_INVALID_INPUT, `invalid --quality-bar "${raw}": not a finite number`);
|
|
74
|
+
}
|
|
75
|
+
if (n < 0 || n > 1) {
|
|
76
|
+
throw new CliError(EXIT_INVALID_INPUT, `invalid --quality-bar "${raw}": must be in [0, 1]`);
|
|
77
|
+
}
|
|
78
|
+
return n;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parse an optional numeric flag like `--max-cost-usd=0.005`. Returns
|
|
82
|
+
* `undefined` if the flag is absent.
|
|
83
|
+
*/
|
|
84
|
+
export function parseNonNegativeFloatOptional(flag, value) {
|
|
85
|
+
if (value === undefined)
|
|
86
|
+
return undefined;
|
|
87
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
88
|
+
throw new CliError(EXIT_INVALID_INPUT, `--${flag} expects a value`);
|
|
89
|
+
}
|
|
90
|
+
const n = Number(value);
|
|
91
|
+
if (!Number.isFinite(n)) {
|
|
92
|
+
throw new CliError(EXIT_INVALID_INPUT, `invalid --${flag} "${value}": not a finite number`);
|
|
93
|
+
}
|
|
94
|
+
if (n < 0) {
|
|
95
|
+
throw new CliError(EXIT_INVALID_INPUT, `invalid --${flag} "${value}": must be non-negative`);
|
|
96
|
+
}
|
|
97
|
+
return n;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse an optional positive integer flag like `--n=20`.
|
|
101
|
+
*/
|
|
102
|
+
export function parsePositiveIntOptional(flag, value) {
|
|
103
|
+
if (value === undefined)
|
|
104
|
+
return undefined;
|
|
105
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
106
|
+
throw new CliError(EXIT_INVALID_INPUT, `--${flag} expects a value`);
|
|
107
|
+
}
|
|
108
|
+
const n = Number(value);
|
|
109
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
110
|
+
throw new CliError(EXIT_INVALID_INPUT, `invalid --${flag} "${value}": must be a positive integer`);
|
|
111
|
+
}
|
|
112
|
+
return n;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Parse a `--format=table|json` value with a sensible default.
|
|
116
|
+
*/
|
|
117
|
+
export function parseFormat(value, defaultValue = "table") {
|
|
118
|
+
if (value === undefined)
|
|
119
|
+
return defaultValue;
|
|
120
|
+
if (typeof value !== "string" || !VALID_FORMATS.has(value)) {
|
|
121
|
+
throw new CliError(EXIT_INVALID_INPUT, `invalid --format "${String(value)}". Expected one of: ${[...VALID_FORMATS].join(", ")}`);
|
|
122
|
+
}
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@routerlab/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "CLI for routerlab — cost-quality routing for LLM APIs.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "Faraazuddin Mohammed",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"route": "dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/faraa2m/routerlab.git",
|
|
21
|
+
"directory": "packages/cli"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.json",
|
|
25
|
+
"test": "bun test"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@routerlab/core": "^0.0.1"
|
|
29
|
+
}
|
|
30
|
+
}
|