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
+
+
+
📡 Channels
+
Telegram
+
Discord
+
Slack
+
WeCom
+
+
+
+
+
🧠 Agent Defaults
+
+
+
+
+
+
📁 Workspace
+
+
Restrict to workspace
+
Allow read outside workspace
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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,
+ },
+ },
+ }
+}