@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/context.js DELETED
@@ -1,327 +0,0 @@
1
- /**
2
- * Context Manager - Manages conversation context and file state
3
- * Based on Aider's coder.py and GeminiCLI's context management
4
- * Uses gpt-tokenizer for accurate token counting (with fallback)
5
- */
6
- import { readFile } from 'fs/promises';
7
- import { existsSync } from 'fs';
8
- import { relative, resolve } from 'path';
9
- import { generateRepoMap } from './repoMap.js';
10
- import { getActiveSkill } from './skills.js';
11
- import { loadAllTools, getToolDefinitions } from './registry.js';
12
- import { getPromptProvider } from './prompts/provider.js';
13
- // Cached tokenizer (typed as any to handle missing module)
14
- let tokenEncoder = null;
15
- let tokenizerLoaded = false;
16
- /**
17
- * Load tokenizer lazily (handles case where gpt-tokenizer not installed)
18
- */
19
- async function loadTokenizer() {
20
- if (tokenizerLoaded)
21
- return;
22
- tokenizerLoaded = true;
23
- try {
24
- const mod = await import('gpt-tokenizer');
25
- tokenEncoder = mod.encode;
26
- }
27
- catch {
28
- // gpt-tokenizer not installed, will use fallback
29
- }
30
- }
31
- /**
32
- * Count tokens using gpt-tokenizer (accurate for GPT models)
33
- * Falls back to rough estimation if not available
34
- */
35
- async function countTokensAsync(text) {
36
- await loadTokenizer();
37
- if (tokenEncoder) {
38
- try {
39
- return tokenEncoder(text).length;
40
- }
41
- catch {
42
- // Fall through to estimation
43
- }
44
- }
45
- // Fallback: ~4 chars per token (rough but reasonable)
46
- return Math.ceil(text.length / 4);
47
- }
48
- /**
49
- * Synchronous token count (uses cached encoder or estimation)
50
- */
51
- function countTokens(text) {
52
- if (tokenEncoder) {
53
- try {
54
- return tokenEncoder(text).length;
55
- }
56
- catch {
57
- // Fall through
58
- }
59
- }
60
- return Math.ceil(text.length / 4);
61
- }
62
- /**
63
- * ContextManager class
64
- * Manages the conversation context, file state, and system prompts
65
- */
66
- export class ContextManager {
67
- cwd;
68
- activeFiles = new Set();
69
- readOnlyFiles = new Set();
70
- history = [];
71
- skill;
72
- tools = new Map();
73
- repoMapCache = '';
74
- repoMapTimestamp = 0;
75
- constructor(cwd) {
76
- this.cwd = cwd || process.cwd();
77
- this.skill = getActiveSkill();
78
- }
79
- /**
80
- * Initialize the context manager
81
- */
82
- async initialize() {
83
- // Load tools
84
- this.tools = await loadAllTools();
85
- // Generate initial repo map
86
- await this.refreshRepoMap();
87
- }
88
- /**
89
- * Add a file to active context
90
- */
91
- addFile(path, readOnly = false) {
92
- const fullPath = resolve(this.cwd, path);
93
- if (!existsSync(fullPath)) {
94
- return false;
95
- }
96
- if (readOnly) {
97
- this.readOnlyFiles.add(fullPath);
98
- this.activeFiles.delete(fullPath);
99
- }
100
- else {
101
- this.activeFiles.add(fullPath);
102
- this.readOnlyFiles.delete(fullPath);
103
- }
104
- return true;
105
- }
106
- /**
107
- * Remove a file from context
108
- */
109
- removeFile(path) {
110
- const fullPath = resolve(this.cwd, path);
111
- const wasActive = this.activeFiles.delete(fullPath);
112
- const wasReadOnly = this.readOnlyFiles.delete(fullPath);
113
- return wasActive || wasReadOnly;
114
- }
115
- /**
116
- * Add a message to history
117
- */
118
- addMessage(role, content) {
119
- this.history.push({
120
- role,
121
- content,
122
- timestamp: Date.now(),
123
- });
124
- }
125
- /**
126
- * Clear conversation history
127
- */
128
- clearHistory() {
129
- this.history = [];
130
- }
131
- /**
132
- * Get all files in context
133
- */
134
- getFiles() {
135
- return {
136
- active: Array.from(this.activeFiles).map(f => relative(this.cwd, f)),
137
- readOnly: Array.from(this.readOnlyFiles).map(f => relative(this.cwd, f)),
138
- };
139
- }
140
- /**
141
- * Read all files in context
142
- */
143
- async getFileContents() {
144
- const contents = new Map();
145
- for (const file of [...this.activeFiles, ...this.readOnlyFiles]) {
146
- try {
147
- const content = await readFile(file, 'utf-8');
148
- const relPath = relative(this.cwd, file);
149
- contents.set(relPath, content);
150
- }
151
- catch {
152
- // Skip files that can't be read
153
- }
154
- }
155
- return contents;
156
- }
157
- /**
158
- * Set the active skill
159
- */
160
- setSkill(skill) {
161
- this.skill = skill;
162
- }
163
- /**
164
- * Get the current skill
165
- */
166
- getSkill() {
167
- return this.skill;
168
- }
169
- /**
170
- * Refresh the repository map
171
- */
172
- async refreshRepoMap() {
173
- const now = Date.now();
174
- // Cache for 30 seconds
175
- if (this.repoMapCache && now - this.repoMapTimestamp < 30000) {
176
- return this.repoMapCache;
177
- }
178
- this.repoMapCache = await generateRepoMap(this.cwd);
179
- this.repoMapTimestamp = now;
180
- return this.repoMapCache;
181
- }
182
- /**
183
- * Build the system prompt
184
- */
185
- async buildSystemPrompt() {
186
- const provider = getPromptProvider();
187
- const systemPrompt = await provider.getSystemPrompt({
188
- cwd: this.cwd,
189
- skillPrompt: this.skill.systemPrompt
190
- });
191
- const parts = [systemPrompt];
192
- // Tool definitions
193
- const toolDefs = getToolDefinitions(this.tools);
194
- parts.push('\n## Available Tools\n' + toolDefs);
195
- // Active files
196
- const files = this.getFiles();
197
- if (files.active.length > 0 || files.readOnly.length > 0) {
198
- parts.push('\n## Context Files');
199
- if (files.active.length > 0) {
200
- parts.push('\nEditable files:');
201
- parts.push(files.active.map(f => `- ${f}`).join('\n'));
202
- }
203
- if (files.readOnly.length > 0) {
204
- parts.push('\nRead-only files:');
205
- parts.push(files.readOnly.map(f => `- ${f}`).join('\n'));
206
- }
207
- }
208
- // Repository map (condensed)
209
- const repoMap = await this.refreshRepoMap();
210
- if (repoMap) {
211
- const condensed = repoMap.split('\n').slice(0, 50).join('\n');
212
- parts.push('\n## Repository Structure\n' + condensed);
213
- if (repoMap.split('\n').length > 50) {
214
- parts.push('... (truncated)');
215
- }
216
- }
217
- return parts.join('\n');
218
- }
219
- /**
220
- * Build messages for LLM
221
- */
222
- async buildMessages(userMessage) {
223
- const messages = [];
224
- // System prompt
225
- const systemPrompt = await this.buildSystemPrompt();
226
- messages.push({ role: 'system', content: systemPrompt });
227
- // File contents as context
228
- const fileContents = await this.getFileContents();
229
- if (fileContents.size > 0) {
230
- let fileContext = '## File Contents\n\n';
231
- for (const [path, content] of fileContents) {
232
- const truncated = content.length > 10000
233
- ? content.slice(0, 10000) + '\n... (truncated)'
234
- : content;
235
- fileContext += `### ${path}\n\`\`\`\n${truncated}\n\`\`\`\n\n`;
236
- }
237
- messages.push({ role: 'user', content: fileContext });
238
- messages.push({ role: 'assistant', content: 'I have read the files. How can I help?' });
239
- }
240
- // Conversation history
241
- for (const msg of this.history) {
242
- messages.push({ role: msg.role, content: msg.content });
243
- }
244
- // Current message
245
- messages.push({ role: 'user', content: userMessage });
246
- return messages;
247
- }
248
- /**
249
- * Estimate token count using accurate tokenizer
250
- */
251
- async estimateTokenCount() {
252
- // Ensure tokenizer is loaded
253
- await loadTokenizer();
254
- let total = 0;
255
- // System prompt
256
- const systemPrompt = await this.buildSystemPrompt();
257
- total += countTokens(systemPrompt);
258
- // File contents
259
- const fileContents = await this.getFileContents();
260
- for (const content of fileContents.values()) {
261
- total += countTokens(content);
262
- }
263
- // History
264
- for (const msg of this.history) {
265
- total += countTokens(msg.content);
266
- }
267
- // Add overhead for message formatting (~4 tokens per message)
268
- total += (this.history.length + 2) * 4;
269
- return total;
270
- }
271
- /**
272
- * Get current state
273
- */
274
- getState() {
275
- return {
276
- cwd: this.cwd,
277
- activeFiles: this.activeFiles,
278
- readOnlyFiles: this.readOnlyFiles,
279
- history: this.history,
280
- skill: this.skill,
281
- tokenEstimate: 0, // Calculated async
282
- };
283
- }
284
- /**
285
- * Restore state
286
- */
287
- restoreState(state) {
288
- if (state.cwd)
289
- this.cwd = state.cwd;
290
- if (state.activeFiles)
291
- this.activeFiles = state.activeFiles;
292
- if (state.readOnlyFiles)
293
- this.readOnlyFiles = state.readOnlyFiles;
294
- if (state.history)
295
- this.history = state.history;
296
- if (state.skill)
297
- this.skill = state.skill;
298
- }
299
- /**
300
- * Get conversation history
301
- */
302
- getHistory() {
303
- return [...this.history];
304
- }
305
- /**
306
- * Get tools
307
- */
308
- getTools() {
309
- return this.tools;
310
- }
311
- /**
312
- * Get working directory
313
- */
314
- getCwd() {
315
- return this.cwd;
316
- }
317
- }
318
- // Create a global context manager instance
319
- let globalContext = null;
320
- export function getContextManager(cwd) {
321
- if (!globalContext) {
322
- globalContext = new ContextManager(cwd);
323
- }
324
- return globalContext;
325
- }
326
- // Export token counting utility for use elsewhere
327
- export { countTokens };
package/dist/index.d.ts DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Simple-CLI Orchestrator - Core loop: Observe -> Plan -> Verify -> Act -> Reflect
4
- * Must remain under 150 lines. Supports MoE routing with --moe flag.
5
- */
6
- import 'dotenv/config';
package/dist/index.js DELETED
@@ -1,109 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Simple-CLI Orchestrator - Core loop: Observe -> Plan -> Verify -> Act -> Reflect
4
- * Must remain under 150 lines. Supports MoE routing with --moe flag.
5
- */
6
- import 'dotenv/config';
7
- import * as readline from 'readline';
8
- import { generateRepoMap } from './repoMap.js';
9
- import { loadTools } from './registry.js';
10
- import { createProvider } from './providers/index.js';
11
- import { createMultiProvider } from './providers/multi.js';
12
- import { routeTask, loadTierConfig, formatRoutingDecision } from './router.js';
13
- import { getPromptProvider } from './prompts/provider.js';
14
- const YOLO_MODE = process.argv.includes('--yolo');
15
- const MOE_MODE = process.argv.includes('--moe');
16
- let tools;
17
- let history = [];
18
- const buildPrompt = async () => {
19
- const repoMap = await generateRepoMap();
20
- const toolDefs = Array.from(tools.values()).map(t => `- ${t.name}: ${t.description}`).join('\n');
21
- const provider = getPromptProvider();
22
- const systemPrompt = await provider.getSystemPrompt({ cwd: process.cwd() });
23
- return `${systemPrompt}
24
- ## Context\n${repoMap}
25
- ## Tools\n${toolDefs}
26
- ## Format: <thought>reasoning</thought> then {"tool": "name", "args": {}} or {"tool": "none", "message": "..."}`;
27
- };
28
- import { jsonrepair } from 'jsonrepair';
29
- const parseResponse = (r) => {
30
- const thought = r.match(/<thought>([\s\S]*?)<\/thought>/)?.[1]?.trim() || '';
31
- const jsonMatch = r.match(/\{[\s\S]*"tool"[\s\S]*\}/);
32
- let action = { tool: 'none', message: 'No action', args: {} };
33
- if (jsonMatch) {
34
- try {
35
- const repaired = jsonrepair(jsonMatch[0]);
36
- action = JSON.parse(repaired);
37
- }
38
- catch { /* use default */ }
39
- }
40
- return { thought, action };
41
- };
42
- const confirm = async (tool, args) => {
43
- if (YOLO_MODE)
44
- return true;
45
- const t = tools.get(tool);
46
- if (!t || t.permission === 'read')
47
- return !!t;
48
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
49
- return new Promise(res => {
50
- rl.question(`[CONFIRM] ${tool}(${JSON.stringify(args)})? (y/n) `, a => { rl.close(); res(a.toLowerCase() === 'y'); });
51
- });
52
- };
53
- const execTool = async (name, args) => {
54
- const t = tools.get(name);
55
- if (!t)
56
- return `Error: Tool "${name}" not found`;
57
- try {
58
- const r = await t.execute(args);
59
- return typeof r === 'string' ? r : JSON.stringify(r, null, 2);
60
- }
61
- catch (e) {
62
- return `Error: ${e instanceof Error ? e.message : e}`;
63
- }
64
- };
65
- const main = async () => {
66
- console.log(`Simple-CLI v0.1.0 ${MOE_MODE ? '[MoE]' : ''} ${YOLO_MODE ? '[YOLO]' : '[Safe]'}`);
67
- tools = await loadTools();
68
- const systemPrompt = await buildPrompt();
69
- const tierConfigs = MOE_MODE ? loadTierConfig() : null;
70
- const multiProvider = tierConfigs ? createMultiProvider(tierConfigs) : null;
71
- const singleProvider = !MOE_MODE ? createProvider() : null;
72
- const generate = async (input) => {
73
- if (MOE_MODE && multiProvider && tierConfigs) {
74
- const routing = await routeTask(input, (p) => multiProvider.generateWithTier(1, p, [{ role: 'user', content: input }]));
75
- console.log(formatRoutingDecision(routing, tierConfigs));
76
- return multiProvider.generateWithTier(routing.tier, systemPrompt, history);
77
- }
78
- return singleProvider.generateResponse(systemPrompt, history);
79
- };
80
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
81
- const prompt = () => {
82
- rl.question('\n> ', async (input) => {
83
- if (input.toLowerCase() === 'exit') {
84
- rl.close();
85
- return;
86
- }
87
- history.push({ role: 'user', content: input });
88
- const response = await generate(input);
89
- const { thought, action } = parseResponse(response);
90
- console.log(`\n[Thought] ${thought}`);
91
- if (action.tool !== 'none') {
92
- if (await confirm(action.tool, action.args || {})) {
93
- const result = await execTool(action.tool, action.args || {});
94
- console.log(`[Result] ${result}`);
95
- history.push({ role: 'assistant', content: response }, { role: 'user', content: `Tool result: ${result}` });
96
- }
97
- else
98
- console.log('[Skipped]');
99
- }
100
- else {
101
- console.log(`[Response] ${action.message}`);
102
- history.push({ role: 'assistant', content: response });
103
- }
104
- prompt();
105
- });
106
- };
107
- prompt();
108
- };
109
- main().catch(console.error);
@@ -1,98 +0,0 @@
1
- /**
2
- * Agent Core - Main agent loop with reflection and retry
3
- * Implements Aider-style reasoning and error recovery
4
- */
5
- import { Message } from '../context.js';
6
- import { EditBlock, EditResult } from './editor.js';
7
- import { GitManager } from './git.js';
8
- export interface AgentConfig {
9
- maxReflections: number;
10
- autoLint: boolean;
11
- autoTest: boolean;
12
- autoCommit: boolean;
13
- testCommand?: string;
14
- lintCommand?: string;
15
- }
16
- export interface ToolCall {
17
- tool: string;
18
- args: Record<string, unknown>;
19
- }
20
- export interface AgentResponse {
21
- thought?: string;
22
- action: ToolCall | {
23
- tool: 'none';
24
- message: string;
25
- };
26
- editBlocks?: EditBlock[];
27
- }
28
- export interface ReflectionContext {
29
- attempt: number;
30
- previousError: string;
31
- previousResponse: string;
32
- failedEdits: EditResult[];
33
- }
34
- /**
35
- * Parse LLM response into structured format
36
- */
37
- export declare function parseResponse(response: string): AgentResponse;
38
- /**
39
- * Build reflection prompt for retry
40
- */
41
- export declare function buildReflectionPrompt(context: ReflectionContext): string;
42
- /**
43
- * Build lint error prompt
44
- */
45
- export declare function buildLintErrorPrompt(file: string, errors: string): string;
46
- /**
47
- * Build test failure prompt
48
- */
49
- export declare function buildTestFailurePrompt(output: string): string;
50
- /**
51
- * Agent class - Main orchestration logic
52
- */
53
- export declare class Agent {
54
- private config;
55
- private git;
56
- private generateFn;
57
- private executeTool;
58
- private lintFn?;
59
- private testFn?;
60
- constructor(options: {
61
- config: AgentConfig;
62
- git: GitManager;
63
- generateFn: (messages: Message[]) => Promise<string>;
64
- executeTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
65
- lintFn?: (file: string) => Promise<{
66
- passed: boolean;
67
- output: string;
68
- }>;
69
- testFn?: () => Promise<{
70
- passed: boolean;
71
- output: string;
72
- }>;
73
- });
74
- /**
75
- * Process a user message with reflection loop
76
- */
77
- process(userMessage: string, history: Message[], systemPrompt: string): Promise<{
78
- response: AgentResponse;
79
- editResults: EditResult[];
80
- lintResults: Array<{
81
- file: string;
82
- passed: boolean;
83
- output: string;
84
- }>;
85
- testResult?: {
86
- passed: boolean;
87
- output: string;
88
- };
89
- commitResult?: {
90
- hash: string;
91
- message: string;
92
- };
93
- }>;
94
- }
95
- /**
96
- * Summarize conversation history to reduce tokens
97
- */
98
- export declare function summarizeHistory(history: Message[], generateFn: (messages: Message[]) => Promise<string>, maxMessages?: number): Promise<Message[]>;