• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

smallnest / goclaw / 21970812588

13 Feb 2026 01:14AM UTC coverage: 5.778% (-0.6%) from 6.376%
21970812588

push

github

smallnest
add infoflow

1514 of 26201 relevant lines covered (5.78%)

0.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/cli/root.go
1
package cli
2

3
import (
4
        "context"
5
        "fmt"
6
        "os"
7
        "os/signal"
8
        "syscall"
9

10
        "github.com/smallnest/goclaw/agent"
11
        "github.com/smallnest/goclaw/agent/tools"
12
        "github.com/smallnest/goclaw/bus"
13
        "github.com/smallnest/goclaw/channels"
14
        "github.com/smallnest/goclaw/cli/commands"
15
        "github.com/smallnest/goclaw/config"
16
        "github.com/smallnest/goclaw/cron"
17
        "github.com/smallnest/goclaw/gateway"
18
        "github.com/smallnest/goclaw/internal"
19
        "github.com/smallnest/goclaw/internal/logger"
20
        "github.com/smallnest/goclaw/internal/workspace"
21
        "github.com/smallnest/goclaw/providers"
22
        "github.com/smallnest/goclaw/session"
23
        "github.com/spf13/cobra"
24
        "go.uber.org/zap"
25
)
26

27
// Version information (populated by goreleaser)
28
var Version = "dev"
29

30
var rootCmd = &cobra.Command{
31
        Use:   "goclaw",
32
        Short: "Go-based AI Agent framework",
33
        Long:  `goclaw is a Go language implementation of an AI Agent framework, inspired by nanobot.`,
34
}
35

36
var versionCmd = &cobra.Command{
37
        Use:   "version",
38
        Short: "Print version information",
39
        Run:   runVersion,
40
}
41

42
var startCmd = &cobra.Command{
43
        Use:   "start",
44
        Short: "Start the goclaw agent",
45
        Run:   runStart,
46
}
47

48
var configCmd = &cobra.Command{
49
        Use:   "config",
50
        Short: "Configuration management",
51
}
52

53
var configShowCmd = &cobra.Command{
54
        Use:   "show",
55
        Short: "Show current configuration",
56
        Run:   runConfigShow,
57
}
58

59
var installCmd = &cobra.Command{
60
        Use:   "install",
61
        Short: "Install goclaw workspace templates",
62
        Run:   runInstall,
63
}
64

65
// Flags for install command
66
var (
67
        installConfigPath    string
68
        installWorkspacePath string
69
)
70

71
func init() {
×
72
        // Add install command flags
×
73
        installCmd.Flags().StringVar(&installConfigPath, "config", "", "Path to config file")
×
74
        installCmd.Flags().StringVar(&installWorkspacePath, "workspace", "", "Path to workspace directory (overrides config)")
×
75

×
76
        rootCmd.AddCommand(versionCmd)
×
77
        rootCmd.AddCommand(startCmd)
×
78
        rootCmd.AddCommand(installCmd)
×
79
        rootCmd.AddCommand(configCmd)
×
80
        configCmd.AddCommand(configShowCmd)
×
81
        rootCmd.AddCommand(agentsCmd)
×
82
        rootCmd.AddCommand(agentCmd)
×
83
        rootCmd.AddCommand(sessionsCmd)
×
84
        rootCmd.AddCommand(onboardCmd)
×
85

×
86
        // Register memory and logs commands from commands package
×
87
        // Note: skills command is already registered in cli/skills.go
×
88
        rootCmd.AddCommand(commands.MemoryCmd)
×
89
        rootCmd.AddCommand(commands.LogsCmd)
×
90

×
91
        // Register browser, tui, gateway, health, status commands
×
92
        rootCmd.AddCommand(commands.BrowserCommand())
×
93
        rootCmd.AddCommand(commands.TUICommand())
×
94
        rootCmd.AddCommand(commands.GatewayCommand())
×
95
        rootCmd.AddCommand(commands.HealthCommand())
×
96
        rootCmd.AddCommand(commands.StatusCommand())
×
97
        rootCmd.AddCommand(commands.ChannelsCommand())
×
98

×
99
        // Register approvals, cron, system commands (registered via init)
×
100
        // These commands auto-register themselves
×
101
}
×
102

103
// SetVersion sets the version from main package
104
func SetVersion(v string) {
×
105
        Version = v
×
106
        rootCmd.Version = v
×
107
}
×
108

109
// Execute 执行 CLI
110
func Execute() error {
×
111
        return rootCmd.Execute()
×
112
}
×
113

