wiggum-cli 0.2.1 → 0.2.3
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/ai/enhancer.d.ts +44 -35
- package/dist/ai/enhancer.d.ts.map +1 -1
- package/dist/ai/enhancer.js +135 -90
- package/dist/ai/enhancer.js.map +1 -1
- package/dist/ai/index.d.ts +3 -2
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +3 -1
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/prompts.d.ts +6 -2
- package/dist/ai/prompts.d.ts.map +1 -1
- package/dist/ai/prompts.js +74 -43
- package/dist/ai/prompts.js.map +1 -1
- package/dist/ai/tools.d.ts +47 -0
- package/dist/ai/tools.d.ts.map +1 -0
- package/dist/ai/tools.js +253 -0
- package/dist/ai/tools.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +20 -5
- package/dist/commands/init.js.map +1 -1
- package/dist/utils/colors.d.ts +48 -0
- package/dist/utils/colors.d.ts.map +1 -0
- package/dist/utils/colors.js +85 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/utils/header.d.ts +5 -1
- package/dist/utils/header.d.ts.map +1 -1
- package/dist/utils/header.js +19 -4
- package/dist/utils/header.js.map +1 -1
- package/package.json +3 -2
- package/src/ai/enhancer.ts +186 -138
- package/src/ai/index.ts +10 -5
- package/src/ai/prompts.ts +75 -43
- package/src/ai/tools.ts +278 -0
- package/src/commands/init.ts +25 -4
- package/src/utils/colors.ts +95 -0
- package/src/utils/header.ts +21 -4
package/src/ai/prompts.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { ScanResult, DetectedStack } from '../scanner/types.js';
|
|
7
|
+
import { RIPGREP_SKILL } from './tools.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Format the detected stack for inclusion in prompts
|
|
@@ -87,20 +88,52 @@ export function formatStackForPrompt(stack: DetectedStack): string {
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
/**
|
|
90
|
-
* System prompt for codebase analysis
|
|
91
|
+
* System prompt for codebase analysis (agentic mode)
|
|
91
92
|
*/
|
|
92
|
-
export const
|
|
93
|
+
export const SYSTEM_PROMPT_AGENTIC = `You are an expert codebase analyst with tools to explore the project.
|
|
93
94
|
|
|
94
|
-
Your
|
|
95
|
-
1. Analyze the detected tech stack and provide deeper insights
|
|
96
|
-
2. Identify architectural patterns and coding conventions
|
|
97
|
-
3. Suggest improvements to the detection results
|
|
98
|
-
4. Recommend MCP servers that would benefit this project
|
|
99
|
-
5. Generate custom prompt suggestions for AI coding assistants
|
|
95
|
+
Your goal is to thoroughly understand the codebase structure and produce actionable configuration for AI-assisted development.
|
|
100
96
|
|
|
101
|
-
|
|
97
|
+
## Exploration Strategy
|
|
98
|
+
1. First, list the root directory to understand project structure
|
|
99
|
+
2. Read package.json to understand scripts and dependencies
|
|
100
|
+
3. Search for key patterns: entry points, routes, components, tests
|
|
101
|
+
4. Identify naming conventions by examining existing files
|
|
102
|
+
5. Look for existing documentation (.md files, README)
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
## Tools Available
|
|
105
|
+
You have these tools to explore the codebase:
|
|
106
|
+
- searchCode: Search using ripgrep patterns
|
|
107
|
+
- readFile: Read file contents
|
|
108
|
+
- listDirectory: List directory structure
|
|
109
|
+
- getPackageInfo: Get package.json info
|
|
110
|
+
|
|
111
|
+
${RIPGREP_SKILL}
|
|
112
|
+
|
|
113
|
+
## Output Requirements
|
|
114
|
+
After exploration, output valid JSON with:
|
|
115
|
+
- projectContext: entry points, key directories, naming conventions
|
|
116
|
+
- commands: test, lint, build, dev commands from package.json
|
|
117
|
+
- implementationGuidelines: short actionable rules (5-10 words each, max 7)
|
|
118
|
+
- mcpServers: essential and recommended servers
|
|
119
|
+
- possibleMissedTechnologies: technologies that might be in use
|
|
120
|
+
|
|
121
|
+
Be concise. Focus on WHAT TO DO, not what exists.`;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* System prompt for codebase analysis (simple mode - no tools)
|
|
125
|
+
*/
|
|
126
|
+
export const SYSTEM_PROMPT = `You are analyzing a codebase to help configure AI-assisted development tools.
|
|
127
|
+
|
|
128
|
+
Your goal is to produce SHORT, ACTIONABLE output that helps AI coding assistants work effectively on this codebase.
|
|
129
|
+
|
|
130
|
+
Rules:
|
|
131
|
+
- Output valid JSON only
|
|
132
|
+
- Be extremely concise (5-10 words per item max)
|
|
133
|
+
- Focus on WHAT TO DO, not what exists
|
|
134
|
+
- Include specific file paths and commands
|
|
135
|
+
- Max 5-7 items per array
|
|
136
|
+
- No explanations, just actionable rules`;
|
|
104
137
|
|
|
105
138
|
/**
|
|
106
139
|
* Create the codebase analysis prompt
|
|
@@ -108,49 +141,48 @@ Respond in valid JSON format only.`;
|
|
|
108
141
|
export function createAnalysisPrompt(scanResult: ScanResult): string {
|
|
109
142
|
const stackInfo = formatStackForPrompt(scanResult.stack);
|
|
110
143
|
|
|
111
|
-
return `Analyze this codebase
|
|
144
|
+
return `Analyze this codebase for AI-assisted development configuration.
|
|
112
145
|
|
|
113
|
-
Project
|
|
146
|
+
Project: ${scanResult.projectRoot}
|
|
114
147
|
|
|
115
148
|
Detected Stack:
|
|
116
149
|
${stackInfo || 'No technologies detected'}
|
|
117
150
|
|
|
118
|
-
|
|
151
|
+
Respond with this JSON structure (keep values SHORT - 5-10 words max per item):
|
|
119
152
|
{
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
123
|
-
|
|
153
|
+
"projectContext": {
|
|
154
|
+
"entryPoints": ["src/index.ts", "src/server.ts"],
|
|
155
|
+
"keyDirectories": {
|
|
156
|
+
"src/routes": "API route handlers",
|
|
157
|
+
"src/models": "Database models"
|
|
158
|
+
},
|
|
159
|
+
"namingConventions": "camelCase files, PascalCase components"
|
|
124
160
|
},
|
|
125
|
-
"
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
],
|
|
138
|
-
"recommendedMcpServers": [
|
|
139
|
-
{
|
|
140
|
-
"name": "server name",
|
|
141
|
-
"reason": "why this would be useful"
|
|
142
|
-
}
|
|
143
|
-
],
|
|
144
|
-
"customPromptSuggestions": [
|
|
145
|
-
"Specific prompt suggestions tailored to this codebase"
|
|
161
|
+
"commands": {
|
|
162
|
+
"test": "npm test",
|
|
163
|
+
"lint": "npm run lint",
|
|
164
|
+
"typecheck": "npm run typecheck",
|
|
165
|
+
"build": "npm run build",
|
|
166
|
+
"dev": "npm run dev"
|
|
167
|
+
},
|
|
168
|
+
"implementationGuidelines": [
|
|
169
|
+
"Run npm test after every change",
|
|
170
|
+
"Use Zod for request validation",
|
|
171
|
+
"Place routes in src/routes/<resource>.ts",
|
|
172
|
+
"Follow error pattern in src/utils/errors.ts"
|
|
146
173
|
],
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
}
|
|
174
|
+
"mcpServers": {
|
|
175
|
+
"essential": ["filesystem", "git"],
|
|
176
|
+
"recommended": ["docker", "postgres"]
|
|
177
|
+
},
|
|
178
|
+
"possibleMissedTechnologies": ["Redis", "WebSockets"]
|
|
151
179
|
}
|
|
152
180
|
|
|
153
|
-
|
|
181
|
+
Important:
|
|
182
|
+
- implementationGuidelines should be short rules (not analysis prompts)
|
|
183
|
+
- Include actual file paths from this project
|
|
184
|
+
- Infer commands from package.json patterns
|
|
185
|
+
- Max 5-7 items per array`;
|
|
154
186
|
}
|
|
155
187
|
|
|
156
188
|
/**
|
package/src/ai/tools.ts
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Tools Module
|
|
3
|
+
* Defines tools the AI agent can use to explore the codebase
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { tool, zodSchema } from 'ai';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
|
|
10
|
+
import { join, relative } from 'node:path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create tools for codebase exploration
|
|
14
|
+
*/
|
|
15
|
+
export function createExplorationTools(projectRoot: string) {
|
|
16
|
+
return {
|
|
17
|
+
/**
|
|
18
|
+
* Search code using ripgrep patterns
|
|
19
|
+
*/
|
|
20
|
+
searchCode: tool({
|
|
21
|
+
description: `Search the codebase using ripgrep. Use this to find:
|
|
22
|
+
- Function/class definitions: pattern="^(def|function|class) NAME", fileType="py"
|
|
23
|
+
- Symbol usage: pattern="\\bSymbolName\\b" (word boundaries critical!)
|
|
24
|
+
- Imports: pattern="^import.*module", fileType="ts"
|
|
25
|
+
- File patterns: pattern="pattern", glob="*.tsx"
|
|
26
|
+
|
|
27
|
+
Tips:
|
|
28
|
+
- Use fileType for filtering (py, js, ts, rust, go)
|
|
29
|
+
- Use \\b for word boundaries to avoid partial matches
|
|
30
|
+
- Use literal=true for exact strings (faster)
|
|
31
|
+
- Always use word boundaries when searching symbols`,
|
|
32
|
+
inputSchema: zodSchema(z.object({
|
|
33
|
+
pattern: z.string().describe('The regex pattern to search for'),
|
|
34
|
+
fileType: z.string().optional().describe('File type filter (py, js, ts, etc.)'),
|
|
35
|
+
glob: z.string().optional().describe('Glob pattern like "*.tsx" or "src/**/*.ts"'),
|
|
36
|
+
literal: z.boolean().optional().describe('Use literal search (faster for exact strings)'),
|
|
37
|
+
context: z.number().optional().describe('Lines of context (default 0)'),
|
|
38
|
+
maxResults: z.number().optional().describe('Max results to return (default 50)'),
|
|
39
|
+
})),
|
|
40
|
+
execute: async ({ pattern, fileType, glob, literal, context, maxResults }) => {
|
|
41
|
+
try {
|
|
42
|
+
const args: string[] = [];
|
|
43
|
+
|
|
44
|
+
if (literal) args.push('-F');
|
|
45
|
+
if (fileType) args.push('-t', fileType);
|
|
46
|
+
if (glob) args.push('-g', glob);
|
|
47
|
+
if (context) args.push('-C', String(context));
|
|
48
|
+
|
|
49
|
+
args.push('--max-count', String(maxResults || 50));
|
|
50
|
+
args.push('--no-heading');
|
|
51
|
+
args.push('--line-number');
|
|
52
|
+
args.push('--');
|
|
53
|
+
args.push(pattern);
|
|
54
|
+
args.push(projectRoot);
|
|
55
|
+
|
|
56
|
+
// Use spawnSync instead of execSync for safety (no shell injection)
|
|
57
|
+
const result = spawnSync('rg', args, {
|
|
58
|
+
encoding: 'utf-8',
|
|
59
|
+
maxBuffer: 1024 * 1024,
|
|
60
|
+
timeout: 30000,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (result.error) {
|
|
64
|
+
return `Search error: ${result.error.message}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (result.status === 1) {
|
|
68
|
+
return 'No matches found';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (result.status !== 0) {
|
|
72
|
+
return `Search error: ${result.stderr || 'Unknown error'}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Convert absolute paths to relative
|
|
76
|
+
const lines = result.stdout.split('\n').map(line => {
|
|
77
|
+
if (line.startsWith(projectRoot)) {
|
|
78
|
+
return line.replace(projectRoot + '/', '');
|
|
79
|
+
}
|
|
80
|
+
return line;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return lines.slice(0, 100).join('\n');
|
|
84
|
+
} catch (error: unknown) {
|
|
85
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
86
|
+
return `Search error: ${errMsg}`;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Read a file's contents
|
|
93
|
+
*/
|
|
94
|
+
readFile: tool({
|
|
95
|
+
description: 'Read the contents of a file. Use relative paths from project root.',
|
|
96
|
+
inputSchema: zodSchema(z.object({
|
|
97
|
+
path: z.string().describe('Relative path to the file from project root'),
|
|
98
|
+
startLine: z.number().optional().describe('Start line (1-indexed)'),
|
|
99
|
+
endLine: z.number().optional().describe('End line (inclusive)'),
|
|
100
|
+
})),
|
|
101
|
+
execute: async ({ path: filePath, startLine, endLine }) => {
|
|
102
|
+
try {
|
|
103
|
+
// Prevent path traversal
|
|
104
|
+
const normalizedPath = filePath.replace(/\.\./g, '');
|
|
105
|
+
const fullPath = join(projectRoot, normalizedPath);
|
|
106
|
+
|
|
107
|
+
if (!fullPath.startsWith(projectRoot)) {
|
|
108
|
+
return 'Invalid path: cannot access files outside project';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!existsSync(fullPath)) {
|
|
112
|
+
return `File not found: ${filePath}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const stat = statSync(fullPath);
|
|
116
|
+
if (stat.isDirectory()) {
|
|
117
|
+
return `Path is a directory, not a file: ${filePath}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (stat.size > 100000) {
|
|
121
|
+
return `File too large (${stat.size} bytes). Use startLine/endLine to read a portion.`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
125
|
+
const lines = content.split('\n');
|
|
126
|
+
|
|
127
|
+
if (startLine || endLine) {
|
|
128
|
+
const start = (startLine || 1) - 1;
|
|
129
|
+
const end = endLine || lines.length;
|
|
130
|
+
return lines.slice(start, end).join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return content;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
136
|
+
return `Error reading file: ${errMsg}`;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* List directory contents
|
|
143
|
+
*/
|
|
144
|
+
listDirectory: tool({
|
|
145
|
+
description: 'List contents of a directory. Shows files and subdirectories.',
|
|
146
|
+
inputSchema: zodSchema(z.object({
|
|
147
|
+
path: z.string().describe('Relative path to the directory (use "." for project root)'),
|
|
148
|
+
recursive: z.boolean().optional().describe('List recursively (default false)'),
|
|
149
|
+
maxDepth: z.number().optional().describe('Max depth for recursive listing (default 2)'),
|
|
150
|
+
})),
|
|
151
|
+
execute: async ({ path: dirPath, recursive, maxDepth }) => {
|
|
152
|
+
try {
|
|
153
|
+
// Prevent path traversal
|
|
154
|
+
const normalizedPath = dirPath.replace(/\.\./g, '');
|
|
155
|
+
const fullPath = join(projectRoot, normalizedPath);
|
|
156
|
+
|
|
157
|
+
if (!fullPath.startsWith(projectRoot)) {
|
|
158
|
+
return 'Invalid path: cannot access directories outside project';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!existsSync(fullPath)) {
|
|
162
|
+
return `Directory not found: ${dirPath}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const stat = statSync(fullPath);
|
|
166
|
+
if (!stat.isDirectory()) {
|
|
167
|
+
return `Path is not a directory: ${dirPath}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const results: string[] = [];
|
|
171
|
+
const depth = maxDepth || 2;
|
|
172
|
+
|
|
173
|
+
function scanDir(dir: string, currentDepth: number): void {
|
|
174
|
+
if (currentDepth > depth) return;
|
|
175
|
+
|
|
176
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
177
|
+
|
|
178
|
+
for (const entry of entries) {
|
|
179
|
+
// Skip common ignored directories
|
|
180
|
+
if (['node_modules', '.git', 'dist', 'build', '.next', '__pycache__'].includes(entry.name)) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const relativePath = relative(projectRoot, join(dir, entry.name));
|
|
185
|
+
const prefix = entry.isDirectory() ? '📁 ' : '📄 ';
|
|
186
|
+
results.push(prefix + relativePath);
|
|
187
|
+
|
|
188
|
+
if (recursive && entry.isDirectory() && currentDepth < depth) {
|
|
189
|
+
scanDir(join(dir, entry.name), currentDepth + 1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
scanDir(fullPath, 1);
|
|
195
|
+
return results.slice(0, 200).join('\n');
|
|
196
|
+
} catch (error) {
|
|
197
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
198
|
+
return `Error listing directory: ${errMsg}`;
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get package.json info including scripts
|
|
205
|
+
*/
|
|
206
|
+
getPackageInfo: tool({
|
|
207
|
+
description: 'Get package.json contents including scripts, dependencies, and metadata.',
|
|
208
|
+
inputSchema: zodSchema(z.object({
|
|
209
|
+
field: z.string().optional().describe('Specific field to get (scripts, dependencies, etc.)'),
|
|
210
|
+
})),
|
|
211
|
+
execute: async ({ field }) => {
|
|
212
|
+
try {
|
|
213
|
+
const pkgPath = join(projectRoot, 'package.json');
|
|
214
|
+
|
|
215
|
+
if (!existsSync(pkgPath)) {
|
|
216
|
+
return 'No package.json found';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
220
|
+
|
|
221
|
+
if (field) {
|
|
222
|
+
return JSON.stringify(pkg[field], null, 2) || `Field "${field}" not found`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Return relevant parts
|
|
226
|
+
return JSON.stringify({
|
|
227
|
+
name: pkg.name,
|
|
228
|
+
scripts: pkg.scripts,
|
|
229
|
+
dependencies: Object.keys(pkg.dependencies || {}),
|
|
230
|
+
devDependencies: Object.keys(pkg.devDependencies || {}),
|
|
231
|
+
}, null, 2);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
234
|
+
return `Error reading package.json: ${errMsg}`;
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
}),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Ripgrep skill reference for the AI agent
|
|
243
|
+
*/
|
|
244
|
+
export const RIPGREP_SKILL = `
|
|
245
|
+
## ripgrep Code Search Skill
|
|
246
|
+
|
|
247
|
+
### Essential Patterns
|
|
248
|
+
|
|
249
|
+
**Find definitions:**
|
|
250
|
+
- Python functions: pattern="^def \\w+\\(", fileType="py"
|
|
251
|
+
- JS/TS functions: pattern="^(export )?(function|const) \\w+", fileType="ts"
|
|
252
|
+
- Classes: pattern="^class \\w+", fileType="py"
|
|
253
|
+
|
|
254
|
+
**Find symbol usage (CRITICAL: use word boundaries):**
|
|
255
|
+
- Exact word: pattern="\\bSymbolName\\b"
|
|
256
|
+
- Literal string: pattern="exact.string", literal=true
|
|
257
|
+
|
|
258
|
+
**Find imports:**
|
|
259
|
+
- ES imports: pattern="^import.*from", fileType="ts"
|
|
260
|
+
- CommonJS: pattern="require\\(", fileType="js"
|
|
261
|
+
|
|
262
|
+
**File type options:**
|
|
263
|
+
- py (Python)
|
|
264
|
+
- js (JavaScript)
|
|
265
|
+
- ts (TypeScript)
|
|
266
|
+
- rust (Rust)
|
|
267
|
+
- go (Go)
|
|
268
|
+
|
|
269
|
+
**Performance tips:**
|
|
270
|
+
1. Always use fileType when possible
|
|
271
|
+
2. Use literal=true for exact strings
|
|
272
|
+
3. Use \\b for word boundaries (prevents partial matches)
|
|
273
|
+
4. Keep maxResults reasonable
|
|
274
|
+
|
|
275
|
+
**Word boundaries are critical:**
|
|
276
|
+
- WITHOUT: pattern="log" matches "logger", "blogger", "catalog"
|
|
277
|
+
- WITH: pattern="\\blog\\b" matches only "log"
|
|
278
|
+
`;
|
package/src/commands/init.ts
CHANGED
|
@@ -17,11 +17,14 @@ import * as prompts from '@clack/prompts';
|
|
|
17
17
|
import pc from 'picocolors';
|
|
18
18
|
import fs from 'fs';
|
|
19
19
|
import path from 'path';
|
|
20
|
+
import { simpson, sectionHeader, drawLine } from '../utils/colors.js';
|
|
20
21
|
|
|
21
22
|
export interface InitOptions {
|
|
22
23
|
ai?: boolean;
|
|
23
24
|
provider?: AIProvider;
|
|
24
25
|
yes?: boolean;
|
|
26
|
+
/** Use agentic mode for deep codebase exploration */
|
|
27
|
+
agentic?: boolean;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -53,7 +56,7 @@ export async function initCommand(options: InitOptions): Promise<void> {
|
|
|
53
56
|
|
|
54
57
|
// Display scan results
|
|
55
58
|
console.log('');
|
|
56
|
-
console.log(
|
|
59
|
+
console.log(simpson.yellow('─── Scan Results ───'));
|
|
57
60
|
console.log(formatScanResult(scanResult));
|
|
58
61
|
console.log('');
|
|
59
62
|
|
|
@@ -84,7 +87,7 @@ export async function initCommand(options: InitOptions): Promise<void> {
|
|
|
84
87
|
// If no API key found, prompt for one
|
|
85
88
|
if (!hasKey) {
|
|
86
89
|
console.log('');
|
|
87
|
-
console.log(
|
|
90
|
+
console.log(simpson.pink('No API key found for AI enhancement.'));
|
|
88
91
|
console.log('');
|
|
89
92
|
|
|
90
93
|
// Ask which provider to use
|
|
@@ -174,13 +177,31 @@ export async function initCommand(options: InitOptions): Promise<void> {
|
|
|
174
177
|
const selectedModel = modelChoice as string;
|
|
175
178
|
const modelLabel = modelOptions.find(m => m.value === selectedModel)?.label || selectedModel;
|
|
176
179
|
|
|
180
|
+
// Ask about agentic mode (deep exploration)
|
|
181
|
+
let useAgentic = options.agentic;
|
|
182
|
+
if (useAgentic === undefined && !options.yes) {
|
|
183
|
+
const wantAgentic = await prompts.confirm({
|
|
184
|
+
message: 'Enable deep codebase exploration? (AI will search files and directories)',
|
|
185
|
+
initialValue: true,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (prompts.isCancel(wantAgentic)) {
|
|
189
|
+
logger.info('Initialization cancelled');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
useAgentic = wantAgentic;
|
|
194
|
+
}
|
|
195
|
+
|
|
177
196
|
console.log('');
|
|
178
|
-
|
|
197
|
+
const modeLabel = useAgentic ? 'agentic' : 'simple';
|
|
198
|
+
console.log(simpson.yellow(`─── AI Enhancement (${provider} / ${modelLabel} / ${modeLabel}) ───`));
|
|
179
199
|
|
|
180
200
|
const aiEnhancer = new AIEnhancer({
|
|
181
201
|
provider,
|
|
182
202
|
model: selectedModel,
|
|
183
203
|
verbose: true,
|
|
204
|
+
agentic: useAgentic,
|
|
184
205
|
});
|
|
185
206
|
|
|
186
207
|
spinner.start('Running AI analysis...');
|
|
@@ -236,7 +257,7 @@ export async function initCommand(options: InitOptions): Promise<void> {
|
|
|
236
257
|
spinner.stop('Configuration files generated');
|
|
237
258
|
|
|
238
259
|
console.log('');
|
|
239
|
-
console.log(
|
|
260
|
+
console.log(simpson.yellow('─── Generation Results ───'));
|
|
240
261
|
console.log(formatGenerationResult(generationResult));
|
|
241
262
|
|
|
242
263
|
if (generationResult.success) {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simpson Color Palette
|
|
3
|
+
* Custom ANSI color utilities for the CLI
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Simpson color palette hex values
|
|
8
|
+
*/
|
|
9
|
+
export const SIMPSON_COLORS = {
|
|
10
|
+
blue: '#2f64d6',
|
|
11
|
+
yellow: '#f8db27',
|
|
12
|
+
brown: '#9c5b01',
|
|
13
|
+
white: '#ffffff',
|
|
14
|
+
pink: '#ff81c1',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert hex to RGB
|
|
19
|
+
*/
|
|
20
|
+
function hexToRgb(hex: string): { r: number; g: number; b: number } {
|
|
21
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
22
|
+
if (!result) {
|
|
23
|
+
return { r: 255, g: 255, b: 255 };
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
r: parseInt(result[1], 16),
|
|
27
|
+
g: parseInt(result[2], 16),
|
|
28
|
+
b: parseInt(result[3], 16),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create ANSI escape code for foreground color from hex
|
|
34
|
+
*/
|
|
35
|
+
function fgHex(hex: string): string {
|
|
36
|
+
const { r, g, b } = hexToRgb(hex);
|
|
37
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reset ANSI escape code
|
|
42
|
+
*/
|
|
43
|
+
const reset = '\x1b[0m';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Simpson color functions for terminal output
|
|
47
|
+
*/
|
|
48
|
+
export const simpson = {
|
|
49
|
+
yellow: (text: string): string => `${fgHex(SIMPSON_COLORS.yellow)}${text}${reset}`,
|
|
50
|
+
blue: (text: string): string => `${fgHex(SIMPSON_COLORS.blue)}${text}${reset}`,
|
|
51
|
+
brown: (text: string): string => `${fgHex(SIMPSON_COLORS.brown)}${text}${reset}`,
|
|
52
|
+
white: (text: string): string => `${fgHex(SIMPSON_COLORS.white)}${text}${reset}`,
|
|
53
|
+
pink: (text: string): string => `${fgHex(SIMPSON_COLORS.pink)}${text}${reset}`,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Box drawing characters
|
|
58
|
+
*/
|
|
59
|
+
export const box = {
|
|
60
|
+
topLeft: '┌',
|
|
61
|
+
topRight: '┐',
|
|
62
|
+
bottomLeft: '└',
|
|
63
|
+
bottomRight: '┘',
|
|
64
|
+
horizontal: '─',
|
|
65
|
+
vertical: '│',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Draw a horizontal line
|
|
70
|
+
*/
|
|
71
|
+
export function drawLine(width: number = 50): string {
|
|
72
|
+
return simpson.brown(box.horizontal.repeat(width));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Draw a box around text
|
|
77
|
+
*/
|
|
78
|
+
export function drawBox(text: string, padding: number = 1): string {
|
|
79
|
+
const paddedText = ' '.repeat(padding) + text + ' '.repeat(padding);
|
|
80
|
+
const width = paddedText.length;
|
|
81
|
+
|
|
82
|
+
const top = simpson.brown(box.topLeft + box.horizontal.repeat(width) + box.topRight);
|
|
83
|
+
const middle = simpson.brown(box.vertical) + paddedText + simpson.brown(box.vertical);
|
|
84
|
+
const bottom = simpson.brown(box.bottomLeft + box.horizontal.repeat(width) + box.bottomRight);
|
|
85
|
+
|
|
86
|
+
return `${top}\n${middle}\n${bottom}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Section header with horizontal lines
|
|
91
|
+
*/
|
|
92
|
+
export function sectionHeader(title: string): string {
|
|
93
|
+
const line = drawLine(50);
|
|
94
|
+
return `\n${line}\n${simpson.yellow(title)}\n${line}`;
|
|
95
|
+
}
|
package/src/utils/header.ts
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import cfonts from 'cfonts';
|
|
2
|
+
import { simpson, drawBox, SIMPSON_COLORS } from './colors.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* Display the RALPH ASCII header
|
|
5
|
+
* Display the RALPH ASCII header with welcome box
|
|
5
6
|
*/
|
|
6
7
|
export function displayHeader(): void {
|
|
7
|
-
|
|
8
|
+
// Welcome box like Claude Code
|
|
9
|
+
const welcomeText = simpson.pink('*') + ' Welcome to ' + simpson.yellow('Ralph') + '🍩';
|
|
10
|
+
console.log('');
|
|
11
|
+
console.log(drawBox(welcomeText, 2));
|
|
12
|
+
console.log('');
|
|
13
|
+
|
|
14
|
+
// ASCII art logo in Simpson yellow
|
|
15
|
+
cfonts.say('WIGGUM CLI', {
|
|
8
16
|
font: 'block',
|
|
9
|
-
colors: [
|
|
17
|
+
colors: [SIMPSON_COLORS.yellow],
|
|
10
18
|
letterSpacing: 1,
|
|
11
19
|
lineHeight: 1,
|
|
12
|
-
space:
|
|
20
|
+
space: false,
|
|
13
21
|
maxLength: 0,
|
|
14
22
|
});
|
|
15
23
|
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Display a minimal header (for subcommands)
|
|
27
|
+
*/
|
|
28
|
+
export function displayMinimalHeader(): void {
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(simpson.yellow('Ralph') + simpson.brown(' │ ') + 'AI-powered loop development');
|
|
31
|
+
console.log('');
|
|
32
|
+
}
|