@keychat-io/keychat-openclaw 0.1.8
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/LICENSE +661 -0
- package/README.md +231 -0
- package/docs/setup-guide.md +124 -0
- package/index.ts +108 -0
- package/openclaw.plugin.json +38 -0
- package/package.json +33 -0
- package/scripts/install.sh +154 -0
- package/scripts/postinstall.mjs +97 -0
- package/scripts/publish.sh +25 -0
- package/src/bridge-client.ts +840 -0
- package/src/channel.ts +2607 -0
- package/src/config-schema.ts +50 -0
- package/src/ensure-binary.ts +65 -0
- package/src/keychain.ts +75 -0
- package/src/lightning.ts +128 -0
- package/src/media.ts +189 -0
- package/src/nwc.ts +360 -0
- package/src/paths.ts +38 -0
- package/src/qrcode-types.d.ts +4 -0
- package/src/qrcode.ts +9 -0
- package/src/runtime.ts +14 -0
- package/src/types.ts +125 -0
package/README.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Keychat — OpenClaw Channel Plugin
|
|
2
|
+
|
|
3
|
+
E2E encrypted AI agent communication via Keychat protocol.
|
|
4
|
+
|
|
5
|
+
## What is this?
|
|
6
|
+
|
|
7
|
+
This plugin gives your OpenClaw agent a **sovereign identity** — a self-generated Public Key ID (Nostr keypair) — and enables **end-to-end encrypted communication** using the Signal Protocol over Nostr relays.
|
|
8
|
+
|
|
9
|
+
Your agent becomes a full Keychat citizen: it can receive friend requests, establish Signal Protocol sessions, and exchange messages with Keychat app users. All messages are encrypted with forward and backward secrecy — not even relay operators can read them.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
### Option A: OpenClaw plugin (recommended)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
openclaw plugins install @keychat-io/keychat
|
|
17
|
+
openclaw gateway restart
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The bridge binary is auto-downloaded on first start. Supported platforms: macOS (ARM/x64), Linux (x64/ARM64).
|
|
21
|
+
|
|
22
|
+
### Option B: Shell script
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
curl -fsSL https://raw.githubusercontent.com/keychat-io/keychat-openclaw/main/scripts/install.sh | bash
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This clones the repo, downloads the binary, registers the plugin, and restarts the gateway in one step.
|
|
29
|
+
|
|
30
|
+
### Security Warnings
|
|
31
|
+
|
|
32
|
+
During installation, OpenClaw's security scanner may show two warnings. Both are expected:
|
|
33
|
+
|
|
34
|
+
| Warning | Reason |
|
|
35
|
+
|---------|--------|
|
|
36
|
+
| Shell command execution (bridge-client.ts) | Keychat's Signal Protocol and MLS Protocol encryption are implemented in Rust. The plugin spawns a Rust sidecar process to bridge between Node.js and the native crypto layer. |
|
|
37
|
+
| Shell command execution (keychain.ts) | Agent identity mnemonics are stored in the OS keychain (macOS Keychain / Linux libsecret) rather than plain files, which is the more secure option. |
|
|
38
|
+
|
|
39
|
+
Source code is fully open: [github.com/keychat-io/keychat-openclaw](https://github.com/keychat-io/keychat-openclaw)
|
|
40
|
+
|
|
41
|
+
### Upgrade
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# npm plugin
|
|
45
|
+
openclaw plugins install @keychat-io/keychat@latest
|
|
46
|
+
openclaw gateway restart
|
|
47
|
+
|
|
48
|
+
# shell script (re-run the same command)
|
|
49
|
+
curl -fsSL https://raw.githubusercontent.com/keychat-io/keychat-openclaw/main/scripts/install.sh | bash
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Connect
|
|
53
|
+
|
|
54
|
+
1. Run `openclaw status` to find your agent's **npub**
|
|
55
|
+
2. Open the [Keychat app](https://keychat.io) → tap **Add Contact** on the home page
|
|
56
|
+
3. Paste the agent's npub and confirm
|
|
57
|
+
4. The agent will automatically accept the friend request and establish an encrypted session
|
|
58
|
+
|
|
59
|
+
You can also scan the QR code instead of pasting the npub:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# View QR code in terminal
|
|
63
|
+
chafa ~/.openclaw/keychat/qr-default.png
|
|
64
|
+
# or generate from npub
|
|
65
|
+
qrencode -t ANSIUTF8 "https://www.keychat.io/u/?k=YOUR_NPUB"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
All options go under `channels.keychat` in your OpenClaw config:
|
|
71
|
+
|
|
72
|
+
| Option | Type | Default | Description |
|
|
73
|
+
| ------------------ | -------- | ---------------------------- | --------------------------------------------------------- |
|
|
74
|
+
| `enabled` | boolean | `false` | Enable/disable the Keychat channel |
|
|
75
|
+
| `name` | string | — | Display name for this account |
|
|
76
|
+
| `relays` | string[] | `["wss://relay.keychat.io"]` | Nostr relay WebSocket URLs |
|
|
77
|
+
| `dmPolicy` | enum | `"pairing"` | Access policy: `pairing`, `allowlist`, `open`, `disabled` |
|
|
78
|
+
| `allowFrom` | string[] | `[]` | Allowed sender pubkeys (npub or hex) |
|
|
79
|
+
| `mnemonic` | string | — | Identity mnemonic (auto-generated, stored in keychain) |
|
|
80
|
+
| `publicKey` | string | — | Derived hex public key (read-only) |
|
|
81
|
+
| `npub` | string | — | Derived bech32 npub (read-only) |
|
|
82
|
+
| `lightningAddress` | string | — | Lightning address for receiving payments |
|
|
83
|
+
| `nwcUri` | string | — | Nostr Wallet Connect URI for wallet access |
|
|
84
|
+
| `markdown` | object | — | Markdown formatting overrides |
|
|
85
|
+
|
|
86
|
+
### DM Policies
|
|
87
|
+
|
|
88
|
+
- **`pairing`** (default): New contacts require owner approval via OpenClaw's pairing system
|
|
89
|
+
- **`allowlist`**: Only pubkeys in `allowFrom` can communicate
|
|
90
|
+
- **`open`**: Anyone can message the agent
|
|
91
|
+
- **`disabled`**: No inbound messages accepted
|
|
92
|
+
|
|
93
|
+
## Lightning Wallet
|
|
94
|
+
|
|
95
|
+
Keychat supports Lightning payments:
|
|
96
|
+
|
|
97
|
+
### Lightning Address (receive-only)
|
|
98
|
+
|
|
99
|
+
Configure a Lightning address so your agent can generate invoices:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"lightningAddress": "user@walletofsatoshi.com"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The agent can create invoices via LNURL-pay protocol. Note: payment verification depends on the provider (some don't support verify URLs).
|
|
108
|
+
|
|
109
|
+
### Nostr Wallet Connect (NWC)
|
|
110
|
+
|
|
111
|
+
For full wallet access (create invoices, check balance, verify payments), configure NWC:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"nwcUri": "nostr+walletconnect://pubkey?relay=wss://...&secret=..."
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Generate an NWC connection string from your wallet app (Keychat, Alby Hub, Mutiny, Coinos, etc.).
|
|
120
|
+
|
|
121
|
+
**Security note**: The agent can receive payments freely. Outbound payments require owner approval — the agent will forward the invoice to the owner instead of paying directly.
|
|
122
|
+
|
|
123
|
+
## Architecture
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
┌──────────────┐ JSON-RPC ┌─────────────────────┐ Nostr ┌─────────┐
|
|
127
|
+
│ OpenClaw │◄──────────────►│ keychat-openclaw │◄───────────►│ Relays │
|
|
128
|
+
│ (TypeScript │ stdin/stdout │ (Rust sidecar) │ WebSocket │ │
|
|
129
|
+
│ plugin) │ │ │ │ │
|
|
130
|
+
└──────────────┘ └─────────────────────┘ └─────────┘
|
|
131
|
+
│ Signal Protocol DB │
|
|
132
|
+
│ (SQLite) │
|
|
133
|
+
└────────────────────┘
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- **TypeScript plugin** (`src/channel.ts`): Integrates with OpenClaw's channel system, handles routing, pairing, and message dispatch
|
|
137
|
+
- **Rust sidecar** (`bridge/`): Manages Signal Protocol sessions, Nostr transport, encryption/decryption
|
|
138
|
+
- **Communication**: JSON-RPC over stdin/stdout of spawned child process
|
|
139
|
+
- **Encryption**: Signal Protocol (Double Ratchet) for E2E encryption
|
|
140
|
+
- **Transport**: Nostr relays (kind:4 DMs + kind:1059 Gift Wrap for friend requests)
|
|
141
|
+
|
|
142
|
+
## Pairing
|
|
143
|
+
|
|
144
|
+
1. Agent starts and logs its **npub** and **contact URL**
|
|
145
|
+
2. User opens the URL or scans the QR code in the Keychat app
|
|
146
|
+
3. Keychat app sends a **friend request** (Gift Wrap, kind:1059)
|
|
147
|
+
4. Agent processes the hello, establishes Signal session, and replies
|
|
148
|
+
5. If `dmPolicy` is `pairing`, the owner must approve via `openclaw pair approve keychat <pubkey>`
|
|
149
|
+
6. Once approved, full bidirectional encrypted chat is established
|
|
150
|
+
|
|
151
|
+
## Security
|
|
152
|
+
|
|
153
|
+
- **E2E Encryption**: All messages encrypted with Signal Protocol — relay operators cannot read content
|
|
154
|
+
- **Forward Secrecy**: Compromising current keys doesn't reveal past messages (Double Ratchet)
|
|
155
|
+
- **Backward Secrecy**: New messages use fresh keys after each exchange
|
|
156
|
+
- **Sovereign Identity**: Agent generates its own keypair — no third-party identity provider
|
|
157
|
+
- **Key Storage**: Mnemonic stored in system keychain (macOS Keychain, Linux secret service); falls back to config file
|
|
158
|
+
- **Ephemeral Senders**: Each outbound message uses a fresh Nostr keypair, preventing metadata correlation
|
|
159
|
+
- **Receiving Address Rotation**: Ratchet-derived addresses rotate per message, preventing traffic analysis
|
|
160
|
+
|
|
161
|
+
## Troubleshooting
|
|
162
|
+
|
|
163
|
+
### Bridge not starting
|
|
164
|
+
|
|
165
|
+
- Ensure the binary exists: `ls bridge/target/release/keychat-openclaw`
|
|
166
|
+
- Rebuild: `cd bridge && cargo build --release`
|
|
167
|
+
- Check logs for startup errors
|
|
168
|
+
|
|
169
|
+
### Relay connection issues
|
|
170
|
+
|
|
171
|
+
- Verify relay URLs are correct WebSocket endpoints (`wss://...`)
|
|
172
|
+
- Test relay connectivity: `websocat wss://relay.keychat.io`
|
|
173
|
+
- Try alternative relays
|
|
174
|
+
|
|
175
|
+
### Session corruption
|
|
176
|
+
|
|
177
|
+
- If messages fail to decrypt, the plugin will automatically warn the peer
|
|
178
|
+
- The peer should re-add the agent as a contact to establish a new session
|
|
179
|
+
- As a last resort, delete the Signal DB: `rm ~/.openclaw/keychat/signal-default.db` and restart
|
|
180
|
+
|
|
181
|
+
### Messages not delivered
|
|
182
|
+
|
|
183
|
+
- Check if the bridge is responsive: look for health check logs
|
|
184
|
+
- The plugin queues failed messages (up to 100) and retries every 30s
|
|
185
|
+
- Pending messages are also flushed after bridge restart
|
|
186
|
+
|
|
187
|
+
### No QR code generated
|
|
188
|
+
|
|
189
|
+
- Install the `qrcode` npm package: `npm install qrcode`
|
|
190
|
+
- The contact URL in logs works without QR code
|
|
191
|
+
|
|
192
|
+
## Development
|
|
193
|
+
|
|
194
|
+
### Building
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Build the Rust sidecar
|
|
198
|
+
cd bridge && cargo build --release
|
|
199
|
+
|
|
200
|
+
# Run tests
|
|
201
|
+
cargo test
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Project Structure
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
├── src/
|
|
208
|
+
│ ├── channel.ts # Main channel plugin (OpenClaw integration)
|
|
209
|
+
│ ├── bridge-client.ts # TypeScript RPC client for the Rust sidecar
|
|
210
|
+
│ ├── config-schema.ts # Zod config schema
|
|
211
|
+
│ ├── keychain.ts # System keychain integration
|
|
212
|
+
│ ├── lightning.ts # Lightning address (LNURL-pay) support
|
|
213
|
+
│ ├── nwc.ts # Nostr Wallet Connect (NIP-47) client
|
|
214
|
+
│ ├── media.ts # Blossom media encryption/upload
|
|
215
|
+
│ ├── qrcode.ts # QR code generation
|
|
216
|
+
│ ├── runtime.ts # Plugin runtime accessor
|
|
217
|
+
│ └── types.ts # Account types and resolvers
|
|
218
|
+
├── bridge/
|
|
219
|
+
│ └── src/
|
|
220
|
+
│ ├── main.rs # Sidecar entry point (stdin/stdout loop)
|
|
221
|
+
│ ├── rpc.rs # JSON-RPC dispatch
|
|
222
|
+
│ ├── signal.rs # Signal Protocol manager
|
|
223
|
+
│ ├── protocol.rs # Keychat protocol types
|
|
224
|
+
│ ├── mls.rs # MLS large group support
|
|
225
|
+
│ └── transport.rs # Nostr relay transport
|
|
226
|
+
├── scripts/
|
|
227
|
+
│ └── install.sh # One-line installer
|
|
228
|
+
├── index.ts # Plugin entry point
|
|
229
|
+
├── openclaw.plugin.json # Plugin manifest
|
|
230
|
+
└── LICENSE # AGPL-3.0
|
|
231
|
+
```
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Keychat — Setup Guide
|
|
2
|
+
|
|
3
|
+
Step-by-step instructions to get your OpenClaw agent communicating via Keychat.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- **macOS or Linux** (arm64 or x86_64)
|
|
8
|
+
- **Node.js 20+**
|
|
9
|
+
- **OpenClaw**: Installed and configured (`openclaw gateway status` should work)
|
|
10
|
+
|
|
11
|
+
## Step 1: Install the Plugin
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
openclaw plugins install @keychat-io/keychat
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This auto-downloads the pre-compiled Rust sidecar binary for your platform. No Rust toolchain needed.
|
|
18
|
+
|
|
19
|
+
> **Building from source** (optional): If no pre-compiled binary is available for your platform, install [Rust](https://rustup.rs/) and run `cd bridge && cargo build --release`.
|
|
20
|
+
|
|
21
|
+
## Step 2: Configure OpenClaw
|
|
22
|
+
|
|
23
|
+
Edit `~/.openclaw/openclaw.json` and add the Keychat channel config:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"channels": {
|
|
28
|
+
"keychat": {
|
|
29
|
+
"enabled": true,
|
|
30
|
+
"relays": [
|
|
31
|
+
"wss://relay.keychat.io",
|
|
32
|
+
"wss://relay.damus.io"
|
|
33
|
+
],
|
|
34
|
+
"dmPolicy": "pairing"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### DM Policies
|
|
41
|
+
|
|
42
|
+
| Policy | Description |
|
|
43
|
+
|--------|-------------|
|
|
44
|
+
| `pairing` | New contacts require owner approval (default) |
|
|
45
|
+
| `allowlist` | Only pubkeys in `allowFrom` can communicate |
|
|
46
|
+
| `open` | Anyone can message the agent |
|
|
47
|
+
| `disabled` | No inbound messages accepted |
|
|
48
|
+
|
|
49
|
+
## Step 3: Restart the Gateway
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
openclaw gateway restart
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Watch the logs for your agent's Keychat ID:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
═══════════════════════════════════════════════════
|
|
59
|
+
🔑 Agent Keychat ID (scan with Keychat app):
|
|
60
|
+
|
|
61
|
+
npub1...
|
|
62
|
+
|
|
63
|
+
Add contact link:
|
|
64
|
+
https://www.keychat.io/u/?k=npub1...
|
|
65
|
+
═══════════════════════════════════════════════════
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
A QR code is also saved to `~/.openclaw/keychat/qr-default.png`.
|
|
69
|
+
|
|
70
|
+
## Step 4: Connect with Keychat App
|
|
71
|
+
|
|
72
|
+
1. Open the [Keychat app](https://www.keychat.io/) on your phone
|
|
73
|
+
2. Tap **Add Contact**
|
|
74
|
+
3. Scan the QR code at `~/.openclaw/keychat/qr-default.png`, or paste the npub / contact URL
|
|
75
|
+
4. Send a friend request
|
|
76
|
+
5. If `dmPolicy` is `pairing`, approve the request:
|
|
77
|
+
```bash
|
|
78
|
+
openclaw pair approve keychat <sender-pubkey>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Step 5: Start Chatting
|
|
82
|
+
|
|
83
|
+
Send a message from the Keychat app — your agent will respond with E2E encrypted messages.
|
|
84
|
+
|
|
85
|
+
## Identity Management
|
|
86
|
+
|
|
87
|
+
- **First run**: Agent auto-generates a mnemonic and stores it in your system keychain (macOS Keychain / Linux libsecret)
|
|
88
|
+
- **Backup**: Export the mnemonic from your keychain if needed
|
|
89
|
+
- **Restore**: Set `mnemonic` in the channel config to restore an existing identity on a new machine
|
|
90
|
+
- **Signal DB**: Stored at `~/.openclaw/keychat/signal-default.db` — **do not delete** (destroys all encrypted sessions)
|
|
91
|
+
|
|
92
|
+
## Verifying the Setup
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
openclaw gateway status
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The Keychat channel should show as running with the agent's npub.
|
|
99
|
+
|
|
100
|
+
## Lightning Wallet (Optional)
|
|
101
|
+
|
|
102
|
+
Add a Lightning address for receiving payments:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"channels": {
|
|
107
|
+
"keychat": {
|
|
108
|
+
"lightningAddress": "user@walletofsatoshi.com"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
For full wallet access (balance, payments), configure [Nostr Wallet Connect](https://github.com/nostr-protocol/nips/blob/master/47.md):
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"channels": {
|
|
119
|
+
"keychat": {
|
|
120
|
+
"nwcUri": "nostr+walletconnect://pubkey?relay=wss://...&secret=..."
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
package/index.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
3
|
+
import { keychatPlugin, getAgentKeychatId, getAgentKeychatUrl, getAllAgentContacts } from "./src/channel.js";
|
|
4
|
+
import { setKeychatRuntime } from "./src/runtime.js";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
const plugin = {
|
|
8
|
+
id: "keychat",
|
|
9
|
+
name: "Keychat",
|
|
10
|
+
description:
|
|
11
|
+
"Keychat channel plugin — sovereign identity + E2E encrypted chat. " +
|
|
12
|
+
"Agent generates its own Public Key ID, just like a human Keychat user.",
|
|
13
|
+
configSchema: emptyPluginConfigSchema(),
|
|
14
|
+
register(api: OpenClawPluginApi) {
|
|
15
|
+
console.log("[keychat] register() called");
|
|
16
|
+
try {
|
|
17
|
+
setKeychatRuntime(api.runtime);
|
|
18
|
+
console.log("[keychat] runtime set, registering channel...");
|
|
19
|
+
api.registerChannel({ plugin: keychatPlugin });
|
|
20
|
+
console.log("[keychat] channel registered successfully");
|
|
21
|
+
|
|
22
|
+
// Register keychat_identity agent tool so the agent can fetch ID info
|
|
23
|
+
// and send it to the user on whatever channel they're on
|
|
24
|
+
api.registerTool({
|
|
25
|
+
name: "keychat_identity",
|
|
26
|
+
label: "Keychat Identity",
|
|
27
|
+
description:
|
|
28
|
+
"Get the Keychat identity (npub, contact link, QR code path) for all active agent accounts. " +
|
|
29
|
+
"Call this after Keychat setup/install to retrieve the agent's Keychat ID and share it with the user.",
|
|
30
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
31
|
+
async execute(_toolCallId: string, _params: any) {
|
|
32
|
+
const contacts = getAllAgentContacts();
|
|
33
|
+
if (contacts.length === 0) {
|
|
34
|
+
return {
|
|
35
|
+
details: null,
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: "text" as const,
|
|
39
|
+
text: "No Keychat accounts are active yet. The bridge may still be starting — try again in a few seconds.",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const result = contacts.map((c) => ({
|
|
45
|
+
accountId: c.accountId,
|
|
46
|
+
npub: c.npub,
|
|
47
|
+
contactUrl: c.contactUrl,
|
|
48
|
+
qrCodePath: c.qrCodePath,
|
|
49
|
+
qrExists: existsSync(c.qrCodePath),
|
|
50
|
+
}));
|
|
51
|
+
return {
|
|
52
|
+
details: null,
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: "text" as const,
|
|
56
|
+
text: JSON.stringify(result, null, 2),
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Register /keychat-id command to show agent Keychat ID, link, and QR
|
|
64
|
+
api.registerCommand({
|
|
65
|
+
name: "keychat-id",
|
|
66
|
+
description: "Show Keychat agent ID(s), contact link(s), and QR code path(s)",
|
|
67
|
+
handler: () => {
|
|
68
|
+
const contacts = getAllAgentContacts();
|
|
69
|
+
if (contacts.length === 0) {
|
|
70
|
+
return { text: "⚠️ No Keychat accounts are active yet. Wait for the bridge to start." };
|
|
71
|
+
}
|
|
72
|
+
const lines = contacts.map((c) => {
|
|
73
|
+
const qrExists = existsSync(c.qrCodePath);
|
|
74
|
+
return [
|
|
75
|
+
`🔑 **${c.accountId}**`,
|
|
76
|
+
` npub: \`${c.npub}\``,
|
|
77
|
+
` 📱 Link: ${c.contactUrl}`,
|
|
78
|
+
qrExists
|
|
79
|
+
? ` 🖼️ QR: ${c.qrCodePath}`
|
|
80
|
+
: ` (QR image not found)`,
|
|
81
|
+
].join("\n");
|
|
82
|
+
});
|
|
83
|
+
return { text: lines.join("\n\n") };
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error("[keychat] register failed:", err);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default plugin;
|
|
93
|
+
export { getAgentKeychatId, getAgentKeychatUrl, getAllAgentContacts, resetPeerSession } from "./src/channel.js";
|
|
94
|
+
export { generateQRDataUrl } from "./src/qrcode.js";
|
|
95
|
+
export {
|
|
96
|
+
requestInvoice,
|
|
97
|
+
fetchLnurlPayMetadata,
|
|
98
|
+
verifyPayment,
|
|
99
|
+
formatSats,
|
|
100
|
+
lightningAddressToLnurlp,
|
|
101
|
+
} from "./src/lightning.js";
|
|
102
|
+
export {
|
|
103
|
+
NwcClient,
|
|
104
|
+
parseNwcUri,
|
|
105
|
+
initNwc,
|
|
106
|
+
getNwcClient,
|
|
107
|
+
disconnectNwc,
|
|
108
|
+
} from "./src/nwc.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "keychat",
|
|
3
|
+
"channels": ["keychat"],
|
|
4
|
+
"name": "Keychat",
|
|
5
|
+
"description": "Sovereign identity + E2E encrypted chat via Signal Protocol over Nostr relays. Lightning wallet support via LNURL-pay and NWC.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"repository": "https://github.com/keychat-io/keychat-openclaw",
|
|
8
|
+
"license": "AGPL-3.0",
|
|
9
|
+
"configSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"name": { "type": "string", "description": "Display name for this account" },
|
|
14
|
+
"enabled": { "type": "boolean", "description": "Enable/disable the Keychat channel" },
|
|
15
|
+
"markdown": { "type": "object", "description": "Markdown formatting overrides" },
|
|
16
|
+
"mnemonic": { "type": "string", "description": "Identity mnemonic (auto-generated, stored in keychain)" },
|
|
17
|
+
"publicKey": { "type": "string", "description": "Derived hex public key (read-only)" },
|
|
18
|
+
"npub": { "type": "string", "description": "Derived bech32 npub (read-only)" },
|
|
19
|
+
"relays": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": { "type": "string" },
|
|
22
|
+
"description": "Nostr relay WebSocket URLs"
|
|
23
|
+
},
|
|
24
|
+
"dmPolicy": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"enum": ["pairing", "allowlist", "open", "disabled"],
|
|
27
|
+
"description": "DM access policy"
|
|
28
|
+
},
|
|
29
|
+
"allowFrom": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": { "type": ["string", "number"] },
|
|
32
|
+
"description": "Allowed sender pubkeys (npub or hex)"
|
|
33
|
+
},
|
|
34
|
+
"lightningAddress": { "type": "string", "description": "Lightning address for receiving payments" },
|
|
35
|
+
"nwcUri": { "type": "string", "description": "Nostr Wallet Connect URI" }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@keychat-io/keychat-openclaw",
|
|
3
|
+
"version": "0.1.8",
|
|
4
|
+
"description": "Keychat — E2E encrypted chat + Lightning wallet for OpenClaw agents",
|
|
5
|
+
"license": "AGPL-3.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/keychat-io/keychat-openclaw.git"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"postinstall": "node scripts/postinstall.mjs"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@nostr-dev-kit/ndk": "^3.0.0",
|
|
16
|
+
"qrcode": "^1.5.4",
|
|
17
|
+
"zod": "^4.3.6"
|
|
18
|
+
},
|
|
19
|
+
"openclaw": {
|
|
20
|
+
"extensions": [
|
|
21
|
+
"./index.ts"
|
|
22
|
+
],
|
|
23
|
+
"channel": {
|
|
24
|
+
"id": "keychat",
|
|
25
|
+
"label": "Keychat",
|
|
26
|
+
"selectionLabel": "Keychat (E2E Encrypted)",
|
|
27
|
+
"docsPath": "/channels/keychat",
|
|
28
|
+
"docsLabel": "keychat",
|
|
29
|
+
"blurb": "Sovereign identity + E2E encrypted chat via Signal Protocol over Nostr relays.",
|
|
30
|
+
"order": 56
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Keychat — one-line installer
|
|
3
|
+
# Usage: curl -fsSL https://raw.githubusercontent.com/keychat-io/keychat-openclaw/main/scripts/install.sh | bash
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
REPO="keychat-io/keychat-openclaw"
|
|
7
|
+
INSTALL_DIR="${OPENCLAW_EXTENSIONS:-$HOME/.openclaw/extensions}/keychat"
|
|
8
|
+
BINARY="$INSTALL_DIR/bridge/target/release/keychat-openclaw"
|
|
9
|
+
|
|
10
|
+
echo "🔑 Installing Keychat"
|
|
11
|
+
echo ""
|
|
12
|
+
|
|
13
|
+
# ── Check OpenClaw ──
|
|
14
|
+
if ! command -v openclaw &>/dev/null; then
|
|
15
|
+
echo "❌ OpenClaw not found. Install it first: https://docs.openclaw.ai"
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# ── Detect platform ──
|
|
20
|
+
detect_artifact() {
|
|
21
|
+
local arch=$(uname -m)
|
|
22
|
+
local os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
23
|
+
case "$os-$arch" in
|
|
24
|
+
darwin-arm64) echo "keychat-openclaw-darwin-arm64" ;;
|
|
25
|
+
darwin-x86_64) echo "keychat-openclaw-darwin-x64" ;;
|
|
26
|
+
linux-x86_64) echo "keychat-openclaw-linux-x64" ;;
|
|
27
|
+
linux-aarch64) echo "keychat-openclaw-linux-arm64" ;;
|
|
28
|
+
*) echo "" ;;
|
|
29
|
+
esac
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# ── Clone or update repo ──
|
|
33
|
+
if [ -d "$INSTALL_DIR/.git" ]; then
|
|
34
|
+
echo "📦 Updating existing installation..."
|
|
35
|
+
cd "$INSTALL_DIR"
|
|
36
|
+
git pull --ff-only 2>/dev/null || true
|
|
37
|
+
else
|
|
38
|
+
echo "📦 Downloading Keychat..."
|
|
39
|
+
mkdir -p "$(dirname "$INSTALL_DIR")"
|
|
40
|
+
git clone --depth 1 "https://github.com/$REPO.git" "$INSTALL_DIR" 2>/dev/null
|
|
41
|
+
cd "$INSTALL_DIR"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# ── Install npm dependencies ──
|
|
45
|
+
npm install --omit=dev --silent 2>/dev/null || true
|
|
46
|
+
|
|
47
|
+
# ── Get binary ──
|
|
48
|
+
if [ -f "$BINARY" ]; then
|
|
49
|
+
echo "✅ Bridge binary already exists"
|
|
50
|
+
else
|
|
51
|
+
ARTIFACT=$(detect_artifact)
|
|
52
|
+
DOWNLOADED=false
|
|
53
|
+
|
|
54
|
+
if [ -n "$ARTIFACT" ]; then
|
|
55
|
+
echo "📦 Downloading pre-compiled binary ($ARTIFACT)..."
|
|
56
|
+
URL="https://github.com/$REPO/releases/latest/download/$ARTIFACT"
|
|
57
|
+
mkdir -p "$(dirname "$BINARY")"
|
|
58
|
+
if curl -fSL "$URL" -o "$BINARY" 2>/dev/null; then
|
|
59
|
+
chmod +x "$BINARY"
|
|
60
|
+
echo "✅ Binary downloaded"
|
|
61
|
+
DOWNLOADED=true
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
if [ "$DOWNLOADED" = false ]; then
|
|
66
|
+
if command -v cargo &>/dev/null; then
|
|
67
|
+
echo "🔨 Building from source (this may take a few minutes)..."
|
|
68
|
+
cd "$INSTALL_DIR/bridge"
|
|
69
|
+
cargo build --release 2>&1 | tail -3
|
|
70
|
+
cd "$INSTALL_DIR"
|
|
71
|
+
if [ ! -f "$BINARY" ]; then
|
|
72
|
+
echo "❌ Build failed"
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
echo "✅ Built from source"
|
|
76
|
+
else
|
|
77
|
+
echo "❌ No pre-compiled binary for your platform and Rust not installed."
|
|
78
|
+
echo " Install Rust: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ── Register plugin ──
|
|
85
|
+
echo ""
|
|
86
|
+
echo "📦 Registering plugin..."
|
|
87
|
+
openclaw plugins install "$INSTALL_DIR" 2>&1 || true
|
|
88
|
+
|
|
89
|
+
# ── Auto-configure ──
|
|
90
|
+
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
|
|
91
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
92
|
+
if grep -q '"keychat"' "$CONFIG_FILE" 2>/dev/null; then
|
|
93
|
+
echo "ℹ️ Keychat already in config"
|
|
94
|
+
else
|
|
95
|
+
# Insert keychat into channels object
|
|
96
|
+
if command -v python3 &>/dev/null; then
|
|
97
|
+
python3 -c "
|
|
98
|
+
import json, sys
|
|
99
|
+
try:
|
|
100
|
+
with open('$CONFIG_FILE', 'r') as f:
|
|
101
|
+
cfg = json.load(f)
|
|
102
|
+
if 'channels' not in cfg:
|
|
103
|
+
cfg['channels'] = {}
|
|
104
|
+
cfg['channels']['keychat'] = {'enabled': True}
|
|
105
|
+
with open('$CONFIG_FILE', 'w') as f:
|
|
106
|
+
json.dump(cfg, f, indent=2, ensure_ascii=False)
|
|
107
|
+
print('✅ Keychat enabled in config')
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f'⚠️ Could not auto-configure: {e}')
|
|
110
|
+
print(' Add manually: \"keychat\": {{\"enabled\": true}} under channels')
|
|
111
|
+
"
|
|
112
|
+
elif command -v node &>/dev/null; then
|
|
113
|
+
node -e "
|
|
114
|
+
const fs = require('fs');
|
|
115
|
+
try {
|
|
116
|
+
const cfg = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
|
|
117
|
+
if (!cfg.channels) cfg.channels = {};
|
|
118
|
+
cfg.channels.keychat = { enabled: true };
|
|
119
|
+
fs.writeFileSync('$CONFIG_FILE', JSON.stringify(cfg, null, 2));
|
|
120
|
+
console.log('✅ Keychat enabled in config');
|
|
121
|
+
} catch(e) {
|
|
122
|
+
console.log('⚠️ Could not auto-configure:', e.message);
|
|
123
|
+
console.log(' Add manually: \"keychat\": {\"enabled\": true} under channels');
|
|
124
|
+
}
|
|
125
|
+
"
|
|
126
|
+
else
|
|
127
|
+
echo "⚠️ Add to $CONFIG_FILE under \"channels\":"
|
|
128
|
+
echo ' "keychat": { "enabled": true }'
|
|
129
|
+
fi
|
|
130
|
+
fi
|
|
131
|
+
else
|
|
132
|
+
echo "⚠️ Config not found at $CONFIG_FILE"
|
|
133
|
+
echo " Run 'openclaw init' first, then re-run this installer"
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# ── Restart gateway ──
|
|
137
|
+
echo ""
|
|
138
|
+
echo "🔄 Restarting gateway..."
|
|
139
|
+
openclaw gateway restart 2>&1 || true
|
|
140
|
+
|
|
141
|
+
# ── Done ──
|
|
142
|
+
echo ""
|
|
143
|
+
echo "🎉 Keychat installed!"
|
|
144
|
+
echo ""
|
|
145
|
+
echo "Your agent's Keychat ID will appear in the gateway logs."
|
|
146
|
+
echo "Run 'openclaw status' to see it."
|
|
147
|
+
echo ""
|
|
148
|
+
echo "To connect: open the Keychat app and scan the QR code."
|
|
149
|
+
echo ""
|
|
150
|
+
echo "View QR code in terminal:"
|
|
151
|
+
echo " chafa ~/.openclaw/keychat/qr-default.png"
|
|
152
|
+
echo " # or: qrencode -t ANSIUTF8 \"\$(openclaw status 2>/dev/null | grep -oP 'npub[a-z0-9]+' | head -1 | xargs -I{} echo 'https://www.keychat.io/u/?k={}')\""
|
|
153
|
+
echo ""
|
|
154
|
+
echo "Docs: https://github.com/$REPO"
|