@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.
package/dist/bin.mjs CHANGED
@@ -1,26 +1,902 @@
1
1
  #!/usr/bin/env node
2
- import { t as generate } from "./src-DG37IBZ6.mjs";
2
+ import { n as generate, r as improve, t as evaluate } from "./src-B7X5IQ4U.mjs";
3
3
  import * as fs from "fs";
4
4
  import * as path from "path";
5
+ import { Box, Text, render, useApp } from "ink";
6
+ import React, { useEffect, useState } from "react";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import InkSpinner from "ink-spinner";
9
+ import SelectInput from "ink-select-input";
10
+ import TextInput from "ink-text-input";
5
11
 
6
- //#region src/bin.ts
12
+ //#region src/components/Header.tsx
13
+ function Header({ title, subtitle }) {
14
+ return /* @__PURE__ */ jsxs(Box, {
15
+ flexDirection: "column",
16
+ marginBottom: 1,
17
+ children: [/* @__PURE__ */ jsx(Text, {
18
+ bold: true,
19
+ color: "magenta",
20
+ children: title
21
+ }), subtitle && /* @__PURE__ */ jsxs(Text, {
22
+ dimColor: true,
23
+ children: [" ", subtitle]
24
+ })]
25
+ });
26
+ }
27
+
28
+ //#endregion
29
+ //#region src/components/Spinner.tsx
30
+ function Spinner({ label, color = "cyan" }) {
31
+ return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
32
+ color,
33
+ children: /* @__PURE__ */ jsx(InkSpinner, { type: "dots" })
34
+ }), /* @__PURE__ */ jsxs(Text, { children: [" ", label] })] });
35
+ }
36
+
37
+ //#endregion
38
+ //#region src/components/StatusMessage.tsx
39
+ const STATUS_ICONS = {
40
+ success: "✓",
41
+ error: "✗",
42
+ warning: "⚠",
43
+ info: "ℹ"
44
+ };
45
+ const STATUS_COLORS = {
46
+ success: "green",
47
+ error: "red",
48
+ warning: "yellow",
49
+ info: "blue"
50
+ };
51
+ function StatusMessage({ type, children }) {
52
+ return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
53
+ color: STATUS_COLORS[type],
54
+ children: [STATUS_ICONS[type], " "]
55
+ }), /* @__PURE__ */ jsx(Text, { children })] });
56
+ }
57
+ function Success({ children }) {
58
+ return /* @__PURE__ */ jsx(StatusMessage, {
59
+ type: "success",
60
+ children
61
+ });
62
+ }
63
+ function Error({ children }) {
64
+ return /* @__PURE__ */ jsx(StatusMessage, {
65
+ type: "error",
66
+ children
67
+ });
68
+ }
69
+ function Warning({ children }) {
70
+ return /* @__PURE__ */ jsx(StatusMessage, {
71
+ type: "warning",
72
+ children
73
+ });
74
+ }
75
+
76
+ //#endregion
77
+ //#region src/commands/Eval.tsx
78
+ function EvalCommand({ verbose = false, judge = false }) {
79
+ const { exit } = useApp();
80
+ const [status, setStatus] = useState("loading");
81
+ const [error, setError] = useState(null);
82
+ const [evaluations, setEvaluations] = useState([]);
83
+ const [currentPrompt, setCurrentPrompt] = useState(null);
84
+ useEffect(() => {
85
+ async function run() {
86
+ const cwd = process.cwd();
87
+ const configPath = path.join(cwd, "nudge.config.json");
88
+ let config = {};
89
+ if (fs.existsSync(configPath)) config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
90
+ const outputPath = config.generatedFile ? path.join(cwd, config.generatedFile) : path.join(cwd, "src", "prompts.gen.ts");
91
+ const targetDir = path.dirname(outputPath);
92
+ setStatus("evaluating");
93
+ try {
94
+ await evaluate(targetDir, outputPath, {
95
+ promptFilenamePattern: config.promptFilenamePattern,
96
+ aiConfig: config.ai,
97
+ verbose,
98
+ judge,
99
+ onVariantStart: (promptId, variantName) => {
100
+ setCurrentPrompt(`${promptId}:${variantName}`);
101
+ },
102
+ onVariantDone: (evaluation) => {
103
+ setEvaluations((prev) => [...prev, evaluation]);
104
+ setCurrentPrompt(null);
105
+ }
106
+ });
107
+ setStatus("done");
108
+ setTimeout(() => exit(), 100);
109
+ } catch (err) {
110
+ setError(String(err));
111
+ setStatus("error");
112
+ setTimeout(() => exit(), 100);
113
+ }
114
+ }
115
+ run();
116
+ }, [
117
+ verbose,
118
+ judge,
119
+ exit
120
+ ]);
121
+ const totalPassed = evaluations.reduce((sum, e) => sum + e.passed, 0);
122
+ const totalTests = totalPassed + evaluations.reduce((sum, e) => sum + e.failed, 0);
123
+ const overallRate = totalTests > 0 ? Math.round(totalPassed / totalTests * 100) : 0;
124
+ return /* @__PURE__ */ jsxs(Box, {
125
+ flexDirection: "column",
126
+ padding: 1,
127
+ children: [
128
+ /* @__PURE__ */ jsx(Header, {
129
+ title: "Nudge Eval",
130
+ subtitle: "Running tests against your prompts"
131
+ }),
132
+ status === "loading" && /* @__PURE__ */ jsx(Spinner, { label: "Loading configuration..." }),
133
+ status === "evaluating" && /* @__PURE__ */ jsxs(Box, {
134
+ flexDirection: "column",
135
+ marginTop: 1,
136
+ children: [evaluations.map((evaluation) => /* @__PURE__ */ jsx(EvaluationResult, {
137
+ evaluation,
138
+ verbose
139
+ }, `${evaluation.promptId}-${evaluation.variantName}`)), currentPrompt && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Spinner, { label: `Evaluating ${currentPrompt}...` }) })]
140
+ }),
141
+ status === "done" && /* @__PURE__ */ jsxs(Box, {
142
+ flexDirection: "column",
143
+ marginTop: 1,
144
+ children: [evaluations.map((evaluation) => /* @__PURE__ */ jsx(EvaluationResult, {
145
+ evaluation,
146
+ verbose
147
+ }, `${evaluation.promptId}-${evaluation.variantName}`)), /* @__PURE__ */ jsxs(Box, {
148
+ marginTop: 1,
149
+ flexDirection: "column",
150
+ children: [/* @__PURE__ */ jsx(Text, {
151
+ bold: true,
152
+ children: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
153
+ }), /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
154
+ bold: true,
155
+ children: "Summary: "
156
+ }), /* @__PURE__ */ jsxs(Text, {
157
+ color: overallRate >= 80 ? "green" : overallRate >= 50 ? "yellow" : "red",
158
+ children: [
159
+ totalPassed,
160
+ "/",
161
+ totalTests,
162
+ " tests passed (",
163
+ overallRate,
164
+ "%)"
165
+ ]
166
+ })] })]
167
+ })]
168
+ }),
169
+ status === "error" && /* @__PURE__ */ jsx(Box, {
170
+ marginTop: 1,
171
+ children: /* @__PURE__ */ jsx(Error, { children: error })
172
+ })
173
+ ]
174
+ });
175
+ }
176
+ function EvaluationResult({ evaluation, verbose }) {
177
+ const allPassed = evaluation.failed === 0;
178
+ const rateColor = evaluation.successRate >= 80 ? "green" : evaluation.successRate >= 50 ? "yellow" : "red";
179
+ return /* @__PURE__ */ jsxs(Box, {
180
+ flexDirection: "column",
181
+ marginBottom: 1,
182
+ children: [/* @__PURE__ */ jsxs(Box, { children: [
183
+ /* @__PURE__ */ jsxs(Text, {
184
+ color: allPassed ? "green" : "red",
185
+ children: [allPassed ? "✓" : "✗", " "]
186
+ }),
187
+ /* @__PURE__ */ jsx(Text, {
188
+ bold: true,
189
+ children: evaluation.promptId
190
+ }),
191
+ evaluation.variantName !== "default" && /* @__PURE__ */ jsxs(Text, {
192
+ dimColor: true,
193
+ children: [
194
+ " (",
195
+ evaluation.variantName,
196
+ ")"
197
+ ]
198
+ }),
199
+ /* @__PURE__ */ jsx(Text, { children: " - " }),
200
+ /* @__PURE__ */ jsxs(Text, {
201
+ color: rateColor,
202
+ children: [
203
+ evaluation.passed,
204
+ "/",
205
+ evaluation.total,
206
+ " passed (",
207
+ Math.round(evaluation.successRate),
208
+ "%)"
209
+ ]
210
+ })
211
+ ] }), verbose && evaluation.results.map((result, i) => /* @__PURE__ */ jsxs(Box, {
212
+ marginLeft: 2,
213
+ children: [
214
+ /* @__PURE__ */ jsxs(Text, {
215
+ color: result.passed ? "green" : "red",
216
+ children: [result.passed ? "✓" : "✗", " "]
217
+ }),
218
+ /* @__PURE__ */ jsxs(Text, {
219
+ dimColor: true,
220
+ children: [
221
+ "\"",
222
+ result.input.slice(0, 40),
223
+ result.input.length > 40 ? "..." : "",
224
+ "\""
225
+ ]
226
+ }),
227
+ !result.passed && result.reason && /* @__PURE__ */ jsxs(Text, {
228
+ color: "red",
229
+ children: [" - ", result.reason]
230
+ })
231
+ ]
232
+ }, i))]
233
+ });
234
+ }
235
+
236
+ //#endregion
237
+ //#region src/commands/Generate.tsx
238
+ function GenerateCommand({ force = false }) {
239
+ const { exit } = useApp();
240
+ const [status, setStatus] = useState("loading");
241
+ const [error, setError] = useState(null);
242
+ const [prompts, setPrompts] = useState([]);
243
+ const [outputPath, setOutputPath] = useState("");
244
+ useEffect(() => {
245
+ async function run() {
246
+ const cwd = process.cwd();
247
+ const configPath = path.join(cwd, "nudge.config.json");
248
+ let config = {};
249
+ if (fs.existsSync(configPath)) config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
250
+ const output = config.generatedFile ? path.join(cwd, config.generatedFile) : path.join(cwd, "src", "prompts.gen.ts");
251
+ setOutputPath(output);
252
+ const targetDir = path.dirname(output);
253
+ setStatus("generating");
254
+ try {
255
+ await generate(targetDir, output, {
256
+ promptFilenamePattern: config.promptFilenamePattern,
257
+ aiConfig: config.ai,
258
+ noCache: force,
259
+ onPromptStart: (id, variantCount) => {
260
+ setPrompts((prev) => [...prev, {
261
+ id,
262
+ status: "generating",
263
+ variantCount
264
+ }]);
265
+ },
266
+ onPromptCached: (id) => {
267
+ setPrompts((prev) => [...prev, {
268
+ id,
269
+ status: "cached"
270
+ }]);
271
+ },
272
+ onPromptDone: (id, variantCount) => {
273
+ setPrompts((prev) => prev.map((p) => p.id === id ? {
274
+ ...p,
275
+ status: "done",
276
+ variantCount
277
+ } : p));
278
+ },
279
+ onPromptError: (id, err) => {
280
+ setPrompts((prev) => prev.map((p) => p.id === id ? {
281
+ ...p,
282
+ status: "error"
283
+ } : p));
284
+ }
285
+ });
286
+ setStatus("done");
287
+ setTimeout(() => exit(), 100);
288
+ } catch (err) {
289
+ setError(String(err));
290
+ setStatus("error");
291
+ setTimeout(() => exit(), 100);
292
+ }
293
+ }
294
+ run();
295
+ }, [force, exit]);
296
+ return /* @__PURE__ */ jsxs(Box, {
297
+ flexDirection: "column",
298
+ padding: 1,
299
+ children: [
300
+ /* @__PURE__ */ jsx(Header, {
301
+ title: "Nudge Generate",
302
+ subtitle: "Generating type-safe prompt files"
303
+ }),
304
+ status === "loading" && /* @__PURE__ */ jsx(Spinner, { label: "Loading configuration..." }),
305
+ (status === "generating" || status === "done") && /* @__PURE__ */ jsx(Box, {
306
+ flexDirection: "column",
307
+ marginTop: 1,
308
+ children: prompts.map((prompt) => /* @__PURE__ */ jsxs(Box, {
309
+ gap: 1,
310
+ children: [
311
+ prompt.status === "generating" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
312
+ color: "cyan",
313
+ children: "◐"
314
+ }), /* @__PURE__ */ jsxs(Text, { children: [
315
+ "\"",
316
+ prompt.id,
317
+ "\" generating",
318
+ prompt.variantCount && prompt.variantCount > 1 ? ` ${prompt.variantCount} variant(s)` : "",
319
+ "..."
320
+ ] })] }),
321
+ prompt.status === "cached" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
322
+ color: "gray",
323
+ children: "✓"
324
+ }), /* @__PURE__ */ jsxs(Text, {
325
+ dimColor: true,
326
+ children: [
327
+ "\"",
328
+ prompt.id,
329
+ "\" (cached)"
330
+ ]
331
+ })] }),
332
+ prompt.status === "done" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
333
+ color: "green",
334
+ children: "✓"
335
+ }), /* @__PURE__ */ jsxs(Text, { children: [
336
+ "\"",
337
+ prompt.id,
338
+ "\" generated",
339
+ prompt.variantCount && prompt.variantCount > 1 ? ` ${prompt.variantCount} variant(s)` : ""
340
+ ] })] }),
341
+ prompt.status === "error" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
342
+ color: "red",
343
+ children: "✗"
344
+ }), /* @__PURE__ */ jsxs(Text, { children: [
345
+ "\"",
346
+ prompt.id,
347
+ "\" failed"
348
+ ] })] })
349
+ ]
350
+ }, prompt.id))
351
+ }),
352
+ status === "done" && /* @__PURE__ */ jsx(Box, {
353
+ marginTop: 1,
354
+ children: /* @__PURE__ */ jsxs(Success, { children: [
355
+ "Generated ",
356
+ outputPath,
357
+ " with ",
358
+ prompts.length,
359
+ " prompt(s)"
360
+ ] })
361
+ }),
362
+ status === "error" && /* @__PURE__ */ jsx(Box, {
363
+ marginTop: 1,
364
+ children: /* @__PURE__ */ jsx(Error, { children: error })
365
+ })
366
+ ]
367
+ });
368
+ }
369
+
370
+ //#endregion
371
+ //#region src/commands/Improve.tsx
372
+ function ImproveCommand({ maxIterations = 3, promptIds, verbose = false, judge = false }) {
373
+ const { exit } = useApp();
374
+ const [status, setStatus] = useState("loading");
375
+ const [error, setError] = useState(null);
376
+ const [progress, setProgress] = useState([]);
377
+ const [currentAction, setCurrentAction] = useState(null);
378
+ const [results, setResults] = useState([]);
379
+ useEffect(() => {
380
+ async function run() {
381
+ const cwd = process.cwd();
382
+ const configPath = path.join(cwd, "nudge.config.json");
383
+ let config = {};
384
+ if (fs.existsSync(configPath)) config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
385
+ if (!config.ai) {
386
+ setError("AI config is required in nudge.config.json");
387
+ setStatus("error");
388
+ setTimeout(() => exit(), 100);
389
+ return;
390
+ }
391
+ const outputPath = config.generatedFile ? path.join(cwd, config.generatedFile) : path.join(cwd, "src", "prompts.gen.ts");
392
+ const targetDir = path.dirname(outputPath);
393
+ setStatus("improving");
394
+ try {
395
+ setResults(await improve(targetDir, outputPath, {
396
+ maxIterations,
397
+ promptIds: promptIds ? promptIds.split(",").map((id) => id.trim()) : void 0,
398
+ verbose,
399
+ judge,
400
+ aiConfig: config.ai,
401
+ promptFilenamePattern: config.promptFilenamePattern,
402
+ onStatus: (promptId, variantName, statusMessage) => {
403
+ setProgress((prev) => {
404
+ if (prev.find((p) => p.promptId === promptId && p.variantName === variantName)) return prev.map((p) => p.promptId === promptId && p.variantName === variantName ? {
405
+ ...p,
406
+ currentStatus: statusMessage
407
+ } : p);
408
+ return [...prev, {
409
+ promptId,
410
+ variantName,
411
+ iteration: 0,
412
+ maxIterations,
413
+ status: "improving",
414
+ currentStatus: statusMessage
415
+ }];
416
+ });
417
+ },
418
+ onIterationStart: (promptId, variantName, iteration) => {
419
+ setProgress((prev) => {
420
+ if (prev.find((p) => p.promptId === promptId && p.variantName === variantName)) return prev.map((p) => p.promptId === promptId && p.variantName === variantName ? {
421
+ ...p,
422
+ iteration,
423
+ status: "improving"
424
+ } : p);
425
+ return [...prev, {
426
+ promptId,
427
+ variantName,
428
+ iteration,
429
+ maxIterations,
430
+ status: "improving"
431
+ }];
432
+ });
433
+ setCurrentAction(`Improving ${promptId}:${variantName} (iteration ${iteration}/${maxIterations})`);
434
+ },
435
+ onIterationDone: (promptId, variantName, result) => {
436
+ setProgress((prev) => prev.map((p) => p.promptId === promptId && p.variantName === variantName ? {
437
+ ...p,
438
+ status: result.status,
439
+ initialFailures: result.initialFailures,
440
+ finalFailures: result.finalFailures,
441
+ sourceHints: result.sourceHints
442
+ } : p));
443
+ setCurrentAction(null);
444
+ }
445
+ }));
446
+ setStatus("done");
447
+ setTimeout(() => exit(), 100);
448
+ } catch (err) {
449
+ setError(String(err));
450
+ setStatus("error");
451
+ setTimeout(() => exit(), 100);
452
+ }
453
+ }
454
+ run();
455
+ }, [
456
+ maxIterations,
457
+ promptIds,
458
+ verbose,
459
+ judge,
460
+ exit
461
+ ]);
462
+ const improved = results.filter((r) => r.status === "improved").length;
463
+ const plateau = results.filter((r) => r.status === "plateau").length;
464
+ const maxedOut = results.filter((r) => r.status === "max_iterations").length;
465
+ return /* @__PURE__ */ jsxs(Box, {
466
+ flexDirection: "column",
467
+ padding: 1,
468
+ children: [
469
+ /* @__PURE__ */ jsx(Header, {
470
+ title: "Nudge Improve",
471
+ subtitle: "Iteratively improving prompts based on failing tests"
472
+ }),
473
+ status === "loading" && /* @__PURE__ */ jsx(Spinner, { label: "Loading configuration..." }),
474
+ (status === "improving" || status === "done") && /* @__PURE__ */ jsxs(Box, {
475
+ flexDirection: "column",
476
+ marginTop: 1,
477
+ children: [progress.map((p) => /* @__PURE__ */ jsx(Box, {
478
+ marginBottom: 1,
479
+ children: /* @__PURE__ */ jsx(ImprovementStatus, { progress: p })
480
+ }, `${p.promptId}-${p.variantName}`)), currentAction && /* @__PURE__ */ jsx(Box, {
481
+ marginTop: 1,
482
+ children: /* @__PURE__ */ jsx(Spinner, { label: currentAction })
483
+ })]
484
+ }),
485
+ status === "done" && results.length > 0 && /* @__PURE__ */ jsxs(Box, {
486
+ marginTop: 1,
487
+ flexDirection: "column",
488
+ children: [/* @__PURE__ */ jsx(Text, {
489
+ bold: true,
490
+ children: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
491
+ }), /* @__PURE__ */ jsxs(Box, {
492
+ flexDirection: "column",
493
+ children: [
494
+ /* @__PURE__ */ jsx(Text, {
495
+ bold: true,
496
+ children: "Summary:"
497
+ }),
498
+ improved > 0 && /* @__PURE__ */ jsxs(Success, { children: [improved, " prompt(s) improved"] }),
499
+ plateau > 0 && /* @__PURE__ */ jsxs(Warning, { children: [plateau, " prompt(s) reached plateau (no more improvements possible)"] }),
500
+ maxedOut > 0 && /* @__PURE__ */ jsxs(Warning, { children: [maxedOut, " prompt(s) hit max iterations"] })
501
+ ]
502
+ })]
503
+ }),
504
+ status === "done" && results.length === 0 && /* @__PURE__ */ jsx(Box, {
505
+ marginTop: 1,
506
+ children: /* @__PURE__ */ jsx(Success, { children: "All prompts are passing their tests!" })
507
+ }),
508
+ status === "error" && /* @__PURE__ */ jsx(Box, {
509
+ marginTop: 1,
510
+ children: /* @__PURE__ */ jsx(Error, { children: error })
511
+ })
512
+ ]
513
+ });
514
+ }
515
+ function ImprovementStatus({ progress }) {
516
+ const icon = progress.status === "improved" ? "✓" : progress.status === "improving" ? "◐" : progress.status === "plateau" ? "○" : "⚠";
517
+ return /* @__PURE__ */ jsxs(Box, {
518
+ flexDirection: "column",
519
+ children: [/* @__PURE__ */ jsxs(Box, { children: [
520
+ /* @__PURE__ */ jsxs(Text, {
521
+ color: progress.status === "improved" ? "green" : progress.status === "improving" ? "cyan" : "yellow",
522
+ children: [icon, " "]
523
+ }),
524
+ /* @__PURE__ */ jsx(Text, {
525
+ bold: true,
526
+ children: progress.promptId
527
+ }),
528
+ progress.variantName !== "default" && /* @__PURE__ */ jsxs(Text, {
529
+ dimColor: true,
530
+ children: [
531
+ " (",
532
+ progress.variantName,
533
+ ")"
534
+ ]
535
+ }),
536
+ progress.status === "improving" && progress.iteration > 0 && /* @__PURE__ */ jsxs(Text, {
537
+ dimColor: true,
538
+ children: [
539
+ " ",
540
+ "- iteration ",
541
+ progress.iteration,
542
+ "/",
543
+ progress.maxIterations
544
+ ]
545
+ }),
546
+ progress.status === "improving" && progress.currentStatus && /* @__PURE__ */ jsxs(Text, {
547
+ dimColor: true,
548
+ children: [" - ", progress.currentStatus]
549
+ }),
550
+ progress.status === "improved" && /* @__PURE__ */ jsxs(Text, {
551
+ color: "green",
552
+ children: [
553
+ " ",
554
+ "- improved (",
555
+ progress.initialFailures,
556
+ " → ",
557
+ progress.finalFailures,
558
+ " ",
559
+ "failures)"
560
+ ]
561
+ }),
562
+ progress.status === "plateau" && /* @__PURE__ */ jsx(Text, {
563
+ color: "yellow",
564
+ children: " - plateau reached"
565
+ }),
566
+ progress.status === "max_iterations" && /* @__PURE__ */ jsxs(Text, {
567
+ color: "yellow",
568
+ children: [
569
+ " ",
570
+ "- max iterations (",
571
+ progress.finalFailures,
572
+ " failures remaining)"
573
+ ]
574
+ })
575
+ ] }), progress.sourceHints && progress.sourceHints.length > 0 && /* @__PURE__ */ jsxs(Box, {
576
+ flexDirection: "column",
577
+ marginLeft: 2,
578
+ children: [/* @__PURE__ */ jsx(Text, {
579
+ dimColor: true,
580
+ children: " 💡 Suggested source changes:"
581
+ }), progress.sourceHints.map((hint, i) => /* @__PURE__ */ jsxs(Box, {
582
+ flexDirection: "column",
583
+ marginLeft: 2,
584
+ children: [/* @__PURE__ */ jsxs(Text, {
585
+ dimColor: true,
586
+ children: [
587
+ hint.action,
588
+ " ",
589
+ hint.stepType,
590
+ ": ",
591
+ hint.suggestion
592
+ ]
593
+ }), /* @__PURE__ */ jsxs(Text, {
594
+ dimColor: true,
595
+ italic: true,
596
+ children: ["└ ", hint.reason]
597
+ })]
598
+ }, i))]
599
+ })]
600
+ });
601
+ }
602
+
603
+ //#endregion
604
+ //#region src/commands/Init.tsx
605
+ const PROVIDER_CHOICES = [
606
+ {
607
+ label: "OpenAI",
608
+ value: "openai"
609
+ },
610
+ {
611
+ label: "OpenRouter",
612
+ value: "openrouter"
613
+ },
614
+ {
615
+ label: "Local (custom endpoint)",
616
+ value: "local"
617
+ }
618
+ ];
619
+ const DEFAULT_API_KEYS = {
620
+ openai: "OPENAI_API_KEY",
621
+ openrouter: "OPENROUTER_API_KEY",
622
+ local: "API_KEY"
623
+ };
624
+ const DEFAULT_MODELS = {
625
+ openai: "gpt-4o",
626
+ openrouter: "x-ai/grok-code-fast-1",
627
+ local: "llama2"
628
+ };
629
+ function InitCommand() {
630
+ const { exit } = useApp();
631
+ const cwd = process.cwd();
632
+ const configPath = path.join(cwd, "nudge.config.json");
633
+ const [step, setStep] = useState("check-existing");
634
+ const [provider, setProvider] = useState("openai");
635
+ const [apiKeyEnvVar, setApiKeyEnvVar] = useState("");
636
+ const [baseUrl, setBaseUrl] = useState("");
637
+ const [model, setModel] = useState("");
638
+ const [error, setError] = useState(null);
639
+ const [configExists, setConfigExists] = useState(false);
640
+ useEffect(() => {
641
+ const exists = fs.existsSync(configPath);
642
+ setConfigExists(exists);
643
+ if (exists) setStep("confirm-overwrite");
644
+ else setStep("select-provider");
645
+ }, [configPath]);
646
+ useEffect(() => {
647
+ if (step === "done") {
648
+ const config = { ai: {
649
+ provider,
650
+ apiKeyEnvVar,
651
+ model,
652
+ ...baseUrl && { baseUrl }
653
+ } };
654
+ try {
655
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
656
+ } catch (err) {
657
+ setError(`Failed to write config: ${err}`);
658
+ return;
659
+ }
660
+ setTimeout(() => exit(), 100);
661
+ }
662
+ }, [
663
+ step,
664
+ provider,
665
+ apiKeyEnvVar,
666
+ model,
667
+ baseUrl,
668
+ configPath,
669
+ exit
670
+ ]);
671
+ const handleProviderSelect = (item) => {
672
+ setProvider(item.value);
673
+ setApiKeyEnvVar(DEFAULT_API_KEYS[item.value]);
674
+ setModel(DEFAULT_MODELS[item.value]);
675
+ setStep("enter-api-key-env");
676
+ };
677
+ const handleApiKeySubmit = () => {
678
+ if (provider === "local") {
679
+ setBaseUrl("http://localhost:11434/v1");
680
+ setStep("enter-base-url");
681
+ } else setStep("enter-model");
682
+ };
683
+ const handleBaseUrlSubmit = () => {
684
+ setStep("enter-model");
685
+ };
686
+ const handleModelSubmit = () => {
687
+ setStep("done");
688
+ };
689
+ const handleOverwriteSelect = (item) => {
690
+ if (item.value) setStep("select-provider");
691
+ else exit();
692
+ };
693
+ if (error) return /* @__PURE__ */ jsx(Box, {
694
+ flexDirection: "column",
695
+ children: /* @__PURE__ */ jsxs(Text, {
696
+ color: "red",
697
+ children: ["Error: ", error]
698
+ })
699
+ });
700
+ return /* @__PURE__ */ jsxs(Box, {
701
+ flexDirection: "column",
702
+ padding: 1,
703
+ children: [
704
+ /* @__PURE__ */ jsx(Header, {
705
+ title: "Nudge Init",
706
+ subtitle: "Let's set up your configuration!"
707
+ }),
708
+ step === "confirm-overwrite" && /* @__PURE__ */ jsxs(Box, {
709
+ flexDirection: "column",
710
+ children: [
711
+ /* @__PURE__ */ jsx(Warning, { children: "nudge.config.json already exists!" }),
712
+ /* @__PURE__ */ jsx(Box, {
713
+ marginTop: 1,
714
+ children: /* @__PURE__ */ jsx(Text, { children: "Do you want to overwrite it?" })
715
+ }),
716
+ /* @__PURE__ */ jsx(Box, {
717
+ marginTop: 1,
718
+ children: /* @__PURE__ */ jsx(SelectInput, {
719
+ items: [{
720
+ label: "No",
721
+ value: false
722
+ }, {
723
+ label: "Yes",
724
+ value: true
725
+ }],
726
+ onSelect: handleOverwriteSelect
727
+ })
728
+ })
729
+ ]
730
+ }),
731
+ step === "select-provider" && /* @__PURE__ */ jsxs(Box, {
732
+ flexDirection: "column",
733
+ children: [/* @__PURE__ */ jsx(Text, { children: "Choose your AI provider:" }), /* @__PURE__ */ jsx(Box, {
734
+ marginTop: 1,
735
+ children: /* @__PURE__ */ jsx(SelectInput, {
736
+ items: PROVIDER_CHOICES,
737
+ onSelect: handleProviderSelect
738
+ })
739
+ })]
740
+ }),
741
+ step === "enter-api-key-env" && /* @__PURE__ */ jsxs(Box, {
742
+ flexDirection: "column",
743
+ children: [/* @__PURE__ */ jsx(Text, { children: "API key environment variable name:" }), /* @__PURE__ */ jsxs(Box, {
744
+ marginTop: 1,
745
+ children: [/* @__PURE__ */ jsx(Text, {
746
+ color: "cyan",
747
+ children: "❯ "
748
+ }), /* @__PURE__ */ jsx(TextInput, {
749
+ value: apiKeyEnvVar,
750
+ onChange: setApiKeyEnvVar,
751
+ onSubmit: handleApiKeySubmit
752
+ })]
753
+ })]
754
+ }),
755
+ step === "enter-base-url" && /* @__PURE__ */ jsxs(Box, {
756
+ flexDirection: "column",
757
+ children: [/* @__PURE__ */ jsx(Text, { children: "Base URL for your local provider:" }), /* @__PURE__ */ jsxs(Box, {
758
+ marginTop: 1,
759
+ children: [/* @__PURE__ */ jsx(Text, {
760
+ color: "cyan",
761
+ children: "❯ "
762
+ }), /* @__PURE__ */ jsx(TextInput, {
763
+ value: baseUrl,
764
+ onChange: setBaseUrl,
765
+ onSubmit: handleBaseUrlSubmit
766
+ })]
767
+ })]
768
+ }),
769
+ step === "enter-model" && /* @__PURE__ */ jsxs(Box, {
770
+ flexDirection: "column",
771
+ children: [/* @__PURE__ */ jsx(Text, { children: "Model name:" }), /* @__PURE__ */ jsxs(Box, {
772
+ marginTop: 1,
773
+ children: [/* @__PURE__ */ jsx(Text, {
774
+ color: "cyan",
775
+ children: "❯ "
776
+ }), /* @__PURE__ */ jsx(TextInput, {
777
+ value: model,
778
+ onChange: setModel,
779
+ onSubmit: handleModelSubmit
780
+ })]
781
+ })]
782
+ }),
783
+ step === "done" && /* @__PURE__ */ jsxs(Box, {
784
+ flexDirection: "column",
785
+ marginTop: 1,
786
+ children: [/* @__PURE__ */ jsx(Success, { children: "Configuration saved to nudge.config.json" }), /* @__PURE__ */ jsxs(Box, {
787
+ marginTop: 1,
788
+ flexDirection: "column",
789
+ children: [
790
+ /* @__PURE__ */ jsx(Text, {
791
+ color: "yellow",
792
+ children: "💡 Next steps:"
793
+ }),
794
+ /* @__PURE__ */ jsxs(Text, { children: [
795
+ " 1. Set your ",
796
+ apiKeyEnvVar,
797
+ " environment variable"
798
+ ] }),
799
+ /* @__PURE__ */ jsxs(Text, { children: [" ", "2. Create your prompt files (e.g., src/prompts/example.prompt.ts)"] }),
800
+ /* @__PURE__ */ jsxs(Text, { children: [" ", "3. Run 'npx @nudge-ai/cli generate' to compile your prompts"] }),
801
+ /* @__PURE__ */ jsxs(Text, { children: [" ", "4. Import './prompts.gen' once at your app's entry point"] })
802
+ ]
803
+ })]
804
+ })
805
+ ]
806
+ });
807
+ }
808
+
809
+ //#endregion
810
+ //#region src/bin.tsx
7
811
  const args = process.argv.slice(2);