114
// runStart 启动 Agent
115
func runStart(cmd *cobra.Command, args []string) {
×
116
        // 确保内置技能被复制到用户目录
×
117
        if err := internal.EnsureBuiltinSkills(); err != nil {
×
118
                fmt.Fprintf(os.Stderr, "Warning: Failed to ensure builtin skills: %v\n", err)
×
119
        }
×
120

121
        // 确保配置文件存在
122
        configCreated, err := internal.EnsureConfig()
×
123
        if err != nil {
×
124
                fmt.Fprintf(os.Stderr, "Warning: Failed to ensure config: %v\n", err)
×
125
        }
×
126
        if configCreated {
×
127
                fmt.Println("Config file created at: " + internal.GetConfigPath())
×
128
                fmt.Println("Please edit the config file to set your API keys and other settings.")
×
129
                fmt.Println()
×
130
        }
×
131

132
        // 加载配置
133
        cfg, err := config.Load("")
×
134
        if err != nil {
×
135
                fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
×
136
                os.Exit(1)
×
137
        }
×
138

139
        // 初始化日志
140
        if err := logger.Init("info", false); err != nil {
×
141
                fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err)
×
142
                os.Exit(1)
×
143
        }
×
144
        defer func() { _ = logger.Sync() }()
×
145

146
        logger.Info("Starting goclaw agent")
×
147

×
148
        // 验证配置
×
149
        if err := config.Validate(cfg); err != nil {
×
150
                logger.Fatal("Invalid configuration", zap.Error(err))
×
151
        }
×
152

153
        // 获取 workspace 目录
154
        workspaceDir, err := config.GetWorkspacePath(cfg)
×
155
        if err != nil {
×
156
                logger.Fatal("Failed to get workspace path", zap.Error(err))
×
157
        }
×
158

159
        // 创建 workspace 管理器并确保文件存在
160
        workspaceMgr := workspace.NewManager(workspaceDir)
×
161
        if err := workspaceMgr.Ensure(); err != nil {
×
162
                logger.Warn("Failed to ensure workspace files", zap.Error(err))
×
163
        } else {
×
164
                logger.Info("Workspace ready", zap.String("path", workspaceDir))
×
165
        }
×
166

167
        // 创建消息总线
168
        messageBus := bus.NewMessageBus(100)
×
169
        defer messageBus.Close()
×
170

×
171
        // 创建会话管理器
×
172
        sessionDir := os.Getenv("HOME") + "/.goclaw/sessions"
×
173
        sessionMgr, err := session.NewManager(sessionDir)
×
174
        if err != nil {
×
175
                logger.Fatal("Failed to create session manager", zap.Error(err))
×
176
        }
×
177

178
        // 创建记忆存储
179
        memoryStore := agent.NewMemoryStore(workspaceDir)
×
180

×
181
        // 创建上下文构建器
×
182
        contextBuilder := agent.NewContextBuilder(memoryStore, workspaceDir)
×
183

×
184
        // 创建工具注册表
×
185
        toolRegistry := agent.NewToolRegistry()
×
186

×
187
        // 创建技能加载器(统一使用 ~/.goclaw/skills 目录)
×
188
        goclawDir := os.Getenv("HOME") + "/.goclaw"
×
189
        skillsDir := goclawDir + "/skills"
×
190
        skillsLoader := agent.NewSkillsLoader(goclawDir, []string{skillsDir})
×
191
        if err := skillsLoader.Discover(); err != nil {
×
192
                logger.Warn("Failed to discover skills", zap.Error(err))
×
193
        } else {
×
194
                skills := skillsLoader.List()
×
195
                if len(skills) > 0 {
×
196
                        logger.Info("Skills loaded", zap.Int("count", len(skills)))
×
197
                }
×
198
        }
199

200
        // 注册文件系统工具
201
        fsTool := tools.NewFileSystemTool(cfg.Tools.FileSystem.AllowedPaths, cfg.Tools.FileSystem.DeniedPaths, workspaceDir)
×
202
        for _, tool := range fsTool.GetTools() {
×
203
                if err := toolRegistry.RegisterExisting(tool); err != nil {
×
204
                        logger.Warn("Failed to register tool", zap.String("tool", tool.Name()))
×
205
                }
×
206
        }
207

208
        // 注册 use_skill 工具(用于两阶段技能加载)
209
        if err := toolRegistry.RegisterExisting(tools.NewUseSkillTool()); err != nil {
×
210
                logger.Warn("Failed to register use_skill tool", zap.Error(err))
×
211
        }
×
212

213
        // 注册 Shell 工具
