Files
nono-opencode-tinfoil/tui/profile.go
T
kawaiipunk a5f97a3a1b Add TUI for managing sandbox profile, relicense to GPLv3
- 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
2026-08-12 19:37:54 +01:00

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
}