@stan-chen/simple-cli 0.2.2 → 0.2.4

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 (100) hide show
  1. package/README.md +58 -271
  2. package/dist/anyllm.py +62 -0
  3. package/dist/builtins.d.ts +726 -0
  4. package/dist/builtins.js +481 -0
  5. package/dist/cli.d.ts +0 -4
  6. package/dist/cli.js +37 -279
  7. package/dist/engine.d.ts +33 -0
  8. package/dist/engine.js +138 -0
  9. package/dist/learnings.d.ts +15 -0
  10. package/dist/learnings.js +54 -0
  11. package/dist/llm.d.ts +18 -0
  12. package/dist/llm.js +66 -0
  13. package/dist/mcp.d.ts +132 -0
  14. package/dist/mcp.js +43 -0
  15. package/dist/skills.d.ts +5 -16
  16. package/dist/skills.js +91 -253
  17. package/dist/tui.d.ts +1 -0
  18. package/dist/tui.js +10 -0
  19. package/package.json +88 -78
  20. package/dist/commands/add.d.ts +0 -9
  21. package/dist/commands/add.js +0 -50
  22. package/dist/commands/git/commit.d.ts +0 -12
  23. package/dist/commands/git/commit.js +0 -97
  24. package/dist/commands/git/status.d.ts +0 -6
  25. package/dist/commands/git/status.js +0 -42
  26. package/dist/commands/index.d.ts +0 -16
  27. package/dist/commands/index.js +0 -376
  28. package/dist/commands/mcp/status.d.ts +0 -6
  29. package/dist/commands/mcp/status.js +0 -31
  30. package/dist/commands/swarm.d.ts +0 -36
  31. package/dist/commands/swarm.js +0 -236
  32. package/dist/commands.d.ts +0 -32
  33. package/dist/commands.js +0 -427
  34. package/dist/context.d.ts +0 -116
  35. package/dist/context.js +0 -327
  36. package/dist/index.d.ts +0 -6
  37. package/dist/index.js +0 -109
  38. package/dist/lib/agent.d.ts +0 -98
  39. package/dist/lib/agent.js +0 -281
  40. package/dist/lib/editor.d.ts +0 -74
  41. package/dist/lib/editor.js +0 -441
  42. package/dist/lib/git.d.ts +0 -164
  43. package/dist/lib/git.js +0 -351
  44. package/dist/lib/ui.d.ts +0 -159
  45. package/dist/lib/ui.js +0 -252
  46. package/dist/mcp/client.d.ts +0 -22
  47. package/dist/mcp/client.js +0 -81
  48. package/dist/mcp/manager.d.ts +0 -186
  49. package/dist/mcp/manager.js +0 -446
  50. package/dist/prompts/provider.d.ts +0 -22
  51. package/dist/prompts/provider.js +0 -78
  52. package/dist/providers/index.d.ts +0 -15
  53. package/dist/providers/index.js +0 -82
  54. package/dist/providers/multi.d.ts +0 -11
  55. package/dist/providers/multi.js +0 -28
  56. package/dist/registry.d.ts +0 -24
  57. package/dist/registry.js +0 -379
  58. package/dist/repoMap.d.ts +0 -5
  59. package/dist/repoMap.js +0 -79
  60. package/dist/router.d.ts +0 -41
  61. package/dist/router.js +0 -108
  62. package/dist/swarm/coordinator.d.ts +0 -86
  63. package/dist/swarm/coordinator.js +0 -257
  64. package/dist/swarm/index.d.ts +0 -28
  65. package/dist/swarm/index.js +0 -29
  66. package/dist/swarm/task.d.ts +0 -104
  67. package/dist/swarm/task.js +0 -221
  68. package/dist/swarm/types.d.ts +0 -132
  69. package/dist/swarm/types.js +0 -37
  70. package/dist/swarm/worker.d.ts +0 -107
  71. package/dist/swarm/worker.js +0 -299
  72. package/dist/tools/analyzeFile.d.ts +0 -16
  73. package/dist/tools/analyzeFile.js +0 -43
  74. package/dist/tools/git.d.ts +0 -40
  75. package/dist/tools/git.js +0 -236
  76. package/dist/tools/glob.d.ts +0 -34
  77. package/dist/tools/glob.js +0 -165
  78. package/dist/tools/grep.d.ts +0 -53
  79. package/dist/tools/grep.js +0 -296
  80. package/dist/tools/linter.d.ts +0 -35
  81. package/dist/tools/linter.js +0 -349
  82. package/dist/tools/listDir.d.ts +0 -29
  83. package/dist/tools/listDir.js +0 -50
  84. package/dist/tools/memory.d.ts +0 -34
  85. package/dist/tools/memory.js +0 -215
  86. package/dist/tools/readFiles.d.ts +0 -25
  87. package/dist/tools/readFiles.js +0 -31
  88. package/dist/tools/reloadTools.d.ts +0 -11
  89. package/dist/tools/reloadTools.js +0 -22
  90. package/dist/tools/runCommand.d.ts +0 -32
  91. package/dist/tools/runCommand.js +0 -79
  92. package/dist/tools/scraper.d.ts +0 -31
  93. package/dist/tools/scraper.js +0 -211
  94. package/dist/tools/writeFiles.d.ts +0 -63
  95. package/dist/tools/writeFiles.js +0 -87
  96. package/dist/ui/server.d.ts +0 -5
  97. package/dist/ui/server.js +0 -74
  98. package/dist/watcher.d.ts +0 -35
  99. package/dist/watcher.js +0 -164
  100. /package/{docs/assets → assets}/logo.jpeg +0 -0
