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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,6 @@ FodyWeavers.xsd
Output/
*.lscache
test_ws.py

# Local visual test output
visual-test-output/
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ If a command fails:
Notes:

- If a build/test is blocked by an environmental lock (for example running executable locking output assemblies), stop/close the locking process and rerun.
- Tray tests must isolate `SettingsManager` from real user settings. Do not use `new SettingsManager()` in tests unless the test intentionally reads `%APPDATA%\OpenClawTray\settings.json`; pass a temp settings directory or set `OPENCLAW_TRAY_DATA_DIR` before the test process starts.
- Do not claim completion without reporting validation results.
49 changes: 47 additions & 2 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,8 @@ dotnet test --filter "FullyQualifiedName~AgentActivityTests"
```

**Test Coverage:**
- ✅ **478 tests** in `OpenClaw.Shared.Tests` — models, gateway client, exec approvals, capabilities, URL helpers, notification categorization, shell quoting
- ✅ **93 tests** in `OpenClaw.Tray.Tests` — menu display, menu positioning, settings round-trip, deep link parsing
- ✅ **652 tests** in `OpenClaw.Shared.Tests` — models, gateway client, exec approvals, capabilities, URL helpers, notification categorization, shell quoting
- ✅ **262 tests** in `OpenClaw.Tray.Tests` — menu display, menu positioning, settings round-trip, deep link parsing, onboarding state, setup code decoder, security validation, wizard step parsing, localization validation
- ✅ All tests are pure unit tests (no network, no file system, no external dependencies)

See [tests/OpenClaw.Shared.Tests/README.md](tests/OpenClaw.Shared.Tests/README.md) for detailed test documentation.
Expand Down Expand Up @@ -747,6 +747,51 @@ gh run download <run-id> --repo shanselman/openclaw-windows-hub
- **Discussions**: [GitHub Discussions](https://github.com/shanselman/openclaw-windows-hub/discussions)
- **Documentation**: [OpenClaw Docs](https://docs.molt.bot)

## Developing & Testing the Onboarding Wizard

The onboarding wizard is a 6-screen flow built with OpenClaw's minimal FunctionalUI helper layer for declarative C# WinUI. The chat page uses a WebView2 overlay for visual consistency with the post-setup chat experience.

### Building

The WinUI project requires platform-specific build targets. Use the build script:

```bash
./build.ps1 -Project WinUI # Builds with correct -r win-x64 targets
```

Direct `dotnet build` without the script will fail with "WindowsAppSDKSelfContained requires a supported Windows architecture".

### Environment Variables

| Variable | Purpose |
|----------|---------|
| `OPENCLAW_FORCE_ONBOARDING=1` | Show onboarding wizard even if a token already exists |
| `OPENCLAW_SKIP_UPDATE_CHECK=1` | Skip the update dialog (useful during testing) |
| `OPENCLAW_LANGUAGE=fr-fr` | Override UI language (validated: en-us, fr-fr, nl-nl, zh-cn, zh-tw) |
| `OPENCLAW_GATEWAY_PORT=19001` | Override default gateway port for local dev |
| `OPENCLAW_VISUAL_TEST=1` | Enable automatic screenshot capture on page transitions |
| `OPENCLAW_VISUAL_TEST_DIR=path` | Output directory for visual test screenshots |

### Testing the Wizard Locally

1. Start a local gateway (e.g., in WSL): `cd ~/openclaw && npx openclaw gateway`
2. Set env vars:
```powershell
$env:OPENCLAW_FORCE_ONBOARDING = "1"
$env:OPENCLAW_SKIP_UPDATE_CHECK = "1"
```
3. Build and run: `./build.ps1 -Project WinUI` then launch the exe
4. Navigate through all 6 screens to verify

### Architecture

- **FunctionalUI**: `src/OpenClawTray.FunctionalUI/` — Minimal declarative WinUI helper layer used by onboarding
- **Pages**: `src/OpenClaw.Tray.WinUI/Onboarding/Pages/` — Functional UI components for each wizard screen
- **Services**: `src/OpenClaw.Tray.WinUI/Onboarding/Services/` — State management, setup code decoder, permission checker, health check, input validation
- **Widgets**: `src/OpenClaw.Tray.WinUI/Onboarding/Widgets/` — Shared UI components (cards, step indicators, feature rows)
- **Window**: `src/OpenClaw.Tray.WinUI/Onboarding/OnboardingWindow.cs` — Host window with WebView2 overlay for chat
- **Helpers**: `src/OpenClaw.Tray.WinUI/Helpers/GatewayChatHelper.cs` — Shared WebView2 chat URL builder

---

*Made with 🦞 love by Scott Hanselman and the OpenClaw community*
51 changes: 31 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ Modern Windows 11-style system tray companion that connects to your local OpenCl
- ⏱ **Cron Jobs** - Quick access to scheduled tasks
- 🚀 **Auto-start** - Launch with Windows
- ⚙️ **Settings** - Full configuration dialog
- 🎯 **First-run experience** - Welcome dialog guides new users
- 🎯 **First-run onboarding** — 6-screen setup wizard (connection, permissions, chat, configuration)

#### Quick Send scope requirement

Expand Down Expand Up @@ -164,7 +164,7 @@ These features are available in Windows but not in the Mac app:
| Channel control | Start/stop Telegram & WhatsApp |
| Modern flyout menu | Windows 11-style with dark/light mode |
| Deep links | `openclaw://` URL scheme with IPC |
| First-run welcome | Guided onboarding for new users |
| First-run onboarding | 6-screen guided setup wizard (Welcome → Connection → Wizard → Permissions → Chat → Ready) |
| PowerToys integration | Command Palette extension |

