codeep 1.0.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/LICENSE +201 -0
- package/README.md +576 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.js +421 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.js +1406 -0
- package/dist/components/AgentProgress.d.ts +33 -0
- package/dist/components/AgentProgress.js +97 -0
- package/dist/components/Export.d.ts +8 -0
- package/dist/components/Export.js +27 -0
- package/dist/components/Help.d.ts +2 -0
- package/dist/components/Help.js +3 -0
- package/dist/components/Input.d.ts +9 -0
- package/dist/components/Input.js +89 -0
- package/dist/components/Loading.d.ts +9 -0
- package/dist/components/Loading.js +31 -0
- package/dist/components/Login.d.ts +7 -0
- package/dist/components/Login.js +77 -0
- package/dist/components/Logo.d.ts +8 -0
- package/dist/components/Logo.js +89 -0
- package/dist/components/LogoutPicker.d.ts +8 -0
- package/dist/components/LogoutPicker.js +61 -0
- package/dist/components/Message.d.ts +10 -0
- package/dist/components/Message.js +234 -0
- package/dist/components/MessageList.d.ts +10 -0
- package/dist/components/MessageList.js +8 -0
- package/dist/components/ProjectPermission.d.ts +7 -0
- package/dist/components/ProjectPermission.js +52 -0
- package/dist/components/Search.d.ts +10 -0
- package/dist/components/Search.js +30 -0
- package/dist/components/SessionPicker.d.ts +9 -0
- package/dist/components/SessionPicker.js +88 -0
- package/dist/components/Sessions.d.ts +12 -0
- package/dist/components/Sessions.js +102 -0
- package/dist/components/Settings.d.ts +7 -0
- package/dist/components/Settings.js +162 -0
- package/dist/components/Status.d.ts +2 -0
- package/dist/components/Status.js +12 -0
- package/dist/config/config.test.d.ts +1 -0
- package/dist/config/config.test.js +157 -0
- package/dist/config/index.d.ts +121 -0
- package/dist/config/index.js +555 -0
- package/dist/config/providers.d.ts +43 -0
- package/dist/config/providers.js +82 -0
- package/dist/config/providers.test.d.ts +1 -0
- package/dist/config/providers.test.js +132 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +38 -0
- package/dist/utils/agent.d.ts +37 -0
- package/dist/utils/agent.js +627 -0
- package/dist/utils/codeReview.d.ts +36 -0
- package/dist/utils/codeReview.js +390 -0
- package/dist/utils/context.d.ts +49 -0
- package/dist/utils/context.js +216 -0
- package/dist/utils/diffPreview.d.ts +57 -0
- package/dist/utils/diffPreview.js +335 -0
- package/dist/utils/export.d.ts +19 -0
- package/dist/utils/export.js +94 -0
- package/dist/utils/git.d.ts +85 -0
- package/dist/utils/git.js +399 -0
- package/dist/utils/git.test.d.ts +1 -0
- package/dist/utils/git.test.js +193 -0
- package/dist/utils/history.d.ts +93 -0
- package/dist/utils/history.js +348 -0
- package/dist/utils/interactive.d.ts +34 -0
- package/dist/utils/interactive.js +206 -0
- package/dist/utils/keychain.d.ts +17 -0
- package/dist/utils/keychain.js +160 -0
- package/dist/utils/learning.d.ts +89 -0
- package/dist/utils/learning.js +330 -0
- package/dist/utils/logger.d.ts +33 -0
- package/dist/utils/logger.js +130 -0
- package/dist/utils/project.d.ts +86 -0
- package/dist/utils/project.js +415 -0
- package/dist/utils/project.test.d.ts +1 -0
- package/dist/utils/project.test.js +212 -0
- package/dist/utils/ratelimit.d.ts +26 -0
- package/dist/utils/ratelimit.js +132 -0
- package/dist/utils/ratelimit.test.d.ts +1 -0
- package/dist/utils/ratelimit.test.js +131 -0
- package/dist/utils/retry.d.ts +28 -0
- package/dist/utils/retry.js +109 -0
- package/dist/utils/retry.test.d.ts +1 -0
- package/dist/utils/retry.test.js +163 -0
- package/dist/utils/search.d.ts +11 -0
- package/dist/utils/search.js +29 -0
- package/dist/utils/shell.d.ts +45 -0
- package/dist/utils/shell.js +242 -0
- package/dist/utils/skills.d.ts +144 -0
- package/dist/utils/skills.js +1137 -0
- package/dist/utils/smartContext.d.ts +29 -0
- package/dist/utils/smartContext.js +441 -0
- package/dist/utils/tools.d.ts +224 -0
- package/dist/utils/tools.js +731 -0
- package/dist/utils/update.d.ts +22 -0
- package/dist/utils/update.js +128 -0
- package/dist/utils/validation.d.ts +28 -0
- package/dist/utils/validation.js +141 -0
- package/dist/utils/validation.test.d.ts +1 -0
- package/dist/utils/validation.test.js +164 -0
- package/dist/utils/verify.d.ts +78 -0
- package/dist/utils/verify.js +464 -0
- package/package.json +68 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent action history for undo/rollback functionality
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, rmSync, statSync } from 'fs';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
// In-memory current session
|
|
8
|
+
let currentSession = null;
|
|
9
|
+
// History storage path
|
|
10
|
+
const HISTORY_DIR = join(homedir(), '.codeep', 'history');
|
|
11
|
+
/**
|
|
12
|
+
* Initialize history directory
|
|
13
|
+
*/
|
|
14
|
+
function ensureHistoryDir() {
|
|
15
|
+
if (!existsSync(HISTORY_DIR)) {
|
|
16
|
+
mkdirSync(HISTORY_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generate unique ID
|
|
21
|
+
*/
|
|
22
|
+
function generateId() {
|
|
23
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Start a new action session
|
|
27
|
+
*/
|
|
28
|
+
export function startSession(prompt, projectRoot) {
|
|
29
|
+
const sessionId = generateId();
|
|
30
|
+
currentSession = {
|
|
31
|
+
id: sessionId,
|
|
32
|
+
startTime: Date.now(),
|
|
33
|
+
prompt,
|
|
34
|
+
actions: [],
|
|
35
|
+
projectRoot,
|
|
36
|
+
};
|
|
37
|
+
return sessionId;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* End current session and save to disk
|
|
41
|
+
*/
|
|
42
|
+
export function endSession() {
|
|
43
|
+
if (!currentSession)
|
|
44
|
+
return;
|
|
45
|
+
currentSession.endTime = Date.now();
|
|
46
|
+
// Only save if there were actions
|
|
47
|
+
if (currentSession.actions.length > 0) {
|
|
48
|
+
ensureHistoryDir();
|
|
49
|
+
const filename = `${currentSession.id}.json`;
|
|
50
|
+
const filepath = join(HISTORY_DIR, filename);
|
|
51
|
+
writeFileSync(filepath, JSON.stringify(currentSession, null, 2));
|
|
52
|
+
}
|
|
53
|
+
currentSession = null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Record a file write action (before it happens)
|
|
57
|
+
*/
|
|
58
|
+
export function recordWrite(path) {
|
|
59
|
+
if (!currentSession)
|
|
60
|
+
return null;
|
|
61
|
+
const record = {
|
|
62
|
+
id: generateId(),
|
|
63
|
+
timestamp: Date.now(),
|
|
64
|
+
type: 'write',
|
|
65
|
+
path,
|
|
66
|
+
previousExisted: existsSync(path),
|
|
67
|
+
};
|
|
68
|
+
// Save previous content if file existed
|
|
69
|
+
if (record.previousExisted) {
|
|
70
|
+
try {
|
|
71
|
+
record.previousContent = readFileSync(path, 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Could be binary or unreadable
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
currentSession.actions.push(record);
|
|
78
|
+
return record;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Record a file edit action (before it happens)
|
|
82
|
+
*/
|
|
83
|
+
export function recordEdit(path) {
|
|
84
|
+
if (!currentSession)
|
|
85
|
+
return null;
|
|
86
|
+
const record = {
|
|
87
|
+
id: generateId(),
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
type: 'edit',
|
|
90
|
+
path,
|
|
91
|
+
previousExisted: true,
|
|
92
|
+
};
|
|
93
|
+
// Save previous content
|
|
94
|
+
try {
|
|
95
|
+
record.previousContent = readFileSync(path, 'utf-8');
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Could be binary or unreadable
|
|
99
|
+
}
|
|
100
|
+
currentSession.actions.push(record);
|
|
101
|
+
return record;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Record a file/directory delete action (before it happens)
|
|
105
|
+
*/
|
|
106
|
+
export function recordDelete(path) {
|
|
107
|
+
if (!currentSession)
|
|
108
|
+
return null;
|
|
109
|
+
const record = {
|
|
110
|
+
id: generateId(),
|
|
111
|
+
timestamp: Date.now(),
|
|
112
|
+
type: 'delete',
|
|
113
|
+
path,
|
|
114
|
+
previousExisted: true,
|
|
115
|
+
};
|
|
116
|
+
try {
|
|
117
|
+
const stat = statSync(path);
|
|
118
|
+
record.wasDirectory = stat.isDirectory();
|
|
119
|
+
if (!record.wasDirectory) {
|
|
120
|
+
record.deletedContent = readFileSync(path, 'utf-8');
|
|
121
|
+
}
|
|
122
|
+
// Note: For directories, we can't easily restore all contents
|
|
123
|
+
// User should use git for that
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Ignore errors
|
|
127
|
+
}
|
|
128
|
+
currentSession.actions.push(record);
|
|
129
|
+
return record;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Record a mkdir action
|
|
133
|
+
*/
|
|
134
|
+
export function recordMkdir(path) {
|
|
135
|
+
if (!currentSession)
|
|
136
|
+
return null;
|
|
137
|
+
const record = {
|
|
138
|
+
id: generateId(),
|
|
139
|
+
timestamp: Date.now(),
|
|
140
|
+
type: 'mkdir',
|
|
141
|
+
path,
|
|
142
|
+
previousExisted: existsSync(path),
|
|
143
|
+
};
|
|
144
|
+
currentSession.actions.push(record);
|
|
145
|
+
return record;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Record a command execution (can't be undone, but tracked)
|
|
149
|
+
*/
|
|
150
|
+
export function recordCommand(command, args) {
|
|
151
|
+
if (!currentSession)
|
|
152
|
+
return null;
|
|
153
|
+
const record = {
|
|
154
|
+
id: generateId(),
|
|
155
|
+
timestamp: Date.now(),
|
|
156
|
+
type: 'command',
|
|
157
|
+
command,
|
|
158
|
+
args,
|
|
159
|
+
};
|
|
160
|
+
currentSession.actions.push(record);
|
|
161
|
+
return record;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get current session
|
|
165
|
+
*/
|
|
166
|
+
export function getCurrentSession() {
|
|
167
|
+
return currentSession;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Undo the last action in current session
|
|
171
|
+
*/
|
|
172
|
+
export function undoLastAction() {
|
|
173
|
+
if (!currentSession || currentSession.actions.length === 0) {
|
|
174
|
+
return { success: false, message: 'No actions to undo' };
|
|
175
|
+
}
|
|
176
|
+
// Find last non-undone action
|
|
177
|
+
const action = [...currentSession.actions].reverse().find(a => !a.undone);
|
|
178
|
+
if (!action) {
|
|
179
|
+
return { success: false, message: 'All actions already undone' };
|
|
180
|
+
}
|
|
181
|
+
return undoAction(action);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Undo a specific action
|
|
185
|
+
*/
|
|
186
|
+
export function undoAction(action) {
|
|
187
|
+
try {
|
|
188
|
+
switch (action.type) {
|
|
189
|
+
case 'write':
|
|
190
|
+
if (action.previousExisted && action.previousContent !== undefined) {
|
|
191
|
+
// Restore previous content
|
|
192
|
+
writeFileSync(action.path, action.previousContent);
|
|
193
|
+
action.undone = true;
|
|
194
|
+
return { success: true, message: `Restored: ${action.path}` };
|
|
195
|
+
}
|
|
196
|
+
else if (!action.previousExisted) {
|
|
197
|
+
// Delete the newly created file
|
|
198
|
+
if (existsSync(action.path)) {
|
|
199
|
+
unlinkSync(action.path);
|
|
200
|
+
}
|
|
201
|
+
action.undone = true;
|
|
202
|
+
return { success: true, message: `Deleted new file: ${action.path}` };
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
case 'edit':
|
|
206
|
+
if (action.previousContent !== undefined) {
|
|
207
|
+
writeFileSync(action.path, action.previousContent);
|
|
208
|
+
action.undone = true;
|
|
209
|
+
return { success: true, message: `Restored: ${action.path}` };
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
case 'delete':
|
|
213
|
+
if (action.deletedContent !== undefined) {
|
|
214
|
+
// Recreate the file
|
|
215
|
+
const dir = dirname(action.path);
|
|
216
|
+
if (!existsSync(dir)) {
|
|
217
|
+
mkdirSync(dir, { recursive: true });
|
|
218
|
+
}
|
|
219
|
+
writeFileSync(action.path, action.deletedContent);
|
|
220
|
+
action.undone = true;
|
|
221
|
+
return { success: true, message: `Restored deleted file: ${action.path}` };
|
|
222
|
+
}
|
|
223
|
+
else if (action.wasDirectory) {
|
|
224
|
+
return { success: false, message: `Cannot restore directory: ${action.path}. Use git checkout.` };
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
case 'mkdir':
|
|
228
|
+
if (!action.previousExisted && existsSync(action.path)) {
|
|
229
|
+
// Only remove if empty
|
|
230
|
+
try {
|
|
231
|
+
rmSync(action.path, { recursive: false });
|
|
232
|
+
action.undone = true;
|
|
233
|
+
return { success: true, message: `Removed directory: ${action.path}` };
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return { success: false, message: `Cannot remove non-empty directory: ${action.path}` };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
case 'command':
|
|
241
|
+
return { success: false, message: `Cannot undo command: ${action.command} ${action.args?.join(' ')}` };
|
|
242
|
+
}
|
|
243
|
+
return { success: false, message: 'Cannot undo this action' };
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
const err = error;
|
|
247
|
+
return { success: false, message: `Undo failed: ${err.message}` };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Undo all actions in current session
|
|
252
|
+
*/
|
|
253
|
+
export function undoAllActions() {
|
|
254
|
+
if (!currentSession || currentSession.actions.length === 0) {
|
|
255
|
+
return { success: false, results: ['No actions to undo'] };
|
|
256
|
+
}
|
|
257
|
+
const results = [];
|
|
258
|
+
let allSuccess = true;
|
|
259
|
+
// Undo in reverse order
|
|
260
|
+
const actions = [...currentSession.actions].reverse();
|
|
261
|
+
for (const action of actions) {
|
|
262
|
+
if (action.undone)
|
|
263
|
+
continue;
|
|
264
|
+
const result = undoAction(action);
|
|
265
|
+
results.push(result.message);
|
|
266
|
+
if (!result.success)
|
|
267
|
+
allSuccess = false;
|
|
268
|
+
}
|
|
269
|
+
return { success: allSuccess, results };
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get list of recent sessions
|
|
273
|
+
*/
|
|
274
|
+
export function getRecentSessions(limit = 10) {
|
|
275
|
+
ensureHistoryDir();
|
|
276
|
+
try {
|
|
277
|
+
const files = require('fs').readdirSync(HISTORY_DIR)
|
|
278
|
+
.filter((f) => f.endsWith('.json'))
|
|
279
|
+
.sort()
|
|
280
|
+
.reverse()
|
|
281
|
+
.slice(0, limit);
|
|
282
|
+
return files.map((f) => {
|
|
283
|
+
try {
|
|
284
|
+
const content = readFileSync(join(HISTORY_DIR, f), 'utf-8');
|
|
285
|
+
return JSON.parse(content);
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}).filter(Boolean);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get a specific session by ID
|
|
298
|
+
*/
|
|
299
|
+
export function getSession(sessionId) {
|
|
300
|
+
const filepath = join(HISTORY_DIR, `${sessionId}.json`);
|
|
301
|
+
if (!existsSync(filepath)) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
const content = readFileSync(filepath, 'utf-8');
|
|
306
|
+
return JSON.parse(content);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Format session for display
|
|
314
|
+
*/
|
|
315
|
+
export function formatSession(session) {
|
|
316
|
+
const date = new Date(session.startTime).toLocaleString();
|
|
317
|
+
const duration = session.endTime
|
|
318
|
+
? `${Math.round((session.endTime - session.startTime) / 1000)}s`
|
|
319
|
+
: 'ongoing';
|
|
320
|
+
const lines = [
|
|
321
|
+
`Session: ${session.id}`,
|
|
322
|
+
`Date: ${date}`,
|
|
323
|
+
`Duration: ${duration}`,
|
|
324
|
+
`Prompt: ${session.prompt.slice(0, 50)}${session.prompt.length > 50 ? '...' : ''}`,
|
|
325
|
+
`Actions (${session.actions.length}):`,
|
|
326
|
+
];
|
|
327
|
+
for (const action of session.actions) {
|
|
328
|
+
const status = action.undone ? '↩️' : '✓';
|
|
329
|
+
const target = action.path || `${action.command} ${action.args?.join(' ')}`;
|
|
330
|
+
lines.push(` ${status} ${action.type}: ${target}`);
|
|
331
|
+
}
|
|
332
|
+
return lines.join('\n');
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Clear all history
|
|
336
|
+
*/
|
|
337
|
+
export function clearHistory() {
|
|
338
|
+
ensureHistoryDir();
|
|
339
|
+
try {
|
|
340
|
+
const files = require('fs').readdirSync(HISTORY_DIR);
|
|
341
|
+
for (const f of files) {
|
|
342
|
+
unlinkSync(join(HISTORY_DIR, f));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// Ignore errors
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Mode - agent asks clarifying questions when needed
|
|
3
|
+
*/
|
|
4
|
+
export interface ClarificationQuestion {
|
|
5
|
+
question: string;
|
|
6
|
+
options?: string[];
|
|
7
|
+
type: 'choice' | 'text' | 'confirm';
|
|
8
|
+
context?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface InteractiveContext {
|
|
11
|
+
needsClarification: boolean;
|
|
12
|
+
questions: ClarificationQuestion[];
|
|
13
|
+
originalPrompt: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Analyze prompt for ambiguity and generate clarifying questions
|
|
17
|
+
*/
|
|
18
|
+
export declare function analyzeForClarification(prompt: string): InteractiveContext;
|
|
19
|
+
/**
|
|
20
|
+
* Format questions for display
|
|
21
|
+
*/
|
|
22
|
+
export declare function formatQuestions(context: InteractiveContext): string;
|
|
23
|
+
/**
|
|
24
|
+
* Parse user's answers to questions
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseAnswers(response: string, context: InteractiveContext): Map<number, string>;
|
|
27
|
+
/**
|
|
28
|
+
* Enhance prompt with user's answers
|
|
29
|
+
*/
|
|
30
|
+
export declare function enhancePromptWithAnswers(context: InteractiveContext, answers: Map<number, string>): string;
|
|
31
|
+
/**
|
|
32
|
+
* Generate interactive prompt for agent
|
|
33
|
+
*/
|
|
34
|
+
export declare function getInteractiveSystemPrompt(): string;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Mode - agent asks clarifying questions when needed
|
|
3
|
+
*/
|
|
4
|
+
// Patterns that indicate ambiguity
|
|
5
|
+
const AMBIGUOUS_PATTERNS = [
|
|
6
|
+
{
|
|
7
|
+
pattern: /add\s+(?:an?\s+)?auth(?:entication)?/i,
|
|
8
|
+
question: 'What type of authentication do you want?',
|
|
9
|
+
options: ['JWT tokens', 'Session-based', 'OAuth (Google/GitHub)', 'Basic auth'],
|
|
10
|
+
type: 'choice',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
pattern: /add\s+(?:a\s+)?database/i,
|
|
14
|
+
question: 'Which database do you want to use?',
|
|
15
|
+
options: ['PostgreSQL', 'MySQL', 'MongoDB', 'SQLite', 'Redis'],
|
|
16
|
+
type: 'choice',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
pattern: /create\s+(?:an?\s+)?api/i,
|
|
20
|
+
question: 'What type of API do you want?',
|
|
21
|
+
options: ['REST API', 'GraphQL', 'gRPC', 'WebSocket'],
|
|
22
|
+
type: 'choice',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
pattern: /add\s+(?:a\s+)?form/i,
|
|
26
|
+
question: 'What fields should the form have?',
|
|
27
|
+
type: 'text',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
pattern: /add\s+(?:a\s+)?component/i,
|
|
31
|
+
question: 'Should this be a functional or class component?',
|
|
32
|
+
options: ['Functional (hooks)', 'Class component'],
|
|
33
|
+
type: 'choice',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
pattern: /add\s+(?:a\s+)?test/i,
|
|
37
|
+
question: 'What testing framework should I use?',
|
|
38
|
+
options: ['Vitest', 'Jest', 'Mocha', 'PHPUnit', 'pytest'],
|
|
39
|
+
type: 'choice',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
pattern: /refactor/i,
|
|
43
|
+
question: 'What aspect should I focus on?',
|
|
44
|
+
options: ['Performance', 'Readability', 'Type safety', 'Modularity', 'All of the above'],
|
|
45
|
+
type: 'choice',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
pattern: /add\s+(?:a\s+)?state\s+management/i,
|
|
49
|
+
question: 'Which state management solution?',
|
|
50
|
+
options: ['React Context', 'Redux', 'Zustand', 'MobX', 'Jotai'],
|
|
51
|
+
type: 'choice',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
pattern: /add\s+(?:a\s+)?(?:css|style|styling)/i,
|
|
55
|
+
question: 'Which styling approach?',
|
|
56
|
+
options: ['CSS Modules', 'Tailwind CSS', 'Styled Components', 'SCSS/Sass', 'Plain CSS'],
|
|
57
|
+
type: 'choice',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
pattern: /deploy/i,
|
|
61
|
+
question: 'Where do you want to deploy?',
|
|
62
|
+
options: ['Vercel', 'Netlify', 'AWS', 'Docker', 'Heroku'],
|
|
63
|
+
type: 'choice',
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
/**
|
|
67
|
+
* Analyze prompt for ambiguity and generate clarifying questions
|
|
68
|
+
*/
|
|
69
|
+
export function analyzeForClarification(prompt) {
|
|
70
|
+
const questions = [];
|
|
71
|
+
for (const pattern of AMBIGUOUS_PATTERNS) {
|
|
72
|
+
if (pattern.pattern.test(prompt)) {
|
|
73
|
+
// Check if the prompt already specifies details
|
|
74
|
+
const hasDetails = checkForDetails(prompt, pattern);
|
|
75
|
+
if (!hasDetails) {
|
|
76
|
+
questions.push({
|
|
77
|
+
question: pattern.question,
|
|
78
|
+
options: pattern.options,
|
|
79
|
+
type: pattern.type,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
needsClarification: questions.length > 0,
|
|
86
|
+
questions,
|
|
87
|
+
originalPrompt: prompt,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if prompt already contains specific details
|
|
92
|
+
*/
|
|
93
|
+
function checkForDetails(prompt, pattern) {
|
|
94
|
+
if (!pattern.options)
|
|
95
|
+
return false;
|
|
96
|
+
const promptLower = prompt.toLowerCase();
|
|
97
|
+
// Check if any option is mentioned in the prompt
|
|
98
|
+
for (const option of pattern.options) {
|
|
99
|
+
const optionWords = option.toLowerCase().split(/[\s/()]+/);
|
|
100
|
+
for (const word of optionWords) {
|
|
101
|
+
if (word.length > 3 && promptLower.includes(word)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Format questions for display
|
|
110
|
+
*/
|
|
111
|
+
export function formatQuestions(context) {
|
|
112
|
+
if (!context.needsClarification) {
|
|
113
|
+
return '';
|
|
114
|
+
}
|
|
115
|
+
const lines = ['I have a few questions before proceeding:', ''];
|
|
116
|
+
for (let i = 0; i < context.questions.length; i++) {
|
|
117
|
+
const q = context.questions[i];
|
|
118
|
+
lines.push(`${i + 1}. ${q.question}`);
|
|
119
|
+
if (q.options) {
|
|
120
|
+
for (let j = 0; j < q.options.length; j++) {
|
|
121
|
+
lines.push(` ${String.fromCharCode(97 + j)}) ${q.options[j]}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
lines.push('');
|
|
125
|
+
}
|
|
126
|
+
lines.push('Please answer the questions or say "proceed" to let me decide.');
|
|
127
|
+
return lines.join('\n');
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Parse user's answers to questions
|
|
131
|
+
*/
|
|
132
|
+
export function parseAnswers(response, context) {
|
|
133
|
+
const answers = new Map();
|
|
134
|
+
const responseLower = response.toLowerCase();
|
|
135
|
+
// Check for "proceed" or similar
|
|
136
|
+
if (/proceed|continue|decide|auto|skip/i.test(response)) {
|
|
137
|
+
return answers; // Empty answers = let agent decide
|
|
138
|
+
}
|
|
139
|
+
// Try to match answers
|
|
140
|
+
for (let i = 0; i < context.questions.length; i++) {
|
|
141
|
+
const q = context.questions[i];
|
|
142
|
+
if (q.options) {
|
|
143
|
+
// Check if user selected an option by letter (a, b, c)
|
|
144
|
+
const letterMatch = response.match(new RegExp(`${i + 1}[.:\\s]*([a-z])`, 'i'));
|
|
145
|
+
if (letterMatch) {
|
|
146
|
+
const letterIndex = letterMatch[1].toLowerCase().charCodeAt(0) - 97;
|
|
147
|
+
if (letterIndex >= 0 && letterIndex < q.options.length) {
|
|
148
|
+
answers.set(i, q.options[letterIndex]);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Check if user mentioned an option directly
|
|
153
|
+
for (const option of q.options) {
|
|
154
|
+
if (responseLower.includes(option.toLowerCase())) {
|
|
155
|
+
answers.set(i, option);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// Free text - try to extract answer
|
|
162
|
+
const textMatch = response.match(new RegExp(`${i + 1}[.:\\s]*(.+?)(?=\\d+[.:]|$)`, 'i'));
|
|
163
|
+
if (textMatch) {
|
|
164
|
+
answers.set(i, textMatch[1].trim());
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return answers;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Enhance prompt with user's answers
|
|
172
|
+
*/
|
|
173
|
+
export function enhancePromptWithAnswers(context, answers) {
|
|
174
|
+
let enhancedPrompt = context.originalPrompt;
|
|
175
|
+
const specifications = [];
|
|
176
|
+
for (let i = 0; i < context.questions.length; i++) {
|
|
177
|
+
const q = context.questions[i];
|
|
178
|
+
const answer = answers.get(i);
|
|
179
|
+
if (answer) {
|
|
180
|
+
specifications.push(`${q.question} -> ${answer}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (specifications.length > 0) {
|
|
184
|
+
enhancedPrompt += '\n\nUser specifications:\n- ' + specifications.join('\n- ');
|
|
185
|
+
}
|
|
186
|
+
return enhancedPrompt;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Generate interactive prompt for agent
|
|
190
|
+
*/
|
|
191
|
+
export function getInteractiveSystemPrompt() {
|
|
192
|
+
return `
|
|
193
|
+
## Interactive Mode
|
|
194
|
+
When the task is ambiguous, you may ask clarifying questions before proceeding.
|
|
195
|
+
Format your questions like this:
|
|
196
|
+
|
|
197
|
+
CLARIFICATION_NEEDED:
|
|
198
|
+
1. [Question]
|
|
199
|
+
a) Option 1
|
|
200
|
+
b) Option 2
|
|
201
|
+
c) Option 3
|
|
202
|
+
|
|
203
|
+
The user will respond with their choices. Once you have enough information, proceed with the task.
|
|
204
|
+
Only ask questions when truly necessary - don't over-ask.
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure storage for API keys using native keychain
|
|
3
|
+
* - macOS: Keychain
|
|
4
|
+
* - Linux: Secret Service API (libsecret)
|
|
5
|
+
* - Windows: Credential Vault
|
|
6
|
+
*/
|
|
7
|
+
export interface SecureStorage {
|
|
8
|
+
getApiKey(providerId: string): Promise<string | null>;
|
|
9
|
+
setApiKey(providerId: string, apiKey: string): Promise<void>;
|
|
10
|
+
deleteApiKey(providerId: string): Promise<void>;
|
|
11
|
+
hasApiKey(providerId: string): Promise<boolean>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Migrate existing plain-text API keys to keychain
|
|
15
|
+
*/
|
|
16
|
+
export declare function migrateApiKeysToKeychain(config: any): Promise<void>;
|
|
17
|
+
export declare function createSecureStorage(config: any): SecureStorage;
|