@nudge-ai/cli 0.0.1-beta.4 → 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/dist/bin.mjs +893 -17
- package/dist/bin.mjs.map +1 -1
- package/dist/index.d.mts +71 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{src-DG37IBZ6.mjs → src-B7X5IQ4U.mjs} +1074 -74
- package/dist/src-B7X5IQ4U.mjs.map +1 -0
- package/package.json +13 -14
- package/dist/bin.cjs +0 -29
- package/dist/bin.d.cts +0 -1
- package/dist/index.cjs +0 -4
- package/dist/index.d.cts +0 -27
- package/dist/index.d.cts.map +0 -1
- package/dist/src-6tjbSqai.cjs +0 -5837
- package/dist/src-DG37IBZ6.mjs.map +0 -1
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import "dotenv/config";
|
|
3
2
|
import * as fs$3 from "fs";
|
|
4
|
-
import { formatStepForAI } from "@nudge-ai/core";
|
|
5
|
-
import * as z from "zod/mini";
|
|
6
3
|
import crypto from "crypto";
|
|
4
|
+
import { formatStepForAI } from "@nudge-ai/core/internal";
|
|
5
|
+
import * as z from "zod/mini";
|
|
7
6
|
import { pathToFileURL } from "url";
|
|
8
7
|
|
|
9
8
|
//#region rolldown:runtime
|
|
@@ -34,13 +33,488 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
34
33
|
}) : target, mod));
|
|
35
34
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
36
35
|
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region ../../node_modules/dotenv/package.json
|
|
38
|
+
var require_package = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
39
|
+
module.exports = {
|
|
40
|
+
"name": "dotenv",
|
|
41
|
+
"version": "17.2.3",
|
|
42
|
+
"description": "Loads environment variables from .env file",
|
|
43
|
+
"main": "lib/main.js",
|
|
44
|
+
"types": "lib/main.d.ts",
|
|
45
|
+
"exports": {
|
|
46
|
+
".": {
|
|
47
|
+
"types": "./lib/main.d.ts",
|
|
48
|
+
"require": "./lib/main.js",
|
|
49
|
+
"default": "./lib/main.js"
|
|
50
|
+
},
|
|
51
|
+
"./config": "./config.js",
|
|
52
|
+
"./config.js": "./config.js",
|
|
53
|
+
"./lib/env-options": "./lib/env-options.js",
|
|
54
|
+
"./lib/env-options.js": "./lib/env-options.js",
|
|
55
|
+
"./lib/cli-options": "./lib/cli-options.js",
|
|
56
|
+
"./lib/cli-options.js": "./lib/cli-options.js",
|
|
57
|
+
"./package.json": "./package.json"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"dts-check": "tsc --project tests/types/tsconfig.json",
|
|
61
|
+
"lint": "standard",
|
|
62
|
+
"pretest": "npm run lint && npm run dts-check",
|
|
63
|
+
"test": "tap run tests/**/*.js --allow-empty-coverage --disable-coverage --timeout=60000",
|
|
64
|
+
"test:coverage": "tap run tests/**/*.js --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov",
|
|
65
|
+
"prerelease": "npm test",
|
|
66
|
+
"release": "standard-version"
|
|
67
|
+
},
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "git://github.com/motdotla/dotenv.git"
|
|
71
|
+
},
|
|
72
|
+
"homepage": "https://github.com/motdotla/dotenv#readme",
|
|
73
|
+
"funding": "https://dotenvx.com",
|
|
74
|
+
"keywords": [
|
|
75
|
+
"dotenv",
|
|
76
|
+
"env",
|
|
77
|
+
".env",
|
|
78
|
+
"environment",
|
|
79
|
+
"variables",
|
|
80
|
+
"config",
|
|
81
|
+
"settings"
|
|
82
|
+
],
|
|
83
|
+
"readmeFilename": "README.md",
|
|
84
|
+
"license": "BSD-2-Clause",
|
|
85
|
+
"devDependencies": {
|
|
86
|
+
"@types/node": "^18.11.3",
|
|
87
|
+
"decache": "^4.6.2",
|
|
88
|
+
"sinon": "^14.0.1",
|
|
89
|
+
"standard": "^17.0.0",
|
|
90
|
+
"standard-version": "^9.5.0",
|
|
91
|
+
"tap": "^19.2.0",
|
|
92
|
+
"typescript": "^4.8.4"
|
|
93
|
+
},
|
|
94
|
+
"engines": { "node": ">=12" },
|
|
95
|
+
"browser": { "fs": false }
|
|
96
|
+
};
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region ../../node_modules/dotenv/lib/main.js
|
|
101
|
+
var require_main = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
102
|
+
const fs$4 = __require("fs");
|
|
103
|
+
const path$9 = __require("path");
|
|
104
|
+
const os$2 = __require("os");
|
|
105
|
+
const crypto$1 = __require("crypto");
|
|
106
|
+
const version = require_package().version;
|
|
107
|
+
const TIPS = [
|
|
108
|
+
"🔐 encrypt with Dotenvx: https://dotenvx.com",
|
|
109
|
+
"🔐 prevent committing .env to code: https://dotenvx.com/precommit",
|
|
110
|
+
"🔐 prevent building .env in docker: https://dotenvx.com/prebuild",
|
|
111
|
+
"📡 add observability to secrets: https://dotenvx.com/ops",
|
|
112
|
+
"👥 sync secrets across teammates & machines: https://dotenvx.com/ops",
|
|
113
|
+
"🗂️ backup and recover secrets: https://dotenvx.com/ops",
|
|
114
|
+
"✅ audit secrets and track compliance: https://dotenvx.com/ops",
|
|
115
|
+
"🔄 add secrets lifecycle management: https://dotenvx.com/ops",
|
|
116
|
+
"🔑 add access controls to secrets: https://dotenvx.com/ops",
|
|
117
|
+
"🛠️ run anywhere with `dotenvx run -- yourcommand`",
|
|
118
|
+
"⚙️ specify custom .env file path with { path: '/custom/path/.env' }",
|
|
119
|
+
"⚙️ enable debug logging with { debug: true }",
|
|
120
|
+
"⚙️ override existing env vars with { override: true }",
|
|
121
|
+
"⚙️ suppress all logs with { quiet: true }",
|
|
122
|
+
"⚙️ write to custom object with { processEnv: myObject }",
|
|
123
|
+
"⚙️ load multiple .env files with { path: ['.env.local', '.env'] }"
|
|
124
|
+
];
|
|
125
|
+
function _getRandomTip() {
|
|
126
|
+
return TIPS[Math.floor(Math.random() * TIPS.length)];
|
|
127
|
+
}
|
|
128
|
+
function parseBoolean(value) {
|
|
129
|
+
if (typeof value === "string") return ![
|
|
130
|
+
"false",
|
|
131
|
+
"0",
|
|
132
|
+
"no",
|
|
133
|
+
"off",
|
|
134
|
+
""
|
|
135
|
+
].includes(value.toLowerCase());
|
|
136
|
+
return Boolean(value);
|
|
137
|
+
}
|
|
138
|
+
function supportsAnsi() {
|
|
139
|
+
return process.stdout.isTTY;
|
|
140
|
+
}
|
|
141
|
+
function dim(text) {
|
|
142
|
+
return supportsAnsi() ? `\x1b[2m${text}\x1b[0m` : text;
|
|
143
|
+
}
|
|
144
|
+
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
|
145
|
+
function parse(src) {
|
|
146
|
+
const obj = {};
|
|
147
|
+
let lines = src.toString();
|
|
148
|
+
lines = lines.replace(/\r\n?/gm, "\n");
|
|
149
|
+
let match;
|
|
150
|
+
while ((match = LINE.exec(lines)) != null) {
|
|
151
|
+
const key = match[1];
|
|
152
|
+
let value = match[2] || "";
|
|
153
|
+
value = value.trim();
|
|
154
|
+
const maybeQuote = value[0];
|
|
155
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
|
|
156
|
+
if (maybeQuote === "\"") {
|
|
157
|
+
value = value.replace(/\\n/g, "\n");
|
|
158
|
+
value = value.replace(/\\r/g, "\r");
|
|
159
|
+
}
|
|
160
|
+
obj[key] = value;
|
|
161
|
+
}
|
|
162
|
+
return obj;
|
|
163
|
+
}
|
|
164
|
+
function _parseVault(options) {
|
|
165
|
+
options = options || {};
|
|
166
|
+
const vaultPath = _vaultPath(options);
|
|
167
|
+
options.path = vaultPath;
|
|
168
|
+
const result = DotenvModule.configDotenv(options);
|
|
169
|
+
if (!result.parsed) {
|
|
170
|
+
const err = /* @__PURE__ */ new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
|
|
171
|
+
err.code = "MISSING_DATA";
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
const keys = _dotenvKey(options).split(",");
|
|
175
|
+
const length = keys.length;
|
|
176
|
+
let decrypted;
|
|
177
|
+
for (let i = 0; i < length; i++) try {
|
|
178
|
+
const attrs = _instructions(result, keys[i].trim());
|
|
179
|
+
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
180
|
+
break;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (i + 1 >= length) throw error;
|
|
183
|
+
}
|
|
184
|
+
return DotenvModule.parse(decrypted);
|
|
185
|
+
}
|
|
186
|
+
function _warn(message) {
|
|
187
|
+
console.error(`[dotenv@${version}][WARN] ${message}`);
|
|
188
|
+
}
|
|
189
|
+
function _debug(message) {
|
|
190
|
+
console.log(`[dotenv@${version}][DEBUG] ${message}`);
|
|
191
|
+
}
|
|
192
|
+
function _log(message) {
|
|
193
|
+
console.log(`[dotenv@${version}] ${message}`);
|
|
194
|
+
}
|
|
195
|
+
function _dotenvKey(options) {
|
|
196
|
+
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) return options.DOTENV_KEY;
|
|
197
|
+
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) return process.env.DOTENV_KEY;
|
|
198
|
+
return "";
|
|
199
|
+
}
|
|
200
|
+
function _instructions(result, dotenvKey) {
|
|
201
|
+
let uri;
|
|
202
|
+
try {
|
|
203
|
+
uri = new URL(dotenvKey);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (error.code === "ERR_INVALID_URL") {
|
|
206
|
+
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
|
|
207
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
const key = uri.password;
|
|
213
|
+
if (!key) {
|
|
214
|
+
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Missing key part");
|
|
215
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
216
|
+
throw err;
|
|
217
|
+
}
|
|
218
|
+
const environment = uri.searchParams.get("environment");
|
|
219
|
+
if (!environment) {
|
|
220
|
+
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Missing environment part");
|
|
221
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
222
|
+
throw err;
|
|
223
|
+
}
|
|
224
|
+
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
|
|
225
|
+
const ciphertext = result.parsed[environmentKey];
|
|
226
|
+
if (!ciphertext) {
|
|
227
|
+
const err = /* @__PURE__ */ new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
228
|
+
err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
ciphertext,
|
|
233
|
+
key
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function _vaultPath(options) {
|
|
237
|
+
let possibleVaultPath = null;
|
|
238
|
+
if (options && options.path && options.path.length > 0) if (Array.isArray(options.path)) {
|
|
239
|
+
for (const filepath of options.path) if (fs$4.existsSync(filepath)) possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
240
|
+
} else possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
|
|
241
|
+
else possibleVaultPath = path$9.resolve(process.cwd(), ".env.vault");
|
|
242
|
+
if (fs$4.existsSync(possibleVaultPath)) return possibleVaultPath;
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
function _resolveHome(envPath) {
|
|
246
|
+
return envPath[0] === "~" ? path$9.join(os$2.homedir(), envPath.slice(1)) : envPath;
|
|
247
|
+
}
|
|
248
|
+
function _configVault(options) {
|
|
249
|
+
const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
250
|
+
const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
251
|
+
if (debug || !quiet) _log("Loading env from encrypted .env.vault");
|
|
252
|
+
const parsed = DotenvModule._parseVault(options);
|
|
253
|
+
let processEnv = process.env;
|
|
254
|
+
if (options && options.processEnv != null) processEnv = options.processEnv;
|
|
255
|
+
DotenvModule.populate(processEnv, parsed, options);
|
|
256
|
+
return { parsed };
|
|
257
|
+
}
|
|
258
|
+
function configDotenv(options) {
|
|
259
|
+
const dotenvPath = path$9.resolve(process.cwd(), ".env");
|
|
260
|
+
let encoding = "utf8";
|
|
261
|
+
let processEnv = process.env;
|
|
262
|
+
if (options && options.processEnv != null) processEnv = options.processEnv;
|
|
263
|
+
let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
264
|
+
let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
265
|
+
if (options && options.encoding) encoding = options.encoding;
|
|
266
|
+
else if (debug) _debug("No encoding is specified. UTF-8 is used by default");
|
|
267
|
+
let optionPaths = [dotenvPath];
|
|
268
|
+
if (options && options.path) if (!Array.isArray(options.path)) optionPaths = [_resolveHome(options.path)];
|
|
269
|
+
else {
|
|
270
|
+
optionPaths = [];
|
|
271
|
+
for (const filepath of options.path) optionPaths.push(_resolveHome(filepath));
|
|
272
|
+
}
|
|
273
|
+
let lastError;
|
|
274
|
+
const parsedAll = {};
|
|
275
|
+
for (const path$10 of optionPaths) try {
|
|
276
|
+
const parsed = DotenvModule.parse(fs$4.readFileSync(path$10, { encoding }));
|
|
277
|
+
DotenvModule.populate(parsedAll, parsed, options);
|
|
278
|
+
} catch (e) {
|
|
279
|
+
if (debug) _debug(`Failed to load ${path$10} ${e.message}`);
|
|
280
|
+
lastError = e;
|
|
281
|
+
}
|
|
282
|
+
const populated = DotenvModule.populate(processEnv, parsedAll, options);
|
|
283
|
+
debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
|
|
284
|
+
quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
|
|
285
|
+
if (debug || !quiet) {
|
|
286
|
+
const keysCount = Object.keys(populated).length;
|
|
287
|
+
const shortPaths = [];
|
|
288
|
+
for (const filePath of optionPaths) try {
|
|
289
|
+
const relative = path$9.relative(process.cwd(), filePath);
|
|
290
|
+
shortPaths.push(relative);
|
|
291
|
+
} catch (e) {
|
|
292
|
+
if (debug) _debug(`Failed to load ${filePath} ${e.message}`);
|
|
293
|
+
lastError = e;
|
|
294
|
+
}
|
|
295
|
+
_log(`injecting env (${keysCount}) from ${shortPaths.join(",")} ${dim(`-- tip: ${_getRandomTip()}`)}`);
|
|
296
|
+
}
|
|
297
|
+
if (lastError) return {
|
|
298
|
+
parsed: parsedAll,
|
|
299
|
+
error: lastError
|
|
300
|
+
};
|
|
301
|
+
else return { parsed: parsedAll };
|
|
302
|
+
}
|
|
303
|
+
function config(options) {
|
|
304
|
+
if (_dotenvKey(options).length === 0) return DotenvModule.configDotenv(options);
|
|
305
|
+
const vaultPath = _vaultPath(options);
|
|
306
|
+
if (!vaultPath) {
|
|
307
|
+
_warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
|
|
308
|
+
return DotenvModule.configDotenv(options);
|
|
309
|
+
}
|
|
310
|
+
return DotenvModule._configVault(options);
|
|
311
|
+
}
|
|
312
|
+
function decrypt(encrypted, keyStr) {
|
|
313
|
+
const key = Buffer.from(keyStr.slice(-64), "hex");
|
|
314
|
+
let ciphertext = Buffer.from(encrypted, "base64");
|
|
315
|
+
const nonce = ciphertext.subarray(0, 12);
|
|
316
|
+
const authTag = ciphertext.subarray(-16);
|
|
317
|
+
ciphertext = ciphertext.subarray(12, -16);
|
|
318
|
+
try {
|
|
319
|
+
const aesgcm = crypto$1.createDecipheriv("aes-256-gcm", key, nonce);
|
|
320
|
+
aesgcm.setAuthTag(authTag);
|
|
321
|
+
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
322
|
+
} catch (error) {
|
|
323
|
+
const isRange = error instanceof RangeError;
|
|
324
|
+
const invalidKeyLength = error.message === "Invalid key length";
|
|
325
|
+
const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
|
|
326
|
+
if (isRange || invalidKeyLength) {
|
|
327
|
+
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
|
|
328
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
329
|
+
throw err;
|
|
330
|
+
} else if (decryptionFailed) {
|
|
331
|
+
const err = /* @__PURE__ */ new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
|
|
332
|
+
err.code = "DECRYPTION_FAILED";
|
|
333
|
+
throw err;
|
|
334
|
+
} else throw error;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function populate(processEnv, parsed, options = {}) {
|
|
338
|
+
const debug = Boolean(options && options.debug);
|
|
339
|
+
const override = Boolean(options && options.override);
|
|
340
|
+
const populated = {};
|
|
341
|
+
if (typeof parsed !== "object") {
|
|
342
|
+
const err = /* @__PURE__ */ new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
|
|
343
|
+
err.code = "OBJECT_REQUIRED";
|
|
344
|
+
throw err;
|
|
345
|
+
}
|
|
346
|
+
for (const key of Object.keys(parsed)) if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
347
|
+
if (override === true) {
|
|
348
|
+
processEnv[key] = parsed[key];
|
|
349
|
+
populated[key] = parsed[key];
|
|
350
|
+
}
|
|
351
|
+
if (debug) if (override === true) _debug(`"${key}" is already defined and WAS overwritten`);
|
|
352
|
+
else _debug(`"${key}" is already defined and was NOT overwritten`);
|
|
353
|
+
} else {
|
|
354
|
+
processEnv[key] = parsed[key];
|
|
355
|
+
populated[key] = parsed[key];
|
|
356
|
+
}
|
|
357
|
+
return populated;
|
|
358
|
+
}
|
|
359
|
+
const DotenvModule = {
|
|
360
|
+
configDotenv,
|
|
361
|
+
_configVault,
|
|
362
|
+
_parseVault,
|
|
363
|
+
config,
|
|
364
|
+
decrypt,
|
|
365
|
+
parse,
|
|
366
|
+
populate
|
|
367
|
+
};
|
|
368
|
+
module.exports.configDotenv = DotenvModule.configDotenv;
|
|
369
|
+
module.exports._configVault = DotenvModule._configVault;
|
|
370
|
+
module.exports._parseVault = DotenvModule._parseVault;
|
|
371
|
+
module.exports.config = DotenvModule.config;
|
|
372
|
+
module.exports.decrypt = DotenvModule.decrypt;
|
|
373
|
+
module.exports.parse = DotenvModule.parse;
|
|
374
|
+
module.exports.populate = DotenvModule.populate;
|
|
375
|
+
module.exports = DotenvModule;
|
|
376
|
+
}));
|
|
377
|
+
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region ../../node_modules/dotenv/lib/env-options.js
|
|
380
|
+
var require_env_options = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
381
|
+
const options = {};
|
|
382
|
+
if (process.env.DOTENV_CONFIG_ENCODING != null) options.encoding = process.env.DOTENV_CONFIG_ENCODING;
|
|
383
|
+
if (process.env.DOTENV_CONFIG_PATH != null) options.path = process.env.DOTENV_CONFIG_PATH;
|
|
384
|
+
if (process.env.DOTENV_CONFIG_QUIET != null) options.quiet = process.env.DOTENV_CONFIG_QUIET;
|
|
385
|
+
if (process.env.DOTENV_CONFIG_DEBUG != null) options.debug = process.env.DOTENV_CONFIG_DEBUG;
|
|
386
|
+
if (process.env.DOTENV_CONFIG_OVERRIDE != null) options.override = process.env.DOTENV_CONFIG_OVERRIDE;
|
|
387
|
+
if (process.env.DOTENV_CONFIG_DOTENV_KEY != null) options.DOTENV_KEY = process.env.DOTENV_CONFIG_DOTENV_KEY;
|
|
388
|
+
module.exports = options;
|
|
389
|
+
}));
|
|
390
|
+
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region ../../node_modules/dotenv/lib/cli-options.js
|
|
393
|
+
var require_cli_options = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
394
|
+
const re = /^dotenv_config_(encoding|path|quiet|debug|override|DOTENV_KEY)=(.+)$/;
|
|
395
|
+
module.exports = function optionMatcher(args) {
|
|
396
|
+
const options = args.reduce(function(acc, cur) {
|
|
397
|
+
const matches = cur.match(re);
|
|
398
|
+
if (matches) acc[matches[1]] = matches[2];
|
|
399
|
+
return acc;
|
|
400
|
+
}, {});
|
|
401
|
+
if (!("quiet" in options)) options.quiet = "true";
|
|
402
|
+
return options;
|
|
403
|
+
};
|
|
404
|
+
}));
|
|
405
|
+
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region ../../node_modules/dotenv/config.js
|
|
408
|
+
(function() {
|
|
409
|
+
require_main().config(Object.assign({}, require_env_options(), require_cli_options()(process.argv)));
|
|
410
|
+
})();
|
|
411
|
+
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region src/errors.ts
|
|
414
|
+
/**
|
|
415
|
+
* Format API errors with helpful context for users
|
|
416
|
+
*/
|
|
417
|
+
function formatAPIError(error, context) {
|
|
418
|
+
const { model, operation } = context;
|
|
419
|
+
if (error instanceof TypeError && error.message.includes("fetch")) return `
|
|
420
|
+
Network error while ${operation}.
|
|
421
|
+
|
|
422
|
+
Could not connect to the AI provider. Please check:
|
|
423
|
+
• Your internet connection
|
|
424
|
+
• The API base URL in nudge.config.json
|
|
425
|
+
• If using a local model, ensure it's running
|
|
426
|
+
`;
|
|
427
|
+
if (error instanceof Error) {
|
|
428
|
+
const msg = error.message;
|
|
429
|
+
if (msg.includes("401")) return `
|
|
430
|
+
Authentication failed (401) while ${operation}.
|
|
431
|
+
|
|
432
|
+
Please check:
|
|
433
|
+
• Your API key environment variable is set correctly
|
|
434
|
+
• The API key is valid and not expired
|
|
435
|
+
• The API key has the required permissions
|
|
436
|
+
`;
|
|
437
|
+
if (msg.includes("403")) return `
|
|
438
|
+
Access forbidden (403) while ${operation}.
|
|
439
|
+
|
|
440
|
+
Please check:
|
|
441
|
+
• Your API key has access to the model "${model}"
|
|
442
|
+
• You have sufficient quota/credits
|
|
443
|
+
`;
|
|
444
|
+
if (msg.includes("404")) return `
|
|
445
|
+
Model not found (404) while ${operation}.
|
|
446
|
+
|
|
447
|
+
The model "${model}" was not found. Please check:
|
|
448
|
+
• The model name is spelled correctly in nudge.config.json
|
|
449
|
+
• The model is available with your provider
|
|
450
|
+
• For OpenRouter: use format "provider/model-name"
|
|
451
|
+
`;
|
|
452
|
+
if (msg.includes("429")) return `
|
|
453
|
+
Rate limit exceeded (429) while ${operation}.
|
|
454
|
+
|
|
455
|
+
You've hit the API rate limit. Please:
|
|
456
|
+
• Wait a moment and try again
|
|
457
|
+
• Consider using a different model
|
|
458
|
+
• Check your API plan limits
|
|
459
|
+
`;
|
|
460
|
+
if (msg.includes("500") || msg.includes("502") || msg.includes("503")) return `
|
|
461
|
+
Server error while ${operation}.
|
|
462
|
+
|
|
463
|
+
The AI provider is experiencing issues. Please:
|
|
464
|
+
• Wait a moment and try again
|
|
465
|
+
• Check the provider's status page
|
|
466
|
+
`;
|
|
467
|
+
if (msg.includes("empty") || msg.includes("no content")) return `
|
|
468
|
+
Empty response while ${operation}.
|
|
469
|
+
|
|
470
|
+
The model "${model}" returned an empty response. This can happen with:
|
|
471
|
+
• Smaller/local models that don't handle the task well
|
|
472
|
+
• Models with very short context windows
|
|
473
|
+
|
|
474
|
+
Try using a more capable model (e.g., gpt-4o, claude-3.5-sonnet).
|
|
475
|
+
`;
|
|
476
|
+
if (msg.includes("JSON") || msg.includes("parse") || msg.includes("Unexpected token")) return `
|
|
477
|
+
Invalid response format while ${operation}.
|
|
478
|
+
|
|
479
|
+
The model "${model}" didn't return valid JSON. This often happens with:
|
|
480
|
+
• Smaller models that don't follow instructions well
|
|
481
|
+
• Local models without proper instruction tuning
|
|
482
|
+
|
|
483
|
+
Try using a more capable model that follows structured output formats.
|
|
484
|
+
`;
|
|
485
|
+
if (msg.includes("validation") || msg.includes("Expected")) return `
|
|
486
|
+
Unexpected response structure while ${operation}.
|
|
487
|
+
|
|
488
|
+
The model "${model}" returned an unexpected format.
|
|
489
|
+
This can happen with models that don't follow instructions precisely.
|
|
490
|
+
|
|
491
|
+
Try using a more capable model (e.g., gpt-4o, claude-3.5-sonnet).
|
|
492
|
+
`;
|
|
493
|
+
return msg;
|
|
494
|
+
}
|
|
495
|
+
return String(error);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Format a warning message for non-fatal issues
|
|
499
|
+
*/
|
|
500
|
+
function formatWarning(message) {
|
|
501
|
+
return `⚠️ ${message}`;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Check if a model response looks valid (non-empty, reasonable length)
|
|
505
|
+
*/
|
|
506
|
+
function validateModelResponse(response, context) {
|
|
507
|
+
if (!response || response.trim().length === 0) throw new Error(formatAPIError(/* @__PURE__ */ new Error("empty response"), context));
|
|
508
|
+
if (response.trim().length < 10) console.warn(formatWarning(`Model returned a very short response. This may indicate the model "${context.model}" is struggling with the task.`));
|
|
509
|
+
}
|
|
510
|
+
|
|
37
511
|
//#endregion
|
|
38
512
|
//#region src/ai.ts
|
|
39
|
-
const PROVIDER_BASE_URLS = {
|
|
513
|
+
const PROVIDER_BASE_URLS$2 = {
|
|
40
514
|
openai: "https://api.openai.com/v1",
|
|
41
515
|
openrouter: "https://openrouter.ai/api/v1"
|
|
42
516
|
};
|
|
43
|
-
const ChatCompletionResponse = z.object({ choices: z.array(z.object({ message: z.object({ content: z.string() }) })) });
|
|
517
|
+
const ChatCompletionResponse$2 = z.object({ choices: z.array(z.object({ message: z.object({ content: z.string() }) })) });
|
|
44
518
|
const SYSTEM_PROMPT = `You are an expert prompt engineer. Your task is to generate a well-crafted system prompt for an AI assistant.
|
|
45
519
|
|
|
46
520
|
You will receive a series of building blocks that describe what the system prompt should contain. Each block has a type, instructions, and a value. Some blocks may have a "Nudge" level (1-5) indicating how strongly to convey the instruction.
|
|
@@ -75,40 +549,69 @@ Example: "You must keep responses concise. Under no circumstances should you pro
|
|
|
75
549
|
Bad example (don't do this):
|
|
76
550
|
"## Do
|
|
77
551
|
- Keep responses brief
|
|
78
|
-
## Don't
|
|
552
|
+
## Don't
|
|
79
553
|
- Use jargon"
|
|
80
554
|
|
|
81
555
|
Good example (do this instead):
|
|
82
556
|
"Keep your responses brief and accessible. Avoid technical jargon that might confuse users."
|
|
83
557
|
|
|
84
558
|
Output ONLY the final system prompt text. Do not include any explanations, preamble, or meta-commentary.`;
|
|
85
|
-
async function processPrompt(steps, config) {
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
559
|
+
async function processPrompt(steps, config, options) {
|
|
560
|
+
let baseUrl;
|
|
561
|
+
if (config.baseUrl) {
|
|
562
|
+
if (!config.baseUrl.startsWith("http://") && !config.baseUrl.startsWith("https://")) throw new Error(`Invalid baseUrl "${config.baseUrl}": must start with http:// or https://`);
|
|
563
|
+
baseUrl = config.baseUrl;
|
|
564
|
+
} else if (config.provider === "local") throw new Error("Local provider requires \"baseUrl\" in config (e.g., \"http://localhost:8080/v1\")");
|
|
565
|
+
else baseUrl = PROVIDER_BASE_URLS$2[config.provider];
|
|
566
|
+
let apiKey;
|
|
567
|
+
if (config.apiKeyEnvVar) {
|
|
568
|
+
apiKey = process.env[config.apiKeyEnvVar];
|
|
569
|
+
if (!apiKey) throw new Error(`Missing API key: environment variable "${config.apiKeyEnvVar}" is not set`);
|
|
570
|
+
} else if (config.provider !== "local") throw new Error(`Missing "apiKeyEnvVar" in config for provider "${config.provider}"`);
|
|
571
|
+
const headers = { "Content-Type": "application/json" };
|
|
572
|
+
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
89
573
|
const stepsDescription = steps.map(formatStepForAI).join("\n\n");
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
})
|
|
574
|
+
let response;
|
|
575
|
+
try {
|
|
576
|
+
response = await fetch(`${baseUrl}/chat/completions`, {
|
|
577
|
+
method: "POST",
|
|
578
|
+
headers,
|
|
579
|
+
body: JSON.stringify({
|
|
580
|
+
model: config.model,
|
|
581
|
+
messages: [{
|
|
582
|
+
role: "system",
|
|
583
|
+
content: SYSTEM_PROMPT
|
|
584
|
+
}, {
|
|
585
|
+
role: "user",
|
|
586
|
+
content: `Generate a system prompt from these building blocks:\n\n${stepsDescription}`
|
|
587
|
+
}]
|
|
588
|
+
})
|
|
589
|
+
});
|
|
590
|
+
} catch (e) {
|
|
591
|
+
throw e;
|
|
592
|
+
}
|
|
107
593
|
if (!response.ok) {
|
|
108
|
-
const
|
|
109
|
-
throw new Error(
|
|
594
|
+
const errorText = await response.text();
|
|
595
|
+
throw new Error(formatAPIError(/* @__PURE__ */ new Error(`${response.status} - ${errorText}`), {
|
|
596
|
+
model: config.model,
|
|
597
|
+
operation: "generating prompt"
|
|
598
|
+
}));
|
|
599
|
+
}
|
|
600
|
+
let data;
|
|
601
|
+
try {
|
|
602
|
+
data = ChatCompletionResponse$2.parse(await response.json());
|
|
603
|
+
} catch (e) {
|
|
604
|
+
throw new Error(formatAPIError(e, {
|
|
605
|
+
model: config.model,
|
|
606
|
+
operation: "generating prompt"
|
|
607
|
+
}));
|
|
110
608
|
}
|
|
111
|
-
|
|
609
|
+
const content = data.choices[0]?.message.content ?? "";
|
|
610
|
+
validateModelResponse(content, {
|
|
611
|
+
model: config.model,
|
|
612
|
+
operation: "generating prompt"
|
|
613
|
+
});
|
|
614
|
+
return content;
|
|
112
615
|
}
|
|
113
616
|
|
|
114
617
|
//#endregion
|
|
@@ -1481,7 +1984,7 @@ var require_braces = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
1481
1984
|
}));
|
|
1482
1985
|
|
|
1483
1986
|
//#endregion
|
|
1484
|
-
//#region ../../node_modules/
|
|
1987
|
+
//#region ../../node_modules/picomatch/lib/constants.js
|
|
1485
1988
|
var require_constants$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1486
1989
|
const path$7 = __require("path");
|
|
1487
1990
|
const WIN_SLASH = "\\\\/";
|
|
@@ -1645,7 +2148,7 @@ var require_constants$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
1645
2148
|
}));
|
|
1646
2149
|
|
|
1647
2150
|
//#endregion
|
|
1648
|
-
//#region ../../node_modules/
|
|
2151
|
+
//#region ../../node_modules/picomatch/lib/utils.js
|
|
1649
2152
|
var require_utils$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
1650
2153
|
const path$6 = __require("path");
|
|
1651
2154
|
const win32 = process.platform === "win32";
|
|
@@ -1691,7 +2194,7 @@ var require_utils$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
1691
2194
|
}));
|
|
1692
2195
|
|
|
1693
2196
|
//#endregion
|
|
1694
|
-
//#region ../../node_modules/
|
|
2197
|
+
//#region ../../node_modules/picomatch/lib/scan.js
|
|
1695
2198
|
var require_scan = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1696
2199
|
const utils = require_utils$2();
|
|
1697
2200
|
const { CHAR_ASTERISK, CHAR_AT, CHAR_BACKWARD_SLASH, CHAR_COMMA, CHAR_DOT, CHAR_EXCLAMATION_MARK, CHAR_FORWARD_SLASH, CHAR_LEFT_CURLY_BRACE, CHAR_LEFT_PARENTHESES, CHAR_LEFT_SQUARE_BRACKET, CHAR_PLUS, CHAR_QUESTION_MARK, CHAR_RIGHT_CURLY_BRACE, CHAR_RIGHT_PARENTHESES, CHAR_RIGHT_SQUARE_BRACKET } = require_constants$1();
|
|
@@ -1979,7 +2482,7 @@ var require_scan = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
1979
2482
|
}));
|
|
1980
2483
|
|
|
1981
2484
|
//#endregion
|
|
1982
|
-
//#region ../../node_modules/
|
|
2485
|
+
//#region ../../node_modules/picomatch/lib/parse.js
|
|
1983
2486
|
var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1984
2487
|
const constants = require_constants$1();
|
|
1985
2488
|
const utils = require_utils$2();
|
|
@@ -2841,7 +3344,7 @@ var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
2841
3344
|
}));
|
|
2842
3345
|
|
|
2843
3346
|
//#endregion
|
|
2844
|
-
//#region ../../node_modules/
|
|
3347
|
+
//#region ../../node_modules/picomatch/lib/picomatch.js
|
|
2845
3348
|
var require_picomatch$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
2846
3349
|
const path$5 = __require("path");
|
|
2847
3350
|
const scan = require_scan();
|
|
@@ -3135,7 +3638,7 @@ var require_picomatch$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
3135
3638
|
}));
|
|
3136
3639
|
|
|
3137
3640
|
//#endregion
|
|
3138
|
-
//#region ../../node_modules/
|
|
3641
|
+
//#region ../../node_modules/picomatch/index.js
|
|
3139
3642
|
var require_picomatch = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
3140
3643
|
module.exports = require_picomatch$1();
|
|
3141
3644
|
}));
|
|
@@ -3965,8 +4468,8 @@ var require_tasks = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
3965
4468
|
var require_async$5 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
3966
4469
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3967
4470
|
exports.read = void 0;
|
|
3968
|
-
function read(path$
|
|
3969
|
-
settings.fs.lstat(path$
|
|
4471
|
+
function read(path$10, settings, callback) {
|
|
4472
|
+
settings.fs.lstat(path$10, (lstatError, lstat) => {
|
|
3970
4473
|
if (lstatError !== null) {
|
|
3971
4474
|
callFailureCallback(callback, lstatError);
|
|
3972
4475
|
return;
|
|
@@ -3975,7 +4478,7 @@ var require_async$5 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
3975
4478
|
callSuccessCallback(callback, lstat);
|
|
3976
4479
|
return;
|
|
3977
4480
|
}
|
|
3978
|
-
settings.fs.stat(path$
|
|
4481
|
+
settings.fs.stat(path$10, (statError, stat) => {
|
|
3979
4482
|
if (statError !== null) {
|
|
3980
4483
|
if (settings.throwErrorOnBrokenSymbolicLink) {
|
|
3981
4484
|
callFailureCallback(callback, statError);
|
|
@@ -4003,11 +4506,11 @@ var require_async$5 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
4003
4506
|
var require_sync$5 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
4004
4507
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4005
4508
|
exports.read = void 0;
|
|
4006
|
-
function read(path$
|
|
4007
|
-
const lstat = settings.fs.lstatSync(path$
|
|
4509
|
+
function read(path$10, settings) {
|
|
4510
|
+
const lstat = settings.fs.lstatSync(path$10);
|
|
4008
4511
|
if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) return lstat;
|
|
4009
4512
|
try {
|
|
4010
|
-
const stat = settings.fs.statSync(path$
|
|
4513
|
+
const stat = settings.fs.statSync(path$10);
|
|
4011
4514
|
if (settings.markSymbolicLink) stat.isSymbolicLink = () => true;
|
|
4012
4515
|
return stat;
|
|
4013
4516
|
} catch (error) {
|
|
@@ -4066,17 +4569,17 @@ var require_out$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
4066
4569
|
const sync = require_sync$5();
|
|
4067
4570
|
const settings_1 = require_settings$3();
|
|
4068
4571
|
exports.Settings = settings_1.default;
|
|
4069
|
-
function stat(path$
|
|
4572
|
+
function stat(path$10, optionsOrSettingsOrCallback, callback) {
|
|
4070
4573
|
if (typeof optionsOrSettingsOrCallback === "function") {
|
|
4071
|
-
async.read(path$
|
|
4574
|
+
async.read(path$10, getSettings(), optionsOrSettingsOrCallback);
|
|
4072
4575
|
return;
|
|
4073
4576
|
}
|
|
4074
|
-
async.read(path$
|
|
4577
|
+
async.read(path$10, getSettings(optionsOrSettingsOrCallback), callback);
|
|
4075
4578
|
}
|
|
4076
4579
|
exports.stat = stat;
|
|
4077
|
-
function statSync(path$
|
|
4580
|
+
function statSync(path$10, optionsOrSettings) {
|
|
4078
4581
|
const settings = getSettings(optionsOrSettings);
|
|
4079
|
-
return sync.read(path$
|
|
4582
|
+
return sync.read(path$10, settings);
|
|
4080
4583
|
}
|
|
4081
4584
|
exports.statSync = statSync;
|
|
4082
4585
|
function getSettings(settingsOrOptions = {}) {
|
|
@@ -4275,16 +4778,16 @@ var require_async$4 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
4275
4778
|
return;
|
|
4276
4779
|
}
|
|
4277
4780
|
rpl(names.map((name) => {
|
|
4278
|
-
const path$
|
|
4781
|
+
const path$10 = common.joinPathSegments(directory, name, settings.pathSegmentSeparator);
|
|
4279
4782
|
return (done) => {
|
|
4280
|
-
fsStat.stat(path$
|
|
4783
|
+
fsStat.stat(path$10, settings.fsStatSettings, (error, stats) => {
|
|
4281
4784
|
if (error !== null) {
|
|
4282
4785
|
done(error);
|
|
4283
4786
|
return;
|
|
4284
4787
|
}
|
|
4285
4788
|
const entry = {
|
|
4286
4789
|
name,
|
|
4287
|
-
path: path$
|
|
4790
|
+
path: path$10,
|
|
4288
4791
|
dirent: utils.fs.createDirentFromStats(name, stats)
|
|
4289
4792
|
};
|
|
4290
4793
|
if (settings.stats) entry.stats = stats;
|
|
@@ -4414,17 +4917,17 @@ var require_out$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
4414
4917
|
const sync = require_sync$4();
|
|
4415
4918
|
const settings_1 = require_settings$2();
|
|
4416
4919
|
exports.Settings = settings_1.default;
|
|
4417
|
-
function scandir(path$
|
|
4920
|
+
function scandir(path$10, optionsOrSettingsOrCallback, callback) {
|
|
4418
4921
|
if (typeof optionsOrSettingsOrCallback === "function") {
|
|
4419
|
-
async.read(path$
|
|
4922
|
+
async.read(path$10, getSettings(), optionsOrSettingsOrCallback);
|
|
4420
4923
|
return;
|
|
4421
4924
|
}
|
|
4422
|
-
async.read(path$
|
|
4925
|
+
async.read(path$10, getSettings(optionsOrSettingsOrCallback), callback);
|
|
4423
4926
|
}
|
|
4424
4927
|
exports.scandir = scandir;
|
|
4425
|
-
function scandirSync(path$
|
|
4928
|
+
function scandirSync(path$10, optionsOrSettings) {
|
|
4426
4929
|
const settings = getSettings(optionsOrSettings);
|
|
4427
|
-
return sync.read(path$
|
|
4930
|
+
return sync.read(path$10, settings);
|
|
4428
4931
|
}
|
|
4429
4932
|
exports.scandirSync = scandirSync;
|
|
4430
4933
|
function getSettings(settingsOrOptions = {}) {
|
|
@@ -5765,44 +6268,518 @@ async function discoverPrompts(dir, pattern) {
|
|
|
5765
6268
|
return prompts;
|
|
5766
6269
|
}
|
|
5767
6270
|
|
|
6271
|
+
//#endregion
|
|
6272
|
+
//#region src/eval.ts
|
|
6273
|
+
const ChatCompletionResponse$1 = z.object({ choices: z.array(z.object({ message: z.object({ content: z.string() }) })) });
|
|
6274
|
+
const JudgeResponse = z.object({
|
|
6275
|
+
passed: z.boolean(),
|
|
6276
|
+
reason: z.string()
|
|
6277
|
+
});
|
|
6278
|
+
const PROVIDER_BASE_URLS$1 = {
|
|
6279
|
+
openai: "https://api.openai.com/v1",
|
|
6280
|
+
openrouter: "https://openrouter.ai/api/v1"
|
|
6281
|
+
};
|
|
6282
|
+
const JUDGE_SYSTEM_PROMPT = `You are evaluating whether an AI's output meets a specific assertion.
|
|
6283
|
+
|
|
6284
|
+
You will receive:
|
|
6285
|
+
1. The input that was given to the AI
|
|
6286
|
+
2. The AI's output
|
|
6287
|
+
3. An assertion describing what the output should do/contain
|
|
6288
|
+
|
|
6289
|
+
Evaluate whether the output satisfies the assertion. Be strict but fair.
|
|
6290
|
+
|
|
6291
|
+
Respond in JSON format:
|
|
6292
|
+
{
|
|
6293
|
+
"passed": true/false,
|
|
6294
|
+
"reason": "Brief explanation of why it passed or failed"
|
|
6295
|
+
}`;
|
|
6296
|
+
async function callAI$1(systemPrompt, userMessage, config) {
|
|
6297
|
+
let baseUrl;
|
|
6298
|
+
if (config.baseUrl) baseUrl = config.baseUrl;
|
|
6299
|
+
else if (config.provider === "local") throw new Error("Local provider requires baseUrl");
|
|
6300
|
+
else baseUrl = PROVIDER_BASE_URLS$1[config.provider];
|
|
6301
|
+
let apiKey;
|
|
6302
|
+
if (config.apiKeyEnvVar) apiKey = process.env[config.apiKeyEnvVar];
|
|
6303
|
+
const headers = { "Content-Type": "application/json" };
|
|
6304
|
+
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
6305
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
6306
|
+
method: "POST",
|
|
6307
|
+
headers,
|
|
6308
|
+
body: JSON.stringify({
|
|
6309
|
+
model: config.model,
|
|
6310
|
+
messages: [{
|
|
6311
|
+
role: "system",
|
|
6312
|
+
content: systemPrompt
|
|
6313
|
+
}, {
|
|
6314
|
+
role: "user",
|
|
6315
|
+
content: userMessage
|
|
6316
|
+
}]
|
|
6317
|
+
})
|
|
6318
|
+
});
|
|
6319
|
+
if (!response.ok) {
|
|
6320
|
+
const errorText = await response.text();
|
|
6321
|
+
throw new Error(formatAPIError(/* @__PURE__ */ new Error(`${response.status} - ${errorText}`), {
|
|
6322
|
+
model: config.model,
|
|
6323
|
+
operation: "running evaluation"
|
|
6324
|
+
}));
|
|
6325
|
+
}
|
|
6326
|
+
let data;
|
|
6327
|
+
try {
|
|
6328
|
+
data = ChatCompletionResponse$1.parse(await response.json());
|
|
6329
|
+
} catch (e) {
|
|
6330
|
+
throw new Error(formatAPIError(e, {
|
|
6331
|
+
model: config.model,
|
|
6332
|
+
operation: "running evaluation"
|
|
6333
|
+
}));
|
|
6334
|
+
}
|
|
6335
|
+
return data.choices[0]?.message.content ?? "";
|
|
6336
|
+
}
|
|
6337
|
+
async function runJudge(input, output, assertion, config) {
|
|
6338
|
+
const response = await callAI$1(JUDGE_SYSTEM_PROMPT, `## Input given to AI
|
|
6339
|
+
${input}
|
|
6340
|
+
|
|
6341
|
+
## AI's Output
|
|
6342
|
+
${output}
|
|
6343
|
+
|
|
6344
|
+
## Assertion to check
|
|
6345
|
+
${assertion}`, config);
|
|
6346
|
+
const jsonStr = (response.match(/```json\s*([\s\S]*?)\s*```/) || response.match(/```\s*([\s\S]*?)\s*```/) || [null, response])[1] || response;
|
|
6347
|
+
try {
|
|
6348
|
+
return JudgeResponse.parse(JSON.parse(jsonStr));
|
|
6349
|
+
} catch {
|
|
6350
|
+
const lowerResponse = response.toLowerCase();
|
|
6351
|
+
const passed = lowerResponse.includes("\"passed\": true") || lowerResponse.includes("\"passed\":true") || lowerResponse.includes("passed: true");
|
|
6352
|
+
const failed = lowerResponse.includes("\"passed\": false") || lowerResponse.includes("\"passed\":false") || lowerResponse.includes("passed: false");
|
|
6353
|
+
if (!passed && !failed) return {
|
|
6354
|
+
passed: false,
|
|
6355
|
+
reason: `Judge model didn't return valid JSON. Consider using a more capable model. Response: "${response.slice(0, 100)}${response.length > 100 ? "..." : ""}"`
|
|
6356
|
+
};
|
|
6357
|
+
return {
|
|
6358
|
+
passed,
|
|
6359
|
+
reason: "Inferred from non-JSON response"
|
|
6360
|
+
};
|
|
6361
|
+
}
|
|
6362
|
+
}
|
|
6363
|
+
async function runTest(systemPrompt, test, config, useJudge) {
|
|
6364
|
+
const output = await callAI$1(systemPrompt, test.input, config);
|
|
6365
|
+
let passed;
|
|
6366
|
+
let reason;
|
|
6367
|
+
if (typeof test.assert === "function") try {
|
|
6368
|
+
passed = test.assert(output);
|
|
6369
|
+
if (!passed) reason = "Assertion function returned false";
|
|
6370
|
+
} catch (e) {
|
|
6371
|
+
passed = false;
|
|
6372
|
+
reason = `Assertion threw: ${e}`;
|
|
6373
|
+
}
|
|
6374
|
+
else if (useJudge) {
|
|
6375
|
+
const judgeResult = await runJudge(test.input, output, test.assert, config);
|
|
6376
|
+
passed = judgeResult.passed;
|
|
6377
|
+
reason = judgeResult.reason;
|
|
6378
|
+
} else {
|
|
6379
|
+
passed = true;
|
|
6380
|
+
reason = "String assertion skipped (use --judge to evaluate)";
|
|
6381
|
+
}
|
|
6382
|
+
return {
|
|
6383
|
+
input: test.input,
|
|
6384
|
+
output,
|
|
6385
|
+
passed,
|
|
6386
|
+
description: test.description,
|
|
6387
|
+
reason
|
|
6388
|
+
};
|
|
6389
|
+
}
|
|
6390
|
+
async function evaluateVariant(promptId, variantName, systemPrompt, tests, config, useJudge) {
|
|
6391
|
+
const results = [];
|
|
6392
|
+
for (let i = 0; i < tests.length; i++) {
|
|
6393
|
+
const test = tests[i];
|
|
6394
|
+
const result = await runTest(systemPrompt, test, config, useJudge);
|
|
6395
|
+
results.push(result);
|
|
6396
|
+
}
|
|
6397
|
+
const passed = results.filter((r) => r.passed).length;
|
|
6398
|
+
const failed = results.filter((r) => !r.passed).length;
|
|
6399
|
+
const total = results.length;
|
|
6400
|
+
return {
|
|
6401
|
+
promptId,
|
|
6402
|
+
variantName,
|
|
6403
|
+
results,
|
|
6404
|
+
passed,
|
|
6405
|
+
failed,
|
|
6406
|
+
total,
|
|
6407
|
+
successRate: total > 0 ? passed / total * 100 : 100
|
|
6408
|
+
};
|
|
6409
|
+
}
|
|
6410
|
+
|
|
6411
|
+
//#endregion
|
|
6412
|
+
//#region src/improve-ai.ts
|
|
6413
|
+
const PromptChangeSchema = z.object({
|
|
6414
|
+
action: z.enum([
|
|
6415
|
+
"add",
|
|
6416
|
+
"modify",
|
|
6417
|
+
"remove"
|
|
6418
|
+
]),
|
|
6419
|
+
original: z.optional(z.string()),
|
|
6420
|
+
replacement: z.string(),
|
|
6421
|
+
reason: z.string()
|
|
6422
|
+
});
|
|
6423
|
+
const SourceHintSchema = z.object({
|
|
6424
|
+
stepType: z.string(),
|
|
6425
|
+
action: z.enum([
|
|
6426
|
+
"add",
|
|
6427
|
+
"modify",
|
|
6428
|
+
"remove",
|
|
6429
|
+
"adjust_nudge"
|
|
6430
|
+
]),
|
|
6431
|
+
suggestion: z.string(),
|
|
6432
|
+
reason: z.string()
|
|
6433
|
+
});
|
|
6434
|
+
const ImprovementSuggestionSchema = z.object({
|
|
6435
|
+
analysis: z.string(),
|
|
6436
|
+
promptChanges: z.array(PromptChangeSchema),
|
|
6437
|
+
sourceHints: z.array(SourceHintSchema),
|
|
6438
|
+
confidence: z.number()
|
|
6439
|
+
});
|
|
6440
|
+
const ChatCompletionResponse = z.object({ choices: z.array(z.object({ message: z.object({ content: z.string() }) })) });
|
|
6441
|
+
const PROVIDER_BASE_URLS = {
|
|
6442
|
+
openai: "https://api.openai.com/v1",
|
|
6443
|
+
openrouter: "https://openrouter.ai/api/v1"
|
|
6444
|
+
};
|
|
6445
|
+
const IMPROVEMENT_SYSTEM_PROMPT = `You are an expert prompt engineer improving AI system prompts based on test failures.
|
|
6446
|
+
|
|
6447
|
+
## Input
|
|
6448
|
+
1. Current system prompt text
|
|
6449
|
+
2. Failing tests with: input, expected assertion, actual output, failure reason
|
|
6450
|
+
|
|
6451
|
+
## Your Task
|
|
6452
|
+
1. Analyze why tests are failing
|
|
6453
|
+
2. Suggest specific text modifications to the system prompt
|
|
6454
|
+
3. Provide "source hints" - what builder step changes would help permanently
|
|
6455
|
+
|
|
6456
|
+
IMPORTANT: You MUST respond with ONLY a valid JSON object. No explanations, no markdown, just the JSON.
|
|
6457
|
+
|
|
6458
|
+
## Response Format
|
|
6459
|
+
{
|
|
6460
|
+
"analysis": "Brief explanation of failure pattern",
|
|
6461
|
+
"promptChanges": [
|
|
6462
|
+
{ "action": "add", "replacement": "new text to add to prompt", "reason": "why this helps" },
|
|
6463
|
+
{ "action": "modify", "original": "exact text to find", "replacement": "replacement text", "reason": "why" },
|
|
6464
|
+
{ "action": "remove", "original": "text to remove", "replacement": "", "reason": "why" }
|
|
6465
|
+
],
|
|
6466
|
+
"sourceHints": [
|
|
6467
|
+
{ "stepType": "dont", "action": "add", "suggestion": ".dont(\"add interpretive language\")", "reason": "prevents qualitative assessments" }
|
|
6468
|
+
],
|
|
6469
|
+
"confidence": 0.85
|
|
6470
|
+
}
|
|
6471
|
+
|
|
6472
|
+
## Guidelines
|
|
6473
|
+
- Make minimal changes to fix failures without breaking passing tests
|
|
6474
|
+
- For "add" actions, the replacement text will be appended to the prompt
|
|
6475
|
+
- For "modify" actions, provide the EXACT original text to find (copy from the prompt)
|
|
6476
|
+
- For sourceHints, suggest actual TypeScript code for .prompt.ts files
|
|
6477
|
+
- Available step types: persona, context, input, output, do, dont, constraint, example, raw
|
|
6478
|
+
- Nudge levels 1-5 control instruction strength (1=soft, 5=absolute)
|
|
6479
|
+
- Be conservative - prefer small targeted changes over large rewrites
|
|
6480
|
+
|
|
6481
|
+
Output ONLY the JSON object, nothing else.`;
|
|
6482
|
+
async function callAI(systemPrompt, userMessage, config) {
|
|
6483
|
+
let baseUrl;
|
|
6484
|
+
if (config.baseUrl) baseUrl = config.baseUrl;
|
|
6485
|
+
else if (config.provider === "local") throw new Error("Local provider requires baseUrl");
|
|
6486
|
+
else baseUrl = PROVIDER_BASE_URLS[config.provider];
|
|
6487
|
+
let apiKey;
|
|
6488
|
+
if (config.apiKeyEnvVar) apiKey = process.env[config.apiKeyEnvVar];
|
|
6489
|
+
const headers = { "Content-Type": "application/json" };
|
|
6490
|
+
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
6491
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
6492
|
+
method: "POST",
|
|
6493
|
+
headers,
|
|
6494
|
+
body: JSON.stringify({
|
|
6495
|
+
model: config.model,
|
|
6496
|
+
messages: [{
|
|
6497
|
+
role: "system",
|
|
6498
|
+
content: systemPrompt
|
|
6499
|
+
}, {
|
|
6500
|
+
role: "user",
|
|
6501
|
+
content: userMessage
|
|
6502
|
+
}]
|
|
6503
|
+
})
|
|
6504
|
+
});
|
|
6505
|
+
if (!response.ok) {
|
|
6506
|
+
const errorText = await response.text();
|
|
6507
|
+
throw new Error(formatAPIError(/* @__PURE__ */ new Error(`${response.status} - ${errorText}`), {
|
|
6508
|
+
model: config.model,
|
|
6509
|
+
operation: "improving prompt"
|
|
6510
|
+
}));
|
|
6511
|
+
}
|
|
6512
|
+
let data;
|
|
6513
|
+
try {
|
|
6514
|
+
data = ChatCompletionResponse.parse(await response.json());
|
|
6515
|
+
} catch (e) {
|
|
6516
|
+
throw new Error(formatAPIError(e, {
|
|
6517
|
+
model: config.model,
|
|
6518
|
+
operation: "improving prompt"
|
|
6519
|
+
}));
|
|
6520
|
+
}
|
|
6521
|
+
return data.choices[0]?.message.content ?? "";
|
|
6522
|
+
}
|
|
6523
|
+
async function requestImprovement(currentPrompt, failingTests, config, verbose = false) {
|
|
6524
|
+
const response = await callAI(IMPROVEMENT_SYSTEM_PROMPT, `## Current System Prompt
|
|
6525
|
+
\`\`\`
|
|
6526
|
+
${currentPrompt}
|
|
6527
|
+
\`\`\`
|
|
6528
|
+
|
|
6529
|
+
## Failing Tests
|
|
6530
|
+
${failingTests.map((t, i) => {
|
|
6531
|
+
return `### Test ${i + 1}${t.description ? ` (${t.description})` : ""}
|
|
6532
|
+
Input: ${t.input}
|
|
6533
|
+
Assertion: ${t.assertion}
|
|
6534
|
+
Actual Output: ${t.output}
|
|
6535
|
+
Failure Reason: ${t.reason || "Assertion not satisfied"}`;
|
|
6536
|
+
}).join("\n\n")}
|
|
6537
|
+
|
|
6538
|
+
Respond with ONLY a JSON object containing your analysis and suggested changes.`, config);
|
|
6539
|
+
const jsonStr = ((response.match(/```json\s*([\s\S]*?)\s*```/) || response.match(/```\s*([\s\S]*?)\s*```/) || response.match(/(\{[\s\S]*\})/) || [null, response])[1] || response).trim();
|
|
6540
|
+
try {
|
|
6541
|
+
const parsed = JSON.parse(jsonStr);
|
|
6542
|
+
return ImprovementSuggestionSchema.parse(parsed);
|
|
6543
|
+
} catch (e) {
|
|
6544
|
+
if (verbose) {
|
|
6545
|
+
console.log("\n ⚠️ Model returned invalid JSON");
|
|
6546
|
+
console.log(" Raw AI response:");
|
|
6547
|
+
const lines = response.split("\n");
|
|
6548
|
+
for (const line of lines.slice(0, 15)) console.log(` ${line}`);
|
|
6549
|
+
if (lines.length > 15) console.log(` ... (${lines.length - 15} more lines)`);
|
|
6550
|
+
console.log(`\n Parse error: ${e instanceof Error ? e.message : String(e)}`);
|
|
6551
|
+
} else console.log(" ⚠️ Model returned invalid JSON. Use --verbose to see the raw response.");
|
|
6552
|
+
console.log(" 💡 Tip: The improve command works best with capable models (gpt-4o, claude-3.5-sonnet).\n");
|
|
6553
|
+
return {
|
|
6554
|
+
analysis: "Model did not return valid JSON. Try using a more capable model.",
|
|
6555
|
+
promptChanges: [],
|
|
6556
|
+
sourceHints: [],
|
|
6557
|
+
confidence: 0
|
|
6558
|
+
};
|
|
6559
|
+
}
|
|
6560
|
+
}
|
|
6561
|
+
function applyPromptChanges(prompt, changes) {
|
|
6562
|
+
let result = prompt;
|
|
6563
|
+
for (const change of changes) switch (change.action) {
|
|
6564
|
+
case "add":
|
|
6565
|
+
result = result.trim() + "\n\n" + change.replacement;
|
|
6566
|
+
break;
|
|
6567
|
+
case "modify":
|
|
6568
|
+
if (change.original && result.includes(change.original)) result = result.replace(change.original, change.replacement);
|
|
6569
|
+
break;
|
|
6570
|
+
case "remove":
|
|
6571
|
+
if (change.original) result = result.replace(change.original, "");
|
|
6572
|
+
break;
|
|
6573
|
+
}
|
|
6574
|
+
return result.trim();
|
|
6575
|
+
}
|
|
6576
|
+
|
|
6577
|
+
//#endregion
|
|
6578
|
+
//#region src/improve.ts
|
|
6579
|
+
function escapeForTemplateLiteral$1(text) {
|
|
6580
|
+
return text.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
6581
|
+
}
|
|
6582
|
+
function updatePromptsGenFile(outputPath, promptId, variantName, newPromptText) {
|
|
6583
|
+
const content = fs$3.readFileSync(outputPath, "utf-8");
|
|
6584
|
+
const escaped = escapeForTemplateLiteral$1(newPromptText);
|
|
6585
|
+
const escapedVariantKey = JSON.stringify(variantName).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6586
|
+
const variantRegex = new RegExp(`(${escapedVariantKey}:\\s*)\`(?:[^\`\\\\]|\\\\.)*\``, "gs");
|
|
6587
|
+
const promptKey = JSON.stringify(promptId);
|
|
6588
|
+
const promptSectionRegex = new RegExp(`${promptKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:\\s*\\{[\\s\\S]*?\\n \\}`, "g");
|
|
6589
|
+
const newContent = content.replace(promptSectionRegex, (promptSection) => {
|
|
6590
|
+
return promptSection.replace(variantRegex, `$1\`${escaped}\``);
|
|
6591
|
+
});
|
|
6592
|
+
fs$3.writeFileSync(outputPath, newContent, "utf-8");
|
|
6593
|
+
}
|
|
6594
|
+
function getAssertionString(test) {
|
|
6595
|
+
if (typeof test.assert === "function") return test.assert.toString();
|
|
6596
|
+
return test.assert;
|
|
6597
|
+
}
|
|
6598
|
+
function resultsAreSame(prev, current) {
|
|
6599
|
+
if (prev.length !== current.length) return false;
|
|
6600
|
+
const prevFailing = prev.filter((r) => !r.passed).map((r) => r.input).sort();
|
|
6601
|
+
const currFailing = current.filter((r) => !r.passed).map((r) => r.input).sort();
|
|
6602
|
+
if (prevFailing.length !== currFailing.length) return false;
|
|
6603
|
+
return prevFailing.every((input, i) => input === currFailing[i]);
|
|
6604
|
+
}
|
|
6605
|
+
async function improveVariant(promptId, variantName, currentPrompt, tests, config, options, outputPath) {
|
|
6606
|
+
let prompt = currentPrompt;
|
|
6607
|
+
const allSourceHints = [];
|
|
6608
|
+
options.onStatus?.(promptId, variantName, "Evaluating current prompt...");
|
|
6609
|
+
let evaluation = await evaluateVariant(promptId, variantName, prompt, tests, config, options.judge);
|
|
6610
|
+
const initialFailures = evaluation.failed;
|
|
6611
|
+
if (initialFailures === 0) return {
|
|
6612
|
+
promptId,
|
|
6613
|
+
variantName,
|
|
6614
|
+
iterations: 0,
|
|
6615
|
+
initialFailures: 0,
|
|
6616
|
+
finalFailures: 0,
|
|
6617
|
+
sourceHints: [],
|
|
6618
|
+
status: "improved"
|
|
6619
|
+
};
|
|
6620
|
+
let previousResults = evaluation.results;
|
|
6621
|
+
for (let i = 0; i < options.maxIterations; i++) {
|
|
6622
|
+
options.onIterationStart?.(promptId, variantName, i + 1);
|
|
6623
|
+
const failingTestInfos = evaluation.results.filter((r) => !r.passed).map((r) => {
|
|
6624
|
+
const test = tests.find((t) => t.input === r.input);
|
|
6625
|
+
return {
|
|
6626
|
+
input: r.input,
|
|
6627
|
+
output: r.output,
|
|
6628
|
+
assertion: test ? getAssertionString(test) : "unknown",
|
|
6629
|
+
reason: r.reason,
|
|
6630
|
+
description: r.description
|
|
6631
|
+
};
|
|
6632
|
+
});
|
|
6633
|
+
options.onStatus?.(promptId, variantName, "Requesting AI improvement...");
|
|
6634
|
+
const suggestion = await requestImprovement(prompt, failingTestInfos, config, options.verbose);
|
|
6635
|
+
allSourceHints.push(...suggestion.sourceHints);
|
|
6636
|
+
if (suggestion.promptChanges.length === 0) {
|
|
6637
|
+
const result$1 = {
|
|
6638
|
+
promptId,
|
|
6639
|
+
variantName,
|
|
6640
|
+
iterations: i + 1,
|
|
6641
|
+
initialFailures,
|
|
6642
|
+
finalFailures: evaluation.failed,
|
|
6643
|
+
sourceHints: allSourceHints,
|
|
6644
|
+
status: "plateau"
|
|
6645
|
+
};
|
|
6646
|
+
options.onIterationDone?.(promptId, variantName, result$1);
|
|
6647
|
+
return result$1;
|
|
6648
|
+
}
|
|
6649
|
+
prompt = applyPromptChanges(prompt, suggestion.promptChanges);
|
|
6650
|
+
updatePromptsGenFile(outputPath, promptId, variantName, prompt);
|
|
6651
|
+
options.onStatus?.(promptId, variantName, "Re-evaluating prompt...");
|
|
6652
|
+
evaluation = await evaluateVariant(promptId, variantName, prompt, tests, config, options.judge);
|
|
6653
|
+
if (evaluation.failed === 0) {
|
|
6654
|
+
const result$1 = {
|
|
6655
|
+
promptId,
|
|
6656
|
+
variantName,
|
|
6657
|
+
iterations: i + 1,
|
|
6658
|
+
initialFailures,
|
|
6659
|
+
finalFailures: 0,
|
|
6660
|
+
sourceHints: allSourceHints,
|
|
6661
|
+
status: "improved"
|
|
6662
|
+
};
|
|
6663
|
+
options.onIterationDone?.(promptId, variantName, result$1);
|
|
6664
|
+
return result$1;
|
|
6665
|
+
}
|
|
6666
|
+
if (resultsAreSame(previousResults, evaluation.results)) {
|
|
6667
|
+
const result$1 = {
|
|
6668
|
+
promptId,
|
|
6669
|
+
variantName,
|
|
6670
|
+
iterations: i + 1,
|
|
6671
|
+
initialFailures,
|
|
6672
|
+
finalFailures: evaluation.failed,
|
|
6673
|
+
sourceHints: allSourceHints,
|
|
6674
|
+
status: "plateau"
|
|
6675
|
+
};
|
|
6676
|
+
options.onIterationDone?.(promptId, variantName, result$1);
|
|
6677
|
+
return result$1;
|
|
6678
|
+
}
|
|
6679
|
+
previousResults = evaluation.results;
|
|
6680
|
+
}
|
|
6681
|
+
const result = {
|
|
6682
|
+
promptId,
|
|
6683
|
+
variantName,
|
|
6684
|
+
iterations: options.maxIterations,
|
|
6685
|
+
initialFailures,
|
|
6686
|
+
finalFailures: evaluation.failed,
|
|
6687
|
+
sourceHints: allSourceHints,
|
|
6688
|
+
status: "max_iterations"
|
|
6689
|
+
};
|
|
6690
|
+
options.onIterationDone?.(promptId, variantName, result);
|
|
6691
|
+
return result;
|
|
6692
|
+
}
|
|
6693
|
+
async function improve$1(targetDir, outputPath, options) {
|
|
6694
|
+
const prompts = await discoverPrompts(targetDir, options.promptFilenamePattern ?? "**/*.prompt.{ts,js}");
|
|
6695
|
+
const existingPrompts = loadExistingPrompts(outputPath);
|
|
6696
|
+
if (Object.keys(existingPrompts).length === 0) throw new Error("No generated prompts found. Run 'npx nudge generate' first.");
|
|
6697
|
+
let promptsWithTests = prompts.filter((p) => p.state.tests && p.state.tests.length > 0);
|
|
6698
|
+
if (options.promptIds && options.promptIds.length > 0) promptsWithTests = promptsWithTests.filter((p) => options.promptIds.includes(p.id));
|
|
6699
|
+
if (promptsWithTests.length === 0) throw new Error("No prompts with tests found.");
|
|
6700
|
+
const results = [];
|
|
6701
|
+
for (const prompt of promptsWithTests) {
|
|
6702
|
+
const existing = existingPrompts[prompt.id];
|
|
6703
|
+
if (!existing) continue;
|
|
6704
|
+
const tests = prompt.state.tests;
|
|
6705
|
+
for (const [variantName, text] of Object.entries(existing.variants)) {
|
|
6706
|
+
const result = await improveVariant(prompt.id, variantName, text, tests, options.aiConfig, options, outputPath);
|
|
6707
|
+
results.push(result);
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
return results;
|
|
6711
|
+
}
|
|
6712
|
+
|
|
5768
6713
|
//#endregion
|
|
5769
6714
|
//#region src/index.ts
|
|
6715
|
+
function escapeForTemplateLiteral(text) {
|
|
6716
|
+
return text.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
6717
|
+
}
|
|
6718
|
+
function extractVariables(variants) {
|
|
6719
|
+
const allVars = /* @__PURE__ */ new Set();
|
|
6720
|
+
for (const text of Object.values(variants)) for (const match of text.matchAll(/\{\{(?![#\/])(\w+)\}\}/g)) allVars.add(match[1]);
|
|
6721
|
+
return [...allVars];
|
|
6722
|
+
}
|
|
5770
6723
|
async function generate(targetDir, outputPath, options = {}) {
|
|
5771
6724
|
const prompts = await discoverPrompts(targetDir, options.promptFilenamePattern ?? "**/*.prompt.{ts,js}");
|
|
5772
6725
|
if (!options.aiConfig) throw new Error("AI config is required in nudge.config.json");
|
|
5773
6726
|
const existingPrompts = loadExistingPrompts(outputPath);
|
|
5774
|
-
|
|
5775
|
-
const
|
|
6727
|
+
const results = [];
|
|
6728
|
+
for (const prompt of prompts) {
|
|
5776
6729
|
const hash = hashState(prompt.state);
|
|
5777
6730
|
const existing = existingPrompts[prompt.id];
|
|
5778
|
-
let
|
|
6731
|
+
let variants;
|
|
5779
6732
|
if (!options.noCache && existing && existing.hash === hash) {
|
|
5780
|
-
|
|
5781
|
-
|
|
6733
|
+
options.onPromptCached?.(prompt.id);
|
|
6734
|
+
variants = existing.variants;
|
|
5782
6735
|
} else {
|
|
5783
|
-
|
|
5784
|
-
|
|
6736
|
+
const definedVariants = prompt.state.variants ?? [];
|
|
6737
|
+
const variantCount = definedVariants.length || 1;
|
|
6738
|
+
try {
|
|
6739
|
+
options.onPromptStart?.(prompt.id, variantCount);
|
|
6740
|
+
if (definedVariants.length === 0) variants = { default: await processPrompt(prompt.state.steps, options.aiConfig, { silent: true }) };
|
|
6741
|
+
else {
|
|
6742
|
+
const variantEntries$1 = [];
|
|
6743
|
+
for (let i = 0; i < definedVariants.length; i++) {
|
|
6744
|
+
const v = definedVariants[i];
|
|
6745
|
+
const text = await processPrompt([...prompt.state.steps, ...v.steps], options.aiConfig, { silent: true });
|
|
6746
|
+
variantEntries$1.push([v.name, text]);
|
|
6747
|
+
}
|
|
6748
|
+
variants = Object.fromEntries(variantEntries$1);
|
|
6749
|
+
}
|
|
6750
|
+
options.onPromptDone?.(prompt.id, variantCount);
|
|
6751
|
+
} catch (error) {
|
|
6752
|
+
options.onPromptError?.(prompt.id, error);
|
|
6753
|
+
throw error;
|
|
6754
|
+
}
|
|
5785
6755
|
}
|
|
5786
|
-
const variables =
|
|
5787
|
-
const
|
|
5788
|
-
|
|
6756
|
+
const variables = extractVariables(variants);
|
|
6757
|
+
const variantNames = Object.keys(variants).filter((k) => k !== "default");
|
|
6758
|
+
const variantEntriesStr = Object.entries(variants).map(([name, text]) => {
|
|
6759
|
+
const escaped = escapeForTemplateLiteral(text);
|
|
6760
|
+
return ` ${JSON.stringify(name)}: \`${escaped}\``;
|
|
6761
|
+
}).join(",\n");
|
|
6762
|
+
results.push({
|
|
5789
6763
|
id: prompt.id,
|
|
5790
|
-
entry: ` ${JSON.stringify(prompt.id)}: {\n
|
|
6764
|
+
entry: ` ${JSON.stringify(prompt.id)}: {\n variants: {\n${variantEntriesStr},\n },\n hash: ${JSON.stringify(hash)},\n }`,
|
|
5791
6765
|
registry: ` ${JSON.stringify(prompt.id)}: true;`,
|
|
5792
|
-
variables: variables.length > 0 ? ` ${JSON.stringify(prompt.id)}: ${variables.map((v) => JSON.stringify(v)).join(" | ")};` : null
|
|
5793
|
-
|
|
5794
|
-
|
|
6766
|
+
variables: variables.length > 0 ? ` ${JSON.stringify(prompt.id)}: ${variables.map((v) => JSON.stringify(v)).join(" | ")};` : null,
|
|
6767
|
+
variantNames: variantNames.length > 0 ? ` ${JSON.stringify(prompt.id)}: ${variantNames.map((v) => JSON.stringify(v)).join(" | ")};` : null
|
|
6768
|
+
});
|
|
6769
|
+
}
|
|
5795
6770
|
const promptEntries = results.map((r) => r.entry);
|
|
5796
6771
|
const registryEntries = results.map((r) => r.registry);
|
|
5797
6772
|
const variableEntries = results.map((r) => r.variables).filter((v) => v !== null);
|
|
6773
|
+
const variantEntries = results.map((r) => r.variantNames).filter((v) => v !== null);
|
|
5798
6774
|
const variablesInterface = variableEntries.length > 0 ? `\n interface PromptVariables {\n${variableEntries.join("\n")}\n }` : "";
|
|
6775
|
+
const variantsInterface = variantEntries.length > 0 ? `\n interface PromptVariants {\n${variantEntries.join("\n")}\n }` : "";
|
|
5799
6776
|
const code = `// This file is auto-generated by @nudge-ai/cli. Do not edit manually.
|
|
5800
|
-
import { registerPrompts } from "@nudge-ai/core";
|
|
6777
|
+
import { registerPrompts } from "@nudge-ai/core/internal";
|
|
5801
6778
|
|
|
5802
6779
|
declare module "@nudge-ai/core" {
|
|
5803
6780
|
interface PromptRegistry {
|
|
5804
6781
|
${registryEntries.join("\n")}
|
|
5805
|
-
}${variablesInterface}
|
|
6782
|
+
}${variablesInterface}${variantsInterface}
|
|
5806
6783
|
}
|
|
5807
6784
|
|
|
5808
6785
|
const prompts = {
|
|
@@ -5812,9 +6789,32 @@ ${promptEntries.join(",\n")}
|
|
|
5812
6789
|
registerPrompts(prompts);
|
|
5813
6790
|
`;
|
|
5814
6791
|
fs$3.writeFileSync(outputPath, code, "utf-8");
|
|
5815
|
-
|
|
6792
|
+
}
|
|
6793
|
+
async function evaluate(targetDir, outputPath, options = {}) {
|
|
6794
|
+
const prompts = await discoverPrompts(targetDir, options.promptFilenamePattern ?? "**/*.prompt.{ts,js}");
|
|
6795
|
+
if (!options.aiConfig) throw new Error("AI config is required in nudge.config.json");
|
|
6796
|
+
const existingPrompts = loadExistingPrompts(outputPath);
|
|
6797
|
+
if (Object.keys(existingPrompts).length === 0) throw new Error("No generated prompts found. Run 'nudge generate' first.");
|
|
6798
|
+
const promptsWithTests = prompts.filter((p) => p.state.tests && p.state.tests.length > 0);
|
|
6799
|
+
if (promptsWithTests.length === 0) throw new Error("No prompts with tests found. Add tests using .test(input, assertion)");
|
|
6800
|
+
const evaluations = [];
|
|
6801
|
+
for (const prompt of promptsWithTests) {
|
|
6802
|
+
const existing = existingPrompts[prompt.id];
|
|
6803
|
+
if (!existing) continue;
|
|
6804
|
+
const tests = prompt.state.tests;
|
|
6805
|
+
for (const [variantName, text] of Object.entries(existing.variants)) {
|
|
6806
|
+
options.onVariantStart?.(prompt.id, variantName);
|
|
6807
|
+
const evaluation = await evaluateVariant(prompt.id, variantName, text, tests, options.aiConfig, options.judge ?? false);
|
|
6808
|
+
evaluations.push(evaluation);
|
|
6809
|
+
options.onVariantDone?.(evaluation);
|
|
6810
|
+
}
|
|
6811
|
+
}
|
|
6812
|
+
return evaluations;
|
|
6813
|
+
}
|
|
6814
|
+
async function improve(targetDir, outputPath, options) {
|
|
6815
|
+
return improve$1(targetDir, outputPath, options);
|
|
5816
6816
|
}
|
|
5817
6817
|
|
|
5818
6818
|
//#endregion
|
|
5819
|
-
export { discoverPrompts as
|
|
5820
|
-
//# sourceMappingURL=src-
|
|
6819
|
+
export { discoverPrompts as i, generate as n, improve as r, evaluate as t };
|
|
6820
|
+
//# sourceMappingURL=src-B7X5IQ4U.mjs.map
|