feat(cli): add agent, skill, and runtime management commands
Expand CLI coverage for agent/skill/runtime APIs that previously had no CLI wrappers despite having fully implemented backend endpoints. Agent commands (was: only list): - agent get/create/update/archive/restore/tasks - agent skills list/set - agent list --include-archived Skill commands (new, was: 0% coverage): - skill list/get/create/update/delete/import - skill files list/upsert/delete Runtime commands (new, was: 0% coverage): - runtime list/usage/activity/ping/update - ping and update support --wait for polling
This commit is contained in:
parent
fc6405e4be
commit
10b482fac2
4 changed files with 1155 additions and 3 deletions
|
|
@ -2,9 +2,11 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
|
@ -24,10 +26,122 @@ var agentListCmd = &cobra.Command{
|
|||
RunE: runAgentList,
|
||||
}
|
||||
|
||||
var agentGetCmd = &cobra.Command{
|
||||
Use: "get <id>",
|
||||
Short: "Get agent details",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runAgentGet,
|
||||
}
|
||||
|
||||
var agentCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new agent",
|
||||
RunE: runAgentCreate,
|
||||
}
|
||||
|
||||
var agentUpdateCmd = &cobra.Command{
|
||||
Use: "update <id>",
|
||||
Short: "Update an agent",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runAgentUpdate,
|
||||
}
|
||||
|
||||
var agentArchiveCmd = &cobra.Command{
|
||||
Use: "archive <id>",
|
||||
Short: "Archive an agent",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runAgentArchive,
|
||||
}
|
||||
|
||||
var agentRestoreCmd = &cobra.Command{
|
||||
Use: "restore <id>",
|
||||
Short: "Restore an archived agent",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runAgentRestore,
|
||||
}
|
||||
|
||||
var agentTasksCmd = &cobra.Command{
|
||||
Use: "tasks <id>",
|
||||
Short: "List tasks for an agent",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runAgentTasks,
|
||||
}
|
||||
|
||||
// Agent skills subcommands.
|
||||
|
||||
var agentSkillsCmd = &cobra.Command{
|
||||
Use: "skills",
|
||||
Short: "Manage agent skill assignments",
|
||||
}
|
||||
|
||||
var agentSkillsListCmd = &cobra.Command{
|
||||
Use: "list <agent-id>",
|
||||
Short: "List skills assigned to an agent",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runAgentSkillsList,
|
||||
}
|
||||
|
||||
var agentSkillsSetCmd = &cobra.Command{
|
||||
Use: "set <agent-id>",
|
||||
Short: "Set skills for an agent (replaces all current assignments)",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runAgentSkillsSet,
|
||||
}
|
||||
|
||||
func init() {
|
||||
agentCmd.AddCommand(agentListCmd)
|
||||
agentCmd.AddCommand(agentGetCmd)
|
||||
agentCmd.AddCommand(agentCreateCmd)
|
||||
agentCmd.AddCommand(agentUpdateCmd)
|
||||
agentCmd.AddCommand(agentArchiveCmd)
|
||||
agentCmd.AddCommand(agentRestoreCmd)
|
||||
agentCmd.AddCommand(agentTasksCmd)
|
||||
agentCmd.AddCommand(agentSkillsCmd)
|
||||
|
||||
agentSkillsCmd.AddCommand(agentSkillsListCmd)
|
||||
agentSkillsCmd.AddCommand(agentSkillsSetCmd)
|
||||
|
||||
// agent list
|
||||
agentListCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
agentListCmd.Flags().Bool("include-archived", false, "Include archived agents")
|
||||
|
||||
// agent get
|
||||
agentGetCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
|
||||
// agent create
|
||||
agentCreateCmd.Flags().String("name", "", "Agent name (required)")
|
||||
agentCreateCmd.Flags().String("description", "", "Agent description")
|
||||
agentCreateCmd.Flags().String("instructions", "", "Agent instructions")
|
||||
agentCreateCmd.Flags().String("runtime-id", "", "Runtime ID (required)")
|
||||
agentCreateCmd.Flags().String("visibility", "private", "Visibility: private or workspace")
|
||||
agentCreateCmd.Flags().Int32("max-concurrent-tasks", 6, "Maximum concurrent tasks")
|
||||
agentCreateCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
|
||||
// agent update
|
||||
agentUpdateCmd.Flags().String("name", "", "New name")
|
||||
agentUpdateCmd.Flags().String("description", "", "New description")
|
||||
agentUpdateCmd.Flags().String("instructions", "", "New instructions")
|
||||
agentUpdateCmd.Flags().String("runtime-id", "", "New runtime ID")
|
||||
agentUpdateCmd.Flags().String("visibility", "", "New visibility: private or workspace")
|
||||
agentUpdateCmd.Flags().String("status", "", "New status")
|
||||
agentUpdateCmd.Flags().Int32("max-concurrent-tasks", 0, "New max concurrent tasks")
|
||||
agentUpdateCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
|
||||
// agent archive
|
||||
agentArchiveCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
|
||||
// agent restore
|
||||
agentRestoreCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
|
||||
// agent tasks
|
||||
agentTasksCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
|
||||
// agent skills list
|
||||
agentSkillsListCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
|
||||
// agent skills set
|
||||
agentSkillsSetCmd.Flags().StringSlice("skill-ids", nil, "Skill IDs to assign (comma-separated)")
|
||||
agentSkillsSetCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
}
|
||||
|
||||
// resolveProfile returns the --profile flag value (empty string means default profile).
|
||||
|
|
@ -90,6 +204,10 @@ func resolveWorkspaceID(cmd *cobra.Command) string {
|
|||
return cfg.WorkspaceID
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agent commands
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func runAgentList(cmd *cobra.Command, _ []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
|
|
@ -100,9 +218,16 @@ func runAgentList(cmd *cobra.Command, _ []string) error {
|
|||
defer cancel()
|
||||
|
||||
var agents []map[string]any
|
||||
path := "/api/agents"
|
||||
params := url.Values{}
|
||||
if client.WorkspaceID != "" {
|
||||
path += "?" + url.Values{"workspace_id": {client.WorkspaceID}}.Encode()
|
||||
params.Set("workspace_id", client.WorkspaceID)
|
||||
}
|
||||
if v, _ := cmd.Flags().GetBool("include-archived"); v {
|
||||
params.Set("include_archived", "true")
|
||||
}
|
||||
path := "/api/agents"
|
||||
if len(params) > 0 {
|
||||
path += "?" + params.Encode()
|
||||
}
|
||||
if err := client.GetJSON(ctx, path, &agents); err != nil {
|
||||
return fmt.Errorf("list agents: %w", err)
|
||||
|
|
@ -113,20 +238,326 @@ func runAgentList(cmd *cobra.Command, _ []string) error {
|
|||
return cli.PrintJSON(os.Stdout, agents)
|
||||
}
|
||||
|
||||
headers := []string{"ID", "NAME", "STATUS", "RUNTIME"}
|
||||
headers := []string{"ID", "NAME", "STATUS", "RUNTIME", "ARCHIVED"}
|
||||
rows := make([][]string, 0, len(agents))
|
||||
for _, a := range agents {
|
||||
archived := ""
|
||||
if v := strVal(a, "archived_at"); v != "" {
|
||||
archived = "yes"
|
||||
}
|
||||
rows = append(rows, []string{
|
||||
strVal(a, "id"),
|
||||
strVal(a, "name"),
|
||||
strVal(a, "status"),
|
||||
strVal(a, "runtime_mode"),
|
||||
archived,
|
||||
})
|
||||
}
|
||||
cli.PrintTable(os.Stdout, headers, rows)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAgentGet(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var agent map[string]any
|
||||
if err := client.GetJSON(ctx, "/api/agents/"+args[0], &agent); err != nil {
|
||||
return fmt.Errorf("get agent: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, agent)
|
||||
}
|
||||
|
||||
headers := []string{"ID", "NAME", "STATUS", "RUNTIME", "VISIBILITY", "DESCRIPTION"}
|
||||
rows := [][]string{{
|
||||
strVal(agent, "id"),
|
||||
strVal(agent, "name"),
|
||||
strVal(agent, "status"),
|
||||
strVal(agent, "runtime_mode"),
|
||||
strVal(agent, "visibility"),
|
||||
strVal(agent, "description"),
|
||||
}}
|
||||
cli.PrintTable(os.Stdout, headers, rows)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAgentCreate(cmd *cobra.Command, _ []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
if name == "" {
|
||||
return fmt.Errorf("--name is required")
|
||||
}
|
||||
runtimeID, _ := cmd.Flags().GetString("runtime-id")
|
||||
if runtimeID == "" {
|
||||
return fmt.Errorf("--runtime-id is required")
|
||||
}
|
||||
|
||||
body := map[string]any{
|
||||
"name": name,
|
||||
"runtime_id": runtimeID,
|
||||
}
|
||||
if v, _ := cmd.Flags().GetString("description"); v != "" {
|
||||
body["description"] = v
|
||||
}
|
||||
if v, _ := cmd.Flags().GetString("instructions"); v != "" {
|
||||
body["instructions"] = v
|
||||
}
|
||||
if cmd.Flags().Changed("visibility") {
|
||||
v, _ := cmd.Flags().GetString("visibility")
|
||||
body["visibility"] = v
|
||||
}
|
||||
if cmd.Flags().Changed("max-concurrent-tasks") {
|
||||
v, _ := cmd.Flags().GetInt32("max-concurrent-tasks")
|
||||
body["max_concurrent_tasks"] = v
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var result map[string]any
|
||||
if err := client.PostJSON(ctx, "/api/agents", body, &result); err != nil {
|
||||
return fmt.Errorf("create agent: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, result)
|
||||
}
|
||||
|
||||
fmt.Printf("Agent created: %s (%s)\n", strVal(result, "name"), strVal(result, "id"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAgentUpdate(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := map[string]any{}
|
||||
if cmd.Flags().Changed("name") {
|
||||
v, _ := cmd.Flags().GetString("name")
|
||||
body["name"] = v
|
||||
}
|
||||
if cmd.Flags().Changed("description") {
|
||||
v, _ := cmd.Flags().GetString("description")
|
||||
body["description"] = v
|
||||
}
|
||||
if cmd.Flags().Changed("instructions") {
|
||||
v, _ := cmd.Flags().GetString("instructions")
|
||||
body["instructions"] = v
|
||||
}
|
||||
if cmd.Flags().Changed("runtime-id") {
|
||||
v, _ := cmd.Flags().GetString("runtime-id")
|
||||
body["runtime_id"] = v
|
||||
}
|
||||
if cmd.Flags().Changed("visibility") {
|
||||
v, _ := cmd.Flags().GetString("visibility")
|
||||
body["visibility"] = v
|
||||
}
|
||||
if cmd.Flags().Changed("status") {
|
||||
v, _ := cmd.Flags().GetString("status")
|
||||
body["status"] = v
|
||||
}
|
||||
if cmd.Flags().Changed("max-concurrent-tasks") {
|
||||
v, _ := cmd.Flags().GetInt32("max-concurrent-tasks")
|
||||
body["max_concurrent_tasks"] = v
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("no fields to update; use --name, --description, --instructions, --runtime-id, --visibility, --status, or --max-concurrent-tasks")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var result map[string]any
|
||||
if err := client.PutJSON(ctx, "/api/agents/"+args[0], body, &result); err != nil {
|
||||
return fmt.Errorf("update agent: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, result)
|
||||
}
|
||||
|
||||
fmt.Printf("Agent updated: %s (%s)\n", strVal(result, "name"), strVal(result, "id"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAgentArchive(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var result map[string]any
|
||||
if err := client.PostJSON(ctx, "/api/agents/"+args[0]+"/archive", nil, &result); err != nil {
|
||||
return fmt.Errorf("archive agent: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, result)
|
||||
}
|
||||
|
||||
fmt.Printf("Agent archived: %s (%s)\n", strVal(result, "name"), strVal(result, "id"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAgentRestore(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var result map[string]any
|
||||
if err := client.PostJSON(ctx, "/api/agents/"+args[0]+"/restore", nil, &result); err != nil {
|
||||
return fmt.Errorf("restore agent: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, result)
|
||||
}
|
||||
|
||||
fmt.Printf("Agent restored: %s (%s)\n", strVal(result, "name"), strVal(result, "id"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAgentTasks(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var tasks []map[string]any
|
||||
if err := client.GetJSON(ctx, "/api/agents/"+args[0]+"/tasks", &tasks); err != nil {
|
||||
return fmt.Errorf("list agent tasks: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, tasks)
|
||||
}
|
||||
|
||||
headers := []string{"ID", "ISSUE_ID", "STATUS", "CREATED_AT"}
|
||||
rows := make([][]string, 0, len(tasks))
|
||||
for _, t := range tasks {
|
||||
rows = append(rows, []string{
|
||||
strVal(t, "id"),
|
||||
strVal(t, "issue_id"),
|
||||
strVal(t, "status"),
|
||||
strVal(t, "created_at"),
|
||||
})
|
||||
}
|
||||
cli.PrintTable(os.Stdout, headers, rows)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agent skills subcommands
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func runAgentSkillsList(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var skills []map[string]any
|
||||
if err := client.GetJSON(ctx, "/api/agents/"+args[0]+"/skills", &skills); err != nil {
|
||||
return fmt.Errorf("list agent skills: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, skills)
|
||||
}
|
||||
|
||||
headers := []string{"ID", "NAME", "DESCRIPTION"}
|
||||
rows := make([][]string, 0, len(skills))
|
||||
for _, s := range skills {
|
||||
rows = append(rows, []string{
|
||||
strVal(s, "id"),
|
||||
strVal(s, "name"),
|
||||
strVal(s, "description"),
|
||||
})
|
||||
}
|
||||
cli.PrintTable(os.Stdout, headers, rows)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAgentSkillsSet(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
skillIDs, _ := cmd.Flags().GetStringSlice("skill-ids")
|
||||
if len(skillIDs) == 0 {
|
||||
return fmt.Errorf("--skill-ids is required (comma-separated skill IDs, or empty string to clear)")
|
||||
}
|
||||
// Allow passing empty string to clear all skills.
|
||||
cleanIDs := make([]string, 0, len(skillIDs))
|
||||
for _, id := range skillIDs {
|
||||
id = strings.TrimSpace(id)
|
||||
if id != "" {
|
||||
cleanIDs = append(cleanIDs, id)
|
||||
}
|
||||
}
|
||||
|
||||
body := map[string]any{
|
||||
"skill_ids": cleanIDs,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var result json.RawMessage
|
||||
if err := client.PutJSON(ctx, "/api/agents/"+args[0]+"/skills", body, &result); err != nil {
|
||||
return fmt.Errorf("set agent skills: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
var pretty any
|
||||
json.Unmarshal(result, &pretty)
|
||||
return cli.PrintJSON(os.Stdout, pretty)
|
||||
}
|
||||
|
||||
fmt.Printf("Skills updated for agent %s\n", args[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func strVal(m map[string]any, key string) string {
|
||||
v, ok := m[key]
|
||||
if !ok || v == nil {
|
||||
|
|
|
|||
303
server/cmd/multica/cmd_runtime.go
Normal file
303
server/cmd/multica/cmd_runtime.go
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/multica-ai/multica/server/internal/cli"
|
||||
)
|
||||
|
||||
var runtimeCmd = &cobra.Command{
|
||||
Use: "runtime",
|
||||
Short: "Manage agent runtimes",
|
||||
}
|
||||
|
||||
var runtimeListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List runtimes in the workspace",
|
||||
RunE: runRuntimeList,
|
||||
}
|
||||
|
||||
var runtimeUsageCmd = &cobra.Command{
|
||||
Use: "usage <runtime-id>",
|
||||
Short: "Get token usage for a runtime",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runRuntimeUsage,
|
||||
}
|
||||
|
||||
var runtimeActivityCmd = &cobra.Command{
|
||||
Use: "activity <runtime-id>",
|
||||
Short: "Get hourly task activity for a runtime",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runRuntimeActivity,
|
||||
}
|
||||
|
||||
var runtimePingCmd = &cobra.Command{
|
||||
Use: "ping <runtime-id>",
|
||||
Short: "Ping a runtime to check connectivity",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runRuntimePing,
|
||||
}
|
||||
|
||||
var runtimeUpdateCmd = &cobra.Command{
|
||||
Use: "update <runtime-id>",
|
||||
Short: "Initiate a CLI update on a runtime",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runRuntimeUpdate,
|
||||
}
|
||||
|
||||
func init() {
|
||||
runtimeCmd.AddCommand(runtimeListCmd)
|
||||
runtimeCmd.AddCommand(runtimeUsageCmd)
|
||||
runtimeCmd.AddCommand(runtimeActivityCmd)
|
||||
runtimeCmd.AddCommand(runtimePingCmd)
|
||||
runtimeCmd.AddCommand(runtimeUpdateCmd)
|
||||
|
||||
// runtime list
|
||||
runtimeListCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
|
||||
// runtime usage
|
||||
runtimeUsageCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
runtimeUsageCmd.Flags().Int("days", 90, "Number of days of usage data to retrieve (max 365)")
|
||||
|
||||
// runtime activity
|
||||
runtimeActivityCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
|
||||
// runtime ping
|
||||
runtimePingCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
runtimePingCmd.Flags().Bool("wait", false, "Wait for ping to complete (poll until done)")
|
||||
|
||||
// runtime update
|
||||
runtimeUpdateCmd.Flags().String("target-version", "", "Target version to update to (required)")
|
||||
runtimeUpdateCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
runtimeUpdateCmd.Flags().Bool("wait", false, "Wait for update to complete (poll until done)")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Runtime commands
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func runRuntimeList(cmd *cobra.Command, _ []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var runtimes []map[string]any
|
||||
if err := client.GetJSON(ctx, "/api/runtimes", &runtimes); err != nil {
|
||||
return fmt.Errorf("list runtimes: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, runtimes)
|
||||
}
|
||||
|
||||
headers := []string{"ID", "NAME", "MODE", "PROVIDER", "STATUS", "LAST_SEEN"}
|
||||
rows := make([][]string, 0, len(runtimes))
|
||||
for _, rt := range runtimes {
|
||||
rows = append(rows, []string{
|
||||
strVal(rt, "id"),
|
||||
strVal(rt, "name"),
|
||||
strVal(rt, "runtime_mode"),
|
||||
strVal(rt, "provider"),
|
||||
strVal(rt, "status"),
|
||||
strVal(rt, "last_seen_at"),
|
||||
})
|
||||
}
|
||||
cli.PrintTable(os.Stdout, headers, rows)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRuntimeUsage(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
days, _ := cmd.Flags().GetInt("days")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var usage []map[string]any
|
||||
path := fmt.Sprintf("/api/runtimes/%s/usage?days=%d", args[0], days)
|
||||
if err := client.GetJSON(ctx, path, &usage); err != nil {
|
||||
return fmt.Errorf("get runtime usage: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, usage)
|
||||
}
|
||||
|
||||
headers := []string{"DATE", "PROVIDER", "MODEL", "INPUT_TOKENS", "OUTPUT_TOKENS", "CACHE_READ", "CACHE_WRITE"}
|
||||
rows := make([][]string, 0, len(usage))
|
||||
for _, u := range usage {
|
||||
rows = append(rows, []string{
|
||||
strVal(u, "date"),
|
||||
strVal(u, "provider"),
|
||||
strVal(u, "model"),
|
||||
strVal(u, "input_tokens"),
|
||||
strVal(u, "output_tokens"),
|
||||
strVal(u, "cache_read_tokens"),
|
||||
strVal(u, "cache_write_tokens"),
|
||||
})
|
||||
}
|
||||
cli.PrintTable(os.Stdout, headers, rows)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRuntimeActivity(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var activity []map[string]any
|
||||
if err := client.GetJSON(ctx, "/api/runtimes/"+args[0]+"/activity", &activity); err != nil {
|
||||
return fmt.Errorf("get runtime activity: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, activity)
|
||||
}
|
||||
|
||||
headers := []string{"HOUR", "COUNT"}
|
||||
rows := make([][]string, 0, len(activity))
|
||||
for _, a := range activity {
|
||||
rows = append(rows, []string{
|
||||
strVal(a, "hour"),
|
||||
strVal(a, "count"),
|
||||
})
|
||||
}
|
||||
cli.PrintTable(os.Stdout, headers, rows)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRuntimePing(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Initiate ping.
|
||||
var ping map[string]any
|
||||
if err := client.PostJSON(ctx, "/api/runtimes/"+args[0]+"/ping", nil, &ping); err != nil {
|
||||
return fmt.Errorf("initiate ping: %w", err)
|
||||
}
|
||||
|
||||
wait, _ := cmd.Flags().GetBool("wait")
|
||||
if !wait {
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, ping)
|
||||
}
|
||||
fmt.Printf("Ping initiated: %s (status: %s)\n", strVal(ping, "id"), strVal(ping, "status"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Poll until completed/failed/timeout.
|
||||
pingID := strVal(ping, "id")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("timed out waiting for ping")
|
||||
case <-time.After(1 * time.Second):
|
||||
}
|
||||
|
||||
if err := client.GetJSON(ctx, "/api/runtimes/"+args[0]+"/ping/"+pingID, &ping); err != nil {
|
||||
return fmt.Errorf("get ping status: %w", err)
|
||||
}
|
||||
|
||||
status := strVal(ping, "status")
|
||||
if status == "completed" || status == "failed" || status == "timeout" {
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, ping)
|
||||
}
|
||||
if status == "completed" {
|
||||
fmt.Printf("Ping completed in %sms\n", strVal(ping, "duration_ms"))
|
||||
} else {
|
||||
fmt.Printf("Ping %s: %s\n", status, strVal(ping, "error"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runRuntimeUpdate(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetVersion, _ := cmd.Flags().GetString("target-version")
|
||||
if targetVersion == "" {
|
||||
return fmt.Errorf("--target-version is required")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 150*time.Second)
|
||||
defer cancel()
|
||||
|
||||
body := map[string]any{
|
||||
"target_version": targetVersion,
|
||||
}
|
||||
|
||||
var update map[string]any
|
||||
if err := client.PostJSON(ctx, "/api/runtimes/"+args[0]+"/update", body, &update); err != nil {
|
||||
return fmt.Errorf("initiate update: %w", err)
|
||||
}
|
||||
|
||||
wait, _ := cmd.Flags().GetBool("wait")
|
||||
if !wait {
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, update)
|
||||
}
|
||||
fmt.Printf("Update initiated: %s (status: %s)\n", strVal(update, "id"), strVal(update, "status"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Poll until completed/failed/timeout.
|
||||
updateID := strVal(update, "id")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("timed out waiting for update")
|
||||
case <-time.After(2 * time.Second):
|
||||
}
|
||||
|
||||
if err := client.GetJSON(ctx, "/api/runtimes/"+args[0]+"/update/"+updateID, &update); err != nil {
|
||||
return fmt.Errorf("get update status: %w", err)
|
||||
}
|
||||
|
||||
status := strVal(update, "status")
|
||||
if status == "completed" || status == "failed" || status == "timeout" {
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, update)
|
||||
}
|
||||
if status == "completed" {
|
||||
fmt.Printf("Update completed: %s\n", strVal(update, "output"))
|
||||
} else {
|
||||
fmt.Printf("Update %s: %s\n", status, strVal(update, "error"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
416
server/cmd/multica/cmd_skill.go
Normal file
416
server/cmd/multica/cmd_skill.go
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/multica-ai/multica/server/internal/cli"
|
||||
)
|
||||
|
||||
var skillCmd = &cobra.Command{
|
||||
Use: "skill",
|
||||
Short: "Manage skills",
|
||||
}
|
||||
|
||||
var skillListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List skills in the workspace",
|
||||
RunE: runSkillList,
|
||||
}
|
||||
|
||||
var skillGetCmd = &cobra.Command{
|
||||
Use: "get <id>",
|
||||
Short: "Get skill details (includes files)",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runSkillGet,
|
||||
}
|
||||
|
||||
var skillCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new skill",
|
||||
RunE: runSkillCreate,
|
||||
}
|
||||
|
||||
var skillUpdateCmd = &cobra.Command{
|
||||
Use: "update <id>",
|
||||
Short: "Update a skill",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runSkillUpdate,
|
||||
}
|
||||
|
||||
var skillDeleteCmd = &cobra.Command{
|
||||
Use: "delete <id>",
|
||||
Short: "Delete a skill",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runSkillDelete,
|
||||
}
|
||||
|
||||
var skillImportCmd = &cobra.Command{
|
||||
Use: "import",
|
||||
Short: "Import a skill from a URL (clawhub.ai or skills.sh)",
|
||||
RunE: runSkillImport,
|
||||
}
|
||||
|
||||
// Skill file subcommands.
|
||||
|
||||
var skillFilesCmd = &cobra.Command{
|
||||
Use: "files",
|
||||
Short: "Manage skill files",
|
||||
}
|
||||
|
||||
var skillFilesListCmd = &cobra.Command{
|
||||
Use: "list <skill-id>",
|
||||
Short: "List files for a skill",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runSkillFilesList,
|
||||
}
|
||||
|
||||
var skillFilesUpsertCmd = &cobra.Command{
|
||||
Use: "upsert <skill-id>",
|
||||
Short: "Create or update a skill file",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runSkillFilesUpsert,
|
||||
}
|
||||
|
||||
var skillFilesDeleteCmd = &cobra.Command{
|
||||
Use: "delete <skill-id> <file-id>",
|
||||
Short: "Delete a skill file",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: runSkillFilesDelete,
|
||||
}
|
||||
|
||||
func init() {
|
||||
skillCmd.AddCommand(skillListCmd)
|
||||
skillCmd.AddCommand(skillGetCmd)
|
||||
skillCmd.AddCommand(skillCreateCmd)
|
||||
skillCmd.AddCommand(skillUpdateCmd)
|
||||
skillCmd.AddCommand(skillDeleteCmd)
|
||||
skillCmd.AddCommand(skillImportCmd)
|
||||
skillCmd.AddCommand(skillFilesCmd)
|
||||
|
||||
skillFilesCmd.AddCommand(skillFilesListCmd)
|
||||
skillFilesCmd.AddCommand(skillFilesUpsertCmd)
|
||||
skillFilesCmd.AddCommand(skillFilesDeleteCmd)
|
||||
|
||||
// skill list
|
||||
skillListCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
|
||||
// skill get
|
||||
skillGetCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
|
||||
// skill create
|
||||
skillCreateCmd.Flags().String("name", "", "Skill name (required)")
|
||||
skillCreateCmd.Flags().String("description", "", "Skill description")
|
||||
skillCreateCmd.Flags().String("content", "", "Skill content (SKILL.md body)")
|
||||
skillCreateCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
|
||||
// skill update
|
||||
skillUpdateCmd.Flags().String("name", "", "New name")
|
||||
skillUpdateCmd.Flags().String("description", "", "New description")
|
||||
skillUpdateCmd.Flags().String("content", "", "New content")
|
||||
skillUpdateCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
|
||||
// skill delete (no extra flags)
|
||||
|
||||
// skill import
|
||||
skillImportCmd.Flags().String("url", "", "URL to import from (required)")
|
||||
skillImportCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
|
||||
// skill files list
|
||||
skillFilesListCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
|
||||
// skill files upsert
|
||||
skillFilesUpsertCmd.Flags().String("path", "", "File path within the skill (required)")
|
||||
skillFilesUpsertCmd.Flags().String("content", "", "File content (required)")
|
||||
skillFilesUpsertCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Skill commands
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func runSkillList(cmd *cobra.Command, _ []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var skills []map[string]any
|
||||
if err := client.GetJSON(ctx, "/api/skills", &skills); err != nil {
|
||||
return fmt.Errorf("list skills: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, skills)
|
||||
}
|
||||
|
||||
headers := []string{"ID", "NAME", "DESCRIPTION", "CREATED_AT"}
|
||||
rows := make([][]string, 0, len(skills))
|
||||
for _, s := range skills {
|
||||
rows = append(rows, []string{
|
||||
strVal(s, "id"),
|
||||
strVal(s, "name"),
|
||||
strVal(s, "description"),
|
||||
strVal(s, "created_at"),
|
||||
})
|
||||
}
|
||||
cli.PrintTable(os.Stdout, headers, rows)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSkillGet(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var skill map[string]any
|
||||
if err := client.GetJSON(ctx, "/api/skills/"+args[0], &skill); err != nil {
|
||||
return fmt.Errorf("get skill: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, skill)
|
||||
}
|
||||
|
||||
headers := []string{"ID", "NAME", "DESCRIPTION", "CREATED_AT"}
|
||||
rows := [][]string{{
|
||||
strVal(skill, "id"),
|
||||
strVal(skill, "name"),
|
||||
strVal(skill, "description"),
|
||||
strVal(skill, "created_at"),
|
||||
}}
|
||||
cli.PrintTable(os.Stdout, headers, rows)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSkillCreate(cmd *cobra.Command, _ []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
if name == "" {
|
||||
return fmt.Errorf("--name is required")
|
||||
}
|
||||
|
||||
body := map[string]any{
|
||||
"name": name,
|
||||
}
|
||||
if v, _ := cmd.Flags().GetString("description"); v != "" {
|
||||
body["description"] = v
|
||||
}
|
||||
if v, _ := cmd.Flags().GetString("content"); v != "" {
|
||||
body["content"] = v
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var result map[string]any
|
||||
if err := client.PostJSON(ctx, "/api/skills", body, &result); err != nil {
|
||||
return fmt.Errorf("create skill: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, result)
|
||||
}
|
||||
|
||||
fmt.Printf("Skill created: %s (%s)\n", strVal(result, "name"), strVal(result, "id"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSkillUpdate(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := map[string]any{}
|
||||
if cmd.Flags().Changed("name") {
|
||||
v, _ := cmd.Flags().GetString("name")
|
||||
body["name"] = v
|
||||
}
|
||||
if cmd.Flags().Changed("description") {
|
||||
v, _ := cmd.Flags().GetString("description")
|
||||
body["description"] = v
|
||||
}
|
||||
if cmd.Flags().Changed("content") {
|
||||
v, _ := cmd.Flags().GetString("content")
|
||||
body["content"] = v
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("no fields to update; use --name, --description, or --content")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var result map[string]any
|
||||
if err := client.PutJSON(ctx, "/api/skills/"+args[0], body, &result); err != nil {
|
||||
return fmt.Errorf("update skill: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, result)
|
||||
}
|
||||
|
||||
fmt.Printf("Skill updated: %s (%s)\n", strVal(result, "name"), strVal(result, "id"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSkillDelete(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := client.DeleteJSON(ctx, "/api/skills/"+args[0]); err != nil {
|
||||
return fmt.Errorf("delete skill: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Skill deleted: %s\n", args[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSkillImport(cmd *cobra.Command, _ []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
importURL, _ := cmd.Flags().GetString("url")
|
||||
if importURL == "" {
|
||||
return fmt.Errorf("--url is required")
|
||||
}
|
||||
|
||||
body := map[string]any{
|
||||
"url": importURL,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var result map[string]any
|
||||
if err := client.PostJSON(ctx, "/api/skills/import", body, &result); err != nil {
|
||||
return fmt.Errorf("import skill: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, result)
|
||||
}
|
||||
|
||||
fmt.Printf("Skill imported: %s (%s)\n", strVal(result, "name"), strVal(result, "id"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Skill file subcommands
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func runSkillFilesList(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var files []map[string]any
|
||||
if err := client.GetJSON(ctx, "/api/skills/"+args[0]+"/files", &files); err != nil {
|
||||
return fmt.Errorf("list skill files: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, files)
|
||||
}
|
||||
|
||||
headers := []string{"ID", "PATH", "CREATED_AT", "UPDATED_AT"}
|
||||
rows := make([][]string, 0, len(files))
|
||||
for _, f := range files {
|
||||
rows = append(rows, []string{
|
||||
strVal(f, "id"),
|
||||
strVal(f, "path"),
|
||||
strVal(f, "created_at"),
|
||||
strVal(f, "updated_at"),
|
||||
})
|
||||
}
|
||||
cli.PrintTable(os.Stdout, headers, rows)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSkillFilesUpsert(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filePath, _ := cmd.Flags().GetString("path")
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("--path is required")
|
||||
}
|
||||
content, _ := cmd.Flags().GetString("content")
|
||||
if content == "" {
|
||||
return fmt.Errorf("--content is required")
|
||||
}
|
||||
|
||||
body := map[string]any{
|
||||
"path": filePath,
|
||||
"content": content,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var result map[string]any
|
||||
if err := client.PutJSON(ctx, "/api/skills/"+args[0]+"/files", body, &result); err != nil {
|
||||
return fmt.Errorf("upsert skill file: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, result)
|
||||
}
|
||||
|
||||
fmt.Printf("Skill file upserted: %s (%s)\n", strVal(result, "path"), strVal(result, "id"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSkillFilesDelete(cmd *cobra.Command, args []string) error {
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := client.DeleteJSON(ctx, "/api/skills/"+args[0]+"/files/"+args[1]); err != nil {
|
||||
return fmt.Errorf("delete skill file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Skill file deleted: %s\n", args[1])
|
||||
return nil
|
||||
}
|
||||
|
|
@ -36,6 +36,8 @@ func init() {
|
|||
rootCmd.AddCommand(repoCmd)
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
rootCmd.AddCommand(updateCmd)
|
||||
rootCmd.AddCommand(skillCmd)
|
||||
rootCmd.AddCommand(runtimeCmd)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue