levante 0.1.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/agents/0.init-agent.md +83 -0
- package/agents/1_1.transcript-agent.md +107 -0
- package/agents/1_2.scenario-agent.md +90 -0
- package/agents/2.playwright-generator-agent.md +96 -0
- package/agents/3.refactor-agent.md +51 -0
- package/agents/4.self-healing-agent.md +90 -0
- package/agents/5.qa-testcase-agent.md +77 -0
- package/dist/cli-039axkk7.js +251 -0
- package/dist/cli-0kkk77d6.js +251 -0
- package/dist/cli-69cp23fq.js +76 -0
- package/dist/cli-6wrk5ptg.js +250 -0
- package/dist/cli-akwt7nqw.js +67 -0
- package/dist/cli-hdefnftg.js +13610 -0
- package/dist/cli-w0v11cvq.js +13610 -0
- package/dist/cli-wckvcay0.js +48 -0
- package/dist/cli.js +9214 -0
- package/dist/config/schema.js +9 -0
- package/dist/index-2qecm8mk.js +7073 -0
- package/dist/index.js +15 -0
- package/dist/mcp.js +19647 -0
- package/package.json +43 -0
- package/scripts/auth/setup-auth.mjs +118 -0
- package/scripts/codegen-env.mjs +377 -0
- package/scripts/exporters/zephyr-json-to-import-xml.ts +156 -0
- package/scripts/trace/replay-with-trace.mjs +95 -0
- package/scripts/trace/replay.config.ts +37 -0
- package/scripts/voice/merger.mjs +119 -0
- package/scripts/voice/recorder.mjs +54 -0
- package/scripts/voice/transcriber.mjs +52 -0
- package/templates/e2e-ai.context.example.md +93 -0
- package/templates/workflow.md +289 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONFIG_DIR,
|
|
3
|
+
getPackageRoot,
|
|
4
|
+
getProjectRoot
|
|
5
|
+
} from "./cli-69cp23fq.js";
|
|
6
|
+
import {
|
|
7
|
+
__commonJS,
|
|
8
|
+
__toESM
|
|
9
|
+
} from "./cli-wckvcay0.js";
|
|
10
|
+
|
|
11
|
+
// ../../node_modules/.bun/picocolors@1.1.1/node_modules/picocolors/picocolors.js
|
|
12
|
+
var require_picocolors = __commonJS((exports, module) => {
|
|
13
|
+
var p = process || {};
|
|
14
|
+
var argv = p.argv || [];
|
|
15
|
+
var env = p.env || {};
|
|
16
|
+
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
17
|
+
var formatter = (open, close, replace = open) => (input) => {
|
|
18
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
19
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
20
|
+
};
|
|
21
|
+
var replaceClose = (string, close, replace, index) => {
|
|
22
|
+
let result = "", cursor = 0;
|
|
23
|
+
do {
|
|
24
|
+
result += string.substring(cursor, index) + replace;
|
|
25
|
+
cursor = index + close.length;
|
|
26
|
+
index = string.indexOf(close, cursor);
|
|
27
|
+
} while (~index);
|
|
28
|
+
return result + string.substring(cursor);
|
|
29
|
+
};
|
|
30
|
+
var createColors = (enabled = isColorSupported) => {
|
|
31
|
+
let f = enabled ? formatter : () => String;
|
|
32
|
+
return {
|
|
33
|
+
isColorSupported: enabled,
|
|
34
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
35
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
36
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
37
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
38
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
39
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
40
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
41
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
42
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
43
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
44
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
45
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
46
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
47
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
48
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
49
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
50
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
51
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
52
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
53
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
54
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
55
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
56
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
57
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
58
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
59
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
60
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
61
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
62
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
63
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
64
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
65
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
66
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
67
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
68
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
69
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
70
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
71
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
72
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
73
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
74
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
module.exports = createColors();
|
|
78
|
+
module.exports.createColors = createColors;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// src/utils/logger.ts
|
|
82
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
83
|
+
var verboseEnabled = false;
|
|
84
|
+
function setVerbose(enabled) {
|
|
85
|
+
verboseEnabled = enabled;
|
|
86
|
+
}
|
|
87
|
+
function info(msg) {
|
|
88
|
+
console.log(import_picocolors.default.blue("i") + " " + msg);
|
|
89
|
+
}
|
|
90
|
+
function success(msg) {
|
|
91
|
+
console.log(import_picocolors.default.green("✓") + " " + msg);
|
|
92
|
+
}
|
|
93
|
+
function warn(msg) {
|
|
94
|
+
console.log(import_picocolors.default.yellow("!") + " " + msg);
|
|
95
|
+
}
|
|
96
|
+
function error(msg) {
|
|
97
|
+
console.error(import_picocolors.default.red("✗") + " " + msg);
|
|
98
|
+
}
|
|
99
|
+
function step(current, total, name, description) {
|
|
100
|
+
console.log(import_picocolors.default.cyan(`[${current}/${total}]`) + " " + import_picocolors.default.bold(name) + ": " + description);
|
|
101
|
+
}
|
|
102
|
+
function verbose(msg) {
|
|
103
|
+
if (verboseEnabled) {
|
|
104
|
+
console.log(import_picocolors.default.gray(" " + msg));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function header(title) {
|
|
108
|
+
console.log(`
|
|
109
|
+
` + import_picocolors.default.bold(import_picocolors.default.magenta(title)));
|
|
110
|
+
console.log(import_picocolors.default.gray("─".repeat(title.length + 4)));
|
|
111
|
+
}
|
|
112
|
+
function banner(version) {
|
|
113
|
+
const title = ` levante v${version}`;
|
|
114
|
+
const subtitle = " AI-powered E2E test automation";
|
|
115
|
+
const width = Math.max(title.length, subtitle.length) + 2;
|
|
116
|
+
const top = import_picocolors.default.cyan("┌" + "─".repeat(width) + "┐");
|
|
117
|
+
const mid1 = import_picocolors.default.cyan("│") + import_picocolors.default.bold(title) + " ".repeat(width - title.length) + import_picocolors.default.cyan("│");
|
|
118
|
+
const mid2 = import_picocolors.default.cyan("│") + import_picocolors.default.gray(subtitle) + " ".repeat(width - subtitle.length) + import_picocolors.default.cyan("│");
|
|
119
|
+
const bottom = import_picocolors.default.cyan("└" + "─".repeat(width) + "┘");
|
|
120
|
+
console.log(top);
|
|
121
|
+
console.log(mid1);
|
|
122
|
+
console.log(mid2);
|
|
123
|
+
console.log(bottom);
|
|
124
|
+
}
|
|
125
|
+
function summary(steps) {
|
|
126
|
+
const nameCol = 16;
|
|
127
|
+
const statusCol = 10;
|
|
128
|
+
const headerLine = "Step".padEnd(nameCol) + "Status".padEnd(statusCol) + "Duration";
|
|
129
|
+
const separator = "─".repeat(nameCol + statusCol + 8);
|
|
130
|
+
console.log(import_picocolors.default.bold(headerLine));
|
|
131
|
+
console.log(import_picocolors.default.gray(separator));
|
|
132
|
+
for (const s of steps) {
|
|
133
|
+
const name = s.name.padEnd(nameCol);
|
|
134
|
+
const status = s.result.success ? import_picocolors.default.green("✓ pass".padEnd(statusCol)) : import_picocolors.default.red("✗ fail".padEnd(statusCol));
|
|
135
|
+
const duration = s.durationMs >= 1000 ? `${(s.durationMs / 1000).toFixed(1)}s` : `${Math.round(s.durationMs)}ms`;
|
|
136
|
+
console.log(name + status + duration);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/agents/loadAgent.ts
|
|
141
|
+
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
142
|
+
import { join } from "node:path";
|
|
143
|
+
function resolveAgentFile(dir, agentName) {
|
|
144
|
+
const exact = join(dir, `${agentName}.md`);
|
|
145
|
+
if (existsSync(exact))
|
|
146
|
+
return exact;
|
|
147
|
+
try {
|
|
148
|
+
const files = readdirSync(dir);
|
|
149
|
+
const suffix = `.${agentName}.md`;
|
|
150
|
+
const match = files.find((f) => f.endsWith(suffix));
|
|
151
|
+
if (match)
|
|
152
|
+
return join(dir, match);
|
|
153
|
+
} catch {}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
function loadAgent(agentName, config) {
|
|
157
|
+
const localDir = join(getProjectRoot(), CONFIG_DIR, "agents");
|
|
158
|
+
const packageDir = join(getPackageRoot(), "agents");
|
|
159
|
+
const filePath = resolveAgentFile(localDir, agentName) ?? resolveAgentFile(packageDir, agentName);
|
|
160
|
+
if (!filePath) {
|
|
161
|
+
throw new Error(`Agent file not found for "${agentName}" in ${localDir} or ${packageDir}`);
|
|
162
|
+
}
|
|
163
|
+
let content;
|
|
164
|
+
try {
|
|
165
|
+
content = readFileSync(filePath, "utf-8");
|
|
166
|
+
} catch {
|
|
167
|
+
throw new Error(`Agent file not readable: ${filePath}`);
|
|
168
|
+
}
|
|
169
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
170
|
+
const agentConfig = extractConfig(frontmatter);
|
|
171
|
+
let systemPrompt = body;
|
|
172
|
+
if (config) {
|
|
173
|
+
const contextPath = join(getProjectRoot(), CONFIG_DIR, "context.md");
|
|
174
|
+
if (existsSync(contextPath)) {
|
|
175
|
+
const projectContext = readFileSync(contextPath, "utf-8").trim();
|
|
176
|
+
if (projectContext) {
|
|
177
|
+
systemPrompt = `${body}
|
|
178
|
+
|
|
179
|
+
## Project Context
|
|
180
|
+
|
|
181
|
+
${projectContext}`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (config.llm.agentModels[agentName]) {
|
|
185
|
+
agentConfig.model = config.llm.agentModels[agentName];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const sections = parseSections(body);
|
|
189
|
+
return {
|
|
190
|
+
name: frontmatter.agent ?? agentName,
|
|
191
|
+
systemPrompt,
|
|
192
|
+
inputSchema: sections["Input Schema"],
|
|
193
|
+
outputSchema: sections["Output Schema"],
|
|
194
|
+
rules: sections["Rules"],
|
|
195
|
+
example: sections["Example"],
|
|
196
|
+
config: agentConfig
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function parseFrontmatter(content) {
|
|
200
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
201
|
+
if (!match)
|
|
202
|
+
return { frontmatter: {}, body: content };
|
|
203
|
+
const frontmatter = {};
|
|
204
|
+
for (const line of match[1].split(`
|
|
205
|
+
`)) {
|
|
206
|
+
const colonIdx = line.indexOf(":");
|
|
207
|
+
if (colonIdx === -1)
|
|
208
|
+
continue;
|
|
209
|
+
const key = line.slice(0, colonIdx).trim();
|
|
210
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
211
|
+
if (value.startsWith('"') && value.endsWith('"'))
|
|
212
|
+
value = value.slice(1, -1);
|
|
213
|
+
if (value === "true")
|
|
214
|
+
value = true;
|
|
215
|
+
if (value === "false")
|
|
216
|
+
value = false;
|
|
217
|
+
if (!isNaN(Number(value)) && value !== "")
|
|
218
|
+
value = Number(value);
|
|
219
|
+
frontmatter[key] = value;
|
|
220
|
+
}
|
|
221
|
+
return { frontmatter, body: match[2] };
|
|
222
|
+
}
|
|
223
|
+
function extractConfig(frontmatter) {
|
|
224
|
+
return {
|
|
225
|
+
model: frontmatter.model,
|
|
226
|
+
maxTokens: frontmatter.max_tokens ?? 4096,
|
|
227
|
+
temperature: frontmatter.temperature ?? 0.2
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function parseSections(body) {
|
|
231
|
+
const sections = {};
|
|
232
|
+
const headingRegex = /^##\s+(.+)$/gm;
|
|
233
|
+
const headings = [];
|
|
234
|
+
let match;
|
|
235
|
+
while ((match = headingRegex.exec(body)) !== null) {
|
|
236
|
+
headings.push({ title: match[1].trim(), index: match.index });
|
|
237
|
+
}
|
|
238
|
+
const systemMatch = body.match(/^#\s+System Prompt\n([\s\S]*?)(?=\n##\s|$)/m);
|
|
239
|
+
if (systemMatch) {
|
|
240
|
+
sections["System Prompt"] = systemMatch[1].trim();
|
|
241
|
+
}
|
|
242
|
+
for (let i = 0;i < headings.length; i++) {
|
|
243
|
+
const start = headings[i].index + body.slice(headings[i].index).indexOf(`
|
|
244
|
+
`) + 1;
|
|
245
|
+
const end = i + 1 < headings.length ? headings[i + 1].index : body.length;
|
|
246
|
+
sections[headings[i].title] = body.slice(start, end).trim();
|
|
247
|
+
}
|
|
248
|
+
return sections;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export { require_picocolors, setVerbose, info, success, warn, error, step, verbose, header, banner, summary, loadAgent };
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONFIG_DIR,
|
|
3
|
+
getPackageRoot,
|
|
4
|
+
getProjectRoot
|
|
5
|
+
} from "./cli-69cp23fq.js";
|
|
6
|
+
import {
|
|
7
|
+
__commonJS,
|
|
8
|
+
__toESM
|
|
9
|
+
} from "./cli-wckvcay0.js";
|
|
10
|
+
|
|
11
|
+
// ../../node_modules/.bun/picocolors@1.1.1/node_modules/picocolors/picocolors.js
|
|
12
|
+
var require_picocolors = __commonJS((exports, module) => {
|
|
13
|
+
var p = process || {};
|
|
14
|
+
var argv = p.argv || [];
|
|
15
|
+
var env = p.env || {};
|
|
16
|
+
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
17
|
+
var formatter = (open, close, replace = open) => (input) => {
|
|
18
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
19
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
20
|
+
};
|
|
21
|
+
var replaceClose = (string, close, replace, index) => {
|
|
22
|
+
let result = "", cursor = 0;
|
|
23
|
+
do {
|
|
24
|
+
result += string.substring(cursor, index) + replace;
|
|
25
|
+
cursor = index + close.length;
|
|
26
|
+
index = string.indexOf(close, cursor);
|
|
27
|
+
} while (~index);
|
|
28
|
+
return result + string.substring(cursor);
|
|
29
|
+
};
|
|
30
|
+
var createColors = (enabled = isColorSupported) => {
|
|
31
|
+
let f = enabled ? formatter : () => String;
|
|
32
|
+
return {
|
|
33
|
+
isColorSupported: enabled,
|
|
34
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
35
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
36
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
37
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
38
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
39
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
40
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
41
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
42
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
43
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
44
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
45
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
46
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
47
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
48
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
49
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
50
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
51
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
52
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
53
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
54
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
55
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
56
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
57
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
58
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
59
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
60
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
61
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
62
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
63
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
64
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
65
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
66
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
67
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
68
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
69
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
70
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
71
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
72
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
73
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
74
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
module.exports = createColors();
|
|
78
|
+
module.exports.createColors = createColors;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// src/utils/logger.ts
|
|
82
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
83
|
+
var verboseEnabled = false;
|
|
84
|
+
function setVerbose(enabled) {
|
|
85
|
+
verboseEnabled = enabled;
|
|
86
|
+
}
|
|
87
|
+
function info(msg) {
|
|
88
|
+
console.log(import_picocolors.default.blue("i") + " " + msg);
|
|
89
|
+
}
|
|
90
|
+
function success(msg) {
|
|
91
|
+
console.log(import_picocolors.default.green("✓") + " " + msg);
|
|
92
|
+
}
|
|
93
|
+
function warn(msg) {
|
|
94
|
+
console.log(import_picocolors.default.yellow("!") + " " + msg);
|
|
95
|
+
}
|
|
96
|
+
function error(msg) {
|
|
97
|
+
console.error(import_picocolors.default.red("✗") + " " + msg);
|
|
98
|
+
}
|
|
99
|
+
function step(current, total, name, description) {
|
|
100
|
+
console.log(import_picocolors.default.cyan(`[${current}/${total}]`) + " " + import_picocolors.default.bold(name) + ": " + description);
|
|
101
|
+
}
|
|
102
|
+
function verbose(msg) {
|
|
103
|
+
if (verboseEnabled) {
|
|
104
|
+
console.log(import_picocolors.default.gray(" " + msg));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function header(title) {
|
|
108
|
+
console.log(`
|
|
109
|
+
` + import_picocolors.default.bold(import_picocolors.default.magenta(title)));
|
|
110
|
+
console.log(import_picocolors.default.gray("─".repeat(title.length + 4)));
|
|
111
|
+
}
|
|
112
|
+
function banner(version) {
|
|
113
|
+
const title = ` e2e-ai v${version}`;
|
|
114
|
+
const subtitle = " AI-powered E2E test automation";
|
|
115
|
+
const width = Math.max(title.length, subtitle.length) + 2;
|
|
116
|
+
const top = import_picocolors.default.cyan("┌" + "─".repeat(width) + "┐");
|
|
117
|
+
const mid1 = import_picocolors.default.cyan("│") + import_picocolors.default.bold(title) + " ".repeat(width - title.length) + import_picocolors.default.cyan("│");
|
|
118
|
+
const mid2 = import_picocolors.default.cyan("│") + import_picocolors.default.gray(subtitle) + " ".repeat(width - subtitle.length) + import_picocolors.default.cyan("│");
|
|
119
|
+
const bottom = import_picocolors.default.cyan("└" + "─".repeat(width) + "┘");
|
|
120
|
+
console.log(top);
|
|
121
|
+
console.log(mid1);
|
|
122
|
+
console.log(mid2);
|
|
123
|
+
console.log(bottom);
|
|
124
|
+
}
|
|
125
|
+
function summary(steps) {
|
|
126
|
+
const nameCol = 16;
|
|
127
|
+
const statusCol = 10;
|
|
128
|
+
const headerLine = "Step".padEnd(nameCol) + "Status".padEnd(statusCol) + "Duration";
|
|
129
|
+
const separator = "─".repeat(nameCol + statusCol + 8);
|
|
130
|
+
console.log(import_picocolors.default.bold(headerLine));
|
|
131
|
+
console.log(import_picocolors.default.gray(separator));
|
|
132
|
+
for (const s of steps) {
|
|
133
|
+
const name = s.name.padEnd(nameCol);
|
|
134
|
+
const status = s.result.success ? import_picocolors.default.green("✓ pass".padEnd(statusCol)) : import_picocolors.default.red("✗ fail".padEnd(statusCol));
|
|
135
|
+
const duration = s.durationMs >= 1000 ? `${(s.durationMs / 1000).toFixed(1)}s` : `${Math.round(s.durationMs)}ms`;
|
|
136
|
+
console.log(name + status + duration);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/agents/loadAgent.ts
|
|
141
|
+
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
142
|
+
import { join } from "node:path";
|
|
143
|
+
function resolveAgentFile(dir, agentName) {
|
|
144
|
+
const exact = join(dir, `${agentName}.md`);
|
|
145
|
+
if (existsSync(exact))
|
|
146
|
+
return exact;
|
|
147
|
+
try {
|
|
148
|
+
const files = readdirSync(dir);
|
|
149
|
+
const suffix = `.${agentName}.md`;
|
|
150
|
+
const match = files.find((f) => f.endsWith(suffix));
|
|
151
|
+
if (match)
|
|
152
|
+
return join(dir, match);
|
|
153
|
+
} catch {}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
function loadAgent(agentName, config) {
|
|
157
|
+
const localDir = join(getProjectRoot(), CONFIG_DIR, "agents");
|
|
158
|
+
const packageDir = join(getPackageRoot(), "agents");
|
|
159
|
+
const filePath = resolveAgentFile(localDir, agentName) ?? resolveAgentFile(packageDir, agentName);
|
|
160
|
+
if (!filePath) {
|
|
161
|
+
throw new Error(`Agent file not found for "${agentName}" in ${localDir} or ${packageDir}`);
|
|
162
|
+
}
|
|
163
|
+
let content;
|
|
164
|
+
try {
|
|
165
|
+
content = readFileSync(filePath, "utf-8");
|
|
166
|
+
} catch {
|
|
167
|
+
throw new Error(`Agent file not readable: ${filePath}`);
|
|
168
|
+
}
|
|
169
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
170
|
+
const agentConfig = extractConfig(frontmatter);
|
|
171
|
+
let systemPrompt = body;
|
|
172
|
+
if (config) {
|
|
173
|
+
const contextPath = join(getProjectRoot(), CONFIG_DIR, "context.md");
|
|
174
|
+
if (existsSync(contextPath)) {
|
|
175
|
+
const projectContext = readFileSync(contextPath, "utf-8").trim();
|
|
176
|
+
if (projectContext) {
|
|
177
|
+
systemPrompt = `${body}
|
|
178
|
+
|
|
179
|
+
## Project Context
|
|
180
|
+
|
|
181
|
+
${projectContext}`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (config.llm.agentModels[agentName]) {
|
|
185
|
+
agentConfig.model = config.llm.agentModels[agentName];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const sections = parseSections(body);
|
|
189
|
+
return {
|
|
190
|
+
name: frontmatter.agent ?? agentName,
|
|
191
|
+
systemPrompt,
|
|
192
|
+
inputSchema: sections["Input Schema"],
|
|
193
|
+
outputSchema: sections["Output Schema"],
|
|
194
|
+
rules: sections["Rules"],
|
|
195
|
+
example: sections["Example"],
|
|
196
|
+
config: agentConfig
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function parseFrontmatter(content) {
|
|
200
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
201
|
+
if (!match)
|
|
202
|
+
return { frontmatter: {}, body: content };
|
|
203
|
+
const frontmatter = {};
|
|
204
|
+
for (const line of match[1].split(`
|
|
205
|
+
`)) {
|
|
206
|
+
const colonIdx = line.indexOf(":");
|
|
207
|
+
if (colonIdx === -1)
|
|
208
|
+
continue;
|
|
209
|
+
const key = line.slice(0, colonIdx).trim();
|
|
210
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
211
|
+
if (value.startsWith('"') && value.endsWith('"'))
|
|
212
|
+
value = value.slice(1, -1);
|
|
213
|
+
if (value === "true")
|
|
214
|
+
value = true;
|
|
215
|
+
if (value === "false")
|
|
216
|
+
value = false;
|
|
217
|
+
if (!isNaN(Number(value)) && value !== "")
|
|
218
|
+
value = Number(value);
|
|
219
|
+
frontmatter[key] = value;
|
|
220
|
+
}
|
|
221
|
+
return { frontmatter, body: match[2] };
|
|
222
|
+
}
|
|
223
|
+
function extractConfig(frontmatter) {
|
|
224
|
+
return {
|
|
225
|
+
model: frontmatter.model,
|
|
226
|
+
maxTokens: frontmatter.max_tokens ?? 4096,
|
|
227
|
+
temperature: frontmatter.temperature ?? 0.2
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function parseSections(body) {
|
|
231
|
+
const sections = {};
|
|
232
|
+
const headingRegex = /^##\s+(.+)$/gm;
|
|
233
|
+
const headings = [];
|
|
234
|
+
let match;
|
|
235
|
+
while ((match = headingRegex.exec(body)) !== null) {
|
|
236
|
+
headings.push({ title: match[1].trim(), index: match.index });
|
|
237
|
+
}
|
|
238
|
+
const systemMatch = body.match(/^#\s+System Prompt\n([\s\S]*?)(?=\n##\s|$)/m);
|
|
239
|
+
if (systemMatch) {
|
|
240
|
+
sections["System Prompt"] = systemMatch[1].trim();
|
|
241
|
+
}
|
|
242
|
+
for (let i = 0;i < headings.length; i++) {
|
|
243
|
+
const start = headings[i].index + body.slice(headings[i].index).indexOf(`
|
|
244
|
+
`) + 1;
|
|
245
|
+
const end = i + 1 < headings.length ? headings[i + 1].index : body.length;
|
|
246
|
+
sections[headings[i].title] = body.slice(start, end).trim();
|
|
247
|
+
}
|
|
248
|
+
return sections;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export { require_picocolors, setVerbose, info, success, warn, error, step, verbose, header, banner, summary, loadAgent };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
E2eAiConfigSchema
|
|
3
|
+
} from "./cli-w0v11cvq.js";
|
|
4
|
+
|
|
5
|
+
// src/config/loader.ts
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { dirname, join, resolve } from "node:path";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
var CONFIG_DIR = ".qai/levante";
|
|
10
|
+
var LEGACY_CONFIG_DIR = ".e2e-ai";
|
|
11
|
+
var CONFIG_FILENAMES = ["config.ts", "config.js", "config.mjs"];
|
|
12
|
+
var cachedConfig = null;
|
|
13
|
+
var cachedProjectRoot = null;
|
|
14
|
+
function findConfigDir(startDir) {
|
|
15
|
+
let dir = resolve(startDir);
|
|
16
|
+
const root = dirname(dir) === dir ? dir : undefined;
|
|
17
|
+
while (true) {
|
|
18
|
+
for (const configDir of [CONFIG_DIR, LEGACY_CONFIG_DIR]) {
|
|
19
|
+
const candidate = join(dir, configDir);
|
|
20
|
+
for (const name of CONFIG_FILENAMES) {
|
|
21
|
+
if (existsSync(join(candidate, name))) {
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const parent = dirname(dir);
|
|
27
|
+
if (parent === dir || dir === root)
|
|
28
|
+
return null;
|
|
29
|
+
dir = parent;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function getProjectRoot() {
|
|
33
|
+
if (cachedProjectRoot)
|
|
34
|
+
return cachedProjectRoot;
|
|
35
|
+
const found = findConfigDir(process.cwd());
|
|
36
|
+
cachedProjectRoot = found ?? process.cwd();
|
|
37
|
+
return cachedProjectRoot;
|
|
38
|
+
}
|
|
39
|
+
function getPackageRoot() {
|
|
40
|
+
let dir = import.meta.dirname;
|
|
41
|
+
while (!existsSync(join(dir, "package.json"))) {
|
|
42
|
+
const parent = dirname(dir);
|
|
43
|
+
if (parent === dir)
|
|
44
|
+
return dir;
|
|
45
|
+
dir = parent;
|
|
46
|
+
}
|
|
47
|
+
return dir;
|
|
48
|
+
}
|
|
49
|
+
async function loadConfig() {
|
|
50
|
+
if (cachedConfig)
|
|
51
|
+
return cachedConfig;
|
|
52
|
+
const projectRoot = getProjectRoot();
|
|
53
|
+
let userConfig = {};
|
|
54
|
+
for (const configDir of [CONFIG_DIR, LEGACY_CONFIG_DIR]) {
|
|
55
|
+
const dir = join(projectRoot, configDir);
|
|
56
|
+
let found = false;
|
|
57
|
+
for (const name of CONFIG_FILENAMES) {
|
|
58
|
+
const configPath = join(dir, name);
|
|
59
|
+
if (existsSync(configPath)) {
|
|
60
|
+
try {
|
|
61
|
+
const fileUrl = pathToFileURL(configPath).href;
|
|
62
|
+
const mod = await import(fileUrl);
|
|
63
|
+
userConfig = mod.default ?? mod;
|
|
64
|
+
found = true;
|
|
65
|
+
break;
|
|
66
|
+
} catch {}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (found)
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
cachedConfig = E2eAiConfigSchema.parse(userConfig);
|
|
73
|
+
return cachedConfig;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { CONFIG_DIR, getProjectRoot, getPackageRoot, loadConfig };
|