@empiricalrun/test-gen 0.66.1 → 0.67.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/CHANGELOG.md +17 -0
- package/dist/agent/browsing/run.js +4 -4
- package/dist/agent/browsing/utils.d.ts +1 -0
- package/dist/agent/browsing/utils.d.ts.map +1 -1
- package/dist/agent/browsing/utils.js +5 -4
- package/dist/agent/chat/index.d.ts +1 -0
- package/dist/agent/chat/index.d.ts.map +1 -1
- package/dist/agent/chat/index.js +1 -0
- package/dist/agent/chat/prompt/index.d.ts.map +1 -1
- package/dist/agent/chat/prompt/index.js +3 -1
- package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.d.ts +14 -0
- package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.d.ts.map +1 -0
- package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.js +62 -0
- package/dist/agent/cua/pw-codegen/pw-pause/index.d.ts.map +1 -1
- package/dist/agent/cua/pw-codegen/pw-pause/index.js +19 -15
- package/dist/agent/cua/pw-codegen/pw-pause/types.d.ts +14 -0
- package/dist/agent/cua/pw-codegen/pw-pause/types.d.ts.map +1 -0
- package/dist/agent/cua/pw-codegen/pw-pause/types.js +2 -0
- package/dist/artifacts/utils.d.ts +1 -1
- package/dist/artifacts/utils.d.ts.map +1 -1
- package/dist/artifacts/utils.js +5 -5
- package/dist/auth/api-client.d.ts +12 -0
- package/dist/auth/api-client.d.ts.map +1 -0
- package/dist/auth/api-client.js +133 -0
- package/dist/auth/cli-auth.d.ts +18 -0
- package/dist/auth/cli-auth.d.ts.map +1 -0
- package/dist/auth/cli-auth.js +184 -0
- package/dist/auth/index.d.ts +4 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +17 -0
- package/dist/auth/token-store.d.ts +15 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +149 -0
- package/dist/bin/index.js +98 -1
- package/dist/bin/utils/index.d.ts +2 -1
- package/dist/bin/utils/index.d.ts.map +1 -1
- package/dist/bin/utils/index.js +34 -5
- package/dist/browser-injected-scripts/annotate-elements.js +1 -4
- package/dist/file/client.d.ts +1 -0
- package/dist/file/client.d.ts.map +1 -1
- package/dist/file/client.js +3 -0
- package/dist/file/server.d.ts +2 -0
- package/dist/file/server.d.ts.map +1 -1
- package/dist/file/server.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/recorder/display.d.ts +2 -0
- package/dist/recorder/display.d.ts.map +1 -0
- package/dist/recorder/display.js +50 -0
- package/dist/recorder/index.d.ts +4 -0
- package/dist/recorder/index.d.ts.map +1 -0
- package/dist/recorder/index.js +108 -0
- package/dist/recorder/request.d.ts +6 -0
- package/dist/recorder/request.d.ts.map +1 -0
- package/dist/recorder/request.js +55 -0
- package/dist/recorder/temp-files.d.ts +3 -0
- package/dist/recorder/temp-files.d.ts.map +1 -0
- package/dist/recorder/temp-files.js +39 -0
- package/dist/recorder/upload.d.ts +2 -0
- package/dist/recorder/upload.d.ts.map +1 -0
- package/dist/recorder/upload.js +85 -0
- package/dist/recorder/validation.d.ts +2 -0
- package/dist/recorder/validation.d.ts.map +1 -0
- package/dist/recorder/validation.js +24 -0
- package/package.json +2 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.authenticate = authenticate;
|
|
7
|
+
exports.logout = logout;
|
|
8
|
+
exports.getAuthStatus = getAuthStatus;
|
|
9
|
+
const detect_port_1 = __importDefault(require("detect-port"));
|
|
10
|
+
const http_1 = require("http");
|
|
11
|
+
const open_1 = __importDefault(require("open"));
|
|
12
|
+
const url_1 = require("url");
|
|
13
|
+
const token_store_1 = require("./token-store");
|
|
14
|
+
const CLIENT_PORT_DEFAULT = 8080;
|
|
15
|
+
// Get dashboard URL
|
|
16
|
+
const getDashboardUrl = () => {
|
|
17
|
+
return process.env.DASHBOARD_DOMAIN || "https://dash.empirical.run";
|
|
18
|
+
};
|
|
19
|
+
async function authenticate() {
|
|
20
|
+
// Check if already authenticated
|
|
21
|
+
if (await (0, token_store_1.isAuthenticated)()) {
|
|
22
|
+
const tokens = await (0, token_store_1.getStoredTokens)();
|
|
23
|
+
if (tokens) {
|
|
24
|
+
return {
|
|
25
|
+
success: true,
|
|
26
|
+
user: {
|
|
27
|
+
id: tokens.user_id || "",
|
|
28
|
+
email: tokens.user_email || "",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const appUrl = getDashboardUrl();
|
|
34
|
+
const clientPort = await (0, detect_port_1.default)(CLIENT_PORT_DEFAULT);
|
|
35
|
+
const redirectUri = `http://localhost:${clientPort}/callback`;
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
// Create temporary local server to receive callback
|
|
38
|
+
const server = (0, http_1.createServer)(async (req, res) => {
|
|
39
|
+
const { pathname, query } = (0, url_1.parse)(req.url, true);
|
|
40
|
+
if (pathname === "/callback") {
|
|
41
|
+
const { code, error } = query;
|
|
42
|
+
if (error) {
|
|
43
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
44
|
+
res.end(`
|
|
45
|
+
<html>
|
|
46
|
+
<head><title>Authentication Failed</title></head>
|
|
47
|
+
<body style="font-family: sans-serif; text-align: center; padding: 50px;">
|
|
48
|
+
<h1 style="color: #e74c3c;">Authentication Failed</h1>
|
|
49
|
+
<p>Error: ${error}</p>
|
|
50
|
+
<p>You can close this window and try again.</p>
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
|
53
|
+
`);
|
|
54
|
+
server.close();
|
|
55
|
+
resolve({
|
|
56
|
+
success: false,
|
|
57
|
+
error: error,
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (code) {
|
|
62
|
+
try {
|
|
63
|
+
// Exchange the temporary code for session tokens
|
|
64
|
+
const response = await fetch(`${appUrl}/api/auth/cli/exchange`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify({ code }),
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new Error(`Authentication failed: ${response.statusText}`);
|
|
73
|
+
}
|
|
74
|
+
const { session, user } = await response.json();
|
|
75
|
+
if (!session || !user) {
|
|
76
|
+
throw new Error("Invalid response from authentication server");
|
|
77
|
+
}
|
|
78
|
+
const tokens = {
|
|
79
|
+
access_token: session.access_token,
|
|
80
|
+
refresh_token: session.refresh_token,
|
|
81
|
+
expires_at: session.expires_at,
|
|
82
|
+
user_id: user.id,
|
|
83
|
+
user_email: user.email,
|
|
84
|
+
};
|
|
85
|
+
await (0, token_store_1.storeTokens)(tokens);
|
|
86
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
87
|
+
res.end(`
|
|
88
|
+
<html>
|
|
89
|
+
<head><title>Authentication Successful</title></head>
|
|
90
|
+
<body style="font-family: sans-serif; text-align: center; padding: 50px;">
|
|
91
|
+
<h1 style="color: #27ae60;">Authentication Successful!</h1>
|
|
92
|
+
<p>Welcome, ${user.email}!</p>
|
|
93
|
+
<p>You can close this window and return to your CLI.</p>
|
|
94
|
+
<script>setTimeout(() => window.close(), 3000);</script>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
97
|
+
`);
|
|
98
|
+
server.close();
|
|
99
|
+
resolve({
|
|
100
|
+
success: true,
|
|
101
|
+
user: {
|
|
102
|
+
id: user.id,
|
|
103
|
+
email: user.email,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
109
|
+
res.end(`
|
|
110
|
+
<html>
|
|
111
|
+
<head><title>Authentication Error</title></head>
|
|
112
|
+
<body style="font-family: sans-serif; text-align: center; padding: 50px;">
|
|
113
|
+
<h1 style="color: #e74c3c;">Authentication Error</h1>
|
|
114
|
+
<p>Something went wrong during authentication.</p>
|
|
115
|
+
<p>Please try again.</p>
|
|
116
|
+
</body>
|
|
117
|
+
</html>
|
|
118
|
+
`);
|
|
119
|
+
server.close();
|
|
120
|
+
resolve({
|
|
121
|
+
success: false,
|
|
122
|
+
error: err.message,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Handle other paths
|
|
129
|
+
res.writeHead(404, { "Content-Type": "text/html" });
|
|
130
|
+
res.end("<h1>Not Found</h1>");
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
server.on("error", (err) => {
|
|
134
|
+
if (err.code === "EADDRINUSE") {
|
|
135
|
+
resolve({
|
|
136
|
+
success: false,
|
|
137
|
+
error: `Port ${clientPort} is already in use. Please ensure no other authentication process is running.`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
resolve({
|
|
142
|
+
success: false,
|
|
143
|
+
error: `Server error: ${err.message}`,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
server.listen(clientPort, () => {
|
|
148
|
+
// Open browser to your Next.js auth page
|
|
149
|
+
const authUrl = `${appUrl}/auth/cli?redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
150
|
+
console.log("Opening browser for authentication...");
|
|
151
|
+
console.log(`If the browser doesn't open automatically, visit: ${authUrl}`);
|
|
152
|
+
(0, open_1.default)(authUrl).catch((err) => {
|
|
153
|
+
console.warn("Could not open browser automatically:", err.message);
|
|
154
|
+
console.log(`Please manually visit: ${authUrl}`);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
// Timeout after 10 minutes
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
server.close();
|
|
160
|
+
resolve({
|
|
161
|
+
success: false,
|
|
162
|
+
error: "Authentication timeout (10 minutes)",
|
|
163
|
+
});
|
|
164
|
+
}, 10 * 60 * 1000);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async function logout() {
|
|
168
|
+
await (0, token_store_1.clearTokens)();
|
|
169
|
+
}
|
|
170
|
+
async function getAuthStatus() {
|
|
171
|
+
if (await (0, token_store_1.isAuthenticated)()) {
|
|
172
|
+
const tokens = await (0, token_store_1.getStoredTokens)();
|
|
173
|
+
if (tokens) {
|
|
174
|
+
return {
|
|
175
|
+
authenticated: true,
|
|
176
|
+
user: {
|
|
177
|
+
id: tokens.user_id || "",
|
|
178
|
+
email: tokens.user_email || "",
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return { authenticated: false };
|
|
184
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { APIClient, apiClient, authenticatedFetch } from "./api-client";
|
|
2
|
+
export { authenticate, getAuthStatus, logout } from "./cli-auth";
|
|
3
|
+
export { clearTokens, getStoredTokens, isAuthenticated, type StoredTokens, storeTokens, } from "./token-store";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EACL,WAAW,EACX,eAAe,EACf,eAAe,EACf,KAAK,YAAY,EACjB,WAAW,GACZ,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.storeTokens = exports.isAuthenticated = exports.getStoredTokens = exports.clearTokens = exports.logout = exports.getAuthStatus = exports.authenticate = exports.authenticatedFetch = exports.apiClient = exports.APIClient = void 0;
|
|
4
|
+
// Export all auth-related functionality
|
|
5
|
+
var api_client_1 = require("./api-client");
|
|
6
|
+
Object.defineProperty(exports, "APIClient", { enumerable: true, get: function () { return api_client_1.APIClient; } });
|
|
7
|
+
Object.defineProperty(exports, "apiClient", { enumerable: true, get: function () { return api_client_1.apiClient; } });
|
|
8
|
+
Object.defineProperty(exports, "authenticatedFetch", { enumerable: true, get: function () { return api_client_1.authenticatedFetch; } });
|
|
9
|
+
var cli_auth_1 = require("./cli-auth");
|
|
10
|
+
Object.defineProperty(exports, "authenticate", { enumerable: true, get: function () { return cli_auth_1.authenticate; } });
|
|
11
|
+
Object.defineProperty(exports, "getAuthStatus", { enumerable: true, get: function () { return cli_auth_1.getAuthStatus; } });
|
|
12
|
+
Object.defineProperty(exports, "logout", { enumerable: true, get: function () { return cli_auth_1.logout; } });
|
|
13
|
+
var token_store_1 = require("./token-store");
|
|
14
|
+
Object.defineProperty(exports, "clearTokens", { enumerable: true, get: function () { return token_store_1.clearTokens; } });
|
|
15
|
+
Object.defineProperty(exports, "getStoredTokens", { enumerable: true, get: function () { return token_store_1.getStoredTokens; } });
|
|
16
|
+
Object.defineProperty(exports, "isAuthenticated", { enumerable: true, get: function () { return token_store_1.isAuthenticated; } });
|
|
17
|
+
Object.defineProperty(exports, "storeTokens", { enumerable: true, get: function () { return token_store_1.storeTokens; } });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface StoredTokens {
|
|
2
|
+
access_token: string;
|
|
3
|
+
refresh_token: string;
|
|
4
|
+
expires_at: number;
|
|
5
|
+
user_id?: string;
|
|
6
|
+
user_email?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface TokenStorage {
|
|
9
|
+
[dashboardDomain: string]: StoredTokens;
|
|
10
|
+
}
|
|
11
|
+
export declare function storeTokens(tokens: StoredTokens): Promise<void>;
|
|
12
|
+
export declare function getStoredTokens(): Promise<StoredTokens | null>;
|
|
13
|
+
export declare function clearTokens(): Promise<void>;
|
|
14
|
+
export declare function isAuthenticated(): Promise<boolean>;
|
|
15
|
+
//# sourceMappingURL=token-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,CAAC,eAAe,EAAE,MAAM,GAAG,YAAY,CAAC;CACzC;AAgCD,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CrE;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAqBpE;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CA4CjD;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAUxD"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.storeTokens = storeTokens;
|
|
4
|
+
exports.getStoredTokens = getStoredTokens;
|
|
5
|
+
exports.clearTokens = clearTokens;
|
|
6
|
+
exports.isAuthenticated = isAuthenticated;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const promises_1 = require("fs/promises");
|
|
9
|
+
const os_1 = require("os");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
const util_1 = require("util");
|
|
12
|
+
const CONFIG_DIR = (0, path_1.join)((0, os_1.homedir)(), ".empiricalrun");
|
|
13
|
+
const TOKEN_FILE = (0, path_1.join)(CONFIG_DIR, "auth.enc");
|
|
14
|
+
async function getEncryptionKey(salt) {
|
|
15
|
+
const machineId =
|
|
16
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
17
|
+
process.env.HOME || process.env.USERPROFILE || "default-machine";
|
|
18
|
+
const scryptAsync = (0, util_1.promisify)(crypto_1.scrypt);
|
|
19
|
+
return (await scryptAsync("empirical-cli-tokens-" + machineId, salt, 32));
|
|
20
|
+
}
|
|
21
|
+
function getDashboardDomain() {
|
|
22
|
+
const domain = process.env.DASHBOARD_DOMAIN || "https://dash.empirical.run";
|
|
23
|
+
// Remove protocol (http:// or https://) to make keys more stable
|
|
24
|
+
return domain.replace(/^https?:\/\//, "");
|
|
25
|
+
}
|
|
26
|
+
async function getTokenStorage() {
|
|
27
|
+
try {
|
|
28
|
+
const encryptedData = await (0, promises_1.readFile)(TOKEN_FILE);
|
|
29
|
+
// Extract salt, IV, and encrypted data
|
|
30
|
+
const salt = encryptedData.subarray(0, 16);
|
|
31
|
+
const iv = encryptedData.subarray(16, 32);
|
|
32
|
+
const encrypted = encryptedData.subarray(32);
|
|
33
|
+
// Derive key from password and salt
|
|
34
|
+
const key = await getEncryptionKey(salt);
|
|
35
|
+
// Decrypt the data
|
|
36
|
+
const decipher = (0, crypto_1.createDecipheriv)("aes-256-cbc", key, iv);
|
|
37
|
+
let decrypted = decipher.update(encrypted, undefined, "utf8");
|
|
38
|
+
decrypted += decipher.final("utf8");
|
|
39
|
+
return JSON.parse(decrypted);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
// Token file doesn't exist or is corrupted
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function storeTokens(tokens) {
|
|
47
|
+
try {
|
|
48
|
+
await (0, promises_1.mkdir)(CONFIG_DIR, { recursive: true });
|
|
49
|
+
const dashboardDomain = getDashboardDomain();
|
|
50
|
+
// Load existing token storage or create new
|
|
51
|
+
let tokenStorage = {};
|
|
52
|
+
try {
|
|
53
|
+
const existingStorage = await getTokenStorage();
|
|
54
|
+
if (existingStorage) {
|
|
55
|
+
tokenStorage = existingStorage;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Ignore errors when loading existing storage
|
|
60
|
+
}
|
|
61
|
+
// Update tokens for current dashboard domain
|
|
62
|
+
tokenStorage[dashboardDomain] = tokens;
|
|
63
|
+
// Generate a random salt and IV
|
|
64
|
+
const salt = (0, crypto_1.randomBytes)(16);
|
|
65
|
+
const iv = (0, crypto_1.randomBytes)(16);
|
|
66
|
+
// Derive key from password and salt
|
|
67
|
+
const key = await getEncryptionKey(salt);
|
|
68
|
+
// Encrypt the token data
|
|
69
|
+
const cipher = (0, crypto_1.createCipheriv)("aes-256-cbc", key, iv);
|
|
70
|
+
let encrypted = cipher.update(JSON.stringify(tokenStorage), "utf8", "hex");
|
|
71
|
+
encrypted += cipher.final("hex");
|
|
72
|
+
// Store salt + iv + encrypted data
|
|
73
|
+
const encryptedData = Buffer.concat([
|
|
74
|
+
salt,
|
|
75
|
+
iv,
|
|
76
|
+
Buffer.from(encrypted, "hex"),
|
|
77
|
+
]);
|
|
78
|
+
await (0, promises_1.writeFile)(TOKEN_FILE, encryptedData, { mode: 0o600 }); // Restrict file permissions
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw new Error(`Failed to store authentication tokens: ${error}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function getStoredTokens() {
|
|
85
|
+
try {
|
|
86
|
+
const dashboardDomain = getDashboardDomain();
|
|
87
|
+
const tokenStorage = await getTokenStorage();
|
|
88
|
+
if (!tokenStorage || !tokenStorage[dashboardDomain]) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const tokens = tokenStorage[dashboardDomain];
|
|
92
|
+
// Validate token structure
|
|
93
|
+
if (!tokens.access_token || !tokens.refresh_token || !tokens.expires_at) {
|
|
94
|
+
throw new Error("Invalid token structure");
|
|
95
|
+
}
|
|
96
|
+
return tokens;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// Token file doesn't exist or is corrupted
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function clearTokens() {
|
|
104
|
+
try {
|
|
105
|
+
const dashboardDomain = getDashboardDomain();
|
|
106
|
+
const tokenStorage = await getTokenStorage();
|
|
107
|
+
if (!tokenStorage) {
|
|
108
|
+
return; // Nothing to clear
|
|
109
|
+
}
|
|
110
|
+
// Remove tokens for current dashboard domain
|
|
111
|
+
delete tokenStorage[dashboardDomain];
|
|
112
|
+
// If no tokens left, remove the file
|
|
113
|
+
if (Object.keys(tokenStorage).length === 0) {
|
|
114
|
+
await (0, promises_1.unlink)(TOKEN_FILE);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Otherwise, save the updated storage
|
|
118
|
+
await (0, promises_1.mkdir)(CONFIG_DIR, { recursive: true });
|
|
119
|
+
// Generate a random salt and IV
|
|
120
|
+
const salt = (0, crypto_1.randomBytes)(16);
|
|
121
|
+
const iv = (0, crypto_1.randomBytes)(16);
|
|
122
|
+
// Derive key from password and salt
|
|
123
|
+
const key = await getEncryptionKey(salt);
|
|
124
|
+
// Encrypt the token data
|
|
125
|
+
const cipher = (0, crypto_1.createCipheriv)("aes-256-cbc", key, iv);
|
|
126
|
+
let encrypted = cipher.update(JSON.stringify(tokenStorage), "utf8", "hex");
|
|
127
|
+
encrypted += cipher.final("hex");
|
|
128
|
+
// Store salt + iv + encrypted data
|
|
129
|
+
const encryptedData = Buffer.concat([
|
|
130
|
+
salt,
|
|
131
|
+
iv,
|
|
132
|
+
Buffer.from(encrypted, "hex"),
|
|
133
|
+
]);
|
|
134
|
+
await (0, promises_1.writeFile)(TOKEN_FILE, encryptedData, { mode: 0o600 });
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
// File doesn't exist, ignore
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function isAuthenticated() {
|
|
141
|
+
const tokens = await getStoredTokens();
|
|
142
|
+
if (!tokens)
|
|
143
|
+
return false;
|
|
144
|
+
// Check if token is not expired (with 5 minute buffer)
|
|
145
|
+
const expirationBuffer = 5 * 60 * 1000; // 5 minutes
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
const tokenExpiry = tokens.expires_at * 1000;
|
|
148
|
+
return tokenExpiry > now + expirationBuffer;
|
|
149
|
+
}
|
package/dist/bin/index.js
CHANGED
|
@@ -16,6 +16,8 @@ const diagnosis_agent_1 = require("../agent/diagnosis-agent");
|
|
|
16
16
|
const enrich_prompt_1 = require("../agent/enrich-prompt");
|
|
17
17
|
const infer_agent_1 = require("../agent/infer-agent");
|
|
18
18
|
const run_3 = require("../agent/planner/run");
|
|
19
|
+
const auth_1 = require("../auth");
|
|
20
|
+
const recorder_1 = require("../recorder");
|
|
19
21
|
const reporter_1 = require("../reporter");
|
|
20
22
|
const session_1 = require("../session");
|
|
21
23
|
const test_build_1 = require("../test-build");
|
|
@@ -200,8 +202,96 @@ async function runAgentsWorkflow(testGenConfig, testGenToken) {
|
|
|
200
202
|
}
|
|
201
203
|
async function main() {
|
|
202
204
|
const removeListeners = setupProcessListeners(flushEvents);
|
|
203
|
-
(0, utils_2.printBanner)();
|
|
205
|
+
await (0, utils_2.printBanner)();
|
|
204
206
|
const program = new commander_1.Command();
|
|
207
|
+
// Add authentication commands
|
|
208
|
+
program
|
|
209
|
+
.command("login")
|
|
210
|
+
.description("Authenticate with your Empirical Run account")
|
|
211
|
+
.action(async () => {
|
|
212
|
+
console.log("🔐 Starting authentication...\n");
|
|
213
|
+
try {
|
|
214
|
+
const result = await (0, auth_1.authenticate)();
|
|
215
|
+
if (result.success) {
|
|
216
|
+
console.log("✅ Authentication successful!");
|
|
217
|
+
if (result.user) {
|
|
218
|
+
console.log(`👤 Logged in as: ${result.user.email}`);
|
|
219
|
+
}
|
|
220
|
+
console.log("\nYou can now use authenticated CLI commands.\n");
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
console.error("❌ Authentication failed:", result.error);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
console.error("❌ Authentication error:", error.message);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
process.exit(0);
|
|
232
|
+
});
|
|
233
|
+
program
|
|
234
|
+
.command("logout")
|
|
235
|
+
.description("Sign out of your Empirical Run account")
|
|
236
|
+
.action(async () => {
|
|
237
|
+
try {
|
|
238
|
+
await (0, auth_1.logout)();
|
|
239
|
+
console.log("✅ Successfully signed out.");
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
console.error("❌ Logout error:", error.message);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
process.exit(0);
|
|
246
|
+
});
|
|
247
|
+
program
|
|
248
|
+
.command("whoami")
|
|
249
|
+
.description("Check your authentication status")
|
|
250
|
+
.action(async () => {
|
|
251
|
+
try {
|
|
252
|
+
const status = await (0, auth_1.getAuthStatus)();
|
|
253
|
+
if (status.authenticated) {
|
|
254
|
+
console.log("✅ You are authenticated");
|
|
255
|
+
if (status.user) {
|
|
256
|
+
console.log(`👤 Logged in as: ${status.user.email}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
console.log("❌ You are not authenticated");
|
|
261
|
+
console.log('Run "npx @empiricalrun/test-gen login" to authenticate');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
console.error("❌ Error checking auth status:", error.message);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
removeListeners();
|
|
269
|
+
await (0, llm_1.flushAllTraces)();
|
|
270
|
+
await (0, logger_1.waitForLogsToFlush)();
|
|
271
|
+
process.exit(0);
|
|
272
|
+
});
|
|
273
|
+
program
|
|
274
|
+
.command("auth-test")
|
|
275
|
+
.description("Test authentication by making an API call")
|
|
276
|
+
.action(async () => {
|
|
277
|
+
try {
|
|
278
|
+
console.log("🔍 Testing authentication with API call...\n");
|
|
279
|
+
const data = await (0, auth_1.authenticatedFetch)("/requests");
|
|
280
|
+
console.log("✅ API call successful!");
|
|
281
|
+
console.log("📄 Response:", JSON.stringify(data, null, 2));
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
console.error("❌ API call failed:", error.message);
|
|
285
|
+
if (error.message.includes("Authentication required")) {
|
|
286
|
+
console.log('\n💡 Tip: Run "npx @empiricalrun/test-gen login" to authenticate');
|
|
287
|
+
}
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
removeListeners();
|
|
291
|
+
await (0, llm_1.flushAllTraces)();
|
|
292
|
+
await (0, logger_1.waitForLogsToFlush)();
|
|
293
|
+
process.exit(0);
|
|
294
|
+
});
|
|
205
295
|
program
|
|
206
296
|
.option("--token <token>", "Test generation token")
|
|
207
297
|
.option("--prompt <prompt>", "Prompt for the chat agent")
|
|
@@ -209,13 +299,16 @@ async function main() {
|
|
|
209
299
|
.option("--file <test-file>", "File path of the test case (inside tests dir)")
|
|
210
300
|
.option("--suites <suites>", "Comma separated list of describe blocks")
|
|
211
301
|
.option("--use-chat", "Use chat agent (and not the workflow)")
|
|
302
|
+
.option("--use-recorder", "Run the recorder flow to create a request")
|
|
212
303
|
.option("--chat-session-id <chat-session-id>", "Identifier for chat session (fetched from dash.empirical.run)")
|
|
213
304
|
.option("--use-disk-for-chat-state", "Save and load chat state from disk")
|
|
214
305
|
.option("--chat-model <model>", "Chat model to use (claude-3-7-sonnet-20250219 or claude-3-5-sonnet-20241022 or gemini-2.5-pro-preview-06-05)")
|
|
215
306
|
.option("--initial-prompt <path>", "Path to an initial prompt file (e.g. prompt.md)")
|
|
216
307
|
.option("--with-retry", "Use the retry strategy")
|
|
217
308
|
.parse(process.argv);
|
|
309
|
+
// Check if a command was executed (auth commands exit early)
|
|
218
310
|
const options = program.opts();
|
|
311
|
+
// Only proceed with main workflow if no auth command was executed
|
|
219
312
|
const completedOptions = await (0, utils_2.validateAndCompleteCliOptions)(options);
|
|
220
313
|
const testGenConfig = completedOptions.token
|
|
221
314
|
? (0, scenarios_1.loadTestConfigs)(completedOptions.token)
|
|
@@ -242,6 +335,10 @@ async function main() {
|
|
|
242
335
|
apiKey: process.env.EMPIRICALRUN_API_KEY,
|
|
243
336
|
});
|
|
244
337
|
}
|
|
338
|
+
if (completedOptions.useRecorder) {
|
|
339
|
+
await (0, recorder_1.runRecorder)({ name: completedOptions.name });
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
245
342
|
if (completedOptions.useChat) {
|
|
246
343
|
await runChatAgent({
|
|
247
344
|
chatSessionId: completedOptions.chatSessionId,
|
|
@@ -11,7 +11,8 @@ export interface CLIOptions {
|
|
|
11
11
|
initialPrompt?: string;
|
|
12
12
|
chatSessionId?: string;
|
|
13
13
|
chatModel?: (typeof ARGS_TO_MODEL_MAP)[keyof typeof ARGS_TO_MODEL_MAP];
|
|
14
|
+
useRecorder?: boolean;
|
|
14
15
|
}
|
|
15
16
|
export declare function validateAndCompleteCliOptions(options: CLIOptions): Promise<CLIOptions>;
|
|
16
|
-
export declare function printBanner(): void
|
|
17
|
+
export declare function printBanner(): Promise<void>;
|
|
17
18
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAKjE,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAgBjE,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;IAGvE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAQD,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,UAAU,CAAC,CA8DrB;AAeD,wBAAsB,WAAW,kBAgDhC"}
|
package/dist/bin/utils/index.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.ARGS_TO_MODEL_MAP = void 0;
|
|
|
7
7
|
exports.validateAndCompleteCliOptions = validateAndCompleteCliOptions;
|
|
8
8
|
exports.printBanner = printBanner;
|
|
9
9
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const PACKAGE_NAME = "@empiricalrun/test-gen";
|
|
10
11
|
exports.ARGS_TO_MODEL_MAP = {
|
|
11
12
|
"claude-3-5": "claude-3-5-sonnet-20241022",
|
|
12
13
|
"claude-3-7": "claude-3-7-sonnet-20250219",
|
|
@@ -32,6 +33,9 @@ async function validateAndCompleteCliOptions(options) {
|
|
|
32
33
|
// Chat agent can prompt the user directly, nothing is required in CLI args
|
|
33
34
|
requiredFields = [];
|
|
34
35
|
}
|
|
36
|
+
if (options.useRecorder) {
|
|
37
|
+
requiredFields = ["name"];
|
|
38
|
+
}
|
|
35
39
|
const questions = [];
|
|
36
40
|
if (!options.name && requiredFields.includes("name")) {
|
|
37
41
|
questions.push({
|
|
@@ -76,7 +80,19 @@ async function validateAndCompleteCliOptions(options) {
|
|
|
76
80
|
}
|
|
77
81
|
return options;
|
|
78
82
|
}
|
|
79
|
-
function
|
|
83
|
+
async function getLatestVersion(packageName) {
|
|
84
|
+
try {
|
|
85
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
|
86
|
+
if (!response.ok)
|
|
87
|
+
return null;
|
|
88
|
+
const data = await response.json();
|
|
89
|
+
return data.version;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function printBanner() {
|
|
80
96
|
const gray = "\x1b[90m";
|
|
81
97
|
const reset = "\x1b[0m";
|
|
82
98
|
const asciiArtRaw = `
|
|
@@ -85,7 +101,17 @@ function printBanner() {
|
|
|
85
101
|
/)__)
|
|
86
102
|
--"-"-`;
|
|
87
103
|
const version = require("../../../package.json").version;
|
|
88
|
-
const
|
|
104
|
+
const latestVersion = await getLatestVersion(PACKAGE_NAME);
|
|
105
|
+
let versionSuffix = "";
|
|
106
|
+
if (latestVersion) {
|
|
107
|
+
if (version === latestVersion) {
|
|
108
|
+
versionSuffix = ` (latest)`;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
versionSuffix = ` ${reset}(latest is ${latestVersion})`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const logLine1 = `Running ${PACKAGE_NAME} v${version}${versionSuffix}`;
|
|
89
115
|
const logLine2 = `from ${__dirname.split("/bin/utils")[0]}`;
|
|
90
116
|
// Process ASCII art
|
|
91
117
|
const asciiLines = asciiArtRaw
|
|
@@ -94,20 +120,23 @@ function printBanner() {
|
|
|
94
120
|
const maxWidth = Math.max(...asciiLines.map((line) => line.length));
|
|
95
121
|
const middleIndex = Math.floor(asciiLines.length / 2);
|
|
96
122
|
const spacing = " "; // 4 spaces for padding
|
|
123
|
+
const leftPadding = " "; // Left padding
|
|
124
|
+
console.log(""); // Top padding
|
|
97
125
|
// Print formatted output (art default, logs gray)
|
|
98
126
|
asciiLines.forEach((line, index) => {
|
|
99
127
|
const paddedLine = line.padEnd(maxWidth, " ");
|
|
100
128
|
if (index === middleIndex) {
|
|
101
129
|
// Print art line (default color) + first log line (gray)
|
|
102
|
-
console.log(`${paddedLine}${spacing}${gray}${logLine1}${reset}`);
|
|
130
|
+
console.log(`${leftPadding}${paddedLine}${spacing}${gray}${logLine1}${reset}`);
|
|
103
131
|
}
|
|
104
132
|
else if (index === middleIndex + 1) {
|
|
105
133
|
// Print corresponding art line (default color) + second log line (gray)
|
|
106
|
-
console.log(`${paddedLine}${spacing}${gray}${logLine2}${reset}`);
|
|
134
|
+
console.log(`${leftPadding}${paddedLine}${spacing}${gray}${logLine2}${reset}`);
|
|
107
135
|
}
|
|
108
136
|
else {
|
|
109
137
|
// Print other art lines (default color)
|
|
110
|
-
console.log(paddedLine);
|
|
138
|
+
console.log(`${leftPadding}${paddedLine}`);
|
|
111
139
|
}
|
|
112
140
|
});
|
|
141
|
+
console.log(""); // Bottom padding
|
|
113
142
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable autofix/no-unused-vars */
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Annotates all clickable elements on the page with unique hint markers.
|
|
5
3
|
* Returns an object containing annotations and methods to enable/disable them.
|
|
@@ -10,6 +8,7 @@
|
|
|
10
8
|
* @param {string} options.markerClass - CSS class to apply to hint markers.
|
|
11
9
|
* @returns {Object} An object containing annotations map and enable/disable methods.
|
|
12
10
|
*/
|
|
11
|
+
// eslint-disable-next-line no-unused-vars, autofix/no-unused-vars
|
|
13
12
|
function annotateElementsWithPreference({
|
|
14
13
|
options = {},
|
|
15
14
|
preference = {},
|
|
@@ -32,8 +31,6 @@ function annotateElementsWithPreference({
|
|
|
32
31
|
// Check if the element is not blocked and visible for clicking
|
|
33
32
|
function isElementClickNotBlocked(element, windowToAnnotate) {
|
|
34
33
|
const rect = element.getBoundingClientRect();
|
|
35
|
-
const originalScrollX = windowToAnnotate.scrollX;
|
|
36
|
-
const originalScrollY = windowToAnnotate.scrollY;
|
|
37
34
|
|
|
38
35
|
// Calculate the center point of the element
|
|
39
36
|
const centerX = rect.left + rect.width / 2;
|
package/dist/file/client.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ declare class FileServiceClient {
|
|
|
5
5
|
constructor();
|
|
6
6
|
static isAvailable(): boolean;
|
|
7
7
|
sendAgentResult(payload: BrowserAgentIPCPayload): Promise<any>;
|
|
8
|
+
sendCodegenSources(payload: any): Promise<any>;
|
|
8
9
|
post(path: string, body: any): Promise<any>;
|
|
9
10
|
}
|
|
10
11
|
export default FileServiceClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/file/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAElD,cAAM,iBAAiB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;;IAUzB,MAAM,CAAC,WAAW;IAIZ,eAAe,CAAC,OAAO,EAAE,sBAAsB;IAI/C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;CAgBnC;AAED,eAAe,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/file/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAElD,cAAM,iBAAiB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;;IAUzB,MAAM,CAAC,WAAW;IAIZ,eAAe,CAAC,OAAO,EAAE,sBAAsB;IAI/C,kBAAkB,CAAC,OAAO,EAAE,GAAG;IAI/B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;CAgBnC;AAED,eAAe,iBAAiB,CAAC"}
|
package/dist/file/client.js
CHANGED
|
@@ -16,6 +16,9 @@ class FileServiceClient {
|
|
|
16
16
|
async sendAgentResult(payload) {
|
|
17
17
|
return this.post("/agent-results", payload);
|
|
18
18
|
}
|
|
19
|
+
async sendCodegenSources(payload) {
|
|
20
|
+
return this.post("/codegen-sources", payload);
|
|
21
|
+
}
|
|
19
22
|
async post(path, body) {
|
|
20
23
|
const resp = await fetch(`${this.baseUrl}${path}`, {
|
|
21
24
|
method: "POST",
|