Skip to content

Commit bacbe10

Browse files
committed
Evals TUI tree traversal
1 parent de132bc commit bacbe10

7 files changed

Lines changed: 1212 additions & 262 deletions

File tree

packages/evals/cli.ts

Lines changed: 22 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ await (async () => {
5050

5151
import { red } from "./tui/format.js";
5252
import { getCurrentDirPath, getRuntimeTasksRoot } from "./runtimePaths.js";
53+
import type { TaskRegistry } from "./framework/types.js";
5354

5455
/**
5556
* Directory of the running entry module. Differs between source and
@@ -60,18 +61,6 @@ const ENTRY_DIR = getCurrentDirPath();
6061
const args = process.argv.slice(2);
6162

6263
(async () => {
63-
// Keep heavy command modules behind their command branches. The run stack
64-
// imports Braintrust transitively, and importing it for `help`/`config path`
65-
// makes quiet commands print optional OpenTelemetry warnings.
66-
const {
67-
printHelp,
68-
printRunHelp,
69-
printListHelp,
70-
printNewHelp,
71-
printConfigHelp,
72-
printExperimentsHelp,
73-
} = await import("./tui/commands/help.js");
74-
7564
// Best-effort shutdown: flush Braintrust telemetry and exit with the
7665
// conventional signal code. Does not guarantee in-flight task
7766
// cancellation upstream; the goal is clean process shutdown with no
@@ -128,113 +117,38 @@ const args = process.argv.slice(2);
128117
};
129118
}
130119

131-
async function executeRun(tokens: string[]): Promise<void> {
132-
const { readConfig } = await import("./tui/commands/config.js");
133-
const { runCommand } = await import("./tui/commands/run.js");
134-
const { parseRunArgs, resolveRunOptions } = await import(
135-
"./tui/commands/parse.js"
136-
);
137-
const flags = parseRunArgs(tokens);
138-
const configFile = readConfig(ENTRY_DIR);
139-
const resolved = resolveRunOptions(
140-
flags,
141-
configFile.defaults,
142-
process.env,
143-
configFile.core,
144-
);
145-
146-
if (flags.legacy) {
147-
const { runLegacy } = await import("./tui/commands/legacy.js");
148-
const { discoverTasks } = await import("./framework/discovery.js");
149-
const registry = await discoverTasks(getRuntimeTasksRoot(), false);
150-
await runLegacy(resolved, flags, registry);
151-
return; // unreachable — runLegacy calls process.exit
152-
}
153-
154-
await runCommand(resolved);
155-
}
156-
157120
try {
158121
if (args.length === 0) {
159122
const { startRepl } = await import("./tui/repl.js");
160123
await startRepl(ENTRY_DIR);
161124
return;
162125
}
163126

164-
const command = args[0].toLowerCase();
165-
const subArgs = args.slice(1);
166-
const wantsHelp = subArgs.includes("--help") || subArgs.includes("-h");
167-
168-
switch (command) {
169-
case "run": {
170-
if (wantsHelp) {
171-
printRunHelp();
172-
return;
173-
}
174-
await executeRun(subArgs);
175-
return;
176-
}
127+
const { buildCommandTree, dispatch, tokenizeArgv } = await import(
128+
"./tui/commandTree.js"
129+
);
177130

178-
case "list": {
179-
if (wantsHelp) {
180-
printListHelp();
181-
return;
182-
}
183-
const detailed =
184-
subArgs.includes("--detailed") || subArgs.includes("-d");
185-
const tierFilter = subArgs.find((a) => !a.startsWith("-"));
186-
const tasksRoot = getRuntimeTasksRoot();
131+
let registry: TaskRegistry | null = null;
132+
const getRegistry = async (): Promise<TaskRegistry> => {
133+
if (!registry) {
187134
const { discoverTasks } = await import("./framework/discovery.js");
188-
const { printList } = await import("./tui/commands/list.js");
189-
const registry = await discoverTasks(tasksRoot, false);
190-
printList(registry, tierFilter, detailed);
191-
return;
135+
registry = await discoverTasks(getRuntimeTasksRoot(), false);
192136
}
137+
return registry;
138+
};
193139

194-
case "config": {
195-
if (wantsHelp) {
196-
printConfigHelp();
197-
return;
198-
}
199-
const { handleConfig } = await import("./tui/commands/config.js");
200-
await handleConfig(subArgs, ENTRY_DIR);
201-
return;
202-
}
203-
204-
case "experiments": {
205-
if (wantsHelp && subArgs.length === 0) {
206-
printExperimentsHelp();
207-
return;
208-
}
209-
const { handleExperiments } = await import(
210-
"./tui/commands/experiments.js"
211-
);
212-
await handleExperiments(subArgs);
213-
return;
214-
}
215-
216-
case "new": {
217-
if (wantsHelp) {
218-
printNewHelp();
219-
return;
220-
}
221-
const { scaffoldTask } = await import("./tui/commands/new.js");
222-
scaffoldTask(subArgs);
223-
return;
224-
}
225-
226-
case "help":
227-
case "--help":
228-
case "-h":
229-
printHelp();
230-
return;
231-
232-
default: {
233-
// Unknown first arg → treat as run target: `evals act` == `evals run act`
234-
await executeRun(args);
235-
return;
236-
}
237-
}
140+
const tree = buildCommandTree();
141+
142+
const tokens = tokenizeArgv(args);
143+
await dispatch(tree, tokens, {
144+
entryDir: ENTRY_DIR,
145+
getRegistry,
146+
setRegistry: (r) => {
147+
registry = r;
148+
},
149+
abortRef: null,
150+
contextPath: null,
151+
});
238152
} catch (err) {
239153
console.error(red(`Error: ${(err as Error).message}`));
240154
process.exitCode = 1;

packages/evals/tests/cli.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,29 @@ describe("CLI entrypoint", () => {
149149
path.join(repoRoot, "packages", "evals", "evals.config.json"),
150150
);
151151
});
152+
153+
it("treats `>` as equivalent to a space separator (argv form)", async () => {
154+
const direct = await runCli(["config", "path"]);
155+
const piped = await runCli(["config", ">", "path"]);
156+
expect(piped.code).toBe(0);
157+
expect(piped.stdout).toBe(direct.stdout);
158+
});
159+
160+
it("supports `>` chaining across multiple levels", async () => {
161+
const direct = await runCli(["config", "core", "path"]);
162+
const piped = await runCli(["config", ">", "core", ">", "path"]);
163+
expect(piped.code).toBe(0);
164+
expect(piped.stdout).toBe(direct.stdout);
165+
});
166+
167+
it("strips a leading `evals` sigil token (no-op at root)", async () => {
168+
// From a shell, `evals evals run act --dry-run` should resolve like
169+
// `evals run act --dry-run` — the leading `evals` arg is the sigil.
170+
const { stdout, code } = await runCli(["evals", "run", "act", "--dry-run"]);
171+
expect(code).toBe(0);
172+
const payload = JSON.parse(stdout);
173+
expect(payload.normalizedTarget).toBe("act");
174+
});
152175
});
153176

154177
describe.sequential("core config", () => {

0 commit comments

Comments
 (0)