feat: support Kiro CLI v2 and v3 hooks
This commit is contained in:
1 parent
598b0bd2bb
commit
758d129dbf
7 files changed
+1175
-171
No files matched your search
@@ -0,0 +1,142 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func kiroConfigPaths() (string, string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("resolve home directory: %w", err)
|
||||
}
|
||||
return filepath.Join(home, ".kiro", "agents", kiroV2AgentName+".json"),
|
||||
filepath.Join(home, ".kiro", "hooks", "elydora-audit.json"), nil
|
||||
}
|
||||
|
||||
func readKiroObject(path, label string) (map[string]any, bool, error) {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return map[string]any{}, false, nil
|
||||
}
|
||||
return nil, false, fmt.Errorf("read %s at %s: %w", label, path, err)
|
||||
}
|
||||
var value any
|
||||
if err := json.Unmarshal(raw, &value); err != nil {
|
||||
return nil, true, fmt.Errorf("parse %s at %s: %w", label, path, err)
|
||||
}
|
||||
object, ok := value.(map[string]any)
|
||||
if !ok || object == nil {
|
||||
return nil, true, fmt.Errorf("%s at %s must contain a JSON object", label, path)
|
||||
}
|
||||
return object, true, nil
|
||||
}
|
||||
|
||||
func writeKiroObjectAtomic(path string, value map[string]any) error {
|
||||
encoded, err := json.MarshalIndent(value, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal %s: %w", path, err)
|
||||
}
|
||||
encoded = append(encoded, '\n')
|
||||
directory := filepath.Dir(path)
|
||||
if err := os.MkdirAll(directory, 0700); err != nil {
|
||||
return fmt.Errorf("create directory %s: %w", directory, err)
|
||||
}
|
||||
temporary, err := os.CreateTemp(directory, "."+filepath.Base(path)+".*.tmp")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temporary file for %s: %w", path, err)
|
||||
}
|
||||
temporaryPath := temporary.Name()
|
||||
cleanup := func(cause error) error {
|
||||
closeErr := temporary.Close()
|
||||
removeErr := os.Remove(temporaryPath)
|
||||
if os.IsNotExist(removeErr) {
|
||||
removeErr = nil
|
||||
}
|
||||
return errors.Join(cause, closeErr, removeErr)
|
||||
}
|
||||
if err := temporary.Chmod(0600); err != nil {
|
||||
return cleanup(fmt.Errorf("set permissions for %s: %w", temporaryPath, err))
|
||||
}
|
||||
if _, err := temporary.Write(encoded); err != nil {
|
||||
return cleanup(fmt.Errorf("write temporary file for %s: %w", path, err))
|
||||
}
|
||||
if err := temporary.Sync(); err != nil {
|
||||
return cleanup(fmt.Errorf("sync temporary file for %s: %w", path, err))
|
||||
}
|
||||
if err := temporary.Close(); err != nil {
|
||||
return cleanup(fmt.Errorf("close temporary file for %s: %w", path, err))
|
||||
}
|
||||
if err := os.Rename(temporaryPath, path); err != nil {
|
||||
removeErr := os.Remove(temporaryPath)
|
||||
if os.IsNotExist(removeErr) {
|
||||
removeErr = nil
|
||||
}
|
||||
return errors.Join(fmt.Errorf("replace %s: %w", path, err), removeErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeKiroFile(path, label string) error {
|
||||
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("remove %s at %s: %w", label, path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveNodeRuntime() (string, error) {
|
||||
nodePath, err := exec.LookPath("node")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve Node.js runtime: %w", err)
|
||||
}
|
||||
absolute, err := filepath.Abs(nodePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve Node.js runtime path: %w", err)
|
||||
}
|
||||
return absolute, nil
|
||||
}
|
||||
|
||||
func buildKiroCommand(runtimePath, scriptPath string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return quoteWindowsArgument(runtimePath) + " " + quoteWindowsArgument(scriptPath)
|
||||
}
|
||||
return quotePOSIXArgument(runtimePath) + " " + quotePOSIXArgument(scriptPath)
|
||||
}
|
||||
|
||||
func quotePOSIXArgument(value string) string {
|
||||
return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'"
|
||||
}
|
||||
|
||||
func quoteWindowsArgument(value string) string {
|
||||
if value != "" && !strings.ContainsAny(value, " \t\n\v\"") {
|
||||
return value
|
||||
}
|
||||
var result strings.Builder
|
||||
result.WriteByte('"')
|
||||
backslashes := 0
|
||||
for _, character := range value {
|
||||
if character == '\\' {
|
||||
backslashes++
|
||||
continue
|
||||
}
|
||||
if character == '"' {
|
||||
result.WriteString(strings.Repeat("\\", backslashes*2+1))
|
||||
result.WriteRune(character)
|
||||
backslashes = 0
|
||||
continue
|
||||
}
|
||||
result.WriteString(strings.Repeat("\\", backslashes))
|
||||
backslashes = 0
|
||||
result.WriteRune(character)
|
||||
}
|
||||
result.WriteString(strings.Repeat("\\", backslashes*2))
|
||||
result.WriteByte('"')
|
||||
return result.String()
|
||||
}
|
||||
Reference in new issue
Block a user