- Add Go/Bubble Tea TUI app under tui/ with dashboard (status checks, launch, validate) and profile editor (commands, filesystem, network, environment sub-tabs) - Add Makefile targets: tui, tui-build, tui-install - Add CI step to build TUI - Relicense MIT -> GPL-3.0 - Rewrite wrapper to resolve real opencode binary from PATH (supports brew, npm, volta) - Add PATH ordering detection to install script - Expand README with how-it-works diagram, TUI docs, keybinding table
129 lines
2.5 KiB
Go
129 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
type Profile struct {
|
|
Meta ProfileMeta `json:"meta"`
|
|
Groups ProfileGroups `json:"groups"`
|
|
Commands ProfileCommands `json:"commands"`
|
|
Workdir ProfileWorkdir `json:"workdir"`
|
|
FS ProfileFS `json:"filesystem"`
|
|
Network ProfileNetwork `json:"network"`
|
|
Env ProfileEnv `json:"environment"`
|
|
}
|
|
|
|
type ProfileMeta struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
type ProfileGroups struct {
|
|
Include []string `json:"include"`
|
|
}
|
|
|
|
type ProfileCommands struct {
|
|
Allow []string `json:"allow"`
|
|
Deny []string `json:"deny"`
|
|
}
|
|
|
|
type ProfileWorkdir struct {
|
|
Access string `json:"access"`
|
|
}
|
|
|
|
type ProfileFS struct {
|
|
Allow []string `json:"allow"`
|
|
Read []string `json:"read"`
|
|
Write []string `json:"write"`
|
|
Deny []string `json:"deny"`
|
|
}
|
|
|
|
type ProfileNetwork struct {
|
|
Block bool `json:"block"`
|
|
AllowDomain []string `json:"allow_domain"`
|
|
}
|
|
|
|
type ProfileEnv struct {
|
|
SetVars map[string]string `json:"set_vars"`
|
|
DenyVars []string `json:"deny_vars"`
|
|
}
|
|
|
|
func DefaultProfilePath() (string, error) {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filepath.Join(home, ".config", "nono", "profiles", "opencode-tinfoil.json"), nil
|
|
}
|
|
|
|
func LoadProfile(path string) (*Profile, error) {
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
clean := stripJSONCComments(raw)
|
|
var p Profile
|
|
if err := json.Unmarshal(clean, &p); err != nil {
|
|
return nil, err
|
|
}
|
|
return &p, nil
|
|
}
|
|
|
|
func SaveProfile(path string, p *Profile) error {
|
|
data, err := json.MarshalIndent(p, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(path, data, 0644)
|
|
}
|
|
|
|
func stripJSONCComments(data []byte) []byte {
|
|
var result []byte
|
|
inString := false
|
|
escaped := false
|
|
i := 0
|
|
for i < len(data) {
|
|
c := data[i]
|
|
if inString {
|
|
result = append(result, c)
|
|
if escaped {
|
|
escaped = false
|
|
} else if c == '\\' {
|
|
escaped = true
|
|
} else if c == '"' {
|
|
inString = false
|
|
}
|
|
i++
|
|
continue
|
|
}
|
|
if c == '"' {
|
|
inString = true
|
|
result = append(result, c)
|
|
i++
|
|
continue
|
|
}
|
|
if c == '/' && i+1 < len(data) {
|
|
if data[i+1] == '/' {
|
|
for i < len(data) && data[i] != '\n' {
|
|
i++
|
|
}
|
|
continue
|
|
}
|
|
if data[i+1] == '*' {
|
|
i += 2
|
|
for i+1 < len(data) && !(data[i] == '*' && data[i+1] == '/') {
|
|
i++
|
|
}
|
|
i += 2
|
|
continue
|
|
}
|
|
}
|
|
result = append(result, c)
|
|
i++
|
|
}
|
|
return result
|
|
}
|