claude-flow 2.0.0-alpha.77 → 2.0.0-alpha.78
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 +7 -8
- package/bin/claude-flow +1 -1
- package/bin/claude-flow.js +1 -1
- package/package.json +3 -2
- package/src/agents/agent-loader.ts +247 -0
- package/src/cli/help-text.js +2 -2
- package/src/cli/simple-commands/init/claude-commands/slash-commands.js +2 -1
- package/src/cli/simple-commands/init/index.js +9 -5
- package/src/cli/simple-commands/init/template-copier.js +1 -1
- package/src/cli/simple-commands/init/templates/enhanced-templates.js +107 -0
- package/src/cli/simple-commands/init/templates/github-safe.js +106 -0
- package/src/constants/agent-types.ts +45 -26
- package/src/mcp/claude-flow-tools.ts +47 -44
- package/src/utils/github-cli-safe.js +201 -0
package/README.md
CHANGED
|
@@ -45,8 +45,9 @@
|
|
|
45
45
|
# 1. Install Claude Code globally
|
|
46
46
|
npm install -g @anthropic-ai/claude-code
|
|
47
47
|
|
|
48
|
-
# 2.
|
|
49
|
-
|
|
48
|
+
# 2. (Optional) Skip permissions check for faster setup
|
|
49
|
+
# Only use if you understand the security implications
|
|
50
|
+
# claude --dangerously-skip-permissions
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
💡 **Windows Note**: If you encounter SQLite errors, Claude Flow will automatically use in-memory storage. For persistent storage options, see our [Windows guide](https://github.com/ruvnet/claude-code-flow/blob/main/docs/windows-installation.md).
|
|
@@ -61,7 +62,7 @@ npx claude-flow@alpha init --force
|
|
|
61
62
|
npx claude-flow@alpha --help
|
|
62
63
|
|
|
63
64
|
# 3a. Quick AI coordination (recommended for most tasks)
|
|
64
|
-
npx claude-flow@alpha swarm "build me a REST API"
|
|
65
|
+
npx claude-flow@alpha swarm "build me a REST API"
|
|
65
66
|
|
|
66
67
|
# 3b. OR launch the full hive-mind system (for complex projects)
|
|
67
68
|
npx claude-flow@alpha hive-mind wizard
|
|
@@ -269,7 +270,7 @@ Claude-Flow v2.0.0 introduces groundbreaking hive-mind architecture where a **Qu
|
|
|
269
270
|
|
|
270
271
|
```bash
|
|
271
272
|
# Deploy intelligent swarm coordination
|
|
272
|
-
npx claude-flow@alpha swarm "Build a full-stack application" --strategy development
|
|
273
|
+
npx claude-flow@alpha swarm "Build a full-stack application" --strategy development
|
|
273
274
|
|
|
274
275
|
# Launch hive-mind with specific specializations
|
|
275
276
|
npx claude-flow@alpha hive-mind spawn "Create microservices architecture" --agents 8 --claude
|
|
@@ -501,8 +502,7 @@ npx claude-flow@alpha workflow create --name "Development Pipeline" --parallel
|
|
|
501
502
|
npx claude-flow@alpha hive-mind spawn "Build e-commerce platform with React, Node.js, and PostgreSQL" \
|
|
502
503
|
--agents 10 \
|
|
503
504
|
--strategy parallel \
|
|
504
|
-
--memory-namespace ecommerce
|
|
505
|
-
--claude
|
|
505
|
+
--memory-namespace ecommerce
|
|
506
506
|
|
|
507
507
|
# Monitor progress in real-time
|
|
508
508
|
npx claude-flow@alpha swarm monitor --dashboard --real-time
|
|
@@ -514,8 +514,7 @@ npx claude-flow@alpha swarm monitor --dashboard --real-time
|
|
|
514
514
|
npx claude-flow@alpha swarm "Research AI safety in autonomous systems" \
|
|
515
515
|
--strategy research \
|
|
516
516
|
--neural-patterns enabled \
|
|
517
|
-
--memory-compression high
|
|
518
|
-
--claude
|
|
517
|
+
--memory-compression high
|
|
519
518
|
|
|
520
519
|
# Analyze results with cognitive computing
|
|
521
520
|
npx claude-flow@alpha cognitive analyze --target research-results
|
package/bin/claude-flow
CHANGED
package/bin/claude-flow.js
CHANGED
|
@@ -11,7 +11,7 @@ import { existsSync } from 'fs';
|
|
|
11
11
|
import { spawn } from 'child_process';
|
|
12
12
|
import process from 'process';
|
|
13
13
|
|
|
14
|
-
const VERSION = "2.0.0-alpha.
|
|
14
|
+
const VERSION = "2.0.0-alpha.78";
|
|
15
15
|
|
|
16
16
|
// Get script directory and root directory
|
|
17
17
|
const __filename = fileURLToPath(import.meta.url);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.78",
|
|
4
4
|
"description": "Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)",
|
|
5
5
|
"main": "cli.mjs",
|
|
6
6
|
"bin": {
|
|
@@ -122,7 +122,8 @@
|
|
|
122
122
|
"ora": "^7.0.1",
|
|
123
123
|
"p-queue": "^8.1.0",
|
|
124
124
|
"ruv-swarm": "^1.0.14",
|
|
125
|
-
"ws": "^8.18.3"
|
|
125
|
+
"ws": "^8.18.3",
|
|
126
|
+
"yaml": "^2.8.0"
|
|
126
127
|
},
|
|
127
128
|
"optionalDependencies": {
|
|
128
129
|
"better-sqlite3": "^12.2.0",
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic Agent Loader - Reads agent definitions from .claude/agents/ directory
|
|
3
|
+
* This is the single source of truth for all agent types in the system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
7
|
+
import { glob } from 'glob';
|
|
8
|
+
import { resolve, dirname } from 'node:path';
|
|
9
|
+
import { parse as parseYaml } from 'yaml';
|
|
10
|
+
|
|
11
|
+
export interface AgentDefinition {
|
|
12
|
+
name: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
color?: string;
|
|
15
|
+
description: string;
|
|
16
|
+
capabilities?: string[];
|
|
17
|
+
priority?: 'low' | 'medium' | 'high' | 'critical';
|
|
18
|
+
hooks?: {
|
|
19
|
+
pre?: string;
|
|
20
|
+
post?: string;
|
|
21
|
+
};
|
|
22
|
+
content?: string; // The markdown content after frontmatter
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AgentCategory {
|
|
26
|
+
name: string;
|
|
27
|
+
agents: AgentDefinition[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class AgentLoader {
|
|
31
|
+
private agentCache: Map<string, AgentDefinition> = new Map();
|
|
32
|
+
private categoriesCache: AgentCategory[] = [];
|
|
33
|
+
private lastLoadTime = 0;
|
|
34
|
+
private cacheExpiry = 60000; // 1 minute cache
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the .claude/agents directory path
|
|
38
|
+
*/
|
|
39
|
+
private getAgentsDirectory(): string {
|
|
40
|
+
// Start from current working directory and walk up to find .claude/agents
|
|
41
|
+
let currentDir = process.cwd();
|
|
42
|
+
|
|
43
|
+
while (currentDir !== '/') {
|
|
44
|
+
const claudeAgentsPath = resolve(currentDir, '.claude', 'agents');
|
|
45
|
+
if (existsSync(claudeAgentsPath)) {
|
|
46
|
+
return claudeAgentsPath;
|
|
47
|
+
}
|
|
48
|
+
currentDir = dirname(currentDir);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fallback to relative path
|
|
52
|
+
return resolve(process.cwd(), '.claude', 'agents');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parse agent definition from markdown file
|
|
57
|
+
*/
|
|
58
|
+
private parseAgentFile(filePath: string): AgentDefinition | null {
|
|
59
|
+
try {
|
|
60
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
61
|
+
|
|
62
|
+
// Split frontmatter and content
|
|
63
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
64
|
+
if (!frontmatterMatch) {
|
|
65
|
+
console.warn(`No frontmatter found in ${filePath}`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const [, yamlContent, markdownContent] = frontmatterMatch;
|
|
70
|
+
const frontmatter = parseYaml(yamlContent);
|
|
71
|
+
|
|
72
|
+
if (!frontmatter.name || !frontmatter.metadata?.description) {
|
|
73
|
+
console.warn(`Missing required fields (name, metadata.description) in ${filePath}`);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
name: frontmatter.name,
|
|
79
|
+
type: frontmatter.type,
|
|
80
|
+
color: frontmatter.color,
|
|
81
|
+
description: frontmatter.metadata.description,
|
|
82
|
+
capabilities: frontmatter.metadata.capabilities || frontmatter.capabilities || [],
|
|
83
|
+
priority: frontmatter.priority || 'medium',
|
|
84
|
+
hooks: frontmatter.hooks,
|
|
85
|
+
content: markdownContent.trim(),
|
|
86
|
+
};
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`Error parsing agent file ${filePath}:`, error);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Load all agent definitions from .claude/agents directory
|
|
95
|
+
*/
|
|
96
|
+
private async loadAgents(): Promise<void> {
|
|
97
|
+
const agentsDir = this.getAgentsDirectory();
|
|
98
|
+
|
|
99
|
+
if (!existsSync(agentsDir)) {
|
|
100
|
+
console.warn(`Agents directory not found: ${agentsDir}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Find all .md files in the agents directory
|
|
105
|
+
const agentFiles = await glob('**/*.md', {
|
|
106
|
+
cwd: agentsDir,
|
|
107
|
+
ignore: ['**/README.md', '**/MIGRATION_SUMMARY.md'],
|
|
108
|
+
absolute: true,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Clear cache
|
|
112
|
+
this.agentCache.clear();
|
|
113
|
+
this.categoriesCache = [];
|
|
114
|
+
|
|
115
|
+
// Track categories
|
|
116
|
+
const categoryMap = new Map<string, AgentDefinition[]>();
|
|
117
|
+
|
|
118
|
+
// Parse each agent file
|
|
119
|
+
for (const filePath of agentFiles) {
|
|
120
|
+
const agent = this.parseAgentFile(filePath);
|
|
121
|
+
if (agent) {
|
|
122
|
+
this.agentCache.set(agent.name, agent);
|
|
123
|
+
|
|
124
|
+
// Determine category from file path
|
|
125
|
+
const relativePath = filePath.replace(agentsDir, '');
|
|
126
|
+
const pathParts = relativePath.split('/');
|
|
127
|
+
const category = pathParts[1] || 'uncategorized'; // First directory after agents/
|
|
128
|
+
|
|
129
|
+
if (!categoryMap.has(category)) {
|
|
130
|
+
categoryMap.set(category, []);
|
|
131
|
+
}
|
|
132
|
+
categoryMap.get(category)!.push(agent);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Build categories array
|
|
137
|
+
this.categoriesCache = Array.from(categoryMap.entries()).map(([name, agents]) => ({
|
|
138
|
+
name,
|
|
139
|
+
agents: agents.sort((a, b) => a.name.localeCompare(b.name)),
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
this.lastLoadTime = Date.now();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check if cache needs refresh
|
|
147
|
+
*/
|
|
148
|
+
private needsRefresh(): boolean {
|
|
149
|
+
return Date.now() - this.lastLoadTime > this.cacheExpiry;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Ensure agents are loaded and cache is fresh
|
|
154
|
+
*/
|
|
155
|
+
private async ensureLoaded(): Promise<void> {
|
|
156
|
+
if (this.agentCache.size === 0 || this.needsRefresh()) {
|
|
157
|
+
await this.loadAgents();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get all available agent types
|
|
163
|
+
*/
|
|
164
|
+
async getAvailableAgentTypes(): Promise<string[]> {
|
|
165
|
+
await this.ensureLoaded();
|
|
166
|
+
return Array.from(this.agentCache.keys()).sort();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get agent definition by name
|
|
171
|
+
*/
|
|
172
|
+
async getAgent(name: string): Promise<AgentDefinition | null> {
|
|
173
|
+
await this.ensureLoaded();
|
|
174
|
+
return this.agentCache.get(name) || null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get all agent definitions
|
|
179
|
+
*/
|
|
180
|
+
async getAllAgents(): Promise<AgentDefinition[]> {
|
|
181
|
+
await this.ensureLoaded();
|
|
182
|
+
return Array.from(this.agentCache.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get agents organized by category
|
|
187
|
+
*/
|
|
188
|
+
async getAgentCategories(): Promise<AgentCategory[]> {
|
|
189
|
+
await this.ensureLoaded();
|
|
190
|
+
return this.categoriesCache;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Search agents by capabilities, description, or name
|
|
195
|
+
*/
|
|
196
|
+
async searchAgents(query: string): Promise<AgentDefinition[]> {
|
|
197
|
+
await this.ensureLoaded();
|
|
198
|
+
const lowerQuery = query.toLowerCase();
|
|
199
|
+
|
|
200
|
+
return Array.from(this.agentCache.values()).filter(agent => {
|
|
201
|
+
return (
|
|
202
|
+
agent.name.toLowerCase().includes(lowerQuery) ||
|
|
203
|
+
agent.description.toLowerCase().includes(lowerQuery) ||
|
|
204
|
+
agent.capabilities?.some(cap => cap.toLowerCase().includes(lowerQuery)) ||
|
|
205
|
+
false
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Check if an agent type is valid
|
|
212
|
+
*/
|
|
213
|
+
async isValidAgentType(name: string): Promise<boolean> {
|
|
214
|
+
await this.ensureLoaded();
|
|
215
|
+
return this.agentCache.has(name);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get agents by category name
|
|
220
|
+
*/
|
|
221
|
+
async getAgentsByCategory(category: string): Promise<AgentDefinition[]> {
|
|
222
|
+
const categories = await this.getAgentCategories();
|
|
223
|
+
const found = categories.find(cat => cat.name === category);
|
|
224
|
+
return found?.agents || [];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Force refresh the agent cache
|
|
229
|
+
*/
|
|
230
|
+
async refresh(): Promise<void> {
|
|
231
|
+
this.lastLoadTime = 0; // Force reload
|
|
232
|
+
await this.loadAgents();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Singleton instance
|
|
237
|
+
export const agentLoader = new AgentLoader();
|
|
238
|
+
|
|
239
|
+
// Convenience functions
|
|
240
|
+
export const getAvailableAgentTypes = () => agentLoader.getAvailableAgentTypes();
|
|
241
|
+
export const getAgent = (name: string) => agentLoader.getAgent(name);
|
|
242
|
+
export const getAllAgents = () => agentLoader.getAllAgents();
|
|
243
|
+
export const getAgentCategories = () => agentLoader.getAgentCategories();
|
|
244
|
+
export const searchAgents = (query: string) => agentLoader.searchAgents(query);
|
|
245
|
+
export const isValidAgentType = (name: string) => agentLoader.isValidAgentType(name);
|
|
246
|
+
export const getAgentsByCategory = (category: string) => agentLoader.getAgentsByCategory(category);
|
|
247
|
+
export const refreshAgents = () => agentLoader.refresh();
|
package/src/cli/help-text.js
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
import { HelpFormatter } from './help-formatter.js';
|
|
7
7
|
|
|
8
|
-
export const VERSION = '2.0.0-alpha.
|
|
8
|
+
export const VERSION = '2.0.0-alpha.78';
|
|
9
9
|
|
|
10
10
|
export const MAIN_HELP = `
|
|
11
11
|
🌊 Claude-Flow v${VERSION} - Enterprise-Grade AI Agent Orchestration Platform
|
|
12
12
|
|
|
13
13
|
🎯 ENTERPRISE FEATURES: Complete ruv-swarm integration with 87 MCP tools, neural networking, and production-ready infrastructure
|
|
14
14
|
🐝 NEW: Advanced Hive Mind System with Queen-led coordination, collective intelligence, and unlimited scaling
|
|
15
|
-
⚡ ALPHA
|
|
15
|
+
⚡ ALPHA 78: Removed legacy Deno warnings, enhanced SPARC slash commands
|
|
16
16
|
|
|
17
17
|
USAGE:
|
|
18
18
|
claude-flow <command> [options]
|
|
@@ -51,6 +51,7 @@ export async function createClaudeSlashCommands(workingDir) {
|
|
|
51
51
|
// Create claude-flow specific commands
|
|
52
52
|
await createClaudeFlowCommands(workingDir);
|
|
53
53
|
} catch (err) {
|
|
54
|
-
|
|
54
|
+
// Legacy slash command creation - silently skip if it fails
|
|
55
|
+
// SPARC slash commands are already created successfully
|
|
55
56
|
}
|
|
56
57
|
}
|
|
@@ -299,7 +299,8 @@ export async function initCommand(subArgs, flags) {
|
|
|
299
299
|
await createClaudeSlashCommands(workingDir);
|
|
300
300
|
}
|
|
301
301
|
} catch (err) {
|
|
302
|
-
|
|
302
|
+
// Legacy slash command creation - silently skip if it fails
|
|
303
|
+
// SPARC slash commands are already created successfully above
|
|
303
304
|
}
|
|
304
305
|
}
|
|
305
306
|
}
|
|
@@ -1069,7 +1070,7 @@ ${commands.map((cmd) => `- [${cmd}](./${cmd}.md)`).join('\n')}
|
|
|
1069
1070
|
}
|
|
1070
1071
|
|
|
1071
1072
|
// Create helper scripts
|
|
1072
|
-
const helpers = ['setup-mcp.sh', 'quick-start.sh', 'github-setup.sh'];
|
|
1073
|
+
const helpers = ['setup-mcp.sh', 'quick-start.sh', 'github-setup.sh', 'github-safe.js'];
|
|
1073
1074
|
for (const helper of helpers) {
|
|
1074
1075
|
if (!dryRun) {
|
|
1075
1076
|
const content = createHelperScript(helper);
|
|
@@ -1308,17 +1309,20 @@ ${commands.map((cmd) => `- [${cmd}](./${cmd}.md)`).join('\n')}
|
|
|
1308
1309
|
console.log('\n📚 Quick Start:');
|
|
1309
1310
|
if (isClaudeCodeInstalled()) {
|
|
1310
1311
|
console.log('1. View available commands: ls .claude/commands/');
|
|
1311
|
-
console.log('2. Start a swarm: npx claude-flow swarm
|
|
1312
|
-
console.log('3. Use
|
|
1312
|
+
console.log('2. Start a swarm: npx claude-flow@alpha swarm "your objective" --claude');
|
|
1313
|
+
console.log('3. Use hive-mind: npx claude-flow@alpha hive-mind spawn "command" --claude');
|
|
1314
|
+
console.log('4. Use MCP tools in Claude Code for enhanced coordination');
|
|
1313
1315
|
} else {
|
|
1314
1316
|
console.log('1. Install Claude Code: npm install -g @anthropic-ai/claude-code');
|
|
1315
1317
|
console.log('2. Add MCP servers (see instructions above)');
|
|
1316
1318
|
console.log('3. View available commands: ls .claude/commands/');
|
|
1317
|
-
console.log('4. Start a swarm: npx claude-flow swarm
|
|
1319
|
+
console.log('4. Start a swarm: npx claude-flow@alpha swarm "your objective" --claude');
|
|
1320
|
+
console.log('5. Use hive-mind: npx claude-flow@alpha hive-mind spawn "command" --claude');
|
|
1318
1321
|
}
|
|
1319
1322
|
console.log('\n💡 Tips:');
|
|
1320
1323
|
console.log('• Check .claude/commands/ for detailed documentation');
|
|
1321
1324
|
console.log('• Use --help with any command for options');
|
|
1325
|
+
console.log('• Run commands with --claude flag for best Claude Code integration');
|
|
1322
1326
|
console.log('• Enable GitHub integration with .claude/helpers/github-setup.sh');
|
|
1323
1327
|
} catch (err) {
|
|
1324
1328
|
printError(`Failed to initialize Claude Flow v2.0.0: ${err.message}`);
|
|
@@ -311,7 +311,7 @@ async function copyHelperScripts(templatesDir, targetDir, options, results) {
|
|
|
311
311
|
await fs.mkdir(helpersDir, { recursive: true });
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
-
const helpers = ['setup-mcp.sh', 'quick-start.sh', 'github-setup.sh'];
|
|
314
|
+
const helpers = ['setup-mcp.sh', 'quick-start.sh', 'github-setup.sh', 'github-safe.js'];
|
|
315
315
|
const { createHelperScript } = await import('./templates/enhanced-templates.js');
|
|
316
316
|
|
|
317
317
|
for (const helper of helpers) {
|
|
@@ -1061,6 +1061,113 @@ echo " - npx claude-flow github swarm"
|
|
|
1061
1061
|
echo " - npx claude-flow repo analyze"
|
|
1062
1062
|
echo " - npx claude-flow pr enhance"
|
|
1063
1063
|
echo " - npx claude-flow issue triage"
|
|
1064
|
+
`,
|
|
1065
|
+
'github-safe.js': `#!/usr/bin/env node
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Safe GitHub CLI Helper
|
|
1069
|
+
* Prevents timeout issues when using gh commands with special characters
|
|
1070
|
+
*
|
|
1071
|
+
* Usage:
|
|
1072
|
+
* ./github-safe.js issue comment 123 "Message with \`backticks\`"
|
|
1073
|
+
* ./github-safe.js pr create --title "Title" --body "Complex body"
|
|
1074
|
+
*/
|
|
1075
|
+
|
|
1076
|
+
import { execSync } from 'child_process';
|
|
1077
|
+
import { writeFileSync, unlinkSync } from 'fs';
|
|
1078
|
+
import { tmpdir } from 'os';
|
|
1079
|
+
import { join } from 'path';
|
|
1080
|
+
import { randomBytes } from 'crypto';
|
|
1081
|
+
|
|
1082
|
+
const args = process.argv.slice(2);
|
|
1083
|
+
|
|
1084
|
+
if (args.length < 2) {
|
|
1085
|
+
console.log(\`
|
|
1086
|
+
Safe GitHub CLI Helper
|
|
1087
|
+
|
|
1088
|
+
Usage:
|
|
1089
|
+
./github-safe.js issue comment <number> <body>
|
|
1090
|
+
./github-safe.js pr comment <number> <body>
|
|
1091
|
+
./github-safe.js issue create --title <title> --body <body>
|
|
1092
|
+
./github-safe.js pr create --title <title> --body <body>
|
|
1093
|
+
|
|
1094
|
+
This helper prevents timeout issues with special characters like:
|
|
1095
|
+
- Backticks in code examples
|
|
1096
|
+
- Command substitution \\$(...)
|
|
1097
|
+
- Directory paths
|
|
1098
|
+
- Special shell characters
|
|
1099
|
+
\`);
|
|
1100
|
+
process.exit(1);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const [command, subcommand, ...restArgs] = args;
|
|
1104
|
+
|
|
1105
|
+
// Handle commands that need body content
|
|
1106
|
+
if ((command === 'issue' || command === 'pr') &&
|
|
1107
|
+
(subcommand === 'comment' || subcommand === 'create')) {
|
|
1108
|
+
|
|
1109
|
+
let bodyIndex = -1;
|
|
1110
|
+
let body = '';
|
|
1111
|
+
|
|
1112
|
+
if (subcommand === 'comment' && restArgs.length >= 2) {
|
|
1113
|
+
// Simple format: github-safe.js issue comment 123 "body"
|
|
1114
|
+
body = restArgs[1];
|
|
1115
|
+
bodyIndex = 1;
|
|
1116
|
+
} else {
|
|
1117
|
+
// Flag format: --body "content"
|
|
1118
|
+
bodyIndex = restArgs.indexOf('--body');
|
|
1119
|
+
if (bodyIndex !== -1 && bodyIndex < restArgs.length - 1) {
|
|
1120
|
+
body = restArgs[bodyIndex + 1];
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (body) {
|
|
1125
|
+
// Use temporary file for body content
|
|
1126
|
+
const tmpFile = join(tmpdir(), \`gh-body-\${randomBytes(8).toString('hex')}.tmp\`);
|
|
1127
|
+
|
|
1128
|
+
try {
|
|
1129
|
+
writeFileSync(tmpFile, body, 'utf8');
|
|
1130
|
+
|
|
1131
|
+
// Build new command with --body-file
|
|
1132
|
+
const newArgs = [...restArgs];
|
|
1133
|
+
if (subcommand === 'comment' && bodyIndex === 1) {
|
|
1134
|
+
// Replace body with --body-file
|
|
1135
|
+
newArgs[1] = '--body-file';
|
|
1136
|
+
newArgs.push(tmpFile);
|
|
1137
|
+
} else if (bodyIndex !== -1) {
|
|
1138
|
+
// Replace --body with --body-file
|
|
1139
|
+
newArgs[bodyIndex] = '--body-file';
|
|
1140
|
+
newArgs[bodyIndex + 1] = tmpFile;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Execute safely
|
|
1144
|
+
const ghCommand = \`gh \${command} \${subcommand} \${newArgs.join(' ')}\`;
|
|
1145
|
+
console.log(\`Executing: \${ghCommand}\`);
|
|
1146
|
+
|
|
1147
|
+
const result = execSync(ghCommand, {
|
|
1148
|
+
stdio: 'inherit',
|
|
1149
|
+
timeout: 30000 // 30 second timeout
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
console.error('Error:', error.message);
|
|
1154
|
+
process.exit(1);
|
|
1155
|
+
} finally {
|
|
1156
|
+
// Clean up
|
|
1157
|
+
try {
|
|
1158
|
+
unlinkSync(tmpFile);
|
|
1159
|
+
} catch (e) {
|
|
1160
|
+
// Ignore cleanup errors
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
} else {
|
|
1164
|
+
// No body content, execute normally
|
|
1165
|
+
execSync(\`gh \${args.join(' ')}\`, { stdio: 'inherit' });
|
|
1166
|
+
}
|
|
1167
|
+
} else {
|
|
1168
|
+
// Other commands, execute normally
|
|
1169
|
+
execSync(\`gh \${args.join(' ')}\`, { stdio: 'inherit' });
|
|
1170
|
+
}
|
|
1064
1171
|
`,
|
|
1065
1172
|
};
|
|
1066
1173
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Safe GitHub CLI Helper
|
|
5
|
+
* Prevents timeout issues when using gh commands with special characters
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ./github-safe.js issue comment 123 "Message with `backticks`"
|
|
9
|
+
* ./github-safe.js pr create --title "Title" --body "Complex body"
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execSync } from 'child_process';
|
|
13
|
+
import { writeFileSync, unlinkSync } from 'fs';
|
|
14
|
+
import { tmpdir } from 'os';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
import { randomBytes } from 'crypto';
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
|
|
20
|
+
if (args.length < 2) {
|
|
21
|
+
console.log(`
|
|
22
|
+
Safe GitHub CLI Helper
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
./github-safe.js issue comment <number> <body>
|
|
26
|
+
./github-safe.js pr comment <number> <body>
|
|
27
|
+
./github-safe.js issue create --title <title> --body <body>
|
|
28
|
+
./github-safe.js pr create --title <title> --body <body>
|
|
29
|
+
|
|
30
|
+
This helper prevents timeout issues with special characters like:
|
|
31
|
+
- Backticks in code examples
|
|
32
|
+
- Command substitution \$(...)
|
|
33
|
+
- Directory paths
|
|
34
|
+
- Special shell characters
|
|
35
|
+
`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const [command, subcommand, ...restArgs] = args;
|
|
40
|
+
|
|
41
|
+
// Handle commands that need body content
|
|
42
|
+
if ((command === 'issue' || command === 'pr') &&
|
|
43
|
+
(subcommand === 'comment' || subcommand === 'create')) {
|
|
44
|
+
|
|
45
|
+
let bodyIndex = -1;
|
|
46
|
+
let body = '';
|
|
47
|
+
|
|
48
|
+
if (subcommand === 'comment' && restArgs.length >= 2) {
|
|
49
|
+
// Simple format: github-safe.js issue comment 123 "body"
|
|
50
|
+
body = restArgs[1];
|
|
51
|
+
bodyIndex = 1;
|
|
52
|
+
} else {
|
|
53
|
+
// Flag format: --body "content"
|
|
54
|
+
bodyIndex = restArgs.indexOf('--body');
|
|
55
|
+
if (bodyIndex !== -1 && bodyIndex < restArgs.length - 1) {
|
|
56
|
+
body = restArgs[bodyIndex + 1];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (body) {
|
|
61
|
+
// Use temporary file for body content
|
|
62
|
+
const tmpFile = join(tmpdir(), `gh-body-${randomBytes(8).toString('hex')}.tmp`);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
writeFileSync(tmpFile, body, 'utf8');
|
|
66
|
+
|
|
67
|
+
// Build new command with --body-file
|
|
68
|
+
const newArgs = [...restArgs];
|
|
69
|
+
if (subcommand === 'comment' && bodyIndex === 1) {
|
|
70
|
+
// Replace body with --body-file
|
|
71
|
+
newArgs[1] = '--body-file';
|
|
72
|
+
newArgs.push(tmpFile);
|
|
73
|
+
} else if (bodyIndex !== -1) {
|
|
74
|
+
// Replace --body with --body-file
|
|
75
|
+
newArgs[bodyIndex] = '--body-file';
|
|
76
|
+
newArgs[bodyIndex + 1] = tmpFile;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Execute safely
|
|
80
|
+
const ghCommand = `gh ${command} ${subcommand} ${newArgs.join(' ')}`;
|
|
81
|
+
console.log(`Executing: ${ghCommand}`);
|
|
82
|
+
|
|
83
|
+
const result = execSync(ghCommand, {
|
|
84
|
+
stdio: 'inherit',
|
|
85
|
+
timeout: 30000 // 30 second timeout
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Error:', error.message);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
} finally {
|
|
92
|
+
// Clean up
|
|
93
|
+
try {
|
|
94
|
+
unlinkSync(tmpFile);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
// Ignore cleanup errors
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
// No body content, execute normally
|
|
101
|
+
execSync(`gh ${args.join(' ')}`, { stdio: 'inherit' });
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
// Other commands, execute normally
|
|
105
|
+
execSync(`gh ${args.join(' ')}`, { stdio: 'inherit' });
|
|
106
|
+
}
|
|
@@ -1,37 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* This file
|
|
2
|
+
* Agent Types - Dynamic loading from .claude/agents/ directory
|
|
3
|
+
* This file provides type-safe access to dynamically loaded agent definitions
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
import { getAvailableAgentTypes, isValidAgentType as validateAgentType } from '../agents/agent-loader.js';
|
|
7
|
+
|
|
8
|
+
// Dynamic agent type - will be a string that matches available agents
|
|
9
|
+
export type AgentType = string;
|
|
10
|
+
|
|
11
|
+
// Legacy agent type mapping for backward compatibility
|
|
12
|
+
export const LEGACY_AGENT_MAPPING = {
|
|
13
|
+
analyst: 'code-analyzer',
|
|
14
|
+
coordinator: 'task-orchestrator',
|
|
15
|
+
optimizer: 'perf-analyzer',
|
|
16
|
+
documenter: 'api-docs',
|
|
17
|
+
monitor: 'performance-benchmarker',
|
|
18
|
+
specialist: 'system-architect',
|
|
19
|
+
architect: 'system-architect',
|
|
18
20
|
} as const;
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Get all valid agent types dynamically
|
|
24
|
+
*/
|
|
25
|
+
export async function getValidAgentTypes(): Promise<string[]> {
|
|
26
|
+
return await getAvailableAgentTypes();
|
|
27
|
+
}
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Helper function to validate agent type
|
|
31
|
+
*/
|
|
32
|
+
export async function isValidAgentType(type: string): Promise<boolean> {
|
|
33
|
+
return await validateAgentType(type);
|
|
34
|
+
}
|
|
24
35
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Resolve legacy agent types to current equivalents
|
|
38
|
+
*/
|
|
39
|
+
export function resolveLegacyAgentType(legacyType: string): string {
|
|
40
|
+
const mapped = LEGACY_AGENT_MAPPING[legacyType as keyof typeof LEGACY_AGENT_MAPPING];
|
|
41
|
+
return mapped || legacyType;
|
|
42
|
+
}
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Create JSON Schema for agent type validation (async)
|
|
46
|
+
*/
|
|
47
|
+
export async function getAgentTypeSchema() {
|
|
48
|
+
const validTypes = await getValidAgentTypes();
|
|
49
|
+
return {
|
|
50
|
+
type: 'string',
|
|
51
|
+
enum: validTypes,
|
|
52
|
+
description: 'Type of specialized AI agent',
|
|
53
|
+
};
|
|
35
54
|
}
|
|
36
55
|
|
|
37
56
|
// Strategy types
|
|
@@ -4,17 +4,49 @@
|
|
|
4
4
|
|
|
5
5
|
import type { MCPTool, MCPContext, AgentProfile, Task, MemoryEntry } from '../utils/types.js';
|
|
6
6
|
import type { ILogger } from '../core/logger.js';
|
|
7
|
+
import { getAvailableAgentTypes, getAgentTypeSchema } from '../constants/agent-types.js';
|
|
7
8
|
import type { Permissions } from './auth.js';
|
|
8
9
|
|
|
9
10
|
export interface ClaudeFlowToolContext extends MCPContext {
|
|
10
11
|
orchestrator?: any; // Reference to orchestrator instance
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Enhance tool schema with dynamic agent types
|
|
16
|
+
*/
|
|
17
|
+
async function enhanceToolWithAgentTypes(tool: MCPTool): Promise<MCPTool> {
|
|
18
|
+
const availableTypes = await getAvailableAgentTypes();
|
|
19
|
+
|
|
20
|
+
// Clone the tool to avoid modifying the original
|
|
21
|
+
const enhancedTool = JSON.parse(JSON.stringify(tool));
|
|
22
|
+
|
|
23
|
+
// Find and populate enum fields for agent types
|
|
24
|
+
function addEnumToAgentTypeFields(obj: any) {
|
|
25
|
+
if (typeof obj !== 'object' || obj === null) return;
|
|
26
|
+
|
|
27
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
28
|
+
if (typeof value === 'object' && value !== null) {
|
|
29
|
+
// Check if this is an agent type field
|
|
30
|
+
if (key === 'type' || key === 'filterByType' || key === 'assignToAgentType') {
|
|
31
|
+
const field = value as any;
|
|
32
|
+
if (field.type === 'string' && field.description?.includes('loaded dynamically from .claude/agents/')) {
|
|
33
|
+
field.enum = availableTypes;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
addEnumToAgentTypeFields(value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
addEnumToAgentTypeFields(enhancedTool.inputSchema);
|
|
42
|
+
return enhancedTool;
|
|
43
|
+
}
|
|
44
|
+
|
|
13
45
|
/**
|
|
14
46
|
* Create all Claude-Flow specific MCP tools
|
|
15
47
|
*/
|
|
16
|
-
export function createClaudeFlowTools(logger: ILogger): MCPTool[] {
|
|
17
|
-
|
|
48
|
+
export async function createClaudeFlowTools(logger: ILogger): Promise<MCPTool[]> {
|
|
49
|
+
const tools = [
|
|
18
50
|
// Agent management tools
|
|
19
51
|
createSpawnAgentTool(logger),
|
|
20
52
|
createListAgentsTool(logger),
|
|
@@ -55,6 +87,13 @@ export function createClaudeFlowTools(logger: ILogger): MCPTool[] {
|
|
|
55
87
|
createListTerminalsTool(logger),
|
|
56
88
|
createCreateTerminalTool(logger),
|
|
57
89
|
];
|
|
90
|
+
|
|
91
|
+
// Enhance tools with dynamic agent types
|
|
92
|
+
const enhancedTools = await Promise.all(
|
|
93
|
+
tools.map(tool => enhanceToolWithAgentTypes(tool))
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return enhancedTools;
|
|
58
97
|
}
|
|
59
98
|
|
|
60
99
|
function createSpawnAgentTool(logger: ILogger): MCPTool {
|
|
@@ -66,20 +105,8 @@ function createSpawnAgentTool(logger: ILogger): MCPTool {
|
|
|
66
105
|
properties: {
|
|
67
106
|
type: {
|
|
68
107
|
type: 'string',
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
'researcher',
|
|
72
|
-
'coder',
|
|
73
|
-
'analyst',
|
|
74
|
-
'architect',
|
|
75
|
-
'tester',
|
|
76
|
-
'reviewer',
|
|
77
|
-
'optimizer',
|
|
78
|
-
'documenter',
|
|
79
|
-
'monitor',
|
|
80
|
-
'specialist',
|
|
81
|
-
],
|
|
82
|
-
description: 'Type of agent to spawn',
|
|
108
|
+
// Note: enum will be populated dynamically at runtime
|
|
109
|
+
description: 'Type of specialized agent to spawn (loaded dynamically from .claude/agents/)',
|
|
83
110
|
},
|
|
84
111
|
name: {
|
|
85
112
|
type: 'string',
|
|
@@ -161,20 +188,8 @@ function createListAgentsTool(logger: ILogger): MCPTool {
|
|
|
161
188
|
},
|
|
162
189
|
filterByType: {
|
|
163
190
|
type: 'string',
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
'researcher',
|
|
167
|
-
'coder',
|
|
168
|
-
'analyst',
|
|
169
|
-
'architect',
|
|
170
|
-
'tester',
|
|
171
|
-
'reviewer',
|
|
172
|
-
'optimizer',
|
|
173
|
-
'documenter',
|
|
174
|
-
'monitor',
|
|
175
|
-
'specialist',
|
|
176
|
-
],
|
|
177
|
-
description: 'Filter agents by type',
|
|
191
|
+
// Note: enum will be populated dynamically at runtime
|
|
192
|
+
description: 'Filter agents by type (loaded dynamically from .claude/agents/)',
|
|
178
193
|
},
|
|
179
194
|
},
|
|
180
195
|
},
|
|
@@ -317,20 +332,8 @@ function createCreateTaskTool(logger: ILogger): MCPTool {
|
|
|
317
332
|
},
|
|
318
333
|
assignToAgentType: {
|
|
319
334
|
type: 'string',
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
'researcher',
|
|
323
|
-
'coder',
|
|
324
|
-
'analyst',
|
|
325
|
-
'architect',
|
|
326
|
-
'tester',
|
|
327
|
-
'reviewer',
|
|
328
|
-
'optimizer',
|
|
329
|
-
'documenter',
|
|
330
|
-
'monitor',
|
|
331
|
-
'specialist',
|
|
332
|
-
],
|
|
333
|
-
description: 'Type of agent to assign the task to',
|
|
335
|
+
// Note: enum will be populated dynamically at runtime
|
|
336
|
+
description: 'Type of specialized agent to assign the task to (loaded dynamically from .claude/agents/)',
|
|
334
337
|
},
|
|
335
338
|
input: {
|
|
336
339
|
type: 'object',
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe GitHub CLI command execution utilities
|
|
3
|
+
* Prevents shell injection and timeout issues with special characters
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'fs';
|
|
7
|
+
import { tmpdir } from 'os';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { randomBytes } from 'crypto';
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Execute GitHub CLI command with safe body content handling
|
|
14
|
+
* @param {string} command - The gh command (e.g., 'issue comment')
|
|
15
|
+
* @param {string} target - Target (issue/PR number or repo)
|
|
16
|
+
* @param {string} body - Body content that may contain special characters
|
|
17
|
+
* @param {Object} options - Additional options
|
|
18
|
+
* @returns {Promise<string>} - Command output
|
|
19
|
+
*/
|
|
20
|
+
export async function safeGhCommand(command, target, body, options = {}) {
|
|
21
|
+
const tmpFile = join(tmpdir(), `gh-body-${randomBytes(8).toString('hex')}.tmp`);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// Write body content to temporary file
|
|
25
|
+
await fs.writeFile(tmpFile, body, 'utf8');
|
|
26
|
+
|
|
27
|
+
// Build command arguments
|
|
28
|
+
const args = command.split(' ');
|
|
29
|
+
args.push(target);
|
|
30
|
+
args.push('--body-file', tmpFile);
|
|
31
|
+
|
|
32
|
+
// Add any additional options
|
|
33
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
34
|
+
args.push(`--${key}`, value);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Execute command with timeout
|
|
38
|
+
const result = await executeWithTimeout('gh', args, options.timeout || 30000);
|
|
39
|
+
return result;
|
|
40
|
+
} finally {
|
|
41
|
+
// Clean up temp file
|
|
42
|
+
try {
|
|
43
|
+
await fs.unlink(tmpFile);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
// Ignore cleanup errors
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Execute command with timeout and proper cleanup
|
|
52
|
+
* @param {string} command - Command to execute
|
|
53
|
+
* @param {string[]} args - Command arguments
|
|
54
|
+
* @param {number} timeout - Timeout in milliseconds
|
|
55
|
+
* @returns {Promise<string>} - Command output
|
|
56
|
+
*/
|
|
57
|
+
function executeWithTimeout(command, args, timeout) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const child = spawn(command, args, {
|
|
60
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
61
|
+
shell: false, // Important: don't use shell
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
let stdout = '';
|
|
65
|
+
let stderr = '';
|
|
66
|
+
let timedOut = false;
|
|
67
|
+
|
|
68
|
+
// Set timeout
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
timedOut = true;
|
|
71
|
+
child.kill('SIGTERM');
|
|
72
|
+
setTimeout(() => child.kill('SIGKILL'), 5000); // Force kill after 5s
|
|
73
|
+
}, timeout);
|
|
74
|
+
|
|
75
|
+
child.stdout.on('data', (data) => {
|
|
76
|
+
stdout += data.toString();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
child.stderr.on('data', (data) => {
|
|
80
|
+
stderr += data.toString();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
child.on('close', (code) => {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
|
|
86
|
+
if (timedOut) {
|
|
87
|
+
reject(new Error(`Command timed out after ${timeout}ms`));
|
|
88
|
+
} else if (code !== 0) {
|
|
89
|
+
reject(new Error(`Command failed with exit code ${code}: ${stderr}`));
|
|
90
|
+
} else {
|
|
91
|
+
resolve(stdout.trim());
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
child.on('error', (err) => {
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
reject(err);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Safe methods for common GitHub CLI operations
|
|
104
|
+
*/
|
|
105
|
+
export const gh = {
|
|
106
|
+
/**
|
|
107
|
+
* Create issue comment safely
|
|
108
|
+
* @param {number|string} issue - Issue number
|
|
109
|
+
* @param {string} body - Comment body
|
|
110
|
+
* @param {Object} options - Additional options
|
|
111
|
+
*/
|
|
112
|
+
async issueComment(issue, body, options = {}) {
|
|
113
|
+
return safeGhCommand('issue comment', issue.toString(), body, options);
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create PR comment safely
|
|
118
|
+
* @param {number|string} pr - PR number
|
|
119
|
+
* @param {string} body - Comment body
|
|
120
|
+
* @param {Object} options - Additional options
|
|
121
|
+
*/
|
|
122
|
+
async prComment(pr, body, options = {}) {
|
|
123
|
+
return safeGhCommand('pr comment', pr.toString(), body, options);
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create issue safely
|
|
128
|
+
* @param {Object} params - Issue parameters
|
|
129
|
+
*/
|
|
130
|
+
async createIssue({ title, body, labels = [], assignees = [] }) {
|
|
131
|
+
const tmpFile = join(tmpdir(), `gh-issue-${randomBytes(8).toString('hex')}.tmp`);
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
await fs.writeFile(tmpFile, body, 'utf8');
|
|
135
|
+
|
|
136
|
+
const args = ['issue', 'create', '--title', title, '--body-file', tmpFile];
|
|
137
|
+
|
|
138
|
+
if (labels.length > 0) {
|
|
139
|
+
args.push('--label', labels.join(','));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (assignees.length > 0) {
|
|
143
|
+
args.push('--assignee', assignees.join(','));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return await executeWithTimeout('gh', args, 30000);
|
|
147
|
+
} finally {
|
|
148
|
+
try {
|
|
149
|
+
await fs.unlink(tmpFile);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
// Ignore cleanup errors
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create PR safely
|
|
158
|
+
* @param {Object} params - PR parameters
|
|
159
|
+
*/
|
|
160
|
+
async createPR({ title, body, base = 'main', head, draft = false }) {
|
|
161
|
+
const tmpFile = join(tmpdir(), `gh-pr-${randomBytes(8).toString('hex')}.tmp`);
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
await fs.writeFile(tmpFile, body, 'utf8');
|
|
165
|
+
|
|
166
|
+
const args = ['pr', 'create', '--title', title, '--body-file', tmpFile, '--base', base];
|
|
167
|
+
|
|
168
|
+
if (head) {
|
|
169
|
+
args.push('--head', head);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (draft) {
|
|
173
|
+
args.push('--draft');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return await executeWithTimeout('gh', args, 30000);
|
|
177
|
+
} finally {
|
|
178
|
+
try {
|
|
179
|
+
await fs.unlink(tmpFile);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
// Ignore cleanup errors
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Example usage:
|
|
189
|
+
*
|
|
190
|
+
* import { gh } from './github-cli-safe.js';
|
|
191
|
+
*
|
|
192
|
+
* // Safe issue comment with special characters
|
|
193
|
+
* await gh.issueComment(123, 'Code: `npm install` and $(echo test)');
|
|
194
|
+
*
|
|
195
|
+
* // Safe PR creation with complex body
|
|
196
|
+
* await gh.createPR({
|
|
197
|
+
* title: 'Fix: Handle special characters',
|
|
198
|
+
* body: 'This fixes issues with `backticks` and .claude/agents/ paths',
|
|
199
|
+
* base: 'main'
|
|
200
|
+
* });
|
|
201
|
+
*/
|