wiggum-cli 0.2.6 → 0.3.1

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.
Files changed (89) hide show
  1. package/README.md +73 -60
  2. package/dist/ai/agents/codebase-analyst.d.ts +11 -0
  3. package/dist/ai/agents/codebase-analyst.d.ts.map +1 -0
  4. package/dist/ai/agents/codebase-analyst.js +146 -0
  5. package/dist/ai/agents/codebase-analyst.js.map +1 -0
  6. package/dist/ai/agents/index.d.ts +16 -0
  7. package/dist/ai/agents/index.d.ts.map +1 -0
  8. package/dist/ai/agents/index.js +85 -0
  9. package/dist/ai/agents/index.js.map +1 -0
  10. package/dist/ai/agents/orchestrator.d.ts +15 -0
  11. package/dist/ai/agents/orchestrator.d.ts.map +1 -0
  12. package/dist/ai/agents/orchestrator.js +181 -0
  13. package/dist/ai/agents/orchestrator.js.map +1 -0
  14. package/dist/ai/agents/stack-researcher.d.ts +15 -0
  15. package/dist/ai/agents/stack-researcher.d.ts.map +1 -0
  16. package/dist/ai/agents/stack-researcher.js +269 -0
  17. package/dist/ai/agents/stack-researcher.js.map +1 -0
  18. package/dist/ai/agents/types.d.ts +123 -0
  19. package/dist/ai/agents/types.d.ts.map +1 -0
  20. package/dist/ai/agents/types.js +6 -0
  21. package/dist/ai/agents/types.js.map +1 -0
  22. package/dist/ai/enhancer.d.ts +39 -1
  23. package/dist/ai/enhancer.d.ts.map +1 -1
  24. package/dist/ai/enhancer.js +78 -36
  25. package/dist/ai/enhancer.js.map +1 -1
  26. package/dist/ai/index.d.ts +4 -2
  27. package/dist/ai/index.d.ts.map +1 -1
  28. package/dist/ai/index.js +5 -1
  29. package/dist/ai/index.js.map +1 -1
  30. package/dist/ai/prompts.d.ts +2 -2
  31. package/dist/ai/prompts.d.ts.map +1 -1
  32. package/dist/ai/prompts.js +66 -4
  33. package/dist/ai/prompts.js.map +1 -1
  34. package/dist/ai/providers.d.ts +28 -0
  35. package/dist/ai/providers.d.ts.map +1 -1
  36. package/dist/ai/providers.js +40 -0
  37. package/dist/ai/providers.js.map +1 -1
  38. package/dist/ai/tools/context7.d.ts +34 -0
  39. package/dist/ai/tools/context7.d.ts.map +1 -0
  40. package/dist/ai/tools/context7.js +135 -0
  41. package/dist/ai/tools/context7.js.map +1 -0
  42. package/dist/ai/tools/index.d.ts +7 -0
  43. package/dist/ai/tools/index.d.ts.map +1 -0
  44. package/dist/ai/tools/index.js +7 -0
  45. package/dist/ai/tools/index.js.map +1 -0
  46. package/dist/ai/tools/tavily.d.ts +27 -0
  47. package/dist/ai/tools/tavily.d.ts.map +1 -0
  48. package/dist/ai/tools/tavily.js +75 -0
  49. package/dist/ai/tools/tavily.js.map +1 -0
  50. package/dist/cli.d.ts.map +1 -1
  51. package/dist/cli.js +14 -12
  52. package/dist/cli.js.map +1 -1
  53. package/dist/commands/init.d.ts +2 -5
  54. package/dist/commands/init.d.ts.map +1 -1
  55. package/dist/commands/init.js +233 -154
  56. package/dist/commands/init.js.map +1 -1
  57. package/dist/utils/colors.d.ts.map +1 -1
  58. package/dist/utils/colors.js +10 -3
  59. package/dist/utils/colors.js.map +1 -1
  60. package/dist/utils/header.d.ts +1 -1
  61. package/dist/utils/header.js +3 -3
  62. package/dist/utils/header.js.map +1 -1
  63. package/dist/utils/json-repair.d.ts +14 -0
  64. package/dist/utils/json-repair.d.ts.map +1 -0
  65. package/dist/utils/json-repair.js +103 -0
  66. package/dist/utils/json-repair.js.map +1 -0
  67. package/dist/utils/tracing.d.ts +25 -0
  68. package/dist/utils/tracing.d.ts.map +1 -0
  69. package/dist/utils/tracing.js +64 -0
  70. package/dist/utils/tracing.js.map +1 -0
  71. package/package.json +5 -3
  72. package/src/ai/agents/codebase-analyst.ts +169 -0
  73. package/src/ai/agents/index.ts +147 -0
  74. package/src/ai/agents/orchestrator.ts +218 -0
  75. package/src/ai/agents/stack-researcher.ts +294 -0
  76. package/src/ai/agents/types.ts +132 -0
  77. package/src/ai/enhancer.ts +128 -38
  78. package/src/ai/index.ts +31 -1
  79. package/src/ai/prompts.ts +67 -4
  80. package/src/ai/providers.ts +48 -0
  81. package/src/ai/tools/context7.ts +167 -0
  82. package/src/ai/tools/index.ts +17 -0
  83. package/src/ai/tools/tavily.ts +101 -0
  84. package/src/cli.ts +14 -12
  85. package/src/commands/init.ts +278 -173
  86. package/src/utils/colors.ts +11 -3
  87. package/src/utils/header.ts +3 -3
  88. package/src/utils/json-repair.ts +113 -0
  89. package/src/utils/tracing.ts +76 -0