214
        shellTool := tools.NewShellTool(
×
215
                cfg.Tools.Shell.Enabled,
×
216
                cfg.Tools.Shell.AllowedCmds,
×
217
                cfg.Tools.Shell.DeniedCmds,
×
218
                cfg.Tools.Shell.Timeout,
×
219
                cfg.Tools.Shell.WorkingDir,
×
220
                cfg.Tools.Shell.Sandbox,
×
221
        )
×
222
        for _, tool := range shellTool.GetTools() {
×
223
                if err := toolRegistry.RegisterExisting(tool); err != nil {
×
224
                        logger.Warn("Failed to register tool", zap.String("tool", tool.Name()))
×
225
                }
×
226
        }
227

228
        // 注册 Web 工具
229
        webTool := tools.NewWebTool(
×
230
                cfg.Tools.Web.SearchAPIKey,
×
231
                cfg.Tools.Web.SearchEngine,
×
232
                cfg.Tools.Web.Timeout,
×
233
        )
×
234
        for _, tool := range webTool.GetTools() {
×
235
                if err := toolRegistry.RegisterExisting(tool); err != nil {
×
236
                        logger.Warn("Failed to register tool", zap.String("tool", tool.Name()))
×
237
                }
×
238
        }
239

240
        // 注册智能搜索工具(支持 web search 失败时自动回退到 Google browser 搜索)
241
        browserTimeout := 30
×
242
        if cfg.Tools.Browser.Timeout > 0 {
×
243
                browserTimeout = cfg.Tools.Browser.Timeout
×
244
        }
×
245
        if err := toolRegistry.RegisterExisting(tools.NewSmartSearch(webTool, true, browserTimeout).GetTool()); err != nil {
×
246
                logger.Warn("Failed to register smart_search tool", zap.Error(err))
×
247
        }
×
248

249
        // 注册浏览器工具(如果启用)
250
        if cfg.Tools.Browser.Enabled {
×
251
                browserTool := tools.NewBrowserTool(
×
252
                        cfg.Tools.Browser.Headless,
×
253
                        cfg.Tools.Browser.Timeout,
×
254
                )
×
255
                for _, tool := range browserTool.GetTools() {
×
256
                        if err := toolRegistry.RegisterExisting(tool); err != nil {
×
257
                                logger.Warn("Failed to register tool", zap.String("tool", tool.Name()))
×
258
                        }
×
259
                }
260
                logger.Info("Browser tools registered")
×
261
        }
262

263
        // 创建 LLM 提供商
264
        provider, err := providers.NewProvider(cfg)
×
265
        if err != nil {
×
266
                logger.Fatal("Failed to create LLM provider", zap.Error(err))
×
267
        }
×
268
        defer provider.Close()
×
269

×
270
        // 创建上下文
×
271
        ctx, cancel := context.WithCancel(context.Background())
×
272
        defer cancel()
×
273

×
274
        // 创建通道管理器
×
275
        channelMgr := channels.NewManager(messageBus)
×
276
        if err := channelMgr.SetupFromConfig(cfg); err != nil {
×
277
                logger.Warn("Failed to setup channels from config", zap.Error(err))
×
278
        }
×
279

280
        // 创建网关服务器
281
        gatewayServer := gateway.NewServer(&cfg.Gateway, messageBus, channelMgr, sessionMgr)
×
282
        if err := gatewayServer.Start(ctx); err != nil {
×
283
                logger.Warn("Failed to start gateway server", zap.Error(err))
×
284
        }
×
285
        defer func() { _ = gatewayServer.Stop() }()
×
286

287
        // 创建调度器
288
        scheduler := cron.NewScheduler(messageBus, provider, sessionMgr)
×
289

×
290
        // 创建 AgentManager
×
291
        agentManager := agent.NewAgentManager(&agent.NewAgentManagerConfig{
×
292
                Bus:            messageBus,
×
293
                Provider:       provider,
×
294
                SessionMgr:     sessionMgr,
×
295
                Tools:          toolRegistry,
×
296
                DataDir:        workspaceDir, // 使用 workspace 作为数据目录
×
297
                ContextBuilder: contextBuilder,
×
298
                SkillsLoader:   skillsLoader,
×
299
        })
×
300

×
301
        // 从配置设置 Agent 和绑定
×
302
        if err := agentManager.SetupFromConfig(cfg, contextBuilder); err != nil {
×
303
                logger.Fatal("Failed to setup agent manager", zap.Error(err))
×
304
        }
×
305

306
        // 处理信号
307
        sigChan := make(chan os.Signal, 1)
×
308
        signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
×
309

×
310
        // 启动通道
×
311
        if err := channelMgr.Start(ctx); err != nil {
×
312
                logger.Error("Failed to start channels", zap.Error(err))
×
313
        }
×
314
        defer func() { _ = channelMgr.Stop() }()
