Move Kimi, Grok, and Qwen onto managed_runtime.go and shell_command.go; share the encoded and shell command builders and the quoted Windows legacy parser with Droid; split managed_changes.go; dedupe Letta's hook entry filtering and stop swallowing settings read errors. Claude-Session: https://claude.ai/code/session_01Agoc9bRVE9KL7mmWtey9Vq
135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
package plugins
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
func kimiHomeDirectory() (string, error) {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve home directory: %w", err)
|
|
}
|
|
return home, nil
|
|
}
|
|
|
|
func isMissingKimiPath(err error) bool {
|
|
return os.IsNotExist(err) || errors.Is(err, syscall.ENOTDIR)
|
|
}
|
|
|
|
func kimiEntryExists(path, label string) (bool, error) {
|
|
_, err := os.Lstat(path)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if isMissingKimiPath(err) {
|
|
return false, nil
|
|
}
|
|
return false, fmt.Errorf("inspect %s at %s: %w", label, path, err)
|
|
}
|
|
|
|
func stableKimiContract(configPath string) kimiContract {
|
|
return kimiContract{
|
|
generation: "stable", runtimeName: "Kimi Code",
|
|
label: "Kimi Code hooks config", directoryLabel: "Kimi Code home directory",
|
|
configPath: configPath, events: kimiModernEvents,
|
|
}
|
|
}
|
|
|
|
func legacyKimiContract(configPath string) kimiContract {
|
|
return kimiContract{
|
|
generation: "legacy", runtimeName: "kimi-cli",
|
|
label: "kimi-cli legacy hooks config",
|
|
directoryLabel: "kimi-cli legacy home directory",
|
|
configPath: configPath, events: kimiSharedEvents,
|
|
}
|
|
}
|
|
|
|
func resolveKimiContracts() ([]kimiContract, error) {
|
|
home, err := kimiHomeDirectory()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
configuredHome := os.Getenv("KIMI_CODE_HOME")
|
|
stableHome := filepath.Join(home, ".kimi-code")
|
|
explicitHome := configuredHome != ""
|
|
if explicitHome {
|
|
stableHome, err = filepath.Abs(configuredHome)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("resolve KIMI_CODE_HOME at %s: %w", configuredHome, err)
|
|
}
|
|
}
|
|
legacyHome := filepath.Join(home, ".kimi")
|
|
stable := stableKimiContract(filepath.Join(stableHome, "config.toml"))
|
|
legacy := legacyKimiContract(filepath.Join(legacyHome, "config.toml"))
|
|
if sameManagedPath(stable.configPath, legacy.configPath) {
|
|
return []kimiContract{stable}, nil
|
|
}
|
|
|
|
stableDetected := explicitHome
|
|
if !stableDetected {
|
|
stableDetected, err = kimiEntryExists(stableHome, "Kimi Code home")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
legacyDetected, err := kimiEntryExists(legacyHome, "kimi-cli legacy home")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if legacyDetected && !stableDetected {
|
|
return []kimiContract{legacy}, nil
|
|
}
|
|
if legacyDetected {
|
|
return []kimiContract{stable, legacy}, nil
|
|
}
|
|
return []kimiContract{stable}, nil
|
|
}
|
|
|
|
func readKimiConfig(contract kimiContract) (kimiDocument, error) {
|
|
directory := filepath.Dir(contract.configPath)
|
|
if _, err := managedPhysicalDirectoryExists(directory, contract.directoryLabel); err != nil {
|
|
return kimiDocument{}, err
|
|
}
|
|
snapshot, err := readManagedFile(
|
|
contract.configPath,
|
|
contract.label,
|
|
maxManagedSourceBytes,
|
|
)
|
|
if err != nil {
|
|
return kimiDocument{}, err
|
|
}
|
|
if snapshot == nil {
|
|
return parseKimiDocument(contract, nil, false)
|
|
}
|
|
return parseKimiDocument(contract, snapshot.contents, true)
|
|
}
|
|
|
|
func readAllKimiConfigs() ([]kimiDocument, error) {
|
|
contracts, err := resolveKimiContracts()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
documents := make([]kimiDocument, 0, len(contracts))
|
|
for _, contract := range contracts {
|
|
document, err := readKimiConfig(contract)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
documents = append(documents, document)
|
|
}
|
|
return documents, nil
|
|
}
|
|
|
|
func kimiRuntimeNames(documents []kimiDocument) string {
|
|
names := make([]string, 0, len(documents))
|
|
for _, document := range documents {
|
|
names = append(names, document.contract.runtimeName)
|
|
}
|
|
return strings.Join(names, " and ")
|
|
}
|