8
- if (args[0] !== "generate") {
9
- console.error(`Usage: nudge generate [--no-cache]`);
10
- process.exit(1);
812
+ const command = args[0];
813
+ function parseArgs(args$1) {
814
+ const result = {};
815
+ for (let i = 0; i < args$1.length; i++) {
816
+ const arg = args$1[i];
817
+ if (arg.startsWith("--")) {
818
+ const key = arg.slice(2);
819
+ const next = args$1[i + 1];
820
+ if (next && !next.startsWith("--")) {
821
+ result[key] = next;
822
+ i++;
823
+ } else result[key] = true;
824
+ }
825
+ }
826
+ return result;
827
+ }
828
+ function showHelp() {
829
+ console.log(`
830
+ Nudge CLI - Generate type-safe prompt files for AI applications
831
+
832
+ Usage:
833
+ nudge <command> [options]
834
+
835
+ Commands:
836
+ init Initialize a new Nudge configuration
837
+ generate Generate type-safe prompt files from your prompt templates
838
+ eval Run tests defined in prompts to evaluate quality
839
+ improve Iteratively improve prompts based on failing tests
840
+
841
+ Options:
842
+ generate:
843
+ --force Skip cache and regenerate all prompts
844
+
845
+ eval:
846
+ --verbose Show detailed test results
847
+ --judge Use LLM to evaluate string assertions
848
+
849
+ improve:
850
+ --max-iterations Maximum improvement iterations (default: 3)
851
+ --prompt-ids Comma-separated list of specific prompt IDs to improve
852
+ --verbose Show detailed improvement steps
853
+ --judge Use LLM to evaluate string assertions
854
+
855
+ Examples:
856
+ nudge init
857
+ nudge generate
858
+ nudge generate --force
859
+ nudge eval --verbose
860
+ nudge eval --judge
861
+ nudge improve --max-iterations 5
862
+ nudge improve --prompt-ids summarizer,translator
863
+ `);
864
+ }
865
+ async function main() {
866
+ if (!command || command === "--help" || command === "-h") {
867
+ showHelp();
868
+ return;
869
+ }
870
+ const options = parseArgs(args.slice(1));
871
+ switch (command) {
872
+ case "init":
873
+ render(React.createElement(InitCommand));
874
+ break;
875
+ case "generate":
876
+ render(React.createElement(GenerateCommand, { force: options.force === true }));
877
+ break;
878
+ case "eval":
879
+ render(React.createElement(EvalCommand, {
880
+ verbose: options.verbose === true,
881
+ judge: options.judge === true
882
+ }));
883
+ break;
884
+ case "improve":
885
+ render(React.createElement(ImproveCommand, {
886
+ maxIterations: typeof options["max-iterations"] === "string" ? parseInt(options["max-iterations"], 10) : 3,
887
+ promptIds: typeof options["prompt-ids"] === "string" ? options["prompt-ids"] : void 0,
888
+ verbose: options.verbose === true,
889
+ judge: options.judge === true
890
+ }));
891
+ break;
892
+ default:
893
+ console.error(`Unknown command: ${command}`);
894
+ console.log("Run \"nudge --help\" for usage information.");
895
+ process.exit(1);
896
+ }
11
897
  }
12
- const noCache = args.includes("--no-cache");
13
- const cwd = process.cwd();
14
- const configPath = path.join(cwd, "nudge.config.json");
15
- let config = {};
16
- if (fs.existsSync(configPath)) config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
17
- const outputPath = config.generatedFile ? path.join(cwd, config.generatedFile) : path.join(cwd, "src", "prompts.gen.ts");
18
- generate(path.dirname(outputPath), outputPath, {
19
- promptFilenamePattern: config.promptFilenamePattern,
20
- aiConfig: config.ai,
21
- noCache
22
- }).catch((error) => {
23
- console.error("Error generating prompts:", error);
898
+ main().catch((err) => {
899
+ console.error("Error:", err);
24
900
  process.exit(1);
25
901
  });
26
902