From fa6c23b82accf23316afbf34c6c5853b812eb34a Mon Sep 17 00:00:00 2001 From: Felix <24791380+vcfgv@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:26:06 +0800 Subject: [PATCH] feature: channels and skills (#2) Co-authored-by: paisley <8197966+su8su@users.noreply.github.com> Co-authored-by: Cursor --- .gitignore | 2 + electron-builder.yml | 10 +- electron/gateway/clawhub.ts | 304 ++++++++++ electron/gateway/manager.ts | 38 +- electron/main/index.ts | 11 +- electron/main/ipc-handlers.ts | 342 +++++++++-- electron/preload/index.ts | 48 +- electron/utils/channel-config.ts | 579 +++++++++++++++++++ electron/utils/skill-config.ts | 130 +++++ electron/utils/uv-setup.ts | 114 ++++ package.json | 5 +- pnpm-lock.yaml | 210 +++++++ resources/icons/icon.icns | Bin 0 -> 231268 bytes scripts/download-bundled-uv.mjs | 139 +++++ src/pages/Channels/index.tsx | 870 ++++++++++++++++++---------- src/pages/Setup/index.tsx | 463 +++++++++++++-- src/pages/Skills/index.tsx | 952 ++++++++++++++++++++++--------- src/stores/channels.ts | 105 +++- src/stores/chat.ts | 36 +- src/stores/gateway.ts | 62 +- src/stores/skills.ts | 217 ++++++- src/types/channel.ts | 448 ++++++++++++++- src/types/skill.ts | 32 +- 23 files changed, 4315 insertions(+), 802 deletions(-) create mode 100644 electron/gateway/clawhub.ts create mode 100644 electron/utils/channel-config.ts create mode 100644 electron/utils/skill-config.ts create mode 100644 electron/utils/uv-setup.ts create mode 100644 resources/icons/icon.icns create mode 100644 scripts/download-bundled-uv.mjs diff --git a/.gitignore b/.gitignore index 9a6945c0e..b26251cb6 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,8 @@ coverage/ *.deb *.rpm +resources/bin + # Secrets *.p12 *.pem diff --git a/electron-builder.yml b/electron-builder.yml index e551be086..08464907c 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -1,7 +1,7 @@ appId: app.clawx.desktop productName: ClawX copyright: Copyright © 2026 ClawX -compression: maximum +compression: normal artifactName: ${productName}-${version}-${os}-${arch}.${ext} directories: @@ -20,6 +20,7 @@ extraResources: - "**/*" - "!icons/*.md" - "!icons/*.svg" + - "!bin/**" # OpenClaw submodule - include only necessary files for runtime - from: openclaw/ to: openclaw/ @@ -30,6 +31,7 @@ extraResources: - "skills/**/*" - "extensions/**/*" - "scripts/run-node.mjs" + - "!**/node_modules/**" - "!**/*.test.ts" - "!**/*.test.js" - "!**/test/**" @@ -50,6 +52,9 @@ publish: # macOS Configuration mac: + extraResources: + - from: resources/bin/darwin-${arch} + to: bin category: public.app-category.productivity icon: resources/icons/icon.icns target: @@ -84,6 +89,9 @@ dmg: # Windows Configuration win: + extraResources: + - from: resources/bin/win32-${arch} + to: bin icon: resources/icons/icon.ico target: - target: nsis diff --git a/electron/gateway/clawhub.ts b/electron/gateway/clawhub.ts new file mode 100644 index 000000000..591c933c7 --- /dev/null +++ b/electron/gateway/clawhub.ts @@ -0,0 +1,304 @@ +/** + * ClawHub Service + * Manages interactions with the ClawHub CLI for skills management + */ +import { spawn } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import { app, shell } from 'electron'; +import { getOpenClawConfigDir, ensureDir } from '../utils/paths'; + +export interface ClawHubSearchParams { + query: string; + limit?: number; +} + +export interface ClawHubInstallParams { + slug: string; + version?: string; + force?: boolean; +} + +export interface ClawHubUninstallParams { + slug: string; +} + +export interface ClawHubSkillResult { + slug: string; + name: string; + description: string; + version: string; + author?: string; + downloads?: number; + stars?: number; +} + +export class ClawHubService { + private workDir: string; + private cliPath: string; + private ansiRegex: RegExp; + + constructor() { + // Use the user's OpenClaw config directory (~/.openclaw) for skill management + // This avoids installing skills into the project's openclaw submodule + this.workDir = getOpenClawConfigDir(); + ensureDir(this.workDir); + + // In development, we use the locally installed clawhub CLI from node_modules + const isWin = process.platform === 'win32'; + const binName = isWin ? 'clawhub.cmd' : 'clawhub'; + const localCli = path.resolve(app.getAppPath(), 'node_modules', '.bin', binName); + this.cliPath = localCli; + const esc = String.fromCharCode(27); + const csi = String.fromCharCode(155); + const pattern = `(?:${esc}|${csi})[[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]`; + this.ansiRegex = new RegExp(pattern, 'g'); + } + + private stripAnsi(line: string): string { + return line.replace(this.ansiRegex, '').trim(); + } + + /** + * Run a ClawHub CLI command + */ + private async runCommand(args: string[]): Promise { + return new Promise((resolve, reject) => { + console.log(`Running ClawHub command: ${this.cliPath} ${args.join(' ')}`); + + const isWin = process.platform === 'win32'; + const child = spawn(this.cliPath, args, { + cwd: this.workDir, + shell: isWin, + env: { + ...process.env, + CI: 'true', + FORCE_COLOR: '0', // Disable colors for easier parsing + }, + }); + + let stdout = ''; + let stderr = ''; + + child.stdout.on('data', (data) => { + stdout += data.toString(); + }); + + child.stderr.on('data', (data) => { + stderr += data.toString(); + }); + + child.on('error', (error) => { + console.error('ClawHub process error:', error); + reject(error); + }); + + child.on('close', (code) => { + if (code !== 0 && code !== null) { + console.error(`ClawHub command failed with code ${code}`); + console.error('Stderr:', stderr); + reject(new Error(`Command failed: ${stderr || stdout}`)); + } else { + resolve(stdout.trim()); + } + }); + }); + } + + /** + * Search for skills + */ + async search(params: ClawHubSearchParams): Promise { + try { + // If query is empty, use 'explore' to show trending skills + if (!params.query || params.query.trim() === '') { + return this.explore({ limit: params.limit }); + } + + const args = ['search', params.query]; + if (params.limit) { + args.push('--limit', String(params.limit)); + } + + const output = await this.runCommand(args); + if (!output || output.includes('No skills found')) { + return []; + } + + const lines = output.split('\n').filter(l => l.trim()); + return lines.map(line => { + const cleanLine = this.stripAnsi(line); + + // Format could be: slug vversion description (score) + // Or sometimes: slug vversion description + const match = cleanLine.match(/^(\S+)\s+v?(\d+\.\S+)\s+(.+)$/); + if (match) { + const slug = match[1]; + const version = match[2]; + let description = match[3]; + + // Clean up score if present at the end + description = description.replace(/\(\d+\.\d+\)$/, '').trim(); + + return { + slug, + name: slug, + version, + description, + }; + } + return null; + }).filter((s): s is ClawHubSkillResult => s !== null); + } catch (error) { + console.error('ClawHub search error:', error); + return []; + } + } + + /** + * Explore trending skills + */ + async explore(params: { limit?: number } = {}): Promise { + try { + const args = ['explore']; + if (params.limit) { + args.push('--limit', String(params.limit)); + } + + const output = await this.runCommand(args); + if (!output) return []; + + const lines = output.split('\n').filter(l => l.trim()); + return lines.map(line => { + const cleanLine = this.stripAnsi(line); + + // Format: slug vversion time description + // Example: my-skill v1.0.0 2 hours ago A great skill + const match = cleanLine.match(/^(\S+)\s+v?(\d+\.\S+)\s+(.+? ago|just now|yesterday)\s+(.+)$/i); + if (match) { + return { + slug: match[1], + name: match[1], + version: match[2], + description: match[4], + }; + } + return null; + }).filter((s): s is ClawHubSkillResult => s !== null); + } catch (error) { + console.error('ClawHub explore error:', error); + return []; + } + } + + /** + * Install a skill + */ + async install(params: ClawHubInstallParams): Promise { + const args = ['install', params.slug]; + + if (params.version) { + args.push('--version', params.version); + } + + if (params.force) { + args.push('--force'); + } + + await this.runCommand(args); + } + + /** + * Uninstall a skill + */ + async uninstall(params: ClawHubUninstallParams): Promise { + const fsPromises = fs.promises; + + // 1. Delete the skill directory + const skillDir = path.join(this.workDir, 'skills', params.slug); + if (fs.existsSync(skillDir)) { + console.log(`Deleting skill directory: ${skillDir}`); + await fsPromises.rm(skillDir, { recursive: true, force: true }); + } + + // 2. Remove from lock.json + const lockFile = path.join(this.workDir, '.clawhub', 'lock.json'); + if (fs.existsSync(lockFile)) { + try { + const lockData = JSON.parse(fs.readFileSync(lockFile, 'utf8')); + if (lockData.skills && lockData.skills[params.slug]) { + console.log(`Removing ${params.slug} from lock.json`); + delete lockData.skills[params.slug]; + await fsPromises.writeFile(lockFile, JSON.stringify(lockData, null, 2)); + } + } catch (err) { + console.error('Failed to update ClawHub lock file:', err); + } + } + } + + /** + * List installed skills + */ + async listInstalled(): Promise> { + try { + const output = await this.runCommand(['list']); + if (!output || output.includes('No installed skills')) { + return []; + } + + const lines = output.split('\n').filter(l => l.trim()); + return lines.map(line => { + const cleanLine = this.stripAnsi(line); + const match = cleanLine.match(/^(\S+)\s+v?(\d+\.\S+)/); + if (match) { + return { + slug: match[1], + version: match[2], + }; + } + return null; + }).filter((s): s is { slug: string; version: string } => s !== null); + } catch (error) { + console.error('ClawHub list error:', error); + return []; + } + } + + /** + * Open skill README/manual in default editor + */ + async openSkillReadme(slug: string): Promise { + const skillDir = path.join(this.workDir, 'skills', slug); + + // Try to find documentation file + const possibleFiles = ['SKILL.md', 'README.md', 'skill.md', 'readme.md']; + let targetFile = ''; + + for (const file of possibleFiles) { + const filePath = path.join(skillDir, file); + if (fs.existsSync(filePath)) { + targetFile = filePath; + break; + } + } + + if (!targetFile) { + // If no md file, just open the directory + if (fs.existsSync(skillDir)) { + targetFile = skillDir; + } else { + throw new Error('Skill directory not found'); + } + } + + try { + // Open file with default application + await shell.openPath(targetFile); + return true; + } catch (error) { + console.error('Failed to open skill readme:', error); + throw error; + } + } +} diff --git a/electron/gateway/manager.ts b/electron/gateway/manager.ts index 55753c019..41c2f0ccc 100644 --- a/electron/gateway/manager.ts +++ b/electron/gateway/manager.ts @@ -2,6 +2,8 @@ * Gateway Process Manager * Manages the OpenClaw Gateway process lifecycle */ +import { app } from 'electron'; +import path from 'path'; import { spawn, ChildProcess } from 'child_process'; import { EventEmitter } from 'events'; import { existsSync } from 'fs'; @@ -361,16 +363,40 @@ export class GatewayManager extends EventEmitter { // Production mode: use openclaw.mjs directly console.log('Starting Gateway in production mode (using dist)'); command = 'node'; - args = [entryScript, 'gateway', 'run', '--port', String(this.status.port), '--token', gatewayToken, '--dev', '--allow-unconfigured']; + args = [entryScript, 'gateway', '--port', String(this.status.port), '--token', gatewayToken, '--dev', '--allow-unconfigured']; } else { // Development mode: use pnpm gateway:dev which handles tsx compilation console.log('Starting Gateway in development mode (using pnpm)'); command = 'pnpm'; - args = ['run', 'dev', 'gateway', 'run', '--port', String(this.status.port), '--token', gatewayToken, '--dev', '--allow-unconfigured']; + args = ['run', 'dev', 'gateway', '--port', String(this.status.port), '--token', gatewayToken, '--dev', '--allow-unconfigured']; } console.log(`Spawning Gateway: ${command} ${args.join(' ')}`); console.log(`Working directory: ${openclawDir}`); + + // Resolve bundled bin path for uv + let binPath = ''; + const platform = process.platform; + const arch = process.arch; + // Map arch if necessary (e.g. x64 is standard, but ensure consistency with script) + const target = `${platform}-${arch}`; + + if (app.isPackaged) { + // In production, we flattened the structure to 'bin/' using electron-builder macros + binPath = path.join(process.resourcesPath, 'bin'); + } else { + // In dev, resources are at project root/resources/bin/- + binPath = path.join(process.cwd(), 'resources', 'bin', target); + } + + // Only inject if the bundled directory exists + const finalPath = existsSync(binPath) + ? `${binPath}${path.delimiter}${process.env.PATH || ''}` + : process.env.PATH || ''; + + if (existsSync(binPath)) { + console.log('Injecting bundled bin path:', binPath); + } // Load provider API keys from secure storage to pass as environment variables const providerEnv: Record = {}; @@ -398,13 +424,15 @@ export class GatewayManager extends EventEmitter { shell: process.platform === 'win32', // Use shell on Windows for pnpm env: { ...process.env, + PATH: finalPath, // Inject bundled bin path if it exists // Provider API keys ...providerEnv, - // Skip channel auto-connect during startup for faster boot - OPENCLAW_SKIP_CHANNELS: '1', - CLAWDBOT_SKIP_CHANNELS: '1', // Also set token via environment variable as fallback OPENCLAW_GATEWAY_TOKEN: gatewayToken, + // Ensure OPENCLAW_SKIP_CHANNELS is NOT set so channels auto-start + // and config hot-reload can restart channels when config changes + OPENCLAW_SKIP_CHANNELS: '', + CLAWDBOT_SKIP_CHANNELS: '', }, }); diff --git a/electron/main/index.ts b/electron/main/index.ts index 207d2ac9a..a4b0eca0a 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -14,9 +14,12 @@ import { appUpdater, registerUpdateHandlers } from './updater'; // Disable GPU acceleration for better compatibility app.disableHardwareAcceleration(); +import { ClawHubService } from '../gateway/clawhub'; + // Global references let mainWindow: BrowserWindow | null = null; const gatewayManager = new GatewayManager(); +const clawHubService = new ClawHubService(); /** * Create the main application window @@ -80,12 +83,12 @@ async function initialize(): Promise { // which prevents embedding in an iframe. Only apply to gateway URLs. session.defaultSession.webRequest.onHeadersReceived((details, callback) => { const isGatewayUrl = details.url.includes('127.0.0.1:18789') || details.url.includes('localhost:18789'); - + if (!isGatewayUrl) { callback({ responseHeaders: details.responseHeaders }); return; } - + const headers = { ...details.responseHeaders }; // Remove X-Frame-Options to allow embedding in iframe delete headers['X-Frame-Options']; @@ -103,9 +106,9 @@ async function initialize(): Promise { } callback({ responseHeaders: headers }); }); - + // Register IPC handlers - registerIpcHandlers(gatewayManager, mainWindow); + registerIpcHandlers(gatewayManager, clawHubService, mainWindow); // Register update handlers registerUpdateHandlers(appUpdater, mainWindow); diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 85e130abc..b6daf0367 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -4,6 +4,7 @@ */ import { ipcMain, BrowserWindow, shell, dialog, app } from 'electron'; import { GatewayManager } from '../gateway/manager'; +import { ClawHubService, ClawHubSearchParams, ClawHubInstallParams, ClawHubUninstallParams } from '../gateway/clawhub'; import { storeApiKey, getApiKey, @@ -22,31 +23,107 @@ import { import { getOpenClawStatus } from '../utils/paths'; import { getSetting } from '../utils/store'; import { saveProviderKeyToOpenClaw, setOpenClawDefaultModel } from '../utils/openclaw-auth'; +import { + saveChannelConfig, + getChannelConfig, + getChannelFormValues, + deleteChannelConfig, + listConfiguredChannels, + setChannelEnabled, + validateChannelConfig, + validateChannelCredentials, +} from '../utils/channel-config'; +import { checkUvInstalled, installUv, setupManagedPython } from '../utils/uv-setup'; +import { updateSkillConfig, getSkillConfig, getAllSkillConfigs } from '../utils/skill-config'; /** * Register all IPC handlers */ export function registerIpcHandlers( gatewayManager: GatewayManager, + clawHubService: ClawHubService, mainWindow: BrowserWindow ): void { // Gateway handlers registerGatewayHandlers(gatewayManager, mainWindow); - + + // ClawHub handlers + registerClawHubHandlers(clawHubService); + // OpenClaw handlers registerOpenClawHandlers(); - + // Provider handlers registerProviderHandlers(); - + // Shell handlers registerShellHandlers(); - + // Dialog handlers registerDialogHandlers(); - + // App handlers registerAppHandlers(); + + // UV handlers + registerUvHandlers(); + + // Skill config handlers (direct file access, no Gateway RPC) + registerSkillConfigHandlers(); +} + +/** + * Skill config IPC handlers + * Direct read/write to ~/.openclaw/openclaw.json (bypasses Gateway RPC) + */ +function registerSkillConfigHandlers(): void { + // Update skill config (apiKey and env) + ipcMain.handle('skill:updateConfig', async (_, params: { + skillKey: string; + apiKey?: string; + env?: Record; + }) => { + return updateSkillConfig(params.skillKey, { + apiKey: params.apiKey, + env: params.env, + }); + }); + + // Get skill config + ipcMain.handle('skill:getConfig', async (_, skillKey: string) => { + return getSkillConfig(skillKey); + }); + + // Get all skill configs + ipcMain.handle('skill:getAllConfigs', async () => { + return getAllSkillConfigs(); + }); +} + +/** + * UV-related IPC handlers + */ +function registerUvHandlers(): void { + // Check if uv is installed + ipcMain.handle('uv:check', async () => { + return await checkUvInstalled(); + }); + + // Install uv and setup managed Python + ipcMain.handle('uv:install-all', async () => { + try { + const isInstalled = await checkUvInstalled(); + if (!isInstalled) { + await installUv(); + } + // Always run python setup to ensure it exists in uv's cache + await setupManagedPython(); + return { success: true }; + } catch (error) { + console.error('Failed to setup uv/python:', error); + return { success: false, error: String(error) }; + } + }); } /** @@ -60,12 +137,12 @@ function registerGatewayHandlers( ipcMain.handle('gateway:status', () => { return gatewayManager.getStatus(); }); - + // Check if Gateway is connected ipcMain.handle('gateway:isConnected', () => { return gatewayManager.isConnected(); }); - + // Start Gateway ipcMain.handle('gateway:start', async () => { try { @@ -75,7 +152,7 @@ function registerGatewayHandlers( return { success: false, error: String(error) }; } }); - + // Stop Gateway ipcMain.handle('gateway:stop', async () => { try { @@ -85,7 +162,7 @@ function registerGatewayHandlers( return { success: false, error: String(error) }; } }); - + // Restart Gateway ipcMain.handle('gateway:restart', async () => { try { @@ -95,7 +172,7 @@ function registerGatewayHandlers( return { success: false, error: String(error) }; } }); - + // Gateway RPC call ipcMain.handle('gateway:rpc', async (_, method: string, params?: unknown, timeoutMs?: number) => { try { @@ -105,7 +182,7 @@ function registerGatewayHandlers( return { success: false, error: String(error) }; } }); - + // Get the Control UI URL with token for embedding ipcMain.handle('gateway:getControlUiUrl', async () => { try { @@ -119,7 +196,7 @@ function registerGatewayHandlers( return { success: false, error: String(error) }; } }); - + // Health check ipcMain.handle('gateway:health', async () => { try { @@ -129,44 +206,44 @@ function registerGatewayHandlers( return { success: false, ok: false, error: String(error) }; } }); - + // Forward Gateway events to renderer gatewayManager.on('status', (status) => { if (!mainWindow.isDestroyed()) { mainWindow.webContents.send('gateway:status-changed', status); } }); - + gatewayManager.on('message', (message) => { if (!mainWindow.isDestroyed()) { mainWindow.webContents.send('gateway:message', message); } }); - + gatewayManager.on('notification', (notification) => { if (!mainWindow.isDestroyed()) { mainWindow.webContents.send('gateway:notification', notification); } }); - + gatewayManager.on('channel:status', (data) => { if (!mainWindow.isDestroyed()) { mainWindow.webContents.send('gateway:channel-status', data); } }); - + gatewayManager.on('chat:message', (data) => { if (!mainWindow.isDestroyed()) { mainWindow.webContents.send('gateway:chat-message', data); } }); - + gatewayManager.on('exit', (code) => { if (!mainWindow.isDestroyed()) { mainWindow.webContents.send('gateway:exit', code); } }); - + gatewayManager.on('error', (error) => { if (!mainWindow.isDestroyed()) { mainWindow.webContents.send('gateway:error', error.message); @@ -176,21 +253,113 @@ function registerGatewayHandlers( /** * OpenClaw-related IPC handlers - * For checking submodule status + * For checking submodule status and channel configuration */ function registerOpenClawHandlers(): void { + // Get OpenClaw submodule status ipcMain.handle('openclaw:status', () => { return getOpenClawStatus(); }); - + // Check if OpenClaw is ready (submodule present and dependencies installed) ipcMain.handle('openclaw:isReady', () => { const status = getOpenClawStatus(); return status.submoduleExists && status.isInstalled; }); + + // ==================== Channel Configuration Handlers ==================== + + // Save channel configuration + ipcMain.handle('channel:saveConfig', async (_, channelType: string, config: Record) => { + try { + saveChannelConfig(channelType, config); + return { success: true }; + } catch (error) { + console.error('Failed to save channel config:', error); + return { success: false, error: String(error) }; + } + }); + + // Get channel configuration + ipcMain.handle('channel:getConfig', async (_, channelType: string) => { + try { + const config = getChannelConfig(channelType); + return { success: true, config }; + } catch (error) { + console.error('Failed to get channel config:', error); + return { success: false, error: String(error) }; + } + }); + + // Get channel form values (reverse-transformed for UI pre-fill) + ipcMain.handle('channel:getFormValues', async (_, channelType: string) => { + try { + const values = getChannelFormValues(channelType); + return { success: true, values }; + } catch (error) { + console.error('Failed to get channel form values:', error); + return { success: false, error: String(error) }; + } + }); + + // Delete channel configuration + ipcMain.handle('channel:deleteConfig', async (_, channelType: string) => { + try { + deleteChannelConfig(channelType); + return { success: true }; + } catch (error) { + console.error('Failed to delete channel config:', error); + return { success: false, error: String(error) }; + } + }); + + // List configured channels + ipcMain.handle('channel:listConfigured', async () => { + try { + const channels = listConfiguredChannels(); + return { success: true, channels }; + } catch (error) { + console.error('Failed to list channels:', error); + return { success: false, error: String(error) }; + } + }); + + // Enable or disable a channel + ipcMain.handle('channel:setEnabled', async (_, channelType: string, enabled: boolean) => { + try { + setChannelEnabled(channelType, enabled); + return { success: true }; + } catch (error) { + console.error('Failed to set channel enabled:', error); + return { success: false, error: String(error) }; + } + }); + + // Validate channel configuration + ipcMain.handle('channel:validate', async (_, channelType: string) => { + try { + const result = await validateChannelConfig(channelType); + return { success: true, ...result }; + } catch (error) { + console.error('Failed to validate channel:', error); + return { success: false, valid: false, errors: [String(error)], warnings: [] }; + } + }); + + // Validate channel credentials by calling actual service APIs (before saving) + ipcMain.handle('channel:validateCredentials', async (_, channelType: string, config: Record) => { + try { + const result = await validateChannelCredentials(channelType, config); + return { success: true, ...result }; + } catch (error) { + console.error('Failed to validate channel credentials:', error); + return { success: false, valid: false, errors: [String(error)], warnings: [] }; + } + }); } + /** * Provider-related IPC handlers */ @@ -199,27 +368,27 @@ function registerProviderHandlers(): void { ipcMain.handle('provider:encryptionAvailable', () => { return isEncryptionAvailable(); }); - + // Get all providers with key info ipcMain.handle('provider:list', async () => { return await getAllProvidersWithKeyInfo(); }); - + // Get a specific provider ipcMain.handle('provider:get', async (_, providerId: string) => { return await getProvider(providerId); }); - + // Save a provider configuration ipcMain.handle('provider:save', async (_, config: ProviderConfig, apiKey?: string) => { try { // Save the provider config await saveProvider(config); - + // Store the API key if provided if (apiKey) { await storeApiKey(config.id, apiKey); - + // Also write to OpenClaw auth-profiles.json so the gateway can use it try { saveProviderKeyToOpenClaw(config.type, apiKey); @@ -227,20 +396,20 @@ function registerProviderHandlers(): void { console.warn('Failed to save key to OpenClaw auth-profiles:', err); } } - + // Set the default model in OpenClaw config based on provider type try { setOpenClawDefaultModel(config.type); } catch (err) { console.warn('Failed to set OpenClaw default model:', err); } - + return { success: true }; } catch (error) { return { success: false, error: String(error) }; } }); - + // Delete a provider ipcMain.handle('provider:delete', async (_, providerId: string) => { try { @@ -250,12 +419,12 @@ function registerProviderHandlers(): void { return { success: false, error: String(error) }; } }); - + // Update API key for a provider ipcMain.handle('provider:setApiKey', async (_, providerId: string, apiKey: string) => { try { await storeApiKey(providerId, apiKey); - + // Also write to OpenClaw auth-profiles.json // Resolve provider type from stored config, or use providerId as type const provider = await getProvider(providerId); @@ -265,13 +434,13 @@ function registerProviderHandlers(): void { } catch (err) { console.warn('Failed to save key to OpenClaw auth-profiles:', err); } - + return { success: true }; } catch (error) { return { success: false, error: String(error) }; } }); - + // Delete API key for a provider ipcMain.handle('provider:deleteApiKey', async (_, providerId: string) => { try { @@ -281,17 +450,17 @@ function registerProviderHandlers(): void { return { success: false, error: String(error) }; } }); - + // Check if a provider has an API key ipcMain.handle('provider:hasApiKey', async (_, providerId: string) => { return await hasApiKey(providerId); }); - + // Get the actual API key (for internal use only - be careful!) ipcMain.handle('provider:getApiKey', async (_, providerId: string) => { return await getApiKey(providerId); }); - + // Set default provider ipcMain.handle('provider:setDefault', async (_, providerId: string) => { try { @@ -301,23 +470,23 @@ function registerProviderHandlers(): void { return { success: false, error: String(error) }; } }); - + // Get default provider ipcMain.handle('provider:getDefault', async () => { return await getDefaultProvider(); }); - + // Validate API key by making a real test request to the provider // providerId can be either a stored provider ID or a provider type (e.g., 'openrouter', 'anthropic') ipcMain.handle('provider:validateKey', async (_, providerId: string, apiKey: string) => { try { // First try to get existing provider const provider = await getProvider(providerId); - + // Use provider.type if provider exists, otherwise use providerId as the type // This allows validation during setup when provider hasn't been saved yet const providerType = provider?.type || providerId; - + console.log(`Validating API key for provider type: ${providerType}`); return await validateApiKeyWithProvider(providerType, apiKey); } catch (error) { @@ -368,15 +537,15 @@ async function validateApiKeyWithProvider( */ function parseApiError(data: unknown): string { if (!data || typeof data !== 'object') return 'Unknown error'; - + // Anthropic format: { error: { message: "..." } } // OpenAI format: { error: { message: "..." } } // Google format: { error: { message: "..." } } const obj = data as { error?: { message?: string; type?: string }; message?: string }; - + if (obj.error?.message) return obj.error.message; if (obj.message) return obj.message; - + return 'Unknown error'; } @@ -564,17 +733,17 @@ async function validateOpenRouterKey(apiKey: string): Promise<{ valid: boolean; const isAuthError = (d: unknown): boolean => { const errorObj = (d as { error?: { message?: string; code?: number | string; type?: string } })?.error; if (!errorObj) return false; - + const message = (errorObj.message || '').toLowerCase(); const code = errorObj.code; const type = (errorObj.type || '').toLowerCase(); - + // Check for explicit auth-related errors if (code === 401 || code === '401' || code === 403 || code === '403') return true; if (type.includes('auth') || type.includes('invalid')) return true; - if (message.includes('invalid api key') || message.includes('invalid key') || - message.includes('unauthorized') || message.includes('authentication') || - message.includes('invalid credentials') || message.includes('api key is not valid')) { + if (message.includes('invalid api key') || message.includes('invalid key') || + message.includes('unauthorized') || message.includes('authentication') || + message.includes('invalid credentials') || message.includes('api key is not valid')) { return true; } return false; @@ -611,12 +780,12 @@ async function validateOpenRouterKey(apiKey: string): Promise<{ valid: boolean; // But be conservative - require explicit success indication const errorObj = (data as { error?: { message?: string; code?: number } })?.error; const message = (errorObj?.message || '').toLowerCase(); - + // Only consider valid if the error is clearly about the model, not the key if (message.includes('model') && !message.includes('key') && !message.includes('auth')) { return { valid: true }; } - + // Default to invalid for ambiguous 400/404 errors return { valid: false, error: parseApiError(data) || 'Invalid API key or request' }; } @@ -635,18 +804,73 @@ function registerShellHandlers(): void { ipcMain.handle('shell:openExternal', async (_, url: string) => { await shell.openExternal(url); }); - + // Open path in file explorer ipcMain.handle('shell:showItemInFolder', async (_, path: string) => { shell.showItemInFolder(path); }); - + // Open path ipcMain.handle('shell:openPath', async (_, path: string) => { return await shell.openPath(path); }); } +/** + * ClawHub-related IPC handlers + */ +function registerClawHubHandlers(clawHubService: ClawHubService): void { + // Search skills + ipcMain.handle('clawhub:search', async (_, params: ClawHubSearchParams) => { + try { + const results = await clawHubService.search(params); + return { success: true, results }; + } catch (error) { + return { success: false, error: String(error) }; + } + }); + + // Install skill + ipcMain.handle('clawhub:install', async (_, params: ClawHubInstallParams) => { + try { + await clawHubService.install(params); + return { success: true }; + } catch (error) { + return { success: false, error: String(error) }; + } + }); + + // Uninstall skill + ipcMain.handle('clawhub:uninstall', async (_, params: ClawHubUninstallParams) => { + try { + await clawHubService.uninstall(params); + return { success: true }; + } catch (error) { + return { success: false, error: String(error) }; + } + }); + + // List installed skills + ipcMain.handle('clawhub:list', async () => { + try { + const results = await clawHubService.listInstalled(); + return { success: true, results }; + } catch (error) { + return { success: false, error: String(error) }; + } + }); + + // Open skill readme + ipcMain.handle('clawhub:openSkillReadme', async (_, slug: string) => { + try { + await clawHubService.openSkillReadme(slug); + return { success: true }; + } catch (error) { + return { success: false, error: String(error) }; + } + }); +} + /** * Dialog-related IPC handlers */ @@ -656,13 +880,13 @@ function registerDialogHandlers(): void { const result = await dialog.showOpenDialog(options); return result; }); - + // Show save dialog ipcMain.handle('dialog:save', async (_, options: Electron.SaveDialogOptions) => { const result = await dialog.showSaveDialog(options); return result; }); - + // Show message box ipcMain.handle('dialog:message', async (_, options: Electron.MessageBoxOptions) => { const result = await dialog.showMessageBox(options); @@ -678,27 +902,27 @@ function registerAppHandlers(): void { ipcMain.handle('app:version', () => { return app.getVersion(); }); - + // Get app name ipcMain.handle('app:name', () => { return app.getName(); }); - + // Get app path ipcMain.handle('app:getPath', (_, name: Parameters[0]) => { return app.getPath(name); }); - + // Get platform ipcMain.handle('app:platform', () => { return process.platform; }); - + // Quit app ipcMain.handle('app:quit', () => { app.quit(); }); - + // Relaunch app ipcMain.handle('app:relaunch', () => { app.relaunch(); diff --git a/electron/preload/index.ts b/electron/preload/index.ts index c4850bef7..f849d7c1d 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -78,15 +78,37 @@ const electronAPI = { 'cron:delete', 'cron:toggle', 'cron:trigger', + // Channel Config + 'channel:saveConfig', + 'channel:getConfig', + 'channel:getFormValues', + 'channel:deleteConfig', + 'channel:listConfigured', + 'channel:setEnabled', + 'channel:validate', + 'channel:validateCredentials', + // ClawHub + 'clawhub:search', + 'clawhub:install', + 'clawhub:uninstall', + 'clawhub:list', + 'clawhub:openSkillReadme', + // UV + 'uv:check', + 'uv:install-all', + // Skill config (direct file access) + 'skill:updateConfig', + 'skill:getConfig', + 'skill:getAllConfigs', ]; - + if (validChannels.includes(channel)) { return ipcRenderer.invoke(channel, ...args); } - + throw new Error(`Invalid IPC channel: ${channel}`); }, - + /** * Listen for events from main process */ @@ -109,23 +131,23 @@ const electronAPI = { 'update:error', 'cron:updated', ]; - + if (validChannels.includes(channel)) { // Wrap the callback to strip the event const subscription = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => { callback(...args); }; ipcRenderer.on(channel, subscription); - + // Return unsubscribe function return () => { ipcRenderer.removeListener(channel, subscription); }; } - + throw new Error(`Invalid IPC channel: ${channel}`); }, - + /** * Listen for a single event from main process */ @@ -147,15 +169,15 @@ const electronAPI = { 'update:downloaded', 'update:error', ]; - + if (validChannels.includes(channel)) { ipcRenderer.once(channel, (_event, ...args) => callback(...args)); return; } - + throw new Error(`Invalid IPC channel: ${channel}`); }, - + /** * Remove all listeners for a channel */ @@ -168,19 +190,19 @@ const electronAPI = { } }, }, - + /** * Open external URL in default browser */ openExternal: (url: string) => { return ipcRenderer.invoke('shell:openExternal', url); }, - + /** * Get current platform */ platform: process.platform, - + /** * Check if running in development */ diff --git a/electron/utils/channel-config.ts b/electron/utils/channel-config.ts new file mode 100644 index 000000000..89b5b4caa --- /dev/null +++ b/electron/utils/channel-config.ts @@ -0,0 +1,579 @@ +/** + * Channel Configuration Utilities + * Manages channel configuration in OpenClaw config files + */ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; +import { join } from 'path'; +import { homedir } from 'os'; + +const OPENCLAW_DIR = join(homedir(), '.openclaw'); +const CONFIG_FILE = join(OPENCLAW_DIR, 'openclaw.json'); + +export interface ChannelConfigData { + enabled?: boolean; + [key: string]: unknown; +} + +export interface OpenClawConfig { + channels?: Record; + [key: string]: unknown; +} + +/** + * Ensure OpenClaw config directory exists + */ +function ensureConfigDir(): void { + if (!existsSync(OPENCLAW_DIR)) { + mkdirSync(OPENCLAW_DIR, { recursive: true }); + } +} + +/** + * Read OpenClaw configuration + */ +export function readOpenClawConfig(): OpenClawConfig { + ensureConfigDir(); + + if (!existsSync(CONFIG_FILE)) { + return {}; + } + + try { + const content = readFileSync(CONFIG_FILE, 'utf-8'); + return JSON.parse(content) as OpenClawConfig; + } catch (error) { + console.error('Failed to read OpenClaw config:', error); + return {}; + } +} + +/** + * Write OpenClaw configuration + */ +export function writeOpenClawConfig(config: OpenClawConfig): void { + ensureConfigDir(); + + try { + writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8'); + } catch (error) { + console.error('Failed to write OpenClaw config:', error); + throw error; + } +} + +/** + * Save channel configuration + * @param channelType - The channel type (e.g., 'telegram', 'discord') + * @param config - The channel configuration object + */ +export function saveChannelConfig( + channelType: string, + config: ChannelConfigData +): void { + const currentConfig = readOpenClawConfig(); + + if (!currentConfig.channels) { + currentConfig.channels = {}; + } + + // Transform config to match OpenClaw expected format + let transformedConfig: ChannelConfigData = { ...config }; + + // Special handling for Discord: convert guildId/channelId to complete structure + if (channelType === 'discord') { + const { guildId, channelId, ...restConfig } = config; + transformedConfig = { ...restConfig }; + + // Add standard Discord config + transformedConfig.groupPolicy = 'allowlist'; + transformedConfig.dm = { enabled: false }; + transformedConfig.retry = { + attempts: 3, + minDelayMs: 500, + maxDelayMs: 30000, + jitter: 0.1, + }; + + // Build guilds structure + if (guildId && typeof guildId === 'string' && guildId.trim()) { + const guildConfig: Record = { + users: ['*'], + requireMention: true, + }; + + // Add channels config + if (channelId && typeof channelId === 'string' && channelId.trim()) { + // Specific channel + guildConfig.channels = { + [channelId.trim()]: { allow: true, requireMention: true } + }; + } else { + // All channels + guildConfig.channels = { + '*': { allow: true, requireMention: true } + }; + } + + transformedConfig.guilds = { + [guildId.trim()]: guildConfig + }; + } + } + + // Merge with existing config + currentConfig.channels[channelType] = { + ...currentConfig.channels[channelType], + ...transformedConfig, + enabled: transformedConfig.enabled ?? true, + }; + + writeOpenClawConfig(currentConfig); + console.log(`Saved channel config for ${channelType}`); +} + +/** + * Get channel configuration + * @param channelType - The channel type + */ +export function getChannelConfig(channelType: string): ChannelConfigData | undefined { + const config = readOpenClawConfig(); + return config.channels?.[channelType]; +} + +/** + * Get channel configuration as form-friendly values. + * Reverses the transformation done in saveChannelConfig so the + * values can be fed back into the UI form fields. + * + * @param channelType - The channel type + * @returns A flat Record matching the form field keys, or undefined + */ +export function getChannelFormValues(channelType: string): Record | undefined { + const saved = getChannelConfig(channelType); + if (!saved) return undefined; + + const values: Record = {}; + + if (channelType === 'discord') { + // token is stored at top level + if (saved.token && typeof saved.token === 'string') { + values.token = saved.token; + } + + // Extract guildId and channelId from the nested guilds structure + const guilds = saved.guilds as Record> | undefined; + if (guilds) { + const guildIds = Object.keys(guilds); + if (guildIds.length > 0) { + values.guildId = guildIds[0]; + + const guildConfig = guilds[guildIds[0]]; + const channels = guildConfig?.channels as Record | undefined; + if (channels) { + const channelIds = Object.keys(channels).filter((id) => id !== '*'); + if (channelIds.length > 0) { + values.channelId = channelIds[0]; + } + } + } + } + } else { + // For other channel types, extract all string values directly + for (const [key, value] of Object.entries(saved)) { + if (typeof value === 'string' && key !== 'enabled') { + values[key] = value; + } + } + } + + return Object.keys(values).length > 0 ? values : undefined; +} + +/** + * Delete channel configuration + * @param channelType - The channel type + */ +export function deleteChannelConfig(channelType: string): void { + const currentConfig = readOpenClawConfig(); + + if (currentConfig.channels?.[channelType]) { + delete currentConfig.channels[channelType]; + writeOpenClawConfig(currentConfig); + console.log(`Deleted channel config for ${channelType}`); + } +} + +/** + * List all configured channels + */ +export function listConfiguredChannels(): string[] { + const config = readOpenClawConfig(); + if (!config.channels) { + return []; + } + + return Object.keys(config.channels).filter( + (channelType) => config.channels![channelType]?.enabled !== false + ); +} + +/** + * Enable or disable a channel + */ +export function setChannelEnabled(channelType: string, enabled: boolean): void { + const currentConfig = readOpenClawConfig(); + + if (!currentConfig.channels) { + currentConfig.channels = {}; + } + + if (!currentConfig.channels[channelType]) { + currentConfig.channels[channelType] = {}; + } + + currentConfig.channels[channelType].enabled = enabled; + writeOpenClawConfig(currentConfig); + console.log(`Set channel ${channelType} enabled: ${enabled}`); +} + +export interface ValidationResult { + valid: boolean; + errors: string[]; + warnings: string[]; +} + +export interface CredentialValidationResult { + valid: boolean; + errors: string[]; + warnings: string[]; + /** Extra info returned from the API (e.g. bot username, guild name) */ + details?: Record; +} + +/** + * Validate channel credentials by calling the actual service APIs + * This validates the raw config values BEFORE saving them. + * + * @param channelType - The channel type (e.g., 'discord', 'telegram') + * @param config - The raw config values from the form + */ +export async function validateChannelCredentials( + channelType: string, + config: Record +): Promise { + switch (channelType) { + case 'discord': + return validateDiscordCredentials(config); + case 'telegram': + return validateTelegramCredentials(config); + case 'slack': + return validateSlackCredentials(config); + default: + // For channels without specific validation, just check required fields are present + return { valid: true, errors: [], warnings: ['No online validation available for this channel type.'] }; + } +} + +/** + * Validate Discord bot token and optional guild/channel IDs + */ +async function validateDiscordCredentials( + config: Record +): Promise { + const result: CredentialValidationResult = { valid: true, errors: [], warnings: [], details: {} }; + const token = config.token?.trim(); + + if (!token) { + return { valid: false, errors: ['Bot token is required'], warnings: [] }; + } + + // 1) Validate bot token by calling GET /users/@me + try { + const meResponse = await fetch('https://discord.com/api/v10/users/@me', { + headers: { Authorization: `Bot ${token}` }, + }); + + if (!meResponse.ok) { + if (meResponse.status === 401) { + return { valid: false, errors: ['Invalid bot token. Please check and try again.'], warnings: [] }; + } + const errorData = await meResponse.json().catch(() => ({})); + const msg = (errorData as { message?: string }).message || `Discord API error: ${meResponse.status}`; + return { valid: false, errors: [msg], warnings: [] }; + } + + const meData = (await meResponse.json()) as { username?: string; id?: string; bot?: boolean }; + if (!meData.bot) { + return { + valid: false, + errors: ['The provided token belongs to a user account, not a bot. Please use a bot token.'], + warnings: [], + }; + } + result.details!.botUsername = meData.username || 'Unknown'; + result.details!.botId = meData.id || ''; + } catch (error) { + return { + valid: false, + errors: [`Connection error when validating bot token: ${error instanceof Error ? error.message : String(error)}`], + warnings: [], + }; + } + + // 2) Validate guild ID (optional) + const guildId = config.guildId?.trim(); + if (guildId) { + try { + const guildResponse = await fetch(`https://discord.com/api/v10/guilds/${guildId}`, { + headers: { Authorization: `Bot ${token}` }, + }); + + if (!guildResponse.ok) { + if (guildResponse.status === 403 || guildResponse.status === 404) { + result.errors.push( + `Cannot access guild (server) with ID "${guildId}". Make sure the bot has been invited to this server.` + ); + result.valid = false; + } else { + result.errors.push(`Failed to verify guild ID: Discord API returned ${guildResponse.status}`); + result.valid = false; + } + } else { + const guildData = (await guildResponse.json()) as { name?: string }; + result.details!.guildName = guildData.name || 'Unknown'; + } + } catch (error) { + result.warnings.push(`Could not verify guild ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + + // 3) Validate channel ID (optional) + const channelId = config.channelId?.trim(); + if (channelId) { + try { + const channelResponse = await fetch(`https://discord.com/api/v10/channels/${channelId}`, { + headers: { Authorization: `Bot ${token}` }, + }); + + if (!channelResponse.ok) { + if (channelResponse.status === 403 || channelResponse.status === 404) { + result.errors.push( + `Cannot access channel with ID "${channelId}". Make sure the bot has permission to view this channel.` + ); + result.valid = false; + } else { + result.errors.push(`Failed to verify channel ID: Discord API returned ${channelResponse.status}`); + result.valid = false; + } + } else { + const channelData = (await channelResponse.json()) as { name?: string; guild_id?: string }; + result.details!.channelName = channelData.name || 'Unknown'; + + // Cross-check: if both guild and channel are provided, make sure channel belongs to the guild + if (guildId && channelData.guild_id && channelData.guild_id !== guildId) { + result.errors.push( + `Channel "${channelData.name}" does not belong to the specified guild. It belongs to a different server.` + ); + result.valid = false; + } + } + } catch (error) { + result.warnings.push(`Could not verify channel ID: ${error instanceof Error ? error.message : String(error)}`); + } + } + + return result; +} + +/** + * Validate Telegram bot token + */ +async function validateTelegramCredentials( + config: Record +): Promise { + const botToken = config.botToken?.trim(); + + if (!botToken) { + return { valid: false, errors: ['Bot token is required'], warnings: [] }; + } + + try { + const response = await fetch(`https://api.telegram.org/bot${botToken}/getMe`); + const data = (await response.json()) as { ok?: boolean; description?: string; result?: { username?: string } }; + + if (data.ok) { + return { + valid: true, + errors: [], + warnings: [], + details: { botUsername: data.result?.username || 'Unknown' }, + }; + } + + return { + valid: false, + errors: [data.description || 'Invalid bot token'], + warnings: [], + }; + } catch (error) { + return { + valid: false, + errors: [`Connection error: ${error instanceof Error ? error.message : String(error)}`], + warnings: [], + }; + } +} + +/** + * Validate Slack bot token + */ +async function validateSlackCredentials( + config: Record +): Promise { + const botToken = config.botToken?.trim(); + + if (!botToken) { + return { valid: false, errors: ['Bot token is required'], warnings: [] }; + } + + try { + const response = await fetch('https://slack.com/api/auth.test', { + method: 'POST', + headers: { + Authorization: `Bearer ${botToken}`, + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }); + + const data = (await response.json()) as { ok?: boolean; error?: string; team?: string; user?: string }; + + if (data.ok) { + return { + valid: true, + errors: [], + warnings: [], + details: { team: data.team || 'Unknown', user: data.user || 'Unknown' }, + }; + } + + const errorMap: Record = { + invalid_auth: 'Invalid bot token', + account_inactive: 'Account is inactive', + token_revoked: 'Token has been revoked', + not_authed: 'No authentication token provided', + }; + + return { + valid: false, + errors: [errorMap[data.error || ''] || `Slack error: ${data.error}`], + warnings: [], + }; + } catch (error) { + return { + valid: false, + errors: [`Connection error: ${error instanceof Error ? error.message : String(error)}`], + warnings: [], + }; + } +} + +/** + * Validate channel configuration using OpenClaw doctor + */ +export async function validateChannelConfig(channelType: string): Promise { + const { execSync } = await import('child_process'); + const { join } = await import('path'); + const { app } = await import('electron'); + + const result: ValidationResult = { + valid: true, + errors: [], + warnings: [], + }; + + try { + // Get OpenClaw path + const openclawPath = app.isPackaged + ? join(process.resourcesPath, 'openclaw') + : join(__dirname, '../../openclaw'); + + // Run openclaw doctor command to validate config + const output = execSync( + `node openclaw.mjs doctor --json 2>&1`, + { + cwd: openclawPath, + encoding: 'utf-8', + timeout: 30000, + } + ); + + // Parse output for errors related to the channel + const lines = output.split('\n'); + for (const line of lines) { + const lowerLine = line.toLowerCase(); + if (lowerLine.includes(channelType) && lowerLine.includes('error')) { + result.errors.push(line.trim()); + result.valid = false; + } else if (lowerLine.includes(channelType) && lowerLine.includes('warning')) { + result.warnings.push(line.trim()); + } else if (lowerLine.includes('unrecognized key') && lowerLine.includes(channelType)) { + result.errors.push(line.trim()); + result.valid = false; + } + } + + // If no specific errors found, check if config exists and is valid + const config = readOpenClawConfig(); + if (!config.channels?.[channelType]) { + result.errors.push(`Channel ${channelType} is not configured`); + result.valid = false; + } else if (!config.channels[channelType].enabled) { + result.warnings.push(`Channel ${channelType} is disabled`); + } + + // Channel-specific validation + if (channelType === 'discord') { + const discordConfig = config.channels?.discord; + if (!discordConfig?.token) { + result.errors.push('Discord: Bot token is required'); + result.valid = false; + } + } else if (channelType === 'telegram') { + const telegramConfig = config.channels?.telegram; + if (!telegramConfig?.botToken) { + result.errors.push('Telegram: Bot token is required'); + result.valid = false; + } + } + + if (result.errors.length === 0 && result.warnings.length === 0) { + result.valid = true; + } + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + + // Check for config errors in the error message + if (errorMessage.includes('Unrecognized key') || errorMessage.includes('invalid config')) { + result.errors.push(errorMessage); + result.valid = false; + } else if (errorMessage.includes('ENOENT')) { + result.errors.push('OpenClaw not found. Please ensure OpenClaw is installed.'); + result.valid = false; + } else { + // Doctor command might fail but config could still be valid + // Just log it and do basic validation + console.warn('Doctor command failed:', errorMessage); + + const config = readOpenClawConfig(); + if (config.channels?.[channelType]) { + result.valid = true; + } else { + result.errors.push(`Channel ${channelType} is not configured`); + result.valid = false; + } + } + } + + return result; +} diff --git a/electron/utils/skill-config.ts b/electron/utils/skill-config.ts new file mode 100644 index 000000000..b75e839c1 --- /dev/null +++ b/electron/utils/skill-config.ts @@ -0,0 +1,130 @@ +/** + * Skill Config Utilities + * Direct read/write access to skill configuration in ~/.openclaw/openclaw.json + * This bypasses the Gateway RPC for faster and more reliable config updates + */ +import { readFileSync, writeFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import { homedir } from 'os'; + +const OPENCLAW_CONFIG_PATH = join(homedir(), '.openclaw', 'openclaw.json'); + +interface SkillEntry { + enabled?: boolean; + apiKey?: string; + env?: Record; +} + +interface OpenClawConfig { + skills?: { + entries?: Record; + [key: string]: unknown; + }; + [key: string]: unknown; +} + +/** + * Read the current OpenClaw config + */ +function readConfig(): OpenClawConfig { + if (!existsSync(OPENCLAW_CONFIG_PATH)) { + return {}; + } + try { + const raw = readFileSync(OPENCLAW_CONFIG_PATH, 'utf-8'); + return JSON.parse(raw); + } catch (err) { + console.error('Failed to read openclaw config:', err); + return {}; + } +} + +/** + * Write the OpenClaw config + */ +function writeConfig(config: OpenClawConfig): void { + const json = JSON.stringify(config, null, 2); + writeFileSync(OPENCLAW_CONFIG_PATH, json, 'utf-8'); +} + +/** + * Get skill config + */ +export function getSkillConfig(skillKey: string): SkillEntry | undefined { + const config = readConfig(); + return config.skills?.entries?.[skillKey]; +} + +/** + * Update skill config (apiKey and env) + */ +export function updateSkillConfig( + skillKey: string, + updates: { apiKey?: string; env?: Record } +): { success: boolean; error?: string } { + try { + const config = readConfig(); + + // Ensure skills.entries exists + if (!config.skills) { + config.skills = {}; + } + if (!config.skills.entries) { + config.skills.entries = {}; + } + + // Get or create skill entry + const entry = config.skills.entries[skillKey] || {}; + + // Update apiKey + if (updates.apiKey !== undefined) { + const trimmed = updates.apiKey.trim(); + if (trimmed) { + entry.apiKey = trimmed; + } else { + delete entry.apiKey; + } + } + + // Update env + if (updates.env !== undefined) { + const newEnv: Record = {}; + + // Process all keys from the update + for (const [key, value] of Object.entries(updates.env)) { + const trimmedKey = key.trim(); + if (!trimmedKey) continue; + + const trimmedVal = value.trim(); + if (trimmedVal) { + newEnv[trimmedKey] = trimmedVal; + } + // Empty value = don't include (delete) + } + + // Only set env if there are values, otherwise delete + if (Object.keys(newEnv).length > 0) { + entry.env = newEnv; + } else { + delete entry.env; + } + } + + // Save entry back + config.skills.entries[skillKey] = entry; + + writeConfig(config); + return { success: true }; + } catch (err) { + console.error('Failed to update skill config:', err); + return { success: false, error: String(err) }; + } +} + +/** + * Get all skill configs (for syncing to frontend) + */ +export function getAllSkillConfigs(): Record { + const config = readConfig(); + return config.skills?.entries || {}; +} diff --git a/electron/utils/uv-setup.ts b/electron/utils/uv-setup.ts new file mode 100644 index 000000000..acf7c1377 --- /dev/null +++ b/electron/utils/uv-setup.ts @@ -0,0 +1,114 @@ +import { app } from 'electron'; +import { spawn } from 'child_process'; +import { existsSync } from 'fs'; +import { join } from 'path'; + +/** + * Get the path to the bundled uv binary + */ +function getBundledUvPath(): string { + const platform = process.platform; + const arch = process.arch; + const target = `${platform}-${arch}`; + const binName = platform === 'win32' ? 'uv.exe' : 'uv'; + + if (app.isPackaged) { + // In production, we flattened the structure to 'bin/' + return join(process.resourcesPath, 'bin', binName); + } else { + // In dev, resources are at project root/resources/bin/- + return join(process.cwd(), 'resources', 'bin', target, binName); + } +} + +/** + * Check if uv is available (either in system PATH or bundled) + */ +export async function checkUvInstalled(): Promise { + // 1. Check system PATH first + const inPath = await new Promise((resolve) => { + const cmd = process.platform === 'win32' ? 'where.exe' : 'which'; + const child = spawn(cmd, ['uv']); + child.on('close', (code) => resolve(code === 0)); + child.on('error', () => resolve(false)); + }); + + if (inPath) return true; + + // 2. Check bundled path + const bin = getBundledUvPath(); + return existsSync(bin); +} + +/** + * "Install" uv - now just verifies that uv is available somewhere. + * Kept for API compatibility with frontend. + */ +export async function installUv(): Promise { + const isAvailable = await checkUvInstalled(); + if (!isAvailable) { + const bin = getBundledUvPath(); + throw new Error(`uv not found in system PATH and bundled binary missing at ${bin}`); + } + console.log('uv is available and ready to use'); +} + +/** + * Use bundled uv to install a managed Python version (default 3.12) + * Automatically picks the best available uv binary + */ +export async function setupManagedPython(): Promise { + // Use 'uv' if in PATH, otherwise use full bundled path + const inPath = await new Promise((resolve) => { + const cmd = process.platform === 'win32' ? 'where.exe' : 'which'; + const child = spawn(cmd, ['uv']); + child.on('close', (code) => resolve(code === 0)); + child.on('error', () => resolve(false)); + }); + + const uvBin = inPath ? 'uv' : getBundledUvPath(); + + console.log(`Setting up python with: ${uvBin}`); + + await new Promise((resolve, reject) => { + const child = spawn(uvBin, ['python', 'install', '3.12'], { + shell: process.platform === 'win32' + }); + + child.stdout?.on('data', (data) => { + console.log(`python setup stdout: ${data}`); + }); + + child.stderr?.on('data', (data) => { + // uv prints progress to stderr, so we log it as info + console.log(`python setup info: ${data.toString().trim()}`); + }); + + child.on('close', (code) => { + if (code === 0) resolve(); + else reject(new Error(`Python installation failed with code ${code}`)); + }); + + child.on('error', (err) => reject(err)); + }); + + // After installation, find and print where the Python executable is + try { + const findPath = await new Promise((resolve) => { + const child = spawn(uvBin, ['python', 'find', '3.12'], { + shell: process.platform === 'win32' + }); + let output = ''; + child.stdout?.on('data', (data) => { output += data; }); + child.on('close', () => resolve(output.trim())); + }); + + if (findPath) { + console.log(`✅ Managed Python 3.12 path: ${findPath}`); + // Note: uv stores environments in a central cache, + // Individual skills will create their own venvs in ~/.cache/uv or similar. + } + } catch (err) { + console.warn('Could not determine Python path:', err); + } +} \ No newline at end of file diff --git a/package.json b/package.json index afe7d192c..b48a0e131 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "scripts": { "dev": "vite", "dev:electron": "electron .", - "build": "pnpm run build:vite && pnpm run package", + "build": "pnpm run uv:download && pnpm run build:vite && pnpm run package", "build:vite": "vite build", "build:electron": "tsc -p tsconfig.node.json", "preview": "vite preview", @@ -26,6 +26,8 @@ "test:watch": "vitest", "test:coverage": "vitest run --coverage", "test:e2e": "playwright test", + "uv:download": "node scripts/download-bundled-uv.mjs", + "uv:download:all": "node scripts/download-bundled-uv.mjs --all", "icons": "bash scripts/generate-icons.sh", "clean": "rm -rf dist dist-electron release", "package": "electron-builder", @@ -78,6 +80,7 @@ "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "class-variance-authority": "^0.7.1", + "clawhub": "^0.5.0", "clsx": "^2.1.1", "electron": "^33.3.0", "electron-builder": "^25.1.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d54bf3a4..af0ccf72d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: class-variance-authority: specifier: ^0.7.1 version: 0.7.1 + clawhub: + specifier: ^0.5.0 + version: 0.5.0 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -184,6 +187,12 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@ark/schema@0.56.0': + resolution: {integrity: sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==} + + '@ark/util@0.56.0': + resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==} + '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -274,6 +283,12 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@clack/core@0.5.0': + resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} + + '@clack/prompts@0.11.0': + resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==} + '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} @@ -1695,6 +1710,12 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + arkregex@0.0.5: + resolution: {integrity: sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==} + + arktype@2.1.29: + resolution: {integrity: sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ==} + assert-plus@1.0.0: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} @@ -1838,6 +1859,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -1872,6 +1897,11 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + clawhub@0.5.0: + resolution: {integrity: sha512-tIPoup8mY3ojR+fzzf85ft+vrhMd6u6188QzBEOf/f5/0NSoWW0fl7ojw6VgVSLbBtLa5MGQDxSuZkf9TqPwIw==} + engines: {node: '>=20'} + hasBin: true + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -1880,10 +1910,18 @@ packages: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + cli-spinners@2.9.2: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} + engines: {node: '>=18.20'} + cli-truncate@2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} @@ -1921,6 +1959,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -2321,6 +2363,9 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -2420,6 +2465,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2638,9 +2687,17 @@ packages: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + is-network-error@1.3.0: + resolution: {integrity: sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==} + engines: {node: '>=16'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -2656,6 +2713,10 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -2789,6 +2850,10 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + log-symbols@7.0.1: + resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} + engines: {node: '>=18'} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -2990,6 +3055,11 @@ packages: engines: {node: '>=4.0.0'} hasBin: true + mime@4.1.0: + resolution: {integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==} + engines: {node: '>=16'} + hasBin: true + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -3151,6 +3221,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3159,6 +3233,10 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + ora@9.3.0: + resolution: {integrity: sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==} + engines: {node: '>=20'} + p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -3175,6 +3253,10 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} + p-retry@7.1.1: + resolution: {integrity: sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==} + engines: {node: '>=20'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -3482,6 +3564,10 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -3580,6 +3666,9 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} @@ -3633,6 +3722,10 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stdin-discarder@0.3.1: + resolution: {integrity: sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==} + engines: {node: '>=18'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -3641,6 +3734,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@8.1.1: + resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==} + engines: {node: '>=20'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -3825,6 +3922,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@7.20.0: + resolution: {integrity: sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==} + engines: {node: '>=20.18.1'} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -4118,6 +4219,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + zip-stream@4.1.1: resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} engines: {node: '>= 10'} @@ -4151,6 +4256,12 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@ark/schema@0.56.0': + dependencies: + '@ark/util': 0.56.0 + + '@ark/util@0.56.0': {} + '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -4273,6 +4384,17 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@clack/core@0.5.0': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@clack/prompts@0.11.0': + dependencies: + '@clack/core': 0.5.0 + picocolors: 1.1.1 + sisteransi: 1.0.5 + '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -5644,6 +5766,16 @@ snapshots: dependencies: dequal: 2.0.3 + arkregex@0.0.5: + dependencies: + '@ark/util': 0.56.0 + + arktype@2.1.29: + dependencies: + '@ark/schema': 0.56.0 + '@ark/util': 0.56.0 + arkregex: 0.0.5 + assert-plus@1.0.0: optional: true @@ -5827,6 +5959,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -5859,14 +5993,34 @@ snapshots: dependencies: clsx: 2.1.1 + clawhub@0.5.0: + dependencies: + '@clack/prompts': 0.11.0 + arktype: 2.1.29 + commander: 14.0.3 + fflate: 0.8.2 + ignore: 7.0.5 + json5: 2.2.3 + mime: 4.1.0 + ora: 9.3.0 + p-retry: 7.1.1 + semver: 7.7.3 + undici: 7.20.0 + clean-stack@2.2.0: {} cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + cli-spinners@2.9.2: {} + cli-spinners@3.4.0: {} + cli-truncate@2.1.0: dependencies: slice-ansi: 3.0.0 @@ -5901,6 +6055,8 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@14.0.3: {} + commander@4.1.1: {} commander@5.1.0: {} @@ -6398,6 +6554,8 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fflate@0.8.2: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -6502,6 +6660,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.4.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6762,8 +6922,12 @@ snapshots: is-interactive@1.0.0: {} + is-interactive@2.0.0: {} + is-lambda@1.0.1: {} + is-network-error@1.3.0: {} + is-number@7.0.0: {} is-plain-obj@4.1.0: {} @@ -6772,6 +6936,8 @@ snapshots: is-unicode-supported@0.1.0: {} + is-unicode-supported@2.1.0: {} + isarray@1.0.0: {} isbinaryfile@4.0.10: {} @@ -6901,6 +7067,11 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.1.2 + longest-streak@3.1.0: {} loupe@3.2.1: {} @@ -7319,6 +7490,8 @@ snapshots: mime@2.6.0: {} + mime@4.1.0: {} + mimic-fn@2.1.0: {} mimic-function@5.0.1: {} @@ -7468,6 +7641,10 @@ snapshots: dependencies: mimic-fn: 2.1.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -7489,6 +7666,17 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 + ora@9.3.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.1 + string-width: 8.1.1 + p-cancelable@2.1.1: {} p-limit@3.1.0: @@ -7503,6 +7691,10 @@ snapshots: dependencies: aggregate-error: 3.1.0 + p-retry@7.1.1: + dependencies: + is-network-error: 1.3.0 + package-json-from-dist@1.0.1: {} parent-module@1.0.1: @@ -7807,6 +7999,11 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + retry@0.12.0: {} reusify@1.1.0: {} @@ -7914,6 +8111,8 @@ snapshots: dependencies: semver: 7.7.3 + sisteransi@1.0.5: {} + slice-ansi@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -7965,6 +8164,8 @@ snapshots: std-env@3.10.0: {} + stdin-discarder@0.3.1: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -7977,6 +8178,11 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string-width@8.1.1: + dependencies: + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -8178,6 +8384,8 @@ snapshots: undici-types@6.21.0: {} + undici@7.20.0: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -8436,6 +8644,8 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors@2.1.2: {} + zip-stream@4.1.1: dependencies: archiver-utils: 3.0.4 diff --git a/resources/icons/icon.icns b/resources/icons/icon.icns new file mode 100644 index 0000000000000000000000000000000000000000..c8a510024e91a0a85477b5da26825e9952abc249 GIT binary patch literal 231268 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 =FMWkbS^RziwV$vyfE_w-?oqiuL~);)tL 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 z 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 F{{detve^It literal 0 HcmV?d00001 diff --git a/scripts/download-bundled-uv.mjs b/scripts/download-bundled-uv.mjs new file mode 100644 index 000000000..d14bc7be0 --- /dev/null +++ b/scripts/download-bundled-uv.mjs @@ -0,0 +1,139 @@ +import { spawnSync } from 'node:child_process'; +import { mkdirSync, rmSync, existsSync, chmodSync, renameSync, writeFileSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { platform, arch } from 'node:os'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT_DIR = join(__dirname, '..'); + +// Configuration +const UV_VERSION = '0.10.0'; +const BASE_URL = `https://github.com/astral-sh/uv/releases/download/${UV_VERSION}`; +const OUTPUT_BASE = join(ROOT_DIR, 'resources', 'bin'); + +// Mapping Node platforms/archs to uv release naming +const TARGETS = { + 'darwin-arm64': { + filename: 'uv-aarch64-apple-darwin.tar.gz', + binName: 'uv', + extractCmd: (src, dest) => spawnSync('tar', ['-xzf', src, '-C', dest]) + }, + 'darwin-x64': { + filename: 'uv-x86_64-apple-darwin.tar.gz', + binName: 'uv', + extractCmd: (src, dest) => spawnSync('tar', ['-xzf', src, '-C', dest]) + }, + 'win32-x64': { + filename: 'uv-x86_64-pc-windows-msvc.zip', + binName: 'uv.exe', + extractCmd: (src, dest) => { + if (platform() === 'win32') { + return spawnSync('powershell.exe', ['-Command', `Expand-Archive -Path "${src}" -DestinationPath "${dest}" -Force`]); + } else { + return spawnSync('unzip', ['-q', '-o', src, '-d', dest]); + } + } + } +}; + +async function downloadFile(url, dest) { + console.log(`⬇️ Downloading: ${url}`); + const response = await fetch(url); + if (!response.ok) throw new Error(`Failed to download: ${response.statusText}`); + const arrayBuffer = await response.arrayBuffer(); + writeFileSync(dest, Buffer.from(arrayBuffer)); +} + +async function setupTarget(id) { + const target = TARGETS[id]; + if (!target) { + console.warn(`⚠️ Target ${id} is not supported by this script.`); + return; + } + + const targetDir = join(OUTPUT_BASE, id); + const tempDir = join(ROOT_DIR, 'temp_uv_extract'); + const archivePath = join(ROOT_DIR, target.filename); + + console.log(` +📦 Setting up uv for ${id}...`); + + // Cleanup & Prep + if (existsSync(targetDir)) rmSync(targetDir, { recursive: true }); + if (existsSync(tempDir)) rmSync(tempDir, { recursive: true }); + mkdirSync(targetDir, { recursive: true }); + mkdirSync(tempDir, { recursive: true }); + + try { + // Download + await downloadFile(`${BASE_URL}/${target.filename}`, archivePath); + + // Extract + console.log('📂 Extracting...'); + target.extractCmd(archivePath, tempDir); + + // Move binary to final location + // uv archives usually contain a folder named after the target + const folderName = target.filename.replace('.tar.gz', '').replace('.zip', ''); + const sourceBin = join(tempDir, folderName, target.binName); + const destBin = join(targetDir, target.binName); + + if (existsSync(sourceBin)) { + renameSync(sourceBin, destBin); + } else { + // Fallback: search for the binary if folder structure changed + console.log('🔍 Binary not found in expected subfolder, searching...'); + const findResult = spawnSync(platform() === 'win32' ? 'where' : 'find', + platform() === 'win32' ? ['/R', tempDir, target.binName] : [tempDir, '-name', target.binName]); + + const foundPath = findResult.stdout.toString().trim().split('\n')[0]; + if (foundPath && existsSync(foundPath)) { + renameSync(foundPath, destBin); + } else { + throw new Error(`Could not find ${target.binName} in extracted files.`); + } + } + + // Permission fix + if (platform() !== 'win32') { + chmodSync(destBin, 0o755); + } + + console.log(`✅ Success: ${destBin}`); + } finally { + // Cleanup + if (existsSync(archivePath)) rmSync(archivePath); + if (existsSync(tempDir)) rmSync(tempDir, { recursive: true }); + } +} + +async function main() { + const args = process.argv.slice(2); + const downloadAll = args.includes('--all'); + + if (downloadAll) { + console.log('🌐 Downloading uv binaries for ALL supported platforms...'); + for (const id of Object.keys(TARGETS)) { + await setupTarget(id); + } + } else { + const currentId = `${platform()}-${arch()}`; + console.log(`💻 Detected system: ${currentId}`); + + if (TARGETS[currentId]) { + await setupTarget(currentId); + } else { + console.error(`❌ Current system ${currentId} is not in the supported download list.`); + console.log('Supported targets:', Object.keys(TARGETS).join(', ')); + process.exit(1); + } + } + + console.log('\n🎉 Done!'); +} + +main().catch(err => { + console.error('\n❌ Error:', err.message); + process.exit(1); +}); diff --git a/src/pages/Channels/index.tsx b/src/pages/Channels/index.tsx index 058ebc9e0..a5fb7b2ea 100644 --- a/src/pages/Channels/index.tsx +++ b/src/pages/Channels/index.tsx @@ -1,120 +1,100 @@ /** * Channels Page - * Manage messaging channel connections + * Manage messaging channel connections with configuration UI */ import { useState, useEffect } from 'react'; -import { - Plus, - Radio, - RefreshCw, - Settings, - Trash2, - Power, +import { + Plus, + Radio, + RefreshCw, + Trash2, + Power, PowerOff, QrCode, Loader2, X, ExternalLink, - Copy, + BookOpen, + Eye, + EyeOff, Check, + AlertCircle, + CheckCircle, + ShieldCheck, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; +import { Badge } from '@/components/ui/badge'; import { useChannelsStore } from '@/stores/channels'; import { useGatewayStore } from '@/stores/gateway'; import { StatusBadge, type Status } from '@/components/common/StatusBadge'; import { LoadingSpinner } from '@/components/common/LoadingSpinner'; -import { CHANNEL_ICONS, CHANNEL_NAMES, type ChannelType, type Channel } from '@/types/channel'; +import { + CHANNEL_ICONS, + CHANNEL_NAMES, + CHANNEL_META, + getPrimaryChannels, + getAllChannels, + type ChannelType, + type Channel, + type ChannelMeta, + type ChannelConfigField, +} from '@/types/channel'; import { toast } from 'sonner'; -// Channel type info with connection instructions -const channelInfo: Record = { - whatsapp: { - description: 'Connect WhatsApp by scanning a QR code', - connectionType: 'qr', - instructions: [ - 'Open WhatsApp on your phone', - 'Go to Settings > Linked Devices', - 'Tap "Link a Device"', - 'Scan the QR code below', - ], - docsUrl: 'https://faq.whatsapp.com/1317564962315842', - }, - telegram: { - description: 'Connect Telegram using a bot token', - connectionType: 'token', - instructions: [ - 'Open Telegram and search for @BotFather', - 'Send /newbot and follow the instructions', - 'Copy the bot token provided', - 'Paste it below', - ], - tokenLabel: 'Bot Token', - docsUrl: 'https://core.telegram.org/bots#how-do-i-create-a-bot', - }, - discord: { - description: 'Connect Discord using a bot token', - connectionType: 'token', - instructions: [ - 'Go to Discord Developer Portal', - 'Create a new Application', - 'Go to Bot section and create a bot', - 'Copy the bot token', - ], - tokenLabel: 'Bot Token', - docsUrl: 'https://discord.com/developers/applications', - }, - slack: { - description: 'Connect Slack via OAuth', - connectionType: 'token', - instructions: [ - 'Go to Slack API apps page', - 'Create a new app', - 'Configure OAuth scopes', - 'Install to workspace and copy the token', - ], - tokenLabel: 'Bot Token (xoxb-...)', - docsUrl: 'https://api.slack.com/apps', - }, - wechat: { - description: 'Connect WeChat by scanning a QR code', - connectionType: 'qr', - instructions: [ - 'Open WeChat on your phone', - 'Scan the QR code below', - 'Confirm login on your phone', - ], - }, -}; - export function Channels() { - const { channels, loading, error, fetchChannels, connectChannel, disconnectChannel, deleteChannel } = useChannelsStore(); + const { channels, loading, error, fetchChannels, deleteChannel } = useChannelsStore(); const gatewayStatus = useGatewayStore((state) => state.status); - + const [showAddDialog, setShowAddDialog] = useState(false); const [selectedChannelType, setSelectedChannelType] = useState(null); - const [connectingChannelId, setConnectingChannelId] = useState(null); - + const [showAllChannels, setShowAllChannels] = useState(false); + const [configuredTypes, setConfiguredTypes] = useState([]); + // Fetch channels on mount useEffect(() => { fetchChannels(); }, [fetchChannels]); - - // Supported channel types for adding - const supportedTypes: ChannelType[] = ['whatsapp', 'telegram', 'discord', 'slack']; - + + useEffect(() => { + const unsubscribe = window.electron.ipcRenderer.on('gateway:channel-status', () => { + fetchChannels(); + }); + return () => { + if (typeof unsubscribe === 'function') { + unsubscribe(); + } + }; + }, [fetchChannels]); + + // Fetch configured channel types from config file + const fetchConfiguredTypes = async () => { + try { + const result = await window.electron.ipcRenderer.invoke('channel:listConfigured') as { + success: boolean; + channels?: string[]; + }; + if (result.success && result.channels) { + setConfiguredTypes(result.channels); + } + } catch { + // ignore + } + }; + + useEffect(() => { + fetchConfiguredTypes(); + }, []); + + // Get channel types to display + const displayedChannelTypes = showAllChannels ? getAllChannels() : getPrimaryChannels(); + // Connected/disconnected channel counts const connectedCount = channels.filter((c) => c.status === 'connected').length; - + if (loading) { return (

@@ -122,7 +102,7 @@ export function Channels() {
); } - + return (
{/* Header */} @@ -144,7 +124,7 @@ export function Channels() {
- + {/* Stats */}
@@ -187,19 +167,19 @@ export function Channels() {
- + {/* Gateway Warning */} {gatewayStatus.state !== 'running' && ( -
+ Gateway is not running. Channels cannot connect without an active Gateway. )} - + {/* Error Display */} {error && ( @@ -208,72 +188,87 @@ export function Channels() { )} - - {/* Channels Grid */} - {channels.length === 0 ? ( + + {/* Configured Channels */} + {channels.length > 0 && ( - - -

No channels configured

-

- Connect a messaging channel to start using ClawX -

- + + Configured Channels + Channels you have set up + + +
+ {channels.map((channel) => ( + { + if (confirm('Are you sure you want to delete this channel?')) { + deleteChannel(channel.id); + } + }} + /> + ))} +
- ) : ( -
- {channels.map((channel) => ( - { - setConnectingChannelId(channel.id); - connectChannel(channel.id); - }} - onDisconnect={() => disconnectChannel(channel.id)} - onDelete={() => { - if (confirm('Are you sure you want to delete this channel?')) { - deleteChannel(channel.id); - } - }} - isConnecting={connectingChannelId === channel.id} - /> - ))} -
)} - - {/* Add Channel Section */} + + {/* Available Channels */} - Supported Channels - - Click on a channel type to add it - +
+
+ Available Channels + + Click on a channel type to configure it + +
+ +
- {supportedTypes.map((type) => ( - - ))} + {displayedChannelTypes.map((type) => { + const meta = CHANNEL_META[type]; + const isConfigured = configuredTypes.includes(type); + return ( + + ); + })}
- + {/* Add Channel Dialog */} {showAddDialog && ( { + fetchChannels(); + fetchConfiguredTypes(); + setShowAddDialog(false); + setSelectedChannelType(null); + }} /> )}
@@ -294,24 +294,21 @@ export function Channels() { interface ChannelCardProps { channel: Channel; - onConnect: () => void; - onDisconnect: () => void; onDelete: () => void; - isConnecting: boolean; } -function ChannelCard({ channel, onConnect, onDisconnect, onDelete, isConnecting }: ChannelCardProps) { +function ChannelCard({ channel, onDelete }: ChannelCardProps) { return ( - +
- + {CHANNEL_ICONS[channel.type]}
- {channel.name} - + {channel.name} + {CHANNEL_NAMES[channel.type]}
@@ -319,50 +316,18 @@ function ChannelCard({ channel, onConnect, onDisconnect, onDelete, isConnecting
- - {channel.lastActivity && ( -

- Last activity: {new Date(channel.lastActivity).toLocaleString()} -

- )} + {channel.error && ( -

{channel.error}

+

{channel.error}

)}
- {channel.status === 'connected' ? ( - - ) : ( - - )} - -
@@ -376,64 +341,259 @@ interface AddChannelDialogProps { selectedType: ChannelType | null; onSelectType: (type: ChannelType | null) => void; onClose: () => void; - supportedTypes: ChannelType[]; + onChannelAdded: () => void; } -function AddChannelDialog({ selectedType, onSelectType, onClose, supportedTypes }: AddChannelDialogProps) { +function AddChannelDialog({ selectedType, onSelectType, onClose, onChannelAdded }: AddChannelDialogProps) { const { addChannel } = useChannelsStore(); + const [configValues, setConfigValues] = useState>({}); const [channelName, setChannelName] = useState(''); - const [token, setToken] = useState(''); const [connecting, setConnecting] = useState(false); + const [showSecrets, setShowSecrets] = useState>({}); const [qrCode, setQrCode] = useState(null); - const [copied, setCopied] = useState(false); - - const info = selectedType ? channelInfo[selectedType] : null; - - const handleConnect = async () => { + const [validating, setValidating] = useState(false); + const [loadingConfig, setLoadingConfig] = useState(false); + const [isExistingConfig, setIsExistingConfig] = useState(false); + const [validationResult, setValidationResult] = useState<{ + valid: boolean; + errors: string[]; + warnings: string[]; + } | null>(null); + + const meta: ChannelMeta | null = selectedType ? CHANNEL_META[selectedType] : null; + + // Load existing config when a channel type is selected + useEffect(() => { + if (!selectedType) { + setConfigValues({}); + setChannelName(''); + setIsExistingConfig(false); + return; + } + + let cancelled = false; + setLoadingConfig(true); + + (async () => { + try { + const result = await window.electron.ipcRenderer.invoke( + 'channel:getFormValues', + selectedType + ) as { success: boolean; values?: Record }; + + if (cancelled) return; + + if (result.success && result.values && Object.keys(result.values).length > 0) { + setConfigValues(result.values); + setIsExistingConfig(true); + } else { + setConfigValues({}); + setIsExistingConfig(false); + } + } catch { + if (!cancelled) { + setConfigValues({}); + setIsExistingConfig(false); + } + } finally { + if (!cancelled) setLoadingConfig(false); + } + })(); + + return () => { cancelled = true; }; + }, [selectedType]); + + const handleValidate = async () => { if (!selectedType) return; - - setConnecting(true); - + + setValidating(true); + setValidationResult(null); + try { - // For QR-based channels, we'd request a QR code from the gateway - if (info?.connectionType === 'qr') { - // Simulate QR code generation + const result = await window.electron.ipcRenderer.invoke( + 'channel:validateCredentials', + selectedType, + configValues + ) as { + success: boolean; + valid?: boolean; + errors?: string[]; + warnings?: string[]; + details?: Record; + }; + + const warnings = result.warnings || []; + if (result.valid && result.details) { + const details = result.details; + if (details.botUsername) warnings.push(`Bot: @${details.botUsername}`); + if (details.guildName) warnings.push(`Server: ${details.guildName}`); + if (details.channelName) warnings.push(`Channel: #${details.channelName}`); + } + + setValidationResult({ + valid: result.valid || false, + errors: result.errors || [], + warnings, + }); + } catch (error) { + setValidationResult({ + valid: false, + errors: [String(error)], + warnings: [], + }); + } finally { + setValidating(false); + } + }; + + + const handleConnect = async () => { + if (!selectedType || !meta) return; + + setConnecting(true); + setValidationResult(null); + + try { + // For QR-based channels, request QR code + if (meta.connectionType === 'qr') { + // Simulate QR code generation (in real implementation, call Gateway) await new Promise((resolve) => setTimeout(resolve, 1500)); setQrCode('placeholder-qr'); - } else { - // For token-based, add the channel with the token - await addChannel({ - type: selectedType, - name: channelName || CHANNEL_NAMES[selectedType], - token: token || undefined, - }); - - toast.success(`${CHANNEL_NAMES[selectedType]} channel added`); - onClose(); + setConnecting(false); + return; } + + // Step 1: Validate credentials against the actual service API + if (meta.connectionType === 'token') { + const validationResponse = await window.electron.ipcRenderer.invoke( + 'channel:validateCredentials', + selectedType, + configValues + ) as { + success: boolean; + valid?: boolean; + errors?: string[]; + warnings?: string[]; + details?: Record; + }; + + if (!validationResponse.valid) { + setValidationResult({ + valid: false, + errors: validationResponse.errors || ['Validation failed'], + warnings: validationResponse.warnings || [], + }); + setConnecting(false); + return; + } + + // Show success details (bot name, guild name, etc.) as warnings/info + const warnings = validationResponse.warnings || []; + if (validationResponse.details) { + const details = validationResponse.details; + if (details.botUsername) { + warnings.push(`Bot: @${details.botUsername}`); + } + if (details.guildName) { + warnings.push(`Server: ${details.guildName}`); + } + if (details.channelName) { + warnings.push(`Channel: #${details.channelName}`); + } + } + + // Show validation success with details + setValidationResult({ + valid: true, + errors: [], + warnings, + }); + } + + // Step 2: Save channel configuration via IPC + const config: Record = { ...configValues }; + await window.electron.ipcRenderer.invoke('channel:saveConfig', selectedType, config); + + // Step 3: Add a local channel entry for the UI + await addChannel({ + type: selectedType, + name: channelName || CHANNEL_NAMES[selectedType], + token: configValues[meta.configFields[0]?.key] || undefined, + }); + + toast.success(`${meta.name} channel saved. Restarting Gateway to connect...`); + + // Step 4: Restart the Gateway so it picks up the new channel config + // The Gateway watches the config file, but a restart ensures a clean start + // especially when adding a channel for the first time. + try { + await window.electron.ipcRenderer.invoke('gateway:restart'); + toast.success(`${meta.name} channel is now connecting via Gateway`); + } catch (restartError) { + console.warn('Gateway restart after channel config:', restartError); + toast.info('Config saved. Please restart the Gateway manually for the channel to connect.'); + } + + // Brief delay so user can see the success state before dialog closes + await new Promise((resolve) => setTimeout(resolve, 800)); + onChannelAdded(); } catch (error) { - toast.error(`Failed to add channel: ${error}`); - } finally { + toast.error(`Failed to configure channel: ${error}`); setConnecting(false); } }; - - const copyToken = () => { - navigator.clipboard.writeText(token); - setCopied(true); - setTimeout(() => setCopied(false), 2000); + + const openDocs = () => { + if (meta?.docsUrl) { + try { + if (window.electron?.openExternal) { + window.electron.openExternal(meta.docsUrl); + } else { + // Fallback: open in new window + window.open(meta.docsUrl, '_blank'); + } + } catch (error) { + console.error('Failed to open docs:', error); + // Fallback: open in new window + window.open(meta.docsUrl, '_blank'); + } + } }; - + + + const isFormValid = () => { + if (!meta) return false; + + // Check all required fields are filled + return meta.configFields + .filter((field) => field.required) + .every((field) => configValues[field.key]?.trim()); + }; + + const updateConfigValue = (key: string, value: string) => { + setConfigValues((prev) => ({ ...prev, [key]: value })); + }; + + const toggleSecretVisibility = (key: string) => { + setShowSecrets((prev) => ({ ...prev, [key]: !prev[key] })); + }; + return (
- +
- {selectedType ? `Connect ${CHANNEL_NAMES[selectedType]}` : 'Add Channel'} + {selectedType + ? isExistingConfig + ? `Update ${CHANNEL_NAMES[selectedType]}` + : `Configure ${CHANNEL_NAMES[selectedType]}` + : 'Add Channel'} - {info?.description || 'Select a messaging channel to connect'} + {selectedType && isExistingConfig + ? 'Existing configuration loaded. You can update and re-save.' + : meta?.description || 'Select a messaging channel to connect'}
- ))} + {getPrimaryChannels().map((type) => { + const channelMeta = CHANNEL_META[type]; + return ( + + ); + })}
) : qrCode ? ( // QR Code display @@ -467,7 +630,7 @@ function AddChannelDialog({ selectedType, onSelectType, onClose, supportedTypes

- Scan this QR code with {CHANNEL_NAMES[selectedType]} to connect + Scan this QR code with {meta?.name} to connect

+ ) : loadingConfig ? ( + // Loading saved config +
+ + Loading configuration... +
) : ( // Connection form
+ {/* Existing config hint */} + {isExistingConfig && ( +
+ + Previously saved configuration has been loaded. Modify if needed and save. +
+ )} + {/* Instructions */} -
-

How to connect:

-
    - {info?.instructions.map((instruction, i) => ( -
  1. {instruction}
  2. - ))} -
- {info?.docsUrl && ( +
+
+

How to connect:

- )} +
+
    + {meta?.instructions.map((instruction, i) => ( +
  1. {instruction}
  2. + ))} +
- + {/* Channel name */}
setChannelName(e.target.value)} />
- - {/* Token input for token-based channels */} - {info?.connectionType === 'token' && ( -
- -
- setToken(e.target.value)} - /> - {token && ( - + + {/* Configuration fields */} + {meta?.configFields.map((field) => ( + updateConfigValue(field.key, value)} + showSecret={showSecrets[field.key] || false} + onToggleSecret={() => toggleSecretVisibility(field.key)} + /> + ))} + + {/* Validation Results */} + {validationResult && ( +
+
+ {validationResult.valid ? ( + + ) : ( + )} +
+

+ {validationResult.valid ? 'Credentials Verified' : 'Validation Failed'} +

+ {validationResult.errors.length > 0 && ( +
    + {validationResult.errors.map((err, i) => ( +
  • {err}
  • + ))} +
+ )} + {validationResult.valid && validationResult.warnings.length > 0 && ( +
+ {validationResult.warnings.map((info, i) => ( +

{info}

+ ))} +
+ )} + {!validationResult.valid && validationResult.warnings.length > 0 && ( +
+

Warnings:

+
    + {validationResult.warnings.map((warn, i) => ( +
  • {warn}
  • + ))} +
+
+ )} +
)} - + - +
- )} - + +
)} @@ -566,4 +803,57 @@ function AddChannelDialog({ selectedType, onSelectType, onClose, supportedTypes ); } +// ==================== Config Field Component ==================== + +interface ConfigFieldProps { + field: ChannelConfigField; + value: string; + onChange: (value: string) => void; + showSecret: boolean; + onToggleSecret: () => void; +} + +function ConfigField({ field, value, onChange, showSecret, onToggleSecret }: ConfigFieldProps) { + const isPassword = field.type === 'password'; + + return ( +
+ +
+ onChange(e.target.value)} + className="font-mono text-sm" + /> + {isPassword && ( + + )} +
+ {field.description && ( +

+ {field.description} +

+ )} + {field.envVar && ( +

+ Or set via environment variable: {field.envVar} +

+ )} +
+ ); +} + export default Channels; diff --git a/src/pages/Setup/index.tsx b/src/pages/Setup/index.tsx index 90d8e6dd0..db7edc70a 100644 --- a/src/pages/Setup/index.tsx +++ b/src/pages/Setup/index.tsx @@ -16,6 +16,8 @@ import { RefreshCw, CheckCircle2, XCircle, + ExternalLink, + BookOpen, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -24,6 +26,13 @@ import { cn } from '@/lib/utils'; import { useGatewayStore } from '@/stores/gateway'; import { useSettingsStore } from '@/stores/settings'; import { toast } from 'sonner'; +import { + CHANNEL_META, + getPrimaryChannels, + type ChannelType, + type ChannelMeta, + type ChannelConfigField, +} from '@/types/channel'; interface SetupStep { id: string; @@ -31,6 +40,15 @@ interface SetupStep { description: string; } +const STEP = { + WELCOME: 0, + RUNTIME: 1, + PROVIDER: 2, + CHANNEL: 3, + INSTALLING: 4, + COMPLETE: 5, +} as const; + const steps: SetupStep[] = [ { id: 'welcome', @@ -47,7 +65,11 @@ const steps: SetupStep[] = [ title: 'AI Provider', description: 'Configure your AI service', }, - // Skills selection removed - auto-install essential components + { + id: 'channel', + title: 'Connect a Channel', + description: 'Connect a messaging platform (optional)', + }, { id: 'installing', title: 'Setting Up', @@ -143,19 +165,23 @@ export function Setup() { // Update canProceed based on current step useEffect(() => { switch (currentStep) { - case 0: // Welcome + case STEP.WELCOME: setCanProceed(true); break; - case 1: // Runtime + case STEP.RUNTIME: // Will be managed by RuntimeContent break; - case 2: // Provider + case STEP.PROVIDER: setCanProceed(selectedProvider !== null && apiKey.length > 0); break; - case 3: // Installing + case STEP.CHANNEL: + // Always allow proceeding — channel step is optional + setCanProceed(true); + break; + case STEP.INSTALLING: setCanProceed(false); // Cannot manually proceed, auto-proceeds when done break; - case 4: // Complete + case STEP.COMPLETE: setCanProceed(true); break; } @@ -213,9 +239,9 @@ export function Setup() { {/* Step-specific content */}
- {currentStep === 0 && } - {currentStep === 1 && } - {currentStep === 2 && ( + {currentStep === STEP.WELCOME && } + {currentStep === STEP.RUNTIME && } + {currentStep === STEP.PROVIDER && ( )} - {currentStep === 3 && ( + {currentStep === STEP.CHANNEL && } + {currentStep === STEP.INSTALLING && ( setCurrentStep((i) => i + 1)} /> )} - {currentStep === 4 && ( + {currentStep === STEP.COMPLETE && ( {/* Navigation - hidden during installation step */} - {currentStep !== 3 && ( + {currentStep !== STEP.INSTALLING && (
{!isFirstStep && ( @@ -250,7 +278,12 @@ export function Setup() { )}
- {!isLastStep && currentStep !== 1 && ( + {currentStep === STEP.CHANNEL && ( + + )} + {!isLastStep && currentStep !== STEP.RUNTIME && currentStep !== STEP.CHANNEL && ( @@ -531,6 +564,45 @@ function ProviderContent({ const [showKey, setShowKey] = useState(false); const [validating, setValidating] = useState(false); const [keyValid, setKeyValid] = useState(null); + useEffect(() => { + let cancelled = false; + (async () => { + try { + const list = await window.electron.ipcRenderer.invoke('provider:list') as Array<{ id: string; hasKey: boolean }>; + const defaultId = await window.electron.ipcRenderer.invoke('provider:getDefault') as string | null; + const preferred = (defaultId && list.find((p) => p.id === defaultId && p.hasKey)) || list.find((p) => p.hasKey); + if (preferred && !cancelled) { + onSelectProvider(preferred.id); + const storedKey = await window.electron.ipcRenderer.invoke('provider:getApiKey', preferred.id) as string | null; + if (storedKey) { + onApiKeyChange(storedKey); + } + } + } catch (error) { + if (!cancelled) { + console.error('Failed to load provider list:', error); + } + } + })(); + return () => { cancelled = true; }; + }, [onApiKeyChange, onSelectProvider]); + useEffect(() => { + let cancelled = false; + (async () => { + if (!selectedProvider) return; + try { + const storedKey = await window.electron.ipcRenderer.invoke('provider:getApiKey', selectedProvider) as string | null; + if (!cancelled && storedKey) { + onApiKeyChange(storedKey); + } + } catch (error) { + if (!cancelled) { + console.error('Failed to load provider key:', error); + } + } + })(); + return () => { cancelled = true; }; + }, [onApiKeyChange, selectedProvider]); const selectedProviderData = providers.find((p) => p.id === selectedProvider); @@ -628,6 +700,7 @@ function ProviderContent({ onApiKeyChange(e.target.value); setKeyValid(null); }} + autoComplete="off" className="pr-10 bg-white/5 border-white/10" /> +
+ ); + } + + // Channel type not selected — show picker + if (!selectedChannel) { + return ( +
+
+
📡
+

Connect a Messaging Channel

+

+ Choose a platform to connect your AI assistant to. You can add more channels later in Settings. +

+
+
+ {primaryChannels.map((type) => { + const channelMeta = CHANNEL_META[type]; + if (channelMeta.connectionType !== 'token') return null; + return ( + + ); + })} +
+
+ ); + } + + // Channel selected — show config form + return ( +
+
+ +
+

+ {meta?.icon} Configure {meta?.name} +

+

{meta?.description}

+
+
+ + {/* Instructions */} +
+
+

How to connect:

+ {meta?.docsUrl && ( + + )} +
+
    + {meta?.instructions.map((inst, i) => ( +
  1. {inst}
  2. + ))} +
+
+ + {/* Config fields */} + {meta?.configFields.map((field: ChannelConfigField) => { + const isPassword = field.type === 'password'; + return ( +
+ +
+ setConfigValues((prev) => ({ ...prev, [field.key]: e.target.value }))} + autoComplete="off" + className="font-mono text-sm bg-white/5 border-white/10" + /> + {isPassword && ( + + )} +
+ {field.description && ( +

{field.description}

+ )} +
+ ); + })} + + {/* Validation error */} + {validationError && ( +
+ + {validationError} +
+ )} + + {/* Save button */} + +
+ ); +} + // NOTE: SkillsContent component removed - auto-install essential skills // Installation status for each skill @@ -682,55 +1005,55 @@ interface SkillInstallState { interface InstallingContentProps { skills: DefaultSkill[]; onComplete: (installedSkills: string[]) => void; + onSkip: () => void; } -function InstallingContent({ skills, onComplete }: InstallingContentProps) { +function InstallingContent({ skills, onComplete, onSkip }: InstallingContentProps) { const [skillStates, setSkillStates] = useState( skills.map((s) => ({ ...s, status: 'pending' as InstallStatus })) ); const [overallProgress, setOverallProgress] = useState(0); + const [errorMessage, setErrorMessage] = useState(null); const installStarted = useRef(false); - // Simulate installation process + // Real installation process useEffect(() => { if (installStarted.current) return; installStarted.current = true; - const installSkills = async () => { - const installedIds: string[] = []; - - for (let i = 0; i < skills.length; i++) { - // Set current skill to installing - setSkillStates((prev) => - prev.map((s, idx) => - idx === i ? { ...s, status: 'installing' } : s - ) - ); - - // Simulate installation time (1-2 seconds per skill) - const installTime = 1000 + Math.random() * 1000; - await new Promise((resolve) => setTimeout(resolve, installTime)); - - // Mark as completed - setSkillStates((prev) => - prev.map((s, idx) => - idx === i ? { ...s, status: 'completed' } : s - ) - ); - installedIds.push(skills[i].id); - - // Update overall progress - setOverallProgress(Math.round(((i + 1) / skills.length) * 100)); + const runRealInstall = async () => { + try { + // Step 1: Initialize all skills to 'installing' state for UI + setSkillStates(prev => prev.map(s => ({ ...s, status: 'installing' }))); + setOverallProgress(10); + + // Step 2: Call the backend to install uv and setup Python + const result = await window.electron.ipcRenderer.invoke('uv:install-all') as { + success: boolean; + error?: string + }; + + if (result.success) { + setSkillStates(prev => prev.map(s => ({ ...s, status: 'completed' }))); + setOverallProgress(100); + + await new Promise((resolve) => setTimeout(resolve, 800)); + onComplete(skills.map(s => s.id)); + } else { + setSkillStates(prev => prev.map(s => ({ ...s, status: 'failed' }))); + setErrorMessage(result.error || 'Unknown error during installation'); + toast.error('Environment setup failed'); + } + } catch (err) { + setSkillStates(prev => prev.map(s => ({ ...s, status: 'failed' }))); + setErrorMessage(String(err)); + toast.error('Installation error'); } - - // Small delay before completing - await new Promise((resolve) => setTimeout(resolve, 500)); - onComplete(installedIds); }; - installSkills(); + runRealInstall(); }, [skills, onComplete]); - + const getStatusIcon = (status: InstallStatus) => { switch (status) { case 'pending': @@ -784,7 +1107,7 @@ function InstallingContent({ skills, onComplete }: InstallingContentProps) {
{/* Skill list */} -
+
{skillStates.map((skill) => ( ))}
+ + {/* Error Message Display */} + {errorMessage && ( + +
+ +
+

Setup Error:

+
+                {errorMessage}
+              
+ +
+
+
+ )} -

- This may take a few moments... -

+ {!errorMessage && ( +

+ This may take a few moments... +

+ )} +
+ +
); } - interface CompleteContentProps { selectedProvider: string | null; installedSkills: string[]; diff --git a/src/pages/Skills/index.tsx b/src/pages/Skills/index.tsx index f3dd0cd9e..0caa9a278 100644 --- a/src/pages/Skills/index.tsx +++ b/src/pages/Skills/index.tsx @@ -3,13 +3,13 @@ * Browse and manage AI skills */ import { useEffect, useState, useCallback } from 'react'; -import { - Search, - Puzzle, - RefreshCw, - Lock, +import { motion, AnimatePresence } from 'framer-motion'; +import { + Search, + Puzzle, + RefreshCw, + Lock, Package, - Info, X, Settings, CheckCircle2, @@ -17,6 +17,14 @@ import { AlertCircle, ChevronRight, Sparkles, + Download, + Trash2, + Globe, + FileCode, + Plus, + Save, + Key, + ChevronDown, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; @@ -29,73 +37,10 @@ import { useGatewayStore } from '@/stores/gateway'; import { LoadingSpinner } from '@/components/common/LoadingSpinner'; import { cn } from '@/lib/utils'; import { toast } from 'sonner'; -import type { Skill, SkillCategory, SkillBundle } from '@/types/skill'; +import type { Skill, MarketplaceSkill } from '@/types/skill'; -const categoryLabels: Record = { - productivity: 'Productivity', - developer: 'Developer', - 'smart-home': 'Smart Home', - media: 'Media', - communication: 'Communication', - security: 'Security', - information: 'Information', - utility: 'Utility', - custom: 'Custom', -}; -const categoryIcons: Record = { - productivity: '📋', - developer: '💻', - 'smart-home': '🏠', - media: '🎬', - communication: '💬', - security: '🔒', - information: '📰', - utility: '🔧', - custom: '⚡', -}; -// Predefined skill bundles -const skillBundles: SkillBundle[] = [ - { - id: 'productivity', - name: 'Productivity Pack', - nameZh: '效率工具包', - description: 'Essential tools for daily productivity including calendar, reminders, and notes', - descriptionZh: '日常效率必备工具,包含日历、提醒和笔记', - icon: '📋', - skills: ['calendar', 'reminders', 'notes', 'tasks', 'timer'], - recommended: true, - }, - { - id: 'developer', - name: 'Developer Tools', - nameZh: '开发者工具', - description: 'Code assistance, git operations, and technical documentation lookup', - descriptionZh: '代码辅助、Git 操作和技术文档查询', - icon: '💻', - skills: ['code-assist', 'git-ops', 'docs-lookup', 'snippet-manager'], - recommended: true, - }, - { - id: 'information', - name: 'Information Hub', - nameZh: '信息中心', - description: 'Stay informed with web search, news, weather, and knowledge base', - descriptionZh: '通过网页搜索、新闻、天气和知识库保持信息畅通', - icon: '📰', - skills: ['web-search', 'news', 'weather', 'wikipedia', 'translate'], - }, - { - id: 'smart-home', - name: 'Smart Home', - nameZh: '智能家居', - description: 'Control your smart home devices and automation routines', - descriptionZh: '控制智能家居设备和自动化场景', - icon: '🏠', - skills: ['lights', 'thermostat', 'security-cam', 'routines'], - }, -]; // Skill detail dialog component interface SkillDetailDialogProps { @@ -105,10 +50,114 @@ interface SkillDetailDialogProps { } function SkillDetailDialog({ skill, onClose, onToggle }: SkillDetailDialogProps) { + const { fetchSkills } = useSkillsStore(); + const [activeTab, setActiveTab] = useState('info'); + const [envVars, setEnvVars] = useState>([]); + const [apiKey, setApiKey] = useState(''); + const [isEnvExpanded, setIsEnvExpanded] = useState(true); + const [isSaving, setIsSaving] = useState(false); + + // Initialize config from skill + useEffect(() => { + // API Key + if (skill.config?.apiKey) { + setApiKey(String(skill.config.apiKey)); + } else { + setApiKey(''); + } + + // Env Vars + if (skill.config?.env) { + const vars = Object.entries(skill.config.env).map(([key, value]) => ({ + key, + value: String(value), + })); + setEnvVars(vars); + } else { + setEnvVars([]); + } + }, [skill.config]); + + const handleOpenClawhub = async () => { + if (skill.slug) { + await window.electron.ipcRenderer.invoke('shell:openExternal', `https://clawhub.ai/s/${skill.slug}`); + } + }; + + const handleOpenEditor = async () => { + if (skill.slug) { + try { + const result = await window.electron.ipcRenderer.invoke('clawhub:openSkillReadme', skill.slug) as { success: boolean; error?: string }; + if (result.success) { + toast.success('Opened in editor'); + } else { + toast.error(result.error || 'Failed to open editor'); + } + } catch (err) { + toast.error('Failed to open editor: ' + String(err)); + } + } + }; + + const handleAddEnv = () => { + setEnvVars([...envVars, { key: '', value: '' }]); + }; + + const handleUpdateEnv = (index: number, field: 'key' | 'value', value: string) => { + const newVars = [...envVars]; + newVars[index] = { ...newVars[index], [field]: value }; + setEnvVars(newVars); + }; + + const handleRemoveEnv = (index: number) => { + const newVars = [...envVars]; + newVars.splice(index, 1); + setEnvVars(newVars); + }; + + const handleSaveConfig = async () => { + if (isSaving) return; + setIsSaving(true); + try { + // Build env object, filtering out empty keys + const envObj = envVars.reduce((acc, curr) => { + const key = curr.key.trim(); + const value = curr.value.trim(); + if (key) { + acc[key] = value; + } + return acc; + }, {} as Record); + + // Use direct file access instead of Gateway RPC for reliability + const result = await window.electron.ipcRenderer.invoke( + 'skill:updateConfig', + { + skillKey: skill.id, + apiKey: apiKey || '', // Empty string will delete the key + env: envObj // Empty object will clear all env vars + } + ) as { success: boolean; error?: string }; + + if (!result.success) { + throw new Error(result.error || 'Unknown error'); + } + + // Refresh skills from gateway to get updated config + await fetchSkills(); + + toast.success('Configuration saved'); + } catch (err) { + toast.error('Failed to save configuration: ' + String(err)); + } finally { + setIsSaving(false); + } + }; + return (
- e.stopPropagation()}> - + e.stopPropagation()}> +
{skill.icon || '🔧'}
@@ -116,43 +165,174 @@ function SkillDetailDialog({ skill, onClose, onToggle }: SkillDetailDialogProps) {skill.name} {skill.isCore && } - {categoryLabels[skill.category]} +
+ {skill.slug && !skill.isBundled && !skill.isCore && ( + <> + + + + )} +
- -

{skill.description}

- -
- {skill.version && ( - v{skill.version} - )} - {skill.author && ( - by {skill.author} - )} - {skill.isCore && ( - - - Core Skill - - )} + + +
+ + Information + Configuration +
- - {skill.dependencies && skill.dependencies.length > 0 && ( -
-

Dependencies:

-
- {skill.dependencies.map((dep) => ( - {dep} - ))} -
+ +
+
+ +
+
+

Description

+

{skill.description}

+
+ +
+
+

Version

+

{skill.version}

+
+ {skill.author && ( +
+

Author

+

{skill.author}

+
+ )} +
+ +
+

Source

+ + {skill.isCore ? 'Core System' : skill.isBundled ? 'Bundled' : 'User Installed'} + +
+
+
+ + +
+ {/* API Key Section */} +
+

+ + API Key +

+ setApiKey(e.target.value)} + type="password" + className="font-mono text-sm" + /> +

+ The primary API key for this skill. Leave blank if not required or configured elsewhere. +

+
+ + {/* Environment Variables Section */} +
+
+ + + +
+ + {isEnvExpanded && ( +
+ {envVars.length === 0 && ( +

+ No environment variables configured. +

+ )} + + {envVars.map((env, index) => ( +
+ handleUpdateEnv(index, 'key', e.target.value)} + className="flex-1 font-mono text-xs bg-muted/20" + placeholder="KEY (e.g. BASE_URL)" + /> + = + handleUpdateEnv(index, 'value', e.target.value)} + className="flex-1 font-mono text-xs bg-muted/20" + placeholder="VALUE" + /> + +
+ ))} + + {envVars.length > 0 && ( +

+ Note: Rows with empty keys will be automatically removed during save. +

+ )} +
+ )} +
+
+ +
+ +
+
- )} - -
+
+ +
{skill.enabled ? ( <> @@ -167,12 +347,6 @@ function SkillDetailDialog({ skill, onClose, onToggle }: SkillDetailDialogProps) )}
- {skill.configurable && ( - - )} onToggle(!skill.enabled)} @@ -180,115 +354,242 @@ function SkillDetailDialog({ skill, onClose, onToggle }: SkillDetailDialogProps) />
- +
); } -// Bundle card component -interface BundleCardProps { - bundle: SkillBundle; - skills: Skill[]; - onApply: () => void; +// Marketplace skill card component +interface MarketplaceSkillCardProps { + skill: MarketplaceSkill; + isInstalling: boolean; + isInstalled: boolean; + onInstall: () => void; + onUninstall: () => void; } -function BundleCard({ bundle, skills, onApply }: BundleCardProps) { - const bundleSkills = skills.filter((s) => bundle.skills.includes(s.id)); - const enabledCount = bundleSkills.filter((s) => s.enabled).length; - const isFullyEnabled = bundleSkills.length > 0 && enabledCount === bundleSkills.length; - +function MarketplaceSkillCard({ + skill, + isInstalling, + isInstalled, + onInstall, + onUninstall +}: MarketplaceSkillCardProps) { + const handleCardClick = () => { + window.electron.ipcRenderer.invoke('shell:openExternal', `https://clawhub.ai/s/${skill.slug}`); + }; + return ( - - + +
- {bundle.icon} +
+ 📦 +
- - {bundle.name} - {bundle.recommended && ( - - - Recommended - + {skill.name} + + v{skill.version} + {skill.author && ( + <> + + {skill.author} + )} - - - {enabledCount}/{bundleSkills.length} skills enabled
+
e.stopPropagation()}> + + {isInstalled ? ( + + + + ) : ( + + + + )} + +
- -

- {bundle.description} + +

+ {skill.description}

-
- {bundleSkills.slice(0, 4).map((skill) => ( - - {skill.icon} {skill.name} - - ))} - {bundleSkills.length > 4 && ( - - +{bundleSkills.length - 4} more - +
+ {skill.downloads !== undefined && ( +
+ + {skill.downloads.toLocaleString()} +
+ )} + {skill.stars !== undefined && ( +
+ + {skill.stars.toLocaleString()} +
)}
- ); } export function Skills() { - const { skills, loading, error, fetchSkills, enableSkill, disableSkill } = useSkillsStore(); + const { + skills, + loading, + error, + fetchSkills, + enableSkill, + disableSkill, + searchResults, + searchSkills, + installSkill, + uninstallSkill, + searching, + installing + } = useSkillsStore(); const gatewayStatus = useGatewayStore((state) => state.status); const [searchQuery, setSearchQuery] = useState(''); - const [selectedCategory, setSelectedCategory] = useState('all'); + const [marketplaceQuery, setMarketplaceQuery] = useState(''); const [selectedSkill, setSelectedSkill] = useState(null); const [activeTab, setActiveTab] = useState('all'); - + const [selectedSource, setSelectedSource] = useState<'all' | 'built-in' | 'marketplace'>('all'); + const isGatewayRunning = gatewayStatus.state === 'running'; - + const [showGatewayWarning, setShowGatewayWarning] = useState(false); + + // Debounce the gateway warning to avoid flickering during brief restarts (like skill toggles) + useEffect(() => { + let timer: NodeJS.Timeout; + if (!isGatewayRunning) { + // Wait 1.5s before showing the warning + timer = setTimeout(() => { + setShowGatewayWarning(true); + }, 1500); + } else { + setShowGatewayWarning(false); + } + return () => clearTimeout(timer); + }, [isGatewayRunning]); + // Fetch skills on mount useEffect(() => { if (isGatewayRunning) { fetchSkills(); } }, [fetchSkills, isGatewayRunning]); - + // Filter skills const filteredSkills = skills.filter((skill) => { const matchesSearch = skill.name.toLowerCase().includes(searchQuery.toLowerCase()) || skill.description.toLowerCase().includes(searchQuery.toLowerCase()); - const matchesCategory = selectedCategory === 'all' || skill.category === selectedCategory; - return matchesSearch && matchesCategory; + + let matchesSource = true; + if (selectedSource === 'built-in') { + matchesSource = !!skill.isBundled; + } else if (selectedSource === 'marketplace') { + matchesSource = !skill.isBundled; + } + + return matchesSearch && matchesSource; + }).sort((a, b) => { + // Enabled skills first + if (a.enabled && !b.enabled) return -1; + if (!a.enabled && b.enabled) return 1; + // Then core/bundled + if (a.isCore && !b.isCore) return -1; + if (!a.isCore && b.isCore) return 1; + // Finally alphabetical + return a.name.localeCompare(b.name); }); - - // Get unique categories with counts - const categoryStats = skills.reduce((acc, skill) => { - acc[skill.category] = (acc[skill.category] || 0) + 1; - return acc; - }, {} as Record); - + + const sourceStats = { + all: skills.length, + builtIn: skills.filter(s => s.isBundled).length, + marketplace: skills.filter(s => !s.isBundled).length, + }; + // Handle toggle const handleToggle = useCallback(async (skillId: string, enable: boolean) => { try { @@ -303,30 +604,45 @@ export function Skills() { toast.error(String(err)); } }, [enableSkill, disableSkill]); - - // Handle bundle apply - const handleBundleApply = useCallback(async (bundle: SkillBundle) => { - const bundleSkills = skills.filter((s) => bundle.skills.includes(s.id)); - const allEnabled = bundleSkills.every((s) => s.enabled); - - try { - for (const skill of bundleSkills) { - if (allEnabled) { - if (!skill.isCore) { - await disableSkill(skill.id); - } - } else { - if (!skill.enabled) { - await enableSkill(skill.id); - } - } - } - toast.success(allEnabled ? 'Bundle disabled' : 'Bundle enabled'); - } catch { - toast.error('Failed to apply bundle'); + + // Handle marketplace search + const handleMarketplaceSearch = useCallback((e: React.FormEvent) => { + e.preventDefault(); + if (marketplaceQuery.trim()) { + searchSkills(marketplaceQuery); } - }, [skills, enableSkill, disableSkill]); - + }, [marketplaceQuery, searchSkills]); + + // Handle install + const handleInstall = useCallback(async (slug: string) => { + try { + await installSkill(slug); + // Automatically enable after install + // We need to find the skill id which is usually the slug + await enableSkill(slug); + toast.success('Skill installed and enabled'); + } catch (err) { + toast.error(`Failed to install: ${String(err)}`); + } + }, [installSkill, enableSkill]); + + // Initial marketplace load (Discovery) + useEffect(() => { + if (activeTab === 'marketplace' && searchResults.length === 0 && !searching) { + searchSkills(''); + } + }, [activeTab, searchResults.length, searching, searchSkills]); + + // Handle uninstall + const handleUninstall = useCallback(async (slug: string) => { + try { + await uninstallSkill(slug); + toast.success('Skill uninstalled successfully'); + } catch (err) { + toast.error(`Failed to uninstall: ${String(err)}`); + } + }, [uninstallSkill]); + if (loading) { return (
@@ -334,7 +650,7 @@ export function Skills() {
); } - + return (
{/* Header */} @@ -350,9 +666,9 @@ export function Skills() { Refresh
- + {/* Gateway Warning */} - {!isGatewayRunning && ( + {showGatewayWarning && ( @@ -362,20 +678,24 @@ export function Skills() { )} - + {/* Tabs */} - All Skills + Installed - + + + Marketplace + + {/* Bundles - + */} - + {/* Search and Filter */}
@@ -388,29 +708,36 @@ export function Skills() { className="pl-9" />
-
+ +
+ + - {Object.entries(categoryStats).map(([category, count]) => ( - - ))}
- + {/* Error Display */} {error && ( @@ -420,7 +747,7 @@ export function Skills() { )} - + {/* Skills Grid */} {filteredSkills.length === 0 ? ( @@ -435,8 +762,8 @@ export function Skills() { ) : (
{filteredSkills.map((skill) => ( -
- {skill.icon || categoryIcons[skill.category]} + {skill.icon || '🧩'}
{skill.name} - {skill.isCore && ( + {skill.isCore ? ( + ) : skill.isBundled ? ( + + ) : ( + )} - - {categoryLabels[skill.category]} -
- { - handleToggle(skill.id, checked); - }} - disabled={skill.isCore} - onClick={(e) => e.stopPropagation()} - /> +
+ {!skill.isBundled && !skill.isCore && ( + + )} + { + handleToggle(skill.id, checked); + }} + disabled={skill.isCore} + onClick={(e) => e.stopPropagation()} + /> +
@@ -492,13 +838,103 @@ export function Skills() {
)}
- - + + +
+
+
+
+ + setMarketplaceQuery(e.target.value)} + className="pl-9" + /> +
+ +
+
+ + {searchResults.length > 0 ? ( +
+ {searchResults.map((skill) => { + const isInstalled = skills.some(s => s.id === skill.slug || s.name === skill.name); // Simple check, ideally check by ID/slug + return ( + handleInstall(skill.slug)} + onUninstall={() => handleUninstall(skill.slug)} + /> + ); + })} +
+ ) : ( + + + +

Marketplace

+

+ {searching + ? 'Searching ClawHub...' + : marketplaceQuery + ? 'No skills found matching your search.' + : 'Search for new skills to expand your capabilities.'} +

+
+
+ )} +
+
+ + {/*

- Skill bundles are pre-configured collections of skills for common use cases. + Skill bundles are pre-configured collections of skills for common use cases. Enable a bundle to quickly set up multiple related skills at once.

- +
{skillBundles.map((bundle) => ( ))}
-
+
*/}
- - {/* Statistics */} - - -
-
- - - {skills.filter((s) => s.enabled).length} - - {' '}of {skills.length} skills enabled - - - - {skills.filter((s) => s.isCore).length} - - {' '}core skills - -
- -
-
-
- + + + {/* Skill Detail Dialog */} {selectedSkill && ( ((set, get) => ({ error: null, fetchChannels: async () => { - // channels.status returns a complex nested object, not a simple array. - // Channel management is deferred to Settings > Channels page. - // For now, just use empty list - channels will be added later. - set({ channels: [], loading: false }); + set({ loading: true, error: null }); + try { + const result = await window.electron.ipcRenderer.invoke( + 'gateway:rpc', + 'channels.status', + { probe: true } + ) as { + success: boolean; + result?: { + channelOrder?: string[]; + channels?: Record; + channelAccounts?: Record>; + channelDefaultAccountId?: Record; + }; + error?: string; + }; + + if (result.success && result.result) { + const data = result.result; + const channels: Channel[] = []; + + // Parse the complex channels.status response into simple Channel objects + const channelOrder = data.channelOrder || Object.keys(data.channels || {}); + for (const channelId of channelOrder) { + const summary = (data.channels as Record | undefined)?.[channelId] as Record | undefined; + const configured = + typeof summary?.configured === 'boolean' + ? summary.configured + : typeof (summary as { running?: boolean })?.running === 'boolean' + ? true + : false; + if (!configured) continue; + + const accounts = data.channelAccounts?.[channelId] || []; + const defaultAccountId = data.channelDefaultAccountId?.[channelId]; + const primaryAccount = + (defaultAccountId ? accounts.find((a) => a.accountId === defaultAccountId) : undefined) || + accounts.find((a) => a.connected === true || a.linked === true) || + accounts[0]; + + // Map gateway status to our status format + let status: Channel['status'] = 'disconnected'; + const now = Date.now(); + const RECENT_MS = 10 * 60 * 1000; + const hasRecentActivity = (a: { lastInboundAt?: number | null; lastOutboundAt?: number | null; lastConnectedAt?: number | null }) => + (typeof a.lastInboundAt === 'number' && now - a.lastInboundAt < RECENT_MS) || + (typeof a.lastOutboundAt === 'number' && now - a.lastOutboundAt < RECENT_MS) || + (typeof a.lastConnectedAt === 'number' && now - a.lastConnectedAt < RECENT_MS); + const anyConnected = accounts.some((a) => a.connected === true || a.linked === true || hasRecentActivity(a)); + const anyRunning = accounts.some((a) => a.running === true); + const summaryError = + typeof (summary as { error?: string })?.error === 'string' + ? (summary as { error?: string }).error + : typeof (summary as { lastError?: string })?.lastError === 'string' + ? (summary as { lastError?: string }).lastError + : undefined; + const anyError = + accounts.some((a) => typeof a.lastError === 'string' && a.lastError) || Boolean(summaryError); + + if (anyConnected) { + status = 'connected'; + } else if (anyRunning && !anyError) { + status = 'connected'; + } else if (anyError) { + status = 'error'; + } else if (anyRunning) { + status = 'connecting'; + } + + channels.push({ + id: `${channelId}-${primaryAccount?.accountId || 'default'}`, + type: channelId as ChannelType, + name: primaryAccount?.name || channelId, + status, + accountId: primaryAccount?.accountId, + error: + (typeof primaryAccount?.lastError === 'string' ? primaryAccount.lastError : undefined) || + (typeof summaryError === 'string' ? summaryError : undefined), + }); + } + + set({ channels, loading: false }); + } else { + // Gateway not available - try to show channels from local config + set({ channels: [], loading: false }); + } + } catch { + // Gateway not connected, show empty + set({ channels: [], loading: false }); + } }, addChannel: async (params) => { diff --git a/src/stores/chat.ts b/src/stores/chat.ts index 1bf62046f..05b05a9a4 100644 --- a/src/stores/chat.ts +++ b/src/stores/chat.ts @@ -242,17 +242,31 @@ export const useChatStore = create((set, get) => ({ // Message complete - add to history and clear streaming const finalMsg = event.message as RawMessage | undefined; if (finalMsg) { - set((s) => ({ - messages: [...s.messages, { - ...finalMsg, - role: finalMsg.role || 'assistant', - id: finalMsg.id || `run-${runId}`, - }], - streamingText: '', - streamingMessage: null, - sending: false, - activeRunId: null, - })); + const msgId = finalMsg.id || `run-${runId}`; + set((s) => { + // Check if message already exists (prevent duplicates) + const alreadyExists = s.messages.some(m => m.id === msgId); + if (alreadyExists) { + // Just clear streaming state, don't add duplicate + return { + streamingText: '', + streamingMessage: null, + sending: false, + activeRunId: null, + }; + } + return { + messages: [...s.messages, { + ...finalMsg, + role: finalMsg.role || 'assistant', + id: msgId, + }], + streamingText: '', + streamingMessage: null, + sending: false, + activeRunId: null, + }; + }); } else { // No message in final event - reload history to get complete data set({ streamingText: '', streamingMessage: null, sending: false, activeRunId: null }); diff --git a/src/stores/gateway.ts b/src/stores/gateway.ts index f46370fb1..f6e6547e1 100644 --- a/src/stores/gateway.ts +++ b/src/stores/gateway.ts @@ -16,7 +16,7 @@ interface GatewayState { health: GatewayHealth | null; isInitialized: boolean; lastError: string | null; - + // Actions init: () => Promise; start: () => Promise; @@ -36,37 +36,37 @@ export const useGatewayStore = create((set, get) => ({ health: null, isInitialized: false, lastError: null, - + init: async () => { if (get().isInitialized) return; - + try { // Get initial status const status = await window.electron.ipcRenderer.invoke('gateway:status') as GatewayStatus; set({ status, isInitialized: true }); - + // Listen for status changes window.electron.ipcRenderer.on('gateway:status-changed', (newStatus) => { set({ status: newStatus as GatewayStatus }); }); - + // Listen for errors window.electron.ipcRenderer.on('gateway:error', (error) => { set({ lastError: String(error) }); }); - + // Listen for notifications window.electron.ipcRenderer.on('gateway:notification', (notification) => { console.log('Gateway notification:', notification); }); - + // Listen for chat events from the gateway and forward to chat store window.electron.ipcRenderer.on('gateway:chat-message', (data) => { try { // Dynamic import to avoid circular dependency import('./chat').then(({ useChatStore }) => { const chatData = data as { message?: Record } | Record; - const event = ('message' in chatData && typeof chatData.message === 'object') + const event = ('message' in chatData && typeof chatData.message === 'object') ? chatData.message as Record : chatData as Record; useChatStore.getState().handleChatEvent(event); @@ -75,32 +75,32 @@ export const useGatewayStore = create((set, get) => ({ console.warn('Failed to forward chat event:', err); } }); - + } catch (error) { console.error('Failed to initialize Gateway:', error); set({ lastError: String(error) }); } }, - + start: async () => { try { set({ status: { ...get().status, state: 'starting' }, lastError: null }); const result = await window.electron.ipcRenderer.invoke('gateway:start') as { success: boolean; error?: string }; - + if (!result.success) { - set({ + set({ status: { ...get().status, state: 'error', error: result.error }, lastError: result.error || 'Failed to start Gateway' }); } } catch (error) { - set({ + set({ status: { ...get().status, state: 'error', error: String(error) }, lastError: String(error) }); } }, - + stop: async () => { try { await window.electron.ipcRenderer.invoke('gateway:stop'); @@ -110,41 +110,41 @@ export const useGatewayStore = create((set, get) => ({ set({ lastError: String(error) }); } }, - + restart: async () => { try { set({ status: { ...get().status, state: 'starting' }, lastError: null }); const result = await window.electron.ipcRenderer.invoke('gateway:restart') as { success: boolean; error?: string }; - + if (!result.success) { - set({ + set({ status: { ...get().status, state: 'error', error: result.error }, lastError: result.error || 'Failed to restart Gateway' }); } } catch (error) { - set({ + set({ status: { ...get().status, state: 'error', error: String(error) }, lastError: String(error) }); } }, - + checkHealth: async () => { try { - const result = await window.electron.ipcRenderer.invoke('gateway:health') as { - success: boolean; - ok: boolean; - error?: string; - uptime?: number + const result = await window.electron.ipcRenderer.invoke('gateway:health') as { + success: boolean; + ok: boolean; + error?: string; + uptime?: number }; - + const health: GatewayHealth = { ok: result.ok, error: result.error, uptime: result.uptime, }; - + set({ health }); return health; } catch (error) { @@ -153,22 +153,22 @@ export const useGatewayStore = create((set, get) => ({ return health; } }, - + rpc: async (method: string, params?: unknown, timeoutMs?: number): Promise => { const result = await window.electron.ipcRenderer.invoke('gateway:rpc', method, params, timeoutMs) as { success: boolean; result?: T; error?: string; }; - + if (!result.success) { throw new Error(result.error || `RPC call failed: ${method}`); } - + return result.result as T; }, - + setStatus: (status) => set({ status }), - + clearError: () => set({ lastError: null }), })); diff --git a/src/stores/skills.ts b/src/stores/skills.ts index 2aea6d2b1..042fa4681 100644 --- a/src/stores/skills.ts +++ b/src/stores/skills.ts @@ -3,15 +3,50 @@ * Manages skill/plugin state */ import { create } from 'zustand'; -import type { Skill } from '../types/skill'; +import type { Skill, MarketplaceSkill } from '../types/skill'; + +type GatewaySkillStatus = { + skillKey: string; + slug?: string; + name?: string; + description?: string; + disabled?: boolean; + emoji?: string; + version?: string; + author?: string; + config?: Record; + bundled?: boolean; + always?: boolean; +}; + +type GatewaySkillsStatusResult = { + skills?: GatewaySkillStatus[]; +}; + +type GatewayRpcResponse = { + success: boolean; + result?: T; + error?: string; +}; + +type ClawHubListResult = { + slug: string; + version?: string; +}; interface SkillsState { skills: Skill[]; + searchResults: MarketplaceSkill[]; loading: boolean; + searching: boolean; + installing: Record; // slug -> boolean error: string | null; - + // Actions fetchSkills: () => Promise; + searchSkills: (query: string) => Promise; + installSkill: (slug: string, version?: string) => Promise; + uninstallSkill: (slug: string) => Promise; enableSkill: (skillId: string) => Promise; disableSkill: (skillId: string) => Promise; setSkills: (skills: Skill[]) => void; @@ -20,26 +55,163 @@ interface SkillsState { export const useSkillsStore = create((set, get) => ({ skills: [], + searchResults: [], loading: false, + searching: false, + installing: {}, error: null, - + fetchSkills: async () => { - // skills.status returns a complex nested object, not a simple Skill[] array. - // Skill management is handled in the Skills page. - // For now, use empty list - will be properly integrated later. - set({ skills: [], loading: false }); + // Only show loading state if we have no skills yet (initial load) + if (get().skills.length === 0) { + set({ loading: true, error: null }); + } + try { + // 1. Fetch from Gateway (running skills) + const gatewayResult = await window.electron.ipcRenderer.invoke( + 'gateway:rpc', + 'skills.status' + ) as GatewayRpcResponse; + + // 2. Fetch from ClawHub (installed on disk) + const clawhubResult = await window.electron.ipcRenderer.invoke( + 'clawhub:list' + ) as { success: boolean; results?: ClawHubListResult[]; error?: string }; + + // 3. Fetch configurations directly from Electron (since Gateway doesn't return them) + const configResult = await window.electron.ipcRenderer.invoke( + 'skill:getAllConfigs' + ) as Record }>; + + let combinedSkills: Skill[] = []; + const currentSkills = get().skills; + + // Map gateway skills info + if (gatewayResult.success && gatewayResult.result?.skills) { + combinedSkills = gatewayResult.result.skills.map((s: GatewaySkillStatus) => { + // Merge with direct config if available + const directConfig = configResult[s.skillKey] || {}; + + return { + id: s.skillKey, + slug: s.slug || s.skillKey, + name: s.name || s.skillKey, + description: s.description || '', + enabled: !s.disabled, + icon: s.emoji || '📦', + version: s.version || '1.0.0', + author: s.author, + config: { + ...(s.config || {}), + ...directConfig, + }, + isCore: s.bundled && s.always, + isBundled: s.bundled, + }; + }); + } else if (currentSkills.length > 0) { + // ... if gateway down ... + combinedSkills = [...currentSkills]; + } + + // Merge with ClawHub results + if (clawhubResult.success && clawhubResult.results) { + clawhubResult.results.forEach((cs: ClawHubListResult) => { + const existing = combinedSkills.find(s => s.id === cs.slug); + if (!existing) { + const directConfig = configResult[cs.slug] || {}; + combinedSkills.push({ + id: cs.slug, + slug: cs.slug, + name: cs.slug, + description: 'Recently installed, initializing...', + enabled: false, + icon: '⌛', + version: cs.version || 'unknown', + author: undefined, + config: directConfig, + isCore: false, + isBundled: false, + }); + } + }); + } + + set({ skills: combinedSkills, loading: false }); + } catch (error) { + console.error('Failed to fetch skills:', error); + set({ loading: false }); + } }, - + + searchSkills: async (query: string) => { + set({ searching: true, error: null }); + try { + const result = await window.electron.ipcRenderer.invoke('clawhub:search', { query }) as { success: boolean; results?: MarketplaceSkill[]; error?: string }; + if (result.success) { + set({ searchResults: result.results || [] }); + } else { + throw new Error(result.error || 'Search failed'); + } + } catch (error) { + set({ error: String(error) }); + } finally { + set({ searching: false }); + } + }, + + installSkill: async (slug: string, version?: string) => { + set((state) => ({ installing: { ...state.installing, [slug]: true } })); + try { + const result = await window.electron.ipcRenderer.invoke('clawhub:install', { slug, version }) as { success: boolean; error?: string }; + if (!result.success) { + throw new Error(result.error || 'Install failed'); + } + // Refresh skills after install + await get().fetchSkills(); + } catch (error) { + console.error('Install error:', error); + throw error; + } finally { + set((state) => { + const newInstalling = { ...state.installing }; + delete newInstalling[slug]; + return { installing: newInstalling }; + }); + } + }, + + uninstallSkill: async (slug: string) => { + set((state) => ({ installing: { ...state.installing, [slug]: true } })); + try { + const result = await window.electron.ipcRenderer.invoke('clawhub:uninstall', { slug }) as { success: boolean; error?: string }; + if (!result.success) { + throw new Error(result.error || 'Uninstall failed'); + } + // Refresh skills after uninstall + await get().fetchSkills(); + } catch (error) { + console.error('Uninstall error:', error); + throw error; + } finally { + set((state) => { + const newInstalling = { ...state.installing }; + delete newInstalling[slug]; + return { installing: newInstalling }; + }); + } + }, + enableSkill: async (skillId) => { const { updateSkill } = get(); - + try { const result = await window.electron.ipcRenderer.invoke( 'gateway:rpc', - 'skills.enable', - { skillId } - ) as { success: boolean; error?: string }; - + 'skills.update', + { skillKey: skillId, enabled: true } + ) as GatewayRpcResponse; + if (result.success) { updateSkill(skillId, { enabled: true }); } else { @@ -50,23 +222,22 @@ export const useSkillsStore = create((set, get) => ({ throw error; } }, - + disableSkill: async (skillId) => { const { updateSkill, skills } = get(); - - // Check if skill is a core skill + const skill = skills.find((s) => s.id === skillId); if (skill?.isCore) { throw new Error('Cannot disable core skill'); } - + try { const result = await window.electron.ipcRenderer.invoke( 'gateway:rpc', - 'skills.disable', - { skillId } - ) as { success: boolean; error?: string }; - + 'skills.update', + { skillKey: skillId, enabled: false } + ) as GatewayRpcResponse; + if (result.success) { updateSkill(skillId, { enabled: false }); } else { @@ -77,9 +248,9 @@ export const useSkillsStore = create((set, get) => ({ throw error; } }, - + setSkills: (skills) => set({ skills }), - + updateSkill: (skillId, updates) => { set((state) => ({ skills: state.skills.map((skill) => diff --git a/src/types/channel.ts b/src/types/channel.ts index 5bfde9758..433e3b40a 100644 --- a/src/types/channel.ts +++ b/src/types/channel.ts @@ -6,13 +6,30 @@ /** * Supported channel types */ -export type ChannelType = 'whatsapp' | 'telegram' | 'discord' | 'slack' | 'wechat'; +export type ChannelType = + | 'whatsapp' + | 'telegram' + | 'discord' + | 'slack' + | 'signal' + | 'feishu' + | 'imessage' + | 'matrix' + | 'line' + | 'msteams' + | 'googlechat' + | 'mattermost'; /** * Channel connection status */ export type ChannelStatus = 'connected' | 'disconnected' | 'connecting' | 'error'; +/** + * Channel connection type + */ +export type ChannelConnectionType = 'token' | 'qr' | 'oauth' | 'webhook'; + /** * Channel data structure */ @@ -21,6 +38,7 @@ export interface Channel { type: ChannelType; name: string; status: ChannelStatus; + accountId?: string; lastActivity?: string; error?: string; avatar?: string; @@ -28,27 +46,32 @@ export interface Channel { } /** - * Channel configuration for each type + * Channel configuration field definition */ -export interface ChannelConfig { - whatsapp: { - phoneNumber?: string; - }; - telegram: { - botToken?: string; - chatId?: string; - }; - discord: { - botToken?: string; - guildId?: string; - }; - slack: { - botToken?: string; - appToken?: string; - }; - wechat: { - appId?: string; - }; +export interface ChannelConfigField { + key: string; + label: string; + type: 'text' | 'password' | 'select'; + placeholder?: string; + required?: boolean; + envVar?: string; + description?: string; + options?: { value: string; label: string }[]; +} + +/** + * Channel metadata with configuration info + */ +export interface ChannelMeta { + id: ChannelType; + name: string; + icon: string; + description: string; + connectionType: ChannelConnectionType; + docsUrl: string; + configFields: ChannelConfigField[]; + instructions: string[]; + isPlugin?: boolean; } /** @@ -59,7 +82,14 @@ export const CHANNEL_ICONS: Record = { telegram: '✈️', discord: '🎮', slack: '💼', - wechat: '💬', + signal: '🔒', + feishu: '🐦', + imessage: '💬', + matrix: '🔗', + line: '🟢', + msteams: '👔', + googlechat: '💭', + mattermost: '💠', }; /** @@ -70,5 +100,377 @@ export const CHANNEL_NAMES: Record = { telegram: 'Telegram', discord: 'Discord', slack: 'Slack', - wechat: 'WeChat', + signal: 'Signal', + feishu: 'Feishu / Lark', + imessage: 'iMessage', + matrix: 'Matrix', + line: 'LINE', + msteams: 'Microsoft Teams', + googlechat: 'Google Chat', + mattermost: 'Mattermost', }; + +/** + * Channel metadata with configuration information + */ +export const CHANNEL_META: Record = { + telegram: { + id: 'telegram', + name: 'Telegram', + icon: '✈️', + description: 'Connect Telegram using a bot token from @BotFather', + connectionType: 'token', + docsUrl: 'https://docs.openclaw.ai/channels/telegram', + configFields: [ + { + key: 'botToken', + label: 'Bot Token', + type: 'password', + placeholder: '123456:ABC-DEF...', + required: true, + envVar: 'TELEGRAM_BOT_TOKEN', + }, + ], + instructions: [ + 'Open Telegram and search for @BotFather', + 'Send /newbot and follow the instructions', + 'Copy the bot token provided', + 'Paste the token below', + ], + }, + discord: { + id: 'discord', + name: 'Discord', + icon: '🎮', + description: 'Connect Discord using a bot token from Developer Portal', + connectionType: 'token', + docsUrl: 'https://docs.openclaw.ai/channels/discord', + configFields: [ + { + key: 'token', + label: 'Bot Token', + type: 'password', + placeholder: 'Your Discord bot token', + required: true, + envVar: 'DISCORD_BOT_TOKEN', + }, + { + key: 'guildId', + label: 'Guild/Server ID (optional)', + type: 'text', + placeholder: 'e.g., 123456789012345678', + required: false, + description: 'Limit bot to a specific server. Right-click server → Copy Server ID.', + }, + { + key: 'channelId', + label: 'Channel ID (optional)', + type: 'text', + placeholder: 'e.g., 123456789012345678', + required: false, + description: 'Limit bot to a specific channel. Right-click channel → Copy Channel ID.', + }, + ], + instructions: [ + 'Go to Discord Developer Portal → Applications → New Application', + 'In Bot section: Add Bot, then copy the Bot Token', + 'Enable Message Content Intent + Server Members Intent in Bot → Privileged Gateway Intents', + 'In OAuth2 → URL Generator: select "bot" + "applications.commands", add message permissions', + 'Invite the bot to your server using the generated URL', + 'Paste the bot token below', + ], + }, + slack: { + id: 'slack', + name: 'Slack', + icon: '💼', + description: 'Connect Slack using bot and app tokens', + connectionType: 'token', + docsUrl: 'https://docs.openclaw.ai/channels/slack', + configFields: [ + { + key: 'botToken', + label: 'Bot Token (xoxb-...)', + type: 'password', + placeholder: 'xoxb-...', + required: true, + envVar: 'SLACK_BOT_TOKEN', + }, + { + key: 'appToken', + label: 'App Token (xapp-...)', + type: 'password', + placeholder: 'xapp-...', + required: false, + envVar: 'SLACK_APP_TOKEN', + }, + ], + instructions: [ + 'Go to api.slack.com/apps', + 'Create a new app from scratch', + 'Add required OAuth scopes', + 'Install to workspace and copy tokens', + ], + }, + whatsapp: { + id: 'whatsapp', + name: 'WhatsApp', + icon: '📱', + description: 'Connect WhatsApp by scanning a QR code', + connectionType: 'qr', + docsUrl: 'https://docs.openclaw.ai/channels/whatsapp', + configFields: [], + instructions: [ + 'Open WhatsApp on your phone', + 'Go to Settings > Linked Devices', + 'Tap "Link a Device"', + 'Scan the QR code shown below', + ], + }, + signal: { + id: 'signal', + name: 'Signal', + icon: '🔒', + description: 'Connect Signal using signal-cli', + connectionType: 'token', + docsUrl: 'https://docs.openclaw.ai/channels/signal', + configFields: [ + { + key: 'phoneNumber', + label: 'Phone Number', + type: 'text', + placeholder: '+1234567890', + required: true, + }, + ], + instructions: [ + 'Install signal-cli on your system', + 'Register or link your phone number', + 'Enter your phone number below', + ], + }, + feishu: { + id: 'feishu', + name: 'Feishu / Lark', + icon: '🐦', + description: 'Connect Feishu/Lark bot via WebSocket', + connectionType: 'token', + docsUrl: 'https://docs.openclaw.ai/channels/feishu', + configFields: [ + { + key: 'appId', + label: 'App ID', + type: 'text', + placeholder: 'cli_xxxxxx', + required: true, + envVar: 'FEISHU_APP_ID', + }, + { + key: 'appSecret', + label: 'App Secret', + type: 'password', + placeholder: 'Your app secret', + required: true, + envVar: 'FEISHU_APP_SECRET', + }, + ], + instructions: [ + 'Go to Feishu Open Platform', + 'Create a new application', + 'Get App ID and App Secret', + 'Configure event subscription', + ], + isPlugin: true, + }, + imessage: { + id: 'imessage', + name: 'iMessage', + icon: '💬', + description: 'Connect iMessage via BlueBubbles (macOS)', + connectionType: 'token', + docsUrl: 'https://docs.openclaw.ai/channels/bluebubbles', + configFields: [ + { + key: 'serverUrl', + label: 'BlueBubbles Server URL', + type: 'text', + placeholder: 'http://localhost:1234', + required: true, + }, + { + key: 'password', + label: 'Server Password', + type: 'password', + placeholder: 'Your server password', + required: true, + }, + ], + instructions: [ + 'Install BlueBubbles server on your Mac', + 'Note the server URL and password', + 'Enter the connection details below', + ], + }, + matrix: { + id: 'matrix', + name: 'Matrix', + icon: '🔗', + description: 'Connect to Matrix protocol', + connectionType: 'token', + docsUrl: 'https://docs.openclaw.ai/channels/matrix', + configFields: [ + { + key: 'homeserver', + label: 'Homeserver URL', + type: 'text', + placeholder: 'https://matrix.org', + required: true, + }, + { + key: 'accessToken', + label: 'Access Token', + type: 'password', + placeholder: 'Your access token', + required: true, + }, + ], + instructions: [ + 'Create a Matrix account or use existing', + 'Get an access token from your client', + 'Enter the homeserver and token below', + ], + isPlugin: true, + }, + line: { + id: 'line', + name: 'LINE', + icon: '🟢', + description: 'Connect LINE Messaging API', + connectionType: 'token', + docsUrl: 'https://docs.openclaw.ai/channels/line', + configFields: [ + { + key: 'channelAccessToken', + label: 'Channel Access Token', + type: 'password', + placeholder: 'Your LINE channel access token', + required: true, + envVar: 'LINE_CHANNEL_ACCESS_TOKEN', + }, + { + key: 'channelSecret', + label: 'Channel Secret', + type: 'password', + placeholder: 'Your LINE channel secret', + required: true, + envVar: 'LINE_CHANNEL_SECRET', + }, + ], + instructions: [ + 'Go to LINE Developers Console', + 'Create a Messaging API channel', + 'Get Channel Access Token and Secret', + ], + isPlugin: true, + }, + msteams: { + id: 'msteams', + name: 'Microsoft Teams', + icon: '👔', + description: 'Connect Microsoft Teams via Bot Framework', + connectionType: 'token', + docsUrl: 'https://docs.openclaw.ai/channels/msteams', + configFields: [ + { + key: 'appId', + label: 'App ID', + type: 'text', + placeholder: 'Your Microsoft App ID', + required: true, + envVar: 'MSTEAMS_APP_ID', + }, + { + key: 'appPassword', + label: 'App Password', + type: 'password', + placeholder: 'Your Microsoft App Password', + required: true, + envVar: 'MSTEAMS_APP_PASSWORD', + }, + ], + instructions: [ + 'Go to Azure Portal', + 'Register a new Bot application', + 'Get App ID and create a password', + 'Configure Teams channel', + ], + isPlugin: true, + }, + googlechat: { + id: 'googlechat', + name: 'Google Chat', + icon: '💭', + description: 'Connect Google Chat via webhook', + connectionType: 'webhook', + docsUrl: 'https://docs.openclaw.ai/channels/googlechat', + configFields: [ + { + key: 'serviceAccountKey', + label: 'Service Account JSON Path', + type: 'text', + placeholder: '/path/to/service-account.json', + required: true, + }, + ], + instructions: [ + 'Create a Google Cloud project', + 'Enable Google Chat API', + 'Create a service account', + 'Download the JSON key file', + ], + }, + mattermost: { + id: 'mattermost', + name: 'Mattermost', + icon: '💠', + description: 'Connect Mattermost via Bot API', + connectionType: 'token', + docsUrl: 'https://docs.openclaw.ai/channels/mattermost', + configFields: [ + { + key: 'serverUrl', + label: 'Server URL', + type: 'text', + placeholder: 'https://your-mattermost.com', + required: true, + }, + { + key: 'botToken', + label: 'Bot Access Token', + type: 'password', + placeholder: 'Your bot access token', + required: true, + }, + ], + instructions: [ + 'Go to Mattermost Integrations', + 'Create a new Bot Account', + 'Copy the access token', + ], + isPlugin: true, + }, +}; + +/** + * Get primary supported channels (non-plugin, commonly used) + */ +export function getPrimaryChannels(): ChannelType[] { + return ['telegram', 'discord', 'slack', 'whatsapp', 'feishu']; +} + +/** + * Get all available channels including plugins + */ +export function getAllChannels(): ChannelType[] { + return Object.keys(CHANNEL_META) as ChannelType[]; +} diff --git a/src/types/skill.ts b/src/types/skill.ts index f7c7efc1d..d1889e24a 100644 --- a/src/types/skill.ts +++ b/src/types/skill.ts @@ -3,34 +3,22 @@ * Types for skills/plugins */ -/** - * Skill category - */ -export type SkillCategory = - | 'productivity' - | 'developer' - | 'smart-home' - | 'media' - | 'communication' - | 'security' - | 'information' - | 'utility' - | 'custom'; - /** * Skill data structure */ export interface Skill { id: string; + slug?: string; name: string; description: string; enabled: boolean; - category: SkillCategory; icon?: string; version?: string; author?: string; configurable?: boolean; + config?: Record; isCore?: boolean; + isBundled?: boolean; dependencies?: string[]; } @@ -48,6 +36,20 @@ export interface SkillBundle { recommended?: boolean; } + +/** + * Marketplace skill data + */ +export interface MarketplaceSkill { + slug: string; + name: string; + description: string; + version: string; + author?: string; + downloads?: number; + stars?: number; +} + /** * Skill configuration schema */