fix: 🐛 fix merge err
This commit is contained in:
parent
76d263aba3
commit
327185b6d8
1 changed files with 0 additions and 786 deletions
|
|
@ -189,797 +189,11 @@ func printHelp() {
|
||||||
fmt.Println(" version Show version information")
|
fmt.Println(" version Show version information")
|
||||||
}
|
}
|
||||||
|
|
||||||
func onboard() {
|
|
||||||
configPath := getConfigPath()
|
|
||||||
|
|
||||||
if _, err := os.Stat(configPath); err == nil {
|
|
||||||
fmt.Printf("Config already exists at %s\n", configPath)
|
|
||||||
fmt.Print("Overwrite? (y/n): ")
|
|
||||||
var response string
|
|
||||||
fmt.Scanln(&response)
|
|
||||||
if response != "y" {
|
|
||||||
fmt.Println("Aborted.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := config.DefaultConfig()
|
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
|
||||||
fmt.Printf("Error saving config: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
workspace := cfg.WorkspacePath()
|
|
||||||
createWorkspaceTemplates(workspace)
|
|
||||||
|
|
||||||
fmt.Printf("%s picoclaw is ready!\n", logo)
|
|
||||||
fmt.Println("\nNext steps:")
|
|
||||||
fmt.Println(" 1. Add your API key to", configPath)
|
|
||||||
fmt.Println(" Get one at: https://openrouter.ai/keys")
|
|
||||||
fmt.Println(" 2. Chat: picoclaw agent -m \"Hello!\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyEmbeddedToTarget(targetDir string) error {
|
|
||||||
// Ensure target directory exists
|
|
||||||
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
|
||||||
return fmt.Errorf("Failed to create target directory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk through all files in embed.FS
|
|
||||||
err := fs.WalkDir(embeddedFiles, "workspace", func(path string, d fs.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip directories
|
|
||||||
if d.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read embedded file
|
|
||||||
data, err := embeddedFiles.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to read embedded file %s: %w", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
new_path, err := filepath.Rel("workspace", path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to get relative path for %s: %v\n", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build target file path
|
|
||||||
targetPath := filepath.Join(targetDir, new_path)
|
|
||||||
|
|
||||||
// Ensure target file's directory exists
|
|
||||||
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
|
||||||
return fmt.Errorf("Failed to create directory %s: %w", filepath.Dir(targetPath), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write file
|
|
||||||
if err := os.WriteFile(targetPath, data, 0644); err != nil {
|
|
||||||
return fmt.Errorf("Failed to write file %s: %w", targetPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func createWorkspaceTemplates(workspace string) {
|
|
||||||
err := copyEmbeddedToTarget(workspace)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error copying workspace templates: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateCmd() {
|
|
||||||
if len(os.Args) > 2 && (os.Args[2] == "--help" || os.Args[2] == "-h") {
|
|
||||||
migrateHelp()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := migrate.Options{}
|
|
||||||
|
|
||||||
args := os.Args[2:]
|
|
||||||
for i := 0; i < len(args); i++ {
|
|
||||||
switch args[i] {
|
|
||||||
case "--dry-run":
|
|
||||||
opts.DryRun = true
|
|
||||||
case "--config-only":
|
|
||||||
opts.ConfigOnly = true
|
|
||||||
case "--workspace-only":
|
|
||||||
opts.WorkspaceOnly = true
|
|
||||||
case "--force":
|
|
||||||
opts.Force = true
|
|
||||||
case "--refresh":
|
|
||||||
opts.Refresh = true
|
|
||||||
case "--openclaw-home":
|
|
||||||
if i+1 < len(args) {
|
|
||||||
opts.OpenClawHome = args[i+1]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
case "--picoclaw-home":
|
|
||||||
if i+1 < len(args) {
|
|
||||||
opts.PicoClawHome = args[i+1]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fmt.Printf("Unknown flag: %s\n", args[i])
|
|
||||||
migrateHelp()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := migrate.Run(opts)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.DryRun {
|
|
||||||
migrate.PrintSummary(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateHelp() {
|
|
||||||
fmt.Println("\nMigrate from OpenClaw to PicoClaw")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Usage: picoclaw migrate [options]")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Options:")
|
|
||||||
fmt.Println(" --dry-run Show what would be migrated without making changes")
|
|
||||||
fmt.Println(" --refresh Re-sync workspace files from OpenClaw (repeatable)")
|
|
||||||
fmt.Println(" --config-only Only migrate config, skip workspace files")
|
|
||||||
fmt.Println(" --workspace-only Only migrate workspace files, skip config")
|
|
||||||
fmt.Println(" --force Skip confirmation prompts")
|
|
||||||
fmt.Println(" --openclaw-home Override OpenClaw home directory (default: ~/.openclaw)")
|
|
||||||
fmt.Println(" --picoclaw-home Override PicoClaw home directory (default: ~/.picoclaw)")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Examples:")
|
|
||||||
fmt.Println(" picoclaw migrate Detect and migrate from OpenClaw")
|
|
||||||
fmt.Println(" picoclaw migrate --dry-run Show what would be migrated")
|
|
||||||
fmt.Println(" picoclaw migrate --refresh Re-sync workspace files")
|
|
||||||
fmt.Println(" picoclaw migrate --force Migrate without confirmation")
|
|
||||||
}
|
|
||||||
|
|
||||||
func agentCmd() {
|
|
||||||
message := ""
|
|
||||||
sessionKey := "cli:default"
|
|
||||||
|
|
||||||
args := os.Args[2:]
|
|
||||||
for i := 0; i < len(args); i++ {
|
|
||||||
switch args[i] {
|
|
||||||
case "--debug", "-d":
|
|
||||||
logger.SetLevel(logger.DEBUG)
|
|
||||||
fmt.Println("🔍 Debug mode enabled")
|
|
||||||
case "-m", "--message":
|
|
||||||
if i+1 < len(args) {
|
|
||||||
message = args[i+1]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
case "-s", "--session":
|
|
||||||
if i+1 < len(args) {
|
|
||||||
sessionKey = args[i+1]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := loadConfig()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error loading config: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
provider, err := providers.CreateProvider(cfg)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating provider: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
msgBus := bus.NewMessageBus()
|
|
||||||
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
|
||||||
|
|
||||||
// Print agent startup info (only for interactive mode)
|
|
||||||
startupInfo := agentLoop.GetStartupInfo()
|
|
||||||
logger.InfoCF("agent", "Agent initialized",
|
|
||||||
map[string]interface{}{
|
|
||||||
"tools_count": startupInfo["tools"].(map[string]interface{})["count"],
|
|
||||||
"skills_total": startupInfo["skills"].(map[string]interface{})["total"],
|
|
||||||
"skills_available": startupInfo["skills"].(map[string]interface{})["available"],
|
|
||||||
})
|
|
||||||
|
|
||||||
if message != "" {
|
|
||||||
ctx := context.Background()
|
|
||||||
response, err := agentLoop.ProcessDirect(ctx, message, sessionKey)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("\n%s %s\n", logo, response)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%s Interactive mode (Ctrl+C to exit)\n\n", logo)
|
|
||||||
interactiveMode(agentLoop, sessionKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
|
||||||
prompt := fmt.Sprintf("%s You: ", logo)
|
|
||||||
|
|
||||||
rl, err := readline.NewEx(&readline.Config{
|
|
||||||
Prompt: prompt,
|
|
||||||
HistoryFile: filepath.Join(os.TempDir(), ".picoclaw_history"),
|
|
||||||
HistoryLimit: 100,
|
|
||||||
InterruptPrompt: "^C",
|
|
||||||
EOFPrompt: "exit",
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error initializing readline: %v\n", err)
|
|
||||||
fmt.Println("Falling back to simple input mode...")
|
|
||||||
simpleInteractiveMode(agentLoop, sessionKey)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer rl.Close()
|
|
||||||
|
|
||||||
for {
|
|
||||||
line, err := rl.Readline()
|
|
||||||
if err != nil {
|
|
||||||
if err == readline.ErrInterrupt || err == io.EOF {
|
|
||||||
fmt.Println("\nGoodbye!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("Error reading input: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
input := strings.TrimSpace(line)
|
|
||||||
if input == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if input == "exit" || input == "quit" {
|
|
||||||
fmt.Println("Goodbye!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
response, err := agentLoop.ProcessDirect(ctx, input, sessionKey)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n\n", logo, response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
for {
|
|
||||||
fmt.Print(fmt.Sprintf("%s You: ", logo))
|
|
||||||
line, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
fmt.Println("\nGoodbye!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("Error reading input: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
input := strings.TrimSpace(line)
|
|
||||||
if input == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if input == "exit" || input == "quit" {
|
|
||||||
fmt.Println("Goodbye!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
response, err := agentLoop.ProcessDirect(ctx, input, sessionKey)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n\n", logo, response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func gatewayCmd() {
|
|
||||||
// Check for --debug flag
|
|
||||||
args := os.Args[2:]
|
|
||||||
for _, arg := range args {
|
|
||||||
if arg == "--debug" || arg == "-d" {
|
|
||||||
logger.SetLevel(logger.DEBUG)
|
|
||||||
fmt.Println("🔍 Debug mode enabled")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := loadConfig()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error loading config: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
provider, err := providers.CreateProvider(cfg)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating provider: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
msgBus := bus.NewMessageBus()
|
|
||||||
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
|
||||||
|
|
||||||
// Print agent startup info
|
|
||||||
fmt.Println("\n📦 Agent Status:")
|
|
||||||
startupInfo := agentLoop.GetStartupInfo()
|
|
||||||
toolsInfo := startupInfo["tools"].(map[string]interface{})
|
|
||||||
skillsInfo := startupInfo["skills"].(map[string]interface{})
|
|
||||||
fmt.Printf(" • Tools: %d loaded\n", toolsInfo["count"])
|
|
||||||
fmt.Printf(" • Skills: %d/%d available\n",
|
|
||||||
skillsInfo["available"],
|
|
||||||
skillsInfo["total"])
|
|
||||||
|
|
||||||
// Log to file as well
|
|
||||||
logger.InfoCF("agent", "Agent initialized",
|
|
||||||
map[string]interface{}{
|
|
||||||
"tools_count": toolsInfo["count"],
|
|
||||||
"skills_total": skillsInfo["total"],
|
|
||||||
"skills_available": skillsInfo["available"],
|
|
||||||
})
|
|
||||||
|
|
||||||
// Setup cron tool and service
|
|
||||||
execTimeout := time.Duration(cfg.Tools.Cron.ExecTimeoutMinutes) * time.Minute
|
|
||||||
cronService := setupCronTool(agentLoop, msgBus, cfg.WorkspacePath(), cfg.Agents.Defaults.RestrictToWorkspace, execTimeout, cfg)
|
|
||||||
|
|
||||||
heartbeatService := heartbeat.NewHeartbeatService(
|
|
||||||
cfg.WorkspacePath(),
|
|
||||||
cfg.Heartbeat.Interval,
|
|
||||||
cfg.Heartbeat.Enabled,
|
|
||||||
)
|
|
||||||
heartbeatService.SetBus(msgBus)
|
|
||||||
heartbeatService.SetHandler(func(prompt, channel, chatID string) *tools.ToolResult {
|
|
||||||
// Use cli:direct as fallback if no valid channel
|
|
||||||
if channel == "" || chatID == "" {
|
|
||||||
channel, chatID = "cli", "direct"
|
|
||||||
}
|
|
||||||
// Use ProcessHeartbeat - no session history, each heartbeat is independent
|
|
||||||
response, err := agentLoop.ProcessHeartbeat(context.Background(), prompt, channel, chatID)
|
|
||||||
if err != nil {
|
|
||||||
return tools.ErrorResult(fmt.Sprintf("Heartbeat error: %v", err))
|
|
||||||
}
|
|
||||||
if response == "HEARTBEAT_OK" {
|
|
||||||
return tools.SilentResult("Heartbeat OK")
|
|
||||||
}
|
|
||||||
// For heartbeat, always return silent - the subagent result will be
|
|
||||||
// sent to user via processSystemMessage when the async task completes
|
|
||||||
return tools.SilentResult(response)
|
|
||||||
})
|
|
||||||
|
|
||||||
channelManager, err := channels.NewManager(cfg, msgBus)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating channel manager: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inject channel manager into agent loop for command handling
|
|
||||||
agentLoop.SetChannelManager(channelManager)
|
|
||||||
|
|
||||||
var transcriber *voice.GroqTranscriber
|
|
||||||
if cfg.Providers.Groq.APIKey != "" {
|
|
||||||
transcriber = voice.NewGroqTranscriber(cfg.Providers.Groq.APIKey)
|
|
||||||
logger.InfoC("voice", "Groq voice transcription enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
if transcriber != nil {
|
|
||||||
if telegramChannel, ok := channelManager.GetChannel("telegram"); ok {
|
|
||||||
if tc, ok := telegramChannel.(*channels.TelegramChannel); ok {
|
|
||||||
tc.SetTranscriber(transcriber)
|
|
||||||
logger.InfoC("voice", "Groq transcription attached to Telegram channel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if discordChannel, ok := channelManager.GetChannel("discord"); ok {
|
|
||||||
if dc, ok := discordChannel.(*channels.DiscordChannel); ok {
|
|
||||||
dc.SetTranscriber(transcriber)
|
|
||||||
logger.InfoC("voice", "Groq transcription attached to Discord channel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if slackChannel, ok := channelManager.GetChannel("slack"); ok {
|
|
||||||
if sc, ok := slackChannel.(*channels.SlackChannel); ok {
|
|
||||||
sc.SetTranscriber(transcriber)
|
|
||||||
logger.InfoC("voice", "Groq transcription attached to Slack channel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enabledChannels := channelManager.GetEnabledChannels()
|
|
||||||
if len(enabledChannels) > 0 {
|
|
||||||
fmt.Printf("✓ Channels enabled: %s\n", enabledChannels)
|
|
||||||
} else {
|
|
||||||
fmt.Println("⚠ Warning: No channels enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("✓ Gateway started on %s:%d\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
|
||||||
fmt.Println("Press Ctrl+C to stop")
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := cronService.Start(); err != nil {
|
|
||||||
fmt.Printf("Error starting cron service: %v\n", err)
|
|
||||||
}
|
|
||||||
fmt.Println("✓ Cron service started")
|
|
||||||
|
|
||||||
if err := heartbeatService.Start(); err != nil {
|
|
||||||
fmt.Printf("Error starting heartbeat service: %v\n", err)
|
|
||||||
}
|
|
||||||
fmt.Println("✓ Heartbeat service started")
|
|
||||||
|
|
||||||
stateManager := state.NewManager(cfg.WorkspacePath())
|
|
||||||
deviceService := devices.NewService(devices.Config{
|
|
||||||
Enabled: cfg.Devices.Enabled,
|
|
||||||
MonitorUSB: cfg.Devices.MonitorUSB,
|
|
||||||
}, stateManager)
|
|
||||||
deviceService.SetBus(msgBus)
|
|
||||||
if err := deviceService.Start(ctx); err != nil {
|
|
||||||
fmt.Printf("Error starting device service: %v\n", err)
|
|
||||||
} else if cfg.Devices.Enabled {
|
|
||||||
fmt.Println("✓ Device event service started")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := channelManager.StartAll(ctx); err != nil {
|
|
||||||
fmt.Printf("Error starting channels: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
healthServer := health.NewServer(cfg.Gateway.Host, cfg.Gateway.Port)
|
|
||||||
go func() {
|
|
||||||
if err := healthServer.Start(); err != nil && err != http.ErrServerClosed {
|
|
||||||
logger.ErrorCF("health", "Health server error", map[string]interface{}{"error": err.Error()})
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
fmt.Printf("✓ Health endpoints available at http://%s:%d/health and /ready\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
|
||||||
|
|
||||||
go agentLoop.Run(ctx)
|
|
||||||
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigChan, os.Interrupt)
|
|
||||||
<-sigChan
|
|
||||||
|
|
||||||
fmt.Println("\nShutting down...")
|
|
||||||
cancel()
|
|
||||||
healthServer.Stop(context.Background())
|
|
||||||
deviceService.Stop()
|
|
||||||
heartbeatService.Stop()
|
|
||||||
cronService.Stop()
|
|
||||||
agentLoop.Stop()
|
|
||||||
channelManager.StopAll(ctx)
|
|
||||||
fmt.Println("✓ Gateway stopped")
|
|
||||||
}
|
|
||||||
|
|
||||||
func statusCmd() {
|
|
||||||
cfg, err := loadConfig()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error loading config: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
configPath := getConfigPath()
|
|
||||||
|
|
||||||
fmt.Printf("%s picoclaw Status\n", logo)
|
|
||||||
fmt.Printf("Version: %s\n", formatVersion())
|
|
||||||
build, _ := formatBuildInfo()
|
|
||||||
if build != "" {
|
|
||||||
fmt.Printf("Build: %s\n", build)
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
if _, err := os.Stat(configPath); err == nil {
|
|
||||||
fmt.Println("Config:", configPath, "✓")
|
|
||||||
} else {
|
|
||||||
fmt.Println("Config:", configPath, "✗")
|
|
||||||
}
|
|
||||||
|
|
||||||
workspace := cfg.WorkspacePath()
|
|
||||||
if _, err := os.Stat(workspace); err == nil {
|
|
||||||
fmt.Println("Workspace:", workspace, "✓")
|
|
||||||
} else {
|
|
||||||
fmt.Println("Workspace:", workspace, "✗")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(configPath); err == nil {
|
|
||||||
fmt.Printf("Model: %s\n", cfg.Agents.Defaults.Model)
|
|
||||||
|
|
||||||
hasOpenRouter := cfg.Providers.OpenRouter.APIKey != ""
|
|
||||||
hasAnthropic := cfg.Providers.Anthropic.APIKey != ""
|
|
||||||
hasOpenAI := cfg.Providers.OpenAI.APIKey != ""
|
|
||||||
hasGemini := cfg.Providers.Gemini.APIKey != ""
|
|
||||||
hasZhipu := cfg.Providers.Zhipu.APIKey != ""
|
|
||||||
hasGroq := cfg.Providers.Groq.APIKey != ""
|
|
||||||
hasVLLM := cfg.Providers.VLLM.APIBase != ""
|
|
||||||
|
|
||||||
status := func(enabled bool) string {
|
|
||||||
if enabled {
|
|
||||||
return "✓"
|
|
||||||
}
|
|
||||||
return "not set"
|
|
||||||
}
|
|
||||||
fmt.Println("OpenRouter API:", status(hasOpenRouter))
|
|
||||||
fmt.Println("Anthropic API:", status(hasAnthropic))
|
|
||||||
fmt.Println("OpenAI API:", status(hasOpenAI))
|
|
||||||
fmt.Println("Gemini API:", status(hasGemini))
|
|
||||||
fmt.Println("Zhipu API:", status(hasZhipu))
|
|
||||||
fmt.Println("Groq API:", status(hasGroq))
|
|
||||||
if hasVLLM {
|
|
||||||
fmt.Printf("vLLM/Local: ✓ %s\n", cfg.Providers.VLLM.APIBase)
|
|
||||||
} else {
|
|
||||||
fmt.Println("vLLM/Local: not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
store, _ := auth.LoadStore()
|
|
||||||
if store != nil && len(store.Credentials) > 0 {
|
|
||||||
fmt.Println("\nOAuth/Token Auth:")
|
|
||||||
for provider, cred := range store.Credentials {
|
|
||||||
status := "authenticated"
|
|
||||||
if cred.IsExpired() {
|
|
||||||
status = "expired"
|
|
||||||
} else if cred.NeedsRefresh() {
|
|
||||||
status = "needs refresh"
|
|
||||||
}
|
|
||||||
fmt.Printf(" %s (%s): %s\n", provider, cred.AuthMethod, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func authCmd() {
|
|
||||||
if len(os.Args) < 3 {
|
|
||||||
authHelp()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch os.Args[2] {
|
|
||||||
case "login":
|
|
||||||
authLoginCmd()
|
|
||||||
case "logout":
|
|
||||||
authLogoutCmd()
|
|
||||||
case "status":
|
|
||||||
authStatusCmd()
|
|
||||||
default:
|
|
||||||
fmt.Printf("Unknown auth command: %s\n", os.Args[2])
|
|
||||||
authHelp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func authHelp() {
|
|
||||||
fmt.Println("\nAuth commands:")
|
|
||||||
fmt.Println(" login Login via OAuth or paste token")
|
|
||||||
fmt.Println(" logout Remove stored credentials")
|
|
||||||
fmt.Println(" status Show current auth status")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Login options:")
|
|
||||||
fmt.Println(" --provider <name> Provider to login with (openai, anthropic)")
|
|
||||||
fmt.Println(" --device-code Use device code flow (for headless environments)")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Examples:")
|
|
||||||
fmt.Println(" picoclaw auth login --provider openai")
|
|
||||||
fmt.Println(" picoclaw auth login --provider openai --device-code")
|
|
||||||
fmt.Println(" picoclaw auth login --provider anthropic")
|
|
||||||
fmt.Println(" picoclaw auth logout --provider openai")
|
|
||||||
fmt.Println(" picoclaw auth status")
|
|
||||||
}
|
|
||||||
|
|
||||||
func authLoginCmd() {
|
|
||||||
provider := ""
|
|
||||||
useDeviceCode := false
|
|
||||||
|
|
||||||
args := os.Args[3:]
|
|
||||||
for i := 0; i < len(args); i++ {
|
|
||||||
switch args[i] {
|
|
||||||
case "--provider", "-p":
|
|
||||||
if i+1 < len(args) {
|
|
||||||
provider = args[i+1]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
case "--device-code":
|
|
||||||
useDeviceCode = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if provider == "" {
|
|
||||||
fmt.Println("Error: --provider is required")
|
|
||||||
fmt.Println("Supported providers: openai, anthropic")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch provider {
|
|
||||||
case "openai":
|
|
||||||
authLoginOpenAI(useDeviceCode)
|
|
||||||
case "anthropic":
|
|
||||||
authLoginPasteToken(provider)
|
|
||||||
default:
|
|
||||||
fmt.Printf("Unsupported provider: %s\n", provider)
|
|
||||||
fmt.Println("Supported providers: openai, anthropic")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func authLoginOpenAI(useDeviceCode bool) {
|
|
||||||
cfg := auth.OpenAIOAuthConfig()
|
|
||||||
|
|
||||||
var cred *auth.AuthCredential
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if useDeviceCode {
|
|
||||||
cred, err = auth.LoginDeviceCode(cfg)
|
|
||||||
} else {
|
|
||||||
cred, err = auth.LoginBrowser(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Login failed: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := auth.SetCredential("openai", cred); err != nil {
|
|
||||||
fmt.Printf("Failed to save credentials: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
appCfg, err := loadConfig()
|
|
||||||
if err == nil {
|
|
||||||
appCfg.Providers.OpenAI.AuthMethod = "oauth"
|
|
||||||
if err := config.SaveConfig(getConfigPath(), appCfg); err != nil {
|
|
||||||
fmt.Printf("Warning: could not update config: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Login successful!")
|
|
||||||
if cred.AccountID != "" {
|
|
||||||
fmt.Printf("Account: %s\n", cred.AccountID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func authLoginPasteToken(provider string) {
|
|
||||||
cred, err := auth.LoginPasteToken(provider, os.Stdin)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Login failed: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := auth.SetCredential(provider, cred); err != nil {
|
|
||||||
fmt.Printf("Failed to save credentials: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
appCfg, err := loadConfig()
|
|
||||||
if err == nil {
|
|
||||||
switch provider {
|
|
||||||
case "anthropic":
|
|
||||||
appCfg.Providers.Anthropic.AuthMethod = "token"
|
|
||||||
case "openai":
|
|
||||||
appCfg.Providers.OpenAI.AuthMethod = "token"
|
|
||||||
}
|
|
||||||
if err := config.SaveConfig(getConfigPath(), appCfg); err != nil {
|
|
||||||
fmt.Printf("Warning: could not update config: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Token saved for %s!\n", provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
func authLogoutCmd() {
|
|
||||||
provider := ""
|
|
||||||
|
|
||||||
args := os.Args[3:]
|
|
||||||
for i := 0; i < len(args); i++ {
|
|
||||||
switch args[i] {
|
|
||||||
case "--provider", "-p":
|
|
||||||
if i+1 < len(args) {
|
|
||||||
provider = args[i+1]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if provider != "" {
|
|
||||||
if err := auth.DeleteCredential(provider); err != nil {
|
|
||||||
fmt.Printf("Failed to remove credentials: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
appCfg, err := loadConfig()
|
|
||||||
if err == nil {
|
|
||||||
switch provider {
|
|
||||||
case "openai":
|
|
||||||
appCfg.Providers.OpenAI.AuthMethod = ""
|
|
||||||
case "anthropic":
|
|
||||||
appCfg.Providers.Anthropic.AuthMethod = ""
|
|
||||||
}
|
|
||||||
config.SaveConfig(getConfigPath(), appCfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Logged out from %s\n", provider)
|
|
||||||
} else {
|
|
||||||
if err := auth.DeleteAllCredentials(); err != nil {
|
|
||||||
fmt.Printf("Failed to remove credentials: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
appCfg, err := loadConfig()
|
|
||||||
if err == nil {
|
|
||||||
appCfg.Providers.OpenAI.AuthMethod = ""
|
|
||||||
appCfg.Providers.Anthropic.AuthMethod = ""
|
|
||||||
config.SaveConfig(getConfigPath(), appCfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Logged out from all providers")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func authStatusCmd() {
|
|
||||||
store, err := auth.LoadStore()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error loading auth store: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(store.Credentials) == 0 {
|
|
||||||
fmt.Println("No authenticated providers.")
|
|
||||||
fmt.Println("Run: picoclaw auth login --provider <name>")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("\nAuthenticated Providers:")
|
|
||||||
fmt.Println("------------------------")
|
|
||||||
for provider, cred := range store.Credentials {
|
|
||||||
status := "active"
|
|
||||||
if cred.IsExpired() {
|
|
||||||
status = "expired"
|
|
||||||
} else if cred.NeedsRefresh() {
|
|
||||||
status = "needs refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf(" %s:\n", provider)
|
|
||||||
fmt.Printf(" Method: %s\n", cred.AuthMethod)
|
|
||||||
fmt.Printf(" Status: %s\n", status)
|
|
||||||
if cred.AccountID != "" {
|
|
||||||
fmt.Printf(" Account: %s\n", cred.AccountID)
|
|
||||||
}
|
|
||||||
if !cred.ExpiresAt.IsZero() {
|
|
||||||
fmt.Printf(" Expires: %s\n", cred.ExpiresAt.Format("2006-01-02 15:04"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfigPath() string {
|
func getConfigPath() string {
|
||||||
home, _ := os.UserHomeDir()
|
home, _ := os.UserHomeDir()
|
||||||
return filepath.Join(home, ".picoclaw", "config.json")
|
return filepath.Join(home, ".picoclaw", "config.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupCronTool(agentLoop *agent.AgentLoop, msgBus *bus.MessageBus, workspace string, restrict bool, execTimeout time.Duration, config *config.Config) *cron.CronService {
|
|
||||||
cronStorePath := filepath.Join(workspace, "cron", "jobs.json")
|
|
||||||
|
|
||||||
// Create cron service
|
|
||||||
cronService := cron.NewCronService(cronStorePath, nil)
|
|
||||||
|
|
||||||
// Create and register CronTool
|
|
||||||
cronTool := tools.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout, config)
|
|
||||||
agentLoop.RegisterTool(cronTool)
|
|
||||||
|
|
||||||
// Set the onJob handler
|
|
||||||
cronService.SetOnJob(func(job *cron.CronJob) (string, error) {
|
|
||||||
result := cronTool.ExecuteJob(context.Background(), job)
|
|
||||||
return result, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return cronService
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadConfig() (*config.Config, error) {
|
func loadConfig() (*config.Config, error) {
|
||||||
return config.LoadConfig(getConfigPath())
|
return config.LoadConfig(getConfigPath())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue