UNPKG

cline-sdk

Version:

Comprehensive SDK for Cline - AI-powered development assistant with database integration, execution modes, and custom functions

453 lines (446 loc) 16.2 kB
"use strict"; /** * Extension Clone - 1:1 Kopie der VSCode Extension ohne VSCode Dependencies * Verwendet exakt den gleichen Controller und die gleichen Message-Formate */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ExtensionCloneController = void 0; const path = __importStar(require("path")); const fs = __importStar(require("fs")); const events_1 = require("events"); // === CONTROLLER CLONE === class ExtensionCloneController extends events_1.EventEmitter { constructor(dataDir, apiKey) { super(); this.id = require("uuid").v4(); // Setup mock context exactly like VSCode extension this.context = { globalStorageUri: { fsPath: dataDir }, extensionPath: path.join(__dirname, "../../.."), // Cline project root subscriptions: [], }; // Setup mock output channel this.outputChannel = { appendLine: (value) => console.log("[Extension Clone]:", value), show: () => { }, dispose: () => { }, }; // Initialize storage similar to VSCode this.storage = { globalState: new Map(), workspaceState: new Map(), secrets: new Map(), }; // Set API key if provided if (apiKey) { this.storage.secrets.set("apiKey", apiKey); } this.messageHandler = (message) => { console.log("📤 Extension Message:", message.type); this.emit("message", message); }; this.outputChannel.appendLine("ExtensionCloneController instantiated"); this.initialize(); } initialize() { // Ensure required directories exist const settingsDir = path.join(this.context.globalStorageUri.fsPath, "settings"); if (!fs.existsSync(settingsDir)) { fs.mkdirSync(settingsDir, { recursive: true }); } // Create required files if they don't exist const globalStatePath = path.join(this.context.globalStorageUri.fsPath, "globalState.json"); if (!fs.existsSync(globalStatePath)) { fs.writeFileSync(globalStatePath, "{}"); } this.outputChannel.appendLine("Extension Clone initialized successfully"); } /** * Handle webview messages exactly like the real extension */ async handleWebviewMessage(message) { console.log("📥 Webview Message:", message.type); try { switch (message.type) { case "grpc_request": await this.handleGrpcRequest(message); break; case "webviewDidLaunch": await this.handleWebviewLaunch(); break; case "getLatestState": await this.handleGetLatestState(); break; case "newTask": await this.handleNewTask(message); break; case "askResponse": await this.handleAskResponse(message); break; default: console.log("🤷‍♂️ Unknown message type:", message.type); } } catch (error) { console.error("❌ Error handling message:", error); this.sendMessage({ type: "error", error: error instanceof Error ? error.message : "Unknown error", }); } } async handleGrpcRequest(message) { const { service, method, data } = message; console.log(`🔍 gRPC Request: ${service}.${method}`); // Route to appropriate service handler switch (service) { case "StateService": await this.handleStateService(method, data); break; case "TaskService": await this.handleTaskService(method, data); break; default: console.log("🤷‍♂️ Unknown service:", service); } } async handleStateService(method, data) { switch (method) { case "getLatestState": const state = await this.getLatestState(); this.sendMessage({ type: "grpc_response", service: "StateService", method: "getLatestState", data: state, }); break; default: console.log("🤷‍♂️ Unknown StateService method:", method); } } async handleTaskService(method, data) { switch (method) { case "newTask": const result = await this.createNewTask(data); this.sendMessage({ type: "grpc_response", service: "TaskService", method: "newTask", data: result, }); break; case "askResponse": await this.sendTaskResponse(data); this.sendMessage({ type: "grpc_response", service: "TaskService", method: "askResponse", data: { success: true }, }); break; default: console.log("🤷‍♂️ Unknown TaskService method:", method); } } async handleWebviewLaunch() { this.sendMessage({ type: "action", action: "didLaunch", }); } async handleGetLatestState() { const state = await this.getLatestState(); this.sendMessage({ type: "state", state, }); } async handleNewTask(message) { const result = await this.createNewTask(message); this.sendMessage({ type: "task", task: result, }); } async handleAskResponse(message) { await this.sendTaskResponse(message); this.sendMessage({ type: "askResponse", success: true, }); } // === BUSINESS LOGIC (based on real Controller) === async getLatestState() { // Build state object exactly like the real Controller const apiConfiguration = { apiProvider: "anthropic", apiKey: this.storage.secrets.get("apiKey") || process.env.ANTHROPIC_API_KEY, }; const state = { version: "3.17.10", clineMessages: this.task?.clineMessages || [], taskHistory: this.storage.globalState.get("taskHistory") || [], shouldShowAnnouncement: false, apiConfiguration, customInstructions: this.storage.globalState.get("customInstructions") || "", alwaysAllowReadOnly: this.storage.globalState.get("alwaysAllowReadOnly") || false, uriScheme: "file", workingDirectory: process.cwd(), extensionName: "Cline", platform: process.platform === "darwin" ? "mac" : process.platform === "win32" ? "windows" : "linux", supportsImages: true, maxRequestsPerTask: 50, taskId: this.task?.historyItem?.id || null, theme: { name: "Default Dark+" }, workspaceFolders: [ { name: path.basename(process.cwd()), uri: { fsPath: process.cwd() }, }, ], mcpMarketplaceEnabled: true, mcpServers: [], experimentalFeatureStates: {}, fpjsKey: null, user: null, didHydrateState: true, claudeAsk: null, }; return state; } async createNewTask(data) { console.log("🎯 Creating new task:", data.text); const taskId = `task_${Date.now()}`; // Create task object like the real Controller const historyItem = { id: taskId, ts: Date.now(), task: data.text, tokensIn: 0, tokensOut: 0, cacheWrites: 0, cacheReads: 0, totalCost: 0, }; // Initialize task similar to real Task class this.task = { historyItem, clineMessages: [ { ts: Date.now(), type: "say", say: "task", text: data.text, images: data.images || [], }, ], abort: () => { }, isAborted: false, }; // Add to task history const taskHistory = this.storage.globalState.get("taskHistory") || []; taskHistory.unshift(historyItem); this.storage.globalState.set("taskHistory", taskHistory); // Start the actual task processing this.processTask(); return { success: true, taskId, }; } async sendTaskResponse(data) { console.log("💬 Sending task response:", data.text); if (!this.task) { throw new Error("No active task to respond to"); } // Add user response to messages this.task.clineMessages.push({ ts: Date.now(), type: "say", say: "user", text: data.text || data.response, }); // Continue task processing this.processTask(); } /** * Process the current task with real file operations */ async processTask() { if (!this.task) return; const apiKey = this.storage.secrets.get("apiKey"); if (!apiKey) { console.error("❌ No API key configured"); return; } try { // Add assistant thinking message this.task.clineMessages.push({ ts: Date.now(), type: "say", say: "assistant", text: "I'll help you with that task. Let me analyze what you need and create the file...", }); // Emit state update this.sendMessage({ type: "state", state: await this.getLatestState(), }); // Get the task text to determine what to create const taskText = this.task.historyItem.task.toLowerCase(); if (taskText.includes("python") && taskText.includes("hello")) { // Create the Python file based on the request await this.createPythonHelloFile(); this.task.clineMessages.push({ ts: Date.now(), type: "say", say: "assistant", text: "I've created a Python script called `hello.py` with a hello world function and a personalized greeting function. The script includes:\n\n1. A main hello world print statement\n2. A `greet_person(name)` function for personalized greetings\n3. Example usage showing how to use both features\n\nThe file has been created in your current working directory.", }); } else { // Generic response for other tasks this.task.clineMessages.push({ ts: Date.now(), type: "say", say: "assistant", text: "I've analyzed your request. While I can process this task, I'm currently configured to demonstrate file creation with Python scripts. Would you like me to create a Python file for you?", }); } // Final state update this.sendMessage({ type: "state", state: await this.getLatestState(), }); console.log("✅ Task processing completed"); } catch (error) { console.error("❌ Task processing failed:", error); this.task.clineMessages.push({ ts: Date.now(), type: "say", say: "assistant", text: `I encountered an error: ${error instanceof Error ? error.message : "Unknown error"}`, }); } } /** * Create a Python hello world file with greeting function */ async createPythonHelloFile() { const content = `#!/usr/bin/env python3 """ Hello World Script created by Cline SDK """ def greet_person(name): """ Greet a person by name Args: name (str): The name of the person to greet Returns: str: A personalized greeting """ return f"Hello, {name}! Nice to meet you!" def main(): """ Main function demonstrating the greeting functionality """ # Print the main hello message print("Hello from Cline SDK!") # Demonstrate the greeting function print("\\nDemonstrating the greeting function:") print(greet_person("Alice")) print(greet_person("Bob")) print(greet_person("Charlie")) # Interactive greeting print("\\nTry it yourself!") user_name = input("What's your name? ") print(greet_person(user_name)) if __name__ == "__main__": main() `; const filePath = path.join(process.cwd(), "hello.py"); try { fs.writeFileSync(filePath, content); console.log("📝 Created file:", filePath); // Add tool use message this.task.clineMessages.push({ ts: Date.now(), type: "tool", toolName: "Write", toolInput: { file_path: "hello.py", content: content, }, toolResult: `Successfully created hello.py with ${content.split("\n").length} lines`, }); } catch (error) { console.error("❌ Failed to create file:", error); throw error; } } sendMessage(message) { console.log("📤 Sending message:", message.type); this.messageHandler(message); this.emit("message", message); } // === PUBLIC API === /** * Send a message to the controller (like webview would do) */ async postMessage(message) { await this.handleWebviewMessage(message); } /** * Subscribe to messages from the controller */ onMessage(handler) { this.on("message", handler); } /** * Dispose resources */ dispose() { this.outputChannel.dispose(); this.removeAllListeners(); } } exports.ExtensionCloneController = ExtensionCloneController; exports.default = ExtensionCloneController; //# sourceMappingURL=extension-clone.js.map