### 🔌 Node Mode (Agent Control)
Expand All @@ -179,6 +179,7 @@ When Node Mode is enabled in Settings, your Windows PC becomes a **node** that t
| **Camera** | `camera.list`, `camera.snap`, `camera.clip` | Enumerate cameras and capture still photos or short video clips |
| **Location** | `location.get` | Return Windows geolocation when permission is available |
| **Device** | `device.info`, `device.status` | Return Windows host/app metadata and lightweight status |
| **Text-to-speech** | `tts.speak` | Speak text aloud through Windows speech synthesis, or ElevenLabs when configured |

#### Node Setup

Expand All @@ -205,23 +206,24 @@ When Node Mode is enabled in Settings, your Windows PC becomes a **node** that t
"canvas.hide",
"canvas.navigate",
"canvas.eval",
"canvas.snapshot",
"canvas.a2ui.push",
"canvas.a2ui.pushJSONL",
"canvas.a2ui.reset",
"screen.snapshot",
"camera.list",
"camera.snap",
"camera.clip",
"location.get",
"device.info",
"device.status"
"canvas.snapshot",
"canvas.a2ui.push",
"canvas.a2ui.pushJSONL",
"canvas.a2ui.reset",
"screen.snapshot",
"camera.list",
"camera.snap",
"camera.clip",
"location.get",
"device.info",
"device.status",
"tts.speak"
]
}
}
}
}
}
```
> ⚠️ **Important**: The gateway has a server-side allowlist. Commands must be listed explicitly - wildcards like `canvas.*` don't work! Privacy-sensitive commands such as `screen.record` should only be added to `allowCommands` when you explicitly want to allow them.
> ⚠️ **Important**: The gateway has a server-side allowlist. Commands must be listed explicitly - wildcards like `canvas.*` don't work! Privacy-sensitive commands such as `screen.record` and agent-driven audio playback via `tts.speak` should only be added to `allowCommands` when you explicitly want to allow them.

5. **Test it** from your Mac/gateway:
```bash
Expand Down Expand Up @@ -249,6 +251,9 @@ When Node Mode is enabled in Settings, your Windows PC becomes a **node** that t
# Take a photo (NV12/MediaCapture fallback)
openclaw nodes invoke --node <id> --command camera.snap --params '{"deviceId":"<device-id>","format":"jpeg","quality":80}'

# Speak text aloud on the Windows node (requires TTS enabled in Settings and tts.speak allowed on the gateway)
openclaw nodes invoke --node <id> --command tts.speak --params '{"text":"Hello from OpenClaw","provider":"windows"}'

# Execute a command on the Windows node
openclaw nodes invoke --node <id> --command system.run --params '{"command":"Get-Process | Select -First 5","shell":"powershell","timeoutMs":10000}'

