From dcec1ea606a0f40a885f58b964739ed4af0ce21e Mon Sep 17 00:00:00 2001 From: mingzhi1 Date: Tue, 3 Mar 2026 12:50:38 +0800 Subject: [PATCH] feat: Wails v2 desktop launcher - 3-tab GUI: Status (gateway control), Chat (AI), Settings (config) - System tray with hide-to-tray on close - Gateway start/stop/restart with real-time status - AI chat interface through desktop GUI - Guided first-run setup when no config exists - go.mod: wails + go-winres as Go tool dependencies - Design doc: docs/design/launcher_wails_migration.md --- cmd/picoclaw-launcher/app.go | 470 +++++++ cmd/picoclaw-launcher/frontend/index.html | 1143 +++++++++++++++++ .../frontend/wailsjs/go/main/App.d.ts | 25 + .../frontend/wailsjs/go/main/App.js | 47 + .../frontend/wailsjs/go/models.ts | 107 ++ .../frontend/wailsjs/runtime/package.json | 24 + .../frontend/wailsjs/runtime/runtime.d.ts | 249 ++++ .../frontend/wailsjs/runtime/runtime.js | 242 ++++ cmd/picoclaw-launcher/helpers.go | 115 ++ .../internal/server/setup.go | 240 ++++ .../internal/server/setup_chat.go | 151 +++ cmd/picoclaw-launcher/main.go | 152 +-- cmd/picoclaw-launcher/proc_other.go | 8 + cmd/picoclaw-launcher/proc_windows.go | 12 + cmd/picoclaw-launcher/tray.go | 61 + cmd/picoclaw-launcher/wails.json | 20 + cmd/picoclaw/internal/initcmd/command.go | 280 ++++ cmd/picoclaw/internal/initcmd/command_test.go | 35 + cmd/picoclaw/internal/onboard/helpers.go | 6 +- cmd/picoclaw/main.go | 2 + docs/design/launcher_wails_migration.md | 50 + go.mod | 94 +- go.sum | 256 +++- pkg/config/config.go | 50 +- pkg/config/onboard.go | 30 + 25 files changed, 3765 insertions(+), 104 deletions(-) create mode 100644 cmd/picoclaw-launcher/app.go create mode 100644 cmd/picoclaw-launcher/frontend/index.html create mode 100644 cmd/picoclaw-launcher/frontend/wailsjs/go/main/App.d.ts create mode 100644 cmd/picoclaw-launcher/frontend/wailsjs/go/main/App.js create mode 100644 cmd/picoclaw-launcher/frontend/wailsjs/go/models.ts create mode 100644 cmd/picoclaw-launcher/frontend/wailsjs/runtime/package.json create mode 100644 cmd/picoclaw-launcher/frontend/wailsjs/runtime/runtime.d.ts create mode 100644 cmd/picoclaw-launcher/frontend/wailsjs/runtime/runtime.js create mode 100644 cmd/picoclaw-launcher/helpers.go create mode 100644 cmd/picoclaw-launcher/internal/server/setup.go create mode 100644 cmd/picoclaw-launcher/internal/server/setup_chat.go create mode 100644 cmd/picoclaw-launcher/proc_other.go create mode 100644 cmd/picoclaw-launcher/proc_windows.go create mode 100644 cmd/picoclaw-launcher/tray.go create mode 100644 cmd/picoclaw-launcher/wails.json create mode 100644 cmd/picoclaw/internal/initcmd/command.go create mode 100644 cmd/picoclaw/internal/initcmd/command_test.go create mode 100644 docs/design/launcher_wails_migration.md create mode 100644 pkg/config/onboard.go diff --git a/cmd/picoclaw-launcher/app.go b/cmd/picoclaw-launcher/app.go new file mode 100644 index 000000000..4a0417477 --- /dev/null +++ b/cmd/picoclaw-launcher/app.go @@ -0,0 +1,470 @@ +package main + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "time" + + "github.com/sipeed/picoclaw/pkg/agent" + "github.com/sipeed/picoclaw/pkg/bus" + "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/providers" +) + +// App is the main Wails application struct. +// All exported methods are automatically bound to the frontend. +type App struct { + ctx context.Context + configPath string + forceQuit bool + + // Chat state + chatMu sync.Mutex + agentLoop *agent.AgentLoop + msgBus *bus.MessageBus +} + +// NewApp creates a new App instance. +func NewApp(configPath string) *App { + return &App{configPath: configPath} +} + +// startup is called when the app starts. +func (a *App) startup(ctx context.Context) { + a.ctx = ctx + a.setupTray() +} + +// shutdown is called when the app is closing. +func (a *App) shutdown(ctx context.Context) { + a.chatMu.Lock() + defer a.chatMu.Unlock() + if a.msgBus != nil { + a.msgBus.Close() + } +} + +// ── Setup ─────────────────────────────────────────── + +// SetupStatus returns whether initial setup is needed. +type SetupStatusResult struct { + NeedsSetup bool `json:"needs_setup"` + ConfigPath string `json:"config_path"` +} + +func (a *App) GetSetupStatus() SetupStatusResult { + needsSetup := true + if cfg, err := config.LoadConfig(a.configPath); err == nil && cfg != nil { + // Config is valid if either providers or model_list has entries + needsSetup = cfg.Providers.IsEmpty() && len(cfg.ModelList) == 0 + } + return SetupStatusResult{ + NeedsSetup: needsSetup, + ConfigPath: a.configPath, + } +} + +// TestLLM tests an LLM connection without persisting anything. +type TestLLMRequest struct { + APIKey string `json:"api_key"` + APIBase string `json:"api_base"` + Model string `json:"model"` +} + +type TestLLMResult struct { + Success bool `json:"success"` + Response string `json:"response"` + Model string `json:"model"` + Protocol string `json:"protocol"` + Error string `json:"error"` +} + +func (a *App) TestLLM(req TestLLMRequest) TestLLMResult { + if req.APIKey == "" || req.Model == "" { + return TestLLMResult{Error: "API key and model are required"} + } + if req.APIBase == "" { + req.APIBase = "https://api.openai.com/v1" + } + + protocol := detectProtocol(req.APIBase) + modelID := buildModelField(protocol, req.Model) + + modelCfg := &config.ModelConfig{ + ModelName: req.Model, + Model: modelID, + APIBase: req.APIBase, + APIKey: req.APIKey, + } + + provider, resolvedModel, err := providers.CreateProviderFromConfig(modelCfg) + if err != nil { + return TestLLMResult{Error: fmt.Sprintf("Provider creation failed: %v", err)} + } + if resolvedModel == "" { + resolvedModel = req.Model + } + + ctx, cancel := context.WithTimeout(a.ctx, 30*time.Second) + defer cancel() + + resp, err := provider.Chat(ctx, []providers.Message{ + {Role: "user", Content: "Reply with exactly one word: PONG"}, + }, nil, resolvedModel, nil) + + if err != nil { + return TestLLMResult{Error: fmt.Sprintf("LLM call failed: %v", err)} + } + + return TestLLMResult{ + Success: true, + Response: strings.TrimSpace(resp.Content), + Model: resolvedModel, + Protocol: protocol, + } +} + +// SaveSetup saves a minimal config from the setup wizard. +type SaveSetupResult struct { + Success bool `json:"success"` + ConfigPath string `json:"config_path"` + Workspace string `json:"workspace"` + Error string `json:"error"` +} + +func (a *App) SaveSetup(req TestLLMRequest) SaveSetupResult { + if req.APIKey == "" || req.Model == "" { + return SaveSetupResult{Error: "API key and model are required"} + } + if req.APIBase == "" { + req.APIBase = "https://api.openai.com/v1" + } + + protocol := detectProtocol(req.APIBase) + modelID := buildModelField(protocol, req.Model) + defaults := config.DefaultConfig() + workspace := defaults.Agents.Defaults.Workspace + + cfg := &config.Config{ + Agents: config.AgentsConfig{ + Defaults: config.AgentDefaults{ + Workspace: workspace, + RestrictToWorkspace: true, + ModelName: req.Model, + MaxTokens: 32768, + MaxToolIterations: 50, + }, + }, + ModelList: []config.ModelConfig{ + {ModelName: req.Model, Model: modelID, APIBase: req.APIBase, APIKey: req.APIKey}, + }, + Gateway: defaults.Gateway, + Tools: config.ToolsConfig{ + Exec: config.ExecConfig{EnableDenyPatterns: true}, + Web: config.WebToolsConfig{ + DuckDuckGo: config.DuckDuckGoConfig{Enabled: true, MaxResults: 5}, + }, + }, + } + + os.MkdirAll(filepath.Dir(a.configPath), 0755) + os.MkdirAll(workspace, 0755) + + if err := config.SaveConfig(a.configPath, cfg); err != nil { + return SaveSetupResult{Error: fmt.Sprintf("Save failed: %v", err)} + } + + return SaveSetupResult{Success: true, ConfigPath: a.configPath, Workspace: workspace} +} + +// ── Config ────────────────────────────────────────── + +func (a *App) GetConfig() (map[string]any, error) { + data, err := os.ReadFile(a.configPath) + if err != nil { + return nil, err + } + var raw map[string]any + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + return map[string]any{"config": raw, "path": a.configPath}, nil +} + +func (a *App) SaveConfig(cfgData string) error { + // Parse into generic map for cleanup + var raw any + if err := json.Unmarshal([]byte(cfgData), &raw); err != nil { + return fmt.Errorf("invalid JSON: %w", err) + } + + // Remove empty/zero/null values + cleaned := cleanJSON(raw) + + data, err := json.MarshalIndent(cleaned, "", " ") + if err != nil { + return fmt.Errorf("format failed: %w", err) + } + + dir := filepath.Dir(a.configPath) + if err := os.MkdirAll(dir, 0o755); err != nil { + return fmt.Errorf("mkdir failed: %w", err) + } + return os.WriteFile(a.configPath, append(data, '\n'), 0o600) +} + +// cleanJSON recursively removes null, empty string, false, zero, empty object/array values. +func cleanJSON(v any) any { + switch val := v.(type) { + case map[string]any: + out := make(map[string]any) + for k, child := range val { + c := cleanJSON(child) + if !isZeroValue(c) { + out[k] = c + } + } + if len(out) == 0 { + return nil + } + return out + case []any: + var out []any + for _, child := range val { + c := cleanJSON(child) + if !isZeroValue(c) { + out = append(out, c) + } + } + if len(out) == 0 { + return nil + } + return out + default: + return v + } +} + +func isZeroValue(v any) bool { + if v == nil { + return true + } + switch val := v.(type) { + case string: + return val == "" + case map[string]any: + return len(val) == 0 + case []any: + return len(val) == 0 + } + return false +} + + +// ── Gateway Process ───────────────────────────────── + +var gatewayLogs = NewLogBuffer(500) + +type GatewayStatus struct { + Status string `json:"status"` // "running", "stopped", "error" + Model string `json:"model"` + Logs []string `json:"logs"` + Total int `json:"total"` +} + +func (a *App) GetGatewayStatus() GatewayStatus { + cfg, err := config.LoadConfig(a.configPath) + host := "127.0.0.1" + port := 18790 + if err == nil && cfg != nil { + if cfg.Gateway.Host != "" && cfg.Gateway.Host != "0.0.0.0" { + host = cfg.Gateway.Host + } + if cfg.Gateway.Port != 0 { + port = cfg.Gateway.Port + } + } + + url := fmt.Sprintf("http://%s/health", net.JoinHostPort(host, strconv.Itoa(port))) + client := &http.Client{Timeout: 2 * time.Second} + resp, err := client.Get(url) + + status := "stopped" + model := "-" + if err == nil { + defer resp.Body.Close() + if resp.StatusCode == http.StatusOK { + status = "running" + } + } + + if err == nil && cfg != nil { + model = cfg.Agents.Defaults.GetModelName() + } + + lines, total, _ := gatewayLogs.LinesSince(0) + if lines == nil { + lines = []string{} + } + + return GatewayStatus{Status: status, Model: model, Logs: lines, Total: total} +} + +// findBinary locates the picoclaw binary: same dir as launcher, PATH, or project build dir. +func findBinary(name string) (string, error) { + suffix := "" + if runtime.GOOS == "windows" { + suffix = ".exe" + } + if exe, err := os.Executable(); err == nil { + // Same directory as launcher + if p := filepath.Join(filepath.Dir(exe), name+suffix); fileExists(p) { + return p, nil + } + // Project build dir (dev mode: ../../build/) + if p := filepath.Join(filepath.Dir(exe), "..", "..", "build", name+suffix); fileExists(p) { + return p, nil + } + } + if p, err := exec.LookPath(name); err == nil { + return p, nil + } + return "", fmt.Errorf("%s not found (checked: same dir, PATH, project build)", name) +} + +func fileExists(path string) bool { + info, err := os.Stat(path) + return err == nil && !info.IsDir() +} + +func (a *App) StartGateway() (string, error) { + execPath, err := findBinary("picoclaw") + if err != nil { + return "", err + } + + cmd := exec.Command(execPath, "gateway") + hideProcessWindow(cmd) + + stdoutPipe, _ := cmd.StdoutPipe() + stderrPipe, _ := cmd.StderrPipe() + + gatewayLogs.Reset() + + if err := cmd.Start(); err != nil { + return "", fmt.Errorf("start failed: %w", err) + } + + go scanPipe(stdoutPipe) + go scanPipe(stderrPipe) + go func() { cmd.Wait() }() + + return fmt.Sprintf("Started (PID: %d)", cmd.Process.Pid), nil +} + +func scanPipe(r io.Reader) { + scanner := bufio.NewScanner(r) + scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) + for scanner.Scan() { + gatewayLogs.Append(scanner.Text()) + } +} + +func (a *App) StopGateway() (string, error) { + var err error + if runtime.GOOS == "windows" { + psCmd := `Get-WmiObject Win32_Process | Where-Object { $_.CommandLine -match 'picoclaw.*gateway' } | ForEach-Object { Stop-Process $_.ProcessId -Force }` + err = exec.Command("powershell", "-Command", psCmd).Run() + } else { + err = exec.Command("pkill", "-f", "picoclaw gateway").Run() + } + if err != nil { + return "Gateway may not be running", nil + } + return "Stopped", nil +} + +func (a *App) RestartGateway() (string, error) { + a.StopGateway() + time.Sleep(500 * time.Millisecond) + + // Reset chat agent loop so it picks up new config + a.chatMu.Lock() + a.agentLoop = nil + a.chatMu.Unlock() + + return a.StartGateway() +} + +func (a *App) GetLogs(offset int) map[string]any { + lines, total, runID := gatewayLogs.LinesSince(offset) + if lines == nil { + lines = []string{} + } + return map[string]any{"logs": lines, "total": total, "run_id": runID} +} + +// ── Chat ──────────────────────────────────────────── + +type ChatResult struct { + Success bool `json:"success"` + Response string `json:"response"` + Error string `json:"error"` +} + +func (a *App) SendChat(message string) ChatResult { + if message == "" { + return ChatResult{Error: "message is required"} + } + + a.chatMu.Lock() + defer a.chatMu.Unlock() + + // Lazy-init agent loop + if a.agentLoop == nil { + if err := a.initAgentLoop(); err != nil { + return ChatResult{Error: fmt.Sprintf("Init failed: %v", err)} + } + } + + ctx, cancel := context.WithTimeout(a.ctx, 120*time.Second) + defer cancel() + + resp, err := a.agentLoop.ProcessDirect(ctx, message, "launcher:chat") + if err != nil { + return ChatResult{Error: fmt.Sprintf("Chat error: %v", err)} + } + + return ChatResult{Success: true, Response: resp} +} + +func (a *App) initAgentLoop() error { + cfg, err := config.LoadConfig(a.configPath) + if err != nil { + return fmt.Errorf("config load failed: %w", err) + } + + provider, modelID, err := providers.CreateProvider(cfg) + if err != nil { + return fmt.Errorf("provider creation failed: %w", err) + } + if modelID != "" { + cfg.Agents.Defaults.ModelName = modelID + } + + a.msgBus = bus.NewMessageBus() + a.agentLoop = agent.NewAgentLoop(cfg, a.msgBus, provider) + return nil +} diff --git a/cmd/picoclaw-launcher/frontend/index.html b/cmd/picoclaw-launcher/frontend/index.html new file mode 100644 index 000000000..d36c42b1e --- /dev/null +++ b/cmd/picoclaw-launcher/frontend/index.html @@ -0,0 +1,1143 @@ + + + + + + + PicoClaw Launcher + + + + + + + + + + + + + +
+ + +
+ +
+
Status
+
Monitor and control PicoClaw gateway service
+
+
🚀 Gateway
+
StatusStopped
+
Model-
+
Config-
+
+ + + +
+
+
+
📝 Logs
+
No logs. Start the gateway to see + output.
+
+
+ + +
+
Chat
+
Talk to your PicoClaw AI agent
+
+
+
+ + +
+
+
+ + +
+
Settings
+
Configure models, channels, and agent identity
+
+
🤖 Models
+
+
+
+
📡 Channels
+
Telegram
+
Discord
+
Slack
+
WeCom
+
+
+
+
+
🧠 Agent Defaults
+
+
+ +
+
+
📁 Workspace
+
+
Restrict to workspace
+
Allow read outside workspace
+
+
+
+
Raw JSON Config +
+
+
+ +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/cmd/picoclaw-launcher/frontend/wailsjs/go/main/App.d.ts b/cmd/picoclaw-launcher/frontend/wailsjs/go/main/App.d.ts new file mode 100644 index 000000000..2be511719 --- /dev/null +++ b/cmd/picoclaw-launcher/frontend/wailsjs/go/main/App.d.ts @@ -0,0 +1,25 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +import {main} from '../models'; + +export function GetConfig():Promise>; + +export function GetGatewayStatus():Promise; + +export function GetLogs(arg1:number):Promise>; + +export function GetSetupStatus():Promise; + +export function RestartGateway():Promise; + +export function SaveConfig(arg1:string):Promise; + +export function SaveSetup(arg1:main.TestLLMRequest):Promise; + +export function SendChat(arg1:string):Promise; + +export function StartGateway():Promise; + +export function StopGateway():Promise; + +export function TestLLM(arg1:main.TestLLMRequest):Promise; diff --git a/cmd/picoclaw-launcher/frontend/wailsjs/go/main/App.js b/cmd/picoclaw-launcher/frontend/wailsjs/go/main/App.js new file mode 100644 index 000000000..e6366f9ef --- /dev/null +++ b/cmd/picoclaw-launcher/frontend/wailsjs/go/main/App.js @@ -0,0 +1,47 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function GetConfig() { + return window['go']['main']['App']['GetConfig'](); +} + +export function GetGatewayStatus() { + return window['go']['main']['App']['GetGatewayStatus'](); +} + +export function GetLogs(arg1) { + return window['go']['main']['App']['GetLogs'](arg1); +} + +export function GetSetupStatus() { + return window['go']['main']['App']['GetSetupStatus'](); +} + +export function RestartGateway() { + return window['go']['main']['App']['RestartGateway'](); +} + +export function SaveConfig(arg1) { + return window['go']['main']['App']['SaveConfig'](arg1); +} + +export function SaveSetup(arg1) { + return window['go']['main']['App']['SaveSetup'](arg1); +} + +export function SendChat(arg1) { + return window['go']['main']['App']['SendChat'](arg1); +} + +export function StartGateway() { + return window['go']['main']['App']['StartGateway'](); +} + +export function StopGateway() { + return window['go']['main']['App']['StopGateway'](); +} + +export function TestLLM(arg1) { + return window['go']['main']['App']['TestLLM'](arg1); +} diff --git a/cmd/picoclaw-launcher/frontend/wailsjs/go/models.ts b/cmd/picoclaw-launcher/frontend/wailsjs/go/models.ts new file mode 100644 index 000000000..aa531c152 --- /dev/null +++ b/cmd/picoclaw-launcher/frontend/wailsjs/go/models.ts @@ -0,0 +1,107 @@ +export namespace main { + + export class ChatResult { + success: boolean; + response: string; + error: string; + + static createFrom(source: any = {}) { + return new ChatResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.success = source["success"]; + this.response = source["response"]; + this.error = source["error"]; + } + } + export class GatewayStatus { + status: string; + model: string; + logs: string[]; + total: number; + + static createFrom(source: any = {}) { + return new GatewayStatus(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.status = source["status"]; + this.model = source["model"]; + this.logs = source["logs"]; + this.total = source["total"]; + } + } + export class SaveSetupResult { + success: boolean; + config_path: string; + workspace: string; + error: string; + + static createFrom(source: any = {}) { + return new SaveSetupResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.success = source["success"]; + this.config_path = source["config_path"]; + this.workspace = source["workspace"]; + this.error = source["error"]; + } + } + export class SetupStatusResult { + needs_setup: boolean; + config_path: string; + + static createFrom(source: any = {}) { + return new SetupStatusResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.needs_setup = source["needs_setup"]; + this.config_path = source["config_path"]; + } + } + export class TestLLMRequest { + api_key: string; + api_base: string; + model: string; + + static createFrom(source: any = {}) { + return new TestLLMRequest(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.api_key = source["api_key"]; + this.api_base = source["api_base"]; + this.model = source["model"]; + } + } + export class TestLLMResult { + success: boolean; + response: string; + model: string; + protocol: string; + error: string; + + static createFrom(source: any = {}) { + return new TestLLMResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.success = source["success"]; + this.response = source["response"]; + this.model = source["model"]; + this.protocol = source["protocol"]; + this.error = source["error"]; + } + } + +} + diff --git a/cmd/picoclaw-launcher/frontend/wailsjs/runtime/package.json b/cmd/picoclaw-launcher/frontend/wailsjs/runtime/package.json new file mode 100644 index 000000000..1e7c8a5d7 --- /dev/null +++ b/cmd/picoclaw-launcher/frontend/wailsjs/runtime/package.json @@ -0,0 +1,24 @@ +{ + "name": "@wailsapp/runtime", + "version": "2.0.0", + "description": "Wails Javascript runtime library", + "main": "runtime.js", + "types": "runtime.d.ts", + "scripts": { + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wailsapp/wails.git" + }, + "keywords": [ + "Wails", + "Javascript", + "Go" + ], + "author": "Lea Anthony ", + "license": "MIT", + "bugs": { + "url": "https://github.com/wailsapp/wails/issues" + }, + "homepage": "https://github.com/wailsapp/wails#readme" +} diff --git a/cmd/picoclaw-launcher/frontend/wailsjs/runtime/runtime.d.ts b/cmd/picoclaw-launcher/frontend/wailsjs/runtime/runtime.d.ts new file mode 100644 index 000000000..4445dac21 --- /dev/null +++ b/cmd/picoclaw-launcher/frontend/wailsjs/runtime/runtime.d.ts @@ -0,0 +1,249 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export interface Position { + x: number; + y: number; +} + +export interface Size { + w: number; + h: number; +} + +export interface Screen { + isCurrent: boolean; + isPrimary: boolean; + width : number + height : number +} + +// Environment information such as platform, buildtype, ... +export interface EnvironmentInfo { + buildType: string; + platform: string; + arch: string; +} + +// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) +// emits the given event. Optional data may be passed with the event. +// This will trigger any event listeners. +export function EventsEmit(eventName: string, ...data: any): void; + +// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. +export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; + +// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) +// sets up a listener for the given event name, but will only trigger a given number times. +export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; + +// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) +// sets up a listener for the given event name, but will only trigger once. +export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; + +// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) +// unregisters the listener for the given event name. +export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; + +// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) +// unregisters all listeners. +export function EventsOffAll(): void; + +// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) +// logs the given message as a raw message +export function LogPrint(message: string): void; + +// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) +// logs the given message at the `trace` log level. +export function LogTrace(message: string): void; + +// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) +// logs the given message at the `debug` log level. +export function LogDebug(message: string): void; + +// [LogError](https://wails.io/docs/reference/runtime/log#logerror) +// logs the given message at the `error` log level. +export function LogError(message: string): void; + +// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) +// logs the given message at the `fatal` log level. +// The application will quit after calling this method. +export function LogFatal(message: string): void; + +// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) +// logs the given message at the `info` log level. +export function LogInfo(message: string): void; + +// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) +// logs the given message at the `warning` log level. +export function LogWarning(message: string): void; + +// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) +// Forces a reload by the main application as well as connected browsers. +export function WindowReload(): void; + +// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) +// Reloads the application frontend. +export function WindowReloadApp(): void; + +// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) +// Sets the window AlwaysOnTop or not on top. +export function WindowSetAlwaysOnTop(b: boolean): void; + +// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) +// *Windows only* +// Sets window theme to system default (dark/light). +export function WindowSetSystemDefaultTheme(): void; + +// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) +// *Windows only* +// Sets window to light theme. +export function WindowSetLightTheme(): void; + +// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) +// *Windows only* +// Sets window to dark theme. +export function WindowSetDarkTheme(): void; + +// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) +// Centers the window on the monitor the window is currently on. +export function WindowCenter(): void; + +// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) +// Sets the text in the window title bar. +export function WindowSetTitle(title: string): void; + +// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) +// Makes the window full screen. +export function WindowFullscreen(): void; + +// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) +// Restores the previous window dimensions and position prior to full screen. +export function WindowUnfullscreen(): void; + +// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) +// Returns the state of the window, i.e. whether the window is in full screen mode or not. +export function WindowIsFullscreen(): Promise; + +// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) +// Sets the width and height of the window. +export function WindowSetSize(width: number, height: number): void; + +// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) +// Gets the width and height of the window. +export function WindowGetSize(): Promise; + +// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) +// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMaxSize(width: number, height: number): void; + +// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) +// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMinSize(width: number, height: number): void; + +// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) +// Sets the window position relative to the monitor the window is currently on. +export function WindowSetPosition(x: number, y: number): void; + +// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) +// Gets the window position relative to the monitor the window is currently on. +export function WindowGetPosition(): Promise; + +// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) +// Hides the window. +export function WindowHide(): void; + +// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) +// Shows the window, if it is currently hidden. +export function WindowShow(): void; + +// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) +// Maximises the window to fill the screen. +export function WindowMaximise(): void; + +// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) +// Toggles between Maximised and UnMaximised. +export function WindowToggleMaximise(): void; + +// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) +// Restores the window to the dimensions and position prior to maximising. +export function WindowUnmaximise(): void; + +// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) +// Returns the state of the window, i.e. whether the window is maximised or not. +export function WindowIsMaximised(): Promise; + +// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) +// Minimises the window. +export function WindowMinimise(): void; + +// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) +// Restores the window to the dimensions and position prior to minimising. +export function WindowUnminimise(): void; + +// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) +// Returns the state of the window, i.e. whether the window is minimised or not. +export function WindowIsMinimised(): Promise; + +// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) +// Returns the state of the window, i.e. whether the window is normal or not. +export function WindowIsNormal(): Promise; + +// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) +// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. +export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; + +// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) +// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. +export function ScreenGetAll(): Promise; + +// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) +// Opens the given URL in the system browser. +export function BrowserOpenURL(url: string): void; + +// [Environment](https://wails.io/docs/reference/runtime/intro#environment) +// Returns information about the environment +export function Environment(): Promise; + +// [Quit](https://wails.io/docs/reference/runtime/intro#quit) +// Quits the application. +export function Quit(): void; + +// [Hide](https://wails.io/docs/reference/runtime/intro#hide) +// Hides the application. +export function Hide(): void; + +// [Show](https://wails.io/docs/reference/runtime/intro#show) +// Shows the application. +export function Show(): void; + +// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) +// Returns the current text stored on clipboard +export function ClipboardGetText(): Promise; + +// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) +// Sets a text on the clipboard +export function ClipboardSetText(text: string): Promise; + +// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) +// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. +export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void + +// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) +// OnFileDropOff removes the drag and drop listeners and handlers. +export function OnFileDropOff() :void + +// Check if the file path resolver is available +export function CanResolveFilePaths(): boolean; + +// Resolves file paths for an array of files +export function ResolveFilePaths(files: File[]): void \ No newline at end of file diff --git a/cmd/picoclaw-launcher/frontend/wailsjs/runtime/runtime.js b/cmd/picoclaw-launcher/frontend/wailsjs/runtime/runtime.js new file mode 100644 index 000000000..7cb89d750 --- /dev/null +++ b/cmd/picoclaw-launcher/frontend/wailsjs/runtime/runtime.js @@ -0,0 +1,242 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export function LogPrint(message) { + window.runtime.LogPrint(message); +} + +export function LogTrace(message) { + window.runtime.LogTrace(message); +} + +export function LogDebug(message) { + window.runtime.LogDebug(message); +} + +export function LogInfo(message) { + window.runtime.LogInfo(message); +} + +export function LogWarning(message) { + window.runtime.LogWarning(message); +} + +export function LogError(message) { + window.runtime.LogError(message); +} + +export function LogFatal(message) { + window.runtime.LogFatal(message); +} + +export function EventsOnMultiple(eventName, callback, maxCallbacks) { + return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); +} + +export function EventsOn(eventName, callback) { + return EventsOnMultiple(eventName, callback, -1); +} + +export function EventsOff(eventName, ...additionalEventNames) { + return window.runtime.EventsOff(eventName, ...additionalEventNames); +} + +export function EventsOffAll() { + return window.runtime.EventsOffAll(); +} + +export function EventsOnce(eventName, callback) { + return EventsOnMultiple(eventName, callback, 1); +} + +export function EventsEmit(eventName) { + let args = [eventName].slice.call(arguments); + return window.runtime.EventsEmit.apply(null, args); +} + +export function WindowReload() { + window.runtime.WindowReload(); +} + +export function WindowReloadApp() { + window.runtime.WindowReloadApp(); +} + +export function WindowSetAlwaysOnTop(b) { + window.runtime.WindowSetAlwaysOnTop(b); +} + +export function WindowSetSystemDefaultTheme() { + window.runtime.WindowSetSystemDefaultTheme(); +} + +export function WindowSetLightTheme() { + window.runtime.WindowSetLightTheme(); +} + +export function WindowSetDarkTheme() { + window.runtime.WindowSetDarkTheme(); +} + +export function WindowCenter() { + window.runtime.WindowCenter(); +} + +export function WindowSetTitle(title) { + window.runtime.WindowSetTitle(title); +} + +export function WindowFullscreen() { + window.runtime.WindowFullscreen(); +} + +export function WindowUnfullscreen() { + window.runtime.WindowUnfullscreen(); +} + +export function WindowIsFullscreen() { + return window.runtime.WindowIsFullscreen(); +} + +export function WindowGetSize() { + return window.runtime.WindowGetSize(); +} + +export function WindowSetSize(width, height) { + window.runtime.WindowSetSize(width, height); +} + +export function WindowSetMaxSize(width, height) { + window.runtime.WindowSetMaxSize(width, height); +} + +export function WindowSetMinSize(width, height) { + window.runtime.WindowSetMinSize(width, height); +} + +export function WindowSetPosition(x, y) { + window.runtime.WindowSetPosition(x, y); +} + +export function WindowGetPosition() { + return window.runtime.WindowGetPosition(); +} + +export function WindowHide() { + window.runtime.WindowHide(); +} + +export function WindowShow() { + window.runtime.WindowShow(); +} + +export function WindowMaximise() { + window.runtime.WindowMaximise(); +} + +export function WindowToggleMaximise() { + window.runtime.WindowToggleMaximise(); +} + +export function WindowUnmaximise() { + window.runtime.WindowUnmaximise(); +} + +export function WindowIsMaximised() { + return window.runtime.WindowIsMaximised(); +} + +export function WindowMinimise() { + window.runtime.WindowMinimise(); +} + +export function WindowUnminimise() { + window.runtime.WindowUnminimise(); +} + +export function WindowSetBackgroundColour(R, G, B, A) { + window.runtime.WindowSetBackgroundColour(R, G, B, A); +} + +export function ScreenGetAll() { + return window.runtime.ScreenGetAll(); +} + +export function WindowIsMinimised() { + return window.runtime.WindowIsMinimised(); +} + +export function WindowIsNormal() { + return window.runtime.WindowIsNormal(); +} + +export function BrowserOpenURL(url) { + window.runtime.BrowserOpenURL(url); +} + +export function Environment() { + return window.runtime.Environment(); +} + +export function Quit() { + window.runtime.Quit(); +} + +export function Hide() { + window.runtime.Hide(); +} + +export function Show() { + window.runtime.Show(); +} + +export function ClipboardGetText() { + return window.runtime.ClipboardGetText(); +} + +export function ClipboardSetText(text) { + return window.runtime.ClipboardSetText(text); +} + +/** + * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * + * @export + * @callback OnFileDropCallback + * @param {number} x - x coordinate of the drop + * @param {number} y - y coordinate of the drop + * @param {string[]} paths - A list of file paths. + */ + +/** + * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. + * + * @export + * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) + */ +export function OnFileDrop(callback, useDropTarget) { + return window.runtime.OnFileDrop(callback, useDropTarget); +} + +/** + * OnFileDropOff removes the drag and drop listeners and handlers. + */ +export function OnFileDropOff() { + return window.runtime.OnFileDropOff(); +} + +export function CanResolveFilePaths() { + return window.runtime.CanResolveFilePaths(); +} + +export function ResolveFilePaths(files) { + return window.runtime.ResolveFilePaths(files); +} \ No newline at end of file diff --git a/cmd/picoclaw-launcher/helpers.go b/cmd/picoclaw-launcher/helpers.go new file mode 100644 index 000000000..b7bc6b816 --- /dev/null +++ b/cmd/picoclaw-launcher/helpers.go @@ -0,0 +1,115 @@ +package main + +import ( + "strings" + "sync" +) + +// LogBuffer is a thread-safe ring buffer that stores the most recent N log lines. +type LogBuffer struct { + mu sync.RWMutex + lines []string + cap int + total int + runID int +} + +func NewLogBuffer(capacity int) *LogBuffer { + return &LogBuffer{lines: make([]string, 0, capacity), cap: capacity} +} + +func (b *LogBuffer) Append(line string) { + b.mu.Lock() + defer b.mu.Unlock() + if len(b.lines) < b.cap { + b.lines = append(b.lines, line) + } else { + b.lines[b.total%b.cap] = line + } + b.total++ +} + +func (b *LogBuffer) Reset() { + b.mu.Lock() + defer b.mu.Unlock() + b.lines = b.lines[:0] + b.total = 0 + b.runID++ +} + +func (b *LogBuffer) LinesSince(offset int) (lines []string, total int, runID int) { + b.mu.RLock() + defer b.mu.RUnlock() + total = b.total + runID = b.runID + if offset >= b.total { + return nil, total, runID + } + buffered := len(b.lines) + newCount := b.total - offset + if newCount > buffered { + newCount = buffered + } + result := make([]string, newCount) + if b.total <= b.cap { + copy(result, b.lines[buffered-newCount:]) + } else { + start := (b.total - newCount) % b.cap + for i := range newCount { + result[i] = b.lines[(start+i)%b.cap] + } + } + return result, total, runID +} + +// buildModelField constructs the "protocol/model" string for config. +// If the model already starts with the detected protocol prefix, it is returned as-is. +// Otherwise the protocol prefix is prepended. +// E.g. buildModelField("nvidia", "nvidia/minimaxai/minimax-m2.5") → "nvidia/minimaxai/minimax-m2.5" +// +// buildModelField("openai", "gpt-4o") → "openai/gpt-4o" +func buildModelField(protocol, model string) string { + if strings.HasPrefix(model, protocol+"/") { + return model + } + return protocol + "/" + model +} + +// detectProtocol guesses the provider protocol from the API base URL. +func detectProtocol(baseURL string) string { + lower := strings.ToLower(baseURL) + switch { + case strings.Contains(lower, "anthropic"): + return "anthropic" + case strings.Contains(lower, "googleapis") || strings.Contains(lower, "generativelanguage"): + return "gemini" + case strings.Contains(lower, "openrouter"): + return "openrouter" + case strings.Contains(lower, "nvidia") || strings.Contains(lower, "integrate.api"): + return "nvidia" + case strings.Contains(lower, "deepseek"): + return "deepseek" + case strings.Contains(lower, "groq"): + return "groq" + case strings.Contains(lower, "bigmodel.cn") || strings.Contains(lower, "zhipu"): + return "zhipu" + case strings.Contains(lower, "moonshot"): + return "moonshot" + case strings.Contains(lower, "dashscope") || strings.Contains(lower, "aliyun"): + return "qwen" + case strings.Contains(lower, "cerebras"): + return "cerebras" + case strings.Contains(lower, "volces.com") || strings.Contains(lower, "volcengine"): + return "volcengine" + case strings.Contains(lower, "shengsuanyun"): + return "shengsuanyun" + case strings.Contains(lower, "mistral"): + return "mistral" + case strings.Contains(lower, "localhost:11434") || strings.Contains(lower, "ollama"): + return "ollama" + case strings.Contains(lower, "localhost:8000"): + return "vllm" + default: + return "openai" + } +} diff --git a/cmd/picoclaw-launcher/internal/server/setup.go b/cmd/picoclaw-launcher/internal/server/setup.go new file mode 100644 index 000000000..662b5bb0a --- /dev/null +++ b/cmd/picoclaw-launcher/internal/server/setup.go @@ -0,0 +1,240 @@ +package server + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/providers" +) + +// RegisterSetupAPI registers endpoints for the initial setup flow. +// These are used when the user hasn't run `picoclaw init` yet. +func RegisterSetupAPI(mux *http.ServeMux, absPath string) { + mux.HandleFunc("GET /api/setup/status", func(w http.ResponseWriter, r *http.Request) { + handleSetupStatus(w, absPath) + }) + mux.HandleFunc("POST /api/setup/test-llm", func(w http.ResponseWriter, r *http.Request) { + handleTestLLM(w, r) + }) + mux.HandleFunc("POST /api/setup/save", func(w http.ResponseWriter, r *http.Request) { + handleSetupSave(w, r, absPath) + }) +} + +// NeedsSetup returns true if config is missing or has no usable LLM configured. +func NeedsSetup(absPath string) bool { + if _, err := os.Stat(absPath); os.IsNotExist(err) { + return true + } + cfg, err := config.LoadConfig(absPath) + if err != nil { + return true + } + if cfg.Agents.Defaults.GetModelName() == "" { + return true + } + if len(cfg.ModelList) == 0 && cfg.Providers.IsEmpty() { + return true + } + return false +} + +// handleSetupStatus returns whether initial setup is needed. +func handleSetupStatus(w http.ResponseWriter, absPath string) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]any{ + "needs_setup": NeedsSetup(absPath), + "config_path": absPath, + }) +} + +// setupTestRequest is the request body for POST /api/setup/test-llm. +type setupTestRequest struct { + APIKey string `json:"api_key"` + APIBase string `json:"api_base"` + Model string `json:"model"` +} + +// handleTestLLM tests an LLM connection without saving config. +func handleTestLLM(w http.ResponseWriter, r *http.Request) { + var req setupTestRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + if req.APIKey == "" || req.Model == "" { + http.Error(w, "api_key and model are required", http.StatusBadRequest) + return + } + if req.APIBase == "" { + req.APIBase = "https://api.openai.com/v1" + } + + protocol := DetectProtocol(req.APIBase) + modelID := protocol + "/" + req.Model + + modelCfg := &config.ModelConfig{ + ModelName: req.Model, + Model: modelID, + APIBase: req.APIBase, + APIKey: req.APIKey, + } + + provider, resolvedModel, err := providers.CreateProviderFromConfig(modelCfg) + if err != nil { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]any{ + "success": false, + "error": fmt.Sprintf("Failed to create provider: %v", err), + }) + return + } + + if resolvedModel == "" { + resolvedModel = req.Model + } + + ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second) + defer cancel() + + resp, err := provider.Chat(ctx, []providers.Message{ + {Role: "user", Content: "Reply with exactly one word: PONG"}, + }, nil, resolvedModel, nil) + + if err != nil { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]any{ + "success": false, + "error": fmt.Sprintf("LLM call failed: %v", err), + }) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]any{ + "success": true, + "response": strings.TrimSpace(resp.Content), + "model": resolvedModel, + "protocol": protocol, + }) +} + +// setupSaveRequest is the request body for POST /api/setup/save. +type setupSaveRequest struct { + APIKey string `json:"api_key"` + APIBase string `json:"api_base"` + Model string `json:"model"` +} + +// handleSetupSave saves a minimal config from the setup form. +func handleSetupSave(w http.ResponseWriter, r *http.Request, absPath string) { + var req setupSaveRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + if req.APIKey == "" || req.Model == "" { + http.Error(w, "api_key and model are required", http.StatusBadRequest) + return + } + if req.APIBase == "" { + req.APIBase = "https://api.openai.com/v1" + } + + protocol := DetectProtocol(req.APIBase) + modelID := protocol + "/" + req.Model + + defaults := config.DefaultConfig() + workspace := defaults.Agents.Defaults.Workspace + + cfg := &config.Config{ + Agents: config.AgentsConfig{ + Defaults: config.AgentDefaults{ + Workspace: workspace, + RestrictToWorkspace: true, + ModelName: req.Model, + MaxTokens: 32768, + MaxToolIterations: 50, + }, + }, + ModelList: []config.ModelConfig{ + { + ModelName: req.Model, + Model: modelID, + APIBase: req.APIBase, + APIKey: req.APIKey, + }, + }, + Gateway: defaults.Gateway, + Tools: config.ToolsConfig{ + Exec: config.ExecConfig{EnableDenyPatterns: true}, + Web: config.WebToolsConfig{ + DuckDuckGo: config.DuckDuckGoConfig{Enabled: true, MaxResults: 5}, + }, + }, + Providers: config.ProvidersConfig{ + OpenAI: config.OpenAIProviderConfig{WebSearch: true}, + }, + } + + // Ensure directories exist. + os.MkdirAll(filepath.Dir(absPath), 0755) + os.MkdirAll(workspace, 0755) + + if err := config.SaveConfig(absPath, cfg); err != nil { + http.Error(w, fmt.Sprintf("Failed to save config: %v", err), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]any{ + "success": true, + "config_path": absPath, + "workspace": workspace, + }) +} + +// DetectProtocol guesses the provider protocol from the API base URL. +func DetectProtocol(baseURL string) string { + lower := strings.ToLower(baseURL) + switch { + case strings.Contains(lower, "anthropic"): + return "anthropic" + case strings.Contains(lower, "generativelanguage.googleapis"): + return "gemini" + case strings.Contains(lower, "dashscope.aliyuncs"): + return "qwen" + case strings.Contains(lower, "open.bigmodel.cn"): + return "zhipu" + case strings.Contains(lower, "moonshot"): + return "moonshot" + case strings.Contains(lower, "deepseek"): + return "deepseek" + case strings.Contains(lower, "openrouter"): + return "openrouter" + case strings.Contains(lower, "groq"): + return "groq" + case strings.Contains(lower, "localhost:11434"): + return "ollama" + case strings.Contains(lower, "volcengine") || strings.Contains(lower, "volces.com"): + return "volcengine" + case strings.Contains(lower, "cerebras"): + return "cerebras" + case strings.Contains(lower, "nvidia") || strings.Contains(lower, "integrate.api"): + return "nvidia" + case strings.Contains(lower, "mistral"): + return "mistral" + default: + return "openai" + } +} diff --git a/cmd/picoclaw-launcher/internal/server/setup_chat.go b/cmd/picoclaw-launcher/internal/server/setup_chat.go new file mode 100644 index 000000000..7909c7c25 --- /dev/null +++ b/cmd/picoclaw-launcher/internal/server/setup_chat.go @@ -0,0 +1,151 @@ +package server + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "sync" + "time" + + "github.com/sipeed/picoclaw/pkg/agent" + "github.com/sipeed/picoclaw/pkg/bus" + "github.com/sipeed/picoclaw/pkg/config" + "github.com/sipeed/picoclaw/pkg/providers" +) + +// setupChat holds the agent loop used for AI-guided configuration. +type setupChat struct { + mu sync.Mutex + agentLoop *agent.AgentLoop + msgBus *bus.MessageBus + provider providers.LLMProvider +} + +var ( + activeSetupChat *setupChat + activeSetupChatMu sync.Mutex +) + +// RegisterChatAPI registers the AI-guided configuration chat endpoint. +func RegisterChatAPI(mux *http.ServeMux, absPath string) { + mux.HandleFunc("POST /api/setup/chat", func(w http.ResponseWriter, r *http.Request) { + handleSetupChat(w, r, absPath) + }) +} + +type chatRequest struct { + Message string `json:"message"` +} + +type chatResponse struct { + Success bool `json:"success"` + Response string `json:"response,omitempty"` + Error string `json:"error,omitempty"` +} + +func handleSetupChat(w http.ResponseWriter, r *http.Request, absPath string) { + var req chatRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + if req.Message == "" { + http.Error(w, "message is required", http.StatusBadRequest) + return + } + + sc, err := getOrCreateSetupChat(absPath) + if err != nil { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(chatResponse{ + Success: false, + Error: fmt.Sprintf("Failed to initialize chat: %v", err), + }) + return + } + + sc.mu.Lock() + defer sc.mu.Unlock() + + ctx, cancel := context.WithTimeout(r.Context(), 120*time.Second) + defer cancel() + + resp, err := sc.agentLoop.ProcessDirect(ctx, req.Message, "cli:setup") + if err != nil { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(chatResponse{ + Success: false, + Error: fmt.Sprintf("Chat error: %v", err), + }) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(chatResponse{ + Success: true, + Response: resp, + }) +} + +func getOrCreateSetupChat(absPath string) (*setupChat, error) { + activeSetupChatMu.Lock() + defer activeSetupChatMu.Unlock() + + if activeSetupChat != nil { + return activeSetupChat, nil + } + + cfg, err := config.LoadConfig(absPath) + if err != nil { + return nil, fmt.Errorf("config load failed: %w", err) + } + + provider, modelID, err := providers.CreateProvider(cfg) + if err != nil { + return nil, fmt.Errorf("provider creation failed: %w", err) + } + if modelID != "" { + cfg.Agents.Defaults.ModelName = modelID + } + + msgBus := bus.NewMessageBus() + agentLoop := agent.NewAgentLoop(cfg, msgBus, provider) + + // Prime the agent with a setup-assistant system context via first message. + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + setupPrompt := buildChatSetupPrompt(absPath, cfg) + _, _ = agentLoop.ProcessDirect(ctx, setupPrompt, "cli:setup") + + activeSetupChat = &setupChat{ + agentLoop: agentLoop, + msgBus: msgBus, + provider: provider, + } + + return activeSetupChat, nil +} + +func buildChatSetupPrompt(configPath string, cfg *config.Config) string { + return fmt.Sprintf(`You are PicoClaw's setup assistant. The user just completed initial API setup. +Config file: %s +Current model: %s + +Your role is to help them configure PicoClaw step by step: + +1. **Communication Channels** — Telegram bot, Discord bot, WeChat, Slack, etc. + Ask which channels they want and guide them to get bot tokens. + +2. **Agent Identity** — Help create/edit SOUL.md (personality), IDENTITY.md (name/description), USER.md (user preferences) in the workspace. + +3. **Tools & Skills** — web search, MCP servers, custom skills. + +4. **Advanced settings** — scheduling, cron jobs, memory tuning. + +You can read and modify the config file using your file tools. +Start by welcoming the user and asking what they'd like to set up. +Keep responses concise. Use Chinese if the user writes in Chinese.`, configPath, cfg.Agents.Defaults.GetModelName()) +} diff --git a/cmd/picoclaw-launcher/main.go b/cmd/picoclaw-launcher/main.go index 3323c31a8..fdf272740 100644 --- a/cmd/picoclaw-launcher/main.go +++ b/cmd/picoclaw-launcher/main.go @@ -1,127 +1,97 @@ -// PicoClaw Launcher - Standalone HTTP service +// PicoClaw Launcher - Desktop GUI for PicoClaw AI Agent // -// Provides a web-based JSON editor for picoclaw config files, -// with OAuth provider authentication support. +// A Wails v2 desktop application that provides: +// - Service status monitoring and control +// - AI chat interface +// - Configuration editor // // Usage: // -// go build -o picoclaw-launcher ./cmd/picoclaw-launcher/ +// wails build -o picoclaw-launcher.exe // ./picoclaw-launcher [config.json] -// ./picoclaw-launcher -public config.json package main import ( + "context" "embed" "flag" "fmt" - "io/fs" - "log" - "net/http" "os" - "os/exec" "path/filepath" - "runtime" - "time" - "github.com/sipeed/picoclaw/cmd/picoclaw-launcher/internal/server" + "github.com/wailsapp/wails/v2" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" + "github.com/wailsapp/wails/v2/pkg/options/windows" + wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime" ) -//go:embed internal/ui/index.html -var staticFiles embed.FS +//go:embed all:frontend +var assets embed.FS + +func defaultConfigPath() string { + if p := os.Getenv("PICOCLAW_CONFIG"); p != "" { + return p + } + home, _ := os.UserHomeDir() + return filepath.Join(home, ".picoclaw", "config.json") +} func main() { - public := flag.Bool("public", false, "Listen on all interfaces (0.0.0.0) instead of localhost only") flag.Usage = func() { - fmt.Fprintf(os.Stderr, "PicoClaw Launcher - A web-based configuration editor\n\n") - fmt.Fprintf(os.Stderr, "Usage: %s [options] [config.json]\n\n", os.Args[0]) - fmt.Fprintf(os.Stderr, "Arguments:\n") - fmt.Fprintf(os.Stderr, " config.json Path to the configuration file (default: ~/.picoclaw/config.json)\n\n") - fmt.Fprintf(os.Stderr, "Options:\n") - flag.PrintDefaults() - fmt.Fprintf(os.Stderr, "\nExamples:\n") - fmt.Fprintf(os.Stderr, " %s Use default config path\n", os.Args[0]) - fmt.Fprintf(os.Stderr, " %s ./config.json Specify a config file\n", os.Args[0]) - fmt.Fprintf( - os.Stderr, - " %s -public ./config.json Allow access from other devices on the network\n", - os.Args[0], - ) + fmt.Fprintf(os.Stderr, "PicoClaw Launcher - Desktop GUI for PicoClaw AI Agent\n\n") + fmt.Fprintf(os.Stderr, "Usage: %s [config.json]\n", os.Args[0]) } flag.Parse() - configPath := server.DefaultConfigPath() + configPath := defaultConfigPath() if flag.NArg() > 0 { configPath = flag.Arg(0) } absPath, err := filepath.Abs(configPath) if err != nil { - log.Fatalf("Failed to resolve config path: %v", err) + fmt.Fprintf(os.Stderr, "Failed to resolve config path: %v\n", err) + os.Exit(1) } - var addr string - if *public { - addr = "0.0.0.0:" + server.DefaultPort - } else { - addr = "127.0.0.1:" + server.DefaultPort - } + app := NewApp(absPath) - mux := http.NewServeMux() - server.RegisterConfigAPI(mux, absPath) - server.RegisterAuthAPI(mux, absPath) - server.RegisterProcessAPI(mux, absPath) + err = wails.Run(&options.App{ + Title: "PicoClaw Launcher", + Width: 960, + Height: 640, + MinWidth: 720, + MinHeight: 480, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + OnStartup: app.startup, + OnShutdown: app.shutdown, + OnBeforeClose: func(ctx context.Context) (prevent bool) { + if app.forceQuit { + return false // allow quit + } + // Hide to tray instead of quitting + wailsRuntime.WindowHide(ctx) + return true + }, + Bind: []interface{}{ + app, + }, + Windows: &windows.Options{ + WebviewIsTransparent: false, + WindowIsTranslucent: false, + DisableWindowIcon: false, + DisableFramelessWindowDecorations: false, + WebviewUserDataPath: "", + Theme: windows.SystemDefault, + }, + }) - staticFS, err := fs.Sub(staticFiles, "internal/ui") if err != nil { - log.Fatalf("Failed to create sub filesystem: %v", err) - } - mux.Handle("/", http.FileServer(http.FS(staticFS))) - - // Print startup banner - fmt.Println("=============================================") - fmt.Println(" PicoClaw Launcher") - fmt.Println("=============================================") - fmt.Printf(" Config file : %s\n", absPath) - fmt.Printf(" Listen addr : %s\n\n", addr) - fmt.Println(" Open the following URL in your browser") - fmt.Println(" to view and edit the configuration:") - fmt.Println() - fmt.Printf(" >> http://localhost:%s <<\n", server.DefaultPort) - if *public { - if ip := server.GetLocalIP(); ip != "" { - fmt.Printf(" >> http://%s:%s <<\n", ip, server.DefaultPort) - } - } - fmt.Println() - // fmt.Println("=============================================") - - go func() { - // Wait briefly to ensure the server is ready before opening the browser - time.Sleep(500 * time.Millisecond) - url := "http://localhost:" + server.DefaultPort - if err := openBrowser(url); err != nil { - log.Printf("Warning: Failed to auto-open browser: %v\n", err) - } - }() - - if err := http.ListenAndServe(addr, mux); err != nil { - log.Fatalf("Server failed: %v", err) + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) } } - -// openBrowser automatically opens the given URL in the default browser. -func openBrowser(url string) error { - var err error - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", url).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() - case "darwin": - err = exec.Command("open", url).Start() - default: - err = fmt.Errorf("unsupported platform") - } - return err -} diff --git a/cmd/picoclaw-launcher/proc_other.go b/cmd/picoclaw-launcher/proc_other.go new file mode 100644 index 000000000..a7f122984 --- /dev/null +++ b/cmd/picoclaw-launcher/proc_other.go @@ -0,0 +1,8 @@ +//go:build !windows + +package main + +import "os/exec" + +// hideProcessWindow is a no-op on non-Windows platforms. +func hideProcessWindow(cmd *exec.Cmd) {} diff --git a/cmd/picoclaw-launcher/proc_windows.go b/cmd/picoclaw-launcher/proc_windows.go new file mode 100644 index 000000000..51064d64a --- /dev/null +++ b/cmd/picoclaw-launcher/proc_windows.go @@ -0,0 +1,12 @@ +package main + +import ( + "os/exec" + "syscall" +) + +// hideProcessWindow sets CREATE_NO_WINDOW on the process so no console +// window appears and no taskbar entry is created. +func hideProcessWindow(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} +} diff --git a/cmd/picoclaw-launcher/tray.go b/cmd/picoclaw-launcher/tray.go new file mode 100644 index 000000000..2c24b90f9 --- /dev/null +++ b/cmd/picoclaw-launcher/tray.go @@ -0,0 +1,61 @@ +package main + +import ( + _ "embed" + + "github.com/energye/systray" + wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime" +) + +//go:embed icon.ico +var appIcon []byte + +// setupTray initializes the system tray icon and menu. +// Uses energye/systray which works alongside Wails without thread conflicts. +func (a *App) setupTray() { + go systray.Run(func() { + // onReady + systray.SetIcon(appIcon) + systray.SetTitle("PicoClaw") + systray.SetTooltip("PicoClaw Launcher") + + // Left click → show window + systray.SetOnClick(func(menu systray.IMenu) { + wailsRuntime.WindowShow(a.ctx) + }) + + // Right click → show context menu + systray.SetOnRClick(func(menu systray.IMenu) { + menu.ShowMenu() + }) + + mShow := systray.AddMenuItem("Show Window", "Show the launcher window") + mShow.Click(func() { + wailsRuntime.WindowShow(a.ctx) + }) + + systray.AddSeparator() + + mStart := systray.AddMenuItem("Start Gateway", "Start PicoClaw gateway service") + mStart.Click(func() { + a.StartGateway() + }) + + mStop := systray.AddMenuItem("Stop Gateway", "Stop PicoClaw gateway service") + mStop.Click(func() { + a.StopGateway() + }) + + systray.AddSeparator() + + mQuit := systray.AddMenuItem("Exit", "Quit PicoClaw Launcher") + mQuit.Click(func() { + a.StopGateway() + a.forceQuit = true + systray.Quit() + wailsRuntime.Quit(a.ctx) + }) + }, func() { + // onExit - cleanup + }) +} diff --git a/cmd/picoclaw-launcher/wails.json b/cmd/picoclaw-launcher/wails.json new file mode 100644 index 000000000..d8dde2469 --- /dev/null +++ b/cmd/picoclaw-launcher/wails.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://wails.io/schemas/config.v2.json", + "name": "picoclaw-launcher", + "outputfilename": "picoclaw-launcher", + "frontend:install": "", + "frontend:build": "", + "frontend:dev:watcher": "", + "frontend:dev:serverUrl": "", + "author": { + "name": "PicoClaw", + "email": "picoclaw@sipeed.com" + }, + "info": { + "companyName": "Sipeed", + "productName": "PicoClaw Launcher", + "productVersion": "0.1.0", + "copyright": "Copyright 2026 PicoClaw contributors", + "comments": "PicoClaw Launcher - Desktop GUI for PicoClaw AI Agent" + } +} \ No newline at end of file diff --git a/cmd/picoclaw/internal/initcmd/command.go b/cmd/picoclaw/internal/initcmd/command.go new file mode 100644 index 000000000..d6f36a8d6 --- /dev/null +++ b/cmd/picoclaw/internal/initcmd/command.go @@ -0,0 +1,280 @@ +package initcmd + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + + "github.com/sipeed/picoclaw/cmd/picoclaw/internal" + "github.com/sipeed/picoclaw/pkg/config" +) + +func NewInitCommand() *cobra.Command { + var baseURL, apiKey, model string + + cmd := &cobra.Command{ + Use: "init [auth ]", + Short: "Quick setup 鈥?API key or OAuth login", + Long: `Initialize picoclaw with minimal configuration. + +Two modes: + + 1. API Key mode (most providers): + picoclaw init --api-key --model [--base-url ] + + 2. OAuth mode (OpenAI, Google Antigravity): + picoclaw init auth openai + picoclaw init auth google-antigravity + picoclaw init auth anthropic (paste token) + +In API Key mode, only api-key is required. Model defaults to gpt-4o, +base-url defaults to https://api.openai.com/v1.`, + Example: ` picoclaw init --api-key sk-xxx --model gpt-4o + picoclaw init --base-url https://api.deepseek.com/v1 --api-key sk-xxx --model deepseek-chat + picoclaw init auth openai + picoclaw init (interactive)`, + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + ensureConfigDir() + runInit(cmd, baseURL, apiKey, model) + }, + } + + cmd.Flags().StringVar(&baseURL, "base-url", "", "API base URL (default: https://api.openai.com/v1)") + cmd.Flags().StringVar(&apiKey, "api-key", "", "API key") + cmd.Flags().StringVar(&model, "model", "", "Model name") + + // Add auth subcommand. + cmd.AddCommand(newInitAuthCommand()) + + return cmd +} + +func newInitAuthCommand() *cobra.Command { + return &cobra.Command{ + Use: "auth ", + Short: "Initialize via OAuth or token (openai, anthropic, google-antigravity)", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + ensureConfigDir() + return runInitAuth(args[0]) + }, + } +} + +func ensureConfigDir() { + configPath := internal.GetConfigPath() + dir := filepath.Dir(configPath) + os.MkdirAll(dir, 0755) +} + +func runInit(cmd *cobra.Command, baseURL, apiKey, model string) { + reader := bufio.NewReader(os.Stdin) + + if apiKey == "" { + fmt.Print("API Key: ") + apiKey, _ = reader.ReadString('\n') + apiKey = strings.TrimSpace(apiKey) + } + if apiKey == "" { + fmt.Println("API key is required.") + fmt.Println(" Or use OAuth: picoclaw init auth openai") + os.Exit(1) + } + + if model == "" { + fmt.Print("Model (default: gpt-4o): ") + model, _ = reader.ReadString('\n') + model = strings.TrimSpace(model) + if model == "" { + model = "gpt-4o" + } + } + + if baseURL == "" { + fmt.Print("API Base URL (default: https://api.openai.com/v1): ") + baseURL, _ = reader.ReadString('\n') + baseURL = strings.TrimSpace(baseURL) + if baseURL == "" { + baseURL = "https://api.openai.com/v1" + } + } + + protocol := detectProtocol(baseURL) + modelID := protocol + "/" + model + + saveAndPrint(cmd, model, modelID, baseURL, apiKey) +} + +func runInitAuth(provider string) error { + switch provider { + case "openai", "anthropic", "google-antigravity", "antigravity": + // Ensure base config exists before auth writes to it. + configPath := internal.GetConfigPath() + if _, err := os.Stat(configPath); os.IsNotExist(err) { + // Create minimal base config so auth can append to it. + defaults := config.DefaultConfig() + cfg := &config.Config{ + Agents: config.AgentsConfig{ + Defaults: config.AgentDefaults{ + Workspace: defaults.Agents.Defaults.Workspace, + RestrictToWorkspace: true, + MaxTokens: 32768, + MaxToolIterations: 50, + }, + }, + Gateway: defaults.Gateway, + Tools: config.ToolsConfig{ + Exec: config.ExecConfig{EnableDenyPatterns: true}, + Web: config.WebToolsConfig{ + DuckDuckGo: config.DuckDuckGoConfig{Enabled: true, MaxResults: 5}, + }, + }, + } + os.MkdirAll(defaults.Agents.Defaults.Workspace, 0755) + if err := config.SaveConfig(configPath, cfg); err != nil { + return fmt.Errorf("failed to create base config: %w", err) + } + fmt.Printf("Created base config at %s\n\n", configPath) + } + + // Delegate to the existing auth command logic. + fmt.Printf("Run: picoclaw auth login --provider %s\n", provider) + fmt.Println("This will open a browser or prompt for your token.") + return nil + default: + return fmt.Errorf("unsupported auth provider: %s\nSupported: openai, anthropic, google-antigravity", provider) + } +} + + +func saveAndPrint(cmd *cobra.Command, model, modelID, baseURL, apiKey string) { + configPath := internal.GetConfigPath() + + if _, err := os.Stat(configPath); err == nil { + fmt.Printf("Config exists at %s. Overwrite? (y/n): ", configPath) + var resp string + fmt.Scanln(&resp) + if resp != "y" { + fmt.Println("Aborted.") + return + } + } + + defaults := config.DefaultConfig() + workspace := defaults.Agents.Defaults.Workspace + + cfgMap := map[string]any{ + "agents": map[string]any{ + "defaults": map[string]any{ + "workspace": workspace, + "restrict_to_workspace": true, + "model_name": model, + "max_tokens": 32768, + "max_tool_iterations": 50, + }, + }, + "model_list": []map[string]any{ + { + "model_name": model, + "model": modelID, + "api_base": baseURL, + "api_key": apiKey, + }, + }, + "gateway": map[string]any{ + "host": defaults.Gateway.Host, + "port": defaults.Gateway.Port, + }, + "tools": map[string]any{ + "exec": map[string]any{"enable_deny_patterns": true}, + }, + } + + data, err := json.MarshalIndent(cfgMap, "", " ") + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + os.MkdirAll(filepath.Dir(configPath), 0755) + if err := os.WriteFile(configPath, data, 0600); err != nil { + fmt.Printf("Error writing config: %v\n", err) + os.Exit(1) + } + + os.MkdirAll(workspace, 0755) + + fmt.Printf("\n%s picoclaw is ready!\n\n", internal.Logo) + fmt.Printf(" Config: %s\n", configPath) + fmt.Printf(" Model: %s\n", model) + fmt.Printf(" API Base: %s\n", baseURL) + + // Test via cobra root command (in-process). + fmt.Println("\n Testing: picoclaw agent -m \"Hello!\"") + fmt.Println(strings.Repeat("\u2500", 50)) + rootCmd := cmd.Root() + rootCmd.SetArgs([]string{"agent", "-m", "Hello!"}) + if err := rootCmd.Execute(); err != nil { + fmt.Println(strings.Repeat("\u2500", 50)) + fmt.Printf(" Test FAILED: %v\n", err) + fmt.Println(" Possible fixes:") + fmt.Println(" - Check your API key") + fmt.Println(" - Check the API base URL") + fmt.Printf(" - Edit: %s\n", configPath) + } else { + fmt.Println(strings.Repeat("\u2500", 50)) + fmt.Println(" Test OK!") + } + + // Next steps. + fmt.Println("\n Quick Start:") + fmt.Println(" picoclaw agent -m \"Hello!\" # send a message") + fmt.Println("") + fmt.Println(" Add Channels (Telegram, Discord, etc):") + fmt.Printf(" Edit %s\n", configPath) + fmt.Println(" picoclaw gateway # start multi-channel server") + fmt.Println("") + fmt.Println(" Docs: https://github.com/sipeed/picoclaw") + fmt.Println(strings.Repeat("\u2500", 50)) +} + +// detectProtocol guesses the provider protocol from the API base URL. +func detectProtocol(baseURL string) string { + lower := strings.ToLower(baseURL) + switch { + case strings.Contains(lower, "anthropic"): + return "anthropic" + case strings.Contains(lower, "generativelanguage.googleapis"): + return "gemini" + case strings.Contains(lower, "dashscope.aliyuncs"): + return "qwen" + case strings.Contains(lower, "open.bigmodel.cn"): + return "zhipu" + case strings.Contains(lower, "moonshot"): + return "moonshot" + case strings.Contains(lower, "deepseek"): + return "deepseek" + case strings.Contains(lower, "openrouter"): + return "openrouter" + case strings.Contains(lower, "groq"): + return "groq" + case strings.Contains(lower, "localhost:11434"): + return "ollama" + case strings.Contains(lower, "volcengine") || strings.Contains(lower, "volces.com"): + return "volcengine" + case strings.Contains(lower, "cerebras"): + return "cerebras" + case strings.Contains(lower, "nvidia") || strings.Contains(lower, "integrate.api"): + return "nvidia" + case strings.Contains(lower, "mistral"): + return "mistral" + default: + return "openai" + } +} \ No newline at end of file diff --git a/cmd/picoclaw/internal/initcmd/command_test.go b/cmd/picoclaw/internal/initcmd/command_test.go new file mode 100644 index 000000000..ace36f643 --- /dev/null +++ b/cmd/picoclaw/internal/initcmd/command_test.go @@ -0,0 +1,35 @@ +package initcmd + +import ( + "testing" +) + +func TestDetectProtocol(t *testing.T) { + tests := []struct { + url string + expected string + }{ + {"https://api.openai.com/v1", "openai"}, + {"https://api.anthropic.com/v1", "anthropic"}, + {"https://api.deepseek.com/v1", "deepseek"}, + {"https://generativelanguage.googleapis.com/v1beta", "gemini"}, + {"https://dashscope.aliyuncs.com/compatible-mode/v1", "qwen"}, + {"https://open.bigmodel.cn/api/paas/v4", "zhipu"}, + {"https://api.moonshot.cn/v1", "moonshot"}, + {"https://openrouter.ai/api/v1", "openrouter"}, + {"https://api.groq.com/openai/v1", "groq"}, + {"http://localhost:11434/v1", "ollama"}, + {"https://api.mistral.ai/v1", "mistral"}, + {"https://api.cerebras.ai/v1", "cerebras"}, + {"https://integrate.api.nvidia.com/v1", "nvidia"}, + {"https://ark.cn-beijing.volces.com/api/v3", "volcengine"}, + {"https://some-custom-endpoint.com/v1", "openai"}, // default + } + + for _, tt := range tests { + got := detectProtocol(tt.url) + if got != tt.expected { + t.Errorf("detectProtocol(%q) = %q, want %q", tt.url, got, tt.expected) + } + } +} diff --git a/cmd/picoclaw/internal/onboard/helpers.go b/cmd/picoclaw/internal/onboard/helpers.go index 4db8bdc8b..911af1bb4 100644 --- a/cmd/picoclaw/internal/onboard/helpers.go +++ b/cmd/picoclaw/internal/onboard/helpers.go @@ -25,7 +25,11 @@ func onboard() { } cfg := config.DefaultConfig() - if err := config.SaveConfig(configPath, cfg); err != nil { + + // For onboard, produce a minimal config — omit empty sections. + minimalCfg := config.MinimalOnboardConfig(cfg) + + if err := config.SaveConfig(configPath, minimalCfg); err != nil { fmt.Printf("Error saving config: %v\n", err) os.Exit(1) } diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 6db69c990..bdc011e63 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -17,6 +17,7 @@ import ( "github.com/sipeed/picoclaw/cmd/picoclaw/internal/auth" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/cron" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/gateway" + "github.com/sipeed/picoclaw/cmd/picoclaw/internal/initcmd" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/migrate" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/onboard" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/skills" @@ -34,6 +35,7 @@ func NewPicoclawCommand() *cobra.Command { } cmd.AddCommand( + initcmd.NewInitCommand(), onboard.NewOnboardCommand(), agent.NewAgentCommand(), auth.NewAuthCommand(), diff --git a/docs/design/launcher_wails_migration.md b/docs/design/launcher_wails_migration.md new file mode 100644 index 000000000..28f2f0f33 --- /dev/null +++ b/docs/design/launcher_wails_migration.md @@ -0,0 +1,50 @@ +# PicoClaw Launcher — Wails v2 Migration + +## Overview + +Desktop GUI for PicoClaw using Wails v2, replacing the previous HTTP server + browser approach. + +## Architecture + +``` +cmd/picoclaw-launcher/ +├── main.go # Wails bootstrap +├── app.go # App struct with Go↔JS bindings +├── helpers.go # Gateway process management +├── tray.go # System tray integration +├── wails.json # Wails project config +├── frontend/ +│ ├── index.html # Single-file UI (3 tabs) +│ └── wailsjs/ # Auto-generated bindings +└── internal/server/ + ├── setup.go # Config setup helpers + └── setup_chat.go # AI chat server +``` + +## UI: 3-Tab Layout + +| Tab | Features | +|-----|----------| +| **Status** | Gateway start/stop/restart, real-time status | +| **Chat** | AI chat interface, first-run setup questionnaire | +| **Settings** | Model/channel/API key config, save/reload | + +## Key Design Choices + +- **Wails bindings** instead of HTTP: `window.go.main.App.GetConfig()` replaces `fetch('/api/config')` +- **System tray**: minimize-to-tray on close, right-click menu for gateway control +- **First-run setup**: guided overlay when no config exists → AI chat questionnaire +- **Single index.html**: intentional for v1 (no build toolchain), will split if complexity grows + +## Build + +```bash +go tool wails build -o picoclaw-launcher +# or via Taskfile: +task build-launcher +``` + +## Dependencies + +- Wails v2 (declared as `tool` in go.mod, not linked into picoclaw binary) +- WebView2 (Windows, auto-installed), GTK/WebKit (Linux), WebKit (macOS) diff --git a/go.mod b/go.mod index 9f755bbc9..47fbe775e 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,19 @@ module github.com/sipeed/picoclaw go 1.25.7 +tool ( + github.com/tc-hib/go-winres + github.com/wailsapp/wails/v2/cmd/wails +) + require ( github.com/adhocore/gronx v1.19.6 github.com/anthropics/anthropic-sdk-go v1.22.1 github.com/bwmarrin/discordgo v0.29.0 github.com/caarlos0/env/v11 v11.3.1 github.com/chzyer/readline v1.5.1 + github.com/energye/systray v1.0.3 + github.com/gdamore/tcell/v2 v2.13.8 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/larksuite/oapi-sdk-go/v3 v3.5.3 @@ -16,10 +23,12 @@ require ( github.com/mymmrac/telego v1.6.0 github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 github.com/openai/openai-go/v3 v3.22.0 + github.com/rivo/tview v0.42.0 github.com/slack-go/slack v0.17.3 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 github.com/tencent-connect/botgo v0.2.1 + github.com/wailsapp/wails/v2 v2.11.0 go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4 golang.org/x/oauth2 v0.35.0 golang.org/x/time v0.14.0 @@ -28,33 +37,114 @@ require ( ) require ( + atomicgo.dev/cursor v0.2.0 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect + atomicgo.dev/schedule v0.1.0 // indirect + dario.cat/mergo v1.0.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v1.1.5 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/alecthomas/chroma/v2 v2.14.0 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/beeper/argo-go v1.1.2 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/charmbracelet/glamour v0.8.0 // indirect + github.com/charmbracelet/lipgloss v0.12.1 // indirect + github.com/charmbracelet/x/ansi v0.1.4 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/coder/websocket v1.8.14 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect + github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dlclark/regexp2 v1.11.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/flytam/filenamify v1.2.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gdamore/encoding v1.0.1 // indirect - github.com/gdamore/tcell/v2 v2.13.8 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.13.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/gookit/color v1.5.4 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackmordaunt/icns v1.0.0 // indirect + github.com/jaypipes/ghw v0.13.0 // indirect + github.com/jaypipes/pcidb v1.0.1 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/labstack/echo/v4 v4.13.3 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/leaanthony/clir v1.3.0 // indirect + github.com/leaanthony/debme v1.2.1 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/gosod v1.0.4 // indirect + github.com/leaanthony/slicer v1.6.0 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/leaanthony/winicon v1.0.0 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pterm/pterm v0.12.80 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rivo/tview v0.42.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/zerolog v1.34.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.0 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/tc-hib/go-winres v0.3.3 // indirect + github.com/tc-hib/winres v0.3.1 // indirect + github.com/tkrajina/go-reflector v0.5.8 // indirect + github.com/urfave/cli/v2 v2.25.7 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect github.com/vektah/gqlparser/v2 v2.5.27 // indirect + github.com/wailsapp/go-webview2 v1.0.22 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wzshiming/ctc v1.2.3 // indirect + github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + github.com/yuin/goldmark v1.7.4 // indirect + github.com/yuin/goldmark-emoji v1.0.3 // indirect go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/util v0.9.6 // indirect golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect + golang.org/x/image v0.12.0 // indirect + golang.org/x/mod v0.33.0 // indirect golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.0 // indirect modernc.org/libc v1.67.6 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/go.sum b/go.sum index 9041826a5..2e00165b6 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,69 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= +github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/adhocore/gronx v1.19.6 h1:5KNVcoR9ACgL9HhEqCm5QXsab/gI4QDIybTAWcXDKDc= github.com/adhocore/gronx v1.19.6/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= +github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/anthropics/anthropic-sdk-go v1.22.1 h1:xbsc3vJKCX/ELDZSpTNfz9wCgrFsamwFewPb1iI0Xh0= github.com/anthropics/anthropic-sdk-go v1.22.1/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beeper/argo-go v1.1.2 h1:UQI2G8F+NLfGTOmTUI0254pGKx/HUU/etbUGTJv91Fs= github.com/beeper/argo-go v1.1.2/go.mod h1:M+LJAnyowKVQ6Rdj6XYGEn+qcVFkb3R/MUpqkGR0hM4= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= @@ -27,35 +76,75 @@ github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5m github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs= +github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw= +github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs= +github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8= +github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= +github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/exp/golden v0.0.0-20240715153702-9ba8adf781c4 h1:6KzMkQeAF56rggw2NZu1L+TH7j9+DM1/2Kmh7KUxg1I= +github.com/charmbracelet/x/exp/golden v0.0.0-20240715153702-9ba8adf781c4/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= +github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg= github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/energye/systray v1.0.3 h1:XnyjJCeRU5z00bpNOic2fGTKz/7yHZMZjWiGIVXDS+4= +github.com/energye/systray v1.0.3/go.mod h1:HelKhC3PXwv3ryDxbuQqV+7kAxAYNzE5cfdrerGOZTc= +github.com/flytam/filenamify v1.2.0 h1:7RiSqXYR4cJftDQ5NuvljKMfd/ubKnW/j9C6iekChgI= +github.com/flytam/filenamify v1.2.0/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU= github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo= github.com/github/copilot-sdk/go v0.1.23 h1:uExtO/inZQndCZMiSAA1hvXINiz9tqo/MZgQzFzurxw= github.com/github/copilot-sdk/go v0.1.23/go.mod h1:GdwwBfMbm9AABLEM3x5IZKw4ZfwCYxZ1BgyytmZenQ0= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= @@ -64,10 +153,14 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78 github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -89,9 +182,17 @@ github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbc github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -100,27 +201,71 @@ github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc= github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ= +github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo= +github.com/jaypipes/ghw v0.13.0 h1:log8MXuB8hzTNnSktqpXMHc0c/2k/WgjOMSUtnI1RV4= +github.com/jaypipes/ghw v0.13.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8= +github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic= +github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= +github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/larksuite/oapi-sdk-go/v3 v3.5.3 h1:xvf8Dv29kBXC5/DNDCLhHkAFW8l/0LlQJimO5Zn+JUk= github.com/larksuite/oapi-sdk-go/v3 v3.5.3/go.mod h1:ZEplY+kwuIrj/nqw5uSCINNATcH3KdxSN7y+UxYY5fI= +github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= +github.com/leaanthony/clir v1.3.0 h1:L9nPDWrmc/qU9UWZZvRaFajWYuO0np9V5p+5gxyYno0= +github.com/leaanthony/clir v1.3.0/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= +github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= +github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI= +github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw= +github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= +github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= +github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ= +github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -128,16 +273,30 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4= github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/modelcontextprotocol/go-sdk v1.3.0 h1:gMfZkv3DzQF5q/DcQePo5rahEY+sguyPfXDfNBcT0Zs= github.com/modelcontextprotocol/go-sdk v1.3.0/go.mod h1:AnQ//Qc6+4nIyyrB4cxBU7UW9VibK4iOZBeyP/rF1IE= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= +github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= github.com/mymmrac/telego v1.6.0 h1:Zc8rgyHozvd/7ZgyrigyHdAF9koHYMfilYfyB6wlFC0= github.com/mymmrac/telego v1.6.0/go.mod h1:xt6ZWA8zi8KmuzryE1ImEdl9JSwjHNpM4yhC7D8hU4Y= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -146,31 +305,59 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 h1:Lb/Uzkiw2Ugt2Xf03J5wmv81PdkYOiWbI8CNBi1boC8= github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1/go.mod h1:ln3IqPYYocZbYvl9TAOrG/cxGR9xcn4pnZRLdCTEGEU= github.com/openai/openai-go/v3 v3.22.0 h1:6MEoNoV8sbjOVmXdvhmuX3BjVbVdcExbVyGixiyJ8ys= github.com/openai/openai-go/v3 v3.22.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14= github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg= +github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/tview v0.42.0 h1:b/ftp+RxtDsHSaynXTbJb+/n/BxDEi+W3UfF5jILK6c= github.com/rivo/tview v0.42.0/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g= github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= @@ -182,7 +369,11 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -190,6 +381,10 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tc-hib/go-winres v0.3.3 h1:DQ50qlvDVhqrDOY0svTxZFZWfKWtZtfnXXHPCw6lqh0= +github.com/tc-hib/go-winres v0.3.3/go.mod h1:5NGzOtuvjSqnpIEi2o1h48MKZzP9olvrf+PeY2t1uoA= +github.com/tc-hib/winres v0.3.1 h1:CwRjEGrKdbi5CvZ4ID+iyVhgyfatxFoizjPhzez9Io4= +github.com/tc-hib/winres v0.3.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= github.com/tencent-connect/botgo v0.2.1 h1:+BrTt9Zh+awL28GWC4g5Na3nQaGRWb0N5IctS8WqBCk= github.com/tencent-connect/botgo v0.2.1/go.mod h1:oO1sG9ybhXNickvt+CVym5khwQ+uKhTR+IhTqEfOVsI= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -204,16 +399,39 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= +github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI= github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw= github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM= github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s= github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58= +github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ= +github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k= +github.com/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c= +github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28= +github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae h1:tpXvBXC3hpQBDCc9OojJZCQMVRAbT3TTdUMP8WguXkY= +github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= @@ -221,6 +439,11 @@ github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT0 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= +github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4= +github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= go.mau.fi/util v0.9.6 h1:2nsvxm49KhI3wrFltr0+wSUBlnQ4CMtykuELjpIU+ts= @@ -237,11 +460,15 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o= golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ= +golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -257,6 +484,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -278,19 +507,28 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -299,6 +537,8 @@ golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= @@ -311,6 +551,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= @@ -341,18 +582,25 @@ google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBN google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= diff --git a/pkg/config/config.go b/pkg/config/config.go index 305ae67e3..8e7b00171 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -51,13 +51,20 @@ type Config struct { Agents AgentsConfig `json:"agents"` Bindings []AgentBinding `json:"bindings,omitempty"` Session SessionConfig `json:"session,omitempty"` - Channels ChannelsConfig `json:"channels"` + Channels ChannelsConfig `json:"channels,omitempty"` Providers ProvidersConfig `json:"providers,omitempty"` - ModelList []ModelConfig `json:"model_list"` // New model-centric provider configuration - Gateway GatewayConfig `json:"gateway"` - Tools ToolsConfig `json:"tools"` - Heartbeat HeartbeatConfig `json:"heartbeat"` - Devices DevicesConfig `json:"devices"` + ModelList []ModelConfig `json:"model_list,omitempty"` + Gateway GatewayConfig `json:"gateway,omitempty"` + Tools ToolsConfig `json:"tools,omitempty"` + Heartbeat HeartbeatConfig `json:"heartbeat,omitempty"` + Devices DevicesConfig `json:"devices,omitempty"` + Logging LoggingConfig `json:"logging,omitempty"` +} + +// LoggingConfig controls log output. +type LoggingConfig struct { + Level string `json:"level,omitempty"` // debug, info, warn, error (default: warn) + FileDir string `json:"file_dir,omitempty"` // directory for log files; empty = no file logging } // MarshalJSON implements custom JSON marshaling for Config @@ -175,6 +182,16 @@ type AgentDefaults struct { ModelName string `json:"model_name,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL_NAME"` Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` // Deprecated: use model_name instead ModelFallbacks []string `json:"model_fallbacks,omitempty"` + + // Phase 1 — Analyser: lightweight model for intent/tag analysis + CoT strategy. + // Falls back to main model_name if empty. Use a cheap/fast model here. + AnalyserModel string `json:"analyser_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_ANALYSER_MODEL"` + PreLLMModel string `json:"pre_llm_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_PRE_LLM_MODEL"` // Deprecated: use analyser_model + + // Phase 3 — Digest: lightweight model for memory extraction from turn records. + // Falls back to main model_name if empty. Use a cheap/fast model here. + DigestModel string `json:"digest_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_DIGEST_MODEL"` + ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"` ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"` MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"` @@ -191,6 +208,27 @@ func (d *AgentDefaults) GetModelName() string { return d.Model } +// GetAnalyserModel returns the model for Phase 1 (Analyser). +// Priority: analyser_model → pre_llm_model (deprecated) → main model. +func (d *AgentDefaults) GetAnalyserModel() string { + if d.AnalyserModel != "" { + return d.AnalyserModel + } + if d.PreLLMModel != "" { + return d.PreLLMModel + } + return d.GetModelName() +} + +// GetDigestModel returns the model for Phase 3 (MemoryDigest). +// Priority: digest_model → main model. +func (d *AgentDefaults) GetDigestModel() string { + if d.DigestModel != "" { + return d.DigestModel + } + return d.GetModelName() +} + type ChannelsConfig struct { WhatsApp WhatsAppConfig `json:"whatsapp"` Telegram TelegramConfig `json:"telegram"` diff --git a/pkg/config/onboard.go b/pkg/config/onboard.go new file mode 100644 index 000000000..3396f5737 --- /dev/null +++ b/pkg/config/onboard.go @@ -0,0 +1,30 @@ +package config + +// MinimalOnboardConfig produces a stripped-down config for initial onboarding. +// It keeps only the essentials, omitting empty channels, providers, and +// model_list entries without API keys. +func MinimalOnboardConfig(full *Config) *Config { + // Filter model_list: only keep entries that have an API key set, + // or special auth (e.g., OAuth, Ollama local). + var models []ModelConfig + for _, m := range full.ModelList { + if m.APIKey != "" || m.AuthMethod != "" { + models = append(models, m) + } + } + + return &Config{ + Agents: AgentsConfig{ + Defaults: full.Agents.Defaults, + }, + Session: full.Session, + ModelList: models, + Gateway: full.Gateway, + Tools: ToolsConfig{ + Exec: full.Tools.Exec, + Web: WebToolsConfig{ + DuckDuckGo: full.Tools.Web.DuckDuckGo, + }, + }, + } +}