@stan-chen/simple-cli 0.2.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 (87) hide show
  1. package/README.md +287 -0
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.js +259 -0
  4. package/dist/commands/add.d.ts +9 -0
  5. package/dist/commands/add.js +50 -0
  6. package/dist/commands/git/commit.d.ts +12 -0
  7. package/dist/commands/git/commit.js +97 -0
  8. package/dist/commands/git/status.d.ts +6 -0
  9. package/dist/commands/git/status.js +42 -0
  10. package/dist/commands/index.d.ts +16 -0
  11. package/dist/commands/index.js +376 -0
  12. package/dist/commands/mcp/status.d.ts +6 -0
  13. package/dist/commands/mcp/status.js +31 -0
  14. package/dist/commands/swarm.d.ts +36 -0
  15. package/dist/commands/swarm.js +236 -0
  16. package/dist/commands.d.ts +32 -0
  17. package/dist/commands.js +427 -0
  18. package/dist/context.d.ts +116 -0
  19. package/dist/context.js +327 -0
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.js +109 -0
  22. package/dist/lib/agent.d.ts +98 -0
  23. package/dist/lib/agent.js +281 -0
  24. package/dist/lib/editor.d.ts +74 -0
  25. package/dist/lib/editor.js +441 -0
  26. package/dist/lib/git.d.ts +164 -0
  27. package/dist/lib/git.js +351 -0
  28. package/dist/lib/ui.d.ts +159 -0
  29. package/dist/lib/ui.js +252 -0
  30. package/dist/mcp/client.d.ts +22 -0
  31. package/dist/mcp/client.js +81 -0
  32. package/dist/mcp/manager.d.ts +186 -0
  33. package/dist/mcp/manager.js +442 -0
  34. package/dist/prompts/provider.d.ts +22 -0
  35. package/dist/prompts/provider.js +78 -0
  36. package/dist/providers/index.d.ts +15 -0
  37. package/dist/providers/index.js +82 -0
  38. package/dist/providers/multi.d.ts +11 -0
  39. package/dist/providers/multi.js +28 -0
  40. package/dist/registry.d.ts +24 -0
  41. package/dist/registry.js +379 -0
  42. package/dist/repoMap.d.ts +5 -0
  43. package/dist/repoMap.js +79 -0
  44. package/dist/router.d.ts +41 -0
  45. package/dist/router.js +108 -0
  46. package/dist/skills.d.ts +25 -0
  47. package/dist/skills.js +288 -0
  48. package/dist/swarm/coordinator.d.ts +86 -0
  49. package/dist/swarm/coordinator.js +257 -0
  50. package/dist/swarm/index.d.ts +28 -0
  51. package/dist/swarm/index.js +29 -0
  52. package/dist/swarm/task.d.ts +104 -0
  53. package/dist/swarm/task.js +221 -0
  54. package/dist/swarm/types.d.ts +132 -0
  55. package/dist/swarm/types.js +37 -0
  56. package/dist/swarm/worker.d.ts +107 -0
  57. package/dist/swarm/worker.js +299 -0
  58. package/dist/tools/analyzeFile.d.ts +16 -0
  59. package/dist/tools/analyzeFile.js +43 -0
  60. package/dist/tools/git.d.ts +40 -0
  61. package/dist/tools/git.js +236 -0
  62. package/dist/tools/glob.d.ts +34 -0
  63. package/dist/tools/glob.js +165 -0
  64. package/dist/tools/grep.d.ts +53 -0
  65. package/dist/tools/grep.js +296 -0
  66. package/dist/tools/linter.d.ts +35 -0
  67. package/dist/tools/linter.js +349 -0
  68. package/dist/tools/listDir.d.ts +29 -0
  69. package/dist/tools/listDir.js +50 -0
  70. package/dist/tools/memory.d.ts +34 -0
  71. package/dist/tools/memory.js +215 -0
  72. package/dist/tools/readFiles.d.ts +25 -0
  73. package/dist/tools/readFiles.js +31 -0
  74. package/dist/tools/reloadTools.d.ts +11 -0
  75. package/dist/tools/reloadTools.js +22 -0
  76. package/dist/tools/runCommand.d.ts +32 -0
  77. package/dist/tools/runCommand.js +79 -0
  78. package/dist/tools/scraper.d.ts +31 -0
  79. package/dist/tools/scraper.js +211 -0
  80. package/dist/tools/writeFiles.d.ts +63 -0
  81. package/dist/tools/writeFiles.js +87 -0
  82. package/dist/ui/server.d.ts +5 -0
  83. package/dist/ui/server.js +74 -0
  84. package/dist/watcher.d.ts +35 -0
  85. package/dist/watcher.js +164 -0
  86. package/docs/assets/logo.jpeg +0 -0
  87. package/package.json +78 -0
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Agent Core - Main agent loop with reflection and retry
3
+ * Implements Aider-style reasoning and error recovery
4
+ */
5
+ import { applyFileEdits, parseEditBlocks } from './editor.js';
6
+ import { generateCommitMessage } from './git.js';
7
+ import * as ui from './ui.js';
8
+ /**
9
+ * Parse LLM response into structured format
10
+ */
11
+ export function parseResponse(response) {
12
+ // Extract thought/reasoning
13
+ const thoughtMatch = response.match(/<thought>([\s\S]*?)<\/thought>/i);
14
+ const thought = thoughtMatch?.[1]?.trim();
15
+ // Extract edit blocks
16
+ const editBlocks = parseEditBlocks(response);
17
+ // Extract tool action
18
+ const jsonMatch = response.match(/\{[\s\S]*"tool"[\s\S]*\}/);
19
+ let action = { tool: 'none', message: 'No action parsed' };
20
+ if (jsonMatch) {
21
+ try {
22
+ action = JSON.parse(jsonMatch[0]);
23
+ }
24
+ catch {
25
+ // Keep default
26
+ }
27
+ }
28
+ return { thought, action, editBlocks };
29
+ }
30
+ /**
31
+ * Build reflection prompt for retry
32
+ */
33
+ export function buildReflectionPrompt(context) {
34
+ let prompt = `## Reflection (Attempt ${context.attempt})
35
+
36
+ Your previous response had errors that need to be fixed.
37
+
38
+ ### Previous Error
39
+ ${context.previousError}
40
+
41
+ `;
42
+ if (context.failedEdits.length > 0) {
43
+ prompt += `### Failed Edits\n`;
44
+ for (const edit of context.failedEdits) {
45
+ prompt += `
46
+ **File:** ${edit.file}
47
+ **Error:** ${edit.error}
48
+ ${edit.suggestion ? `**Suggestion:** ${edit.suggestion}` : ''}
49
+ `;
50
+ }
51
+ }
52
+ prompt += `
53
+ ### Instructions
54
+ - Review the error carefully
55
+ - The SEARCH block must EXACTLY match existing content
56
+ - Include enough context to make the match unique
57
+ - Try again with corrected SEARCH/REPLACE blocks
58
+ `;
59
+ return prompt;
60
+ }
61
+ /**
62
+ * Build lint error prompt
63
+ */
64
+ export function buildLintErrorPrompt(file, errors) {
65
+ return `## Lint Errors Detected
66
+
67
+ The file \`${file}\` has syntax errors after your changes:
68
+
69
+ \`\`\`
70
+ ${errors}
71
+ \`\`\`
72
+
73
+ Please fix these errors by providing corrected SEARCH/REPLACE blocks.
74
+ Focus on the specific lines mentioned in the errors.
75
+ `;
76
+ }
77
+ /**
78
+ * Build test failure prompt
79
+ */
80
+ export function buildTestFailurePrompt(output) {
81
+ return `## Test Failure
82
+
83
+ The tests failed after your changes:
84
+
85
+ \`\`\`
86
+ ${output.slice(0, 2000)}${output.length > 2000 ? '\n... (truncated)' : ''}
87
+ \`\`\`
88
+
89
+ Please analyze the failure and fix the code.
90
+ Provide SEARCH/REPLACE blocks for the necessary changes.
91
+ `;
92
+ }
93
+ /**
94
+ * Agent class - Main orchestration logic
95
+ */
96
+ export class Agent {
97
+ config;
98
+ git;
99
+ generateFn;
100
+ executeTool;
101
+ lintFn;
102
+ testFn;
103
+ constructor(options) {
104
+ this.config = options.config;
105
+ this.git = options.git;
106
+ this.generateFn = options.generateFn;
107
+ this.executeTool = options.executeTool;
108
+ this.lintFn = options.lintFn;
109
+ this.testFn = options.testFn;
110
+ }
111
+ /**
112
+ * Process a user message with reflection loop
113
+ */
114
+ async process(userMessage, history, systemPrompt) {
115
+ let messages = [
116
+ { role: 'system', content: systemPrompt },
117
+ ...history,
118
+ { role: 'user', content: userMessage },
119
+ ];
120
+ let attempt = 0;
121
+ let editResults = [];
122
+ let lintResults = [];
123
+ let testResult;
124
+ let commitResult;
125
+ while (attempt < this.config.maxReflections) {
126
+ attempt++;
127
+ // Generate response
128
+ const llmResponse = await ui.spin(attempt === 1 ? 'Thinking...' : `Reflecting (attempt ${attempt})...`, () => this.generateFn(messages));
129
+ // Parse response
130
+ const response = parseResponse(llmResponse);
131
+ // Show thought if present
132
+ if (response.thought) {
133
+ ui.showThought(response.thought);
134
+ }
135
+ // Apply edit blocks if present
136
+ if (response.editBlocks && response.editBlocks.length > 0) {
137
+ ui.step(`Applying ${response.editBlocks.length} edit(s)...`);
138
+ editResults = await applyFileEdits(response.editBlocks);
139
+ const failed = editResults.filter(r => !r.success);
140
+ const succeeded = editResults.filter(r => r.success);
141
+ // Show results
142
+ for (const result of succeeded) {
143
+ ui.success(`✓ ${result.file}`);
144
+ if (result.diff) {
145
+ ui.showDiff(result.diff);
146
+ }
147
+ }
148
+ for (const result of failed) {
149
+ ui.error(`✗ ${result.file}: ${result.error}`);
150
+ if (result.suggestion) {
151
+ ui.note(result.suggestion, 'Suggestion');
152
+ }
153
+ }
154
+ // If any edits failed, reflect and retry
155
+ if (failed.length > 0 && attempt < this.config.maxReflections) {
156
+ const reflectionPrompt = buildReflectionPrompt({
157
+ attempt,
158
+ previousError: failed.map(f => f.error).join('\n'),
159
+ previousResponse: llmResponse,
160
+ failedEdits: failed,
161
+ });
162
+ messages = [
163
+ ...messages,
164
+ { role: 'assistant', content: llmResponse },
165
+ { role: 'user', content: reflectionPrompt },
166
+ ];
167
+ continue;
168
+ }
169
+ // Run linting if enabled and edits succeeded
170
+ if (this.config.autoLint && this.lintFn && succeeded.length > 0) {
171
+ const filesToLint = [...new Set(succeeded.map(r => r.file))];
172
+ for (const file of filesToLint) {
173
+ const lintResult = await ui.spin(`Linting ${file}...`, () => this.lintFn(file));
174
+ lintResults.push({ file, ...lintResult });
175
+ if (!lintResult.passed && attempt < this.config.maxReflections) {
176
+ ui.error(`Lint errors in ${file}`);
177
+ const lintPrompt = buildLintErrorPrompt(file, lintResult.output);
178
+ messages = [
179
+ ...messages,
180
+ { role: 'assistant', content: llmResponse },
181
+ { role: 'user', content: lintPrompt },
182
+ ];
183
+ continue;
184
+ }
185
+ }
186
+ }
187
+ // Run tests if enabled
188
+ if (this.config.autoTest && this.testFn && succeeded.length > 0) {
189
+ testResult = await ui.spin('Running tests...', () => this.testFn());
190
+ if (!testResult.passed && attempt < this.config.maxReflections) {
191
+ ui.error('Tests failed');
192
+ const testPrompt = buildTestFailurePrompt(testResult.output);
193
+ messages = [
194
+ ...messages,
195
+ { role: 'assistant', content: llmResponse },
196
+ { role: 'user', content: testPrompt },
197
+ ];
198
+ continue;
199
+ }
200
+ }
201
+ // Auto-commit if enabled and all checks passed
202
+ if (this.config.autoCommit && succeeded.length > 0) {
203
+ const allLintsPassed = lintResults.every(r => r.passed);
204
+ const testsPassed = !testResult || testResult.passed;
205
+ if (allLintsPassed && testsPassed) {
206
+ const diff = await this.git.diff();
207
+ if (diff) {
208
+ const commitMessage = await ui.spin('Generating commit message...', () => generateCommitMessage(diff, async (prompt) => {
209
+ const result = await this.generateFn([
210
+ { role: 'user', content: prompt },
211
+ ]);
212
+ return result;
213
+ }));
214
+ commitResult = await this.git.commit({
215
+ message: commitMessage,
216
+ files: succeeded.map(r => r.file),
217
+ }) || undefined;
218
+ if (commitResult) {
219
+ ui.success(`Committed: ${commitResult.hash} ${commitResult.message}`);
220
+ }
221
+ }
222
+ }
223
+ }
224
+ }
225
+ // Execute tool action if not edit blocks
226
+ if (response.action && 'tool' in response.action && response.action.tool !== 'none' && !response.editBlocks?.length) {
227
+ const { tool, args } = response.action;
228
+ ui.showToolCall(tool, args || {});
229
+ try {
230
+ const result = await this.executeTool(tool, args || {});
231
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
232
+ ui.showToolResult(resultStr);
233
+ }
234
+ catch (error) {
235
+ ui.error(`Tool error: ${error instanceof Error ? error.message : error}`);
236
+ }
237
+ }
238
+ return { response, editResults, lintResults, testResult, commitResult };
239
+ }
240
+ // Max reflections reached
241
+ ui.warning(`Max reflections (${this.config.maxReflections}) reached`);
242
+ return {
243
+ response: { action: { tool: 'none', message: 'Max reflections reached' } },
244
+ editResults,
245
+ lintResults,
246
+ testResult,
247
+ commitResult,
248
+ };
249
+ }
250
+ }
251
+ /**
252
+ * Summarize conversation history to reduce tokens
253
+ */
254
+ export async function summarizeHistory(history, generateFn, maxMessages = 10) {
255
+ if (history.length <= maxMessages) {
256
+ return history;
257
+ }
258
+ // Keep first message (usually important context) and last few messages
259
+ const keepFirst = 1;
260
+ const keepLast = maxMessages - 2;
261
+ const toSummarize = history.slice(keepFirst, -keepLast);
262
+ if (toSummarize.length === 0) {
263
+ return history;
264
+ }
265
+ const summaryPrompt = `Summarize this conversation history concisely, preserving key information:
266
+
267
+ ${toSummarize.map(m => `${m.role}: ${m.content}`).join('\n\n')}
268
+
269
+ Provide a brief summary that captures:
270
+ 1. Main topics discussed
271
+ 2. Key decisions made
272
+ 3. Important context for future messages`;
273
+ const summary = await generateFn([
274
+ { role: 'user', content: summaryPrompt },
275
+ ]);
276
+ return [
277
+ history[0], // Keep first
278
+ { role: 'system', content: `[Conversation Summary]\n${summary}` },
279
+ ...history.slice(-keepLast), // Keep last few
280
+ ];
281
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Intelligent Code Editor using ts-morph and fuzzy matching
3
+ * Provides Aider-style SEARCH/REPLACE with smart matching
4
+ */
5
+ import { SourceFile, Node } from 'ts-morph';
6
+ export interface EditBlock {
7
+ file: string;
8
+ search: string;
9
+ replace: string;
10
+ }
11
+ export interface EditResult {
12
+ file: string;
13
+ success: boolean;
14
+ applied: boolean;
15
+ error?: string;
16
+ diff?: string;
17
+ suggestion?: string;
18
+ }
19
+ /**
20
+ * Apply a single edit to content
21
+ */
22
+ export declare function applyEdit(content: string, search: string, replace: string): {
23
+ success: boolean;
24
+ content: string;
25
+ method: string;
26
+ suggestion?: string;
27
+ };
28
+ /**
29
+ * Apply edits to a file
30
+ */
31
+ export declare function applyFileEdits(edits: EditBlock[]): Promise<EditResult[]>;
32
+ /**
33
+ * Parse SEARCH/REPLACE blocks from LLM response
34
+ */
35
+ export declare function parseEditBlocks(response: string, validFiles?: string[]): EditBlock[];
36
+ /**
37
+ * TypeScript/JavaScript AST-aware editing using ts-morph
38
+ */
39
+ export declare class ASTEditor {
40
+ private project;
41
+ constructor();
42
+ /**
43
+ * Add a file to the project
44
+ */
45
+ addFile(path: string, content: string): SourceFile;
46
+ /**
47
+ * Get or create a source file
48
+ */
49
+ getSourceFile(path: string, content?: string): SourceFile | undefined;
50
+ /**
51
+ * Find a function by name
52
+ */
53
+ findFunction(sourceFile: SourceFile, name: string): Node | undefined;
54
+ /**
55
+ * Find a class by name
56
+ */
57
+ findClass(sourceFile: SourceFile, name: string): Node | undefined;
58
+ /**
59
+ * Rename a symbol across the project
60
+ */
61
+ renameSymbol(sourceFile: SourceFile, oldName: string, newName: string): boolean;
62
+ /**
63
+ * Get the modified content of a file
64
+ */
65
+ getContent(path: string): string | undefined;
66
+ /**
67
+ * Save all changes
68
+ */
69
+ saveAll(): Promise<void>;
70
+ }
71
+ /**
72
+ * Create a code editor instance
73
+ */
74
+ export declare function createEditor(): ASTEditor;