Expand Down Expand Up @@ -402,10 +407,16 @@ Default gateway: `ws://localhost:18789`

### First Run

On first run without a token, Molty displays a welcome dialog that:
1. Explains what's needed to get started
2. Links to [documentation](https://docs.molt.bot/web/dashboard) for token setup
3. Opens Settings to configure the connection
On first run, Molty launches a guided onboarding wizard that walks you through setup:

1. **Welcome** — introduces OpenClaw and starts the setup flow
2. **Connection** — choose Local gateway, Remote gateway, or configure later. Paste a setup code or enter gateway URL and token manually. Tests the connection with Ed25519 device authentication.
3. **Wizard** — gateway-driven configuration steps (AI provider selection, personality setup, communication channels). Steps are defined by your gateway.
4. **Permissions** — reviews Windows system permissions (notifications, camera, microphone, screen capture, location) and links to system settings to grant them.
5. **Chat** — meet your agent in a live chat powered by the gateway's web UI.
6. **Ready** — summary of available features, option to launch at startup, and a Finish button.

For detailed setup instructions, see [docs/SETUP.md](docs/SETUP.md). For the full onboarding architecture, see [docs/ONBOARDING_WIZARD.md](docs/ONBOARDING_WIZARD.md).

## License

Expand Down
5 changes: 3 additions & 2 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#>

param(
[ValidateSet("All", "Tray", "WinUI", "Shared", "CommandPalette", "Cli")]
[ValidateSet("All", "Tray", "WinUI", "Shared", "CommandPalette", "Cli", "WinNodeCli")]
[string]$Project = "All",

[ValidateSet("Debug", "Release")]
Expand Down Expand Up @@ -188,12 +188,13 @@ function Build-Project($name, $path, $useRid = $false) {
$projects = @{
"Shared" = @{ Path = "src/OpenClaw.Shared/OpenClaw.Shared.csproj"; UseRid = $false }
"Cli" = @{ Path = "src/OpenClaw.Cli/OpenClaw.Cli.csproj"; UseRid = $false }
"WinNodeCli" = @{ Path = "src/OpenClaw.WinNode.Cli/OpenClaw.WinNode.Cli.csproj"; UseRid = $false }
"Tray" = @{ Path = "src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj"; UseRid = $true }
"WinUI" = @{ Path = "src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj"; UseRid = $true }
"CommandPalette" = @{ Path = "src/OpenClaw.CommandPalette/OpenClaw.CommandPalette.csproj"; UseRid = $false }
}

$toBuild = if ($Project -eq "All") { @("Shared", "Cli", "WinUI") } else { @($Project) }
$toBuild = if ($Project -eq "All") { @("Shared", "Cli", "WinNodeCli", "WinUI") } else { @($Project) }

# Always build Shared first if building other projects
if ($Project -ne "Shared" -and $Project -ne "All" -and $toBuild -notcontains "Shared") {
Expand Down
44 changes: 32 additions & 12 deletions docs/LOCALIZATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ OpenClaw Tray uses WinUI `.resw` resource files for localization. Windows automa
| Language | Locale | Resource File |
|----------|--------|---------------|
| English (US) | `en-us` | `Strings/en-us/Resources.resw` |
| French (France) | `fr-fr` | `Strings/fr-fr/Resources.resw` |
| Dutch (Netherlands) | `nl-nl` | `Strings/nl-nl/Resources.resw` |
| Chinese (Simplified) | `zh-cn` | `Strings/zh-cn/Resources.resw` |
| Chinese (Traditional) | `zh-tw` | `Strings/zh-tw/Resources.resw` |

## Adding a New Language

Expand Down Expand Up @@ -65,16 +68,16 @@ Windows picks the language automatically based on the user's OS display language

## Testing a Language Locally

To test a specific locale without changing your Windows language:
Set the `OPENCLAW_LANGUAGE` environment variable before launching the app:

1. Open `src/OpenClaw.Tray.WinUI/App.xaml.cs`
2. Add this line at the top of the `App()` constructor, **before** `InitializeComponent()`:
```csharp
LocalizationHelper.SetLanguageOverride("zh-CN");
```
3. Build and run (`dotnet build src/OpenClaw.Tray.WinUI -r win-x64`). Remove the line when done testing.
```powershell
$env:OPENCLAW_LANGUAGE = "fr-fr" # or nl-nl, zh-cn, zh-tw
.\src\OpenClaw.Tray.WinUI\bin\Debug\net10.0-windows10.0.19041.0\win-x64\OpenClaw.Tray.WinUI.exe
```

> **Note:** This overrides `LocalizationHelper.GetString()` calls (menus, toasts, dialogs, window titles). XAML `x:Uid` bindings follow the OS display language. For full XAML localization testing, change your Windows display language in Settings → Time & Language.
This overrides `LocalizationHelper.GetString()` calls for menus, toasts, dialogs, and the onboarding wizard. The language is validated against the supported locale list.

> **Note:** XAML `x:Uid` bindings follow the OS display language. For full localization testing including XAML elements, change your Windows display language in Settings → Time & Language.

## Resource Key Naming Conventions

Expand All @@ -87,12 +90,29 @@ To test a specific locale without changing your Windows language:
| `Status_Name` | Status display text | `Status_Connected` |
| `TimeAgo_Format` | Relative time strings | `TimeAgo_MinutesFormat` |

### Onboarding Key Namespace

All onboarding wizard strings use the `Onboarding_` prefix:

| Pattern | Used For | Example |
|---------|----------|---------|
| `Onboarding_PageName_Label` | Page titles, descriptions | `Onboarding_Welcome_Title` |
| `Onboarding_Connection_*` | Connection page labels/status | `Onboarding_Connection_TestConnection` |
| `Onboarding_Perm_*` | Permission names | `Onboarding_Perm_Camera` |
| `Onboarding_Ready_*` | Ready page elements | `Onboarding_Ready_Feature_Voice_Subtitle` |
| `Onboarding_Wizard_*` | Wizard page elements | `Onboarding_Wizard_Continue` |

## Validation

Both resource files must have the **same set of keys**. You can verify with:
All 5 resource files must have the **same set of keys**. You can verify with:

```powershell
$en = (Select-String -Path "src\OpenClaw.Tray.WinUI\Strings\en-us\Resources.resw" -Pattern '<data name="' | Measure-Object).Count
$new = (Select-String -Path "src\OpenClaw.Tray.WinUI\Strings\<locale>\Resources.resw" -Pattern '<data name="' | Measure-Object).Count
Write-Host "en-us: $en keys | <locale>: $new keys | Match: $($en -eq $new)"
$locales = @("en-us", "fr-fr", "nl-nl", "zh-cn", "zh-tw")
$base = "src\OpenClaw.Tray.WinUI\Strings"
foreach ($loc in $locales) {
$count = (Select-String -Path "$base\$loc\Resources.resw" -Pattern '<data name="' | Measure-Object).Count
Write-Host "$loc : $count keys"
}
```

All locale counts should match. Missing or extra keys indicate an incomplete translation.
2 changes: 1 addition & 1 deletion docs/MCP_MODE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Summary

The Windows tray app now ships a **local Model Context Protocol (MCP) server** alongside its existing OpenClaw gateway client. The same node capabilities the agent reaches over the OpenClaw gateway WebSocket — `system.run`, `screen.snapshot`, `canvas.*`, `camera.list`, `camera.snap`, `camera.clip`, `location.get`, `system.notify`, `system.execApprovals.*` — are advertised, on the same machine, as MCP tools over `http://127.0.0.1:8765/`.
The Windows tray app now ships a **local Model Context Protocol (MCP) server** alongside its existing OpenClaw gateway client. The same node capabilities the agent reaches over the OpenClaw gateway WebSocket — `system.run`, `screen.snapshot`, `canvas.*`, `camera.list`, `camera.snap`, `camera.clip`, `location.get`, `tts.speak`, `system.notify`, `system.execApprovals.*` — are advertised, on the same machine, as MCP tools over `http://127.0.0.1:8765/`.

This means any local MCP client (Claude Desktop, Claude Code, Cursor, an MCP-aware CLI, a custom dev script) can reach into the running tray and drive Windows-native capabilities directly, without an OpenClaw gateway in the loop. The tray app can run in **MCP-only mode** with no gateway connection at all.

Expand Down
Loading