import * as vscode from 'vscode'; import * as https from 'https'; import * as path from 'path'; import * as fs from 'fs'; const API_BASE = 'https://clawsouls.ai/api/v1'; function apiGet(urlPath: string): Promise { return new Promise((resolve, reject) => { const url = `${API_BASE}${urlPath}`; https.get(url, { headers: { 'Accept': 'application/json' } }, (res) => { let data = ''; res.on('data', (chunk: string) => data += chunk); res.on('end', () => { try { resolve(JSON.parse(data)); } catch { reject(new Error(`Invalid JSON from ${url}`)); } }); }).on('error', reject); }); } export function setupWizard(): Promise<{ completed: boolean }> { return new Promise((resolve) => { const panel = vscode.window.createWebviewPanel( 'clawsoulsSetup', 'ClawSouls Setup', vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true } ); let currentStep = 1; const maxSteps = 5; let selectedProvider = ''; let finished = false; panel.onDidDispose(() => { if (!finished) { resolve({ completed: false }); } }); panel.webview.onDidReceiveMessage(async (message) => { switch (message.type) { case 'next': if (currentStep === 1 && message.data?.provider) { selectedProvider = message.data.provider; } await handleNextStep(panel, message.data, currentStep); currentStep++; updateWebviewContent(panel, currentStep, selectedProvider); break; case 'back': currentStep--; updateWebviewContent(panel, currentStep, selectedProvider); break; case 'finish': finished = true; await finishSetup(panel, message.data); panel.dispose(); resolve({ completed: true }); break; case 'skip': finished = true; panel.dispose(); resolve({ completed: true }); break; case 'fetchSouls': try { const resp = await apiGet('/souls?limit=200'); panel.webview.postMessage({ type: 'soulsLoaded', souls: resp.souls || [] }); } catch { panel.webview.postMessage({ type: 'soulsLoaded', souls: [] }); } break; case 'applySoul': try { await applySoulFromOnboarding(message.data.owner, message.data.name); panel.webview.postMessage({ type: 'soulApplied', success: true }); } catch (err: any) { panel.webview.postMessage({ type: 'soulApplied', success: false, error: err.message }); } break; } }); updateWebviewContent(panel, currentStep); }); } async function handleNextStep(panel: vscode.WebviewPanel, data: any, step: number): Promise { try { const config = vscode.workspace.getConfiguration('clawsouls'); switch (step) { case 1: await config.update('llmProvider', data.provider, vscode.ConfigurationTarget.Global); // Reset model when switching providers to avoid cross-contamination await config.update('llmModel', '', vscode.ConfigurationTarget.Global); break; case 2: if (data.apiKey) { await config.update('llmApiKey', data.apiKey, vscode.ConfigurationTarget.Global); } if (data.ollamaUrl) { await config.update('ollamaUrl', data.ollamaUrl, vscode.ConfigurationTarget.Global); } if (data.ollamaModel) { await config.update('ollamaModel', data.ollamaModel, vscode.ConfigurationTarget.Global); } break; case 3: if (data.port) { const url = `ws://127.0.0.1:${data.port}`; await config.update('gatewayUrl', url, vscode.ConfigurationTarget.Global); } break; case 4: if (data.soulChoice === 'custom') { await createCustomSoul(data.soulName); } break; } } catch (err) { console.error(`Setup step ${step} error:`, err); } } async function finishSetup(panel: vscode.WebviewPanel, data: any): Promise { // Show completion message vscode.window.showInformationMessage('ClawSouls Agent setup completed! ๐ŸŽ‰'); // Auto-open chat if requested if (data.openChat) { vscode.commands.executeCommand('clawsouls.openChat'); } } function getOpenClawWorkspaceDir(): string { const stateDir = process.env.OPENCLAW_STATE_DIR; if (stateDir) return path.join(stateDir, 'workspace'); const home = process.env.HOME || process.env.USERPROFILE || ''; return path.join(home, '.openclaw', 'workspace'); } async function applySoulFromOnboarding(owner: string, name: string): Promise { const detail = await apiGet(`/souls/${owner}/${name}?files=true`); const targetDir = getOpenClawWorkspaceDir(); fs.mkdirSync(targetDir, { recursive: true }); const soulJson = { name: detail.name, displayName: detail.displayName, description: detail.description, version: detail.version, specVersion: '0.5', license: detail.license, tags: detail.tags, category: detail.category, author: detail.author, files: detail.files }; const fileNames = Array.isArray(detail.files) ? detail.files as string[] : []; const fileContentsMap = detail.fileContents || {}; const fileNameMap: Record = { 'soul': 'SOUL.md', 'identity': 'IDENTITY.md', 'style': 'STYLE.md', 'agents': 'AGENTS.md', 'readme': 'README.md', 'heartbeat': 'HEARTBEAT.md', 'user': 'USER.md', 'memory': 'MEMORY.md', 'tools': 'TOOLS.md', 'bootstrap': 'BOOTSTRAP.md' }; fs.writeFileSync(path.join(targetDir, 'soul.json'), JSON.stringify(soulJson, null, 2)); for (let i = 0; i < fileNames.length; i++) { const key = fileNames[i]; const content = fileContentsMap[String(i)]; if (!content || key === 'soul.json') continue; const filename = fileNameMap[key] || `${key.toUpperCase()}.md`; fs.writeFileSync(path.join(targetDir, filename), content); } } async function createCustomSoul(name: string): Promise { const workspaces = vscode.workspace.workspaceFolders; if (!workspaces || workspaces.length === 0) { vscode.window.showErrorMessage('Please open a workspace first.'); return; } const workspaceUri = workspaces[0].uri; // Create soul.json const soulConfig = { name: name || 'Custom Soul', version: '1.0.0', description: 'A custom soul created with ClawSouls Agent', created: new Date().toISOString() }; const soulJsonUri = vscode.Uri.joinPath(workspaceUri, 'soul.json'); await vscode.workspace.fs.writeFile(soulJsonUri, Buffer.from(JSON.stringify(soulConfig, null, 2))); // Create SOUL.md const soulMarkdown = `# ${name || 'Custom Soul'} ## Persona You are a helpful AI assistant. ## Communication Style - Be concise and clear - Ask clarifying questions when needed - Provide actionable advice ## Capabilities - General assistance - Code help - Research and analysis - Creative tasks ## Limitations - I don't have access to real-time information - I cannot browse the internet - I cannot execute code directly `; const soulMdUri = vscode.Uri.joinPath(workspaceUri, 'SOUL.md'); await vscode.workspace.fs.writeFile(soulMdUri, Buffer.from(soulMarkdown)); } function updateWebviewContent(panel: vscode.WebviewPanel, step: number, provider?: string): void { panel.webview.html = getWebviewContent(step, provider); } function getWebviewContent(step: number, provider?: string): string { switch (step) { case 1: return getStep1Html(); case 2: return getStep2Html(provider); case 3: return getStepPortHtml(); case 4: return getStep3Html(); case 5: return getStep4Html(); default: return getStep1Html(); } } function getStep1Html(): string { return ` ClawSouls Setup - Step 1

๐Ÿ”ฎ Welcome to ClawSouls Agent

Step 1 of 5: Choose your LLM provider

๐Ÿง  Anthropic Claude (Recommended)

Best overall experience with excellent reasoning and coding capabilities.

๐Ÿค– OpenAI GPT

Popular choice with good general performance and wide compatibility.

๐Ÿ  Local Ollama

Privacy-focused local models. Requires Ollama installed on your system.

`; } function getStep2Html(provider?: string): string { // Read provider from VSCode config const isOllama = provider === 'ollama'; if (isOllama) { return ` ClawSouls Setup - Step 2

๐Ÿ  Ollama Configuration

Step 2 of 5: Configure your local Ollama instance

Ollama URL

Default: http://127.0.0.1:11434

Model

Enter the model name you have pulled in Ollama.
`; } // Anthropic / OpenAI โ€” API key const placeholder = provider === 'openai' ? 'sk-...' : 'sk-ant-...'; const providerName = provider === 'openai' ? 'OpenAI' : 'Anthropic'; return ` ClawSouls Setup - Step 2

๐Ÿ”‘ Authentication

Step 2 of 5: Set up your ${providerName} API access

๐Ÿ” ${providerName} API Key

Enter your API key to get started:

Your API key is stored securely in VSCode settings.
`; } function getStepPortHtml(): string { return ` ClawSouls Setup - Step 3

๐Ÿ”Œ Gateway Port

Step 3 of 5: Configure the OpenClaw Gateway connection

Gateway Port

The extension connects to OpenClaw Gateway via WebSocket.

Default: 18789. Change if you have multiple instances or a conflict.
`; } function getStep3Html(): string { return ` ClawSouls Setup - Step 4

๐ŸŽญ Choose Your Soul

Step 4 of 5: Pick an AI persona from the community

All
Loading souls from ClawSouls...
๐ŸŽจ
Create Custom
๐Ÿ“„
Start Empty
`; } function getStep4Html(): string { return ` ClawSouls Setup - Complete
๐ŸŽ‰

Setup Complete!

ClawSouls Agent is now ready to help you with soul-powered AI development.

`; }