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
This commit is contained in:
+128
@@ -0,0 +1,128 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user