@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.
- package/README.md +287 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +259 -0
- package/dist/commands/add.d.ts +9 -0
- package/dist/commands/add.js +50 -0
- package/dist/commands/git/commit.d.ts +12 -0
- package/dist/commands/git/commit.js +97 -0
- package/dist/commands/git/status.d.ts +6 -0
- package/dist/commands/git/status.js +42 -0
- package/dist/commands/index.d.ts +16 -0
- package/dist/commands/index.js +376 -0
- package/dist/commands/mcp/status.d.ts +6 -0
- package/dist/commands/mcp/status.js +31 -0
- package/dist/commands/swarm.d.ts +36 -0
- package/dist/commands/swarm.js +236 -0
- package/dist/commands.d.ts +32 -0
- package/dist/commands.js +427 -0
- package/dist/context.d.ts +116 -0
- package/dist/context.js +327 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +109 -0
- package/dist/lib/agent.d.ts +98 -0
- package/dist/lib/agent.js +281 -0
- package/dist/lib/editor.d.ts +74 -0
- package/dist/lib/editor.js +441 -0
- package/dist/lib/git.d.ts +164 -0
- package/dist/lib/git.js +351 -0
- package/dist/lib/ui.d.ts +159 -0
- package/dist/lib/ui.js +252 -0
- package/dist/mcp/client.d.ts +22 -0
- package/dist/mcp/client.js +81 -0
- package/dist/mcp/manager.d.ts +186 -0
- package/dist/mcp/manager.js +442 -0
- package/dist/prompts/provider.d.ts +22 -0
- package/dist/prompts/provider.js +78 -0
- package/dist/providers/index.d.ts +15 -0
- package/dist/providers/index.js +82 -0
- package/dist/providers/multi.d.ts +11 -0
- package/dist/providers/multi.js +28 -0
- package/dist/registry.d.ts +24 -0
- package/dist/registry.js +379 -0
- package/dist/repoMap.d.ts +5 -0
- package/dist/repoMap.js +79 -0
- package/dist/router.d.ts +41 -0
- package/dist/router.js +108 -0
- package/dist/skills.d.ts +25 -0
- package/dist/skills.js +288 -0
- package/dist/swarm/coordinator.d.ts +86 -0
- package/dist/swarm/coordinator.js +257 -0
- package/dist/swarm/index.d.ts +28 -0
- package/dist/swarm/index.js +29 -0
- package/dist/swarm/task.d.ts +104 -0
- package/dist/swarm/task.js +221 -0
- package/dist/swarm/types.d.ts +132 -0
- package/dist/swarm/types.js +37 -0
- package/dist/swarm/worker.d.ts +107 -0
- package/dist/swarm/worker.js +299 -0
- package/dist/tools/analyzeFile.d.ts +16 -0
- package/dist/tools/analyzeFile.js +43 -0
- package/dist/tools/git.d.ts +40 -0
- package/dist/tools/git.js +236 -0
- package/dist/tools/glob.d.ts +34 -0
- package/dist/tools/glob.js +165 -0
- package/dist/tools/grep.d.ts +53 -0
- package/dist/tools/grep.js +296 -0
- package/dist/tools/linter.d.ts +35 -0
- package/dist/tools/linter.js +349 -0
- package/dist/tools/listDir.d.ts +29 -0
- package/dist/tools/listDir.js +50 -0
- package/dist/tools/memory.d.ts +34 -0
- package/dist/tools/memory.js +215 -0
- package/dist/tools/readFiles.d.ts +25 -0
- package/dist/tools/readFiles.js +31 -0
- package/dist/tools/reloadTools.d.ts +11 -0
- package/dist/tools/reloadTools.js +22 -0
- package/dist/tools/runCommand.d.ts +32 -0
- package/dist/tools/runCommand.js +79 -0
- package/dist/tools/scraper.d.ts +31 -0
- package/dist/tools/scraper.js +211 -0
- package/dist/tools/writeFiles.d.ts +63 -0
- package/dist/tools/writeFiles.js +87 -0
- package/dist/ui/server.d.ts +5 -0
- package/dist/ui/server.js +74 -0
- package/dist/watcher.d.ts +35 -0
- package/dist/watcher.js +164 -0
- package/docs/assets/logo.jpeg +0 -0
- package/package.json +78 -0
package/dist/skills.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills/Presets System
|
|
3
|
+
* Based on OpenHands skills and Aider prompts
|
|
4
|
+
* Provides specialized behavior presets for different tasks
|
|
5
|
+
*/
|
|
6
|
+
import { readFile, writeFile, readdir } from 'fs/promises';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
// Built-in skills
|
|
10
|
+
export const builtinSkills = {
|
|
11
|
+
// Code editing skill (default)
|
|
12
|
+
code: {
|
|
13
|
+
name: 'code',
|
|
14
|
+
description: 'General coding assistant with file editing and custom skill creation capabilities',
|
|
15
|
+
systemPrompt: `You are a coding assistant. You help users write, modify, and debug code.
|
|
16
|
+
|
|
17
|
+
## Custom Skills (Self-Evolution)
|
|
18
|
+
You can create your own specialized tools in \`skills/\`, \`scripts/\`, or \`tools/\`:
|
|
19
|
+
1. **TypeScript/JS**: Standard native exports.
|
|
20
|
+
2. **Scripts & Binaries**:
|
|
21
|
+
- Write any script (Python, Bash, PowerShell, etc.) or binary.
|
|
22
|
+
- **Documentation**: Provide a matching \`.md\` or \`.txt\` file (e.g., \`tool.py\` + \`tool.md\`).
|
|
23
|
+
- **Internal Documentation**: Alternatively, put Markdown in a comment block at the very top of your script.
|
|
24
|
+
|
|
25
|
+
3. **AI Attribution**: All self-created tools MUST include a marker as the first line:
|
|
26
|
+
- **Scripts**: A comment e.g. \`# [Simple-CLI AI-Created]\` or \`// [Simple-CLI AI-Created]\`.
|
|
27
|
+
- **Documentation**: A hidden comment e.g. \`<!-- [Simple-CLI AI-Created] -->\`.
|
|
28
|
+
|
|
29
|
+
**Markdown Specification Example**:
|
|
30
|
+
\`\`\`markdown
|
|
31
|
+
<!-- [Simple-CLI AI-Created] -->
|
|
32
|
+
# toolName
|
|
33
|
+
Brief description.
|
|
34
|
+
|
|
35
|
+
## Command
|
|
36
|
+
python scripts/tool.py
|
|
37
|
+
\`\`\`
|
|
38
|
+
Inputs are passed via **stdin** (JSON) and the \`TOOL_INPUT\` env var.
|
|
39
|
+
After creating/modifying, call \`reloadTools\`.
|
|
40
|
+
|
|
41
|
+
4. **Self-Orchestration**: If a project lacks clear success criteria (missing \`.agent/\` or \`.simple/\` directories), you should take the initiative to create them. Use these folders to store implementation plans, SPEC/PRDs, and validation tests.
|
|
42
|
+
|
|
43
|
+
When making changes to files:
|
|
44
|
+
1. Read the file first to understand the context
|
|
45
|
+
2. Make precise, targeted changes using search/replace
|
|
46
|
+
3. Verify changes don't break existing functionality
|
|
47
|
+
4. Follow the existing code style`,
|
|
48
|
+
tools: ['readFiles', 'writeFiles', 'runCommand', 'glob', 'grep', 'lint', 'reloadTools'],
|
|
49
|
+
},
|
|
50
|
+
// Architect skill for planning
|
|
51
|
+
architect: {
|
|
52
|
+
name: 'architect',
|
|
53
|
+
description: 'Design and planning mode - focuses on architecture decisions',
|
|
54
|
+
systemPrompt: `You are a software architect assistant. Your role is to:
|
|
55
|
+
|
|
56
|
+
1. Help design system architecture
|
|
57
|
+
2. Make technology decisions
|
|
58
|
+
3. Plan implementation strategies
|
|
59
|
+
4. Review and improve existing designs
|
|
60
|
+
|
|
61
|
+
When working on architecture:
|
|
62
|
+
- Ask clarifying questions about requirements
|
|
63
|
+
- Consider scalability, maintainability, and security
|
|
64
|
+
- Provide multiple options when appropriate
|
|
65
|
+
- Document decisions and trade-offs
|
|
66
|
+
|
|
67
|
+
Focus on high-level design rather than implementation details.
|
|
68
|
+
Generate diagrams and documentation when helpful.`,
|
|
69
|
+
tools: ['readFiles', 'glob', 'grep', 'memory'],
|
|
70
|
+
modelPreference: 'orchestrator',
|
|
71
|
+
},
|
|
72
|
+
// Ask skill for questions only
|
|
73
|
+
ask: {
|
|
74
|
+
name: 'ask',
|
|
75
|
+
description: 'Question-answering mode - no file modifications',
|
|
76
|
+
systemPrompt: `You are a helpful coding assistant in read-only mode.
|
|
77
|
+
|
|
78
|
+
You can:
|
|
79
|
+
- Answer questions about the codebase
|
|
80
|
+
- Explain how code works
|
|
81
|
+
- Suggest improvements
|
|
82
|
+
- Help with debugging
|
|
83
|
+
|
|
84
|
+
You should NOT:
|
|
85
|
+
- Modify any files
|
|
86
|
+
- Run commands that change state
|
|
87
|
+
- Create new files
|
|
88
|
+
|
|
89
|
+
Always read files before answering questions about them.`,
|
|
90
|
+
tools: ['readFiles', 'glob', 'grep'],
|
|
91
|
+
},
|
|
92
|
+
// Help/docs skill
|
|
93
|
+
help: {
|
|
94
|
+
name: 'help',
|
|
95
|
+
description: 'Help and documentation assistant',
|
|
96
|
+
systemPrompt: `You are a documentation assistant. Help users understand:
|
|
97
|
+
|
|
98
|
+
- How to use this CLI tool
|
|
99
|
+
- Programming concepts and patterns
|
|
100
|
+
- Best practices and conventions
|
|
101
|
+
- Error messages and debugging
|
|
102
|
+
|
|
103
|
+
Be concise and provide examples when helpful.
|
|
104
|
+
Reference official documentation when available.`,
|
|
105
|
+
tools: ['readFiles', 'scrapeUrl'],
|
|
106
|
+
},
|
|
107
|
+
// Test skill
|
|
108
|
+
test: {
|
|
109
|
+
name: 'test',
|
|
110
|
+
description: 'Test writing and debugging assistant',
|
|
111
|
+
systemPrompt: `You are a test engineering assistant. Your focus is:
|
|
112
|
+
|
|
113
|
+
1. Writing comprehensive tests
|
|
114
|
+
2. Debugging failing tests
|
|
115
|
+
3. Improving test coverage
|
|
116
|
+
4. Setting up test infrastructure
|
|
117
|
+
|
|
118
|
+
Test guidelines:
|
|
119
|
+
- Follow the project's existing test patterns
|
|
120
|
+
- Cover edge cases and error conditions
|
|
121
|
+
- Write clear test descriptions
|
|
122
|
+
- Use appropriate mocking when needed
|
|
123
|
+
|
|
124
|
+
After writing tests, run them to verify they pass.`,
|
|
125
|
+
tools: ['readFiles', 'writeFiles', 'runCommand', 'glob', 'grep', 'lint'],
|
|
126
|
+
},
|
|
127
|
+
// Debug skill
|
|
128
|
+
debug: {
|
|
129
|
+
name: 'debug',
|
|
130
|
+
description: 'Debugging assistant for troubleshooting issues',
|
|
131
|
+
systemPrompt: `You are a debugging assistant. Your process:
|
|
132
|
+
|
|
133
|
+
1. Understand the error or unexpected behavior
|
|
134
|
+
2. Reproduce the issue if possible
|
|
135
|
+
3. Analyze logs, stack traces, and code
|
|
136
|
+
4. Form hypotheses about the cause
|
|
137
|
+
5. Test fixes incrementally
|
|
138
|
+
|
|
139
|
+
Debugging tips:
|
|
140
|
+
- Add logging to understand program flow
|
|
141
|
+
- Check recent changes that might have caused the issue
|
|
142
|
+
- Look for common patterns (null checks, async issues, etc.)
|
|
143
|
+
- Use the linter to catch syntax errors`,
|
|
144
|
+
tools: ['readFiles', 'writeFiles', 'runCommand', 'grep', 'lint', 'git'],
|
|
145
|
+
},
|
|
146
|
+
// Refactor skill
|
|
147
|
+
refactor: {
|
|
148
|
+
name: 'refactor',
|
|
149
|
+
description: 'Code refactoring with safety focus',
|
|
150
|
+
systemPrompt: `You are a refactoring assistant. Your approach:
|
|
151
|
+
|
|
152
|
+
1. Understand the current implementation
|
|
153
|
+
2. Identify areas for improvement
|
|
154
|
+
3. Make incremental, safe changes
|
|
155
|
+
4. Verify behavior is preserved
|
|
156
|
+
|
|
157
|
+
Refactoring principles:
|
|
158
|
+
- Small, focused changes
|
|
159
|
+
- Maintain existing tests
|
|
160
|
+
- Improve readability and maintainability
|
|
161
|
+
- Reduce duplication
|
|
162
|
+
- Follow SOLID principles
|
|
163
|
+
|
|
164
|
+
Always run tests after refactoring to ensure nothing broke.`,
|
|
165
|
+
tools: ['readFiles', 'writeFiles', 'runCommand', 'glob', 'grep', 'lint', 'git'],
|
|
166
|
+
},
|
|
167
|
+
// Review skill
|
|
168
|
+
review: {
|
|
169
|
+
name: 'review',
|
|
170
|
+
description: 'Code review assistant',
|
|
171
|
+
systemPrompt: `You are a code reviewer. Review code for:
|
|
172
|
+
|
|
173
|
+
1. Correctness - Does it work as intended?
|
|
174
|
+
2. Security - Are there vulnerabilities?
|
|
175
|
+
3. Performance - Are there inefficiencies?
|
|
176
|
+
4. Style - Does it follow conventions?
|
|
177
|
+
5. Maintainability - Is it easy to understand?
|
|
178
|
+
|
|
179
|
+
Provide constructive feedback with specific suggestions.
|
|
180
|
+
Prioritize critical issues over minor style concerns.`,
|
|
181
|
+
tools: ['readFiles', 'glob', 'grep', 'git', 'lint'],
|
|
182
|
+
},
|
|
183
|
+
// Shell skill
|
|
184
|
+
shell: {
|
|
185
|
+
name: 'shell',
|
|
186
|
+
description: 'Shell/command-line assistant',
|
|
187
|
+
systemPrompt: `You are a shell/command-line assistant. Help with:
|
|
188
|
+
|
|
189
|
+
- Writing shell scripts
|
|
190
|
+
- Running and debugging commands
|
|
191
|
+
- System administration tasks
|
|
192
|
+
- Build and deployment scripts
|
|
193
|
+
|
|
194
|
+
Safety guidelines:
|
|
195
|
+
- Explain what commands do before running
|
|
196
|
+
- Use safe defaults (no force flags unless needed)
|
|
197
|
+
- Be careful with destructive operations
|
|
198
|
+
- Test commands before applying to production`,
|
|
199
|
+
tools: ['runCommand', 'readFiles', 'writeFiles', 'glob'],
|
|
200
|
+
},
|
|
201
|
+
// Git skill
|
|
202
|
+
git: {
|
|
203
|
+
name: 'git',
|
|
204
|
+
description: 'Git version control assistant',
|
|
205
|
+
systemPrompt: `You are a Git assistant. Help with:
|
|
206
|
+
|
|
207
|
+
- Understanding git history and changes
|
|
208
|
+
- Creating meaningful commits
|
|
209
|
+
- Managing branches
|
|
210
|
+
- Resolving merge conflicts
|
|
211
|
+
- Best practices for version control
|
|
212
|
+
|
|
213
|
+
Git guidelines:
|
|
214
|
+
- Write clear, descriptive commit messages
|
|
215
|
+
- Make small, focused commits
|
|
216
|
+
- Keep branches up to date
|
|
217
|
+
- Review changes before committing`,
|
|
218
|
+
tools: ['git', 'readFiles', 'glob', 'grep'],
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
// Get active skill from environment or default
|
|
222
|
+
export function getActiveSkill() {
|
|
223
|
+
const skillName = process.env.SIMPLE_CLI_SKILL || 'code';
|
|
224
|
+
return builtinSkills[skillName] || builtinSkills.code;
|
|
225
|
+
}
|
|
226
|
+
// Set active skill
|
|
227
|
+
export function setActiveSkill(name) {
|
|
228
|
+
if (builtinSkills[name]) {
|
|
229
|
+
process.env.SIMPLE_CLI_SKILL = name;
|
|
230
|
+
return builtinSkills[name];
|
|
231
|
+
}
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
// List available skills
|
|
235
|
+
export function listSkills() {
|
|
236
|
+
return Object.values(builtinSkills);
|
|
237
|
+
}
|
|
238
|
+
// Load custom skill from file
|
|
239
|
+
export async function loadSkillFromFile(path) {
|
|
240
|
+
try {
|
|
241
|
+
const content = await readFile(path, 'utf-8');
|
|
242
|
+
const skill = JSON.parse(content);
|
|
243
|
+
if (!skill.name || !skill.systemPrompt) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
return skill;
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Save custom skill to file
|
|
253
|
+
export async function saveSkillToFile(skill, path) {
|
|
254
|
+
await writeFile(path, JSON.stringify(skill, null, 2));
|
|
255
|
+
}
|
|
256
|
+
// Load all custom skills from a directory
|
|
257
|
+
export async function loadCustomSkills(dir) {
|
|
258
|
+
const skills = {};
|
|
259
|
+
if (!existsSync(dir)) {
|
|
260
|
+
return skills;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const files = await readdir(dir);
|
|
264
|
+
for (const file of files) {
|
|
265
|
+
if (file.endsWith('.json')) {
|
|
266
|
+
const skill = await loadSkillFromFile(join(dir, file));
|
|
267
|
+
if (skill) {
|
|
268
|
+
skills[skill.name] = skill;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// Ignore errors
|
|
275
|
+
}
|
|
276
|
+
return skills;
|
|
277
|
+
}
|
|
278
|
+
// Get skill system prompt with context
|
|
279
|
+
export function buildSkillPrompt(skill, context) {
|
|
280
|
+
let prompt = skill.systemPrompt;
|
|
281
|
+
if (context?.files?.length) {
|
|
282
|
+
prompt += `\n\n## Active Files\nYou are working with these files:\n${context.files.join('\n')}`;
|
|
283
|
+
}
|
|
284
|
+
if (context?.repoMap) {
|
|
285
|
+
prompt += `\n\n## Repository Structure\n${context.repoMap}`;
|
|
286
|
+
}
|
|
287
|
+
return prompt;
|
|
288
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Coordinator - Orchestrates multiple workers executing tasks
|
|
3
|
+
*/
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
import type { SwarmTask, CoordinatorOptions, SwarmResult, SwarmEventMap } from './types.js';
|
|
6
|
+
export declare class SwarmCoordinator extends EventEmitter {
|
|
7
|
+
private options;
|
|
8
|
+
private taskQueue;
|
|
9
|
+
private workerPool;
|
|
10
|
+
private retryPolicy;
|
|
11
|
+
private running;
|
|
12
|
+
private startTime;
|
|
13
|
+
private aborted;
|
|
14
|
+
constructor(options?: CoordinatorOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Add a single task
|
|
17
|
+
*/
|
|
18
|
+
addTask(task: SwarmTask): void;
|
|
19
|
+
/**
|
|
20
|
+
* Add multiple tasks
|
|
21
|
+
*/
|
|
22
|
+
addTasks(tasks: SwarmTask[]): void;
|
|
23
|
+
/**
|
|
24
|
+
* Get next task from queue
|
|
25
|
+
*/
|
|
26
|
+
getTask(): SwarmTask | null;
|
|
27
|
+
/**
|
|
28
|
+
* Peek at next task
|
|
29
|
+
*/
|
|
30
|
+
peekTask(): SwarmTask | null;
|
|
31
|
+
/**
|
|
32
|
+
* Get all workers status
|
|
33
|
+
*/
|
|
34
|
+
getAllWorkers(): import("./types.js").WorkerStatus[];
|
|
35
|
+
/**
|
|
36
|
+
* Get worker status by ID
|
|
37
|
+
*/
|
|
38
|
+
getWorkerStatus(id: string): import("./types.js").WorkerStatus | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Stop the swarm gracefully
|
|
41
|
+
*/
|
|
42
|
+
stop(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Abort immediately
|
|
45
|
+
*/
|
|
46
|
+
abort(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Run the swarm
|
|
49
|
+
*/
|
|
50
|
+
run(concurrency?: number): Promise<SwarmResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Main processing loop
|
|
53
|
+
*/
|
|
54
|
+
private processLoop;
|
|
55
|
+
/**
|
|
56
|
+
* Execute a single task on a worker
|
|
57
|
+
*/
|
|
58
|
+
private executeTask;
|
|
59
|
+
/**
|
|
60
|
+
* Sleep helper
|
|
61
|
+
*/
|
|
62
|
+
private sleep;
|
|
63
|
+
/**
|
|
64
|
+
* Get queue statistics
|
|
65
|
+
*/
|
|
66
|
+
getStats(): {
|
|
67
|
+
total: number;
|
|
68
|
+
pending: number;
|
|
69
|
+
running: number;
|
|
70
|
+
completed: number;
|
|
71
|
+
failed: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Check if swarm is running
|
|
75
|
+
*/
|
|
76
|
+
isRunning(): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Type-safe event emitter methods
|
|
79
|
+
*/
|
|
80
|
+
on<K extends keyof SwarmEventMap>(event: K, listener: SwarmEventMap[K]): this;
|
|
81
|
+
emit<K extends keyof SwarmEventMap>(event: K, ...args: Parameters<SwarmEventMap[K]>): boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Create a swarm coordinator
|
|
85
|
+
*/
|
|
86
|
+
export declare function createSwarmCoordinator(options?: CoordinatorOptions): SwarmCoordinator;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Coordinator - Orchestrates multiple workers executing tasks
|
|
3
|
+
*/
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
import { DEFAULT_COORDINATOR_OPTIONS, DEFAULT_RETRY_POLICY } from './types.js';
|
|
6
|
+
import { TaskQueue } from './task.js';
|
|
7
|
+
import { WorkerPool } from './worker.js';
|
|
8
|
+
export class SwarmCoordinator extends EventEmitter {
|
|
9
|
+
options;
|
|
10
|
+
taskQueue;
|
|
11
|
+
workerPool;
|
|
12
|
+
retryPolicy;
|
|
13
|
+
running = false;
|
|
14
|
+
startTime = 0;
|
|
15
|
+
aborted = false;
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
super();
|
|
18
|
+
this.options = {
|
|
19
|
+
...DEFAULT_COORDINATOR_OPTIONS,
|
|
20
|
+
...options,
|
|
21
|
+
retryPolicy: {
|
|
22
|
+
...DEFAULT_RETRY_POLICY,
|
|
23
|
+
...options.retryPolicy,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
this.retryPolicy = this.options.retryPolicy;
|
|
27
|
+
this.taskQueue = new TaskQueue();
|
|
28
|
+
this.workerPool = new WorkerPool(this.options.concurrency, {
|
|
29
|
+
cwd: this.options.cwd,
|
|
30
|
+
yolo: this.options.yolo,
|
|
31
|
+
timeout: this.options.timeout,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Add a single task
|
|
36
|
+
*/
|
|
37
|
+
addTask(task) {
|
|
38
|
+
this.taskQueue.addTask(task);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Add multiple tasks
|
|
42
|
+
*/
|
|
43
|
+
addTasks(tasks) {
|
|
44
|
+
this.taskQueue.addTasks(tasks);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get next task from queue
|
|
48
|
+
*/
|
|
49
|
+
getTask() {
|
|
50
|
+
return this.taskQueue.getNextTask();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Peek at next task
|
|
54
|
+
*/
|
|
55
|
+
peekTask() {
|
|
56
|
+
return this.taskQueue.peekNextTask();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get all workers status
|
|
60
|
+
*/
|
|
61
|
+
getAllWorkers() {
|
|
62
|
+
return this.workerPool.getAllWorkers().map(w => w.getStatus());
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get worker status by ID
|
|
66
|
+
*/
|
|
67
|
+
getWorkerStatus(id) {
|
|
68
|
+
return this.workerPool.getWorkerById(id)?.getStatus();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Stop the swarm gracefully
|
|
72
|
+
*/
|
|
73
|
+
stop() {
|
|
74
|
+
this.running = false;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Abort immediately
|
|
78
|
+
*/
|
|
79
|
+
abort() {
|
|
80
|
+
this.aborted = true;
|
|
81
|
+
this.running = false;
|
|
82
|
+
this.workerPool.killAll();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Run the swarm
|
|
86
|
+
*/
|
|
87
|
+
async run(concurrency) {
|
|
88
|
+
if (concurrency !== undefined) {
|
|
89
|
+
this.options.concurrency = concurrency;
|
|
90
|
+
this.workerPool = new WorkerPool(concurrency, {
|
|
91
|
+
cwd: this.options.cwd,
|
|
92
|
+
yolo: this.options.yolo,
|
|
93
|
+
timeout: this.options.timeout,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
this.running = true;
|
|
97
|
+
this.aborted = false;
|
|
98
|
+
this.startTime = Date.now();
|
|
99
|
+
// Skip tasks blocked by failed dependencies
|
|
100
|
+
const skipped = this.taskQueue.skipBlockedTasks();
|
|
101
|
+
for (const taskId of skipped) {
|
|
102
|
+
const task = this.taskQueue.getTask(taskId);
|
|
103
|
+
if (task) {
|
|
104
|
+
this.emit('task:fail', task, new Error('Blocked by failed dependency'));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Process tasks
|
|
108
|
+
await this.processLoop();
|
|
109
|
+
// Mark as no longer running
|
|
110
|
+
this.running = false;
|
|
111
|
+
// Build result
|
|
112
|
+
const stats = this.taskQueue.getStats();
|
|
113
|
+
const results = this.taskQueue.getResults();
|
|
114
|
+
const failures = this.taskQueue.getFailures();
|
|
115
|
+
const duration = Date.now() - this.startTime;
|
|
116
|
+
const result = {
|
|
117
|
+
total: stats.total,
|
|
118
|
+
completed: stats.completed,
|
|
119
|
+
failed: stats.failed,
|
|
120
|
+
skipped: skipped.length,
|
|
121
|
+
duration,
|
|
122
|
+
successRate: stats.total > 0 ? stats.completed / stats.total : 0,
|
|
123
|
+
results,
|
|
124
|
+
failedTasks: failures,
|
|
125
|
+
};
|
|
126
|
+
this.emit('swarm:complete', result);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Main processing loop
|
|
131
|
+
*/
|
|
132
|
+
async processLoop() {
|
|
133
|
+
const activePromises = new Map();
|
|
134
|
+
while (this.running && !this.aborted) {
|
|
135
|
+
// Check if done
|
|
136
|
+
if (this.taskQueue.isDone() && activePromises.size === 0) {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
// Skip blocked tasks
|
|
140
|
+
const skipped = this.taskQueue.skipBlockedTasks();
|
|
141
|
+
for (const taskId of skipped) {
|
|
142
|
+
const task = this.taskQueue.getTask(taskId);
|
|
143
|
+
if (task) {
|
|
144
|
+
this.emit('task:fail', task, new Error('Blocked by failed dependency'));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Try to start new tasks
|
|
148
|
+
while (this.workerPool.getAvailableCount() > 0) {
|
|
149
|
+
const task = this.taskQueue.getNextTask();
|
|
150
|
+
if (!task)
|
|
151
|
+
break;
|
|
152
|
+
const worker = this.workerPool.getWorker();
|
|
153
|
+
if (!worker) {
|
|
154
|
+
// No worker available, put task back
|
|
155
|
+
this.taskQueue.addTask(task);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
// Start task execution
|
|
159
|
+
const promise = this.executeTask(worker, task);
|
|
160
|
+
activePromises.set(task.id, promise);
|
|
161
|
+
promise.finally(() => {
|
|
162
|
+
activePromises.delete(task.id);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// Wait a bit before checking again
|
|
166
|
+
if (activePromises.size > 0) {
|
|
167
|
+
await Promise.race([
|
|
168
|
+
...activePromises.values(),
|
|
169
|
+
this.sleep(100),
|
|
170
|
+
]);
|
|
171
|
+
}
|
|
172
|
+
else if (this.taskQueue.hasWork()) {
|
|
173
|
+
// Have work but no available workers, wait
|
|
174
|
+
await this.sleep(100);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Wait for remaining tasks to complete
|
|
181
|
+
if (activePromises.size > 0) {
|
|
182
|
+
await Promise.all(activePromises.values());
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Execute a single task on a worker
|
|
187
|
+
*/
|
|
188
|
+
async executeTask(worker, task) {
|
|
189
|
+
const attempt = this.taskQueue.getAttempts(task.id) + 1;
|
|
190
|
+
this.emit('task:start', task, worker.id);
|
|
191
|
+
if (attempt > 1) {
|
|
192
|
+
this.emit('task:retry', task, attempt);
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const result = await worker.execute(task);
|
|
196
|
+
if (result.success) {
|
|
197
|
+
const taskResult = {
|
|
198
|
+
task,
|
|
199
|
+
workerId: worker.id,
|
|
200
|
+
result,
|
|
201
|
+
};
|
|
202
|
+
this.taskQueue.completeTask(task.id, taskResult);
|
|
203
|
+
this.emit('task:complete', task, result);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
const willRetry = this.taskQueue.failTask(task.id, result.error || 'Unknown error', this.retryPolicy.maxRetries);
|
|
207
|
+
if (!willRetry) {
|
|
208
|
+
this.emit('task:fail', task, new Error(result.error || 'Max retries exceeded'));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
214
|
+
const willRetry = this.taskQueue.failTask(task.id, errorMsg, this.retryPolicy.maxRetries);
|
|
215
|
+
if (!willRetry) {
|
|
216
|
+
this.emit('task:fail', task, error instanceof Error ? error : new Error(errorMsg));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
// Release worker back to pool
|
|
221
|
+
this.workerPool.releaseWorker(worker);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Sleep helper
|
|
226
|
+
*/
|
|
227
|
+
sleep(ms) {
|
|
228
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get queue statistics
|
|
232
|
+
*/
|
|
233
|
+
getStats() {
|
|
234
|
+
return this.taskQueue.getStats();
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Check if swarm is running
|
|
238
|
+
*/
|
|
239
|
+
isRunning() {
|
|
240
|
+
return this.running;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Type-safe event emitter methods
|
|
244
|
+
*/
|
|
245
|
+
on(event, listener) {
|
|
246
|
+
return super.on(event, listener);
|
|
247
|
+
}
|
|
248
|
+
emit(event, ...args) {
|
|
249
|
+
return super.emit(event, ...args);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Create a swarm coordinator
|
|
254
|
+
*/
|
|
255
|
+
export function createSwarmCoordinator(options) {
|
|
256
|
+
return new SwarmCoordinator(options);
|
|
257
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Module - Coordinate multiple Simple-CLI agents
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { SwarmCoordinator } from 'simplecli/swarm';
|
|
7
|
+
*
|
|
8
|
+
* const coordinator = new SwarmCoordinator({
|
|
9
|
+
* cwd: '/path/to/repo',
|
|
10
|
+
* concurrency: 4,
|
|
11
|
+
* yolo: true,
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* coordinator.addTasks([
|
|
15
|
+
* { id: 'task1', type: 'implement', description: '...', scope: {}, priority: 1, timeout: 300000, retries: 2 },
|
|
16
|
+
* { id: 'task2', type: 'test', description: '...', scope: {}, priority: 2, timeout: 180000, retries: 1 },
|
|
17
|
+
* ]);
|
|
18
|
+
*
|
|
19
|
+
* const result = await coordinator.run();
|
|
20
|
+
* console.log(`Completed: ${result.completed}/${result.total}`);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export type { SwarmTask, TaskScope, TaskType, Priority, WorkerStatus, WorkerState, WorkerResult, TaskResult, FailedTask, SwarmResult, CoordinatorOptions, RetryPolicy, SwarmEventMap, } from './types.js';
|
|
24
|
+
export { swarmTaskSchema, taskScopeSchema, DEFAULT_RETRY_POLICY, DEFAULT_COORDINATOR_OPTIONS, } from './types.js';
|
|
25
|
+
export { TaskQueue } from './task.js';
|
|
26
|
+
export { Worker, WorkerPool } from './worker.js';
|
|
27
|
+
export type { WorkerOptions } from './worker.js';
|
|
28
|
+
export { SwarmCoordinator, createSwarmCoordinator } from './coordinator.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Module - Coordinate multiple Simple-CLI agents
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { SwarmCoordinator } from 'simplecli/swarm';
|
|
7
|
+
*
|
|
8
|
+
* const coordinator = new SwarmCoordinator({
|
|
9
|
+
* cwd: '/path/to/repo',
|
|
10
|
+
* concurrency: 4,
|
|
11
|
+
* yolo: true,
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* coordinator.addTasks([
|
|
15
|
+
* { id: 'task1', type: 'implement', description: '...', scope: {}, priority: 1, timeout: 300000, retries: 2 },
|
|
16
|
+
* { id: 'task2', type: 'test', description: '...', scope: {}, priority: 2, timeout: 180000, retries: 1 },
|
|
17
|
+
* ]);
|
|
18
|
+
*
|
|
19
|
+
* const result = await coordinator.run();
|
|
20
|
+
* console.log(`Completed: ${result.completed}/${result.total}`);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export { swarmTaskSchema, taskScopeSchema, DEFAULT_RETRY_POLICY, DEFAULT_COORDINATOR_OPTIONS, } from './types.js';
|
|
24
|
+
// Task Queue
|
|
25
|
+
export { TaskQueue } from './task.js';
|
|
26
|
+
// Worker
|
|
27
|
+
export { Worker, WorkerPool } from './worker.js';
|
|
28
|
+
// Coordinator
|
|
29
|
+
export { SwarmCoordinator, createSwarmCoordinator } from './coordinator.js';
|