@@ -3,12 +3,15 @@
3
3
  * Uses AI to analyze the codebase for deeper insights
4
4
  */
5
5
 
6
- import { generateText, stepCountIs } from 'ai';
6
+ import { stepCountIs } from 'ai';
7
7
  import type { ScanResult, DetectedStack, DetectionResult } from '../scanner/types.js';
8
8
  import { getModel, type AIProvider, hasApiKey, getApiKeyEnvVar, isReasoningModel } from './providers.js';
9
9
  import { SYSTEM_PROMPT, SYSTEM_PROMPT_AGENTIC, createAnalysisPrompt } from './prompts.js';
10
10
  import { createExplorationTools } from './tools.js';
11
+ import { runMultiAgentAnalysis, type MultiAgentAnalysis } from './agents/index.js';
11
12
  import { logger } from '../utils/logger.js';
13
+ import { parseJsonSafe } from '../utils/json-repair.js';
14
+ import { getTracedAI, traced } from '../utils/tracing.js';
12
15
 
13
16
  /**
14
17
  * Project context from AI analysis - key structure information
@@ -44,6 +47,32 @@ export interface McpRecommendations {
44
47
  recommended?: string[];
45
48
  }
46
49
 
50
+ /**
51
+ * Technology-specific testing and debugging tools
52
+ */
53
+ export interface TechnologyTools {
54
+ /** Testing commands specific to the detected technologies */
55
+ testing?: string[];
56
+ /** Debugging/inspection tools for the stack */
57
+ debugging?: string[];
58
+ /** Linting/validation tools beyond the standard ones */
59
+ validation?: string[];
60
+ }
61
+
62
+ /**
63
+ * Technology-specific best practices
64
+ */
65
+ export interface TechnologyPractices {
66
+ /** The primary project type (e.g., "MCP Server", "REST API", "React SPA") */
67
+ projectType?: string;
68
+ /** Practices specific to the detected technologies */
69
+ practices?: string[];
70
+ /** Anti-patterns to avoid for this stack */
71
+ antiPatterns?: string[];
72
+ /** Links to relevant documentation (optional) */
73
+ documentationHints?: string[];
74
+ }
75
+
47
76
  /**
48
77
  * AI analysis result - focused on actionable outputs
49
78
  */
@@ -58,6 +87,10 @@ export interface AIAnalysisResult {
58
87
  mcpServers?: McpRecommendations;
59
88
  /** Additional technologies that may have been missed */
60
89
  possibleMissedTechnologies?: string[];
90
+ /** Technology-specific tools for testing/debugging */
91
+ technologyTools?: TechnologyTools;
92
+ /** Technology-specific best practices based on detected stack */
93
+ technologyPractices?: TechnologyPractices;
61
94
  }
62
95
 
