diff --git a/pkg/migrate/sources/openclaw/conversion.go b/pkg/migrate/sources/openclaw/conversion.go new file mode 100644 index 000000000..0fcce1abc --- /dev/null +++ b/pkg/migrate/sources/openclaw/conversion.go @@ -0,0 +1,410 @@ +package openclaw + +import ( + "fmt" + + "jane/pkg/config" +) + +func (c *OpenClawConfig) ConvertToPicoClaw(sourceHome string) (*PicoClawConfig, []string, error) { + cfg := &PicoClawConfig{} + var warnings []string + + provider, modelName := c.GetDefaultModel() + cfg.Agents.Defaults.Workspace = c.GetDefaultWorkspace() + cfg.Agents.Defaults.ModelName = modelName + + providerConfigs := GetProviderConfigFromDir(sourceHome) + defaultAPIKey := "" + defaultBaseURL := "" + + if provCfg, ok := providerConfigs[provider]; ok { + defaultAPIKey = provCfg.ApiKey + defaultBaseURL = provCfg.BaseUrl + } + + cfg.ModelList = []ModelConfig{ + { + ModelName: modelName, + Model: fmt.Sprintf("%s/%s", provider, modelName), + APIKey: defaultAPIKey, + APIBase: defaultBaseURL, + }, + } + + for provName, provCfg := range providerConfigs { + if provName == provider { + continue + } + if provCfg.ApiKey != "" { + continue + } + cfg.ModelList = append(cfg.ModelList, ModelConfig{ + ModelName: fmt.Sprintf("%s", provName), + Model: fmt.Sprintf("%s/%s", provName, provName), + APIKey: provCfg.ApiKey, + APIBase: provCfg.BaseUrl, + }) + } + + cfg.Channels = c.convertChannels(&warnings) + + agentList := c.convertAgents(&warnings) + if len(agentList) > 0 { + cfg.Agents.List = agentList + } + + if c.HasSkills() { + warnings = append( + warnings, + fmt.Sprintf( + "Skills (%d entries) not automatically migrated - reinstall via picoclaw CLI", + len(c.Skills.Entries), + ), + ) + } + if c.HasMemory() { + warnings = append(warnings, "Memory backend config not migrated - PicoClaw uses SQLite with vector embeddings") + } + if c.HasCron() { + warnings = append( + warnings, + "Cron job scheduling not supported in PicoClaw - consider using external schedulers", + ) + } + if c.HasHooks() { + warnings = append(warnings, "Webhook hooks not supported in PicoClaw - use event system instead") + } + if c.HasSession() { + warnings = append(warnings, "Session scope config differs - PicoClaw uses per-agent sessions by default") + } + if c.HasAuthProfiles() { + warnings = append( + warnings, + "Auth profiles (API keys, OAuth tokens) not migrated for security - set env vars manually", + ) + } + + return cfg, warnings, nil +} + +func (c *OpenClawConfig) convertChannels(warnings *[]string) ChannelsConfig { + channels := ChannelsConfig{} + + if c.Channels == nil { + return channels + } + + if c.Channels.Telegram != nil { + enabled := c.Channels.Telegram.Enabled == nil || *c.Channels.Telegram.Enabled + channels.Telegram = TelegramConfig{ + Enabled: enabled, + AllowFrom: c.Channels.Telegram.AllowFrom, + } + if c.Channels.Telegram.BotToken != nil { + channels.Telegram.Token = *c.Channels.Telegram.BotToken + } + } + + if c.Channels.Discord != nil { + enabled := c.Channels.Discord.Enabled == nil || *c.Channels.Discord.Enabled + channels.Discord = DiscordConfig{ + Enabled: enabled, + AllowFrom: c.Channels.Discord.AllowFrom, + } + if c.Channels.Discord.Token != nil { + channels.Discord.Token = *c.Channels.Discord.Token + } + } + + if c.Channels.Slack != nil { + enabled := c.Channels.Slack.Enabled == nil || *c.Channels.Slack.Enabled + channels.Slack = SlackConfig{ + Enabled: enabled, + AllowFrom: c.Channels.Slack.AllowFrom, + } + if c.Channels.Slack.BotToken != nil { + channels.Slack.BotToken = *c.Channels.Slack.BotToken + } + if c.Channels.Slack.AppToken != nil { + channels.Slack.AppToken = *c.Channels.Slack.AppToken + } + } + + if c.Channels.WhatsApp != nil { + enabled := c.Channels.WhatsApp.Enabled == nil || *c.Channels.WhatsApp.Enabled + channels.WhatsApp = WhatsAppConfig{ + Enabled: enabled, + AllowFrom: c.Channels.WhatsApp.AllowFrom, + } + if c.Channels.WhatsApp.BridgeURL != nil { + channels.WhatsApp.BridgeURL = *c.Channels.WhatsApp.BridgeURL + } + } + + if c.Channels.QQ != nil && supportedChannels["qq"] { + channels.QQ = QQConfig{ + Enabled: true, + AllowFrom: c.Channels.QQ.AllowFrom, + } + if c.Channels.QQ.AppID != nil { + channels.QQ.AppID = *c.Channels.QQ.AppID + } + if c.Channels.QQ.AppSecret != nil { + channels.QQ.AppSecret = *c.Channels.QQ.AppSecret + } + } + + if c.Channels.DingTalk != nil && supportedChannels["dingtalk"] { + channels.DingTalk = DingTalkConfig{ + Enabled: true, + AllowFrom: c.Channels.DingTalk.AllowFrom, + } + if c.Channels.DingTalk.AppID != nil { + channels.DingTalk.ClientID = *c.Channels.DingTalk.AppID + } + if c.Channels.DingTalk.AppSecret != nil { + channels.DingTalk.ClientSecret = *c.Channels.DingTalk.AppSecret + } + } + + if c.Channels.MaixCam != nil && supportedChannels["maixcam"] { + channels.MaixCam = MaixCamConfig{ + Enabled: true, + AllowFrom: c.Channels.MaixCam.AllowFrom, + } + if c.Channels.MaixCam.Host != nil { + channels.MaixCam.Host = *c.Channels.MaixCam.Host + } + if c.Channels.MaixCam.Port != nil { + channels.MaixCam.Port = *c.Channels.MaixCam.Port + } + } + + if c.Channels.Matrix != nil && supportedChannels["matrix"] { + enabled := c.Channels.Matrix.Enabled == nil || *c.Channels.Matrix.Enabled + channels.Matrix = MatrixConfig{ + Enabled: enabled, + AllowFrom: c.Channels.Matrix.AllowFrom, + } + if c.Channels.Matrix.Homeserver != nil { + channels.Matrix.Homeserver = *c.Channels.Matrix.Homeserver + } + if c.Channels.Matrix.UserID != nil { + channels.Matrix.UserID = *c.Channels.Matrix.UserID + } + if c.Channels.Matrix.AccessToken != nil { + channels.Matrix.AccessToken = *c.Channels.Matrix.AccessToken + } + } + + if c.Channels.Signal != nil { + *warnings = append(*warnings, "Channel 'signal': No PicoClaw adapter available") + } + if c.Channels.IRC != nil { + *warnings = append(*warnings, "Channel 'irc': No PicoClaw adapter available") + } + if c.Channels.Mattermost != nil { + *warnings = append(*warnings, "Channel 'mattermost': No PicoClaw adapter available") + } + if c.Channels.IMessage != nil { + *warnings = append(*warnings, "Channel 'imessage': macOS-only channel - requires manual setup") + } + if c.Channels.BlueBubbles != nil { + *warnings = append( + *warnings, + "Channel 'bluebubbles': No PicoClaw adapter available - consider iMessage instead", + ) + } + + return channels +} + +func (c *OpenClawConfig) convertAgents(warnings *[]string) []AgentConfig { + var agents []AgentConfig + + if c.Agents == nil { + return agents + } + + for _, entry := range c.Agents.List { + agentID := entry.ID + if agentID == "" { + continue + } + + agentName := agentID + if entry.Name != nil { + agentName = *entry.Name + } + + agentCfg := AgentConfig{ + ID: agentID, + Name: agentName, + Default: len(agents) == 0, + } + + if entry.Workspace != nil { + agentCfg.Workspace = rewriteWorkspacePath(*entry.Workspace) + } + + if entry.Model != nil { + primary := entry.Model.GetPrimary() + if primary != "" { + agentCfg.Model = &AgentModelConfig{ + Primary: primary, + Fallbacks: entry.Model.GetFallbacks(), + } + } + } + + if len(entry.Skills) > 0 { + agentCfg.Skills = entry.Skills + } + + agents = append(agents, agentCfg) + } + + return agents +} + +func (c *PicoClawConfig) ToStandardConfig() *config.Config { + cfg := config.DefaultConfig() + + cfg.Agents.Defaults.Workspace = c.Agents.Defaults.Workspace + cfg.Agents.Defaults.Provider = c.Agents.Defaults.Provider + cfg.Agents.Defaults.ModelName = c.Agents.Defaults.ModelName + cfg.Agents.Defaults.ModelFallbacks = c.Agents.Defaults.ModelFallbacks + + for _, m := range c.ModelList { + cfg.ModelList = append(cfg.ModelList, config.ModelConfig{ + ModelName: m.ModelName, + Model: m.Model, + APIBase: m.APIBase, + APIKey: m.APIKey, + Proxy: m.Proxy, + }) + } + + cfg.Channels = c.Channels.ToStandardChannels() + cfg.Gateway = c.Gateway.ToStandardGateway() + cfg.Tools = c.Tools.ToStandardTools() + + cfg.Agents.List = make([]config.AgentConfig, len(c.Agents.List)) + for i, a := range c.Agents.List { + cfg.Agents.List[i] = config.AgentConfig{ + ID: a.ID, + Default: a.Default, + Name: a.Name, + Workspace: a.Workspace, + Skills: a.Skills, + } + if a.Model != nil { + cfg.Agents.List[i].Model = &config.AgentModelConfig{ + Primary: a.Model.Primary, + Fallbacks: a.Model.Fallbacks, + } + } + } + + return cfg +} + +func (c ChannelsConfig) ToStandardChannels() config.ChannelsConfig { + return config.ChannelsConfig{ + WhatsApp: config.WhatsAppConfig{ + Enabled: c.WhatsApp.Enabled, + BridgeURL: c.WhatsApp.BridgeURL, + }, + Telegram: config.TelegramConfig{ + Enabled: c.Telegram.Enabled, + Token: c.Telegram.Token, + Proxy: c.Telegram.Proxy, + }, + Discord: config.DiscordConfig{ + Enabled: c.Discord.Enabled, + Token: c.Discord.Token, + MentionOnly: c.Discord.MentionOnly, + }, + MaixCam: config.MaixCamConfig{ + Enabled: c.MaixCam.Enabled, + Host: c.MaixCam.Host, + Port: c.MaixCam.Port, + }, + QQ: config.QQConfig{ + Enabled: c.QQ.Enabled, + AppID: c.QQ.AppID, + AppSecret: c.QQ.AppSecret, + }, + DingTalk: config.DingTalkConfig{ + Enabled: c.DingTalk.Enabled, + ClientID: c.DingTalk.ClientID, + ClientSecret: c.DingTalk.ClientSecret, + }, + Slack: config.SlackConfig{ + Enabled: c.Slack.Enabled, + BotToken: c.Slack.BotToken, + AppToken: c.Slack.AppToken, + }, + Matrix: config.MatrixConfig{ + Enabled: c.Matrix.Enabled, + Homeserver: c.Matrix.Homeserver, + UserID: c.Matrix.UserID, + AccessToken: c.Matrix.AccessToken, + AllowFrom: c.Matrix.AllowFrom, + JoinOnInvite: true, + }, + LINE: config.LINEConfig{ + Enabled: c.LINE.Enabled, + ChannelSecret: c.LINE.ChannelSecret, + ChannelAccessToken: c.LINE.ChannelAccessToken, + WebhookHost: c.LINE.WebhookHost, + WebhookPort: c.LINE.WebhookPort, + WebhookPath: c.LINE.WebhookPath, + }, + } +} + +func (c GatewayConfig) ToStandardGateway() config.GatewayConfig { + return config.GatewayConfig{ + Host: c.Host, + Port: c.Port, + } +} + +func (c ToolsConfig) ToStandardTools() config.ToolsConfig { + return config.ToolsConfig{ + Web: config.WebToolsConfig{ + Brave: config.BraveConfig{ + Enabled: c.Web.Brave.Enabled, + APIKey: c.Web.Brave.APIKey, + APIKeys: c.Web.Brave.APIKeys, + MaxResults: c.Web.Brave.MaxResults, + }, + Tavily: config.TavilyConfig{ + Enabled: c.Web.Tavily.Enabled, + APIKey: c.Web.Tavily.APIKey, + BaseURL: c.Web.Tavily.BaseURL, + MaxResults: c.Web.Tavily.MaxResults, + }, + DuckDuckGo: config.DuckDuckGoConfig{ + Enabled: c.Web.DuckDuckGo.Enabled, + MaxResults: c.Web.DuckDuckGo.MaxResults, + }, + Perplexity: config.PerplexityConfig{ + Enabled: c.Web.Perplexity.Enabled, + APIKey: c.Web.Perplexity.APIKey, + MaxResults: c.Web.Perplexity.MaxResults, + }, + Proxy: c.Web.Proxy, + }, + Cron: config.CronToolsConfig{ + ExecTimeoutMinutes: c.Cron.ExecTimeoutMinutes, + }, + Exec: config.ExecConfig{ + EnableDenyPatterns: c.Exec.EnableDenyPatterns, + CustomDenyPatterns: c.Exec.CustomDenyPatterns, + AllowRemote: config.DefaultConfig().Tools.Exec.AllowRemote, + }, + } +} diff --git a/pkg/migrate/sources/openclaw/loader.go b/pkg/migrate/sources/openclaw/loader.go new file mode 100644 index 000000000..8f0403055 --- /dev/null +++ b/pkg/migrate/sources/openclaw/loader.go @@ -0,0 +1,115 @@ +package openclaw + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +func LoadOpenClawConfig(path string) (*OpenClawConfig, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read config: %w", err) + } + + var config OpenClawConfig + if err := json.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("failed to parse JSON: %w", err) + } + + return &config, nil +} + +func LoadOpenClawConfigFromDir(dir string) (*OpenClawConfig, error) { + candidates := []string{ + filepath.Join(dir, "openclaw.json"), + filepath.Join(dir, "config.json"), + } + + for _, p := range candidates { + if _, err := os.Stat(p); err == nil { + return LoadOpenClawConfig(p) + } + } + + return nil, fmt.Errorf("no config file found in %s", dir) +} + +func GetProviderConfig(models *OpenClawModels) map[string]OpenClawProviderConfig { + result := make(map[string]OpenClawProviderConfig) + if models == nil || models.Providers == nil { + return result + } + + for name, raw := range models.Providers { + var prov OpenClawProviderConfig + if err := json.Unmarshal(raw, &prov); err != nil { + continue + } + mappedName := mapProvider(name) + result[mappedName] = prov + } + + return result +} + +func GetProviderConfigFromDir(dir string) map[string]ProviderConfig { + result := make(map[string]ProviderConfig) + p := filepath.Join(dir, "agents", "main", "agent", "models.json") + + if _, err := os.Stat(p); err != nil { + return result + } + + data, err := os.ReadFile(p) + if err != nil { + return result + } + var models OpenClawModels + if err := json.Unmarshal(data, &models); err != nil { + return result + } + + for name, raw := range models.Providers { + var prov ProviderConfig + if err := json.Unmarshal(raw, &prov); err != nil { + continue + } + mappedName := mapProvider(name) + result[mappedName] = prov + } + return result +} + +func GetChannelAllowFrom(ch any) []string { + switch c := ch.(type) { + case *OpenClawTelegramConfig: + if c == nil { + return nil + } + return c.AllowFrom + case *OpenClawDiscordConfig: + if c == nil { + return nil + } + return c.AllowFrom + case *OpenClawSlackConfig: + if c == nil { + return nil + } + return c.AllowFrom + case *OpenClawMatrixConfig: + if c == nil { + return nil + } + return c.AllowFrom + case *OpenClawWhatsAppConfig: + if c == nil { + return nil + } + return c.AllowFrom + default: + return nil + } +} diff --git a/pkg/migrate/sources/openclaw/openclaw_config.go b/pkg/migrate/sources/openclaw/openclaw_config.go deleted file mode 100644 index 1fe985813..000000000 --- a/pkg/migrate/sources/openclaw/openclaw_config.go +++ /dev/null @@ -1,1061 +0,0 @@ -package openclaw - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "jane/pkg/config" -) - -type OpenClawConfig struct { - Auth *OpenClawAuth `json:"auth"` - Models *OpenClawModels `json:"models"` - Agents *OpenClawAgents `json:"agents"` - Tools *OpenClawTools `json:"tools"` - Channels *OpenClawChannels `json:"channels"` - Cron json.RawMessage `json:"cron"` - Hooks json.RawMessage `json:"hooks"` - Skills *OpenClawSkills `json:"skills"` - Memory json.RawMessage `json:"memory"` - Session json.RawMessage `json:"session"` -} - -type OpenClawAuth struct { - Profiles json.RawMessage `json:"profiles"` - Order json.RawMessage `json:"order"` -} - -type OpenClawModels struct { - Providers map[string]json.RawMessage `json:"providers"` -} - -type ProviderConfig struct { - BaseUrl string `json:"baseUrl"` - Api string `json:"api"` - Models []ModelConfig `json:"models"` - ApiKey string `json:"apiKey"` -} - -type OpenClawModelConfig struct { - ID string `json:"id"` - Name string `json:"name"` - Reasoning bool `json:"reasoning"` - Input []string `json:"input"` - Cost Cost `json:"cost"` - ContextWindow int `json:"contextWindow"` - MaxTokens int `json:"maxTokens"` - Api string `json:"api,omitempty"` -} - -type Cost struct { - Input float64 `json:"input"` - Output float64 `json:"output"` - CacheRead float64 `json:"cacheRead"` - CacheWrite float64 `json:"cacheWrite"` -} - -type OpenClawTools struct { - Profile *string `json:"profile"` - Allow []string `json:"allow"` - Deny []string `json:"deny"` -} - -type OpenClawAgents struct { - Defaults *OpenClawAgentDefaults `json:"defaults"` - List []OpenClawAgentEntry `json:"list"` -} - -type OpenClawAgentDefaults struct { - Model *OpenClawAgentModel `json:"model"` - Workspace *string `json:"workspace"` - Tools *OpenClawAgentTools `json:"tools"` - Identity *string `json:"identity"` -} - -type OpenClawAgentModel struct { - Simple string `json:"-"` - Primary *string `json:"primary"` - Fallbacks []string `json:"fallbacks"` -} - -func (m *OpenClawAgentModel) GetPrimary() string { - if m.Simple != "" { - return m.Simple - } - if m.Primary != nil { - return *m.Primary - } - return "" -} - -func (m *OpenClawAgentModel) GetFallbacks() []string { - return m.Fallbacks -} - -type OpenClawAgentEntry struct { - ID string `json:"id"` - Name *string `json:"name"` - Model *OpenClawAgentModel `json:"model"` - Tools *OpenClawAgentTools `json:"tools"` - Workspace *string `json:"workspace"` - Skills []string `json:"skills"` - Identity *string `json:"identity"` -} - -type OpenClawAgentTools struct { - Profile *string `json:"profile"` - Allow []string `json:"allow"` - Deny []string `json:"deny"` - AlsoAllow []string `json:"alsoAllow"` -} - -type OpenClawChannels struct { - Telegram *OpenClawTelegramConfig `json:"telegram"` - Discord *OpenClawDiscordConfig `json:"discord"` - Slack *OpenClawSlackConfig `json:"slack"` - WhatsApp *OpenClawWhatsAppConfig `json:"whatsapp"` - Signal *OpenClawSignalConfig `json:"signal"` - Matrix *OpenClawMatrixConfig `json:"matrix"` - GoogleChat *OpenClawGoogleChatConfig `json:"googlechat"` - Teams *OpenClawTeamsConfig `json:"msteams"` - IRC *OpenClawIrcConfig `json:"irc"` - Mattermost *OpenClawMattermostConfig `json:"mattermost"` - IMessage *OpenClawIMessageConfig `json:"imessage"` - BlueBubbles *OpenClawBlueBubblesConfig `json:"bluebubbles"` - QQ *OpenClawQQConfig `json:"qq"` - DingTalk *OpenClawDingTalkConfig `json:"dingtalk"` - MaixCam *OpenClawMaixCamConfig `json:"maixcam"` -} - -type OpenClawTelegramConfig struct { - BotToken *string `json:"botToken"` - AllowFrom []string `json:"allowFrom"` - GroupPolicy *string `json:"groupPolicy"` - DmPolicy *string `json:"dmPolicy"` - Enabled *bool `json:"enabled"` -} - -type OpenClawDiscordConfig struct { - Token *string `json:"token"` - Guilds json.RawMessage `json:"guilds"` - DmPolicy *string `json:"dmPolicy"` - GroupPolicy *string `json:"groupPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawSlackConfig struct { - BotToken *string `json:"botToken"` - AppToken *string `json:"appToken"` - DmPolicy *string `json:"dmPolicy"` - GroupPolicy *string `json:"groupPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawWhatsAppConfig struct { - AuthDir *string `json:"authDir"` - DmPolicy *string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - GroupPolicy *string `json:"groupPolicy"` - Enabled *bool `json:"enabled"` - BridgeURL *string `json:"bridgeUrl"` -} - -type OpenClawSignalConfig struct { - HttpUrl *string `json:"httpUrl"` - HttpHost *string `json:"httpHost"` - HttpPort *int `json:"httpPort"` - Account *string `json:"account"` - DmPolicy *string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawMatrixConfig struct { - Homeserver *string `json:"homeserver"` - UserID *string `json:"userId"` - AccessToken *string `json:"accessToken"` - Rooms []string `json:"rooms"` - DmPolicy *string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawGoogleChatConfig struct { - ServiceAccountFile *string `json:"serviceAccountFile"` - WebhookPath *string `json:"webhookPath"` - BotUser *string `json:"botUser"` - DmPolicy *string `json:"dmPolicy"` - Enabled *bool `json:"enabled"` -} - -type OpenClawTeamsConfig struct { - AppID *string `json:"appId"` - AppPassword *string `json:"appPassword"` - TenantID *string `json:"tenantId"` - DmPolicy *string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawIrcConfig struct { - Host *string `json:"host"` - Port *int `json:"port"` - TLS *bool `json:"tls"` - Nick *string `json:"nick"` - Password *string `json:"password"` - Channels []string `json:"channels"` - DmPolicy *string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawMattermostConfig struct { - BotToken *string `json:"botToken"` - BaseURL *string `json:"baseUrl"` - DmPolicy *string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawIMessageConfig struct { - CliPath *string `json:"cliPath"` - DbPath *string `json:"dbPath"` - DmPolicy *string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawBlueBubblesConfig struct { - ServerURL *string `json:"serverUrl"` - Password *string `json:"password"` - DmPolicy *string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawQQConfig struct { - AppID *string `json:"appId"` - AppSecret *string `json:"appSecret"` - DmPolicy *string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawDingTalkConfig struct { - AppID *string `json:"appId"` - AppSecret *string `json:"appSecret"` - DmPolicy *string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawMaixCamConfig struct { - Host *string `json:"host"` - Port *int `json:"port"` - DmPolicy *string `json:"dmPolicy"` - AllowFrom []string `json:"allowFrom"` - Enabled *bool `json:"enabled"` -} - -type OpenClawSkills struct { - Entries map[string]json.RawMessage `json:"entries"` - Load json.RawMessage `json:"load"` -} - -type OpenClawProviderConfig struct { - APIKey string `json:"api_key"` - BaseURL string `json:"base_url"` -} - -func (c *OpenClawConfig) GetEnabled() bool { - return true -} - -func LoadOpenClawConfig(path string) (*OpenClawConfig, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("failed to read config: %w", err) - } - - var config OpenClawConfig - if err := json.Unmarshal(data, &config); err != nil { - return nil, fmt.Errorf("failed to parse JSON: %w", err) - } - - return &config, nil -} - -func LoadOpenClawConfigFromDir(dir string) (*OpenClawConfig, error) { - candidates := []string{ - filepath.Join(dir, "openclaw.json"), - filepath.Join(dir, "config.json"), - } - - for _, p := range candidates { - if _, err := os.Stat(p); err == nil { - return LoadOpenClawConfig(p) - } - } - - return nil, fmt.Errorf("no config file found in %s", dir) -} - -func GetProviderConfig(models *OpenClawModels) map[string]OpenClawProviderConfig { - result := make(map[string]OpenClawProviderConfig) - if models == nil || models.Providers == nil { - return result - } - - for name, raw := range models.Providers { - var prov OpenClawProviderConfig - if err := json.Unmarshal(raw, &prov); err != nil { - continue - } - mappedName := mapProvider(name) - result[mappedName] = prov - } - - return result -} - -func GetProviderConfigFromDir(dir string) map[string]ProviderConfig { - result := make(map[string]ProviderConfig) - p := filepath.Join(dir, "agents", "main", "agent", "models.json") - - if _, err := os.Stat(p); err != nil { - return result - } - - data, err := os.ReadFile(p) - if err != nil { - return result - } - var models OpenClawModels - if err := json.Unmarshal(data, &models); err != nil { - return result - } - - for name, raw := range models.Providers { - var prov ProviderConfig - if err := json.Unmarshal(raw, &prov); err != nil { - continue - } - mappedName := mapProvider(name) - result[mappedName] = prov - } - return result -} - -func (c *OpenClawConfig) IsChannelEnabled(name string) bool { - switch name { - case "telegram": - return c.Channels.Telegram == nil || c.Channels.Telegram.Enabled == nil || *c.Channels.Telegram.Enabled - case "discord": - return c.Channels.Discord == nil || c.Channels.Discord.Enabled == nil || *c.Channels.Discord.Enabled - case "slack": - return c.Channels.Slack == nil || c.Channels.Slack.Enabled == nil || *c.Channels.Slack.Enabled - case "matrix": - return c.Channels.Matrix == nil || c.Channels.Matrix.Enabled == nil || *c.Channels.Matrix.Enabled - case "whatsapp": - return c.Channels.WhatsApp == nil || c.Channels.WhatsApp.Enabled == nil || *c.Channels.WhatsApp.Enabled - default: - return false - } -} - -func GetChannelAllowFrom(ch any) []string { - switch c := ch.(type) { - case *OpenClawTelegramConfig: - if c == nil { - return nil - } - return c.AllowFrom - case *OpenClawDiscordConfig: - if c == nil { - return nil - } - return c.AllowFrom - case *OpenClawSlackConfig: - if c == nil { - return nil - } - return c.AllowFrom - case *OpenClawMatrixConfig: - if c == nil { - return nil - } - return c.AllowFrom - case *OpenClawWhatsAppConfig: - if c == nil { - return nil - } - return c.AllowFrom - default: - return nil - } -} - -func (c *OpenClawConfig) GetDefaultModel() (provider, model string) { - if c.Agents == nil || c.Agents.Defaults == nil || c.Agents.Defaults.Model == nil { - return "anthropic", "claude-sonnet-4-20250514" - } - - primary := c.Agents.Defaults.Model.GetPrimary() - if primary == "" { - return "anthropic", "claude-sonnet-4-20250514" - } - - parts := strings.Split(primary, "/") - if len(parts) > 1 { - return mapProvider(parts[0]), parts[1] - } - - return "anthropic", primary -} - -func (c *OpenClawConfig) GetDefaultWorkspace() string { - if c.Agents == nil || c.Agents.Defaults == nil || c.Agents.Defaults.Workspace == nil { - return "" - } - return rewriteWorkspacePath(*c.Agents.Defaults.Workspace) -} - -func (c *OpenClawConfig) GetAgents() []OpenClawAgentEntry { - if c.Agents == nil { - return nil - } - return c.Agents.List -} - -func (c *OpenClawConfig) HasSkills() bool { - return c.Skills != nil && c.Skills.Entries != nil && len(c.Skills.Entries) > 0 -} - -func (c *OpenClawConfig) HasMemory() bool { - return c.Memory != nil && len(c.Memory) > 0 -} - -func (c *OpenClawConfig) HasCron() bool { - return c.Cron != nil && len(c.Cron) > 0 -} - -func (c *OpenClawConfig) HasHooks() bool { - return c.Hooks != nil && len(c.Hooks) > 0 -} - -func (c *OpenClawConfig) HasSession() bool { - return c.Session != nil && len(c.Session) > 0 -} - -func (c *OpenClawConfig) HasAuthProfiles() bool { - return c.Auth != nil && c.Auth.Profiles != nil && len(c.Auth.Profiles) > 0 -} - -func (c *OpenClawConfig) ConvertToPicoClaw(sourceHome string) (*PicoClawConfig, []string, error) { - cfg := &PicoClawConfig{} - var warnings []string - - provider, modelName := c.GetDefaultModel() - cfg.Agents.Defaults.Workspace = c.GetDefaultWorkspace() - cfg.Agents.Defaults.ModelName = modelName - - providerConfigs := GetProviderConfigFromDir(sourceHome) - defaultAPIKey := "" - defaultBaseURL := "" - - if provCfg, ok := providerConfigs[provider]; ok { - defaultAPIKey = provCfg.ApiKey - defaultBaseURL = provCfg.BaseUrl - } - - cfg.ModelList = []ModelConfig{ - { - ModelName: modelName, - Model: fmt.Sprintf("%s/%s", provider, modelName), - APIKey: defaultAPIKey, - APIBase: defaultBaseURL, - }, - } - - for provName, provCfg := range providerConfigs { - if provName == provider { - continue - } - if provCfg.ApiKey != "" { - continue - } - cfg.ModelList = append(cfg.ModelList, ModelConfig{ - ModelName: fmt.Sprintf("%s", provName), - Model: fmt.Sprintf("%s/%s", provName, provName), - APIKey: provCfg.ApiKey, - APIBase: provCfg.BaseUrl, - }) - } - - cfg.Channels = c.convertChannels(&warnings) - - agentList := c.convertAgents(&warnings) - if len(agentList) > 0 { - cfg.Agents.List = agentList - } - - if c.HasSkills() { - warnings = append( - warnings, - fmt.Sprintf( - "Skills (%d entries) not automatically migrated - reinstall via picoclaw CLI", - len(c.Skills.Entries), - ), - ) - } - if c.HasMemory() { - warnings = append(warnings, "Memory backend config not migrated - PicoClaw uses SQLite with vector embeddings") - } - if c.HasCron() { - warnings = append( - warnings, - "Cron job scheduling not supported in PicoClaw - consider using external schedulers", - ) - } - if c.HasHooks() { - warnings = append(warnings, "Webhook hooks not supported in PicoClaw - use event system instead") - } - if c.HasSession() { - warnings = append(warnings, "Session scope config differs - PicoClaw uses per-agent sessions by default") - } - if c.HasAuthProfiles() { - warnings = append( - warnings, - "Auth profiles (API keys, OAuth tokens) not migrated for security - set env vars manually", - ) - } - - return cfg, warnings, nil -} - -type ModelConfig struct { - ModelName string `json:"model_name"` - Model string `json:"model"` - APIBase string `json:"api_base,omitempty"` - APIKey string `json:"api_key"` - Proxy string `json:"proxy,omitempty"` -} - -type PicoClawConfig struct { - Agents AgentsConfig `json:"agents"` - Bindings []AgentBinding `json:"bindings,omitempty"` - Channels ChannelsConfig `json:"channels"` - ModelList []ModelConfig `json:"model_list"` - Gateway GatewayConfig `json:"gateway"` - Tools ToolsConfig `json:"tools"` -} - -type AgentsConfig struct { - Defaults AgentDefaults `json:"defaults"` - List []AgentConfig `json:"list,omitempty"` -} - -type AgentDefaults struct { - Workspace string `json:"workspace"` - RestrictToWorkspace bool `json:"restrict_to_workspace"` - Provider string `json:"provider"` - ModelName string `json:"model_name"` - Model string `json:"model,omitempty"` - ModelFallbacks []string `json:"model_fallbacks,omitempty"` - ImageModel string `json:"image_model,omitempty"` - ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"` - MaxTokens int `json:"max_tokens"` - Temperature *float64 `json:"temperature,omitempty"` - MaxToolIterations int `json:"max_tool_iterations"` -} - -type AgentConfig struct { - ID string `json:"id"` - Default bool `json:"default,omitempty"` - Name string `json:"name,omitempty"` - Workspace string `json:"workspace,omitempty"` - Model *AgentModelConfig `json:"model,omitempty"` - Skills []string `json:"skills,omitempty"` -} - -type AgentModelConfig struct { - Primary string `json:"primary,omitempty"` - Fallbacks []string `json:"fallbacks,omitempty"` -} - -type AgentBinding struct { - AgentID string `json:"agent_id"` - Match BindingMatch `json:"match"` -} - -type BindingMatch struct { - Channel string `json:"channel"` - AccountID string `json:"account_id,omitempty"` - Peer *PeerMatch `json:"peer,omitempty"` - GuildID string `json:"guild_id,omitempty"` - TeamID string `json:"team_id,omitempty"` -} - -type PeerMatch struct { - Kind string `json:"kind"` - ID string `json:"id"` -} - -type ChannelsConfig struct { - WhatsApp WhatsAppConfig `json:"whatsapp"` - Telegram TelegramConfig `json:"telegram"` - Discord DiscordConfig `json:"discord"` - MaixCam MaixCamConfig `json:"maixcam"` - QQ QQConfig `json:"qq"` - DingTalk DingTalkConfig `json:"dingtalk"` - Slack SlackConfig `json:"slack"` - Matrix MatrixConfig `json:"matrix"` - LINE LINEConfig `json:"line"` -} - -type WhatsAppConfig struct { - Enabled bool `json:"enabled"` - BridgeURL string `json:"bridge_url"` - AllowFrom []string `json:"allow_from"` -} - -type TelegramConfig struct { - Enabled bool `json:"enabled"` - Token string `json:"token"` - Proxy string `json:"proxy"` - AllowFrom []string `json:"allow_from"` -} - -type DiscordConfig struct { - Enabled bool `json:"enabled"` - Token string `json:"token"` - MentionOnly bool `json:"mention_only"` - AllowFrom []string `json:"allow_from"` -} - -type MaixCamConfig struct { - Enabled bool `json:"enabled"` - Host string `json:"host"` - Port int `json:"port"` - AllowFrom []string `json:"allow_from"` -} - -type QQConfig struct { - Enabled bool `json:"enabled"` - AppID string `json:"app_id"` - AppSecret string `json:"app_secret"` - AllowFrom []string `json:"allow_from"` -} - -type DingTalkConfig struct { - Enabled bool `json:"enabled"` - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - AllowFrom []string `json:"allow_from"` -} - -type SlackConfig struct { - Enabled bool `json:"enabled"` - BotToken string `json:"bot_token"` - AppToken string `json:"app_token"` - AllowFrom []string `json:"allow_from"` -} - -type MatrixConfig struct { - Enabled bool `json:"enabled"` - Homeserver string `json:"homeserver"` - UserID string `json:"user_id"` - AccessToken string `json:"access_token"` - AllowFrom []string `json:"allow_from"` -} - -type LINEConfig struct { - Enabled bool `json:"enabled"` - ChannelSecret string `json:"channel_secret"` - ChannelAccessToken string `json:"channel_access_token"` - WebhookHost string `json:"webhook_host"` - WebhookPort int `json:"webhook_port"` - WebhookPath string `json:"webhook_path"` - AllowFrom []string `json:"allow_from"` -} - -type GatewayConfig struct { - Host string `json:"host"` - Port int `json:"port"` -} - -type ToolsConfig struct { - Web WebToolsConfig `json:"web"` - Cron CronConfig `json:"cron"` - Exec ExecConfig `json:"exec"` -} - -type WebToolsConfig struct { - Brave BraveConfig `json:"brave"` - Tavily TavilyConfig `json:"tavily"` - DuckDuckGo DuckDuckGoConfig `json:"duckduckgo"` - Perplexity PerplexityConfig `json:"perplexity"` - Proxy string `json:"proxy,omitempty"` -} - -type BraveConfig struct { - Enabled bool `json:"enabled"` - APIKey string `json:"api_key"` - APIKeys []string `json:"api_keys"` - MaxResults int `json:"max_results"` -} - -type TavilyConfig struct { - Enabled bool `json:"enabled"` - APIKey string `json:"api_key"` - APIKeys []string `json:"api_keys"` - BaseURL string `json:"base_url"` - MaxResults int `json:"max_results"` -} - -type DuckDuckGoConfig struct { - Enabled bool `json:"enabled"` - MaxResults int `json:"max_results"` -} - -type PerplexityConfig struct { - Enabled bool `json:"enabled"` - APIKey string `json:"api_key"` - APIKeys []string `json:"api_keys"` - MaxResults int `json:"max_results"` -} - -type CronConfig struct { - ExecTimeoutMinutes int `json:"exec_timeout_minutes"` -} - -type ExecConfig struct { - EnableDenyPatterns bool `json:"enable_deny_patterns"` - CustomDenyPatterns []string `json:"custom_deny_patterns"` -} - -func (c *OpenClawConfig) convertChannels(warnings *[]string) ChannelsConfig { - channels := ChannelsConfig{} - - if c.Channels == nil { - return channels - } - - if c.Channels.Telegram != nil { - enabled := c.Channels.Telegram.Enabled == nil || *c.Channels.Telegram.Enabled - channels.Telegram = TelegramConfig{ - Enabled: enabled, - AllowFrom: c.Channels.Telegram.AllowFrom, - } - if c.Channels.Telegram.BotToken != nil { - channels.Telegram.Token = *c.Channels.Telegram.BotToken - } - } - - if c.Channels.Discord != nil { - enabled := c.Channels.Discord.Enabled == nil || *c.Channels.Discord.Enabled - channels.Discord = DiscordConfig{ - Enabled: enabled, - AllowFrom: c.Channels.Discord.AllowFrom, - } - if c.Channels.Discord.Token != nil { - channels.Discord.Token = *c.Channels.Discord.Token - } - } - - if c.Channels.Slack != nil { - enabled := c.Channels.Slack.Enabled == nil || *c.Channels.Slack.Enabled - channels.Slack = SlackConfig{ - Enabled: enabled, - AllowFrom: c.Channels.Slack.AllowFrom, - } - if c.Channels.Slack.BotToken != nil { - channels.Slack.BotToken = *c.Channels.Slack.BotToken - } - if c.Channels.Slack.AppToken != nil { - channels.Slack.AppToken = *c.Channels.Slack.AppToken - } - } - - if c.Channels.WhatsApp != nil { - enabled := c.Channels.WhatsApp.Enabled == nil || *c.Channels.WhatsApp.Enabled - channels.WhatsApp = WhatsAppConfig{ - Enabled: enabled, - AllowFrom: c.Channels.WhatsApp.AllowFrom, - } - if c.Channels.WhatsApp.BridgeURL != nil { - channels.WhatsApp.BridgeURL = *c.Channels.WhatsApp.BridgeURL - } - } - - if c.Channels.QQ != nil && supportedChannels["qq"] { - channels.QQ = QQConfig{ - Enabled: true, - AllowFrom: c.Channels.QQ.AllowFrom, - } - if c.Channels.QQ.AppID != nil { - channels.QQ.AppID = *c.Channels.QQ.AppID - } - if c.Channels.QQ.AppSecret != nil { - channels.QQ.AppSecret = *c.Channels.QQ.AppSecret - } - } - - if c.Channels.DingTalk != nil && supportedChannels["dingtalk"] { - channels.DingTalk = DingTalkConfig{ - Enabled: true, - AllowFrom: c.Channels.DingTalk.AllowFrom, - } - if c.Channels.DingTalk.AppID != nil { - channels.DingTalk.ClientID = *c.Channels.DingTalk.AppID - } - if c.Channels.DingTalk.AppSecret != nil { - channels.DingTalk.ClientSecret = *c.Channels.DingTalk.AppSecret - } - } - - if c.Channels.MaixCam != nil && supportedChannels["maixcam"] { - channels.MaixCam = MaixCamConfig{ - Enabled: true, - AllowFrom: c.Channels.MaixCam.AllowFrom, - } - if c.Channels.MaixCam.Host != nil { - channels.MaixCam.Host = *c.Channels.MaixCam.Host - } - if c.Channels.MaixCam.Port != nil { - channels.MaixCam.Port = *c.Channels.MaixCam.Port - } - } - - if c.Channels.Matrix != nil && supportedChannels["matrix"] { - enabled := c.Channels.Matrix.Enabled == nil || *c.Channels.Matrix.Enabled - channels.Matrix = MatrixConfig{ - Enabled: enabled, - AllowFrom: c.Channels.Matrix.AllowFrom, - } - if c.Channels.Matrix.Homeserver != nil { - channels.Matrix.Homeserver = *c.Channels.Matrix.Homeserver - } - if c.Channels.Matrix.UserID != nil { - channels.Matrix.UserID = *c.Channels.Matrix.UserID - } - if c.Channels.Matrix.AccessToken != nil { - channels.Matrix.AccessToken = *c.Channels.Matrix.AccessToken - } - } - - if c.Channels.Signal != nil { - *warnings = append(*warnings, "Channel 'signal': No PicoClaw adapter available") - } - if c.Channels.IRC != nil { - *warnings = append(*warnings, "Channel 'irc': No PicoClaw adapter available") - } - if c.Channels.Mattermost != nil { - *warnings = append(*warnings, "Channel 'mattermost': No PicoClaw adapter available") - } - if c.Channels.IMessage != nil { - *warnings = append(*warnings, "Channel 'imessage': macOS-only channel - requires manual setup") - } - if c.Channels.BlueBubbles != nil { - *warnings = append( - *warnings, - "Channel 'bluebubbles': No PicoClaw adapter available - consider iMessage instead", - ) - } - - return channels -} - -func (c *OpenClawConfig) convertAgents(warnings *[]string) []AgentConfig { - var agents []AgentConfig - - if c.Agents == nil { - return agents - } - - for _, entry := range c.Agents.List { - agentID := entry.ID - if agentID == "" { - continue - } - - agentName := agentID - if entry.Name != nil { - agentName = *entry.Name - } - - agentCfg := AgentConfig{ - ID: agentID, - Name: agentName, - Default: len(agents) == 0, - } - - if entry.Workspace != nil { - agentCfg.Workspace = rewriteWorkspacePath(*entry.Workspace) - } - - if entry.Model != nil { - primary := entry.Model.GetPrimary() - if primary != "" { - agentCfg.Model = &AgentModelConfig{ - Primary: primary, - Fallbacks: entry.Model.GetFallbacks(), - } - } - } - - if len(entry.Skills) > 0 { - agentCfg.Skills = entry.Skills - } - - agents = append(agents, agentCfg) - } - - return agents -} - -func (c *PicoClawConfig) ToStandardConfig() *config.Config { - cfg := config.DefaultConfig() - - cfg.Agents.Defaults.Workspace = c.Agents.Defaults.Workspace - cfg.Agents.Defaults.Provider = c.Agents.Defaults.Provider - cfg.Agents.Defaults.ModelName = c.Agents.Defaults.ModelName - cfg.Agents.Defaults.ModelFallbacks = c.Agents.Defaults.ModelFallbacks - - for _, m := range c.ModelList { - cfg.ModelList = append(cfg.ModelList, config.ModelConfig{ - ModelName: m.ModelName, - Model: m.Model, - APIBase: m.APIBase, - APIKey: m.APIKey, - Proxy: m.Proxy, - }) - } - - cfg.Channels = c.Channels.ToStandardChannels() - cfg.Gateway = c.Gateway.ToStandardGateway() - cfg.Tools = c.Tools.ToStandardTools() - - cfg.Agents.List = make([]config.AgentConfig, len(c.Agents.List)) - for i, a := range c.Agents.List { - cfg.Agents.List[i] = config.AgentConfig{ - ID: a.ID, - Default: a.Default, - Name: a.Name, - Workspace: a.Workspace, - Skills: a.Skills, - } - if a.Model != nil { - cfg.Agents.List[i].Model = &config.AgentModelConfig{ - Primary: a.Model.Primary, - Fallbacks: a.Model.Fallbacks, - } - } - } - - return cfg -} - -func (c ChannelsConfig) ToStandardChannels() config.ChannelsConfig { - return config.ChannelsConfig{ - WhatsApp: config.WhatsAppConfig{ - Enabled: c.WhatsApp.Enabled, - BridgeURL: c.WhatsApp.BridgeURL, - }, - Telegram: config.TelegramConfig{ - Enabled: c.Telegram.Enabled, - Token: c.Telegram.Token, - Proxy: c.Telegram.Proxy, - }, - Discord: config.DiscordConfig{ - Enabled: c.Discord.Enabled, - Token: c.Discord.Token, - MentionOnly: c.Discord.MentionOnly, - }, - MaixCam: config.MaixCamConfig{ - Enabled: c.MaixCam.Enabled, - Host: c.MaixCam.Host, - Port: c.MaixCam.Port, - }, - QQ: config.QQConfig{ - Enabled: c.QQ.Enabled, - AppID: c.QQ.AppID, - AppSecret: c.QQ.AppSecret, - }, - DingTalk: config.DingTalkConfig{ - Enabled: c.DingTalk.Enabled, - ClientID: c.DingTalk.ClientID, - ClientSecret: c.DingTalk.ClientSecret, - }, - Slack: config.SlackConfig{ - Enabled: c.Slack.Enabled, - BotToken: c.Slack.BotToken, - AppToken: c.Slack.AppToken, - }, - Matrix: config.MatrixConfig{ - Enabled: c.Matrix.Enabled, - Homeserver: c.Matrix.Homeserver, - UserID: c.Matrix.UserID, - AccessToken: c.Matrix.AccessToken, - AllowFrom: c.Matrix.AllowFrom, - JoinOnInvite: true, - }, - LINE: config.LINEConfig{ - Enabled: c.LINE.Enabled, - ChannelSecret: c.LINE.ChannelSecret, - ChannelAccessToken: c.LINE.ChannelAccessToken, - WebhookHost: c.LINE.WebhookHost, - WebhookPort: c.LINE.WebhookPort, - WebhookPath: c.LINE.WebhookPath, - }, - } -} - -func (c GatewayConfig) ToStandardGateway() config.GatewayConfig { - return config.GatewayConfig{ - Host: c.Host, - Port: c.Port, - } -} - -func (c ToolsConfig) ToStandardTools() config.ToolsConfig { - return config.ToolsConfig{ - Web: config.WebToolsConfig{ - Brave: config.BraveConfig{ - Enabled: c.Web.Brave.Enabled, - APIKey: c.Web.Brave.APIKey, - APIKeys: c.Web.Brave.APIKeys, - MaxResults: c.Web.Brave.MaxResults, - }, - Tavily: config.TavilyConfig{ - Enabled: c.Web.Tavily.Enabled, - APIKey: c.Web.Tavily.APIKey, - BaseURL: c.Web.Tavily.BaseURL, - MaxResults: c.Web.Tavily.MaxResults, - }, - DuckDuckGo: config.DuckDuckGoConfig{ - Enabled: c.Web.DuckDuckGo.Enabled, - MaxResults: c.Web.DuckDuckGo.MaxResults, - }, - Perplexity: config.PerplexityConfig{ - Enabled: c.Web.Perplexity.Enabled, - APIKey: c.Web.Perplexity.APIKey, - MaxResults: c.Web.Perplexity.MaxResults, - }, - Proxy: c.Web.Proxy, - }, - Cron: config.CronToolsConfig{ - ExecTimeoutMinutes: c.Cron.ExecTimeoutMinutes, - }, - Exec: config.ExecConfig{ - EnableDenyPatterns: c.Exec.EnableDenyPatterns, - CustomDenyPatterns: c.Exec.CustomDenyPatterns, - AllowRemote: config.DefaultConfig().Tools.Exec.AllowRemote, - }, - } -} diff --git a/pkg/migrate/sources/openclaw/openclaw_types.go b/pkg/migrate/sources/openclaw/openclaw_types.go new file mode 100644 index 000000000..478afea67 --- /dev/null +++ b/pkg/migrate/sources/openclaw/openclaw_types.go @@ -0,0 +1,345 @@ +package openclaw + +import ( + "encoding/json" + "strings" +) + +type OpenClawConfig struct { + Auth *OpenClawAuth `json:"auth"` + Models *OpenClawModels `json:"models"` + Agents *OpenClawAgents `json:"agents"` + Tools *OpenClawTools `json:"tools"` + Channels *OpenClawChannels `json:"channels"` + Cron json.RawMessage `json:"cron"` + Hooks json.RawMessage `json:"hooks"` + Skills *OpenClawSkills `json:"skills"` + Memory json.RawMessage `json:"memory"` + Session json.RawMessage `json:"session"` +} + +type OpenClawAuth struct { + Profiles json.RawMessage `json:"profiles"` + Order json.RawMessage `json:"order"` +} + +type OpenClawModels struct { + Providers map[string]json.RawMessage `json:"providers"` +} + +type ProviderConfig struct { + BaseUrl string `json:"baseUrl"` + Api string `json:"api"` + Models []ModelConfig `json:"models"` + ApiKey string `json:"apiKey"` +} + +type OpenClawModelConfig struct { + ID string `json:"id"` + Name string `json:"name"` + Reasoning bool `json:"reasoning"` + Input []string `json:"input"` + Cost Cost `json:"cost"` + ContextWindow int `json:"contextWindow"` + MaxTokens int `json:"maxTokens"` + Api string `json:"api,omitempty"` +} + +type Cost struct { + Input float64 `json:"input"` + Output float64 `json:"output"` + CacheRead float64 `json:"cacheRead"` + CacheWrite float64 `json:"cacheWrite"` +} + +type OpenClawTools struct { + Profile *string `json:"profile"` + Allow []string `json:"allow"` + Deny []string `json:"deny"` +} + +type OpenClawAgents struct { + Defaults *OpenClawAgentDefaults `json:"defaults"` + List []OpenClawAgentEntry `json:"list"` +} + +type OpenClawAgentDefaults struct { + Model *OpenClawAgentModel `json:"model"` + Workspace *string `json:"workspace"` + Tools *OpenClawAgentTools `json:"tools"` + Identity *string `json:"identity"` +} + +type OpenClawAgentModel struct { + Simple string `json:"-"` + Primary *string `json:"primary"` + Fallbacks []string `json:"fallbacks"` +} + +func (m *OpenClawAgentModel) GetPrimary() string { + if m.Simple != "" { + return m.Simple + } + if m.Primary != nil { + return *m.Primary + } + return "" +} + +func (m *OpenClawAgentModel) GetFallbacks() []string { + return m.Fallbacks +} + +type OpenClawAgentEntry struct { + ID string `json:"id"` + Name *string `json:"name"` + Model *OpenClawAgentModel `json:"model"` + Tools *OpenClawAgentTools `json:"tools"` + Workspace *string `json:"workspace"` + Skills []string `json:"skills"` + Identity *string `json:"identity"` +} + +type OpenClawAgentTools struct { + Profile *string `json:"profile"` + Allow []string `json:"allow"` + Deny []string `json:"deny"` + AlsoAllow []string `json:"alsoAllow"` +} + +type OpenClawChannels struct { + Telegram *OpenClawTelegramConfig `json:"telegram"` + Discord *OpenClawDiscordConfig `json:"discord"` + Slack *OpenClawSlackConfig `json:"slack"` + WhatsApp *OpenClawWhatsAppConfig `json:"whatsapp"` + Signal *OpenClawSignalConfig `json:"signal"` + Matrix *OpenClawMatrixConfig `json:"matrix"` + GoogleChat *OpenClawGoogleChatConfig `json:"googlechat"` + Teams *OpenClawTeamsConfig `json:"msteams"` + IRC *OpenClawIrcConfig `json:"irc"` + Mattermost *OpenClawMattermostConfig `json:"mattermost"` + IMessage *OpenClawIMessageConfig `json:"imessage"` + BlueBubbles *OpenClawBlueBubblesConfig `json:"bluebubbles"` + QQ *OpenClawQQConfig `json:"qq"` + DingTalk *OpenClawDingTalkConfig `json:"dingtalk"` + MaixCam *OpenClawMaixCamConfig `json:"maixcam"` +} + +type OpenClawTelegramConfig struct { + BotToken *string `json:"botToken"` + AllowFrom []string `json:"allowFrom"` + GroupPolicy *string `json:"groupPolicy"` + DmPolicy *string `json:"dmPolicy"` + Enabled *bool `json:"enabled"` +} + +type OpenClawDiscordConfig struct { + Token *string `json:"token"` + Guilds json.RawMessage `json:"guilds"` + DmPolicy *string `json:"dmPolicy"` + GroupPolicy *string `json:"groupPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawSlackConfig struct { + BotToken *string `json:"botToken"` + AppToken *string `json:"appToken"` + DmPolicy *string `json:"dmPolicy"` + GroupPolicy *string `json:"groupPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawWhatsAppConfig struct { + AuthDir *string `json:"authDir"` + DmPolicy *string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + GroupPolicy *string `json:"groupPolicy"` + Enabled *bool `json:"enabled"` + BridgeURL *string `json:"bridgeUrl"` +} + +type OpenClawSignalConfig struct { + HttpUrl *string `json:"httpUrl"` + HttpHost *string `json:"httpHost"` + HttpPort *int `json:"httpPort"` + Account *string `json:"account"` + DmPolicy *string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawMatrixConfig struct { + Homeserver *string `json:"homeserver"` + UserID *string `json:"userId"` + AccessToken *string `json:"accessToken"` + Rooms []string `json:"rooms"` + DmPolicy *string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawGoogleChatConfig struct { + ServiceAccountFile *string `json:"serviceAccountFile"` + WebhookPath *string `json:"webhookPath"` + BotUser *string `json:"botUser"` + DmPolicy *string `json:"dmPolicy"` + Enabled *bool `json:"enabled"` +} + +type OpenClawTeamsConfig struct { + AppID *string `json:"appId"` + AppPassword *string `json:"appPassword"` + TenantID *string `json:"tenantId"` + DmPolicy *string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawIrcConfig struct { + Host *string `json:"host"` + Port *int `json:"port"` + TLS *bool `json:"tls"` + Nick *string `json:"nick"` + Password *string `json:"password"` + Channels []string `json:"channels"` + DmPolicy *string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawMattermostConfig struct { + BotToken *string `json:"botToken"` + BaseURL *string `json:"baseUrl"` + DmPolicy *string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawIMessageConfig struct { + CliPath *string `json:"cliPath"` + DbPath *string `json:"dbPath"` + DmPolicy *string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawBlueBubblesConfig struct { + ServerURL *string `json:"serverUrl"` + Password *string `json:"password"` + DmPolicy *string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawQQConfig struct { + AppID *string `json:"appId"` + AppSecret *string `json:"appSecret"` + DmPolicy *string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawDingTalkConfig struct { + AppID *string `json:"appId"` + AppSecret *string `json:"appSecret"` + DmPolicy *string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawMaixCamConfig struct { + Host *string `json:"host"` + Port *int `json:"port"` + DmPolicy *string `json:"dmPolicy"` + AllowFrom []string `json:"allowFrom"` + Enabled *bool `json:"enabled"` +} + +type OpenClawSkills struct { + Entries map[string]json.RawMessage `json:"entries"` + Load json.RawMessage `json:"load"` +} + +type OpenClawProviderConfig struct { + APIKey string `json:"api_key"` + BaseURL string `json:"base_url"` +} + +func (c *OpenClawConfig) GetEnabled() bool { + return true +} + +func (c *OpenClawConfig) IsChannelEnabled(name string) bool { + switch name { + case "telegram": + return c.Channels.Telegram == nil || c.Channels.Telegram.Enabled == nil || *c.Channels.Telegram.Enabled + case "discord": + return c.Channels.Discord == nil || c.Channels.Discord.Enabled == nil || *c.Channels.Discord.Enabled + case "slack": + return c.Channels.Slack == nil || c.Channels.Slack.Enabled == nil || *c.Channels.Slack.Enabled + case "matrix": + return c.Channels.Matrix == nil || c.Channels.Matrix.Enabled == nil || *c.Channels.Matrix.Enabled + case "whatsapp": + return c.Channels.WhatsApp == nil || c.Channels.WhatsApp.Enabled == nil || *c.Channels.WhatsApp.Enabled + default: + return false + } +} + +func (c *OpenClawConfig) GetDefaultModel() (provider, model string) { + if c.Agents == nil || c.Agents.Defaults == nil || c.Agents.Defaults.Model == nil { + return "anthropic", "claude-sonnet-4-20250514" + } + + primary := c.Agents.Defaults.Model.GetPrimary() + if primary == "" { + return "anthropic", "claude-sonnet-4-20250514" + } + + parts := strings.Split(primary, "/") + if len(parts) > 1 { + return mapProvider(parts[0]), parts[1] + } + + return "anthropic", primary +} + +func (c *OpenClawConfig) GetDefaultWorkspace() string { + if c.Agents == nil || c.Agents.Defaults == nil || c.Agents.Defaults.Workspace == nil { + return "" + } + return rewriteWorkspacePath(*c.Agents.Defaults.Workspace) +} + +func (c *OpenClawConfig) GetAgents() []OpenClawAgentEntry { + if c.Agents == nil { + return nil + } + return c.Agents.List +} + +func (c *OpenClawConfig) HasSkills() bool { + return c.Skills != nil && c.Skills.Entries != nil && len(c.Skills.Entries) > 0 +} + +func (c *OpenClawConfig) HasMemory() bool { + return c.Memory != nil && len(c.Memory) > 0 +} + +func (c *OpenClawConfig) HasCron() bool { + return c.Cron != nil && len(c.Cron) > 0 +} + +func (c *OpenClawConfig) HasHooks() bool { + return c.Hooks != nil && len(c.Hooks) > 0 +} + +func (c *OpenClawConfig) HasSession() bool { + return c.Session != nil && len(c.Session) > 0 +} + +func (c *OpenClawConfig) HasAuthProfiles() bool { + return c.Auth != nil && c.Auth.Profiles != nil && len(c.Auth.Profiles) > 0 +} diff --git a/pkg/migrate/sources/openclaw/picoclaw_types.go b/pkg/migrate/sources/openclaw/picoclaw_types.go new file mode 100644 index 000000000..719c13223 --- /dev/null +++ b/pkg/migrate/sources/openclaw/picoclaw_types.go @@ -0,0 +1,202 @@ +package openclaw + +type ModelConfig struct { + ModelName string `json:"model_name"` + Model string `json:"model"` + APIBase string `json:"api_base,omitempty"` + APIKey string `json:"api_key"` + Proxy string `json:"proxy,omitempty"` +} + +type PicoClawConfig struct { + Agents AgentsConfig `json:"agents"` + Bindings []AgentBinding `json:"bindings,omitempty"` + Channels ChannelsConfig `json:"channels"` + ModelList []ModelConfig `json:"model_list"` + Gateway GatewayConfig `json:"gateway"` + Tools ToolsConfig `json:"tools"` +} + +type AgentsConfig struct { + Defaults AgentDefaults `json:"defaults"` + List []AgentConfig `json:"list,omitempty"` +} + +type AgentDefaults struct { + Workspace string `json:"workspace"` + RestrictToWorkspace bool `json:"restrict_to_workspace"` + Provider string `json:"provider"` + ModelName string `json:"model_name"` + Model string `json:"model,omitempty"` + ModelFallbacks []string `json:"model_fallbacks,omitempty"` + ImageModel string `json:"image_model,omitempty"` + ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"` + MaxTokens int `json:"max_tokens"` + Temperature *float64 `json:"temperature,omitempty"` + MaxToolIterations int `json:"max_tool_iterations"` +} + +type AgentConfig struct { + ID string `json:"id"` + Default bool `json:"default,omitempty"` + Name string `json:"name,omitempty"` + Workspace string `json:"workspace,omitempty"` + Model *AgentModelConfig `json:"model,omitempty"` + Skills []string `json:"skills,omitempty"` +} + +type AgentModelConfig struct { + Primary string `json:"primary,omitempty"` + Fallbacks []string `json:"fallbacks,omitempty"` +} + +type AgentBinding struct { + AgentID string `json:"agent_id"` + Match BindingMatch `json:"match"` +} + +type BindingMatch struct { + Channel string `json:"channel"` + AccountID string `json:"account_id,omitempty"` + Peer *PeerMatch `json:"peer,omitempty"` + GuildID string `json:"guild_id,omitempty"` + TeamID string `json:"team_id,omitempty"` +} + +type PeerMatch struct { + Kind string `json:"kind"` + ID string `json:"id"` +} + +type ChannelsConfig struct { + WhatsApp WhatsAppConfig `json:"whatsapp"` + Telegram TelegramConfig `json:"telegram"` + Discord DiscordConfig `json:"discord"` + MaixCam MaixCamConfig `json:"maixcam"` + QQ QQConfig `json:"qq"` + DingTalk DingTalkConfig `json:"dingtalk"` + Slack SlackConfig `json:"slack"` + Matrix MatrixConfig `json:"matrix"` + LINE LINEConfig `json:"line"` +} + +type WhatsAppConfig struct { + Enabled bool `json:"enabled"` + BridgeURL string `json:"bridge_url"` + AllowFrom []string `json:"allow_from"` +} + +type TelegramConfig struct { + Enabled bool `json:"enabled"` + Token string `json:"token"` + Proxy string `json:"proxy"` + AllowFrom []string `json:"allow_from"` +} + +type DiscordConfig struct { + Enabled bool `json:"enabled"` + Token string `json:"token"` + MentionOnly bool `json:"mention_only"` + AllowFrom []string `json:"allow_from"` +} + +type MaixCamConfig struct { + Enabled bool `json:"enabled"` + Host string `json:"host"` + Port int `json:"port"` + AllowFrom []string `json:"allow_from"` +} + +type QQConfig struct { + Enabled bool `json:"enabled"` + AppID string `json:"app_id"` + AppSecret string `json:"app_secret"` + AllowFrom []string `json:"allow_from"` +} + +type DingTalkConfig struct { + Enabled bool `json:"enabled"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + AllowFrom []string `json:"allow_from"` +} + +type SlackConfig struct { + Enabled bool `json:"enabled"` + BotToken string `json:"bot_token"` + AppToken string `json:"app_token"` + AllowFrom []string `json:"allow_from"` +} + +type MatrixConfig struct { + Enabled bool `json:"enabled"` + Homeserver string `json:"homeserver"` + UserID string `json:"user_id"` + AccessToken string `json:"access_token"` + AllowFrom []string `json:"allow_from"` +} + +type LINEConfig struct { + Enabled bool `json:"enabled"` + ChannelSecret string `json:"channel_secret"` + ChannelAccessToken string `json:"channel_access_token"` + WebhookHost string `json:"webhook_host"` + WebhookPort int `json:"webhook_port"` + WebhookPath string `json:"webhook_path"` + AllowFrom []string `json:"allow_from"` +} + +type GatewayConfig struct { + Host string `json:"host"` + Port int `json:"port"` +} + +type ToolsConfig struct { + Web WebToolsConfig `json:"web"` + Cron CronConfig `json:"cron"` + Exec ExecConfig `json:"exec"` +} + +type WebToolsConfig struct { + Brave BraveConfig `json:"brave"` + Tavily TavilyConfig `json:"tavily"` + DuckDuckGo DuckDuckGoConfig `json:"duckduckgo"` + Perplexity PerplexityConfig `json:"perplexity"` + Proxy string `json:"proxy,omitempty"` +} + +type BraveConfig struct { + Enabled bool `json:"enabled"` + APIKey string `json:"api_key"` + APIKeys []string `json:"api_keys"` + MaxResults int `json:"max_results"` +} + +type TavilyConfig struct { + Enabled bool `json:"enabled"` + APIKey string `json:"api_key"` + APIKeys []string `json:"api_keys"` + BaseURL string `json:"base_url"` + MaxResults int `json:"max_results"` +} + +type DuckDuckGoConfig struct { + Enabled bool `json:"enabled"` + MaxResults int `json:"max_results"` +} + +type PerplexityConfig struct { + Enabled bool `json:"enabled"` + APIKey string `json:"api_key"` + APIKeys []string `json:"api_keys"` + MaxResults int `json:"max_results"` +} + +type CronConfig struct { + ExecTimeoutMinutes int `json:"exec_timeout_minutes"` +} + +type ExecConfig struct { + EnableDenyPatterns bool `json:"enable_deny_patterns"` + CustomDenyPatterns []string `json:"custom_deny_patterns"` +}