openclaw-voice 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -138
- package/package.json +2 -2
- package/index.ts +0 -9
package/README.md
CHANGED
|
@@ -1,36 +1,40 @@
|
|
|
1
|
-
# OpenClaw Voice
|
|
1
|
+
# OpenClaw Voice
|
|
2
2
|
|
|
3
|
-

|
|
3
|
+

|
|
4
4
|
|
|
5
5
|
[](https://expo.dev/)
|
|
6
6
|
[](https://reactnative.dev/)
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Voice-first OpenClaw experience for mobile and code.
|
|
10
10
|
|
|
11
|
-
Speak -> edit
|
|
11
|
+
Speak -> edit -> send -> stream response.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
<p align="center">
|
|
14
|
+
<video
|
|
15
|
+
src="https://github.com/user-attachments/assets/65f799f3-c87b-4c13-8b5f-23491efd5ec5"
|
|
16
|
+
poster="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/response-light.png"
|
|
17
|
+
controls
|
|
18
|
+
playsinline
|
|
19
|
+
preload="none"
|
|
20
|
+
width="360"
|
|
21
|
+
>
|
|
22
|
+
Your browser cannot play inline video.
|
|
23
|
+
</video>
|
|
24
|
+
</p>
|
|
14
25
|
|
|
15
|
-
|
|
26
|
+
<p align="center">
|
|
27
|
+
<a href="https://github.com/user-attachments/assets/65f799f3-c87b-4c13-8b5f-23491efd5ec5"><strong>Watch demo video (MP4)</strong></a>
|
|
28
|
+
</p>
|
|
16
29
|
|
|
17
|
-
|
|
18
|
-
npm install openclaw-voice
|
|
19
|
-
```
|
|
30
|
+
## Why OpenClaw Voice
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
- Fast voice-to-chat workflow optimized for iOS
|
|
33
|
+
- Reusable `GatewayClient` SDK on npm (`openclaw-voice`)
|
|
34
|
+
- Streaming response handling with reconnect and pairing flow support
|
|
35
|
+
- Secure device identity signing via Ed25519
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
import { GatewayClient } from 'openclaw-voice';
|
|
25
|
-
|
|
26
|
-
const client = new GatewayClient('wss://your-openclaw-gateway.example.com', {
|
|
27
|
-
token: 'your-token',
|
|
28
|
-
clientId: 'openclaw-ios',
|
|
29
|
-
displayName: 'OpenClawVoice',
|
|
30
|
-
});
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Quick Start (5 Minutes)
|
|
37
|
+
## Run The App (5 Minutes)
|
|
34
38
|
|
|
35
39
|
Prerequisites:
|
|
36
40
|
|
|
@@ -39,104 +43,127 @@ Prerequisites:
|
|
|
39
43
|
- CocoaPods
|
|
40
44
|
- A running OpenClaw Gateway endpoint (`wss://...`)
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
Quick setup:
|
|
43
47
|
|
|
44
48
|
```bash
|
|
45
49
|
bash scripts/bootstrap.sh
|
|
46
50
|
```
|
|
47
51
|
|
|
48
|
-
What
|
|
52
|
+
What it does:
|
|
49
53
|
|
|
50
54
|
- Installs dependencies with `npm install`
|
|
51
55
|
- Generates iOS native project (if missing)
|
|
52
56
|
- Installs CocoaPods
|
|
53
57
|
- Launches the app on a physical device (`npm run ios -- --device`)
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
Manual setup:
|
|
56
60
|
|
|
57
61
|
```bash
|
|
58
62
|
npm install
|
|
63
|
+
npm run ios
|
|
59
64
|
```
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
## Use It As npm Package
|
|
67
|
+
|
|
68
|
+
Install:
|
|
62
69
|
|
|
63
70
|
```bash
|
|
64
|
-
npm
|
|
71
|
+
npm install openclaw-voice
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { GatewayClient, setStorage } from 'openclaw-voice';
|
|
78
|
+
|
|
79
|
+
// Optional: set persistent storage for device identity.
|
|
80
|
+
setStorage({
|
|
81
|
+
getString: (key) => localStorage.getItem(key) ?? undefined,
|
|
82
|
+
set: (key, value) => localStorage.setItem(key, value),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const client = new GatewayClient('wss://your-openclaw-gateway.example.com', {
|
|
86
|
+
token: 'your-token',
|
|
87
|
+
clientId: 'openclaw-ios',
|
|
88
|
+
displayName: 'OpenClawVoice',
|
|
89
|
+
role: 'operator',
|
|
90
|
+
scopes: ['operator.read', 'operator.write'],
|
|
91
|
+
caps: ['talk'],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
client.onConnectionStateChange((state) => console.log('connection:', state));
|
|
95
|
+
client.onChatEvent((event) => console.log('chat event:', event.state, event.message));
|
|
96
|
+
|
|
97
|
+
await client.connect();
|
|
98
|
+
const result = await client.chatSend('demo-session-1', 'Hello from openclaw-voice');
|
|
99
|
+
console.log('runId:', result.runId);
|
|
65
100
|
```
|
|
66
101
|
|
|
67
|
-
|
|
102
|
+
Notes:
|
|
68
103
|
|
|
69
|
-
|
|
104
|
+
- Without `setStorage`, identity is in-memory and may require re-pairing after restart.
|
|
105
|
+
- In React Native/Expo, load crypto/base64 polyfills before using the client when needed.
|
|
106
|
+
|
|
107
|
+
## Screenshots
|
|
108
|
+
|
|
109
|
+
<table>
|
|
110
|
+
<tr>
|
|
111
|
+
<td align="center" width="25%">
|
|
112
|
+
<strong>Idle</strong><br>
|
|
113
|
+
<sub>Connected, waiting for input</sub><br><br>
|
|
114
|
+
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/idle.png" width="200" alt="Idle state" />
|
|
115
|
+
</td>
|
|
116
|
+
<td align="center" width="25%">
|
|
117
|
+
<strong>Ready to Send</strong><br>
|
|
118
|
+
<sub>Transcript ready, tap to send</sub><br><br>
|
|
119
|
+
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/ready-to-send.png" width="200" alt="Ready to send state" />
|
|
120
|
+
</td>
|
|
121
|
+
<td align="center" width="25%">
|
|
122
|
+
<strong>Sending</strong><br>
|
|
123
|
+
<sub>Waiting for Gateway response</sub><br><br>
|
|
124
|
+
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/sending.png" width="200" alt="Sending state" />
|
|
125
|
+
</td>
|
|
126
|
+
<td align="center" width="25%">
|
|
127
|
+
<strong>Response (Light)</strong><br>
|
|
128
|
+
<sub>Streamed response displayed</sub><br><br>
|
|
129
|
+
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/response-light.png" width="200" alt="Response in light theme" />
|
|
130
|
+
</td>
|
|
131
|
+
</tr>
|
|
132
|
+
</table>
|
|
70
133
|
|
|
71
134
|
## Features
|
|
72
135
|
|
|
73
136
|
- Speech-to-text input using `expo-speech-recognition`
|
|
74
137
|
- Editable transcript before sending
|
|
75
138
|
- OpenClaw Gateway connection with URL + token/password
|
|
76
|
-
- Streaming
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
- Device identity persistence for gateway auth (Ed25519 key pair)
|
|
81
|
-
- Compact UI with header connection status and bottom round action button
|
|
82
|
-
|
|
83
|
-
## Tech Stack
|
|
84
|
-
|
|
85
|
-
- Expo SDK 54
|
|
86
|
-
- React Native 0.81
|
|
87
|
-
- TypeScript
|
|
88
|
-
- `expo-speech-recognition`
|
|
89
|
-
- `expo-secure-store`
|
|
90
|
-
- `expo-crypto`
|
|
91
|
-
- `@noble/ed25519` + `@noble/hashes`
|
|
139
|
+
- Streaming response rendering with per-turn states (`WAIT`, `OK`, `ERR`)
|
|
140
|
+
- Auto reconnect support
|
|
141
|
+
- Persistent settings for gateway URL, token/password, and theme
|
|
142
|
+
- Local device identity generation/signing for gateway auth
|
|
92
143
|
|
|
93
|
-
## Environment
|
|
144
|
+
## Environment Variables
|
|
94
145
|
|
|
95
|
-
Copy `.env.example` to `.env
|
|
146
|
+
Copy `.env.example` to `.env`:
|
|
96
147
|
|
|
97
148
|
```bash
|
|
98
149
|
cp .env.example .env
|
|
99
150
|
```
|
|
100
151
|
|
|
101
|
-
Available variables:
|
|
102
|
-
|
|
103
152
|
- `EXPO_PUBLIC_DEFAULT_GATEWAY_URL`
|
|
104
153
|
- `EXPO_PUBLIC_DEFAULT_THEME` (`light` or `dark`)
|
|
105
154
|
- `EXPO_PUBLIC_GATEWAY_CLIENT_ID` (default: `openclaw-ios`)
|
|
106
155
|
- `EXPO_PUBLIC_GATEWAY_DISPLAY_NAME` (default: `OpenClawVoice`)
|
|
156
|
+
- `EXPO_PUBLIC_DEBUG_MODE` (`true` to show warnings in dev, default: `false`)
|
|
107
157
|
|
|
108
|
-
|
|
158
|
+
## Connection Defaults
|
|
109
159
|
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
| `App.tsx` | Main UI, voice capture flow, transcript editing, gateway connect/send flow, history rendering |
|
|
118
|
-
| `src/openclaw/client.ts` | OpenClaw Gateway WebSocket protocol client (handshake, events, requests, reconnect, keepalive) |
|
|
119
|
-
| `src/openclaw/protocol.ts` | Protocol v3 types and event/method constants |
|
|
120
|
-
| `src/openclaw/device-identity.ts` | Device identity generation/signing for gateway authentication |
|
|
121
|
-
| `src/openclaw/storage.ts` | Storage abstraction used by identity module |
|
|
122
|
-
| `crypto-polyfill.ts` | `crypto.getRandomValues`, `atob`, `btoa` polyfills for React Native runtime compatibility |
|
|
123
|
-
| `scripts/bootstrap.sh` | One-command setup + run for iOS |
|
|
124
|
-
| `.env.example` | Sample environment configuration |
|
|
125
|
-
| `CONTRIBUTING.md` | Contribution workflow and local checks |
|
|
126
|
-
| `docs/TROUBLESHOOTING.md` | Common issues and fixes |
|
|
127
|
-
| `app.json` | Expo configuration and microphone/speech permission strings |
|
|
128
|
-
|
|
129
|
-
## How to Use
|
|
130
|
-
|
|
131
|
-
1. Launch the app.
|
|
132
|
-
2. If not connected, the gateway panel opens automatically.
|
|
133
|
-
3. Enter your `Gateway URL` (example: `wss://your-openclaw-gateway.example.com`) and optional `Token / Password`.
|
|
134
|
-
4. Tap `Connect`.
|
|
135
|
-
5. Hold the round mic button to speak.
|
|
136
|
-
6. Release to stop recognition.
|
|
137
|
-
7. Edit transcript if needed.
|
|
138
|
-
8. Tap the send button (same round button state) to submit.
|
|
139
|
-
9. View streamed response in History.
|
|
160
|
+
- `clientId: openclaw-ios`
|
|
161
|
+
- `displayName: OpenClawVoice`
|
|
162
|
+
- `role: operator`
|
|
163
|
+
- `scopes: operator.read, operator.write`
|
|
164
|
+
- `caps: talk`
|
|
165
|
+
|
|
166
|
+
Device identity is generated locally and reused when persistent storage is available.
|
|
140
167
|
|
|
141
168
|
## Scripts
|
|
142
169
|
|
|
@@ -147,53 +174,21 @@ Notes:
|
|
|
147
174
|
- `npm run typecheck` - Run TypeScript checks
|
|
148
175
|
- `npm run build:package` - Build npm package files to `dist/`
|
|
149
176
|
|
|
150
|
-
##
|
|
151
|
-
|
|
152
|
-
The client currently connects with these defaults:
|
|
153
|
-
|
|
154
|
-
- `clientId: openclaw-ios`
|
|
155
|
-
- `displayName: OpenClawVoice`
|
|
156
|
-
- `role: operator`
|
|
157
|
-
- `scopes: operator.read, operator.write`
|
|
158
|
-
- `caps: talk`
|
|
159
|
-
- Device identity is generated locally and reused via secure persistence when available.
|
|
160
|
-
- If identity is reset, the gateway may request pairing again.
|
|
161
|
-
|
|
162
|
-
## Speech Recognition Behavior
|
|
163
|
-
|
|
164
|
-
- Language is selectable from the Gateway panel (`日本語 / English`).
|
|
165
|
-
- Interim results are shown while speaking.
|
|
166
|
-
- Final recognition text is stored in editable transcript.
|
|
167
|
-
- Some `aborted/cancelled` recognition errors are intentionally ignored when caused by expected stop flow.
|
|
168
|
-
|
|
169
|
-
## Configuration Persistence
|
|
170
|
-
|
|
171
|
-
These values are persisted locally:
|
|
172
|
-
|
|
173
|
-
- Gateway URL
|
|
174
|
-
- Token/password
|
|
175
|
-
- Theme mode
|
|
176
|
-
- Device identity record
|
|
177
|
+
## Security Notes
|
|
177
178
|
|
|
178
|
-
|
|
179
|
+
- Do not commit private gateway tokens.
|
|
180
|
+
- Use secure `wss://` endpoints.
|
|
181
|
+
- Preferred exposure path order: Tailscale/WireGuard -> Cloudflare Tunnel + access control -> Hardened VPS reverse proxy.
|
|
182
|
+
- Do not expose raw Gateway ports publicly.
|
|
183
|
+
- Rotate credentials and keep TLS/server packages up to date.
|
|
179
184
|
|
|
180
185
|
## Troubleshooting
|
|
181
186
|
|
|
182
|
-
See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)
|
|
183
|
-
|
|
184
|
-
## UI / UX Design Direction
|
|
185
|
-
|
|
186
|
-
The current UI is intentionally minimal:
|
|
187
|
-
|
|
188
|
-
- Small header with live connection chip
|
|
189
|
-
- Gateway settings panel shown only when disconnected
|
|
190
|
-
- Card-based transcript/history surfaces with subtle border + shadow
|
|
191
|
-
- Bottom round icon-only primary action (mic/send state switch)
|
|
192
|
-
- Dark/light theme toggle in header
|
|
187
|
+
See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md).
|
|
193
188
|
|
|
194
189
|
## Contributing
|
|
195
190
|
|
|
196
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
191
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
197
192
|
|
|
198
193
|
## CI
|
|
199
194
|
|
|
@@ -203,7 +198,7 @@ GitHub Actions runs on push/PR:
|
|
|
203
198
|
- Lint (`npm run lint --if-present`)
|
|
204
199
|
- Tests (`npm test --if-present`)
|
|
205
200
|
|
|
206
|
-
Issue
|
|
201
|
+
Issue/PR templates are in `.github/`.
|
|
207
202
|
|
|
208
203
|
## Publish to npm
|
|
209
204
|
|
|
@@ -212,26 +207,10 @@ npm run build:package
|
|
|
212
207
|
npm publish --access public
|
|
213
208
|
```
|
|
214
209
|
|
|
215
|
-
## Security Notes
|
|
216
|
-
|
|
217
|
-
- Do not commit private gateway tokens.
|
|
218
|
-
- Use secure `wss://` endpoints.
|
|
219
|
-
- Pair only trusted devices in OpenClaw.
|
|
220
|
-
- Prefer private network exposure first. Recommended order:
|
|
221
|
-
1. Tailscale/WireGuard private network
|
|
222
|
-
2. Cloudflare Tunnel with access control
|
|
223
|
-
3. Hardened VPS reverse proxy
|
|
224
|
-
- Do not expose the Gateway port directly to the public internet.
|
|
225
|
-
- If using Cloudflare Tunnel or VPS, require authentication at both layers:
|
|
226
|
-
- Edge access control (IdP login / service token / mTLS as applicable)
|
|
227
|
-
- Gateway token/password validation
|
|
228
|
-
- Restrict source access (ACL / allowlist), rotate tokens regularly, and revoke leaked credentials immediately.
|
|
229
|
-
- Keep TLS certificates and server packages updated, and enable request/rate logging for incident response.
|
|
230
|
-
|
|
231
210
|
## Acknowledgements
|
|
232
211
|
|
|
233
|
-
- [`expo-openclaw-chat`](https://github.com/brunobar79/expo-openclaw-chat)
|
|
212
|
+
- [`expo-openclaw-chat`](https://github.com/brunobar79/expo-openclaw-chat)
|
|
234
213
|
|
|
235
214
|
## License
|
|
236
215
|
|
|
237
|
-
|
|
216
|
+
MIT. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
package/index.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import './crypto-polyfill';
|
|
2
|
-
import { registerRootComponent } from 'expo';
|
|
3
|
-
|
|
4
|
-
import App from './App';
|
|
5
|
-
|
|
6
|
-
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
|
|
7
|
-
// It also ensures that whether you load the app in Expo Go or in a native build,
|
|
8
|
-
// the environment is set up appropriately
|
|
9
|
-
registerRootComponent(App);
|