core/plan/command.go
core/plan/command.goBrowse 1970 files
1,601 tokens
6,161 bytes
Token encoding: o200k_base
Snapshot 21a254f
← Back to SKILL.md
1package plan2 3import (4 "encoding/json"5 "fmt"6 "os"7 "strings"8)9 10type Command interface {11 CommandType() string12 Spreadable13}14 15type ExecOptions struct {16 CustomName string17}18 19// ExecCommand represents a shell command to be executed during the build20type ExecCommand struct {21 Cmd string `json:"cmd" jsonschema:"description=The shell command to execute (e.g. 'go build' or 'npm install')"`22 CustomName string `json:"customName,omitempty" jsonschema:"description=Optional custom name to display for this command in build output"`23}24 25// PathCommand represents adding a directory to the global PATH environment variable26type PathCommand struct {27 Path string `json:"path" jsonschema:"description=Directory path to add to the global PATH environment variable. This path will be available to all subsequent commands in the build"`28}29 30// CopyCommand represents copying files or directories during the build31type CopyCommand struct {32 Image string `json:"image,omitempty" jsonschema:"description=Optional source image to copy from. This can be any public image URL"`33 Src string `json:"src" jsonschema:"description=Source path to copy from. Can be a file or directory"`34 Dest string `json:"dest" jsonschema:"description=Destination path to copy to. Will be created if it doesn't exist"`35}36 37type FileOptions struct {38 Mode os.FileMode39 CustomName string40}41 42// FileCommand represents creating or modifying a file during the build43type FileCommand struct {44 Path string `json:"path" jsonschema:"description=Directory path where the file should be created"`45 Name string `json:"name" jsonschema:"description=Name of the file to create"`46 Mode os.FileMode `json:"mode,omitempty" jsonschema:"description=Optional Unix file permissions mode (e.g. 0644 for regular file)"`47 CustomName string `json:"customName,omitempty" jsonschema:"description=Optional custom name to display for this file operation"`48}49 50func (e ExecCommand) CommandType() string { return "exec" }51func (g PathCommand) CommandType() string { return "globalPath" }52func (c CopyCommand) CommandType() string { return "copy" }53func (f FileCommand) CommandType() string { return "file" }54 55func NewExecCommand(cmd string, options ...ExecOptions) Command {56 exec := ExecCommand{Cmd: cmd}57 if len(options) > 0 {58 exec.CustomName = options[0].CustomName59 }60 return exec61}62 63func ShellCommandString(cmd string) string {64 return "sh -c '" + cmd + "'"65}66 67func NewExecShellCommand(cmd string, options ...ExecOptions) Command {68 if len(options) == 0 {69 options = []ExecOptions{70 {CustomName: cmd},71 }72 }73 74 exec := NewExecCommand(ShellCommandString(cmd), options...)75 return exec76}77 78func NewPathCommand(path string, customName ...string) Command {79 pathCmd := PathCommand{Path: path}80 return pathCmd81}82 83func NewCopyCommand(src string, dst ...string) Command {84 dstPath := src85 if len(dst) > 0 {86 dstPath = dst[0]87 }88 89 copyCmd := CopyCommand{Src: src, Dest: dstPath}90 return copyCmd91}92 93func NewFileCommand(path, name string, options ...FileOptions) Command {94 fileCmd := FileCommand{Path: path, Name: name}95 if len(options) > 0 {96 fileCmd.CustomName = options[0].CustomName97 fileCmd.Mode = options[0].Mode98 }99 return fileCmd100}101 102func UnmarshalCommand(data []byte) (Command, error) {103 // First try to unmarshal as JSON object104 if cmd, err := UnmarshalJsonCommand(data); err == nil {105 return cmd, nil106 }107 108 // If that fails, parse the string into a command109 return UnmarshalStringCommand(data)110}111 112func UnmarshalJsonCommand(data []byte) (Command, error) {113 // Try to unmarshal as JSON object114 var rawMap map[string]any115 if err := json.Unmarshal(data, &rawMap); err != nil {116 return nil, err117 }118 119 // Determine command type based on fields present120 if _, ok := rawMap["cmd"]; ok {121 var cmd ExecCommand122 if err := json.Unmarshal(data, &cmd); err != nil {123 return nil, err124 }125 return cmd, nil126 }127 128 if _, ok := rawMap["path"]; ok {129 if _, ok := rawMap["name"]; ok {130 var file FileCommand131 if err := json.Unmarshal(data, &file); err != nil {132 return nil, err133 }134 return file, nil135 }136 var path PathCommand137 if err := json.Unmarshal(data, &path); err != nil {138 return nil, err139 }140 return path, nil141 }142 143 if _, ok := rawMap["src"]; ok {144 var copy CopyCommand145 if err := json.Unmarshal(data, ©); err != nil {146 return nil, err147 }148 return copy, nil149 }150 151 return nil, fmt.Errorf("unknown command type: %v", rawMap)152}153 154func UnmarshalStringCommand(data []byte) (Command, error) {155 str := string(data)156 157 // If no prefix, treat as exec command158 if !strings.Contains(str, ":") {159 cmdToRun := strings.Trim(str, "\"")160 return NewExecShellCommand(cmdToRun, ExecOptions{CustomName: cmdToRun}), nil161 }162 163 parts := strings.SplitN(str, ":", 2)164 if len(parts) != 2 {165 return nil, fmt.Errorf("invalid command format: %s", str)166 }167 168 prefix := parts[0]169 payload := parts[1]170 171 // Split prefix into command type and custom name172 prefixParts := strings.SplitN(prefix, "#", 2)173 cmdType := prefixParts[0]174 customName := ""175 if len(prefixParts) > 1 {176 customName = prefixParts[1]177 }178 179 switch cmdType {180 case "RUN":181 return NewExecShellCommand(payload, ExecOptions{CustomName: customName}), nil182 case "PATH":183 return NewPathCommand(payload), nil184 case "COPY":185 copyParts := strings.Fields(payload)186 if len(copyParts) != 2 {187 return nil, fmt.Errorf("invalid COPY format: %s", payload)188 }189 return NewCopyCommand(copyParts[0], copyParts[1]), nil190 case "FILE":191 fileParts := strings.Fields(payload)192 if len(fileParts) != 2 {193 return nil, fmt.Errorf("invalid FILE format: %s", payload)194 }195 return NewFileCommand(fileParts[0], fileParts[1], FileOptions{CustomName: customName}), nil196 }197 198 // fallback to exec command type199 cmdToRun := strings.Trim(str, "\"")200 if customName == "" {201 customName = cmdToRun202 }203 return NewExecShellCommand(cmdToRun, ExecOptions{CustomName: customName}), nil204}205 206func (e ExecCommand) IsSpread() bool {207 return e.Cmd == ShellCommandString("...") || e.Cmd == "..."208}209 210func (p PathCommand) IsSpread() bool {211 return false212}213 214func (c CopyCommand) IsSpread() bool {215 return false216}217 218func (f FileCommand) IsSpread() bool {219 return false220}221