makecc 0.1.0

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.
@@ -0,0 +1,222 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import type {
3
+ ExecutionNode,
4
+ SubagentNodeData,
5
+ SkillNodeData,
6
+ InputNodeData,
7
+ OutputNodeData,
8
+ McpNodeData,
9
+ } from '../types';
10
+
11
+ type ModelId = 'claude-sonnet-4-20250514' | 'claude-opus-4-20250514' | 'claude-3-5-haiku-20241022';
12
+
13
+ export class ClaudeService {
14
+ private client: Anthropic;
15
+ private abortController: AbortController | null = null;
16
+ private executionResults: Map<string, string> = new Map();
17
+
18
+ constructor() {
19
+ this.client = new Anthropic();
20
+ }
21
+
22
+ private getModelId(model?: 'sonnet' | 'opus' | 'haiku'): ModelId {
23
+ switch (model) {
24
+ case 'opus':
25
+ return 'claude-opus-4-20250514';
26
+ case 'haiku':
27
+ return 'claude-3-5-haiku-20241022';
28
+ default:
29
+ return 'claude-sonnet-4-20250514';
30
+ }
31
+ }
32
+
33
+ async executeNode(
34
+ node: ExecutionNode,
35
+ onProgress?: (progress: number) => void
36
+ ): Promise<string> {
37
+ this.abortController = new AbortController();
38
+
39
+ switch (node.type) {
40
+ case 'input':
41
+ return this.executeInputNode(node.id, node.data as InputNodeData);
42
+
43
+ case 'subagent':
44
+ return this.executeSubagentNode(
45
+ node.id,
46
+ node.data as SubagentNodeData,
47
+ onProgress
48
+ );
49
+
50
+ case 'skill':
51
+ return this.executeSkillNode(node.id, node.data as SkillNodeData);
52
+
53
+ case 'mcp':
54
+ return this.executeMcpNode(node.id, node.data as McpNodeData);
55
+
56
+ case 'output':
57
+ return this.executeOutputNode(node.id, node.data as OutputNodeData);
58
+
59
+ default:
60
+ throw new Error(`Unknown node type: ${node.type}`);
61
+ }
62
+ }
63
+
64
+ private async executeInputNode(
65
+ nodeId: string,
66
+ data: InputNodeData
67
+ ): Promise<string> {
68
+ const result = data.value || '';
69
+ this.executionResults.set(nodeId, result);
70
+ return result;
71
+ }
72
+
73
+ private async executeSubagentNode(
74
+ nodeId: string,
75
+ data: SubagentNodeData,
76
+ onProgress?: (progress: number) => void
77
+ ): Promise<string> {
78
+ onProgress?.(10);
79
+
80
+ // Build context from used inputs
81
+ let context = '';
82
+ if (data.usedInputs && data.usedInputs.length > 0) {
83
+ const inputs = data.usedInputs
84
+ .map((inputId) => this.executionResults.get(inputId))
85
+ .filter(Boolean)
86
+ .join('\n\n');
87
+ context = `Context from previous steps:\n${inputs}\n\n`;
88
+ }
89
+
90
+ // Build system prompt based on role
91
+ let systemPrompt = data.systemPrompt || this.getDefaultSystemPrompt(data.role);
92
+
93
+ // Add tool instructions if tools are specified
94
+ if (data.tools && data.tools.length > 0) {
95
+ systemPrompt += `\n\nYou have access to the following tools: ${data.tools.join(', ')}`;
96
+ }
97
+
98
+ onProgress?.(30);
99
+
100
+ try {
101
+ const response = await this.client.messages.create({
102
+ model: this.getModelId(data.model),
103
+ max_tokens: 4096,
104
+ system: systemPrompt,
105
+ messages: [
106
+ {
107
+ role: 'user',
108
+ content: `${context}${data.description || 'Please complete the assigned task.'}`,
109
+ },
110
+ ],
111
+ });
112
+
113
+ onProgress?.(80);
114
+
115
+ // Extract text from response
116
+ const result = response.content
117
+ .filter((block) => block.type === 'text')
118
+ .map((block) => (block as { type: 'text'; text: string }).text)
119
+ .join('\n');
120
+
121
+ this.executionResults.set(nodeId, result);
122
+ onProgress?.(100);
123
+
124
+ return result;
125
+ } catch (error) {
126
+ throw new Error(`Claude API error: ${error instanceof Error ? error.message : 'Unknown error'}`);
127
+ }
128
+ }
129
+
130
+ private async executeSkillNode(
131
+ nodeId: string,
132
+ data: SkillNodeData
133
+ ): Promise<string> {
134
+ // Build context from used inputs
135
+ let context = '';
136
+ if (data.usedInputs && data.usedInputs.length > 0) {
137
+ const inputs = data.usedInputs
138
+ .map((inputId) => this.executionResults.get(inputId))
139
+ .filter(Boolean)
140
+ .join('\n\n');
141
+ context = inputs;
142
+ }
143
+
144
+ // For now, return the skill configuration as result
145
+ // In a full implementation, this would execute the skill's SKILL.md
146
+ const result = data.mdContent
147
+ ? `Skill executed with content:\n${data.mdContent}\n\nContext: ${context}`
148
+ : `Skill ${data.skillId || 'custom'} executed with context: ${context}`;
149
+
150
+ this.executionResults.set(nodeId, result);
151
+ return result;
152
+ }
153
+
154
+ private async executeMcpNode(
155
+ nodeId: string,
156
+ data: McpNodeData
157
+ ): Promise<string> {
158
+ // MCP server integration would go here
159
+ // For now, return a placeholder result
160
+ const result = `MCP Server ${data.serverName} (${data.serverType}) connection established`;
161
+ this.executionResults.set(nodeId, result);
162
+ return result;
163
+ }
164
+
165
+ private async executeOutputNode(
166
+ nodeId: string,
167
+ data: OutputNodeData
168
+ ): Promise<string> {
169
+ // Collect all results from used inputs
170
+ let combinedResult = '';
171
+ if (data.usedInputs && data.usedInputs.length > 0) {
172
+ const inputs = data.usedInputs
173
+ .map((inputId) => this.executionResults.get(inputId))
174
+ .filter(Boolean);
175
+ combinedResult = inputs.join('\n\n---\n\n');
176
+ }
177
+
178
+ // Format based on output type
179
+ let formattedResult: string;
180
+ switch (data.outputType) {
181
+ case 'markdown':
182
+ formattedResult = combinedResult;
183
+ break;
184
+ case 'document':
185
+ formattedResult = `# Document Output\n\n${combinedResult}`;
186
+ break;
187
+ default:
188
+ formattedResult = combinedResult;
189
+ }
190
+
191
+ this.executionResults.set(nodeId, formattedResult);
192
+ return formattedResult;
193
+ }
194
+
195
+ private getDefaultSystemPrompt(role: string): string {
196
+ const prompts: Record<string, string> = {
197
+ researcher:
198
+ 'You are an expert researcher. Your task is to gather, analyze, and synthesize information on the given topic. Provide comprehensive, well-structured findings with citations where applicable.',
199
+ writer:
200
+ 'You are a professional writer. Your task is to create clear, engaging, and well-structured content. Focus on readability and appropriate tone for the target audience.',
201
+ analyst:
202
+ 'You are a data analyst. Your task is to analyze information, identify patterns, and provide insights. Present your findings in a clear, actionable format.',
203
+ coder:
204
+ 'You are an expert software developer. Your task is to write clean, efficient, and well-documented code. Follow best practices and consider edge cases.',
205
+ custom:
206
+ 'You are a helpful AI assistant. Complete the assigned task to the best of your ability.',
207
+ };
208
+
209
+ return prompts[role] || prompts.custom;
210
+ }
211
+
212
+ cancelExecution(): void {
213
+ if (this.abortController) {
214
+ this.abortController.abort();
215
+ this.abortController = null;
216
+ }
217
+ }
218
+
219
+ clearResults(): void {
220
+ this.executionResults.clear();
221
+ }
222
+ }
@@ -0,0 +1,277 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+
5
+ // Type definitions for save operations
6
+ export interface McpServerConfig {
7
+ type: 'stdio' | 'sse' | 'http';
8
+ command?: string;
9
+ args?: string[];
10
+ url?: string;
11
+ env?: Record<string, string>;
12
+ }
13
+
14
+ export interface SkillConfig {
15
+ name: string;
16
+ path: string;
17
+ content: string;
18
+ }
19
+
20
+ export interface CommandConfig {
21
+ name: string;
22
+ path: string;
23
+ content: string;
24
+ }
25
+
26
+ export interface AgentConfig {
27
+ name: string;
28
+ path: string;
29
+ content: string;
30
+ }
31
+
32
+ export interface McpSettingsUpdate {
33
+ mcpServers: Record<string, McpServerConfig>;
34
+ }
35
+
36
+ export interface ClaudeConfigExport {
37
+ skills: SkillConfig[];
38
+ commands: CommandConfig[];
39
+ agents: AgentConfig[];
40
+ mcpSettings: McpSettingsUpdate | null;
41
+ }
42
+
43
+ export type SaveLocation = 'local' | 'global';
44
+
45
+ export interface SaveOptions {
46
+ location: SaveLocation;
47
+ includeSkills: boolean;
48
+ includeCommands: boolean;
49
+ includeAgents: boolean;
50
+ includeMcpSettings: boolean;
51
+ }
52
+
53
+ export interface SaveItemResult {
54
+ name: string;
55
+ path: string;
56
+ success: boolean;
57
+ error?: string;
58
+ }
59
+
60
+ export interface SaveResult {
61
+ success: boolean;
62
+ skills: SaveItemResult[];
63
+ commands: SaveItemResult[];
64
+ agents: SaveItemResult[];
65
+ mcpSettings: { success: boolean; error?: string } | null;
66
+ errors: { type: string; name: string; error: string }[];
67
+ }
68
+
69
+ export class FileService {
70
+ private projectRoot: string;
71
+
72
+ constructor(projectRoot?: string) {
73
+ this.projectRoot = projectRoot || process.cwd();
74
+ }
75
+
76
+ /**
77
+ * Resolves a path, expanding ~ to home directory
78
+ */
79
+ private resolvePath(filePath: string): string {
80
+ if (filePath.startsWith('~')) {
81
+ return path.join(os.homedir(), filePath.slice(1));
82
+ }
83
+ if (path.isAbsolute(filePath)) {
84
+ return filePath;
85
+ }
86
+ return path.join(this.projectRoot, filePath);
87
+ }
88
+
89
+ /**
90
+ * Ensures a directory exists
91
+ */
92
+ private async ensureDir(dirPath: string): Promise<void> {
93
+ const resolvedPath = this.resolvePath(dirPath);
94
+ await fs.mkdir(resolvedPath, { recursive: true });
95
+ }
96
+
97
+ /**
98
+ * Writes content to a file, creating directories as needed
99
+ */
100
+ private async writeFile(filePath: string, content: string): Promise<void> {
101
+ const resolvedPath = this.resolvePath(filePath);
102
+ const dirPath = path.dirname(resolvedPath);
103
+ await this.ensureDir(dirPath);
104
+ await fs.writeFile(resolvedPath, content, 'utf-8');
105
+ }
106
+
107
+ /**
108
+ * Saves a skill configuration
109
+ */
110
+ async saveSkill(config: SkillConfig): Promise<SaveItemResult> {
111
+ try {
112
+ await this.writeFile(config.path, config.content);
113
+ return { name: config.name, path: config.path, success: true };
114
+ } catch (error) {
115
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
116
+ return { name: config.name, path: config.path, success: false, error: errorMessage };
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Saves a command configuration
122
+ */
123
+ async saveCommand(config: CommandConfig): Promise<SaveItemResult> {
124
+ try {
125
+ await this.writeFile(config.path, config.content);
126
+ return { name: config.name, path: config.path, success: true };
127
+ } catch (error) {
128
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
129
+ return { name: config.name, path: config.path, success: false, error: errorMessage };
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Saves an agent configuration
135
+ */
136
+ async saveAgent(config: AgentConfig): Promise<SaveItemResult> {
137
+ try {
138
+ await this.writeFile(config.path, config.content);
139
+ return { name: config.name, path: config.path, success: true };
140
+ } catch (error) {
141
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
142
+ return { name: config.name, path: config.path, success: false, error: errorMessage };
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Updates MCP settings in settings.local.json
148
+ */
149
+ async updateMcpSettings(
150
+ settings: McpSettingsUpdate,
151
+ location: SaveLocation
152
+ ): Promise<{ success: boolean; error?: string }> {
153
+ try {
154
+ const settingsPath = location === 'global'
155
+ ? '~/.claude/settings.local.json'
156
+ : '.claude/settings.local.json';
157
+
158
+ const resolvedPath = this.resolvePath(settingsPath);
159
+
160
+ // Read existing settings if they exist
161
+ let existingSettings: Record<string, unknown> = {};
162
+ try {
163
+ const content = await fs.readFile(resolvedPath, 'utf-8');
164
+ existingSettings = JSON.parse(content);
165
+ } catch {
166
+ // File doesn't exist or is invalid, start fresh
167
+ }
168
+
169
+ // Merge MCP servers
170
+ const existingMcpServers = (existingSettings.mcpServers as Record<string, unknown>) || {};
171
+ const mergedSettings = {
172
+ ...existingSettings,
173
+ mcpServers: {
174
+ ...existingMcpServers,
175
+ ...settings.mcpServers,
176
+ },
177
+ };
178
+
179
+ // Write updated settings
180
+ const dirPath = path.dirname(resolvedPath);
181
+ await this.ensureDir(dirPath);
182
+ await fs.writeFile(resolvedPath, JSON.stringify(mergedSettings, null, 2), 'utf-8');
183
+
184
+ return { success: true };
185
+ } catch (error) {
186
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
187
+ return { success: false, error: errorMessage };
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Saves an entire workflow configuration
193
+ */
194
+ async saveWorkflow(config: ClaudeConfigExport, options: SaveOptions): Promise<SaveResult> {
195
+ const result: SaveResult = {
196
+ success: true,
197
+ skills: [],
198
+ commands: [],
199
+ agents: [],
200
+ mcpSettings: null,
201
+ errors: [],
202
+ };
203
+
204
+ // Save skills
205
+ if (options.includeSkills) {
206
+ for (const skill of config.skills) {
207
+ const skillResult = await this.saveSkill(skill);
208
+ result.skills.push(skillResult);
209
+ if (!skillResult.success) {
210
+ result.success = false;
211
+ result.errors.push({
212
+ type: 'skill',
213
+ name: skill.name,
214
+ error: skillResult.error || 'Unknown error',
215
+ });
216
+ }
217
+ }
218
+ }
219
+
220
+ // Save commands
221
+ if (options.includeCommands) {
222
+ for (const command of config.commands) {
223
+ const commandResult = await this.saveCommand(command);
224
+ result.commands.push(commandResult);
225
+ if (!commandResult.success) {
226
+ result.success = false;
227
+ result.errors.push({
228
+ type: 'command',
229
+ name: command.name,
230
+ error: commandResult.error || 'Unknown error',
231
+ });
232
+ }
233
+ }
234
+ }
235
+
236
+ // Save agents
237
+ if (options.includeAgents) {
238
+ for (const agent of config.agents) {
239
+ const agentResult = await this.saveAgent(agent);
240
+ result.agents.push(agentResult);
241
+ if (!agentResult.success) {
242
+ result.success = false;
243
+ result.errors.push({
244
+ type: 'agent',
245
+ name: agent.name,
246
+ error: agentResult.error || 'Unknown error',
247
+ });
248
+ }
249
+ }
250
+ }
251
+
252
+ // Update MCP settings
253
+ if (options.includeMcpSettings && config.mcpSettings) {
254
+ const mcpResult = await this.updateMcpSettings(config.mcpSettings, options.location);
255
+ result.mcpSettings = mcpResult;
256
+ if (!mcpResult.success) {
257
+ result.success = false;
258
+ result.errors.push({
259
+ type: 'mcp',
260
+ name: 'settings.local.json',
261
+ error: mcpResult.error || 'Unknown error',
262
+ });
263
+ }
264
+ }
265
+
266
+ return result;
267
+ }
268
+
269
+ /**
270
+ * Gets the project root path
271
+ */
272
+ getProjectPath(): string {
273
+ return this.projectRoot;
274
+ }
275
+ }
276
+
277
+ export const fileService = new FileService();