-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathindex.ts
More file actions
200 lines (182 loc) · 6.96 KB
/
Copy pathindex.ts
File metadata and controls
200 lines (182 loc) · 6.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import path from 'node:path';
import {
loadConfig,
loadEnvPathFiles,
runBeforeSessionHook,
runEnvFromEntries,
} from '@agentv/core';
import { binary, run, subcommands } from 'cmd-ts';
import { findRepoRoot } from './commands/eval/shared.js';
import packageJson from '../package.json' with { type: 'json' };
import { compareCommand } from './commands/compare/index.js';
import { convertCommand } from './commands/convert/index.js';
import { createCommand } from './commands/create/index.js';
import { doctorCommand } from './commands/doctor/index.js';
import { evalCommand } from './commands/eval/index.js';
import { gradeCommand } from './commands/grade/index.js';
import { importCommand } from './commands/import/index.js';
import { initCmdTsCommand } from './commands/init/index.js';
import { inspectCommand } from './commands/inspect/index.js';
import { pipelineCommand } from './commands/pipeline/index.js';
import { prepareCommand } from './commands/prepare/index.js';
import { resultsCommand } from './commands/results/index.js';
import { resultsServeCommand } from './commands/results/serve.js';
import { runsCommand } from './commands/runs/index.js';
import { selfCommand } from './commands/self/index.js';
import { skillsCommand } from './commands/skills/index.js';
import { transpileCommand } from './commands/transpile/index.js';
import { trendCommand } from './commands/trend/index.js';
import { trimCommand } from './commands/trim/index.js';
import { validateCommand } from './commands/validate/index.js';
import { getUpdateNotice } from './update-check.js';
export const app = subcommands({
name: 'agentv',
description: 'AgentV CLI',
version: packageJson.version,
cmds: {
dashboard: resultsServeCommand,
eval: evalCommand,
grade: gradeCommand,
import: importCommand,
compare: compareCommand,
convert: convertCommand,
create: createCommand,
doctor: doctorCommand,
init: initCmdTsCommand,
pipeline: pipelineCommand,
prepare: prepareCommand,
results: resultsCommand,
runs: runsCommand,
self: selfCommand,
skills: skillsCommand,
serve: resultsServeCommand,
inspect: inspectCommand,
trend: trendCommand,
transpile: transpileCommand,
trim: trimCommand,
validate: validateCommand,
},
});
/**
* Known eval subcommand names — used to decide whether to inject the
* implicit `run` subcommand for backward-compatible `agentv eval <paths>`.
*/
const EVAL_SUBCOMMANDS = new Set(['run', 'assert', 'aggregate', 'bundle', 'vitest']);
const VITEST_VERIFIER_RE = /(?:^|[/\\])(?:EVAL|[^/\\]+[.-](?:test|spec))\.[cm]?[jt]sx?$/i;
/**
* Top-level CLI command names (excluding `eval` itself).
* Used to ensure `eval` is the top-level subcommand, not nested.
*/
const TOP_LEVEL_COMMANDS = new Set([
'import',
'inspect',
'compare',
'convert',
'create',
'dashboard',
'doctor',
'grade',
'init',
'pipeline',
'prepare',
'results',
'runs',
'self',
'skills',
'serve',
'studio',
'trend',
'transpile',
'trim',
'validate',
]);
export function usesDeprecatedStudioAlias(argv: string[]): boolean {
return argv[2] === 'studio';
}
export function shouldRunBeforeSessionHook(argv: string[]): boolean {
return !(argv[2] === 'eval' && argv[3] === 'vitest');
}
export function inferEvalSubcommand(arg: string | undefined): 'run' | 'vitest' {
return arg && VITEST_VERIFIER_RE.test(arg) ? 'vitest' : 'run';
}
/**
* Preprocess argv for convenience aliases:
* - `--eval-id` → `--test-id`
* - `agentv eval <non-subcommand>` → `agentv eval run <non-subcommand>`
* (backward compat: `eval` used to be a direct command, now it's a group)
*/
export function preprocessArgv(argv: string[]): string[] {
const result = [...argv];
if (result[2] === 'studio') {
result[2] = 'dashboard';
}
// Rewrite --eval-id → --test-id (convenience alias)
for (let i = 0; i < result.length; i++) {
if (result[i] === '--eval-id') {
result[i] = '--test-id';
} else if (result[i].startsWith('--eval-id=')) {
result[i] = `--test-id=${result[i].slice('--eval-id='.length)}`;
}
}
// Implicit eval subcommand: `agentv eval [<arg>]` injects the inferred command
// when the first arg after `eval` is absent or is not a known eval subcommand.
// Backward-compat: `eval` used to be a direct command; now it is a subcommands group.
// Bare `agentv eval` falls through to the run handler so its TTY check can launch
// the interactive wizard. Vitest-looking verifier files use the protocol adapter
// directly so deterministic workspace graders can stay short in eval YAML.
// Only applies when `eval` is the top-level subcommand.
// Exception: `--help` / `-h` should show the eval group help, not run's help.
const evalIdx = result.indexOf('eval');
if (evalIdx !== -1) {
// Ensure no top-level command appears before `eval` in the argv —
// if one does, `eval` is a nested subcommand.
const isTopLevel = !result.slice(0, evalIdx).some((arg) => TOP_LEVEL_COMMANDS.has(arg));
if (isTopLevel) {
const nextArg = result[evalIdx + 1];
const isHelp = nextArg === '--help' || nextArg === '-h';
const isKnownSubcommand = nextArg !== undefined && EVAL_SUBCOMMANDS.has(nextArg);
if (!isHelp && !isKnownSubcommand) {
result.splice(evalIdx + 1, 0, inferEvalSubcommand(nextArg));
}
}
}
return result;
}
export async function runCli(argv: string[] = process.argv): Promise<void> {
// Kick off update check: reads from local cache (fast), spawns a detached
// child to refresh if stale. The notice is printed on process exit so it
// appears after command output, even if the command calls process.exit().
let updateNotice: string | null = null;
process.on('exit', () => {
if (updateNotice) process.stderr.write(`\n${updateNotice}\n`);
});
getUpdateNotice(packageJson.version).then((n) => {
updateNotice = n;
});
const processedArgv = preprocessArgv(argv);
if (usesDeprecatedStudioAlias(argv)) {
process.stderr.write(
'Warning: `agentv studio` is deprecated and will be removed in a future release. Use `agentv dashboard` instead.\n',
);
}
if (shouldRunBeforeSessionHook(processedArgv)) {
// Load env_path/env_from and run the before_session hook once at startup,
// before any command executes. Uses cwd as the search root for
// .agentv/config.yaml so validate/eval commands see the injected vars.
const cwd = process.cwd();
const repoRoot = await findRepoRoot(cwd);
const sessionConfig = await loadConfig(path.join(cwd, '_'), repoRoot);
const configDir = sessionConfig?.configDir ?? repoRoot;
if (sessionConfig?.env_path) {
await loadEnvPathFiles(sessionConfig.env_path, configDir);
}
if (sessionConfig?.env_from) {
await runEnvFromEntries(sessionConfig.env_from, { cwd: configDir });
}
const beforeSessionCommand = sessionConfig?.hooks?.before_session;
if (beforeSessionCommand) {
runBeforeSessionHook(beforeSessionCommand);
}
}
await run(binary(app), processedArgv);
}