package/dist/cli.js CHANGED
@@ -1,287 +1,45 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Simple-CLI - Premium TUI agentic coding assistant
4
- * Powered by @clack/prompts.
5
- */
6
2
  import 'dotenv/config';
7
- import { text, confirm as clackConfirm, isCancel, select } from '@clack/prompts';
8
- import pc from 'picocolors';
9
- import { getContextManager } from './context.js';
10
- import { createProvider } from './providers/index.js';
11
- import { createMultiProvider } from './providers/multi.js';
12
- import { routeTask, loadTierConfig } from './router.js';
13
- import { executeCommand } from './commands.js';
14
- import { getMCPManager } from './mcp/manager.js';
15
- import { listSkills, setActiveSkill, getActiveSkill } from './skills.js';
16
3
  import { statSync } from 'fs';
17
- import { resolve } from 'path';
18
- import { runSwarm, parseSwarmArgs, printSwarmHelp } from './commands/swarm.js';
19
- import { jsonrepair } from 'jsonrepair';
20
- // CLI flags
21
- const YOLO_MODE = process.argv.includes('--yolo');
22
- const MOE_MODE = process.argv.includes('--moe');
23
- const SWARM_MODE = process.argv.includes('--swarm');
24
- const CLAW_MODE = process.argv.includes('--claw') || process.argv.includes('-claw');
25
- const DEBUG = process.argv.includes('--debug') || process.env.DEBUG === 'true';
26
- const VERSION = '0.2.2';
27
- // Handle --version and --help immediately
28
- if (process.argv.includes('--version') || process.argv.includes('-v')) {
29
- console.log(`Simple-CLI v${VERSION}`);
30
- process.exit(0);
31
- }
32
- if (process.argv.includes('--help') || process.argv.includes('-h')) {
33
- console.log(`
34
- ${pc.bgCyan(pc.black(' SIMPLE-CLI '))} ${pc.dim(`v${VERSION}`)}
35
-
36
- ${pc.bold('Usage:')}
37
- simple [target_dir] [prompt] [options]
38
-
39
- ${pc.bold('Options:')}
40
- --version, -v Show version
41
- --help, -h Show help
42
- --yolo Skip all confirmation prompts
43
- --moe Enable Mixture of Experts (multi-model)
44
- --swarm Enable Swarm orchestration mode
45
- --claw "intent" Enable OpenClaw JIT agent generation
46
- --debug Enable debug logging
47
-
48
- ${pc.bold('Examples:')}
49
- simple . "Build a login page"
50
- simple --claw "Security audit this project"
51
- simple --moe "Refactor this entire folder"
52
- `);
53
- process.exit(0);
54
- }
55
- // Handle --claw mode (JIT Agent Generation)
56
- if (CLAW_MODE) {
57
- const { execSync } = await import('child_process');
58
- const args = process.argv.slice(2).filter(a => !a.startsWith('-'));
59
- const intent = args.join(' ') || 'unspecified task';
60
- console.log(pc.cyan('🧬 Initiating JIT Agent Generation...'));
61
- console.log(pc.dim(`Intent: "${intent}"`));
62
- try {
63
- const output = execSync(`npx tsx tools/claw.ts run clawJit intent="${intent}"`, {
64
- cwd: process.cwd(),
65
- encoding: 'utf-8',
66
- stdio: 'inherit'
67
- });
68
- console.log(pc.green('\n✅ JIT Agent ready. Run `simple` to begin.'));
69
- process.exit(0);
70
- }
71
- catch (error) {
72
- console.error(pc.red('❌ Failed to initialize Claw mode:'), error);
73
- process.exit(1);
74
- }
75
- }
76
- // Handle --swarm mode
77
- if (SWARM_MODE) {
78
- if (process.argv.includes('--help')) {
79
- printSwarmHelp();
80
- process.exit(0);
81
- }
82
- const swarmOptions = parseSwarmArgs(process.argv.slice(2));
83
- swarmOptions.yolo = swarmOptions.yolo || YOLO_MODE;
84
- runSwarm(swarmOptions).catch(err => {
85
- console.error('Swarm error:', err);
86
- process.exit(1);
87
- });
88
- }
89
- else {
90
- main().catch(console.error);
91
- }
92
- function parseResponse(response) {
93
- const thought = response.match(/<thought>([\s\S]*?)<\/thought>/)?.[1]?.trim() || '';
94
- const jsonMatch = response.match(/\{[\s\S]*"tool"[\s\S]*\}/);
95
- let action = { tool: 'none', message: '', args: {} };
96
- if (jsonMatch) {
97
- try {
98
- action = JSON.parse(jsonrepair(jsonMatch[0]));
99
- }
100
- catch { /* skip */ }
101
- }
102
- return { thought, action };
103
- }
104
- async function confirm(tool, args, ctx) {
105
- if (YOLO_MODE)
106
- return true;
107
- const t = ctx.getTools().get(tool);
108
- if (!t || t.permission === 'read')
109
- return true;
110
- const confirmed = await clackConfirm({
111
- message: `Allow ${pc.cyan(tool)} with args ${pc.dim(JSON.stringify(args))}?`,
112
- initialValue: true,
113
- });
114
- return !isCancel(confirmed) && confirmed;
115
- }
116
- async function executeTool(name, args, ctx) {
117
- const tool = ctx.getTools().get(name);
118
- if (!tool)
119
- return `Error: Tool "${name}" not found`;
120
- try {
121
- const result = await tool.execute(args);
122
- return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
123
- }
124
- catch (error) {
125
- return `Error: ${error instanceof Error ? error.message : error}`;
126
- }
127
- }
4
+ import { Engine, Context, Registry } from './engine.js';
5
+ import { allBuiltins } from './builtins.js';
6
+ import { createLLM } from './llm.js';
7
+ import { MCP } from './mcp.js';
8
+ import { getActiveSkill } from './skills.js';
9
+ import { showBanner } from './tui.js';
128
10
  async function main() {
129
- console.clear();
130
- const args = process.argv.slice(2).filter(arg => !arg.startsWith('-'));
131
- let targetDir = process.cwd();
132
- if (args.length > 0) {
133
- try {
134
- if (statSync(args[0]).isDirectory()) {
135
- targetDir = resolve(args[0]);
136
- process.chdir(targetDir);
137
- // Reload .env from the new directory
138
- const { config } = await import('dotenv');
139
- config();
140
- args.shift();
141
- }
142
- }
143
- catch { /* ignored */ }
144
- }
145
- console.log(`\n ${pc.bgCyan(pc.black(' SIMPLE-CLI '))} ${pc.dim(`v${VERSION}`)} ${pc.green('●')} ${pc.cyan(targetDir)}\n`);
146
- console.log(`${pc.dim('○')} Initializing...`);
147
- const ctx = getContextManager(targetDir);
148
- await ctx.initialize();
149
- console.log(`${pc.green('●')} Ready.`);
150
- const mcpManager = getMCPManager();
151
- try {
152
- const configs = await mcpManager.loadConfig();
153
- if (configs.length > 0)
154
- await mcpManager.connectAll(configs);
155
- }
156
- catch (error) {
157
- if (DEBUG)
158
- console.error('MCP init error:', error);
159
- }
160
- const tierConfigs = MOE_MODE ? loadTierConfig() : null;
161
- const multiProvider = tierConfigs ? createMultiProvider(tierConfigs) : null;
162
- const singleProvider = !MOE_MODE ? createProvider() : null;
163
- const generate = async (input) => {
164
- const history = ctx.getHistory();
165
- const fullPrompt = await ctx.buildSystemPrompt();
166
- if (MOE_MODE && multiProvider && tierConfigs) {
167
- const routing = await routeTask(input, async (prompt) => {
168
- return multiProvider.generateWithTier(1, prompt, [{ role: 'user', content: input }]);
169
- });
170
- if (DEBUG)
171
- console.log(pc.dim(`[Routing] Tier: ${routing.tier}`));
172
- return multiProvider.generateWithTier(routing.tier, fullPrompt, history.map(m => ({ role: m.role, content: m.content })));
173
- }
174
- return singleProvider.generateResponse(fullPrompt, history.map(m => ({ role: m.role, content: m.content })));
175
- };
176
- let isFirstPrompt = true;
177
- while (true) {
178
- const skill = getActiveSkill();
179
- let input;
180
- // Support initial prompt from command line
181
- if (isFirstPrompt && args.length > 0) {
182
- input = args.join(' ');
183
- console.log(`\n${pc.magenta('➤')} ${pc.bold(input)}`);
184
- }
185
- else {
186
- input = await text({
187
- message: pc.dim(`[@${skill.name}]`) + ' Chat with Simple-CLI',
188
- placeholder: 'Ask anything or use /help',
189
- validate(value) {
190
- if (value.trim().length === 0)
191
- return 'Input required';
192
- }
193
- });
194
- }
195
- isFirstPrompt = false;
196
- if (isCancel(input)) {
197
- console.log(`\n${pc.dim('—')} Goodbye!`);
198
- mcpManager.disconnectAll();
199
- process.exit(0);
200
- }
201
- const trimmedInput = input.trim();
202
- // Slash command
203
- if (trimmedInput.startsWith('/')) {
204
- try {
205
- await executeCommand(trimmedInput, {
206
- cwd: ctx.getCwd(),
207
- activeFiles: ctx.getState().activeFiles,
208
- readOnlyFiles: ctx.getState().readOnlyFiles,
209
- history: ctx.getHistory(),
210
- io: {
211
- output: (m) => console.log(`\n${pc.dim('○')} ${m}`),
212
- error: (m) => console.log(`\n${pc.red('✖')} ${m}`),
213
- confirm: async (m) => {
214
- const c = await clackConfirm({ message: m });
215
- return !isCancel(c) && c;
216
- },
217
- prompt: async (m) => {
218
- const p = await text({ message: m });
219
- return isCancel(p) ? '' : p;
220
- }
221
- }
222
- });
223
- }
224
- catch (err) {
225
- console.log(`\n${pc.red('✖')} ${pc.red(String(err))}`);
226
- }
227
- continue;
228
- }
229
- // Skill switching
230
- if (trimmedInput.startsWith('@')) {
231
- const skillName = trimmedInput.slice(1).trim();
232
- if (skillName === 'list') {
233
- const skills = listSkills();
234
- const selected = await select({
235
- message: 'Select a skill',
236
- options: skills.map(s => ({ label: `@${s.name} - ${s.description}`, value: s.name }))
237
- });
238
- if (!isCancel(selected)) {
239
- const newSkill = setActiveSkill(selected);
240
- if (newSkill)
241
- ctx.setSkill(newSkill);
242
- }
243
- }
244
- else {
245
- const newSkill = setActiveSkill(skillName);
246
- if (newSkill) {
247
- ctx.setSkill(newSkill);
248
- console.log(`\n${pc.cyan('★')} Switched to @${newSkill.name}`);
249
- }
250
- else {
251
- console.log(`\n${pc.red('✖')} Skill @${skillName} not found.`);
252
- }
253
- }
11
+ const args = process.argv.slice(2);
12
+ // Handle optional directory argument
13
+ let cwd = process.cwd();
14
+ let interactive = true;
15
+ const remainingArgs = [];
16
+ for (const arg of args) {
17
+ if (arg === '--non-interactive') {
18
+ interactive = false;
254
19
  continue;
255
20
  }
256
- ctx.addMessage('user', trimmedInput);
257
- let steps = 0;
258
- while (steps < 15) {
259
- const response = await generate(trimmedInput);
260
- const { thought, action } = parseResponse(response);
261
- if (thought)
262
- console.log(`\n${pc.dim('💭')} ${pc.cyan(thought)}`);
263
- if (action.tool !== 'none') {
264
- if (await confirm(action.tool, action.args || {}, ctx)) {
265
- console.log(`${pc.yellow('⚙')} ${pc.dim(`Executing ${action.tool}...`)}`);
266
- const result = await executeTool(action.tool, action.args || {}, ctx);
267
- console.log(`${pc.green('✔')} ${pc.dim(result.length > 500 ? result.slice(0, 500) + '...' : result)}`);
268
- ctx.addMessage('assistant', response);
269
- ctx.addMessage('user', `Tool result: ${result}`);
270
- steps++;
271
- }
272
- else {
273
- console.log(`${pc.yellow('⚠')} Skipped.`);
274
- ctx.addMessage('assistant', response);
275
- break;
21
+ if (!arg.startsWith('-')) {
22
+ try {
23
+ if (statSync(arg).isDirectory()) {
24
+ cwd = arg;
25
+ process.chdir(cwd);
26
+ continue;
276
27
  }
277
28
  }
278
- else {
279
- const msg = action.message || response.replace(/<thought>[\s\S]*?<\/thought>/, '').trim();
280
- if (msg)
281
- console.log(`\n${pc.magenta('✦')} ${msg}`);
282
- ctx.addMessage('assistant', response);
283
- break;
284
- }
285
- }
286
- }
287
- }
29
+ catch { }
30
+ }
31
+ remainingArgs.push(arg);
32
+ }
33
+ const prompt = remainingArgs.filter(a => !a.startsWith('-')).join(' ');
34
+ const registry = new Registry();
35
+ allBuiltins.forEach(t => registry.tools.set(t.name, t));
36
+ await registry.loadProjectTools(cwd);
37
+ const mcp = new MCP();
38
+ const provider = createLLM();
39
+ const engine = new Engine(provider, registry, mcp);
40
+ const skill = await getActiveSkill(cwd);
41
+ const ctx = new Context(cwd, skill);
42
+ showBanner();
43
+ await engine.run(ctx, prompt || undefined, { interactive });
44
+ }
45
+ main().catch(console.error);
@@ -0,0 +1,33 @@
1
+ import { MCP } from './mcp.js';
2
+ import { Skill } from './skills.js';
3
+ export interface Message {
4
+ role: 'user' | 'assistant' | 'system';
5
+ content: string;
6
+ }
7
+ export interface Tool {
8
+ name: string;
9
+ description: string;
10
+ execute: (args: any) => Promise<any>;
11
+ }
12
+ export declare class Context {
13
+ history: Message[];
14
+ activeFiles: Set<string>;
15
+ cwd: string;
16
+ skill: Skill;
17
+ constructor(cwd: string, skill: Skill);
18
+ buildPrompt(tools: Map<string, Tool>): Promise<string>;
19
+ }
20
+ export declare class Registry {
21
+ tools: Map<string, Tool>;
22
+ loadProjectTools(cwd: string): Promise<void>;
23
+ }
24
+ export declare class Engine {
25
+ private llm;
26
+ private registry;
27
+ private mcp;
28
+ private learningManager;
29
+ constructor(llm: any, registry: Registry, mcp: MCP);
30
+ run(ctx: Context, initialPrompt?: string, options?: {
31
+ interactive: boolean;
32
+ }): Promise<void>;
33
+ }
package/dist/engine.js ADDED
@@ -0,0 +1,138 @@
1
+ import { readdir } from 'fs/promises';
2
+ import { existsSync } from 'fs';
3
+ import { join, relative } from 'path';
4
+ import { pathToFileURL } from 'url';
5
+ import pc from 'picocolors';
6
+ import { text, isCancel } from '@clack/prompts';
7
+ import { LearningManager } from './learnings.js';
8
+ async function getRepoMap(cwd) {
9
+ const files = [];
10
+ async function walk(dir) {
11
+ const entries = await readdir(dir, { withFileTypes: true });
12
+ for (const e of entries) {
13
+ if (['node_modules', '.git', 'dist'].includes(e.name))
14
+ continue;
15
+ const res = join(dir, e.name);
16
+ if (e.isDirectory())
17
+ await walk(res);
18
+ else if (['.ts', '.js', '.py', '.md'].includes(res.slice(-3)))
19
+ files.push(relative(cwd, res));
20
+ }
21
+ }
22
+ try {
23
+ await walk(cwd);
24
+ }
25
+ catch { }
26
+ return files.slice(0, 50).join('\n');
27
+ }
28
+ export class Context {
29
+ history = [];
30
+ activeFiles = new Set();
31
+ cwd;
32
+ skill;
33
+ constructor(cwd, skill) {
34
+ this.cwd = cwd;
35
+ this.skill = skill;
36
+ }
37
+ async buildPrompt(tools) {
38
+ const repoMap = await getRepoMap(this.cwd);
39
+ const toolDefs = Array.from(tools.values()).map(t => `- ${t.name}: ${t.description}`).join('\n');
40
+ return `${this.skill.systemPrompt}\n\n## Tools\n${toolDefs}\n\n## Repository\n${repoMap}\n\n## Active Files\n${Array.from(this.activeFiles).map(f => relative(this.cwd, f)).join(', ')}`;
41
+ }
42
+ }
43
+ export class Registry {
44
+ tools = new Map();
45
+ async loadProjectTools(cwd) {
46
+ const dir = join(cwd, '.agent', 'tools');
47
+ if (!existsSync(dir))
48
+ return;
49
+ for (const f of await readdir(dir)) {
50
+ if (f.endsWith('.ts') || f.endsWith('.js')) {
51
+ const mod = await import(pathToFileURL(join(dir, f)).href);
52
+ const t = mod.tool || mod.default;
53
+ if (t?.name)
54
+ this.tools.set(t.name, t);
55
+ }
56
+ }
57
+ }
58
+ }
59
+ export class Engine {
60
+ llm;
61
+ registry;
62
+ mcp;
63
+ learningManager;
64
+ constructor(llm, registry, mcp) {
65
+ this.llm = llm;
66
+ this.registry = registry;
67
+ this.mcp = mcp;
68
+ this.learningManager = new LearningManager(process.cwd());
69
+ }
70
+ async run(ctx, initialPrompt, options = { interactive: true }) {
71
+ await this.learningManager.load();
72
+ let input = initialPrompt;
73
+ await this.mcp.init();
74
+ (await this.mcp.getTools()).forEach(t => this.registry.tools.set(t.name, t));
75
+ // Ensure tools are loaded for the context cwd
76
+ await this.registry.loadProjectTools(ctx.cwd);
77
+ while (true) {
78
+ if (!input) {
79
+ if (!options.interactive)
80
+ break;
81
+ const res = await text({ message: pc.cyan('Chat') });
82
+ if (isCancel(res))
83
+ break;
84
+ input = res;
85
+ }
86
+ ctx.history.push({ role: 'user', content: input });
87
+ // RAG: Inject learnings
88
+ let prompt = await ctx.buildPrompt(this.registry.tools);
89
+ const userHistory = ctx.history.filter(m => m.role === 'user' && !['Continue.', 'Fix the error.'].includes(m.content));
90
+ const lastUserMsg = userHistory[userHistory.length - 1]?.content || '';
91
+ const query = (input && !['Continue.', 'Fix the error.'].includes(input)) ? input : lastUserMsg;
92
+ const learnings = await this.learningManager.search(query);
93
+ if (learnings.length > 0) {
94
+ prompt += `\n\n## Past Learnings\n${learnings.map(l => `- ${l}`).join('\n')}`;
95
+ }
96
+ const response = await this.llm.generate(prompt, ctx.history);
97
+ const { thought, tool, args, message } = response;
98
+ if (thought)
99
+ console.log(pc.dim(`💭 ${thought}`));
100
+ if (tool && tool !== 'none') {
101
+ const t = this.registry.tools.get(tool);
102
+ if (t) {
103
+ console.log(pc.yellow(`⚙ Executing ${tool}...`));
104
+ try {
105
+ const result = await t.execute(args);
106
+ // Reload tools if create_tool was used
107
+ if (tool === 'create_tool') {
108
+ await this.registry.loadProjectTools(ctx.cwd);
109
+ console.log(pc.magenta('🔄 Tools reloaded.'));
110
+ }
111
+ ctx.history.push({ role: 'assistant', content: JSON.stringify(response) });
112
+ ctx.history.push({ role: 'user', content: `Result: ${JSON.stringify(result)}` });
113
+ // Reflection: Learning loop
114
+ const reflectPrompt = "Analyze the previous tool execution. What went well? What failed? Summarize as a concise learning point for future reference.";
115
+ const reflection = await this.llm.generate(reflectPrompt, [...ctx.history, { role: 'user', content: reflectPrompt }]);
116
+ if (reflection.message) {
117
+ // Find the relevant task description
118
+ const userHistory = ctx.history.filter(m => m.role === 'user' && !['Continue.', 'Fix the error.'].includes(m.content));
119
+ const task = (input && !['Continue.', 'Fix the error.'].includes(input)) ? input : (userHistory[userHistory.length - 1]?.content || 'Task');
120
+ await this.learningManager.add(task, reflection.message);
121
+ console.log(pc.blue(`📝 Learning stored: ${reflection.message}`));
122
+ }
123
+ input = 'The previous tool execution was successful. Proceed with the next step.';
124
+ continue;
125
+ }
126
+ catch (e) {
127
+ ctx.history.push({ role: 'user', content: `Error: ${e.message}` });
128
+ input = 'Fix the error.';
129
+ continue;
130
+ }
131
+ }
132
+ }
133
+ console.log(`\n${pc.green('🤖')} ${message || response.raw}\n`);
134
+ ctx.history.push({ role: 'assistant', content: message || response.raw });
135
+ input = undefined;
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,15 @@
1
+ export interface Learning {
2
+ id: string;
3
+ task: string;
4
+ reflection: string;
5
+ timestamp: number;
6
+ }
7
+ export declare class LearningManager {
8
+ private learnings;
9
+ private path;
10
+ constructor(cwd: string);
11
+ load(): Promise<void>;
12
+ save(): Promise<void>;
13
+ add(task: string, reflection: string): Promise<void>;
14
+ search(query: string): Promise<string[]>;
15
+ }
@@ -0,0 +1,54 @@
1
+ import { readFile, writeFile } from 'fs/promises';
2
+ import { existsSync, mkdirSync } from 'fs';
3
+ import { join } from 'path';
4
+ export class LearningManager {
5
+ learnings = [];
6
+ path;
7
+ constructor(cwd) {
8
+ const agentDir = join(cwd, '.agent');
9
+ if (!existsSync(agentDir)) {
10
+ try {
11
+ mkdirSync(agentDir, { recursive: true });
12
+ }
13
+ catch { }
14
+ }
15
+ this.path = join(agentDir, 'learnings.json');
16
+ }
17
+ async load() {
18
+ if (!existsSync(this.path))
19
+ return;
20
+ try {
21
+ const data = await readFile(this.path, 'utf-8');
22
+ this.learnings = JSON.parse(data);
23
+ }
24
+ catch {
25
+ this.learnings = [];
26
+ }
27
+ }
28
+ async save() {
29
+ await writeFile(this.path, JSON.stringify(this.learnings, null, 2));
30
+ }
31
+ async add(task, reflection) {
32
+ this.learnings.push({
33
+ id: Date.now().toString(),
34
+ task,
35
+ reflection,
36
+ timestamp: Date.now()
37
+ });
38
+ await this.save();
39
+ }
40
+ async search(query) {
41
+ if (!query)
42
+ return [];
43
+ const keywords = query.toLowerCase().split(/\s+/).filter(w => w.length > 3);
44
+ if (keywords.length === 0)
45
+ return [];
46
+ return this.learnings
47
+ .filter(l => {
48
+ const text = (l.task + ' ' + l.reflection).toLowerCase();
49
+ return keywords.some(k => text.includes(k));
50
+ })
51
+ .map(l => l.reflection)
52
+ .slice(0, 5);
53
+ }
54
+ }
package/dist/llm.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ export interface LLMResponse {
2
+ thought: string;
3
+ tool: string;
4
+ args: any;
5
+ message?: string;
6
+ raw: string;
7
+ }
8
+ export declare class LLM {
9
+ private config;
10
+ constructor(config: {
11
+ provider: string;
12
+ model: string;
13
+ apiKey?: string;
14
+ });
15
+ generate(system: string, history: any[]): Promise<LLMResponse>;
16
+ private parse;
17
+ }
18
+ export declare const createLLM: (model?: string) => LLM;
package/dist/llm.js ADDED
@@ -0,0 +1,66 @@
1
+ import { spawn } from 'child_process';
2
+ import { dirname, join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { jsonrepair } from 'jsonrepair';
5
+ import fs from 'fs';
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ export class LLM {
8
+ config;
9
+ constructor(config) {
10
+ this.config = config;
11
+ }
12
+ async generate(system, history) {
13
+ const payload = {
14
+ ...this.config,
15
+ messages: [{ role: 'system', content: system }, ...history],
16
+ api_key: this.config.apiKey || process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY || process.env.ANTHROPIC_API_KEY
17
+ };
18
+ return new Promise((resolve, reject) => {
19
+ // Find python bridge
20
+ let py = join(__dirname, 'anyllm.py');
21
+ if (!fs.existsSync(py))
22
+ py = join(process.cwd(), 'src/lib/anyllm.py'); // Fallback
23
+ const child = spawn('python3', [py]);
24
+ let out = '';
25
+ let err = '';
26
+ child.stdout.on('data', d => out += d);
27
+ child.stderr.on('data', d => err += d);
28
+ child.on('close', code => {
29
+ if (code !== 0)
30
+ return reject(new Error(err));
31
+ try {
32
+ const res = JSON.parse(out);
33
+ if (res.error)
34
+ return reject(new Error(res.error));
35
+ resolve(this.parse(res.content));
36
+ }
37
+ catch (e) {
38
+ reject(e);
39
+ }
40
+ });
41
+ child.stdin.write(JSON.stringify(payload));
42
+ child.stdin.end();
43
+ });
44
+ }
45
+ parse(raw) {
46
+ try {
47
+ const repaired = jsonrepair(raw.trim().match(/\{[\s\S]*\}/)?.[0] || raw);
48
+ const p = JSON.parse(repaired);
49
+ return {
50
+ thought: p.thought || '',
51
+ tool: (p.tool || p.command || 'none').toLowerCase(),
52
+ args: p.args || p.parameters || {},
53
+ message: p.message || '',
54
+ raw
55
+ };
56
+ }
57
+ catch {
58
+ return { thought: '', tool: 'none', args: {}, message: raw, raw };
59
+ }
60
+ }
61
+ }
62
+ export const createLLM = (model) => {
63
+ const m = model || process.env.MODEL || 'openai:gpt-5.2-codex';
64
+ const [p, n] = m.includes(':') ? m.split(':') : ['openai', m];
65
+ return new LLM({ provider: p, model: n });
66
+ };