63
96
  /**
@@ -79,6 +112,10 @@ export interface EnhancerOptions {
79
112
  verbose?: boolean;
80
113
  /** Use agentic mode with tools for deeper codebase exploration */
81
114
  agentic?: boolean;
115
+ /** Tavily API key for web search (optional) */
116
+ tavilyApiKey?: string;
117
+ /** Context7 API key for documentation lookup (optional) */
118
+ context7ApiKey?: string;
82
119
  }
83
120
 
84
121
  /**
@@ -92,46 +129,26 @@ function parseAIResponse(text: string, verbose: boolean = false): AIAnalysisResu
92
129
  return null;
93
130
  }
94
131
 
95
- try {
96
- // Try to extract JSON from the response
97
- // The AI might wrap it in markdown code blocks
98
- let jsonText = text;
99
-
100
- // Remove markdown code blocks if present
101
- const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
102
- if (jsonMatch) {
103
- jsonText = jsonMatch[1];
104
- }
105
-
106
- // Try to find JSON object - use greedy match for the outermost braces
107
- // This handles cases where there's text before/after the JSON
108
- const objectMatch = jsonText.match(/\{[\s\S]*\}/);
109
- if (objectMatch) {
110
- jsonText = objectMatch[0];
111
- }
112
-
113
- // Try to parse JSON
114
- const result = JSON.parse(jsonText) as AIAnalysisResult;
132
+ // Use safe JSON parser with repair capabilities
133
+ const result = parseJsonSafe<AIAnalysisResult>(text);
115
134
 
116
- // Validate that we got the expected structure
117
- if (!result || typeof result !== 'object') {
118
- if (verbose) {
119
- logger.warn('AI response parsed but is not an object');
120
- }
121
- return null;
135
+ if (!result) {
136
+ if (verbose) {
137
+ logger.warn('Failed to parse AI response as JSON');
138
+ logger.warn(`Response preview: ${text.substring(0, 500)}...`);
122
139
  }
140
+ return null;
141
+ }
123
142
 
124
- return result;
125
- } catch (error) {
143
+ // Validate that we got the expected structure
144
+ if (typeof result !== 'object') {
126
145
  if (verbose) {
127
- logger.warn(`Failed to parse AI response as JSON: ${error instanceof Error ? error.message : String(error)}`);
128
- // Log first 500 chars of response for debugging
129
- logger.warn(`Response preview: ${text.substring(0, 500)}...`);
130
- } else {
131
- logger.warn('Failed to parse AI response as JSON');
146
+ logger.warn('AI response parsed but is not an object');
132
147
  }
133
148
  return null;
134
149
  }
150
+
151
+ return result;
135
152
  }
136
153
 
137
154
  /**
@@ -173,12 +190,16 @@ export class AIEnhancer {
173
190
  private model?: string;
174
191
  private verbose: boolean;
175
192
  private agentic: boolean;
193
+ private tavilyApiKey?: string;
194
+ private context7ApiKey?: string;
176
195
 
177
196
  constructor(options: EnhancerOptions = {}) {
178
197
  this.provider = options.provider || 'anthropic';
179
198
  this.model = options.model;
180
199
  this.verbose = options.verbose || false;
181
200
  this.agentic = options.agentic || false;
201
+ this.tavilyApiKey = options.tavilyApiKey;
202
+ this.context7ApiKey = options.context7ApiKey;
182
203
  }
183
204
 
184
205
  /**
@@ -271,6 +292,7 @@ export class AIEnhancer {
271
292
  scanResult: ScanResult
272
293
  ): Promise<AIAnalysisResult | null> {
273
294
  const prompt = createAnalysisPrompt(scanResult);
295
+ const { generateText } = getTracedAI();
274
296
 
275
297
  const { text } = await generateText({
276
298
  model,
@@ -279,20 +301,54 @@ export class AIEnhancer {
279
301
  maxOutputTokens: 2000,
280
302
  // Reasoning models don't support temperature
281
303
  ...(isReasoningModel(modelId) ? {} : { temperature: 0.3 }),
304
+ experimental_telemetry: {
305
+ isEnabled: true,
306
+ metadata: { phase: 'simple-analysis', projectRoot: scanResult.projectRoot },
307
+ },
282
308
  });
283
309
 
284
310
  return parseAIResponse(text);
285
311
  }
286
312
 
287
313
  /**
288
- * Agentic enhancement mode - use tools to explore codebase
314
+ * Agentic enhancement mode - use multi-agent system to explore codebase
289
315
  */
