Skip to content

Commit ab672de

Browse files
committed
Merge remote-tracking branch 'origin/main' into sawka/tsunami-build
2 parents 6b29a30 + a9db209 commit ab672de

27 files changed

Lines changed: 1769 additions & 86 deletions

aiprompts/newview.md

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

aiprompts/view-prompt.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ Wave Terminal uses a modular ViewModel system to define interactive blocks. Each
77
### Key Concepts
88

99
1. **ViewModel Structure**
10-
1110
- Implements the `ViewModel` interface.
1211
- Defines:
1312
- `viewType`: Unique block type identifier.
@@ -19,28 +18,24 @@ Wave Terminal uses a modular ViewModel system to define interactive blocks. Each
1918
- Lifecycle methods like `dispose()`, `giveFocus()`, `keyDownHandler()`.
2019

2120
2. **ViewComponent Structure**
22-
2321
- A **React function component** implementing `ViewComponentProps<T extends ViewModel>`.
2422
- Uses `blockId`, `blockRef`, `contentRef`, and `model` as props.
2523
- Retrieves ViewModel state using Jotai atoms.
2624
- Returns JSX for rendering.
2725

2826
3. **Header Elements (`HeaderElem[]`)**
29-
3027
- Can include:
3128
- **Icons (`IconButtonDecl`)**: Clickable buttons.
3229
- **Text (`HeaderText`)**: Metadata or status.
3330
- **Inputs (`HeaderInput`)**: Editable fields.
3431
- **Menu Buttons (`MenuButton`)**: Dropdowns.
3532

3633
4. **Jotai Atoms for State Management**
37-
3834
- Use `atom<T>`, `PrimitiveAtom<T>`, `WritableAtom<T>` for dynamic properties.
3935
- `splitAtom` for managing lists of atoms.
4036
- Read settings from `globalStore` and override with block metadata.
4137

4238
5. **Metadata vs. Global Config**
43-
4439
- **Block Metadata (`SetMetaCommand`)**: Each block persists its **own configuration** in its metadata (`blockAtom.meta`).
4540
- **Global Config (`SetConfigCommand`)**: Provides **default settings** for all blocks, stored in config files.
4641
- **Cascading Behavior**:
@@ -50,7 +45,6 @@ Wave Terminal uses a modular ViewModel system to define interactive blocks. Each
5045
- Updating a global setting is done via `SetConfigCommand` (applies globally unless overridden).
5146

5247
6. **Useful Helper Functions**
53-
5448
- To avoid repetitive boilerplate, use these global utilities from `global.ts`:
5549
- `useBlockMetaKeyAtom(blockId, key)`: Retrieves and updates block-specific metadata.
5650
- `useOverrideConfigAtom(blockId, key)`: Reads from global config but allows per-block overrides.
@@ -139,7 +133,7 @@ type HeaderTextButton = {
139133
type HeaderText = {
140134
elemtype: "text";
141135
text: string;
142-
ref?: React.MutableRefObject<HTMLDivElement>;
136+
ref?: React.RefObject<HTMLDivElement>;
143137
className?: string;
144138
noGrow?: boolean;
145139
onClick?: (e: React.MouseEvent<any>) => void;
@@ -150,7 +144,7 @@ type HeaderInput = {
150144
value: string;
151145
className?: string;
152146
isDisabled?: boolean;
153-
ref?: React.MutableRefObject<HTMLInputElement>;
147+
ref?: React.RefObject<HTMLInputElement>;
154148
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
155149
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
156150
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;

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>

emain/emain-ipc.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,10 @@ export function initIpcHandlers() {
400400
incrementTermCommandsRun();
401401
});
402402

403+
electron.ipcMain.on("native-paste", (event) => {
404+
event.sender.paste();
405+
});
406+
403407
electron.ipcMain.on("open-builder", (event, appId?: string) => {
404408
fireAndForget(() => createBuilderWindow(appId || ""));
405409
});

emain/preload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ contextBridge.exposeInMainWorld("api", {
6464
setWaveAIOpen: (isOpen: boolean) => ipcRenderer.send("set-waveai-open", isOpen),
6565
closeBuilderWindow: () => ipcRenderer.send("close-builder-window"),
6666
incrementTermCommands: () => ipcRenderer.send("increment-term-commands"),
67+
nativePaste: () => ipcRenderer.send("native-paste"),
6768
});
6869

6970
// Custom event for "new-window"

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

frontend/app/element/flyoutmenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ type SubMenuProps = {
206206
};
207207
visibleSubMenus: { [key: string]: any };
208208
hoveredItems: string[];
209-
subMenuRefs: React.MutableRefObject<{ [key: string]: React.RefObject<HTMLDivElement> }>;
209+
subMenuRefs: React.RefObject<{ [key: string]: React.RefObject<HTMLDivElement> }>;
210210
handleMouseEnterItem: (
211211
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
212212
parentKey: string | null,

frontend/app/element/input.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ interface InputProps {
7070
autoSelect?: boolean;
7171
disabled?: boolean;
7272
isNumber?: boolean;
73-
inputRef?: React.MutableRefObject<any>;
73+
inputRef?: React.RefObject<any>;
7474
manageFocus?: (isFocused: boolean) => void;
7575
}
7676

0 commit comments

Comments
 (0)