Compare commits

..

1 Commits

Author SHA1 Message Date
zhi
dbc599171f feat: schedule-type CLI commands
- hf schedule-type list
- hf schedule-type create <name> --work <from>-<to> --entertainment <from>-<to>
- hf schedule-type delete <id>
- hf assign-schedule-type <agent-id> <schedule-type-name>

Requires schedule_type.read / schedule_type.manage permissions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 09:25:39 +00:00
3 changed files with 225 additions and 0 deletions

View File

@@ -224,6 +224,12 @@ func handleGroup(group help.Group, args []string) {
case "monitor": case "monitor":
handleMonitorCommand(sub.Name, remaining) handleMonitorCommand(sub.Name, remaining)
return return
case "schedule-type":
handleScheduleTypeCommand(sub.Name, remaining)
return
case "assign-schedule-type":
handleAssignScheduleType(remaining)
return
} }
if len(args) > 0 && args[0] == "update-discord-id" { if len(args) > 0 && args[0] == "update-discord-id" {
@@ -1133,3 +1139,47 @@ func handleMonitorAPIKeyCommand(args []string, tokenFlag string) {
output.Errorf("unknown monitor api-key subcommand: %s", subCmd) output.Errorf("unknown monitor api-key subcommand: %s", subCmd)
} }
} }
func handleScheduleTypeCommand(subCmd string, args []string) {
tokenFlag := ""
var filtered []string
for i := 0; i < len(args); i++ {
switch args[i] {
case "--token":
if i+1 < len(args) {
i++
tokenFlag = args[i]
}
default:
filtered = append(filtered, args[i])
}
}
switch subCmd {
case "list":
commands.RunScheduleTypeList(tokenFlag)
case "create":
commands.RunScheduleTypeCreate(filtered, tokenFlag)
case "delete":
commands.RunScheduleTypeDelete(filtered, tokenFlag)
default:
output.Errorf("hf schedule-type %s is not implemented yet", subCmd)
}
}
func handleAssignScheduleType(args []string) {
tokenFlag := ""
var filtered []string
for i := 0; i < len(args); i++ {
switch args[i] {
case "--token":
if i+1 < len(args) {
i++
tokenFlag = args[i]
}
default:
filtered = append(filtered, args[i])
}
}
commands.RunAssignScheduleType(filtered, tokenFlag)
}

View File

