mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
ef7078a356
Refactor command handlers into separate files to improve code organization and maintainability. Each command (agent, auth, cron, gateway, migrate, onboard, skills, status) now has its own dedicated file. Restructure provider creation to support new model_list configuration system that enables zero-code addition of OpenAI-compatible providers. Move legacy provider logic to separate file for backward compatibility. Move configuration functions from config.go to separate files (defaults.go, migration.go) for better organization.
228 lines
4.9 KiB
Go
228 lines
4.9 KiB
Go
// PicoClaw - Ultra-lightweight personal AI agent
|
|
// License: MIT
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/cron"
|
|
)
|
|
|
|
func cronCmd() {
|
|
if len(os.Args) < 3 {
|
|
cronHelp()
|
|
return
|
|
}
|
|
|
|
subcommand := os.Args[2]
|
|
|
|
// Load config to get workspace path
|
|
cfg, err := loadConfig()
|
|
if err != nil {
|
|
fmt.Printf("Error loading config: %v\n", err)
|
|
return
|
|
}
|
|
|
|
cronStorePath := filepath.Join(cfg.WorkspacePath(), "cron", "jobs.json")
|
|
|
|
switch subcommand {
|
|
case "list":
|
|
cronListCmd(cronStorePath)
|
|
case "add":
|
|
cronAddCmd(cronStorePath)
|
|
case "remove":
|
|
if len(os.Args) < 4 {
|
|
fmt.Println("Usage: picoclaw cron remove <job_id>")
|
|
return
|
|
}
|
|
cronRemoveCmd(cronStorePath, os.Args[3])
|
|
case "enable":
|
|
cronEnableCmd(cronStorePath, false)
|
|
case "disable":
|
|
cronEnableCmd(cronStorePath, true)
|
|
default:
|
|
fmt.Printf("Unknown cron command: %s\n", subcommand)
|
|
cronHelp()
|
|
}
|
|
}
|
|
|
|
func cronHelp() {
|
|
fmt.Println("\nCron commands:")
|
|
fmt.Println(" list List all scheduled jobs")
|
|
fmt.Println(" add Add a new scheduled job")
|
|
fmt.Println(" remove <id> Remove a job by ID")
|
|
fmt.Println(" enable <id> Enable a job")
|
|
fmt.Println(" disable <id> Disable a job")
|
|
fmt.Println()
|
|
fmt.Println("Add options:")
|
|
fmt.Println(" -n, --name Job name")
|
|
fmt.Println(" -m, --message Message for agent")
|
|
fmt.Println(" -e, --every Run every N seconds")
|
|
fmt.Println(" -c, --cron Cron expression (e.g. '0 9 * * *')")
|
|
fmt.Println(" -d, --deliver Deliver response to channel")
|
|
fmt.Println(" --to Recipient for delivery")
|
|
fmt.Println(" --channel Channel for delivery")
|
|
}
|
|
|
|
func cronListCmd(storePath string) {
|
|
cs := cron.NewCronService(storePath, nil)
|
|
jobs := cs.ListJobs(true) // Show all jobs, including disabled
|
|
|
|
if len(jobs) == 0 {
|
|
fmt.Println("No scheduled jobs.")
|
|
return
|
|
}
|
|
|
|
fmt.Println("\nScheduled Jobs:")
|
|
fmt.Println("----------------")
|
|
for _, job := range jobs {
|
|
var schedule string
|
|
if job.Schedule.Kind == "every" && job.Schedule.EveryMS != nil {
|
|
schedule = fmt.Sprintf("every %ds", *job.Schedule.EveryMS/1000)
|
|
} else if job.Schedule.Kind == "cron" {
|
|
schedule = job.Schedule.Expr
|
|
} else {
|
|
schedule = "one-time"
|
|
}
|
|
|
|
nextRun := "scheduled"
|
|
if job.State.NextRunAtMS != nil {
|
|
nextTime := time.UnixMilli(*job.State.NextRunAtMS)
|
|
nextRun = nextTime.Format("2006-01-02 15:04")
|
|
}
|
|
|
|
status := "enabled"
|
|
if !job.Enabled {
|
|
status = "disabled"
|
|
}
|
|
|
|
fmt.Printf(" %s (%s)\n", job.Name, job.ID)
|
|
fmt.Printf(" Schedule: %s\n", schedule)
|
|
fmt.Printf(" Status: %s\n", status)
|
|
fmt.Printf(" Next run: %s\n", nextRun)
|
|
}
|
|
}
|
|
|
|
func cronAddCmd(storePath string) {
|
|
name := ""
|
|
message := ""
|
|
var everySec *int64
|
|
cronExpr := ""
|
|
deliver := false
|
|
channel := ""
|
|
to := ""
|
|
|
|
args := os.Args[3:]
|
|
for i := 0; i < len(args); i++ {
|
|
switch args[i] {
|
|
case "-n", "--name":
|
|
if i+1 < len(args) {
|
|
name = args[i+1]
|
|
i++
|
|
}
|
|
case "-m", "--message":
|
|
if i+1 < len(args) {
|
|
message = args[i+1]
|
|
i++
|
|
}
|
|
case "-e", "--every":
|
|
if i+1 < len(args) {
|
|
var sec int64
|
|
fmt.Sscanf(args[i+1], "%d", &sec)
|
|
everySec = &sec
|
|
i++
|
|
}
|
|
case "-c", "--cron":
|
|
if i+1 < len(args) {
|
|
cronExpr = args[i+1]
|
|
i++
|
|
}
|
|
case "-d", "--deliver":
|
|
deliver = true
|
|
case "--to":
|
|
if i+1 < len(args) {
|
|
to = args[i+1]
|
|
i++
|
|
}
|
|
case "--channel":
|
|
if i+1 < len(args) {
|
|
channel = args[i+1]
|
|
i++
|
|
}
|
|
}
|
|
}
|
|
|
|
if name == "" {
|
|
fmt.Println("Error: --name is required")
|
|
return
|
|
}
|
|
|
|
if message == "" {
|
|
fmt.Println("Error: --message is required")
|
|
return
|
|
}
|
|
|
|
if everySec == nil && cronExpr == "" {
|
|
fmt.Println("Error: Either --every or --cron must be specified")
|
|
return
|
|
}
|
|
|
|
var schedule cron.CronSchedule
|
|
if everySec != nil {
|
|
everyMS := *everySec * 1000
|
|
schedule = cron.CronSchedule{
|
|
Kind: "every",
|
|
EveryMS: &everyMS,
|
|
}
|
|
} else {
|
|
schedule = cron.CronSchedule{
|
|
Kind: "cron",
|
|
Expr: cronExpr,
|
|
}
|
|
}
|
|
|
|
cs := cron.NewCronService(storePath, nil)
|
|
job, err := cs.AddJob(name, schedule, message, deliver, channel, to)
|
|
if err != nil {
|
|
fmt.Printf("Error adding job: %v\n", err)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("✓ Added job '%s' (%s)\n", job.Name, job.ID)
|
|
}
|
|
|
|
func cronRemoveCmd(storePath, jobID string) {
|
|
cs := cron.NewCronService(storePath, nil)
|
|
if cs.RemoveJob(jobID) {
|
|
fmt.Printf("✓ Removed job %s\n", jobID)
|
|
} else {
|
|
fmt.Printf("✗ Job %s not found\n", jobID)
|
|
}
|
|
}
|
|
|
|
func cronEnableCmd(storePath string, disable bool) {
|
|
if len(os.Args) < 4 {
|
|
fmt.Println("Usage: picoclaw cron enable/disable <job_id>")
|
|
return
|
|
}
|
|
|
|
jobID := os.Args[3]
|
|
cs := cron.NewCronService(storePath, nil)
|
|
enabled := !disable
|
|
|
|
job := cs.EnableJob(jobID, enabled)
|
|
if job != nil {
|
|
status := "enabled"
|
|
if disable {
|
|
status = "disabled"
|
|
}
|
|
fmt.Printf("✓ Job '%s' %s\n", job.Name, status)
|
|
} else {
|
|
fmt.Printf("✗ Job %s not found\n", jobID)
|
|
}
|
|
}
|