×
315

316
        // 启动调度器
317
        if err := scheduler.Start(ctx); err != nil {
×
318
                logger.Error("Failed to start scheduler", zap.Error(err))
×
319
        }
×
320
        defer scheduler.Stop()
×
321

×
322
        // 启动出站消息分发
×
323
        logger.Info("About to start outbound message dispatcher")
×
324
        go func() {
×
325
                defer func() {
×
326
                        if r := recover(); r != nil {
×
327
                                logger.Error("Outbound message dispatcher panicked",
×
328
                                        zap.Any("panic", r))
×
329
                        }
×
330
                }()
331
                if err := channelMgr.DispatchOutbound(ctx); err != nil {
×
332
                        logger.Error("Outbound message dispatcher exited with error", zap.Error(err))
×
333
                } else {
×
334
                        logger.Info("Outbound message dispatcher exited normally")
×
335
                }
×
336
        }()
337

338
        // 启动 AgentManager
339
        go func() {
×
340
                if err := agentManager.Start(ctx); err != nil {
×
341
                        logger.Error("AgentManager error", zap.Error(err))
×
342
                }
×
343
        }()
344

345
        // 等待信号
346
        <-sigChan
×
347
        logger.Info("Received shutdown signal")
×
348

×
349
        // 停止 AgentManager
×
350
        if err := agentManager.Stop(); err != nil {
×
351
                logger.Error("Failed to stop agent manager", zap.Error(err))
×
352
        }
×
353

354
        logger.Info("goclaw agent stopped")
×
355
}
356

357
// runConfigShow 显示配置
358
func runConfigShow(cmd *cobra.Command, args []string) {
×
359
        cfg, err := config.Load("")
×
360
        if err != nil {
×
361
                fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
×
362
                os.Exit(1)
×
363
        }
×
364

365
        fmt.Println("Current Configuration:")
×
366
        fmt.Printf("  Model: %s\n", cfg.Agents.Defaults.Model)
×
367
        fmt.Printf("  Max Iterations: %d\n", cfg.Agents.Defaults.MaxIterations)
×
368
        fmt.Printf("  Temperature: %.1f\n", cfg.Agents.Defaults.Temperature)
×
369
}
370

371
// runInstall 安装 goclaw workspace 模板
372
func runInstall(cmd *cobra.Command, args []string) {
×
373
        // 加载配置
×
374
        cfg, err := config.Load(installConfigPath)
×
375
        if err != nil {
×
376
                fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
×
377
                os.Exit(1)
×
378
        }
×
379

380
        // 获取 workspace 目录
381
        workspaceDir := installWorkspacePath
×
382
        if workspaceDir == "" {
×
383
                workspaceDir, err = config.GetWorkspacePath(cfg)
×
384
                if err != nil {
×
385
                        fmt.Fprintf(os.Stderr, "Failed to get workspace path: %v\n", err)
×
386
                        os.Exit(1)
×
387
                }
×
388
        }
389

390
        // 创建 workspace 管理器并确保文件存在
391
        workspaceMgr := workspace.NewManager(workspaceDir)
×
392
        if err := workspaceMgr.Ensure(); err != nil {
×
393
                fmt.Fprintf(os.Stderr, "Failed to ensure workspace: %v\n", err)
×
394
                os.Exit(1)
×
395
        }
×
396

397
        fmt.Printf("Workspace installed successfully at: %s\n", workspaceDir)
×
398
        fmt.Println("\nWorkspace files:")
×
399
        files, err := workspaceMgr.ListFiles()
×
400
        if err != nil {
×
401
                fmt.Fprintf(os.Stderr, "Failed to list files: %v\n", err)
×
402
                return
×
403
        }
×
404
        for _, f := range files {
×
405
                fmt.Printf("  - %s\n", f)
×
406
        }
×
407

408
        memoryFiles, err := workspaceMgr.ListMemoryFiles()
×
409
        if err == nil && len(memoryFiles) > 0 {
×
410
                fmt.Println("\nMemory files:")
×
411
                for _, f := range memoryFiles {
×
412
                        fmt.Printf("  - memory/%s\n", f)
×
413
                }
×
414
        }
415

416
        fmt.Println("\nYou can now customize these files to define your agent's personality and behavior.")
×
417
}
418

419
// runVersion prints version information
420
func runVersion(cmd *cobra.Command, args []string) {
×
421
        fmt.Printf("goclaw %s\n", Version)
×
422
        fmt.Println("Copyright (c) 2024 smallnest")
×
423
        fmt.Println("License: MIT")
×
424
        fmt.Println("https://github.com/smallnest/goclaw")
×
425
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc