@nudge-ai/cli 0.0.1-beta.3 → 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.
@@ -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
- const apiKey = process.env[config.apiKeyEnvVar];
87
- if (!apiKey) throw new Error(`Missing API key: environment variable "${config.apiKeyEnvVar}" is not set`);
88
- const baseUrl = PROVIDER_BASE_URLS[config.provider];
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
- const response = await fetch(`${baseUrl}/chat/completions`, {
91
- method: "POST",
92
- headers: {
93
- Authorization: `Bearer ${apiKey}`,
94
- "Content-Type": "application/json"
95
- },
96
- body: JSON.stringify({
97
- model: config.model,
98
- messages: [{
99
- role: "system",
100
- content: SYSTEM_PROMPT
101
- }, {
102
- role: "user",
103
- content: `Generate a system prompt from these building blocks:\n\n${stepsDescription}`
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 error = await response.text();
109
- throw new Error(`AI request failed: ${response.status} - ${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
- return ChatCompletionResponse.parse(await response.json()).choices[0]?.message.content ?? "";
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/micromatch/node_modules/picomatch/lib/constants.js
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/micromatch/node_modules/picomatch/lib/utils.js
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/micromatch/node_modules/picomatch/lib/scan.js
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/micromatch/node_modules/picomatch/lib/parse.js
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/micromatch/node_modules/picomatch/lib/picomatch.js
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/micromatch/node_modules/picomatch/index.js
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$9, settings, callback) {
3969
- settings.fs.lstat(path$9, (lstatError, lstat) => {
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$9, (statError, stat) => {
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$9, settings) {
4007
- const lstat = settings.fs.lstatSync(path$9);
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$9);
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$9, optionsOrSettingsOrCallback, callback) {
4572
+ function stat(path$10, optionsOrSettingsOrCallback, callback) {
4070
4573
  if (typeof optionsOrSettingsOrCallback === "function") {
4071
- async.read(path$9, getSettings(), optionsOrSettingsOrCallback);
4574
+ async.read(path$10, getSettings(), optionsOrSettingsOrCallback);
4072
4575
  return;
4073
4576
  }
4074
- async.read(path$9, getSettings(optionsOrSettingsOrCallback), callback);
4577
+ async.read(path$10, getSettings(optionsOrSettingsOrCallback), callback);
4075
4578
  }
4076
4579
  exports.stat = stat;
4077
- function statSync(path$9, optionsOrSettings) {
4580
+ function statSync(path$10, optionsOrSettings) {
4078
4581
  const settings = getSettings(optionsOrSettings);
4079
- return sync.read(path$9, settings);
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$9 = common.joinPathSegments(directory, name, settings.pathSegmentSeparator);
4781
+ const path$10 = common.joinPathSegments(directory, name, settings.pathSegmentSeparator);
4279
4782
  return (done) => {
4280
- fsStat.stat(path$9, settings.fsStatSettings, (error, stats) => {
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$9,
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$9, optionsOrSettingsOrCallback, callback) {
4920
+ function scandir(path$10, optionsOrSettingsOrCallback, callback) {
4418
4921
  if (typeof optionsOrSettingsOrCallback === "function") {
4419
- async.read(path$9, getSettings(), optionsOrSettingsOrCallback);
4922
+ async.read(path$10, getSettings(), optionsOrSettingsOrCallback);
4420
4923
  return;
4421
4924
  }
4422
- async.read(path$9, getSettings(optionsOrSettingsOrCallback), callback);
4925
+ async.read(path$10, getSettings(optionsOrSettingsOrCallback), callback);
4423
4926
  }
4424
4927
  exports.scandir = scandir;
4425
- function scandirSync(path$9, optionsOrSettings) {
4928
+ function scandirSync(path$10, optionsOrSettings) {
4426
4929
  const settings = getSettings(optionsOrSettings);
4427
- return sync.read(path$9, settings);
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
- console.log(`Processing ${prompts.length} prompt(s)...`);
5775
- const results = await Promise.all(prompts.map(async (prompt) => {
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 text;
6731
+ let variants;
5779
6732
  if (!options.noCache && existing && existing.hash === hash) {
5780
- console.log(` ✓ "${prompt.id}" unchanged (cached)`);
5781
- text = existing.text;
6733
+ options.onPromptCached?.(prompt.id);
6734
+ variants = existing.variants;
5782
6735
  } else {
5783
- console.log(` → "${prompt.id}" processing with AI...`);
5784
- text = await processPrompt(prompt.state, options.aiConfig);
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 = [...new Set([...text.matchAll(/\{\{(?![#\/])(\w+)\}\}/g)].map((m) => m[1]))];
5787
- const escaped = text.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
5788
- return {
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 text: \`${escaped}\`,\n hash: ${JSON.stringify(hash)},\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
- console.log(`Generated ${outputPath} with ${prompts.length} prompt(s)`);
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 n, generate as t };
5820
- //# sourceMappingURL=src-DG37IBZ6.mjs.map
6819
+ export { discoverPrompts as i, generate as n, improve as r, evaluate as t };
6820
+ //# sourceMappingURL=src-B7X5IQ4U.mjs.map