290
316
  private async enhanceAgentic(
291
317
  model: ReturnType<typeof getModel>['model'],
292
318
  modelId: string,
293
319
  scanResult: ScanResult
320
+ ): Promise<AIAnalysisResult | null> {
321
+ // Use the multi-agent system for deeper analysis
322
+ const multiAgentResult = await runMultiAgentAnalysis(
323
+ model,
324
+ modelId,
325
+ scanResult,
326
+ {
327
+ tavilyApiKey: this.tavilyApiKey,
328
+ context7ApiKey: this.context7ApiKey,
329
+ verbose: this.verbose,
330
+ }
331
+ );
332
+
333
+ if (!multiAgentResult) {
334
+ // Fall back to simple agentic mode
335
+ return this.enhanceLegacyAgentic(model, modelId, scanResult);
336
+ }
337
+
338
+ // Convert MultiAgentAnalysis to AIAnalysisResult for backward compatibility
339
+ return convertMultiAgentToAIAnalysis(multiAgentResult);
340
+ }
341
+
342
+ /**
343
+ * Legacy agentic mode (fallback when multi-agent fails)
344
+ */
345
+ private async enhanceLegacyAgentic(
346
+ model: ReturnType<typeof getModel>['model'],
347
+ modelId: string,
348
+ scanResult: ScanResult
294
349
  ): Promise<AIAnalysisResult | null> {
295
350
  const tools = createExplorationTools(scanResult.projectRoot);
351
+ const { generateText } = getTracedAI();
296
352
 
297
353
  const prompt = `Analyze this codebase and produce configuration for AI-assisted development.
298
354
 
@@ -325,7 +381,6 @@ When done exploring, output your final analysis as valid JSON matching this stru
325
381
  }`;
326
382
 
327
383
  // Use agentic loop - AI will call tools until it has enough info
328
- // stopWhen: stepCountIs(10) allows up to 10 tool-calling steps
329
384
  const result = await generateText({
330
385
  model,
331
386
  system: SYSTEM_PROMPT_AGENTIC,
@@ -333,8 +388,11 @@ When done exploring, output your final analysis as valid JSON matching this stru
333
388
  tools,
334
389
  stopWhen: stepCountIs(10),
335
390
  maxOutputTokens: 4000,
336
- // Reasoning models don't support temperature
337
391
  ...(isReasoningModel(modelId) ? {} : { temperature: 0.3 }),
392
+ experimental_telemetry: {
393
+ isEnabled: true,
394
+ metadata: { phase: 'legacy-agentic', projectRoot: scanResult.projectRoot },
395
+ },
338
396
  });
339
397
 
340
398
  // Try to get text from the result
@@ -346,7 +404,6 @@ When done exploring, output your final analysis as valid JSON matching this stru
346
404
  logger.info(`No direct text output, checking ${result.steps?.length || 0} steps...`);
347
405
  }
348
406
 
349
- // Look through steps for text content (from last to first)
350
407
  const steps = result.steps || [];
351
408
  for (let i = steps.length - 1; i >= 0; i--) {
352
409
  const step = steps[i];
@@ -368,6 +425,39 @@ When done exploring, output your final analysis as valid JSON matching this stru
368
425
  }
369
426
  }
370
427
 
428
+ /**
429
+ * Convert MultiAgentAnalysis to AIAnalysisResult for backward compatibility
430
+ */
431
+ function convertMultiAgentToAIAnalysis(multiAgent: MultiAgentAnalysis): AIAnalysisResult {
432
+ const { codebaseAnalysis, stackResearch, mcpServers } = multiAgent;
433
+
434
+ return {
435
+ projectContext: {
436
+ entryPoints: codebaseAnalysis.projectContext.entryPoints,
437
+ keyDirectories: codebaseAnalysis.projectContext.keyDirectories,
438
+ namingConventions: codebaseAnalysis.projectContext.namingConventions,
439
+ },
440
+ commands: codebaseAnalysis.commands,
441
+ implementationGuidelines: codebaseAnalysis.implementationGuidelines,
442
+ mcpServers: {
443
+ essential: mcpServers.essential,
444
+ recommended: mcpServers.recommended,
445
+ },
446
+ possibleMissedTechnologies: codebaseAnalysis.possibleMissedTechnologies,
447
+ technologyTools: {
448
+ testing: stackResearch.testingTools,
449
+ debugging: stackResearch.debuggingTools,
450
+ validation: [],
451
+ },
452
+ technologyPractices: {
453
+ projectType: codebaseAnalysis.projectContext.projectType,
454
+ practices: stackResearch.bestPractices,
455
+ antiPatterns: stackResearch.antiPatterns,
456
+ documentationHints: stackResearch.documentationHints,
457
+ },
458
+ };
459
+ }
460
+
371
461
  /**
372
462
  * Convenience function to enhance scan results with AI
373
463
  */
