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

smallnest / goclaw / 22357241380

24 Feb 2026 03:19PM UTC coverage: 4.288% (-0.01%) from 4.3%
22357241380

push

github

smallnest
#23 fix windows issue

0 of 92 new or added lines in 7 files covered. (0.0%)

1 existing line in 1 file now uncovered.

1059 of 24697 relevant lines covered (4.29%)

0.32 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
        // 创建会话管理器
×
NEW
172
        homeDir, err := os.UserHomeDir()
×
NEW
173
        if err != nil {
×
NEW
174
                logger.Fatal("Failed to get home directory", zap.Error(err))
×
NEW
175
        }
×
NEW
176
        sessionDir := homeDir + "/.goclaw/sessions"
×
177
        sessionMgr, err := session.NewManager(sessionDir)
×
178
        if err != nil {
×
179
                logger.Fatal("Failed to create session manager", zap.Error(err))
×
180
        }
×
181

182
        // 创建记忆存储
183
        memoryStore := agent.NewMemoryStore(workspaceDir)
×
184

×
185
        // 创建上下文构建器
×
186
        contextBuilder := agent.NewContextBuilder(memoryStore, workspaceDir)
×
187

×
188
        // 创建工具注册表
×
189
        toolRegistry := agent.NewToolRegistry()
×
190

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

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

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

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

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

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

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

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

×
274
        // 创建上下文
×
275
        ctx, cancel := context.WithCancel(context.Background())
×
276
        defer cancel()
×
277

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

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

291
        // 创建调度器
292
        scheduler := cron.NewScheduler(messageBus, provider, sessionMgr)
×
293

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

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

310
        // 处理信号
311
        sigChan := make(chan os.Signal, 1)
×
312
        signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
×
313

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

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

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

342
        // 启动 AgentManager
343
        go func() {
×
344
                if err := agentManager.Start(ctx); err != nil {
×
345
                        logger.Error("AgentManager error", zap.Error(err))
×
346
                }
×
347
        }()
348

349
        // 等待信号
350
        <-sigChan
×
351
        logger.Info("Received shutdown signal")
×
352

×
353
        // 停止 AgentManager
×
354
        if err := agentManager.Stop(); err != nil {
×
355
                logger.Error("Failed to stop agent manager", zap.Error(err))
×
356
        }
×
357

358
        logger.Info("goclaw agent stopped")
×
359
}
360

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

369
        fmt.Println("Current Configuration:")
×
370
        fmt.Printf("  Model: %s\n", cfg.Agents.Defaults.Model)
×
371
        fmt.Printf("  Max Iterations: %d\n", cfg.Agents.Defaults.MaxIterations)
×
372
        fmt.Printf("  Temperature: %.1f\n", cfg.Agents.Defaults.Temperature)
×
373
}
374

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

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

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

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

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

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

423
// runVersion prints version information
424
func runVersion(cmd *cobra.Command, args []string) {
×
425
        fmt.Printf("goclaw %s\n", Version)
×
426
        fmt.Println("Copyright (c) 2024 smallnest")
×
427
        fmt.Println("License: MIT")
×
428
        fmt.Println("https://github.com/smallnest/goclaw")
×
429
}
×
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