Skip to content

Commit 4b6a3ed

Browse files
authored
secrets view (UI to manage wave secrets) (#2526)
1 parent 8435958 commit 4b6a3ed

File tree

11 files changed

+1307
-7
lines changed

11 files changed

+1307
-7
lines changed

aiprompts/newview.md

Lines changed: 525 additions & 0 deletions
Large diffs are not rendered by default.

cmd/wsh/cmd/wshcmd-secret.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,20 @@ var secretListCmd = &cobra.Command{
4646
PreRunE: preRunSetupRpcClient,
4747
}
4848

49+
var secretDeleteCmd = &cobra.Command{
50+
Use: "delete [name]",
51+
Short: "delete a secret",
52+
Args: cobra.ExactArgs(1),
53+
RunE: secretDeleteRun,
54+
PreRunE: preRunSetupRpcClient,
55+
}
56+
4957
func init() {
5058
rootCmd.AddCommand(secretCmd)
5159
secretCmd.AddCommand(secretGetCmd)
5260
secretCmd.AddCommand(secretSetCmd)
5361
secretCmd.AddCommand(secretListCmd)
62+
secretCmd.AddCommand(secretDeleteCmd)
5463
}
5564

5665
func secretGetRun(cmd *cobra.Command, args []string) (rtnErr error) {
@@ -103,7 +112,7 @@ func secretSetRun(cmd *cobra.Command, args []string) (rtnErr error) {
103112
return fmt.Errorf("No appropriate secret manager found, cannot set secrets")
104113
}
105114

106-
secrets := map[string]string{name: value}
115+
secrets := map[string]*string{name: &value}
107116
err = wshclient.SetSecretsCommand(RpcClient, secrets, &wshrpc.RpcOpts{Timeout: 2000})
108117
if err != nil {
109118
return fmt.Errorf("setting secret: %w", err)
@@ -127,4 +136,24 @@ func secretListRun(cmd *cobra.Command, args []string) (rtnErr error) {
127136
WriteStdout("%s\n", name)
128137
}
129138
return nil
139+
}
140+
141+
func secretDeleteRun(cmd *cobra.Command, args []string) (rtnErr error) {
142+
defer func() {
143+
sendActivity("secret", rtnErr == nil)
144+
}()
145+
146+
name := args[0]
147+
if !secretNameRegex.MatchString(name) {
148+
return fmt.Errorf("invalid secret name: must start with a letter and contain only letters, numbers, and underscores")
149+
}
150+
151+
secrets := map[string]*string{name: nil}
152+
err := wshclient.SetSecretsCommand(RpcClient, secrets, &wshrpc.RpcOpts{Timeout: 2000})
153+
if err != nil {
154+
return fmt.Errorf("deleting secret: %w", err)
155+
}
156+
157+
WriteStdout("secret deleted: %s\n", name)
158+
return nil
130159
}

docs/docs/wsh-reference.mdx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,4 +895,90 @@ wsh blocks list --workspace=12d0c067-378e-454c-872e-77a314248114
895895
wsh blocks list --json
896896
```
897897
898+
899+
---
900+
901+
## secret
902+
903+
The `secret` command provides secure storage and management of sensitive information like API keys, passwords, and tokens. Secrets are stored using your system's native secure storage backend (Keychain on macOS, Secret Service on Linux, Credential Manager on Windows).
904+
905+
Secret names must start with a letter and contain only letters, numbers, and underscores.
906+
907+
### get
908+
909+
```sh
910+
wsh secret get [name]
911+
```
912+
913+
Retrieve and display the value of a stored secret.
914+
915+
Examples:
916+
917+
```sh
918+
# Get an API key
919+
wsh secret get github_token
920+
921+
# Use in scripts
922+
export API_KEY=$(wsh secret get my_api_key)
923+
```
924+
925+
### set
926+
927+
```sh
928+
wsh secret set [name]=[value]
929+
```
930+
931+
Store a secret value securely. This command requires an appropriate system secret manager to be available and will fail if only basic text storage is available.
932+
933+
Examples:
934+
935+
```sh
936+
# Set an API token
937+
wsh secret set github_token=ghp_abc123xyz
938+
939+
# Set a database password
940+
wsh secret set db_password=mySecurePassword123
941+
```
942+
943+
:::warning
944+
The `set` command requires a proper system secret manager (Keychain, Secret Service, or Credential Manager). It will not work with basic text storage for security reasons.
945+
:::
946+
947+
### list
948+
949+
```sh
950+
wsh secret list
951+
```
952+
953+
Display all stored secret names (values are not shown).
954+
955+
Example:
956+
957+
```sh
958+
# List all secrets
959+
wsh secret list
960+
```
961+
962+
### delete
963+
964+
```sh
965+
wsh secret delete [name]
966+
```
967+
968+
Remove a secret from secure storage.
969+
970+
Examples:
971+
972+
```sh
973+
# Delete an API key
974+
wsh secret delete github_token
975+
976+
# Delete multiple secrets
977+
wsh secret delete old_api_key
978+
wsh secret delete temp_token
979+
```
980+
981+
:::tip
982+
Use secrets in your scripts to avoid hardcoding sensitive values. Secrets work across remote machines - store an API key locally with `wsh secret set`, then access it from any SSH or WSL connection with `wsh secret get`. The secret is securely retrieved from your local machine without needing to duplicate it on remote systems.
983+
:::
898984
</PlatformProvider>

frontend/app/block/block.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { AiFileDiffViewModel } from "@/app/view/aifilediff/aifilediff";
1313
import { LauncherViewModel } from "@/app/view/launcher/launcher";
1414
import { PreviewModel } from "@/app/view/preview/preview-model";
15+
import { SecretStoreViewModel } from "@/app/view/secretstore/secretstore-model";
1516
import { SysinfoViewModel } from "@/app/view/sysinfo/sysinfo";
1617
import { TsunamiViewModel } from "@/app/view/tsunami/tsunami";
1718
import { VDomModel } from "@/app/view/vdom/vdom-model";
@@ -52,6 +53,7 @@ BlockRegistry.set("help", HelpViewModel);
5253
BlockRegistry.set("launcher", LauncherViewModel);
5354
BlockRegistry.set("tsunami", TsunamiViewModel);
5455
BlockRegistry.set("aifilediff", AiFileDiffViewModel);
56+
BlockRegistry.set("secretstore", SecretStoreViewModel);
5557

5658
function makeViewModel(blockId: string, blockView: string, nodeModel: BlockNodeModel): ViewModel {
5759
const ctor = BlockRegistry.get(blockView);

frontend/app/block/blockutil.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ export function blockViewToIcon(view: string): string {
3030
if (view == "tips") {
3131
return "lightbulb";
3232
}
33+
if (view == "secretstore") {
34+
return "key";
35+
}
3336
return "square";
3437
}
3538

@@ -55,6 +58,9 @@ export function blockViewToName(view: string): string {
5558
if (view == "tips") {
5659
return "Tips";
5760
}
61+
if (view == "secretstore") {
62+
return "Secret Store";
63+
}
5864
return view;
5965
}
6066

0 commit comments

Comments
 (0)