phoneclaw-connector 1.0.0
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 +228 -0
- package/SKILL.md +203 -0
- package/dist/device-auth-store.d.ts +29 -0
- package/dist/device-auth-store.d.ts.map +1 -0
- package/dist/device-auth-store.js +75 -0
- package/dist/device-auth-store.js.map +1 -0
- package/dist/device-identity.d.ts +41 -0
- package/dist/device-identity.d.ts.map +1 -0
- package/dist/device-identity.js +133 -0
- package/dist/device-identity.js.map +1 -0
- package/dist/device.d.ts +21 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/device.js +104 -0
- package/dist/device.js.map +1 -0
- package/dist/gateway.d.ts +58 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +314 -0
- package/dist/gateway.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +434 -0
- package/dist/index.js.map +1 -0
- package/dist/pair.d.ts +2 -0
- package/dist/pair.d.ts.map +1 -0
- package/dist/pair.js +98 -0
- package/dist/pair.js.map +1 -0
- package/dist/pairing.d.ts +12 -0
- package/dist/pairing.d.ts.map +1 -0
- package/dist/pairing.js +71 -0
- package/dist/pairing.js.map +1 -0
- package/dist/relay.d.ts +25 -0
- package/dist/relay.d.ts.map +1 -0
- package/dist/relay.js +206 -0
- package/dist/relay.js.map +1 -0
- package/dist/status.d.ts +2 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +34 -0
- package/dist/status.js.map +1 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# PhoneClaw Skill 使用指南
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
PhoneClaw Skill 是一个 OpenClaw 插件,用于将你的 OpenClaw Gateway 与 PhoneClaw iOS App 连接。安装后,Skill 会自动建立与中继服务器的连接,使你可以从手机远程控制你的 AI Agent。
|
|
6
|
+
|
|
7
|
+
## 快速开始
|
|
8
|
+
|
|
9
|
+
### 前置条件
|
|
10
|
+
|
|
11
|
+
- OpenClaw Gateway 已安装并运行
|
|
12
|
+
- Node.js >= 18.0.0
|
|
13
|
+
- 可以访问中继服务器(默认 `wss://relay.phoneclaw.com`)
|
|
14
|
+
|
|
15
|
+
### 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 1. 克隆或下载 Skill 代码
|
|
19
|
+
git clone https://github.com/your-org/phoneclaw-skill.git
|
|
20
|
+
cd phoneclaw-skill
|
|
21
|
+
|
|
22
|
+
# 2. 安装依赖
|
|
23
|
+
npm install
|
|
24
|
+
|
|
25
|
+
# 3. 构建
|
|
26
|
+
npm run build
|
|
27
|
+
|
|
28
|
+
# 4. 安装到 OpenClaw
|
|
29
|
+
openclaw skills install ./dist
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 配置
|
|
33
|
+
|
|
34
|
+
编辑 `config.json` 文件:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"relayServer": "wss://relay.phoneclaw.com",
|
|
39
|
+
"autoConnect": true,
|
|
40
|
+
"heartbeatInterval": 30000,
|
|
41
|
+
"reconnectAttempts": 5,
|
|
42
|
+
"reconnectDelay": 5000,
|
|
43
|
+
"gatewayUrl": "ws://127.0.0.1:18789",
|
|
44
|
+
"gatewayToken": ""
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| 配置项 | 说明 | 默认值 |
|
|
49
|
+
|--------|------|--------|
|
|
50
|
+
| `relayServer` | 中继服务器地址 | `wss://relay.phoneclaw.com` |
|
|
51
|
+
| `autoConnect` | 是否自动连接 | `true` |
|
|
52
|
+
| `heartbeatInterval` | 心跳间隔(毫秒) | `30000` |
|
|
53
|
+
| `reconnectAttempts` | 最大重连次数 | `5` |
|
|
54
|
+
| `reconnectDelay` | 重连基础延迟(毫秒) | `5000` |
|
|
55
|
+
| `gatewayUrl` | OpenClaw Gateway 地址 | `ws://127.0.0.1:18789` |
|
|
56
|
+
| `gatewayToken` | Gateway 认证 Token(可选) | `""` |
|
|
57
|
+
|
|
58
|
+
### 启动
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# 启动 Skill
|
|
62
|
+
npm start
|
|
63
|
+
|
|
64
|
+
# 或使用 openclaw 命令
|
|
65
|
+
openclaw skills enable phoneclaw-connector
|
|
66
|
+
openclaw skills start phoneclaw-connector
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 使用方式
|
|
70
|
+
|
|
71
|
+
### 生成配对码
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# 生成 6 位配对码和二维码
|
|
75
|
+
npm run pair
|
|
76
|
+
|
|
77
|
+
# 输出示例:
|
|
78
|
+
# === PhoneClaw Pairing Code ===
|
|
79
|
+
# Code: 123456
|
|
80
|
+
# Expires: 2024-01-01 12:00:00
|
|
81
|
+
#
|
|
82
|
+
# QR Code:
|
|
83
|
+
# ██████████████████████████████
|
|
84
|
+
# ...
|
|
85
|
+
# ==============================
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 查看状态
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# 查看连接状态
|
|
92
|
+
npm run status
|
|
93
|
+
|
|
94
|
+
# 输出示例:
|
|
95
|
+
# === PhoneClaw Skill Status ===
|
|
96
|
+
# Device ID: abc123def456
|
|
97
|
+
# Gateway: Connected
|
|
98
|
+
# Relay: Connected
|
|
99
|
+
# Active Sessions: 2
|
|
100
|
+
# ===============================
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 日常使用
|
|
104
|
+
|
|
105
|
+
1. **启动 Skill**:运行 `npm start`
|
|
106
|
+
2. **打开 PhoneClaw App**:在 iPhone 上打开 App
|
|
107
|
+
3. **扫码配对**:扫描 Skill 终端显示的二维码,或手动输入配对码
|
|
108
|
+
4. **开始聊天**:配对成功后即可从手机发送消息
|
|
109
|
+
|
|
110
|
+
## 命令参考
|
|
111
|
+
|
|
112
|
+
| 命令 | 说明 |
|
|
113
|
+
|------|------|
|
|
114
|
+
| `npm start` | 启动 Skill(守护进程模式) |
|
|
115
|
+
| `npm run pair` | 生成配对码和二维码 |
|
|
116
|
+
| `npm run status` | 查看连接状态 |
|
|
117
|
+
| `npm run build` | 构建 TypeScript 代码 |
|
|
118
|
+
| `npm run dev` | 开发模式(带热重载) |
|
|
119
|
+
|
|
120
|
+
## 故障排查
|
|
121
|
+
|
|
122
|
+
### 无法连接到中继服务器
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# 检查网络连接
|
|
126
|
+
ping relay.phoneclaw.com
|
|
127
|
+
|
|
128
|
+
# 检查防火墙设置
|
|
129
|
+
# 确保出站 WebSocket 连接(端口 443)未被阻止
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 无法连接到 Gateway
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# 检查 Gateway 是否运行
|
|
136
|
+
openclaw gateway status
|
|
137
|
+
|
|
138
|
+
# 检查 Gateway 地址和端口
|
|
139
|
+
# 默认: ws://127.0.0.1:18789
|
|
140
|
+
|
|
141
|
+
# 检查 Token 是否正确
|
|
142
|
+
# 如果配置了 gatewayToken,确保与 Gateway 配置一致
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 配对失败
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# 检查配对码是否过期(10 分钟有效)
|
|
149
|
+
# 重新生成配对码: npm run pair
|
|
150
|
+
|
|
151
|
+
# 检查设备是否在线
|
|
152
|
+
npm run status
|
|
153
|
+
|
|
154
|
+
# 确保 Skill 已连接到中继服务器
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 消息发送失败
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# 检查 Gateway 连接状态
|
|
161
|
+
npm run status
|
|
162
|
+
|
|
163
|
+
# 检查中继服务器连接状态
|
|
164
|
+
npm run status
|
|
165
|
+
|
|
166
|
+
# 查看日志输出
|
|
167
|
+
# Skill 会在终端输出详细的连接和消息日志
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 高级配置
|
|
171
|
+
|
|
172
|
+
### 自定义中继服务器
|
|
173
|
+
|
|
174
|
+
如果你部署了自己的中继服务器,修改 `config.json`:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"relayServer": "wss://your-relay-server.com"
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 自定义 Gateway 地址
|
|
183
|
+
|
|
184
|
+
如果 Gateway 运行在非默认地址或远程服务器:
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"gatewayUrl": "ws://your-gateway-server.com:18789",
|
|
189
|
+
"gatewayToken": "your-gateway-token"
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 日志级别
|
|
194
|
+
|
|
195
|
+
Skill 默认输出 INFO 级别日志。要启用调试日志:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# 设置环境变量
|
|
199
|
+
DEBUG=phoneclaw:* npm start
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## 安全注意事项
|
|
203
|
+
|
|
204
|
+
- **设备密钥**:`device.json` 文件包含设备认证密钥,请妥善保管
|
|
205
|
+
- **配对码**:配对码 10 分钟有效,使用后自动失效
|
|
206
|
+
- **网络连接**:确保中继服务器使用 WSS(WebSocket Secure)加密连接
|
|
207
|
+
- **Token 保护**:如果配置了 `gatewayToken`,不要将其提交到版本控制系统
|
|
208
|
+
|
|
209
|
+
## 更新
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# 拉取最新代码
|
|
213
|
+
git pull
|
|
214
|
+
|
|
215
|
+
# 重新安装依赖
|
|
216
|
+
npm install
|
|
217
|
+
|
|
218
|
+
# 重新构建
|
|
219
|
+
npm run build
|
|
220
|
+
|
|
221
|
+
# 重启 Skill
|
|
222
|
+
npm start
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## 技术支持
|
|
226
|
+
|
|
227
|
+
- 文档:[PhoneClaw 技术方案](../requirements/technical-solution.md)
|
|
228
|
+
- 问题反馈:wzzvictory_tjsd@163.com
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: phone-claw-skill
|
|
3
|
+
description: Connect PhoneClaw iOS app to this OpenClaw Gateway via relay server. Enables remote control from mobile device. Auto-activates when user wants to pair their phone or send messages through PhoneClaw app.
|
|
4
|
+
metadata:
|
|
5
|
+
openclaw:
|
|
6
|
+
requires:
|
|
7
|
+
bins: ["node"]
|
|
8
|
+
env: ["HOME"]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# PhoneClaw Relay Connector
|
|
12
|
+
|
|
13
|
+
This skill establishes and maintains a WebSocket connection between this OpenClaw Gateway and the PhoneClaw iOS app through a relay server. Once connected, users can interact with this AI agent from their iPhone.
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- User wants to connect their iPhone to this OpenClaw instance
|
|
18
|
+
- User mentions PhoneClaw, mobile connection, or phone pairing
|
|
19
|
+
- User wants to generate a pairing code for their phone
|
|
20
|
+
- User wants to check connection status with PhoneClaw app
|
|
21
|
+
|
|
22
|
+
## How It Works
|
|
23
|
+
|
|
24
|
+
### Architecture
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
PhoneClaw iOS App <--WSS--> Relay Server <--WSS--> PhoneClaw Skill <--WebSocket--> OpenClaw Gateway
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The skill runs as a background process that:
|
|
31
|
+
1. Connects to the relay server via WebSocket
|
|
32
|
+
2. Listens for incoming messages from the PhoneClaw app
|
|
33
|
+
3. Forwards messages to the OpenClaw Gateway
|
|
34
|
+
4. Streams AI responses back to the phone
|
|
35
|
+
|
|
36
|
+
### Quick Start
|
|
37
|
+
|
|
38
|
+
#### 1. Install Dependencies
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cd ~/.openclaw/workspace/skills/phoneclaw-relay
|
|
42
|
+
npm install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### 2. Configure
|
|
46
|
+
|
|
47
|
+
Edit `config.json`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"relayServer": "wss://relay.phoneclaw.com",
|
|
52
|
+
"autoConnect": true,
|
|
53
|
+
"heartbeatInterval": 30000,
|
|
54
|
+
"gatewayUrl": "ws://127.0.0.1:18789",
|
|
55
|
+
"gatewayToken": ""
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Field | Description | Default |
|
|
60
|
+
|-------|-------------|---------|
|
|
61
|
+
| `relayServer` | Relay server WebSocket URL | `wss://relay.phoneclaw.com` |
|
|
62
|
+
| `autoConnect` | Auto-connect on startup | `true` |
|
|
63
|
+
| `heartbeatInterval` | Heartbeat interval (ms) | `30000` |
|
|
64
|
+
| `gatewayUrl` | OpenClaw Gateway URL | `ws://127.0.0.1:18789` |
|
|
65
|
+
| `gatewayToken` | Gateway auth token (optional) | `""` |
|
|
66
|
+
|
|
67
|
+
#### 3. Start the Connector
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm start
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The skill will:
|
|
74
|
+
- Generate a unique device ID (stored in `device.json`)
|
|
75
|
+
- Connect to the relay server
|
|
76
|
+
- Wait for phone pairing
|
|
77
|
+
|
|
78
|
+
#### 4. Pair Your Phone
|
|
79
|
+
|
|
80
|
+
Generate a pairing code:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm run pair
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Output example:
|
|
87
|
+
```
|
|
88
|
+
=== PhoneClaw Pairing Code ===
|
|
89
|
+
Code: 123456
|
|
90
|
+
Expires: 2024-01-01 12:00:00
|
|
91
|
+
|
|
92
|
+
QR Code:
|
|
93
|
+
██████████████████████████████
|
|
94
|
+
...
|
|
95
|
+
==============================
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Open the PhoneClaw app on your iPhone and:
|
|
99
|
+
1. Tap "Add Device"
|
|
100
|
+
2. Scan the QR code or enter the 6-digit code
|
|
101
|
+
3. Wait for pairing confirmation
|
|
102
|
+
|
|
103
|
+
#### 5. Check Status
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm run status
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Output example:
|
|
110
|
+
```
|
|
111
|
+
=== PhoneClaw Skill Status ===
|
|
112
|
+
Device ID: abc123def456
|
|
113
|
+
Gateway: Connected
|
|
114
|
+
Relay: Connected
|
|
115
|
+
Active Sessions: 2
|
|
116
|
+
===============================
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Commands
|
|
120
|
+
|
|
121
|
+
| Command | Description |
|
|
122
|
+
|---------|-------------|
|
|
123
|
+
| `npm start` | Start the connector (daemon mode) |
|
|
124
|
+
| `npm run pair` | Generate pairing code and QR code |
|
|
125
|
+
| `npm run status` | Show connection status |
|
|
126
|
+
| `npm run build` | Build TypeScript code |
|
|
127
|
+
| `npm run dev` | Development mode with hot reload |
|
|
128
|
+
|
|
129
|
+
## Message Flow
|
|
130
|
+
|
|
131
|
+
### Phone Sends Message
|
|
132
|
+
|
|
133
|
+
1. User types message in PhoneClaw app
|
|
134
|
+
2. App sends to relay server: `{ type: "message", deviceId, sessionId, content }`
|
|
135
|
+
3. Relay forwards to this skill: `{ type: "forward_message", sessionId, content }`
|
|
136
|
+
4. Skill forwards to OpenClaw Gateway
|
|
137
|
+
5. Gateway processes with AI model
|
|
138
|
+
6. Skill receives streaming response
|
|
139
|
+
7. Skill forwards chunks to relay: `{ type: "stream_chunk", sessionId, chunk }`
|
|
140
|
+
8. Relay forwards to PhoneClaw app
|
|
141
|
+
9. App displays response in real-time
|
|
142
|
+
|
|
143
|
+
### Session Management
|
|
144
|
+
|
|
145
|
+
- Each conversation has a unique `sessionId`
|
|
146
|
+
- Sessions are tracked in memory
|
|
147
|
+
- User can rename, export, or delete sessions from the app
|
|
148
|
+
- Session history is stored on the device (not on relay server)
|
|
149
|
+
|
|
150
|
+
## Troubleshooting
|
|
151
|
+
|
|
152
|
+
### Cannot Connect to Relay Server
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Check network
|
|
156
|
+
ping relay.phoneclaw.com
|
|
157
|
+
|
|
158
|
+
# Check firewall (ensure outbound WebSocket on port 443 is allowed)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Cannot Connect to Gateway
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Check Gateway is running
|
|
165
|
+
openclaw gateway status
|
|
166
|
+
|
|
167
|
+
# Check Gateway URL and port (default: ws://127.0.0.1:18789)
|
|
168
|
+
|
|
169
|
+
# Check token matches Gateway config
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Pairing Fails
|
|
173
|
+
|
|
174
|
+
- Pairing codes expire after 10 minutes
|
|
175
|
+
- Generate a new code: `npm run pair`
|
|
176
|
+
- Ensure skill is connected to relay server: `npm run status`
|
|
177
|
+
|
|
178
|
+
### Messages Not Sending
|
|
179
|
+
|
|
180
|
+
- Check both Gateway and Relay connections: `npm run status`
|
|
181
|
+
- Review terminal logs for error messages
|
|
182
|
+
- Restart the connector: `npm start`
|
|
183
|
+
|
|
184
|
+
## Security Notes
|
|
185
|
+
|
|
186
|
+
- **Device Key**: `device.json` contains authentication credentials - keep it secure
|
|
187
|
+
- **Pairing Code**: Valid for 10 minutes, single-use only
|
|
188
|
+
- **Encryption**: All connections use WSS (WebSocket Secure) with TLS 1.3
|
|
189
|
+
- **Token Protection**: Never commit `gatewayToken` to version control
|
|
190
|
+
|
|
191
|
+
## Update
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
git pull
|
|
195
|
+
npm install
|
|
196
|
+
npm run build
|
|
197
|
+
npm start
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Support
|
|
201
|
+
|
|
202
|
+
- Technical documentation: See `requirements/technical-solution.md` in project root
|
|
203
|
+
- Issues: Report at GitHub repository
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface StoredDeviceToken {
|
|
2
|
+
/** Gateway-issued device token, opaque string. */
|
|
3
|
+
token: string;
|
|
4
|
+
/** Role the token was issued for, e.g. 'operator'. */
|
|
5
|
+
role: string;
|
|
6
|
+
/** Scopes granted together with the token. */
|
|
7
|
+
scopes: string[];
|
|
8
|
+
/** Device id this token is bound to. */
|
|
9
|
+
deviceId: string;
|
|
10
|
+
/** ISO timestamp when this token was persisted. */
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Persists the OpenClaw Gateway-issued device token for a given role so that
|
|
15
|
+
* subsequent skill launches can reuse it without going through the full
|
|
16
|
+
* device identity pairing flow again.
|
|
17
|
+
*/
|
|
18
|
+
export declare class DeviceAuthStore {
|
|
19
|
+
private storePath;
|
|
20
|
+
private cache;
|
|
21
|
+
constructor(configDir?: string);
|
|
22
|
+
/** Load token for a given role+deviceId. Returns null if not stored. */
|
|
23
|
+
loadToken(role: string, deviceId: string): Promise<StoredDeviceToken | null>;
|
|
24
|
+
/** Persist a new device token to disk and update in-memory cache. */
|
|
25
|
+
saveToken(token: StoredDeviceToken): Promise<void>;
|
|
26
|
+
/** Remove the persisted device token (e.g. after auth failure / reset). */
|
|
27
|
+
clearToken(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=device-auth-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-auth-store.d.ts","sourceRoot":"","sources":["../src/device-auth-store.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;CACnB;AAoBD;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAkC;gBAEnC,SAAS,CAAC,EAAE,MAAM;IAM9B,wEAAwE;IAClE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IA6BlF,qEAAqE;IAC/D,SAAS,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxD,2EAA2E;IACrE,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAQlC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
const AUTH_FILE = 'device-auth.json';
|
|
4
|
+
function isValidToken(value) {
|
|
5
|
+
if (!value || typeof value !== 'object')
|
|
6
|
+
return false;
|
|
7
|
+
const v = value;
|
|
8
|
+
return (typeof v.token === 'string' &&
|
|
9
|
+
v.token.length > 0 &&
|
|
10
|
+
typeof v.role === 'string' &&
|
|
11
|
+
v.role.length > 0 &&
|
|
12
|
+
Array.isArray(v.scopes) &&
|
|
13
|
+
v.scopes.every((s) => typeof s === 'string') &&
|
|
14
|
+
typeof v.deviceId === 'string' &&
|
|
15
|
+
v.deviceId.length > 0 &&
|
|
16
|
+
typeof v.updatedAt === 'string');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Persists the OpenClaw Gateway-issued device token for a given role so that
|
|
20
|
+
* subsequent skill launches can reuse it without going through the full
|
|
21
|
+
* device identity pairing flow again.
|
|
22
|
+
*/
|
|
23
|
+
export class DeviceAuthStore {
|
|
24
|
+
storePath;
|
|
25
|
+
cache = null;
|
|
26
|
+
constructor(configDir) {
|
|
27
|
+
this.storePath = configDir
|
|
28
|
+
? join(configDir, AUTH_FILE)
|
|
29
|
+
: join(process.cwd(), AUTH_FILE);
|
|
30
|
+
}
|
|
31
|
+
/** Load token for a given role+deviceId. Returns null if not stored. */
|
|
32
|
+
async loadToken(role, deviceId) {
|
|
33
|
+
if (this.cache &&
|
|
34
|
+
this.cache.role === role &&
|
|
35
|
+
this.cache.deviceId === deviceId) {
|
|
36
|
+
return this.cache;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const data = await fs.readFile(this.storePath, 'utf-8');
|
|
40
|
+
const parsed = JSON.parse(data);
|
|
41
|
+
if (isValidToken(parsed) &&
|
|
42
|
+
parsed.role === role &&
|
|
43
|
+
parsed.deviceId === deviceId) {
|
|
44
|
+
this.cache = parsed;
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
console.warn('[DeviceAuth] Existing auth file is invalid or mismatched; ignoring.');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// file does not exist or is unreadable — first launch or wiped
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
/** Persist a new device token to disk and update in-memory cache. */
|
|
55
|
+
async saveToken(token) {
|
|
56
|
+
await fs.mkdir(dirname(this.storePath), { recursive: true });
|
|
57
|
+
const payload = { ...token, updatedAt: new Date().toISOString() };
|
|
58
|
+
await fs.writeFile(this.storePath, JSON.stringify(payload, null, 2), {
|
|
59
|
+
mode: 0o600,
|
|
60
|
+
});
|
|
61
|
+
this.cache = payload;
|
|
62
|
+
console.log(`[DeviceAuth] Saved device token for role=${token.role} deviceId=${token.deviceId}`);
|
|
63
|
+
}
|
|
64
|
+
/** Remove the persisted device token (e.g. after auth failure / reset). */
|
|
65
|
+
async clearToken() {
|
|
66
|
+
this.cache = null;
|
|
67
|
+
try {
|
|
68
|
+
await fs.unlink(this.storePath);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// file does not exist — fine
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=device-auth-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-auth-store.js","sourceRoot":"","sources":["../src/device-auth-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface DeviceIdentity {
|
|
2
|
+
/** SHA-256 of raw public key bytes, base64url-encoded (no padding). */
|
|
3
|
+
deviceId: string;
|
|
4
|
+
/** Raw 32-byte Ed25519 public key, base64url-encoded (no padding). */
|
|
5
|
+
publicKey: string;
|
|
6
|
+
/** PKCS#8 PEM private key (32-byte Ed25519 seed wrapped). */
|
|
7
|
+
privateKey: string;
|
|
8
|
+
/** SPKI PEM public key. */
|
|
9
|
+
publicKeyPem: string;
|
|
10
|
+
/** ISO timestamp when identity was generated. */
|
|
11
|
+
createdAt: string;
|
|
12
|
+
}
|
|
13
|
+
declare function base64UrlEncode(buf: Buffer): string;
|
|
14
|
+
declare function deriveDeviceIdFromPublicKeyPem(publicKeyPem: string): string;
|
|
15
|
+
declare function publicKeyRawBase64UrlFromPem(publicKeyPem: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Manages the skill's persistent Ed25519 device identity used for OpenClaw
|
|
18
|
+
* Gateway pairing. The identity is stored next to the skill source so that
|
|
19
|
+
* re-launches keep the same deviceId and can be auto-approved as a paired
|
|
20
|
+
* node.
|
|
21
|
+
*/
|
|
22
|
+
export declare class DeviceIdentityStore {
|
|
23
|
+
private identityPath;
|
|
24
|
+
private identity;
|
|
25
|
+
constructor(configDir?: string);
|
|
26
|
+
/**
|
|
27
|
+
* Load existing identity or generate a new one. Persists to disk on first
|
|
28
|
+
* creation. Subsequent calls return the in-memory cached identity.
|
|
29
|
+
*/
|
|
30
|
+
getOrCreate(): Promise<DeviceIdentity>;
|
|
31
|
+
/** Verify the persisted publicKey (base64url raw) actually matches the PEM. */
|
|
32
|
+
private matchesPublicKey;
|
|
33
|
+
private persist;
|
|
34
|
+
/**
|
|
35
|
+
* Sign a payload string with the identity's Ed25519 private key. Returns
|
|
36
|
+
* base64url-encoded raw signature (no padding).
|
|
37
|
+
*/
|
|
38
|
+
signPayload(payload: string, identity: DeviceIdentity): string;
|
|
39
|
+
}
|
|
40
|
+
export { base64UrlEncode, publicKeyRawBase64UrlFromPem, deriveDeviceIdFromPublicKeyPem };
|
|
41
|
+
//# sourceMappingURL=device-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-identity.d.ts","sourceRoot":"","sources":["../src/device-identity.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,iBAAS,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM5C;AAED,iBAAS,8BAA8B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAgBpE;AAED,iBAAS,4BAA4B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAclE;AA4BD;;;;;GAKG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAA+B;gBAEnC,SAAS,CAAC,EAAE,MAAM;IAM9B;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,cAAc,CAAC;IAuB5C,+EAA+E;IAC/E,OAAO,CAAC,gBAAgB;YASV,OAAO;IAOrB;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM;CAK/D;AAED,OAAO,EAAE,eAAe,EAAE,4BAA4B,EAAE,8BAA8B,EAAE,CAAC"}
|