@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/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"