From e29ee04a18d8922d3cf54b4c3aaa48afb8827f82 Mon Sep 17 00:00:00 2001 From: sky5454 Date: Mon, 16 Mar 2026 12:15:56 +0800 Subject: [PATCH] onboard: make encryption opt-in via --enc flag Encryption (passphrase prompt + SSH key generation) is now only triggered when the user passes --enc to 'picoclaw onboard'. Without the flag, onboard skips the credential-encryption setup and writes a plain config + workspace templates directly. - Add --enc BoolFlag in NewOnboardCommand() - Pass encrypt bool into onboard() - Guard passphrase prompt, SSH key generation, and related env-var setup behind the encrypt branch - Adjust 'Next steps' output so the passphrase reminder only appears when --enc was used --- cmd/picoclaw/internal/onboard/command.go | 7 +- cmd/picoclaw/internal/onboard/command_test.go | 5 +- cmd/picoclaw/internal/onboard/helpers.go | 79 +++++++++++-------- 3 files changed, 54 insertions(+), 37 deletions(-) diff --git a/cmd/picoclaw/internal/onboard/command.go b/cmd/picoclaw/internal/onboard/command.go index ec1012959..9f8b288c6 100644 --- a/cmd/picoclaw/internal/onboard/command.go +++ b/cmd/picoclaw/internal/onboard/command.go @@ -11,14 +11,19 @@ import ( var embeddedFiles embed.FS func NewOnboardCommand() *cobra.Command { + var encrypt bool + cmd := &cobra.Command{ Use: "onboard", Aliases: []string{"o"}, Short: "Initialize picoclaw configuration and workspace", Run: func(cmd *cobra.Command, args []string) { - onboard() + onboard(encrypt) }, } + cmd.Flags().BoolVar(&encrypt, "enc", false, + "Enable credential encryption (generates SSH key and prompts for passphrase)") + return cmd } diff --git a/cmd/picoclaw/internal/onboard/command_test.go b/cmd/picoclaw/internal/onboard/command_test.go index bc799a079..56936190b 100644 --- a/cmd/picoclaw/internal/onboard/command_test.go +++ b/cmd/picoclaw/internal/onboard/command_test.go @@ -24,6 +24,9 @@ func TestNewOnboardCommand(t *testing.T) { assert.Nil(t, cmd.PersistentPreRun) assert.Nil(t, cmd.PersistentPostRun) - assert.False(t, cmd.HasFlags()) + assert.True(t, cmd.HasFlags()) + encFlag := cmd.Flags().Lookup("enc") + require.NotNil(t, encFlag, "expected --enc flag to be registered") + assert.Equal(t, "false", encFlag.DefValue, "--enc should default to false") assert.False(t, cmd.HasSubCommands()) } diff --git a/cmd/picoclaw/internal/onboard/helpers.go b/cmd/picoclaw/internal/onboard/helpers.go index cd637d89b..6f1d4bdd7 100644 --- a/cmd/picoclaw/internal/onboard/helpers.go +++ b/cmd/picoclaw/internal/onboard/helpers.go @@ -13,46 +13,51 @@ import ( "github.com/sipeed/picoclaw/pkg/credential" ) -func onboard() { +func onboard(encrypt bool) { configPath := internal.GetConfigPath() configExists := false if _, err := os.Stat(configPath); err == nil { configExists = true - // Only ask for confirmation when *both* config and SSH key already exist, - // indicating a full re-onboard that would reset the config to defaults. - sshKeyPath, _ := credential.DefaultSSHKeyPath() - if _, err := os.Stat(sshKeyPath); err == nil { - // Both exist — confirm a full reset. - fmt.Printf("Config already exists at %s\n", configPath) - fmt.Print("Overwrite config with defaults? (y/n): ") - var response string - fmt.Scanln(&response) - if response != "y" { - fmt.Println("Aborted.") - return + if encrypt { + // Only ask for confirmation when *both* config and SSH key already exist, + // indicating a full re-onboard that would reset the config to defaults. + sshKeyPath, _ := credential.DefaultSSHKeyPath() + if _, err := os.Stat(sshKeyPath); err == nil { + // Both exist — confirm a full reset. + fmt.Printf("Config already exists at %s\n", configPath) + fmt.Print("Overwrite config with defaults? (y/n): ") + var response string + fmt.Scanln(&response) + if response != "y" { + fmt.Println("Aborted.") + return + } + configExists = false // user agreed to reset; treat as fresh } - configExists = false // user agreed to reset; treat as fresh + // Config exists but SSH key is missing — keep existing config, only add SSH key. } - // Config exists but SSH key is missing — keep existing config, only add SSH key. } - fmt.Println("\nSet up credential encryption") - fmt.Println("-----------------------------") - passphrase, err := promptPassphrase() - if err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) - } - // Expose the passphrase to credential.PassphraseProvider (which calls - // os.Getenv by default) so that SaveConfig can encrypt api_keys. - // This process is a one-shot CLI tool; the env var is never exposed outside - // the current process and disappears when it exits. - os.Setenv(credential.PassphraseEnvVar, passphrase) + var err error + if encrypt { + fmt.Println("\nSet up credential encryption") + fmt.Println("-----------------------------") + passphrase, pErr := promptPassphrase() + if pErr != nil { + fmt.Printf("Error: %v\n", pErr) + os.Exit(1) + } + // Expose the passphrase to credential.PassphraseProvider (which calls + // os.Getenv by default) so that SaveConfig can encrypt api_keys. + // This process is a one-shot CLI tool; the env var is never exposed outside + // the current process and disappears when it exits. + os.Setenv(credential.PassphraseEnvVar, passphrase) - if err = setupSSHKey(); err != nil { - fmt.Printf("Error generating SSH key: %v\n", err) - os.Exit(1) + if err = setupSSHKey(); err != nil { + fmt.Printf("Error generating SSH key: %v\n", err) + os.Exit(1) + } } var cfg *config.Config @@ -76,11 +81,15 @@ func onboard() { fmt.Printf("\n%s picoclaw is ready!\n", internal.Logo) fmt.Println("\nNext steps:") - fmt.Println(" 1. Set your encryption passphrase before starting picoclaw:") - fmt.Println(" export PICOCLAW_KEY_PASSPHRASE= # Linux/macOS") - fmt.Println(" set PICOCLAW_KEY_PASSPHRASE= # Windows cmd") - fmt.Println("") - fmt.Println(" 2. Add your API key to", configPath) + if encrypt { + fmt.Println(" 1. Set your encryption passphrase before starting picoclaw:") + fmt.Println(" export PICOCLAW_KEY_PASSPHRASE= # Linux/macOS") + fmt.Println(" set PICOCLAW_KEY_PASSPHRASE= # Windows cmd") + fmt.Println("") + fmt.Println(" 2. Add your API key to", configPath) + } else { + fmt.Println(" 1. Add your API key to", configPath) + } fmt.Println("") fmt.Println(" Recommended:") fmt.Println(" - OpenRouter: https://openrouter.ai/keys (access 100+ models)")