Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
525 changes: 525 additions & 0 deletions aiprompts/newview.md

Large diffs are not rendered by default.

31 changes: 30 additions & 1 deletion cmd/wsh/cmd/wshcmd-secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,20 @@ var secretListCmd = &cobra.Command{
PreRunE: preRunSetupRpcClient,
}

var secretDeleteCmd = &cobra.Command{
Use: "delete [name]",
Short: "delete a secret",
Args: cobra.ExactArgs(1),
RunE: secretDeleteRun,
PreRunE: preRunSetupRpcClient,
}

func init() {
rootCmd.AddCommand(secretCmd)
secretCmd.AddCommand(secretGetCmd)
secretCmd.AddCommand(secretSetCmd)
secretCmd.AddCommand(secretListCmd)
secretCmd.AddCommand(secretDeleteCmd)
}

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

secrets := map[string]string{name: value}
secrets := map[string]*string{name: &value}
err = wshclient.SetSecretsCommand(RpcClient, secrets, &wshrpc.RpcOpts{Timeout: 2000})
if err != nil {
return fmt.Errorf("setting secret: %w", err)
Expand All @@ -127,4 +136,24 @@ func secretListRun(cmd *cobra.Command, args []string) (rtnErr error) {
WriteStdout("%s\n", name)
}
return nil
}

func secretDeleteRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
sendActivity("secret", rtnErr == nil)
}()

name := args[0]
if !secretNameRegex.MatchString(name) {
return fmt.Errorf("invalid secret name: must start with a letter and contain only letters, numbers, and underscores")
}

secrets := map[string]*string{name: nil}
err := wshclient.SetSecretsCommand(RpcClient, secrets, &wshrpc.RpcOpts{Timeout: 2000})
if err != nil {
return fmt.Errorf("deleting secret: %w", err)
}

WriteStdout("secret deleted: %s\n", name)
return nil
}
86 changes: 86 additions & 0 deletions docs/docs/wsh-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -895,4 +895,90 @@ wsh blocks list --workspace=12d0c067-378e-454c-872e-77a314248114
wsh blocks list --json
```


---

## secret

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).

Secret names must start with a letter and contain only letters, numbers, and underscores.

### get

```sh
wsh secret get [name]
```

Retrieve and display the value of a stored secret.

Examples:

```sh
# Get an API key
wsh secret get github_token

# Use in scripts
export API_KEY=$(wsh secret get my_api_key)
```

### set

```sh
wsh secret set [name]=[value]
```

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.

Examples:

```sh
# Set an API token
wsh secret set github_token=ghp_abc123xyz

# Set a database password
wsh secret set db_password=mySecurePassword123
```

:::warning
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.
:::

### list

```sh
wsh secret list
```

Display all stored secret names (values are not shown).

Example:

```sh
# List all secrets
wsh secret list
```

### delete

```sh
wsh secret delete [name]
```

Remove a secret from secure storage.

Examples:

```sh
# Delete an API key
wsh secret delete github_token

# Delete multiple secrets
wsh secret delete old_api_key
wsh secret delete temp_token
```

:::tip
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.
:::
</PlatformProvider>
2 changes: 2 additions & 0 deletions frontend/app/block/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { AiFileDiffViewModel } from "@/app/view/aifilediff/aifilediff";
import { LauncherViewModel } from "@/app/view/launcher/launcher";
import { PreviewModel } from "@/app/view/preview/preview-model";
import { SecretStoreViewModel } from "@/app/view/secretstore/secretstore-model";
import { SysinfoViewModel } from "@/app/view/sysinfo/sysinfo";
import { TsunamiViewModel } from "@/app/view/tsunami/tsunami";
import { VDomModel } from "@/app/view/vdom/vdom-model";
Expand Down Expand Up @@ -52,6 +53,7 @@ BlockRegistry.set("help", HelpViewModel);
BlockRegistry.set("launcher", LauncherViewModel);
BlockRegistry.set("tsunami", TsunamiViewModel);
BlockRegistry.set("aifilediff", AiFileDiffViewModel);
BlockRegistry.set("secretstore", SecretStoreViewModel);

function makeViewModel(blockId: string, blockView: string, nodeModel: BlockNodeModel): ViewModel {
const ctor = BlockRegistry.get(blockView);
Expand Down
6 changes: 6 additions & 0 deletions frontend/app/block/blockutil.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export function blockViewToIcon(view: string): string {
if (view == "tips") {
return "lightbulb";
}
if (view == "secretstore") {
return "key";
}
return "square";
}

Expand All @@ -55,6 +58,9 @@ export function blockViewToName(view: string): string {
if (view == "tips") {
return "Tips";
}
if (view == "secretstore") {
return "Secret Store";
}
return view;
}

Expand Down
Loading
Loading