22package main
33
44import (
5+ "bufio"
56 "encoding/json"
67 "fmt"
78 "os"
@@ -55,8 +56,8 @@ func main() {
5556with network and filesystem restrictions.
5657
5758By default, all network access is blocked. Configure allowed domains in
58- ~/.fence.json or pass a settings file with --settings, or use a built-in
59- template with --template.
59+ ~/.config/ fence/fence .json ( or ~/Library/Application Support/fence/fence.json on macOS)
60+ or pass a settings file with --settings, or use a built-in template with --template.
6061
6162Examples:
6263 fence curl https://example.com # Will be blocked (no domains allowed)
@@ -68,7 +69,7 @@ Examples:
6869 fence -p 3000 -c "npm run dev" # Expose port 3000 for inbound connections
6970 fence --list-templates # Show available built-in templates
7071
71- Configuration file format (~/.fence.json) :
72+ Configuration file format:
7273{
7374 "network": {
7475 "allowedDomains": ["github.com", "*.npmjs.org"],
@@ -91,7 +92,7 @@ Configuration file format (~/.fence.json):
9192
9293 rootCmd .Flags ().BoolVarP (& debug , "debug" , "d" , false , "Enable debug logging" )
9394 rootCmd .Flags ().BoolVarP (& monitor , "monitor" , "m" , false , "Monitor and log sandbox violations (macOS: log stream, all: proxy denials)" )
94- rootCmd .Flags ().StringVarP (& settingsPath , "settings" , "s" , "" , "Path to settings file (default: ~/.fence.json )" )
95+ rootCmd .Flags ().StringVarP (& settingsPath , "settings" , "s" , "" , "Path to settings file (default: OS config directory )" )
9596 rootCmd .Flags ().StringVarP (& templateName , "template" , "t" , "" , "Use built-in template (e.g., ai-coding-agents, npm-install)" )
9697 rootCmd .Flags ().BoolVar (& listTemplates , "list-templates" , false , "List available templates" )
9798 rootCmd .Flags ().StringVarP (& cmdString , "c" , "c" , "" , "Run command string directly (like sh -c)" )
@@ -303,6 +304,8 @@ func newImportCmd() *cobra.Command {
303304 claudeMode bool
304305 inputFile string
305306 outputFile string
307+ saveFlag bool
308+ forceFlag bool
306309 extendTmpl string
307310 noExtend bool
308311 )
@@ -320,23 +323,25 @@ for network access (npm, GitHub, LLM providers) and filesystem protections.
320323Use --no-extend for a minimal config, or --extend to choose a different template.
321324
322325Examples:
323- # Import from default Claude Code settings (~/.claude/settings.json )
326+ # Preview import (prints JSON to stdout )
324327 fence import --claude
325328
326- # Import from a specific Claude Code settings file
327- fence import --claude -f ~/.claude/settings.json
329+ # Save to the default config path
330+ # Linux: ~/.config/fence/fence.json
331+ # macOS: ~/Library/Application Support/fence/fence.json
332+ fence import --claude --save
333+
334+ # Save to a specific output file
335+ fence import --claude -o ./fence.json
328336
329- # Import and write to a specific output file
330- fence import --claude -o .fence .json
337+ # Import from a specific Claude Code settings file
338+ fence import --claude -f ~/.claude/settings .json --save
331339
332340 # Import without extending any template (minimal config)
333- fence import --claude --no-extend
341+ fence import --claude --no-extend --save
334342
335343 # Import and extend a different template
336- fence import --claude --extend local-dev-server
337-
338- # Import from project-level Claude settings
339- fence import --claude -f .claude/settings.local.json -o .fence.json` ,
344+ fence import --claude --extend local-dev-server --save` ,
340345 RunE : func (cmd * cobra.Command , args []string ) error {
341346 if ! claudeMode {
342347 return fmt .Errorf ("no import source specified. Use --claude to import from Claude Code" )
@@ -357,13 +362,41 @@ Examples:
357362 for _ , warning := range result .Warnings {
358363 fmt .Fprintf (os .Stderr , "Warning: %s\n " , warning )
359364 }
365+ if len (result .Warnings ) > 0 {
366+ fmt .Fprintln (os .Stderr )
367+ }
368+
369+ // Determine output destination
370+ var destPath string
371+ if saveFlag {
372+ destPath = config .DefaultConfigPath ()
373+ } else if outputFile != "" {
374+ destPath = outputFile
375+ }
376+
377+ if destPath != "" {
378+ if ! forceFlag {
379+ if _ , err := os .Stat (destPath ); err == nil {
380+ fmt .Printf ("File %q already exists. Overwrite? [y/N] " , destPath )
381+ reader := bufio .NewReader (os .Stdin )
382+ response , _ := reader .ReadString ('\n' )
383+ response = strings .TrimSpace (strings .ToLower (response ))
384+ if response != "y" && response != "yes" {
385+ fmt .Println ("Aborted." )
386+ return nil
387+ }
388+ }
389+ }
390+
391+ if err := os .MkdirAll (filepath .Dir (destPath ), 0o750 ); err != nil {
392+ return fmt .Errorf ("failed to create config directory: %w" , err )
393+ }
360394
361- if outputFile != "" {
362- if err := importer .WriteConfig (result .Config , outputFile ); err != nil {
395+ if err := importer .WriteConfig (result .Config , destPath ); err != nil {
363396 return err
364397 }
365398 fmt .Printf ("Imported %d rules from %s\n " , result .RulesImported , result .SourcePath )
366- fmt .Printf ("Written to %s \n " , outputFile )
399+ fmt .Printf ("Written to %q \n " , destPath )
367400 } else {
368401 // Print clean JSON to stdout, helpful info to stderr (don't interfere with piping)
369402 data , err := importer .MarshalConfigJSON (result .Config )
@@ -375,7 +408,7 @@ Examples:
375408 fmt .Fprintf (os .Stderr , "\n # Extends %q - inherited rules not shown\n " , result .Config .Extends )
376409 }
377410 fmt .Fprintf (os .Stderr , "# Imported %d rules from %s\n " , result .RulesImported , result .SourcePath )
378- fmt .Fprintf (os .Stderr , "# Use -o <file> to write to a file (includes comments) \n " )
411+ fmt .Fprintf (os .Stderr , "# Use --save to write to the default config path \n " )
379412 }
380413
381414 return nil
@@ -384,10 +417,13 @@ Examples:
384417
385418 cmd .Flags ().BoolVar (& claudeMode , "claude" , false , "Import from Claude Code settings" )
386419 cmd .Flags ().StringVarP (& inputFile , "file" , "f" , "" , "Path to settings file (default: ~/.claude/settings.json for --claude)" )
387- cmd .Flags ().StringVarP (& outputFile , "output" , "o" , "" , "Output file path (default: stdout)" )
420+ cmd .Flags ().StringVarP (& outputFile , "output" , "o" , "" , "Output file path" )
421+ cmd .Flags ().BoolVar (& saveFlag , "save" , false , "Save to the default config path" )
422+ cmd .Flags ().BoolVarP (& forceFlag , "force" , "y" , false , "Overwrite existing file without prompting" )
388423 cmd .Flags ().StringVar (& extendTmpl , "extend" , "" , "Template to extend (default: code)" )
389424 cmd .Flags ().BoolVar (& noExtend , "no-extend" , false , "Don't extend any template (minimal config)" )
390425 cmd .MarkFlagsMutuallyExclusive ("extend" , "no-extend" )
426+ cmd .MarkFlagsMutuallyExclusive ("save" , "output" )
391427
392428 return cmd
393429}
0 commit comments