package/src/ai/index.ts CHANGED
@@ -7,10 +7,17 @@
7
7
  export {
8
8
  type AIProvider,
9
9
  type ProviderConfig,
10
+ type OptionalService,
10
11
  getModel,
11
12
  hasApiKey,
12
13
  getAvailableProvider,
13
14
  getApiKeyEnvVar,
15
+ OPTIONAL_SERVICE_ENV_VARS,
16
+ hasTavilyKey,
17
+ getTavilyKey,
18
+ hasContext7Key,
19
+ getContext7Key,
20
+ getOptionalServicesStatus,
14
21
  } from './providers.js';
15
22
 
16
23
  // Analysis prompts
@@ -29,11 +36,34 @@ export {
29
36
  RIPGREP_SKILL,
30
37
  } from './tools.js';
31
38
 
39
+ // New tools (Tavily, Context7)
40
+ export {
41
+ createTavilySearchTool,
42
+ canUseTavily,
43
+ createContext7Tool,
44
+ canUseContext7,
45
+ } from './tools/index.js';
46
+
47
+ // Agents
48
+ export {
49
+ runMultiAgentAnalysis,
50
+ runCodebaseAnalyst,
51
+ runStackResearcher,
52
+ runOrchestrator,
53
+ mergeAgentResults,
54
+ type CodebaseAnalysis,
55
+ type StackResearch,
56
+ type McpRecommendations,
57
+ type MultiAgentAnalysis,
58
+ type AgentCapabilities,
59
+ type AgentOptions,
60
+ } from './agents/index.js';
61
+
32
62
  // AI enhancer
