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

smallnest / goclaw / 21940614194

12 Feb 2026 09:19AM UTC coverage: 6.376% (-0.07%) from 6.444%
21940614194

push

github

chaoyuepan
add go-releaser to prepare release v0.1.0

0 of 17 new or added lines in 2 files covered. (0.0%)

417 existing lines in 6 files now uncovered.

1514 of 23746 relevant lines covered (6.38%)

0.61 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

×
NEW
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
×
UNCOV
101
}
×
102

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

109
// Execute 执行 CLI
110
func Execute() error {
×
111
        return rootCmd.Execute()
×
UNCOV
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)
×
UNCOV
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()
×
UNCOV
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)
×
UNCOV
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
        }
×
UNCOV
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))
×
UNCOV
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))
×
UNCOV
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))
×
UNCOV
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))
×
UNCOV
176
        }
×
177

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

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

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

×
187
        // 创建技能加载器
×
188
        skillsLoader := agent.NewSkillsLoader(workspaceDir, []string{})
×
189
        if err := skillsLoader.Discover(); err != nil {
×
190
                logger.Warn("Failed to discover skills", zap.Error(err))
×
191
        } else {
×
192
                skills := skillsLoader.List()
×
193
                if len(skills) > 0 {
×
194
                        logger.Info("Skills loaded", zap.Int("count", len(skills)))
×
UNCOV
195
                }
×
196
        }
197

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

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

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

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

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

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

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

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

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

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

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

×
288
        // 创建 Agent
×
289
        agentInstance, err := agent.NewAgent(&agent.NewAgentConfig{
×
290
                Bus:          messageBus,
×
291
                Provider:     provider,
×
292
                SessionMgr:   sessionMgr,
×
293
                Tools:        toolRegistry,
×
294
                Context:      contextBuilder,
×
295
                Workspace:    workspaceDir,
×
296
                MaxIteration: cfg.Agents.Defaults.MaxIterations,
×
297
        })
×
298
        if err != nil {
×
299
                logger.Fatal("Failed to create agent", zap.Error(err))
×
UNCOV
300
        }
×
301

302
        // 处理信号
303
        sigChan := make(chan os.Signal, 1)
×
304
        signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
×
305

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

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

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

334
        // 启动 Agent
335
        go func() {
×
336
                if err := agentInstance.Start(ctx); err != nil {
×
337
                        logger.Error("Agent error", zap.Error(err))
×
UNCOV
338
                }
×
339
        }()
340

341
        // 等待信号
342
        <-sigChan
×
343
        logger.Info("Received shutdown signal")
×
344

×
345
        // 停止 Agent
×
346
        if err := agentInstance.Stop(); err != nil {
×
347
                logger.Error("Failed to stop agent", zap.Error(err))
×
UNCOV
348
        }
×
349

UNCOV
350
        logger.Info("goclaw agent stopped")
×
351
}
352

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

361
        fmt.Println("Current Configuration:")
×
362
        fmt.Printf("  Model: %s\n", cfg.Agents.Defaults.Model)
×
363
        fmt.Printf("  Max Iterations: %d\n", cfg.Agents.Defaults.MaxIterations)
×
UNCOV
364
        fmt.Printf("  Temperature: %.1f\n", cfg.Agents.Defaults.Temperature)
×
365
}
366

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

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

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

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

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

UNCOV
412
        fmt.Println("\nYou can now customize these files to define your agent's personality and behavior.")
×
413
}
414

415
// runVersion prints version information
NEW
416
func runVersion(cmd *cobra.Command, args []string) {
×
NEW
417
        fmt.Printf("goclaw %s\n", Version)
×
NEW
418
        fmt.Println("Copyright (c) 2024 smallnest")
×
NEW
419
        fmt.Println("License: MIT")
×
NEW
420
        fmt.Println("https://github.com/smallnest/goclaw")
×
NEW
421
}
×
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