@langwatch/better-agents 0.1.3-beta.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/index.js ADDED
@@ -0,0 +1,1763 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import * as fs6 from 'fs/promises';
4
+ import * as path6 from 'path';
5
+ import { select, password, input, confirm } from '@inquirer/prompts';
6
+ import { exec, spawn, spawnSync } from 'child_process';
7
+ import pino from 'pino';
8
+ import chalk from 'chalk';
9
+ import ora from 'ora';
10
+ import { promisify } from 'util';
11
+
12
+ var BaseLogger = class {
13
+ projectLogger;
14
+ timers = /* @__PURE__ */ new Map();
15
+ /**
16
+ * Creates a new base logger instance.
17
+ * @param projectPath - Optional path to project for debug log file creation
18
+ */
19
+ constructor(projectPath) {
20
+ if (projectPath) {
21
+ this.setupProjectLogging(projectPath);
22
+ }
23
+ }
24
+ /**
25
+ * Sets up project-specific JSON logging to a timestamped debug log file.
26
+ */
27
+ async setupProjectLogging(projectPath) {
28
+ try {
29
+ const logDir = path6.join(projectPath, ".better-agents");
30
+ const now = /* @__PURE__ */ new Date();
31
+ const timestamp = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
32
+ const logPath = path6.join(logDir, `debug-${timestamp}.log`);
33
+ await fs6.mkdir(logDir, { recursive: true });
34
+ this.projectLogger = pino(
35
+ {
36
+ level: "debug",
37
+ formatters: {
38
+ level: (label) => ({
39
+ level: label,
40
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
41
+ })
42
+ }
43
+ },
44
+ pino.destination(logPath)
45
+ );
46
+ } catch {
47
+ }
48
+ }
49
+ /**
50
+ * Logs debug information in structured JSON format.
51
+ * @param step - The operation or step being logged
52
+ * @param context - Additional structured data for debugging
53
+ */
54
+ debug(step, context = {}) {
55
+ this.projectLogger?.debug({ step, ...context });
56
+ }
57
+ /**
58
+ * Logs informational debug data in structured JSON format.
59
+ * @param step - The operation or step being logged
60
+ * @param context - Additional structured data for analysis
61
+ */
62
+ info(step, context = {}) {
63
+ this.projectLogger?.info({ step, ...context });
64
+ }
65
+ /**
66
+ * Logs error with stack trace in structured JSON format.
67
+ * @param error - The error that occurred
68
+ * @param context - Additional context about the error
69
+ */
70
+ error(error, context = {}) {
71
+ this.projectLogger?.error({
72
+ step: "error",
73
+ error: error.message,
74
+ stack: error.stack,
75
+ ...context
76
+ });
77
+ }
78
+ /**
79
+ * Starts a performance timer and returns a function to end it.
80
+ * @param label - Identifier for the timed operation
81
+ * @returns Function that ends the timer and logs duration
82
+ */
83
+ startTimer(label) {
84
+ const start = Date.now();
85
+ this.timers.set(label, start);
86
+ this.debug("timer-start", { label });
87
+ return () => {
88
+ const duration = Date.now() - start;
89
+ this.timers.delete(label);
90
+ this.debug("timer-end", { label, duration });
91
+ return duration;
92
+ };
93
+ }
94
+ /**
95
+ * Checks if the logger has file logging capability.
96
+ * @returns True if project logging is set up
97
+ */
98
+ hasFileLogging() {
99
+ return this.projectLogger !== void 0;
100
+ }
101
+ };
102
+ var DisplayLogger = class _DisplayLogger {
103
+ spinner;
104
+ /**
105
+ * Logs user-facing informational message with cyan color.
106
+ * @param message - The message to display to the user
107
+ */
108
+ userInfo(message) {
109
+ if (this.spinner) {
110
+ this.spinner.info(chalk.cyan(message));
111
+ } else {
112
+ console.log(chalk.cyan(message));
113
+ }
114
+ }
115
+ /**
116
+ * Logs user-facing success message with green color and checkmark.
117
+ * @param message - The success message to display
118
+ */
119
+ userSuccess(message) {
120
+ if (this.spinner) {
121
+ this.spinner.succeed(chalk.green(message));
122
+ } else {
123
+ console.log(chalk.green(`\u2705 ${message}`));
124
+ }
125
+ }
126
+ /**
127
+ * Logs user-facing error message with red color and X mark.
128
+ * @param message - The error message to display
129
+ */
130
+ userError(message) {
131
+ if (this.spinner) {
132
+ this.spinner.fail(chalk.red(message));
133
+ } else {
134
+ console.error(chalk.red(`\u274C ${message}`));
135
+ }
136
+ }
137
+ /**
138
+ * Logs user-facing warning message with yellow color and warning symbol.
139
+ * @param message - The warning message to display
140
+ */
141
+ userWarning(message) {
142
+ if (this.spinner) {
143
+ this.spinner.warn(chalk.yellow(message));
144
+ } else {
145
+ console.warn(chalk.yellow(`\u26A0\uFE0F ${message}`));
146
+ }
147
+ }
148
+ /**
149
+ * Starts an ora spinner for long-running operations.
150
+ * @param text - Initial spinner text
151
+ * @returns The spinner instance
152
+ */
153
+ startSpinner(text) {
154
+ if (!this.spinner) {
155
+ this.spinner = ora(text).start();
156
+ }
157
+ return this.spinner;
158
+ }
159
+ /**
160
+ * Gets the current spinner instance if one exists.
161
+ * @returns The current spinner or undefined
162
+ */
163
+ getSpinner() {
164
+ return this.spinner;
165
+ }
166
+ /**
167
+ * Creates a child display logger with additional context.
168
+ * For display logger, this is mainly for API compatibility.
169
+ * @param _context - Context (ignored for display logger)
170
+ * @returns New display logger instance
171
+ */
172
+ child(_context) {
173
+ return new _DisplayLogger();
174
+ }
175
+ };
176
+ var ConsoleLogger = class {
177
+ pinoLogger;
178
+ /**
179
+ * Creates a new console logger instance.
180
+ */
181
+ constructor() {
182
+ if (this.detectDebugMode()) {
183
+ this.pinoLogger = pino({
184
+ level: "debug",
185
+ transport: {
186
+ target: "pino-pretty",
187
+ options: {
188
+ colorize: true,
189
+ translateTime: "HH:MM:ss",
190
+ ignore: "pid,hostname"
191
+ }
192
+ }
193
+ });
194
+ }
195
+ }
196
+ /**
197
+ * Detects if debug mode is enabled via environment variable or CLI flag.
198
+ */
199
+ detectDebugMode() {
200
+ return Boolean(
201
+ process.env.SUPERAGENTS_DEBUG || process.argv.includes("--debug") || process.argv.includes("-d")
202
+ );
203
+ }
204
+ /**
205
+ * Logs debug information to console in pretty format.
206
+ * @param step - The operation or step being logged
207
+ * @param context - Additional structured data for debugging
208
+ */
209
+ debug(step, context = {}) {
210
+ this.pinoLogger?.debug({ step, ...context });
211
+ }
212
+ /**
213
+ * Logs informational debug data to console in pretty format.
214
+ * @param step - The operation or step being logged
215
+ * @param context - Additional structured data for analysis
216
+ */
217
+ info(step, context = {}) {
218
+ this.pinoLogger?.info({ step, ...context });
219
+ }
220
+ /**
221
+ * Logs error with stack trace to console in pretty format.
222
+ * @param error - The error that occurred
223
+ * @param context - Additional context about the error
224
+ */
225
+ error(error, context = {}) {
226
+ this.pinoLogger?.error({
227
+ step: "error",
228
+ error: error.message,
229
+ stack: error.stack,
230
+ ...context
231
+ });
232
+ }
233
+ /**
234
+ * Checks if console logging is enabled.
235
+ * @returns True if debug mode console logging is active
236
+ */
237
+ isEnabled() {
238
+ return this.pinoLogger !== void 0;
239
+ }
240
+ };
241
+
242
+ // src/utils/logger/logger-facade.ts
243
+ var LoggerFacade = class _LoggerFacade {
244
+ baseLogger;
245
+ displayLogger;
246
+ consoleLogger;
247
+ /**
248
+ * Creates a new logger facade instance.
249
+ * @param projectPath - Optional path to project for debug log file creation
250
+ */
251
+ constructor(projectPath) {
252
+ this.baseLogger = new BaseLogger(projectPath);
253
+ this.displayLogger = new DisplayLogger();
254
+ this.consoleLogger = new ConsoleLogger();
255
+ }
256
+ /**
257
+ * Logs user-facing informational message with cyan color.
258
+ * @param message - The message to display to the user
259
+ */
260
+ userInfo(message) {
261
+ this.displayLogger.userInfo(message);
262
+ this.baseLogger.debug("user-info", { message, type: "info" });
263
+ this.consoleLogger.debug("user-info", { message, type: "info" });
264
+ }
265
+ /**
266
+ * Logs user-facing success message with green color and checkmark.
267
+ * @param message - The success message to display
268
+ */
269
+ userSuccess(message) {
270
+ this.displayLogger.userSuccess(message);
271
+ this.baseLogger.debug("user-success", { message, type: "success" });
272
+ this.consoleLogger.debug("user-success", { message, type: "success" });
273
+ }
274
+ /**
275
+ * Logs user-facing error message with red color and X mark.
276
+ * @param message - The error message to display
277
+ */
278
+ userError(message) {
279
+ this.displayLogger.userError(message);
280
+ this.baseLogger.error(new Error(message), { type: "user-error" });
281
+ this.consoleLogger.error(new Error(message), { type: "user-error" });
282
+ }
283
+ /**
284
+ * Logs user-facing warning message with yellow color and warning symbol.
285
+ * @param message - The warning message to display
286
+ */
287
+ userWarning(message) {
288
+ this.displayLogger.userWarning(message);
289
+ this.baseLogger.debug("user-warning", { message, type: "warning" });
290
+ this.consoleLogger.debug("user-warning", { message, type: "warning" });
291
+ }
292
+ /**
293
+ * Logs debug information in structured JSON format.
294
+ * @param step - The operation or step being logged
295
+ * @param context - Additional structured data for debugging
296
+ */
297
+ debug(step, context = {}) {
298
+ this.baseLogger.debug(step, context);
299
+ this.consoleLogger.debug(step, context);
300
+ }
301
+ /**
302
+ * Logs informational debug data in structured JSON format.
303
+ * @param step - The operation or step being logged
304
+ * @param context - Additional structured data for analysis
305
+ */
306
+ info(step, context = {}) {
307
+ this.baseLogger.info(step, context);
308
+ this.consoleLogger.info(step, context);
309
+ }
310
+ /**
311
+ * Logs error with stack trace in structured JSON format.
312
+ * @param error - The error that occurred
313
+ * @param context - Additional context about the error
314
+ */
315
+ error(error, context = {}) {
316
+ this.baseLogger.error(error, context);
317
+ this.consoleLogger.error(error, context);
318
+ }
319
+ /**
320
+ * Starts a performance timer and returns a function to end it.
321
+ * @param label - Identifier for the timed operation
322
+ * @returns Function that ends the timer and logs duration
323
+ */
324
+ startTimer(label) {
325
+ return this.baseLogger.startTimer(label);
326
+ }
327
+ /**
328
+ * Creates a child logger with additional context.
329
+ * @param context - Context to add to all log entries from this child
330
+ * @returns New logger instance with inherited context
331
+ */
332
+ child(context) {
333
+ const childFacade = new _LoggerFacade();
334
+ const currentSpinner = this.displayLogger.getSpinner();
335
+ if (currentSpinner) {
336
+ childFacade.displayLogger = this.displayLogger.child(context);
337
+ }
338
+ return childFacade;
339
+ }
340
+ /**
341
+ * Starts an ora spinner for long-running operations.
342
+ * @param text - Initial spinner text
343
+ * @returns The spinner instance
344
+ */
345
+ startSpinner(text) {
346
+ return this.displayLogger.startSpinner(text);
347
+ }
348
+ /**
349
+ * Gets the current spinner instance if one exists.
350
+ * @returns The current spinner or undefined
351
+ */
352
+ getSpinner() {
353
+ return this.displayLogger.getSpinner();
354
+ }
355
+ };
356
+
357
+ // src/utils/logger/index.ts
358
+ var logger = new LoggerFacade();
359
+
360
+ // src/config-collection/choice-builders/language-choices.ts
361
+ var buildLanguageChoices = () => {
362
+ return [
363
+ { name: "TypeScript", value: "typescript" },
364
+ { name: "Python", value: "python" }
365
+ ];
366
+ };
367
+
368
+ // src/providers/frameworks/agno/knowledge.ts
369
+ var getKnowledge = () => ({
370
+ setupInstructions: "Python w/uv + pytest",
371
+ toolingInstructions: "Review the .cursorrules and llms.txt files for Agno best practices",
372
+ agentsGuideSection: `## Framework-Specific Guidelines
373
+
374
+ ### Agno Framework
375
+
376
+ **Always follow Agno best practices:**
377
+
378
+ - Refer to the \`.cursorrules\` file for Agno-specific coding standards
379
+ - Consult \`llms.txt\` for comprehensive Agno documentation
380
+ - Use Agno's agent building patterns and conventions
381
+ - Follow Agno's recommended project structure
382
+
383
+ **Key Agno Resources:**
384
+ - Documentation: https://docs.agno.com/
385
+ - GitHub: https://github.com/agno-agi/agno
386
+ - Local files: \`.cursorrules\` and \`llms.txt\`
387
+
388
+ **When implementing agent features:**
389
+ 1. Review Agno documentation for best practices
390
+ 2. Use Agno's built-in tools and utilities
391
+ 3. Follow Agno's patterns for agent state management
392
+ 4. Leverage Agno's testing utilities
393
+
394
+ ---
395
+ `
396
+ });
397
+ var AGNO_CURSORRULES_URL = "https://raw.githubusercontent.com/agno-agi/agno/main/.cursorrules";
398
+ var AGNO_LLMS_TXT_URL = "https://docs.agno.com/llms.txt";
399
+ var setup = async ({
400
+ projectPath
401
+ }) => {
402
+ await Promise.all([
403
+ fetchFile({
404
+ url: AGNO_CURSORRULES_URL,
405
+ targetPath: path6.join(projectPath, ".cursorrules"),
406
+ fallback: "# Agno cursor rules\n# Please manually download from: " + AGNO_CURSORRULES_URL
407
+ }),
408
+ fetchFile({
409
+ url: AGNO_LLMS_TXT_URL,
410
+ targetPath: path6.join(projectPath, "llms.txt"),
411
+ fallback: "# Agno LLMs documentation\n# Please manually download from: " + AGNO_LLMS_TXT_URL
412
+ })
413
+ ]);
414
+ };
415
+ var fetchFile = async ({
416
+ url,
417
+ targetPath,
418
+ fallback
419
+ }) => {
420
+ try {
421
+ const response = await fetch(url);
422
+ if (!response.ok) {
423
+ throw new Error(`Failed to fetch: ${response.statusText}`);
424
+ }
425
+ const content = await response.text();
426
+ await fs6.writeFile(targetPath, content);
427
+ } catch {
428
+ logger.userWarning(`Could not fetch ${url}, using fallback`);
429
+ await fs6.writeFile(targetPath, fallback);
430
+ }
431
+ };
432
+
433
+ // src/providers/frameworks/agno/index.ts
434
+ var AgnoFrameworkProvider = {
435
+ id: "agno",
436
+ displayName: "Agno",
437
+ language: "python",
438
+ getKnowledge,
439
+ getMCPConfig: () => null,
440
+ setup
441
+ };
442
+
443
+ // src/providers/frameworks/mastra/knowledge.ts
444
+ var getKnowledge2 = () => ({
445
+ setupInstructions: "TypeScript w/pnpm + vitest",
446
+ toolingInstructions: "Use the Mastra MCP to learn about Mastra and how to build agents",
447
+ agentsGuideSection: `## Framework-Specific Guidelines
448
+
449
+ ### Mastra Framework
450
+
451
+ **Always use the Mastra MCP for learning:**
452
+
453
+ - The Mastra MCP server provides real-time documentation
454
+ - Ask it questions about Mastra APIs and best practices
455
+ - Follow Mastra's recommended patterns for agent development
456
+
457
+ **When implementing agent features:**
458
+ 1. Consult the Mastra MCP: "How do I [do X] in Mastra?"
459
+ 2. Use Mastra's built-in agent capabilities
460
+ 3. Follow Mastra's TypeScript patterns and conventions
461
+ 4. Leverage Mastra's integration ecosystem
462
+
463
+ **Initial setup:**
464
+ 1. Use \`pnpx mastra init --default\` to create a new mastra project, do it before setting up the rest of the project, right after having done \`pnpm init\`.
465
+ 2. Then explore the setup it created, the folders, remove what not needed
466
+ 3. Proceed with the user definition request to implement the agent and test it out
467
+ 4. Open the UI for user to see using \`pnpx mastra dev\`
468
+
469
+ ---
470
+ `
471
+ });
472
+
473
+ // src/providers/frameworks/mastra/mcp-config.ts
474
+ var getMCPConfig = () => ({
475
+ type: "stdio",
476
+ command: "npx",
477
+ args: ["-y", "@mastra/mcp-docs-server"]
478
+ });
479
+
480
+ // src/providers/frameworks/mastra/index.ts
481
+ var MastraFrameworkProvider = {
482
+ id: "mastra",
483
+ displayName: "Mastra",
484
+ language: "typescript",
485
+ getKnowledge: getKnowledge2,
486
+ getMCPConfig,
487
+ setup: async () => {
488
+ }
489
+ };
490
+
491
+ // src/providers/frameworks/index.ts
492
+ var PROVIDERS = {
493
+ agno: AgnoFrameworkProvider,
494
+ mastra: MastraFrameworkProvider
495
+ };
496
+ var getFrameworkProvider = ({
497
+ framework
498
+ }) => {
499
+ const provider = PROVIDERS[framework];
500
+ if (!provider) {
501
+ throw new Error(`Framework provider not found: ${framework}`);
502
+ }
503
+ return provider;
504
+ };
505
+ var getFrameworksByLanguage = ({
506
+ language
507
+ }) => {
508
+ return Object.values(PROVIDERS).filter((p) => p.language === language);
509
+ };
510
+
511
+ // src/config-collection/choice-builders/framework-choices.ts
512
+ var buildFrameworkChoices = ({
513
+ language
514
+ }) => {
515
+ return getFrameworksByLanguage({ language }).map((provider) => ({
516
+ name: provider.displayName,
517
+ value: provider.id
518
+ }));
519
+ };
520
+ var ProcessUtils = class {
521
+ /**
522
+ * Launches a command with full terminal control using spawnSync.
523
+ * Blocks until the command completes.
524
+ *
525
+ * @param command - The command to execute
526
+ * @param args - Arguments for the command
527
+ * @param options - Execution options
528
+ *
529
+ * @example
530
+ * ```ts
531
+ * ProcessUtils.launchWithTerminalControl('cursor-agent', ['prompt text'], { cwd: '/path' });
532
+ * // Blocks until cursor-agent exits
533
+ * ```
534
+ */
535
+ static launchWithTerminalControl(command, args, options) {
536
+ const result = spawnSync(command, args, {
537
+ cwd: options.cwd,
538
+ stdio: "inherit"
539
+ });
540
+ if (result.error) {
541
+ throw new Error(`Failed to execute ${command}: ${result.error.message}`);
542
+ }
543
+ if (result.status !== null) {
544
+ process.exit(result.status);
545
+ }
546
+ }
547
+ };
548
+ var execAsync = promisify(exec);
549
+ var CliUtils = class {
550
+ /**
551
+ * Checks if a command is available in the system PATH.
552
+ *
553
+ * @param command - The command to check (e.g., 'claude', 'cursor-agent')
554
+ * @returns Promise resolving to true if command exists, false otherwise
555
+ *
556
+ * @example
557
+ * ```ts
558
+ * const hasCommand = await CliUtils.isCommandAvailable('claude');
559
+ * // Returns: true if claude is installed
560
+ * ```
561
+ */
562
+ static async isCommandAvailable(command) {
563
+ try {
564
+ await execAsync(`which ${command}`);
565
+ return true;
566
+ } catch {
567
+ return false;
568
+ }
569
+ }
570
+ };
571
+
572
+ // src/providers/coding-assistants/claude/index.ts
573
+ var ClaudeCodingAssistantProvider = {
574
+ id: "claude-code",
575
+ displayName: "Claude Code",
576
+ command: "claude",
577
+ async isAvailable() {
578
+ const installed = await CliUtils.isCommandAvailable("claude");
579
+ return {
580
+ installed,
581
+ installCommand: installed ? void 0 : "npm install -g @anthropic-ai/claude-code"
582
+ };
583
+ },
584
+ async writeMCPConfig({ projectPath, config }) {
585
+ const mcpConfigPath = path6.join(projectPath, ".mcp.json");
586
+ await fs6.writeFile(mcpConfigPath, JSON.stringify(config, null, 2));
587
+ const claudeMdPath = path6.join(projectPath, "CLAUDE.md");
588
+ const claudeMdContent = `@AGENTS.md
589
+ `;
590
+ await fs6.writeFile(claudeMdPath, claudeMdContent);
591
+ },
592
+ async launch({
593
+ projectPath,
594
+ prompt
595
+ }) {
596
+ try {
597
+ logger.userInfo(`\u{1F916} Launching ${this.displayName}...`);
598
+ ProcessUtils.launchWithTerminalControl("claude", [prompt], {
599
+ cwd: projectPath
600
+ });
601
+ logger.userSuccess("Session complete!");
602
+ } catch (error) {
603
+ if (error instanceof Error) {
604
+ logger.userError(`Failed to launch ${this.displayName}: ${error.message}`);
605
+ }
606
+ throw error;
607
+ }
608
+ }
609
+ };
610
+ var CursorCodingAssistantProvider = {
611
+ id: "cursor",
612
+ displayName: "Cursor",
613
+ command: "",
614
+ async isAvailable() {
615
+ return { installed: true };
616
+ },
617
+ async writeMCPConfig({ projectPath, config }) {
618
+ const cursorDir = path6.join(projectPath, ".cursor");
619
+ await fs6.mkdir(cursorDir, { recursive: true });
620
+ const mcpConfigPath = path6.join(cursorDir, "mcp.json");
621
+ await fs6.writeFile(mcpConfigPath, JSON.stringify(config, null, 2));
622
+ },
623
+ async launch({
624
+ projectPath
625
+ }) {
626
+ logger.userWarning("To start with Cursor:");
627
+ logger.userInfo(" 1. Open Cursor");
628
+ logger.userInfo(` 2. Open the folder: ${projectPath}`);
629
+ logger.userInfo(" 3. Use the initial prompt above with Cursor Composer");
630
+ }
631
+ };
632
+ var KilocodeCodingAssistantProvider = {
633
+ id: "kilocode",
634
+ displayName: "Kilocode CLI",
635
+ command: "kilocode",
636
+ async isAvailable() {
637
+ const installed = await CliUtils.isCommandAvailable("kilocode");
638
+ return {
639
+ installed,
640
+ installCommand: installed ? void 0 : "npm install -g @kilocode/cli"
641
+ };
642
+ },
643
+ async writeMCPConfig({ projectPath, config }) {
644
+ const mcpConfigPath = path6.join(projectPath, ".mcp.json");
645
+ await fs6.writeFile(mcpConfigPath, JSON.stringify(config, null, 2));
646
+ },
647
+ async launch({
648
+ projectPath,
649
+ prompt
650
+ }) {
651
+ try {
652
+ logger.userInfo(`\u{1F916} Launching ${this.displayName}...`);
653
+ ProcessUtils.launchWithTerminalControl("kilocode", ["-a", prompt], {
654
+ cwd: projectPath
655
+ });
656
+ logger.userSuccess("Session complete!");
657
+ } catch (error) {
658
+ if (error instanceof Error) {
659
+ logger.userError(
660
+ `Failed to launch ${this.displayName}: ${error.message}`
661
+ );
662
+ }
663
+ throw error;
664
+ }
665
+ }
666
+ };
667
+ var NoneCodingAssistantProvider = {
668
+ id: "none",
669
+ displayName: "None - I will prompt it myself",
670
+ command: "",
671
+ async isAvailable() {
672
+ return { installed: true };
673
+ },
674
+ async writeMCPConfig({ projectPath, config }) {
675
+ const mcpConfigPath = path6.join(projectPath, ".mcp.json");
676
+ await fs6.writeFile(mcpConfigPath, JSON.stringify(config, null, 2));
677
+ const cursorDir = path6.join(projectPath, ".cursor");
678
+ await fs6.mkdir(cursorDir, { recursive: true });
679
+ const cursorMcpPath = path6.join(cursorDir, "mcp.json");
680
+ await fs6.writeFile(cursorMcpPath, JSON.stringify(config, null, 2));
681
+ const claudeMdPath = path6.join(projectPath, "CLAUDE.md");
682
+ const claudeMdContent = `@AGENTS.md
683
+ `;
684
+ await fs6.writeFile(claudeMdPath, claudeMdContent);
685
+ },
686
+ async launch(_params) {
687
+ logger.userInfo(
688
+ "When you're ready, use the initial prompt above with your coding assistant."
689
+ );
690
+ }
691
+ };
692
+
693
+ // src/providers/coding-assistants/index.ts
694
+ var PROVIDERS2 = {
695
+ kilocode: KilocodeCodingAssistantProvider,
696
+ "claude-code": ClaudeCodingAssistantProvider,
697
+ cursor: CursorCodingAssistantProvider,
698
+ none: NoneCodingAssistantProvider
699
+ };
700
+ var getCodingAssistantProvider = ({
701
+ assistant
702
+ }) => {
703
+ const provider = PROVIDERS2[assistant];
704
+ if (!provider) {
705
+ throw new Error(`Coding assistant provider not found: ${assistant}`);
706
+ }
707
+ return provider;
708
+ };
709
+ var getAllCodingAssistants = () => {
710
+ return Object.values(PROVIDERS2);
711
+ };
712
+
713
+ // src/utils/coding-assistant.util.ts
714
+ var CodingAssistantUtils = class {
715
+ /**
716
+ * Detects which coding assistants are installed on the system.
717
+ *
718
+ * @returns Promise resolving to a map of assistant IDs to installation status
719
+ *
720
+ * @example
721
+ * ```ts
722
+ * const installed = await CodingAssistantUtils.detectInstalledAgents();
723
+ * // Returns: { 'claude-code': true, 'cursor': false, 'kilocode': true }
724
+ * ```
725
+ */
726
+ static async detectInstalledAgents() {
727
+ const [hasClaude, hasCursor, hasKilocode] = await Promise.all([
728
+ CliUtils.isCommandAvailable("claude"),
729
+ CliUtils.isCommandAvailable("cursor-agent"),
730
+ CliUtils.isCommandAvailable("kilocode")
731
+ ]);
732
+ return {
733
+ "claude-code": hasClaude,
734
+ cursor: hasCursor,
735
+ kilocode: hasKilocode,
736
+ none: true
737
+ // Always available since it doesn't require installation
738
+ };
739
+ }
740
+ };
741
+
742
+ // src/config-collection/choice-builders/coding-assistant-choices.ts
743
+ var buildCodingAssistantChoices = async () => {
744
+ const assistants = getAllCodingAssistants();
745
+ const installedMap = await CodingAssistantUtils.detectInstalledAgents();
746
+ const installed = [];
747
+ const notInstalled = [];
748
+ for (const assistant of assistants) {
749
+ const isInstalled = installedMap[assistant.id];
750
+ const choice = {
751
+ name: isInstalled ? assistant.displayName : chalk.gray(`${assistant.displayName} (not installed)`),
752
+ value: assistant.id
753
+ };
754
+ if (isInstalled) {
755
+ installed.push(choice);
756
+ } else {
757
+ notInstalled.push(choice);
758
+ }
759
+ }
760
+ return [...installed, ...notInstalled];
761
+ };
762
+
763
+ // src/providers/llm-providers/openai/index.ts
764
+ var OpenAIProvider = {
765
+ id: "openai",
766
+ displayName: "OpenAI",
767
+ apiKeyUrl: "https://platform.openai.com/api-keys",
768
+ getEnvVariables: ({ apiKey }) => [
769
+ { key: "OPENAI_API_KEY", value: apiKey }
770
+ ]
771
+ };
772
+
773
+ // src/providers/llm-providers/anthropic/index.ts
774
+ var AnthropicProvider = {
775
+ id: "anthropic",
776
+ displayName: "Anthropic (Claude)",
777
+ apiKeyUrl: "https://console.anthropic.com/settings/keys",
778
+ getEnvVariables: ({ apiKey }) => [
779
+ { key: "ANTHROPIC_API_KEY", value: apiKey }
780
+ ]
781
+ };
782
+
783
+ // src/providers/llm-providers/gemini/index.ts
784
+ var GeminiProvider = {
785
+ id: "gemini",
786
+ displayName: "Google Gemini",
787
+ apiKeyUrl: "https://aistudio.google.com/app/apikey",
788
+ getEnvVariables: ({ apiKey }) => [
789
+ { key: "GOOGLE_API_KEY", value: apiKey },
790
+ { key: "GEMINI_API_KEY", value: apiKey }
791
+ ]
792
+ };
793
+
794
+ // src/providers/llm-providers/bedrock/index.ts
795
+ var BedrockProvider = {
796
+ id: "bedrock",
797
+ displayName: "AWS Bedrock",
798
+ apiKeyUrl: "https://console.aws.amazon.com/iam/home#/security_credentials",
799
+ additionalCredentials: [
800
+ {
801
+ key: "awsSecretKey",
802
+ label: "AWS Secret Access Key",
803
+ type: "password",
804
+ validate: (value) => {
805
+ if (!value || value.length < 10) {
806
+ return "AWS Secret Access Key is required";
807
+ }
808
+ return true;
809
+ }
810
+ },
811
+ {
812
+ key: "awsRegion",
813
+ label: "AWS Region (e.g., us-east-1)",
814
+ type: "text",
815
+ defaultValue: "us-east-1",
816
+ validate: (value) => {
817
+ if (!value || value.length < 5) {
818
+ return "AWS Region is required";
819
+ }
820
+ return true;
821
+ }
822
+ }
823
+ ],
824
+ getEnvVariables: ({ apiKey, additionalInputs }) => {
825
+ const envVars = [
826
+ { key: "AWS_ACCESS_KEY_ID", value: apiKey }
827
+ ];
828
+ if (additionalInputs?.awsSecretKey) {
829
+ envVars.push({
830
+ key: "AWS_SECRET_ACCESS_KEY",
831
+ value: additionalInputs.awsSecretKey
832
+ });
833
+ }
834
+ if (additionalInputs?.awsRegion) {
835
+ envVars.push({
836
+ key: "AWS_REGION",
837
+ value: additionalInputs.awsRegion
838
+ });
839
+ }
840
+ return envVars;
841
+ }
842
+ };
843
+
844
+ // src/providers/llm-providers/openrouter/index.ts
845
+ var OpenRouterProvider = {
846
+ id: "openrouter",
847
+ displayName: "OpenRouter",
848
+ apiKeyUrl: "https://openrouter.ai/keys",
849
+ getEnvVariables: ({ apiKey }) => [
850
+ { key: "OPENROUTER_API_KEY", value: apiKey }
851
+ ]
852
+ };
853
+
854
+ // src/providers/llm-providers/grok/index.ts
855
+ var GrokProvider = {
856
+ id: "grok",
857
+ displayName: "xAI (Grok)",
858
+ apiKeyUrl: "https://console.x.ai/team",
859
+ getEnvVariables: ({ apiKey }) => [
860
+ { key: "XAI_API_KEY", value: apiKey }
861
+ ]
862
+ };
863
+
864
+ // src/providers/llm-providers/index.ts
865
+ var PROVIDERS3 = {
866
+ openai: OpenAIProvider,
867
+ anthropic: AnthropicProvider,
868
+ gemini: GeminiProvider,
869
+ bedrock: BedrockProvider,
870
+ openrouter: OpenRouterProvider,
871
+ grok: GrokProvider
872
+ };
873
+ var getLLMProvider = ({
874
+ provider
875
+ }) => {
876
+ const llmProvider = PROVIDERS3[provider];
877
+ if (!llmProvider) {
878
+ throw new Error(`LLM provider not found: ${provider}`);
879
+ }
880
+ return llmProvider;
881
+ };
882
+ var getAllLLMProviders = () => {
883
+ return Object.values(PROVIDERS3);
884
+ };
885
+
886
+ // src/config-collection/validators/openai-key.ts
887
+ var validateOpenAIKey = (value) => {
888
+ if (!value || value.trim().length === 0) {
889
+ return "API key is required";
890
+ }
891
+ if (!value.startsWith("sk-")) {
892
+ return 'OpenAI API key should start with "sk-"';
893
+ }
894
+ return true;
895
+ };
896
+
897
+ // src/config-collection/validators/langwatch-key.ts
898
+ var validateLangWatchKey = (value) => {
899
+ if (!value || value.trim().length === 0) {
900
+ return "LangWatch API key is required";
901
+ }
902
+ if (!value.startsWith("sk-lw-")) {
903
+ return 'LangWatch API key should start with "sk-lw-"';
904
+ }
905
+ return true;
906
+ };
907
+
908
+ // src/config-collection/validators/project-goal.ts
909
+ var validateProjectGoal = (value) => {
910
+ if (!value || value.trim().length === 0) {
911
+ return "Please describe what you want to build";
912
+ }
913
+ return true;
914
+ };
915
+
916
+ // src/config-collection/collect-config.ts
917
+ var collectConfig = async () => {
918
+ try {
919
+ logger.userInfo(
920
+ "Setting up your agent project following the Better Agent Structure.\n"
921
+ );
922
+ const language = await select({
923
+ message: "What programming language do you want to use?",
924
+ choices: buildLanguageChoices()
925
+ });
926
+ const framework = await select({
927
+ message: "What agent framework do you want to use?",
928
+ choices: buildFrameworkChoices({ language })
929
+ });
930
+ const allProviders = getAllLLMProviders();
931
+ const llmProvider = await select({
932
+ message: "What LLM provider is your agent going to use?",
933
+ choices: allProviders.map((p) => ({
934
+ name: p.displayName,
935
+ value: p.id
936
+ }))
937
+ });
938
+ const selectedProvider = allProviders.find((p) => p.id === llmProvider);
939
+ const providerDisplayName = selectedProvider?.displayName || llmProvider;
940
+ if (selectedProvider?.apiKeyUrl) {
941
+ logger.userInfo(`To get your ${providerDisplayName} API key, visit:`);
942
+ logger.userInfo(`${selectedProvider.apiKeyUrl}`);
943
+ }
944
+ const llmApiKey = await password({
945
+ message: `Enter your ${providerDisplayName} API key:`,
946
+ mask: "*",
947
+ validate: llmProvider === "openai" ? validateOpenAIKey : (value) => {
948
+ if (!value || value.length < 5) {
949
+ return "API key is required and must be at least 5 characters";
950
+ }
951
+ return true;
952
+ }
953
+ });
954
+ let llmAdditionalInputs;
955
+ if (selectedProvider?.additionalCredentials && selectedProvider.additionalCredentials.length > 0) {
956
+ llmAdditionalInputs = {};
957
+ for (const credential of selectedProvider.additionalCredentials) {
958
+ if (credential.type === "password") {
959
+ llmAdditionalInputs[credential.key] = await password({
960
+ message: `Enter your ${credential.label}:`,
961
+ mask: "*",
962
+ validate: credential.validate
963
+ });
964
+ } else {
965
+ llmAdditionalInputs[credential.key] = await input({
966
+ message: `Enter your ${credential.label}:`,
967
+ default: credential.defaultValue,
968
+ validate: credential.validate
969
+ });
970
+ }
971
+ }
972
+ }
973
+ const codingAssistant = await select({
974
+ message: "What is your preferred coding assistant for building the agent?",
975
+ choices: await buildCodingAssistantChoices()
976
+ });
977
+ const codingAssistantProviders = getAllCodingAssistants();
978
+ const selectedCodingProvider = codingAssistantProviders.find(
979
+ (p) => p.id === codingAssistant
980
+ );
981
+ if (selectedCodingProvider) {
982
+ let availability = await selectedCodingProvider.isAvailable();
983
+ if (!availability.installed && availability.installCommand) {
984
+ logger.userWarning(
985
+ `${selectedCodingProvider.displayName} is not installed.`
986
+ );
987
+ logger.userInfo(`To install it, run:`);
988
+ logger.userInfo(`${availability.installCommand}`);
989
+ const shouldInstall = await confirm({
990
+ message: "Would you like me to install it for you?",
991
+ default: true
992
+ });
993
+ if (shouldInstall) {
994
+ logger.userInfo("Installing...");
995
+ try {
996
+ await new Promise((resolve2, reject) => {
997
+ const [cmd, ...args] = availability.installCommand.split(" ");
998
+ const child = spawn(cmd, args, { stdio: "inherit" });
999
+ child.on("close", (code) => {
1000
+ if (code === 0) {
1001
+ resolve2();
1002
+ } else {
1003
+ reject(
1004
+ new Error(`Installation failed with exit code ${code}`)
1005
+ );
1006
+ }
1007
+ });
1008
+ child.on("error", reject);
1009
+ });
1010
+ availability = await selectedCodingProvider.isAvailable();
1011
+ if (availability.installed) {
1012
+ logger.userSuccess(
1013
+ `${selectedCodingProvider.displayName} installed successfully!`
1014
+ );
1015
+ } else {
1016
+ logger.userError(
1017
+ "Installation may have failed. Please try installing manually."
1018
+ );
1019
+ }
1020
+ } catch (error) {
1021
+ logger.userError(
1022
+ `Installation failed: ${error instanceof Error ? error.message : "Unknown error"}`
1023
+ );
1024
+ logger.userInfo("Please try installing manually.");
1025
+ }
1026
+ } else {
1027
+ }
1028
+ }
1029
+ }
1030
+ logger.userInfo("\u2714\uFE0E Your coding assistant will finish setup later if needed\n");
1031
+ logger.userInfo("To get your LangWatch API key, visit:");
1032
+ logger.userInfo("https://app.langwatch.ai/authorize");
1033
+ const langwatchApiKey = await password({
1034
+ message: "Enter your LangWatch API key (for prompt management, scenarios, evaluations and observability):",
1035
+ mask: "*",
1036
+ validate: validateLangWatchKey
1037
+ });
1038
+ logger.userInfo("To get your Smithery API key (optional), visit:");
1039
+ logger.userInfo("https://smithery.ai/account/api-keys");
1040
+ logger.userInfo(
1041
+ "Smithery enables your coding agent to auto-discover MCP tools to integrate with your agent."
1042
+ );
1043
+ const smitheryApiKey = await password({
1044
+ message: "Enter your Smithery API key (Optional - press Enter to skip):",
1045
+ mask: "*",
1046
+ validate: (value) => {
1047
+ if (!value || value.trim() === "") {
1048
+ return true;
1049
+ }
1050
+ if (value.length < 10) {
1051
+ return "Smithery API key must be at least 10 characters";
1052
+ }
1053
+ return true;
1054
+ }
1055
+ });
1056
+ const projectGoal = await input({
1057
+ message: "What is your agent going to do?",
1058
+ validate: validateProjectGoal
1059
+ });
1060
+ return {
1061
+ language,
1062
+ framework,
1063
+ codingAssistant,
1064
+ llmProvider,
1065
+ llmApiKey,
1066
+ llmAdditionalInputs,
1067
+ langwatchApiKey,
1068
+ smitheryApiKey: smitheryApiKey && smitheryApiKey.trim() !== "" ? smitheryApiKey : void 0,
1069
+ projectGoal
1070
+ };
1071
+ } catch (error) {
1072
+ if (error instanceof Error && error.message.includes("User force closed")) {
1073
+ logger.userWarning("Setup cancelled by user");
1074
+ process.exit(0);
1075
+ }
1076
+ throw error;
1077
+ }
1078
+ };
1079
+ var createDirectories = async ({
1080
+ projectPath,
1081
+ config
1082
+ }) => {
1083
+ const srcDir = config.framework === "mastra" ? "src" : "app";
1084
+ const directories = [
1085
+ srcDir,
1086
+ "prompts",
1087
+ "tests",
1088
+ "tests/evaluations",
1089
+ "tests/scenarios"
1090
+ ];
1091
+ for (const dir of directories) {
1092
+ await fs6.mkdir(path6.join(projectPath, dir), { recursive: true });
1093
+ }
1094
+ };
1095
+ var generateEnvFiles = async ({
1096
+ projectPath,
1097
+ config
1098
+ }) => {
1099
+ const provider = getLLMProvider({ provider: config.llmProvider });
1100
+ const envVars = provider.getEnvVariables({
1101
+ apiKey: config.llmApiKey,
1102
+ additionalInputs: config.llmAdditionalInputs
1103
+ });
1104
+ const envExampleLines = [
1105
+ "# LLM Provider API Keys",
1106
+ ...envVars.map((v) => `${v.key}=your_${v.key.toLowerCase()}_here`),
1107
+ "",
1108
+ "# LangWatch",
1109
+ "LANGWATCH_API_KEY=your_langwatch_api_key_here"
1110
+ ];
1111
+ const envExample = envExampleLines.join("\n") + "\n";
1112
+ await fs6.writeFile(path6.join(projectPath, ".env.example"), envExample);
1113
+ const envContentLines = [
1114
+ "# LLM Provider API Keys",
1115
+ ...envVars.map((v) => `${v.key}=${v.value}`),
1116
+ "",
1117
+ "# LangWatch",
1118
+ `LANGWATCH_API_KEY=${config.langwatchApiKey}`
1119
+ ];
1120
+ const envContent = envContentLines.join("\n") + "\n";
1121
+ await fs6.writeFile(path6.join(projectPath, ".env"), envContent);
1122
+ };
1123
+ var generateGitignore = async ({
1124
+ projectPath
1125
+ }) => {
1126
+ const gitignoreContent = `# Environment variables
1127
+ .env
1128
+
1129
+ # Dependencies
1130
+ node_modules/
1131
+ __pycache__/
1132
+ *.pyc
1133
+ venv/
1134
+ .venv/
1135
+
1136
+ # IDE
1137
+ .vscode/
1138
+ .idea/
1139
+ *.swp
1140
+ *.swo
1141
+
1142
+ # OS
1143
+ .DS_Store
1144
+ Thumbs.db
1145
+
1146
+ # Build outputs
1147
+ dist/
1148
+ build/
1149
+ *.egg-info/
1150
+ `;
1151
+ await fs6.writeFile(path6.join(projectPath, ".gitignore"), gitignoreContent);
1152
+ };
1153
+ var generateSamplePrompt = async ({
1154
+ projectPath
1155
+ }) => {
1156
+ const samplePromptYaml = `# Sample prompt for your agent
1157
+ model: gpt-4o
1158
+ temperature: 0.7
1159
+ messages:
1160
+ - role: system
1161
+ content: |
1162
+ You are a helpful AI assistant.
1163
+ `;
1164
+ await fs6.writeFile(
1165
+ path6.join(projectPath, "prompts", "sample_prompt.yaml"),
1166
+ samplePromptYaml
1167
+ );
1168
+ await fs6.writeFile(
1169
+ path6.join(projectPath, "prompts.json"),
1170
+ JSON.stringify({ prompts: [] }, null, 2)
1171
+ );
1172
+ };
1173
+ var generateSampleEvaluation = async ({
1174
+ projectPath,
1175
+ language
1176
+ }) => {
1177
+ const sampleEvalNotebook = {
1178
+ cells: [
1179
+ {
1180
+ cell_type: "markdown",
1181
+ metadata: {},
1182
+ source: [
1183
+ "# Sample Evaluation\n",
1184
+ "\n",
1185
+ "This notebook demonstrates how to evaluate your agent using LangWatch."
1186
+ ]
1187
+ },
1188
+ {
1189
+ cell_type: "code",
1190
+ execution_count: null,
1191
+ metadata: {},
1192
+ outputs: [],
1193
+ source: [
1194
+ "# TODO: Add your evaluation code here using LangWatch Evaluations API\n",
1195
+ "# Refer to LangWatch MCP for documentation on how to use evaluations"
1196
+ ]
1197
+ }
1198
+ ],
1199
+ metadata: {
1200
+ kernelspec: {
1201
+ display_name: language === "python" ? "Python 3" : "TypeScript",
1202
+ language,
1203
+ name: language === "python" ? "python3" : "tslab"
1204
+ }
1205
+ },
1206
+ nbformat: 4,
1207
+ nbformat_minor: 4
1208
+ };
1209
+ await fs6.writeFile(
1210
+ path6.join(projectPath, "tests", "evaluations", "example_eval.ipynb"),
1211
+ JSON.stringify(sampleEvalNotebook, null, 2)
1212
+ );
1213
+ };
1214
+ var generateSampleScenario = async ({
1215
+ projectPath,
1216
+ language
1217
+ }) => {
1218
+ const ext = language === "python" ? "py" : "ts";
1219
+ const sampleScenarioContent = language === "python" ? `"""
1220
+ Sample scenario test for your agent.
1221
+ Follow the Agent Testing Pyramid: use Scenario for end-to-end agentic tests.
1222
+ """
1223
+
1224
+ # TODO: Add your scenario tests here
1225
+ # Refer to https://scenario.langwatch.ai/ for documentation
1226
+ ` : `/**
1227
+ * Sample scenario test for your agent.
1228
+ * Follow the Agent Testing Pyramid: use Scenario for end-to-end agentic tests.
1229
+ */
1230
+
1231
+ // TODO: Add your scenario tests here
1232
+ // Refer to https://scenario.langwatch.ai/ for documentation
1233
+ `;
1234
+ await fs6.writeFile(
1235
+ path6.join(
1236
+ projectPath,
1237
+ "tests",
1238
+ "scenarios",
1239
+ `example_scenario.test.${ext}`
1240
+ ),
1241
+ sampleScenarioContent
1242
+ );
1243
+ };
1244
+ var generateMainEntryPoint = async ({
1245
+ projectPath,
1246
+ config
1247
+ }) => {
1248
+ const srcDir = config.framework === "mastra" ? "src" : "app";
1249
+ const mainFileContent = config.language === "python" ? `"""
1250
+ Main entry point for your agent.
1251
+ """
1252
+
1253
+ def main():
1254
+ print("Welcome to your agent!")
1255
+ # TODO: Implement your agent logic here
1256
+
1257
+ if __name__ == "__main__":
1258
+ main()
1259
+ ` : `/**
1260
+ * Main entry point for your agent.
1261
+ */
1262
+
1263
+ const main = () => {
1264
+ console.log("Welcome to your agent!");
1265
+ // TODO: Implement your agent logic here
1266
+ };
1267
+
1268
+ main();
1269
+ `;
1270
+ const mainFileName = config.language === "python" ? "main.py" : "index.ts";
1271
+ await fs6.writeFile(
1272
+ path6.join(projectPath, srcDir, mainFileName),
1273
+ mainFileContent
1274
+ );
1275
+ };
1276
+
1277
+ // src/project-scaffolding/create-project-structure.ts
1278
+ var createProjectStructure = async ({
1279
+ projectPath,
1280
+ config
1281
+ }) => {
1282
+ await createDirectories({ projectPath, config });
1283
+ await generateEnvFiles({
1284
+ projectPath,
1285
+ config
1286
+ });
1287
+ await generateGitignore({ projectPath });
1288
+ await generateSamplePrompt({ projectPath });
1289
+ await generateSampleEvaluation({ projectPath, language: config.language });
1290
+ await generateSampleScenario({ projectPath, language: config.language });
1291
+ await generateMainEntryPoint({ projectPath, config });
1292
+ };
1293
+
1294
+ // src/documentation/sections/overview-section.ts
1295
+ var buildOverviewSection = ({ config }) => {
1296
+ const { projectGoal, framework, language } = config;
1297
+ return `# Agent Development Guidelines
1298
+
1299
+ ## Project Overview
1300
+
1301
+ **Goal:** ${projectGoal}
1302
+
1303
+ **Framework:** ${framework === "agno" ? "Agno" : "Mastra"}
1304
+ **Language:** ${language === "python" ? "Python" : "TypeScript"}
1305
+
1306
+ This project follows LangWatch best practices for building production-ready AI agents.
1307
+
1308
+ ---
1309
+ `;
1310
+ };
1311
+
1312
+ // src/documentation/sections/principles-section.ts
1313
+ var buildPrinciplesSection = () => {
1314
+ return `## Core Principles
1315
+
1316
+ ### 1. Scenario Agent Testing
1317
+
1318
+ Scenario allows for end-to-end validation of multi-turn conversations and real-world scenarios, most agent functionality should be tested with scenarios
1319
+
1320
+ **CRITICAL**: Every new agent feature MUST be tested with Scenario tests before considering it complete.
1321
+
1322
+ - Write simulation tests for multi-turn conversations
1323
+ - Validate edge cases
1324
+ - Ensure business value is delivered
1325
+ - Test different conversation paths
1326
+
1327
+ Best practices:
1328
+ - NEVER check for regex or word matches in the agent's response, use judge criteria instead
1329
+ - Use functions on the Scenario scripts for things that can be checked deterministically (tool calls, database entries, etc) instead of relying on the judge
1330
+ - For the rest, use the judge criteria to check if agent is reaching the desired goal and
1331
+ - When broken, run on single scenario at a time to debug and iterate faster, not the whole suite
1332
+ - Write as few scenarios as possible, try to cover more ground with few scenarios, as they are heavy to run
1333
+ - If user made 1 request, just 1 scenario might be enough, run it at the end of the implementation to check if it works
1334
+ - ALWAYS consult the Scenario docs on how to write scenarios, do not assume the syntax
1335
+
1336
+ ### 2. Prompt Management
1337
+
1338
+ **ALWAYS** use LangWatch Prompt CLI for managing prompts:
1339
+
1340
+ - Use the LangWatch MCP to learn about prompt management, search for Prompt CLI docs
1341
+ - Never hardcode prompts in your application code
1342
+ - Store all prompts in the \`prompts/\` directory as YAML files, use "langwatch prompt create <name>" to create a new prompt
1343
+ - Run \`langwatch prompt sync\` after changing a prompt to update the registry
1344
+
1345
+ Example prompt structure:
1346
+ \`\`\`yaml
1347
+ # prompts/my_prompt.yaml
1348
+ model: gpt-4o
1349
+ temperature: 0.7
1350
+ messages:
1351
+ - role: system
1352
+ content: |
1353
+ Your system prompt here
1354
+ - role: user
1355
+ content: |
1356
+ {{ user_input }}
1357
+ \`\`\`
1358
+
1359
+ DO NOT use hardcoded prompts in your application code, example:
1360
+
1361
+ BAD:
1362
+ \`\`\`
1363
+ Agent(prompt="You are a helpful assistant.")
1364
+ \`\`\`
1365
+
1366
+ GOOD:
1367
+ \`\`\`python
1368
+ import langwatch
1369
+
1370
+ prompt = langwatch.prompts.get("my_prompt")
1371
+ Agent(prompt=prompt.prompt)
1372
+ \`\`\`
1373
+
1374
+ \`\`\`typescript
1375
+ import { LangWatch } from "langwatch";
1376
+
1377
+ const langwatch = new LangWatch({
1378
+ apiKey: process.env.LANGWATCH_API_KEY
1379
+ });
1380
+
1381
+ const prompt = await langwatch.prompts.get("my_prompt")
1382
+ Agent(prompt=prompt!.prompt)
1383
+ \`\`\`
1384
+
1385
+ Prompt fetching is very reliable when using the prompts cli because the files are local (double check they were created with the CLI and are listed on the prompts.json file).
1386
+ DO NOT add try/catch around it and DO NOT duplicate the prompt here as a fallback
1387
+
1388
+ Explore the prompt management get started and data model docs if you need more advanced usages such as compiled prompts with variables or messages list.
1389
+
1390
+ ### 3. Evaluations for specific cases
1391
+
1392
+ Only write evaluations for specific cases:
1393
+
1394
+ - When a RAG is implemented, so we can evaluate the accuracy given many sample queries (using an LLM to compare expected with generated outputs)
1395
+ - For classification tasks, e.g. categorization, routing, simple true/false detection, etc
1396
+ - When the user asks and you are sure an agent scenario wouldn't test the behaviour better
1397
+
1398
+ This is because evaluations are good for things when you have a lot of examples, with avery clear
1399
+ definition of what is correct and what is not (that is, you can just compare expected with generated)
1400
+ and you are looking for single input/output pairs. This is not the case for multi-turn agent flows.
1401
+
1402
+ Create evaluations in Jupyter notebooks under \`tests/evaluations/\`:
1403
+
1404
+ - Generate csv example datasets yourself to be read by pandas with plenty of examples
1405
+ - Use LangWatch Evaluations API to create evaluation notebooks and track the evaluation results
1406
+ - Use either a simple == comparison or a direct (e.g. openai) LLM call to compare expected with generated if possible and not requested otherwise
1407
+
1408
+ ### 4. General good practices
1409
+
1410
+ - ALWAYS use the package manager cli commands to init, add and install new dependencies, DO NOT guess package versions, DO NOT add them to the dependencies file by hand.
1411
+ - When setting up, remember to load dotenv for the tests so env vars are available
1412
+ - Double check the guidelines on AGENTS.md after the end of the implementation.
1413
+
1414
+ ---
1415
+ `;
1416
+ };
1417
+
1418
+ // src/documentation/sections/workflow-section.ts
1419
+ var buildWorkflowSection = ({ config }) => {
1420
+ const srcDir = config.framework === "mastra" ? "src" : "app";
1421
+ const ext = config.language === "python" ? "py" : "ts";
1422
+ return `## Project Structure
1423
+
1424
+ This project follows a standardized structure for production-ready agents:
1425
+
1426
+ \`\`\`
1427
+ |__ ${srcDir}/ # Main application code
1428
+ |__ prompts/ # Versioned prompt files (YAML)
1429
+ |_____ *.yaml
1430
+ |__ tests/
1431
+ |_____ evaluations/ # Jupyter notebooks for component evaluation
1432
+ |________ *.ipynb
1433
+ |_____ scenarios/ # End-to-end scenario tests
1434
+ |________ *.test.${ext}
1435
+ |__ prompts.json # Prompt registry
1436
+ |__ .env # Environment variables (never commit!)
1437
+ \`\`\`
1438
+
1439
+ ---
1440
+
1441
+ ## Development Workflow
1442
+
1443
+ ### When Starting a New Feature:
1444
+
1445
+ 1. **Understand Requirements**: Clarify what the agent should do
1446
+ 2. **Design the Approach**: Plan which components you'll need
1447
+ 3. **Implement with Prompts**: Use LangWatch Prompt CLI to create/manage prompts
1448
+ 4. **Write Unit Tests**: Test deterministic components
1449
+ 5. **Create Evaluations**: Build evaluation notebooks for probabilistic components
1450
+ 6. **Write Scenario Tests**: Create end-to-end tests using Scenario
1451
+ 7. **Run Tests**: Verify everything works before moving on
1452
+
1453
+ ### Always:
1454
+
1455
+ - \u2705 Version control your prompts
1456
+ - \u2705 Write tests for new features
1457
+ - \u2705 Use LangWatch MCP to learn best practices
1458
+ - \u2705 Follow the Agent Testing Pyramid
1459
+ - \u2705 Document your agent's capabilities
1460
+
1461
+ ### Never:
1462
+
1463
+ - \u274C Hardcode prompts in application code
1464
+ - \u274C Skip testing new features
1465
+ - \u274C Commit API keys or sensitive data
1466
+ - \u274C Optimize without measuring (use evaluations first)
1467
+
1468
+ ---
1469
+
1470
+ ## Using LangWatch MCP
1471
+
1472
+ The LangWatch MCP server provides expert guidance on:
1473
+
1474
+ - Prompt management with Prompt CLI
1475
+ - Writing Scenario tests
1476
+ - Creating evaluations
1477
+ - Best practices for agent development
1478
+
1479
+ **How to use it:**
1480
+ Simply ask your coding assistant questions like:
1481
+ - "How do I use the LangWatch Prompt CLI?"
1482
+ - "Show me how to write a Scenario test"
1483
+ - "How do I create an evaluation for my RAG system?"
1484
+
1485
+ The MCP will provide up-to-date documentation and examples.
1486
+
1487
+ ---
1488
+
1489
+ ## Getting Started
1490
+
1491
+ 1. **Set up your environment**: Copy \`.env.example\` to \`.env\` and fill in your API keys
1492
+ 2. **Learn the tools**: Ask the LangWatch MCP about prompt management and testing
1493
+ 3. **Start building**: Implement your agent in the \`${srcDir}/\` directory
1494
+ 4. **Write tests**: Create scenario tests for your agent's capabilities
1495
+ 5. **Iterate**: Use evaluations to improve your agent's performance
1496
+
1497
+ ---
1498
+
1499
+ ## Resources
1500
+
1501
+ - **Scenario Documentation**: https://scenario.langwatch.ai/
1502
+ - **Agent Testing Pyramid**: https://scenario.langwatch.ai/best-practices/the-agent-testing-pyramid
1503
+ - **LangWatch Dashboard**: https://app.langwatch.ai/
1504
+ ${config.framework === "agno" ? "- **Agno Documentation**: https://docs.agno.com/" : "- **Mastra Documentation**: Use the Mastra MCP for up-to-date docs"}
1505
+
1506
+ ---
1507
+
1508
+ Remember: Building production-ready agents means combining great AI capabilities with solid software engineering practices. Follow these guidelines to create agents that are reliable, testable, and maintainable.
1509
+ `;
1510
+ };
1511
+
1512
+ // src/builders/agents-guide-builder.ts
1513
+ var buildAgentsGuide = async ({
1514
+ projectPath,
1515
+ config
1516
+ }) => {
1517
+ const frameworkProvider = getFrameworkProvider({
1518
+ framework: config.framework
1519
+ });
1520
+ const frameworkKnowledge = frameworkProvider.getKnowledge();
1521
+ const content = [
1522
+ buildOverviewSection({ config }),
1523
+ buildPrinciplesSection(),
1524
+ frameworkKnowledge.agentsGuideSection,
1525
+ buildWorkflowSection({ config })
1526
+ ].join("\n");
1527
+ await fs6.writeFile(path6.join(projectPath, "AGENTS.md"), content);
1528
+ };
1529
+
1530
+ // src/builders/mcp-config-builder.ts
1531
+ var buildMCPConfig = async ({
1532
+ projectPath,
1533
+ config
1534
+ }) => {
1535
+ const mcpConfig = {
1536
+ mcpServers: {}
1537
+ };
1538
+ mcpConfig.mcpServers.langwatch = {
1539
+ command: "npx",
1540
+ args: ["-y", "@langwatch/mcp-server"]
1541
+ };
1542
+ if (config.smitheryApiKey) {
1543
+ mcpConfig.mcpServers.toolbox = {
1544
+ command: "npx",
1545
+ args: [
1546
+ "-y",
1547
+ "@smithery/cli@latest",
1548
+ "run",
1549
+ "@smithery/toolbox",
1550
+ "--key",
1551
+ config.smitheryApiKey
1552
+ ]
1553
+ };
1554
+ }
1555
+ const frameworkProvider = getFrameworkProvider({
1556
+ framework: config.framework
1557
+ });
1558
+ const frameworkMCP = frameworkProvider.getMCPConfig?.();
1559
+ if (frameworkMCP) {
1560
+ mcpConfig.mcpServers[frameworkProvider.id] = frameworkMCP;
1561
+ }
1562
+ const assistantProvider = getCodingAssistantProvider({
1563
+ assistant: config.codingAssistant
1564
+ });
1565
+ await assistantProvider.writeMCPConfig({ projectPath, config: mcpConfig });
1566
+ };
1567
+
1568
+ // src/providers/languages/python/knowledge.ts
1569
+ var getKnowledge3 = () => ({
1570
+ setupInstructions: "Python with uv + pytest (install uv for them if they don't have it)",
1571
+ sourceExtensions: [".py"],
1572
+ testFramework: "pytest"
1573
+ });
1574
+
1575
+ // src/providers/languages/python/index.ts
1576
+ var PythonLanguageProvider = {
1577
+ id: "python",
1578
+ displayName: "Python",
1579
+ getKnowledge: getKnowledge3
1580
+ };
1581
+
1582
+ // src/providers/languages/typescript/knowledge.ts
1583
+ var getKnowledge4 = () => ({
1584
+ setupInstructions: "TypeScript with pnpm + vitest (install pnpm for them if they don't have it)",
1585
+ sourceExtensions: [".ts", ".tsx"],
1586
+ testFramework: "vitest"
1587
+ });
1588
+
1589
+ // src/providers/languages/typescript/index.ts
1590
+ var TypeScriptLanguageProvider = {
1591
+ id: "typescript",
1592
+ displayName: "TypeScript",
1593
+ getKnowledge: getKnowledge4
1594
+ };
1595
+
1596
+ // src/providers/languages/index.ts
1597
+ var PROVIDERS4 = {
1598
+ typescript: TypeScriptLanguageProvider,
1599
+ python: PythonLanguageProvider
1600
+ };
1601
+ var getLanguageProvider = ({
1602
+ language
1603
+ }) => {
1604
+ const provider = PROVIDERS4[language];
1605
+ if (!provider) {
1606
+ throw new Error(`Language provider not found: ${language}`);
1607
+ }
1608
+ return provider;
1609
+ };
1610
+
1611
+ // src/assistant-kickoff/build-initial-prompt.ts
1612
+ var buildInitialPrompt = ({
1613
+ config
1614
+ }) => {
1615
+ const frameworkProvider = getFrameworkProvider({
1616
+ framework: config.framework
1617
+ });
1618
+ const languageProvider = getLanguageProvider({ language: config.language });
1619
+ const frameworkKnowledge = frameworkProvider.getKnowledge();
1620
+ const languageKnowledge = languageProvider.getKnowledge();
1621
+ const instructions = `You are an expert AI agent developer. This project has been set up with Better Agents best practices.
1622
+
1623
+ First steps:
1624
+ 1. Read and understand the AGENTS.md file - it contains all the guidelines for this project
1625
+ 2. Update the AGENTS.md with specific details about what this project does
1626
+ 3. Create a comprehensive README.md explaining the project, setup, and usage
1627
+ 4. Set up the ${languageKnowledge.setupInstructions}
1628
+ 5. ${frameworkKnowledge.toolingInstructions}
1629
+ 6. Execute any installation steps needed yourself, for the library dependencies, the CLI tools, etc
1630
+ 7. Use the LangWatch MCP to learn about prompt management and testing
1631
+ 8. Start implementing the core agent functionality
1632
+ 9. Instrument the agent with LangWatch
1633
+ 10. Use Scenario tests to ensure the agent is working as expected, integrate with the agent and consider it done only when all scenarios pass, check scenario docs on how to implement
1634
+ 11. If available from the framework, tell the user how to open a dev server give them the url they will be able to access so they can play with the agent themselves, don't run it for them
1635
+
1636
+
1637
+ Remember:
1638
+ - The LLM and LangWatch API keys are already available in the .env file, you don't need to set them up
1639
+ - ALWAYS use LangWatch Prompt CLI for prompts (ask the MCP how)
1640
+ - ALWAYS write Scenario tests for new features (ask the MCP how)
1641
+ - DO NOT test it "manually", always use the Scenario tests instead, do not open the dev server for the user, let them do it themselves only at the end of the implementation with everything working
1642
+ - Test everything before considering it done`;
1643
+ return `${instructions}
1644
+
1645
+ Agent Goal: ${config.projectGoal}`;
1646
+ };
1647
+
1648
+ // src/assistant-kickoff/kickoff-assistant.ts
1649
+ var kickoffAssistant = async ({
1650
+ projectPath,
1651
+ config
1652
+ }) => {
1653
+ const prompt = buildInitialPrompt({ config });
1654
+ const provider = getCodingAssistantProvider({ assistant: config.codingAssistant });
1655
+ logger.userSuccess("Project setup complete!");
1656
+ logger.userInfo("Initial prompt:");
1657
+ logger.userInfo(`"${prompt}"`);
1658
+ logger.userInfo(`Project location: ${projectPath}`);
1659
+ await provider.launch({ projectPath, prompt });
1660
+ };
1661
+
1662
+ // src/commands/init.ts
1663
+ var showBanner = () => {
1664
+ const asciiArt = `
1665
+ \u2597\u2584\u2584\u2596 \u2597\u2584\u2584\u2584\u2596\u2597\u2584\u2584\u2584\u2596\u2597\u2584\u2584\u2584\u2596\u2597\u2584\u2584\u2584\u2596\u2597\u2584\u2584\u2596
1666
+ \u2590\u258C \u2590\u258C\u2590\u258C \u2588 \u2588 \u2590\u258C \u2590\u258C \u2590\u258C
1667
+ \u2590\u259B\u2580\u259A\u2596\u2590\u259B\u2580\u2580\u2598 \u2588 \u2588 \u2590\u259B\u2580\u2580\u2598\u2590\u259B\u2580\u259A\u2596
1668
+ \u2590\u2599\u2584\u259E\u2598\u2590\u2599\u2584\u2584\u2596 \u2588 \u2588 \u2590\u2599\u2584\u2584\u2596\u2590\u258C \u2590\u258C
1669
+
1670
+ \u2597\u2584\u2596 \u2597\u2584\u2584\u2596\u2597\u2584\u2584\u2584\u2596\u2597\u2596 \u2597\u2596\u2597\u2584\u2584\u2584\u2596\u2597\u2584\u2584\u2596
1671
+ \u2590\u258C \u2590\u258C\u2590\u258C \u2590\u258C \u2590\u259B\u259A\u2596\u2590\u258C \u2588 \u2590\u258C
1672
+ \u2590\u259B\u2580\u259C\u258C\u2590\u258C\u259D\u259C\u258C\u2590\u259B\u2580\u2580\u2598\u2590\u258C \u259D\u259C\u258C \u2588 \u259D\u2580\u259A\u2596
1673
+ \u2590\u258C \u2590\u258C\u259D\u259A\u2584\u259E\u2598\u2590\u2599\u2584\u2584\u2596\u2590\u258C \u2590\u258C \u2588 \u2597\u2584\u2584\u259E\u2598
1674
+
1675
+ `;
1676
+ console.log();
1677
+ console.log(asciiArt);
1678
+ console.log();
1679
+ };
1680
+ var initCommand = async (targetPath, debug = false) => {
1681
+ if (debug) {
1682
+ process.env.SUPERAGENTS_DEBUG = "true";
1683
+ }
1684
+ const logger2 = new LoggerFacade();
1685
+ try {
1686
+ showBanner();
1687
+ const configTimer = logger2.startTimer("config-collection");
1688
+ const config = await collectConfig();
1689
+ configTimer();
1690
+ const absolutePath = path6.resolve(process.cwd(), targetPath);
1691
+ const projectLogger = new LoggerFacade(absolutePath);
1692
+ const spinner = projectLogger.startSpinner("Setting up your agent project...");
1693
+ try {
1694
+ projectLogger.info("init-started", {
1695
+ targetPath,
1696
+ absolutePath,
1697
+ config: {
1698
+ language: config.language,
1699
+ framework: config.framework,
1700
+ codingAssistant: config.codingAssistant,
1701
+ llmProvider: config.llmProvider,
1702
+ projectGoal: config.projectGoal.substring(0, 100) + "..."
1703
+ // Truncate for logging
1704
+ }
1705
+ });
1706
+ const mkdirTimer = projectLogger.startTimer("directory-creation");
1707
+ await fs6.mkdir(absolutePath, { recursive: true });
1708
+ mkdirTimer();
1709
+ const structureTimer = projectLogger.startTimer("project-structure");
1710
+ await createProjectStructure({ projectPath: absolutePath, config });
1711
+ structureTimer();
1712
+ spinner.text = "Project structure created";
1713
+ const frameworkTimer = projectLogger.startTimer("framework-setup");
1714
+ const frameworkProvider = getFrameworkProvider({
1715
+ framework: config.framework
1716
+ });
1717
+ await frameworkProvider.setup({ projectPath: absolutePath });
1718
+ frameworkTimer();
1719
+ spinner.text = "Framework configuration set up";
1720
+ const mcpTimer = projectLogger.startTimer("mcp-config");
1721
+ await buildMCPConfig({ projectPath: absolutePath, config });
1722
+ mcpTimer();
1723
+ spinner.text = "MCP configuration set up";
1724
+ const agentsTimer = projectLogger.startTimer("agents-guide");
1725
+ await buildAgentsGuide({ projectPath: absolutePath, config });
1726
+ agentsTimer();
1727
+ spinner.text = "AGENTS.md generated";
1728
+ spinner.succeed("Project setup complete!");
1729
+ projectLogger.userSuccess("Your agent project is ready!");
1730
+ projectLogger.userInfo(`Project location: ${absolutePath}`);
1731
+ projectLogger.info("init-completed", {
1732
+ projectPath: absolutePath,
1733
+ success: true
1734
+ });
1735
+ await kickoffAssistant({ projectPath: absolutePath, config });
1736
+ } catch (error) {
1737
+ spinner.fail("Failed to set up project");
1738
+ projectLogger.error(error, {
1739
+ step: "project-setup",
1740
+ projectPath: absolutePath
1741
+ });
1742
+ throw error;
1743
+ }
1744
+ } catch (error) {
1745
+ if (error instanceof Error) {
1746
+ logger2.userError(`Error: ${error.message}`);
1747
+ } else {
1748
+ logger2.userError("An unexpected error occurred");
1749
+ }
1750
+ process.exit(1);
1751
+ }
1752
+ };
1753
+
1754
+ // src/index.ts
1755
+ var program = new Command();
1756
+ program.name("better-agents").description("CLI for kicking off production-ready agent projects with LangWatch best practices").version("0.1.0").option("-d, --debug", "Enable debug logging with structured JSON output");
1757
+ program.command("init").description("Initialize a new agent project").argument("[path]", "Path to initialize the project (defaults to current directory)", ".").action((path16, options) => {
1758
+ const debug = options.parent?.debug || false;
1759
+ return initCommand(path16, debug);
1760
+ });
1761
+ program.parse();
1762
+ //# sourceMappingURL=index.js.map
1763
+ //# sourceMappingURL=index.js.map