@@ -0,0 +1,165 @@
package commands
import (
"bytes"
"encoding/json"
"fmt"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/client"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/config"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/output"
)
type scheduleTypeResponse struct {
ID int `json:"id"`
Name string `json:"name"`
WorkFrom int `json:"work_from"`
WorkTo int `json:"work_to"`
EntertainmentFrom int `json:"entertainment_from"`
EntertainmentTo int `json:"entertainment_to"`
}
// RunScheduleTypeList implements `hf schedule-type list`.
func RunScheduleTypeList(tokenFlag string) {
token := ResolveToken(tokenFlag)
cfg, err := config.Load()
if err != nil {
output.Errorf("config error: %v", err)
}
c := client.New(cfg.BaseURL, token)
data, err := c.Get("/schedule-types/")
if err != nil {
output.Errorf("failed to list schedule types: %v", err)
}
var types []scheduleTypeResponse
if err := json.Unmarshal(data, &types); err != nil {
output.Errorf("invalid response: %v", err)
}
if output.JSONMode {
output.PrintJSON(types)
return
}
if len(types) == 0 {
fmt.Println("No schedule types defined.")
return
}
fmt.Printf("%-4s %-20s %-12s %-12s\n", "ID", "Name", "Work", "Entertainment")
fmt.Printf("%-4s %-20s %-12s %-12s\n", "----", "--------------------", "------------", "------------")
for _, t := range types {
fmt.Printf("%-4d %-20s %02d:00-%02d:00 %02d:00-%02d:00\n",
t.ID, t.Name, t.WorkFrom, t.WorkTo, t.EntertainmentFrom, t.EntertainmentTo)
}
}
// RunScheduleTypeCreate implements `hf schedule-type create <name> --work <from>-<to> --entertainment <from>-<to>`.
func RunScheduleTypeCreate(args []string, tokenFlag string) {
token := ResolveToken(tokenFlag)
if len(args) < 1 {
output.Error("usage: hf schedule-type create <name> --work <from>-<to> --entertainment <from>-<to>")
}
name := args[0]
workFrom, workTo, entFrom, entTo := -1, -1, -1, -1
for i := 1; i < len(args); i++ {
switch args[i] {
case "--work":
if i+1 < len(args) {
i++
fmt.Sscanf(args[i], "%d-%d", &workFrom, &workTo)
}
case "--entertainment":
if i+1 < len(args) {
i++
fmt.Sscanf(args[i], "%d-%d", &entFrom, &entTo)
}
}
}
if workFrom < 0 || workTo < 0 || entFrom < 0 || entTo < 0 {
output.Error("usage: hf schedule-type create <name> --work <from>-<to> --entertainment <from>-<to>\n e.g.: hf schedule-type create standard --work 8-18 --entertainment 19-23")
}
body := map[string]any{
"name": name,
"work_from": workFrom,
"work_to": workTo,
"entertainment_from": entFrom,
"entertainment_to": entTo,
}
cfg, err := config.Load()
if err != nil {
output.Errorf("config error: %v", err)
}
jsonBody, _ := json.Marshal(body)
c := client.New(cfg.BaseURL, token)
data, err := c.Post("/schedule-types/", bytes.NewReader(jsonBody))
if err != nil {
output.Errorf("failed to create schedule type: %v", err)
}
var resp scheduleTypeResponse
json.Unmarshal(data, &resp)
fmt.Printf("Created schedule type: %s (id=%d, work=%02d:00-%02d:00, entertainment=%02d:00-%02d:00)\n",
resp.Name, resp.ID, resp.WorkFrom, resp.WorkTo, resp.EntertainmentFrom, resp.EntertainmentTo)
}
// RunScheduleTypeDelete implements `hf schedule-type delete <id>`.
func RunScheduleTypeDelete(args []string, tokenFlag string) {
token := ResolveToken(tokenFlag)
if len(args) < 1 {
output.Error("usage: hf schedule-type delete <id>")
}
cfg, err := config.Load()
if err != nil {
output.Errorf("config error: %v", err)
}
c := client.New(cfg.BaseURL, token)
_, err = c.Delete("/schedule-types/" + args[0])
if err != nil {
output.Errorf("failed to delete schedule type: %v", err)
}
fmt.Printf("Deleted schedule type %s\n", args[0])
}
// RunAssignScheduleType implements `hf assign-schedule-type <agent-id> <schedule-type-name>`.
func RunAssignScheduleType(args []string, tokenFlag string) {
token := ResolveToken(tokenFlag)
if len(args) < 2 {
output.Error("usage: hf assign-schedule-type <agent-id> <schedule-type-name>")
}
agentID := args[0]
scheduleName := args[1]
body := map[string]string{
"schedule_type_name": scheduleName,
}
cfg, err := config.Load()
if err != nil {
output.Errorf("config error: %v", err)
}
jsonBody, _ := json.Marshal(body)
c := client.New(cfg.BaseURL, token)
data, err := c.Put("/schedule-types/agent/"+agentID+"/assign", bytes.NewReader(jsonBody))
if err != nil {
output.Errorf("failed to assign schedule type: %v", err)
}
var resp map[string]any
json.Unmarshal(data, &resp)
fmt.Printf("Assigned schedule type '%s' to agent '%s'\n", scheduleName, agentID)
}

View File

@@ -180,6 +180,16 @@ func CommandSurface() []Group {
{Name: "api-key", Description: "Manage monitor API keys", Permitted: has(perms, "monitor.manage")}, {Name: "api-key", Description: "Manage monitor API keys", Permitted: has(perms, "monitor.manage")},
}, },
}, },
{
Name: "schedule-type",
Description: "Manage work/entertainment schedule types",
SubCommands: []Command{
{Name: "list", Description: "List schedule types", Permitted: has(perms, "schedule_type.read")},
{Name: "create", Description: "Create a schedule type", Permitted: has(perms, "schedule_type.manage")},
{Name: "delete", Description: "Delete a schedule type", Permitted: has(perms, "schedule_type.manage")},
},
},
{Name: "assign-schedule-type", Description: "Assign a schedule type to an agent: assign-schedule-type <agent-id> <type-name>", Permitted: has(perms, "schedule_type.manage")},
} }
for i := range groups { for i := range groups {