33
63
  export {
34
64
  type ProjectContext,
35
65
  type DetectedCommands,
36
- type McpRecommendations,
66
+ type McpRecommendations as McpRecommendationsLegacy,
37
67
  type AIAnalysisResult,
38
68
  type EnhancedScanResult,
39
69
  type EnhancerOptions,
package/src/ai/prompts.ts CHANGED
@@ -100,6 +100,8 @@ Your goal is to thoroughly understand the codebase structure and produce actiona
100
100
  3. Search for key patterns: entry points, routes, components, tests
101
101
  4. Identify naming conventions by examining existing files
102
102
  5. Look for existing documentation (.md files, README)
103
+ 6. Determine the PROJECT TYPE (e.g., MCP server, REST API, React SPA, CLI tool, library)
104
+ 7. Based on project type, include TECHNOLOGY-SPECIFIC testing/debugging tools
103
105
 
104
106
  ## Tools Available
105
107
  You have these tools to explore the codebase:
@@ -110,6 +112,30 @@ You have these tools to explore the codebase:
110
112
 
111
113
  ${RIPGREP_SKILL}
112
114
 
115
+ ## Technology-Specific Guidance
116
+
117
+ When you detect specific project types, include their specialized tools:
118
+
119
+ **MCP Server Projects** (detected by @modelcontextprotocol dependencies):
120
+ - Testing: "npx @anthropic-ai/mcp-inspector" for interactive debugging
121
+ - Practices: Follow MCP protocol spec, validate tool schemas, handle resources properly
122
+
123
+ **REST APIs** (Express, Fastify, Hono, etc.):
124
+ - Testing: API testing tools (supertest, httpie, curl examples)
125
+ - Debugging: Request logging, OpenAPI validation
126
+
127
+ **React/Next.js Projects**:
128
+ - Testing: React Testing Library patterns, Storybook for components
129
+ - Debugging: React DevTools, component isolation
130
+
131
+ **CLI Tools**:
132
+ - Testing: Integration tests with actual CLI invocation
133
+ - Debugging: --verbose flags, debug logging patterns
134
+
135
+ **Libraries/Packages**:
136
+ - Testing: Unit tests with high coverage, type checking
137
+ - Practices: Semantic versioning, changelog maintenance
138
+
113
139
  ## Output Requirements
114
140
  After exploration, output valid JSON with:
115
141
  - projectContext: entry points, key directories, naming conventions
@@ -117,8 +143,11 @@ After exploration, output valid JSON with:
117
143
  - implementationGuidelines: short actionable rules (5-10 words each, max 7)
118
144
  - mcpServers: essential and recommended servers
119
145
  - possibleMissedTechnologies: technologies that might be in use
146
+ - technologyTools: testing, debugging, and validation tools specific to this project type
147
+ - technologyPractices: projectType, practices, antiPatterns, documentationHints
120
148
 
121
- Be concise. Focus on WHAT TO DO, not what exists.`;
149
+ Be concise. Focus on WHAT TO DO, not what exists.
150
+ Include SPECIFIC testing/debugging commands for the detected project type.`;
122
151
 
123
152
  /**
124
153
  * System prompt for codebase analysis (simple mode - no tools)
@@ -133,7 +162,16 @@ Rules:
133
162
  - Focus on WHAT TO DO, not what exists
134
163
  - Include specific file paths and commands
135
164
  - Max 5-7 items per array
136
- - No explanations, just actionable rules`;
165
+ - No explanations, just actionable rules
166
+ - CRITICAL: Include technology-specific testing and debugging tools
167
+ - Identify the PROJECT TYPE and provide stack-specific practices
168
+
169
+ Technology-specific tools to consider:
170
+ - MCP servers: "npx @anthropic-ai/mcp-inspector" for testing
171
+ - REST APIs: supertest, curl examples, OpenAPI validation
172
+ - React apps: React Testing Library, Storybook, DevTools
173
+ - CLI tools: integration tests, --verbose flags
174
+ - Libraries: high coverage unit tests, semantic versioning`;
137
175
 
138
176
  /**
139
177
  * Create the codebase analysis prompt
@@ -175,16 +213,41 @@ Respond with this JSON structure (keep values SHORT - 5-10 words max per item):
175
213
  "essential": ["filesystem", "git"],
176
214
  "recommended": ["docker", "postgres"]
177
215
  },
178
- "possibleMissedTechnologies": ["Redis", "WebSockets"]
216
+ "possibleMissedTechnologies": ["Redis", "WebSockets"],
217
+ "technologyTools": {
218
+ "testing": ["npx @anthropic-ai/mcp-inspector", "node test/test-*.js"],
219
+ "debugging": ["--verbose flag", "DEBUG=* env var"],
220
+ "validation": ["npx tsc --noEmit", "npm run lint"]
221
+ },
222
+ "technologyPractices": {
223
+ "projectType": "MCP Server",
224
+ "practices": [
225
+ "Validate tool input schemas with Zod",
226
+ "Return structured JSON from tools",
227
+ "Handle errors with proper MCP error codes"
228
+ ],
229
+ "antiPatterns": [
230
+ "Don't expose internal errors to clients",
231
+ "Avoid blocking operations in tool handlers"
232
+ ],
233
+ "documentationHints": [
234
+ "MCP spec: modelcontextprotocol.io/docs",
235
+ "Inspector: modelcontextprotocol.io/docs/tools/inspector"
236
+ ]
237
+ }
179
238
  }
180
239
 
181
- Important:
240
+ CRITICAL:
241
+ - Identify the PROJECT TYPE first (MCP Server, REST API, React SPA, CLI, Library, etc.)
242
+ - Include technology-specific testing tools (e.g., MCP Inspector for MCP projects)
243
+ - Include technology-specific debugging approaches
182
244
  - implementationGuidelines should be short rules (not analysis prompts)
183
245
  - Include actual file paths from this project
184
246
  - Infer commands from package.json patterns
185
247
  - Max 5-7 items per array`;
186
248
  }
187
249
 
250
+
188
251
  /**
189
252
  * Prompt for validating and improving scanner results
190
253
  */
@@ -30,6 +30,16 @@ const API_KEY_ENV_VARS: Record<AIProvider, string> = {
30
30
  openrouter: 'OPENROUTER_API_KEY',
31
31
  };
32
32
 
33
+ /**
34
+ * Environment variable names for optional services
35
+ */
36
+ export const OPTIONAL_SERVICE_ENV_VARS = {
37
+ tavily: 'TAVILY_API_KEY',
38
+ context7: 'CONTEXT7_API_KEY',
39
+ } as const;
40
+
41
+ export type OptionalService = keyof typeof OPTIONAL_SERVICE_ENV_VARS;
42
+
33
43
  /**
34
44
  * Model option with label and value
35
45
  */
@@ -184,3 +194,41 @@ const REASONING_MODELS = [
184
194
  export function isReasoningModel(modelId: string): boolean {
185
195
  return REASONING_MODELS.some(m => modelId.startsWith(m));
186
196
  }
197
+
198
+ /**
199
+ * Check if Tavily API key is available
200
+ */
201
+ export function hasTavilyKey(): boolean {
202
+ return !!process.env[OPTIONAL_SERVICE_ENV_VARS.tavily];
203
+ }
204
+
205
+ /**
206
+ * Get the Tavily API key
207
+ */
208
+ export function getTavilyKey(): string | undefined {
209
+ return process.env[OPTIONAL_SERVICE_ENV_VARS.tavily];
210
+ }
211
+
212
+ /**
213
+ * Check if Context7 API key is available
214
+ */
215
+ export function hasContext7Key(): boolean {
216
+ return !!process.env[OPTIONAL_SERVICE_ENV_VARS.context7];
217
+ }
218
+
219
+ /**
220
+ * Get the Context7 API key
221
+ */
222
+ export function getContext7Key(): string | undefined {
223
+ return process.env[OPTIONAL_SERVICE_ENV_VARS.context7];
224
+ }
225
+
226
+ /**
227
+ * Get the status of all optional services
228
+ */
229
+ export function getOptionalServicesStatus(): Record<OptionalService, boolean> {
230
+ return {
231
+ tavily: hasTavilyKey(),
232
+ context7: hasContext7Key(),
233
+ };
234
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Context7 Documentation Lookup Tool
3
+ * Enables documentation lookup for libraries and frameworks
4
+ */
5
+
6
+ import { tool, zodSchema } from 'ai';
7
+ import { z } from 'zod';
8
+
9
+ /**
10
+ * Context7 library info
11
+ */
12
+ export interface Context7Library {
13
+ id: string;
14
+ name: string;
15
+ description?: string;
16
+ codeSnippetCount?: number;
17
+ }
18
+
19
+ /**
20
+ * Context7 documentation result
21
+ */
22
+ export interface Context7DocResult {
23
+ title: string;
24
+ content: string;
25
+ codeExamples?: string[];
26
+ }
27
+
28
+ /**
29
+ * Create a Context7 documentation lookup tool
30
+ * @param apiKey - Context7 API key
31
+ */
32
+ export function createContext7Tool(apiKey: string) {
33
+ return tool({
34
+ description: `Look up documentation for libraries and frameworks.
35
+ Use this to find:
36
+ - API documentation for specific functions
37
+ - Usage examples and patterns
38
+ - Configuration options
39
+ - Best practices from official docs`,
40
+ inputSchema: zodSchema(z.object({
41
+ library: z.string().describe('Library name (e.g., "react", "express", "prisma")'),
42
+ query: z.string().describe('What you want to find in the documentation'),
43
+ })),
44
+ execute: async ({ library, query }) => {
45
+ try {
46
+ // First, resolve the library ID
47
+ const resolveResponse = await fetch('https://api.context7.com/v1/resolve-library-id', {
48
+ method: 'POST',
49
+ headers: {
50
+ 'Content-Type': 'application/json',
51
+ 'Authorization': `Bearer ${apiKey}`,
52
+ },
53
+ body: JSON.stringify({
54
+ libraryName: library,
55
+ query: query,
56
+ }),
57
+ });
58
+
59
+ if (!resolveResponse.ok) {
60
+ // Try alternative endpoint structure
61
+ return await fallbackDocLookup(apiKey, library, query);
62
+ }
63
+
64
+ const resolveData = await resolveResponse.json() as { libraryId?: string; libraries?: Context7Library[] };
65
+ const libraryId = resolveData.libraryId || resolveData.libraries?.[0]?.id;
66
+
67
+ if (!libraryId) {
68
+ return `No documentation found for "${library}". Try a different library name.`;
69
+ }
70
+
71
+ // Query the documentation
72
+ const queryResponse = await fetch('https://api.context7.com/v1/query-docs', {
73
+ method: 'POST',
74
+ headers: {
75
+ 'Content-Type': 'application/json',
76
+ 'Authorization': `Bearer ${apiKey}`,
77
+ },
78
+ body: JSON.stringify({
79
+ libraryId,
80
+ query,
81
+ }),
82
+ });
83
+
84
+ if (!queryResponse.ok) {
85
+ const errorText = await queryResponse.text();
86
+ return `Documentation lookup failed: ${queryResponse.status} - ${errorText}`;
87
+ }
88
+
89
+ const docsData = await queryResponse.json() as { results?: Context7DocResult[] };
90
+
91
+ // Format results
92
+ const results: string[] = [];
93
+ results.push(`Documentation for ${library}:`);
94
+ results.push('');
95
+
96
+ if (docsData.results && docsData.results.length > 0) {
97
+ for (const doc of docsData.results.slice(0, 3)) {
98
+ results.push(`## ${doc.title}`);
99
+ results.push(doc.content.substring(0, 500));
100
+ if (doc.codeExamples && doc.codeExamples.length > 0) {
101
+ results.push('');
102
+ results.push('Example:');
103
+ results.push('```');
104
+ results.push(doc.codeExamples[0].substring(0, 300));
105
+ results.push('```');
106
+ }
107
+ results.push('');
108
+ }
109
+ } else {
110
+ results.push(`No specific documentation found for query: "${query}"`);
111
+ }
112
+
113
+ return results.join('\n');
114
+ } catch (error) {
115
+ const errMsg = error instanceof Error ? error.message : String(error);
116
+ return `Documentation lookup error: ${errMsg}`;
117
+ }
118
+ },
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Fallback documentation lookup using alternative approach
124
+ */
125
+ async function fallbackDocLookup(apiKey: string, library: string, query: string): Promise<string> {
126
+ try {
127
+ // Try a simpler query format
128
+ const response = await fetch(`https://api.context7.com/v1/search`, {
129
+ method: 'POST',
130
+ headers: {
131
+ 'Content-Type': 'application/json',
132
+ 'Authorization': `Bearer ${apiKey}`,
133
+ },
134
+ body: JSON.stringify({
135
+ query: `${library} ${query}`,
136
+ limit: 5,
137
+ }),
138
+ });
139
+
140
+ if (!response.ok) {
141
+ return `Unable to find documentation for "${library}". The Context7 API may not support this library or the request format has changed.`;
142
+ }
143
+
144
+ const data = await response.json() as { results?: Array<{ title: string; content: string }> };
145
+
146
+ if (data.results && data.results.length > 0) {
147
+ const results: string[] = [`Documentation for ${library}:`, ''];
148
+ for (const item of data.results.slice(0, 3)) {
149
+ results.push(`## ${item.title}`);
150
+ results.push(item.content.substring(0, 400));
151
+ results.push('');
152
+ }
153
+ return results.join('\n');
154
+ }
155
+
156
+ return `No documentation found for "${library}" with query "${query}"`;
157
+ } catch {
158
+ return `Documentation lookup for "${library}" failed. Please check your Context7 API key.`;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Check if Context7 can be used
164
+ */
165
+ export function canUseContext7(apiKey?: string): boolean {
166
+ return !!apiKey && apiKey.length > 0;
167
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * AI Tools Index
3
+ * Exports all tools for agent use
4
+ */
5
+
6
+ export {
7
+ createTavilySearchTool,
8
+ canUseTavily,
9
+ type TavilySearchResult,
10
+ } from './tavily.js';
11
+
12
+ export {
13
+ createContext7Tool,
14
+ canUseContext7,
15
+ type Context7Library,
16
+ type Context7DocResult,
17
+ } from './context7.js';