@keychat-io/keychat 0.1.18

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,170 @@
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
+ ```bash
14
+ openclaw plugins install @keychat-io/keychat
15
+ openclaw gateway restart
16
+ ```
17
+
18
+ That's it. The plugin automatically downloads the bridge binary and initializes the config on first load.
19
+
20
+ Supported platforms: macOS (ARM/x64), Linux (x64/ARM64).
21
+
22
+ Alternatively, install via shell script:
23
+
24
+ ```bash
25
+ curl -fsSL https://raw.githubusercontent.com/keychat-io/keychat/main/scripts/install.sh | bash
26
+ ```
27
+
28
+ ### Security Warnings
29
+
30
+ During installation, OpenClaw's security scanner may show three warnings. All are expected:
31
+
32
+ | Warning | Reason |
33
+ |---------|--------|
34
+ | Shell command execution (bridge-client.ts) | Spawns a Rust sidecar for Signal Protocol and MLS encryption. |
35
+ | Shell command execution (keychain.ts) | Stores identity mnemonics in the OS keychain (macOS Keychain / Linux libsecret). |
36
+ | Shell command execution (notify.ts) | Notifies the agent on startup so it can send the Keychat ID and QR code to the user. |
37
+
38
+ Source code is fully open: [github.com/keychat-io/keychat](https://github.com/keychat-io/keychat)
39
+
40
+ ### Upgrade
41
+
42
+ Tell your agent "upgrade keychat" in any chat, or manually:
43
+
44
+ ```bash
45
+ openclaw plugins install @keychat-io/keychat@latest
46
+ openclaw gateway restart
47
+ ```
48
+
49
+ ## Add Your Agent as a Keychat Contact
50
+
51
+ After `openclaw gateway restart`, the agent will send you its **Keychat ID**, **contact link**, and **QR code** in your active chat (Telegram, webchat, etc.):
52
+
53
+ ```
54
+ 🔑 Keychat ID: npub1...
55
+ 📱 Add contact: https://www.keychat.io/u/?k=npub1...
56
+ 🖼️ QR code image
57
+ ```
58
+
59
+ Open the [Keychat app](https://keychat.io) → tap the link, paste the npub, or scan the QR code to add as contact. If `dmPolicy` is `open` (default after auto-init), the agent accepts immediately.
60
+
61
+ ## Configuration
62
+
63
+ All options go under `channels.keychat` in your OpenClaw config (`~/.openclaw/openclaw.json`):
64
+
65
+ | Option | Type | Default | Description |
66
+ | ------------------ | -------- | ---------------------------- | --------------------------------------------------------- |
67
+ | `enabled` | boolean | `true` | Enable/disable the Keychat channel |
68
+ | `name` | string | — | Display name for this account |
69
+ | `relays` | string[] | `["wss://relay.keychat.io"]` | Nostr relay WebSocket URLs |
70
+ | `dmPolicy` | enum | `"open"` | Access policy: `pairing`, `allowlist`, `open`, `disabled` |
71
+ | `allowFrom` | string[] | `[]` | Allowed sender pubkeys (npub or hex) |
72
+ | `lightningAddress` | string | — | Lightning address for receiving payments |
73
+ | `nwcUri` | string | — | Nostr Wallet Connect URI for wallet access |
74
+
75
+ ### DM Policies
76
+
77
+ - **`open`**: Anyone can message the agent (default)
78
+ - **`pairing`**: New contacts require owner approval via OpenClaw
79
+ - **`allowlist`**: Only pubkeys in `allowFrom` can communicate
80
+ - **`disabled`**: No inbound messages accepted
81
+
82
+ ## Lightning Wallet
83
+
84
+ ### Lightning Address (receive-only)
85
+
86
+ ```json
87
+ { "lightningAddress": "user@walletofsatoshi.com" }
88
+ ```
89
+
90
+ ### Nostr Wallet Connect (NWC)
91
+
92
+ For full wallet access (create invoices, check balance, verify payments):
93
+
94
+ ```json
95
+ { "nwcUri": "nostr+walletconnect://pubkey?relay=wss://...&secret=..." }
96
+ ```
97
+
98
+ Generate an NWC connection string from your wallet app (Keychat, Alby Hub, Mutiny, Coinos, etc.).
99
+
100
+ **Security note**: The agent can receive payments freely. Outbound payments require owner approval.
101
+
102
+ ## Architecture
103
+
104
+ ```
105
+ ┌──────────────┐ JSON-RPC ┌─────────────────────┐ Nostr ┌─────────┐
106
+ │ OpenClaw │◄──────────────►│ keychat │◄───────────►│ Relays │
107
+ │ (TypeScript │ stdin/stdout │ (Rust sidecar) │ WebSocket │ │
108
+ │ plugin) │ │ │ │ │
109
+ └──────────────┘ └─────────────────────┘ └─────────┘
110
+ │ Signal Protocol DB │
111
+ │ (SQLite) │
112
+ └────────────────────┘
113
+ ```
114
+
115
+ - **TypeScript plugin**: OpenClaw channel integration, routing, pairing, message dispatch
116
+ - **Rust sidecar**: Signal Protocol sessions, Nostr transport, encryption/decryption
117
+ - **Communication**: JSON-RPC over stdin/stdout
118
+ - **Encryption**: Signal Protocol (Double Ratchet) with forward and backward secrecy
119
+ - **Transport**: Nostr relays (kind:4 DMs + kind:1059 Gift Wrap for friend requests)
120
+
121
+ ## Security
122
+
123
+ - **E2E Encryption**: All messages encrypted with Signal Protocol — relay operators cannot read content
124
+ - **Forward & Backward Secrecy**: Double Ratchet ensures compromising current keys reveals neither past nor future messages
125
+ - **Sovereign Identity**: Agent generates its own keypair — no third-party identity provider
126
+ - **Key Storage**: Mnemonic stored in system keychain (macOS Keychain, Linux secret service)
127
+ - **Sending Address Rotation**: Each outbound message uses a fresh Nostr keypair, preventing metadata correlation
128
+ - **Receiving Address Rotation**: Ratchet-derived addresses rotate almost per message, preventing traffic analysis
129
+
130
+ ## Troubleshooting
131
+
132
+ - **Bridge not starting**: Check `ls ~/.openclaw/extensions/keychat/bridge/target/release/keychat`. If missing, restart gateway (auto-downloads) or build from source: `cd bridge && cargo build --release`
133
+ - **Relay issues**: Verify relay URLs (`wss://...`), try alternative relays
134
+ - **Decryption errors**: Peer should delete old contact and re-add the agent
135
+ - **Messages not delivered**: Plugin queues failed messages (up to 100) and retries every 30s
136
+
137
+ ## Development
138
+
139
+ ```bash
140
+ cd bridge && cargo build --release
141
+ cargo test
142
+ ```
143
+
144
+ ### Project Structure
145
+
146
+ ```
147
+ ├── src/
148
+ │ ├── channel.ts # Main channel plugin
149
+ │ ├── bridge-client.ts # RPC client for Rust sidecar
150
+ │ ├── config-schema.ts # Zod config schema
151
+ │ ├── keychain.ts # System keychain integration
152
+ │ ├── lightning.ts # LNURL-pay support
153
+ │ ├── nwc.ts # Nostr Wallet Connect (NIP-47)
154
+ │ ├── media.ts # Blossom media encryption/upload
155
+ │ ├── qrcode.ts # QR code generation
156
+ │ ├── runtime.ts # Plugin runtime accessor
157
+ │ └── types.ts # Account types and resolvers
158
+ ├── bridge/src/
159
+ │ ├── main.rs # Sidecar entry point
160
+ │ ├── rpc.rs # JSON-RPC dispatch
161
+ │ ├── signal.rs # Signal Protocol manager
162
+ │ ├── protocol.rs # Keychat protocol types
163
+ │ ├── mls.rs # MLS large group support
164
+ │ └── transport.rs # Nostr relay transport
165
+ ├── scripts/
166
+ │ └── install.sh # One-line installer
167
+ ├── index.ts # Plugin entry point
168
+ ├── openclaw.plugin.json # Plugin manifest
169
+ └── LICENSE # AGPL-3.0
170
+ ```
@@ -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,205 @@
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, mkdirSync, chmodSync, writeFileSync, readFileSync } from "node:fs";
6
+ import { join, dirname } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { homedir } from "node:os";
9
+ import https from "node:https";
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+
13
+ /** Download a URL following redirects, return a Buffer. */
14
+ function downloadBinary(url: string): Promise<Buffer> {
15
+ return new Promise((resolve, reject) => {
16
+ https.get(url, (res) => {
17
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
18
+ return downloadBinary(res.headers.location).then(resolve, reject);
19
+ }
20
+ if (res.statusCode !== 200) {
21
+ return reject(new Error(`HTTP ${res.statusCode}`));
22
+ }
23
+ const chunks: Buffer[] = [];
24
+ res.on("data", (chunk: Buffer) => chunks.push(chunk));
25
+ res.on("end", () => resolve(Buffer.concat(chunks)));
26
+ res.on("error", reject);
27
+ }).on("error", reject);
28
+ });
29
+ }
30
+
31
+ /** Ensure bridge binary exists, download if missing. */
32
+ async function ensureBinary(): Promise<void> {
33
+ const binaryDir = join(__dirname, "bridge", "target", "release");
34
+ const binaryPath = join(binaryDir, "keychat-bridge");
35
+
36
+ if (existsSync(binaryPath)) return;
37
+
38
+ const platform = process.platform;
39
+ const arch = process.arch;
40
+ const artifacts: Record<string, string> = {
41
+ "darwin-arm64": "keychat-openclaw-darwin-arm64",
42
+ "darwin-x64": "keychat-openclaw-darwin-x64",
43
+ "linux-x64": "keychat-openclaw-linux-x64",
44
+ "linux-arm64": "keychat-openclaw-linux-arm64",
45
+ };
46
+
47
+ const artifact = artifacts[`${platform}-${arch}`];
48
+ if (!artifact) {
49
+ console.warn(`[keychat] No pre-compiled binary for ${platform}-${arch}. Build from source: cd bridge && cargo build --release`);
50
+ return;
51
+ }
52
+
53
+ const url = `https://github.com/keychat-io/keychat-openclaw/releases/latest/download/${artifact}`;
54
+ console.log(`[keychat] Bridge binary not found, downloading ${artifact}...`);
55
+
56
+ try {
57
+ mkdirSync(binaryDir, { recursive: true });
58
+ const buffer = await downloadBinary(url);
59
+ writeFileSync(binaryPath, buffer);
60
+ chmodSync(binaryPath, 0o755);
61
+ console.log("[keychat] ✅ Bridge binary installed");
62
+ } catch (err: any) {
63
+ console.warn(`[keychat] Binary download failed: ${err.message}`);
64
+ console.warn("[keychat] Build from source: cd bridge && cargo build --release");
65
+ }
66
+ }
67
+
68
+ /** Ensure channels.keychat exists in openclaw.json config. */
69
+ function ensureConfig(): void {
70
+ const configPath = join(homedir(), ".openclaw", "openclaw.json");
71
+ try {
72
+ let config: any = {};
73
+ if (existsSync(configPath)) {
74
+ config = JSON.parse(readFileSync(configPath, "utf-8"));
75
+ }
76
+ if (config.channels?.keychat) return;
77
+
78
+ if (!config.channels) config.channels = {};
79
+ config.channels.keychat = { enabled: true, dmPolicy: "open" };
80
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
81
+ console.log("[keychat] ✅ Config initialized (channels.keychat.enabled = true)");
82
+ } catch (err: any) {
83
+ console.warn(`[keychat] Could not auto-configure: ${err.message}`);
84
+ }
85
+ }
86
+
87
+ const plugin = {
88
+ id: "keychat",
89
+ name: "Keychat",
90
+ description:
91
+ "Keychat channel plugin — sovereign identity + E2E encrypted chat. " +
92
+ "Agent generates its own Public Key ID, just like a human Keychat user.",
93
+ configSchema: emptyPluginConfigSchema(),
94
+ register(api: OpenClawPluginApi) {
95
+ console.log("[keychat] register() called");
96
+ try {
97
+ // Auto-setup: download binary + init config on first load
98
+ ensureConfig();
99
+ ensureBinary().catch((err) => console.warn("[keychat] ensureBinary error:", err));
100
+ setKeychatRuntime(api.runtime);
101
+ console.log("[keychat] runtime set, registering channel...");
102
+ api.registerChannel({ plugin: keychatPlugin });
103
+ console.log("[keychat] channel registered successfully");
104
+
105
+ // Register keychat_identity agent tool so the agent can fetch ID info
106
+ // and send it to the user on whatever channel they're on
107
+ api.registerTool({
108
+ name: "keychat_identity",
109
+ label: "Keychat Identity",
110
+ description:
111
+ "Get the Keychat identity (npub, contact link, QR code path) for all active agent accounts. " +
112
+ "Call this after Keychat setup/install to retrieve the agent's Keychat ID and share it with the user.",
113
+ parameters: { type: "object", properties: {}, required: [] },
114
+ async execute(_toolCallId: string, _params: any) {
115
+ const contacts = getAllAgentContacts();
116
+ if (contacts.length === 0) {
117
+ return {
118
+ details: null,
119
+ content: [
120
+ {
121
+ type: "text" as const,
122
+ text: "No Keychat accounts are active yet. The bridge may still be starting — try again in a few seconds.",
123
+ },
124
+ ],
125
+ };
126
+ }
127
+ const { generateQRDataUrl } = await import("./src/qrcode.js");
128
+ const results = [];
129
+ const contentParts: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }> = [];
130
+
131
+ for (const c of contacts) {
132
+ const qrDataUrl = await generateQRDataUrl(c.npub);
133
+ results.push({
134
+ accountId: c.accountId,
135
+ npub: c.npub,
136
+ contactUrl: c.contactUrl,
137
+ });
138
+ contentParts.push({
139
+ type: "text" as const,
140
+ text: `Account: ${c.accountId}\nnpub: ${c.npub}\nContact: ${c.contactUrl}`,
141
+ });
142
+ if (qrDataUrl) {
143
+ // Extract base64 from data URL: "data:image/png;base64,..."
144
+ const base64 = qrDataUrl.replace(/^data:image\/png;base64,/, "");
145
+ contentParts.push({
146
+ type: "image" as const,
147
+ data: base64,
148
+ mimeType: "image/png",
149
+ });
150
+ }
151
+ }
152
+
153
+ return {
154
+ details: null,
155
+ content: contentParts,
156
+ };
157
+ },
158
+ });
159
+
160
+ // Register /keychat-id command to show agent Keychat ID, link, and QR
161
+ api.registerCommand({
162
+ name: "keychat-id",
163
+ description: "Show Keychat agent ID(s), contact link(s), and QR code path(s)",
164
+ handler: () => {
165
+ const contacts = getAllAgentContacts();
166
+ if (contacts.length === 0) {
167
+ return { text: "⚠️ No Keychat accounts are active yet. Wait for the bridge to start." };
168
+ }
169
+ const lines = contacts.map((c) => {
170
+ const qrExists = existsSync(c.qrCodePath);
171
+ return [
172
+ `🔑 **${c.accountId}**`,
173
+ ` npub: \`${c.npub}\``,
174
+ ` 📱 Link: ${c.contactUrl}`,
175
+ qrExists
176
+ ? ` 🖼️ QR: ${c.qrCodePath}`
177
+ : ` (QR image not found)`,
178
+ ].join("\n");
179
+ });
180
+ return { text: lines.join("\n\n") };
181
+ },
182
+ });
183
+ } catch (err) {
184
+ console.error("[keychat] register failed:", err);
185
+ }
186
+ },
187
+ };
188
+
189
+ export default plugin;
190
+ export { getAgentKeychatId, getAgentKeychatUrl, getAllAgentContacts, resetPeerSession } from "./src/channel.js";
191
+ export { generateQRDataUrl } from "./src/qrcode.js";
192
+ export {
193
+ requestInvoice,
194
+ fetchLnurlPayMetadata,
195
+ verifyPayment,
196
+ formatSats,
197
+ lightningAddressToLnurlp,
198
+ } from "./src/lightning.js";
199
+ export {
200
+ NwcClient,
201
+ parseNwcUri,
202
+ initNwc,
203
+ getNwcClient,
204
+ disconnectNwc,
205
+ } 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",
3
+ "version": "0.1.18",
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,76 @@
1
+ #!/bin/bash
2
+ # Build release binaries for all supported platforms.
3
+ # Requires: cargo, cargo-zigbuild, zig
4
+ # Linux targets use musl for static linking (no glibc dependency).
5
+ #
6
+ # Usage: ./scripts/build-release.sh [--upload v0.1.x]
7
+ set -e
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ BRIDGE_DIR="$SCRIPT_DIR/../bridge"
11
+ OUT_DIR="$SCRIPT_DIR/../dist"
12
+ REPO="keychat-io/keychat-openclaw"
13
+
14
+ cd "$BRIDGE_DIR"
15
+
16
+ echo "🔨 Building release binaries..."
17
+ echo ""
18
+
19
+ mkdir -p "$OUT_DIR"
20
+
21
+ # ── Darwin ARM64 (native) ──
22
+ echo " [1/4] darwin-arm64..."
23
+ cargo build --release 2>&1 | grep -v "^warning:" || true
24
+ cp target/release/keychat-openclaw "$OUT_DIR/keychat-openclaw-darwin-arm64"
25
+ echo " ✅ darwin-arm64"
26
+
27
+ # ── Darwin x64 (cross) ──
28
+ echo " [2/4] darwin-x64..."
29
+ if cargo zigbuild --target x86_64-apple-darwin --release 2>&1 | grep -v "^warning:" | tail -1; then
30
+ cp target/x86_64-apple-darwin/release/keychat-openclaw "$OUT_DIR/keychat-openclaw-darwin-x64"
31
+ echo " ✅ darwin-x64"
32
+ else
33
+ echo " ⚠️ darwin-x64 failed (optional — most Macs are ARM now)"
34
+ fi
35
+
36
+ # ── Linux x64 (musl static) ──
37
+ echo " [3/4] linux-x64 (musl)..."
38
+ cargo zigbuild --target x86_64-unknown-linux-musl --release 2>&1 | grep -v "^warning:" || true
39
+ cp target/x86_64-unknown-linux-musl/release/keychat-openclaw "$OUT_DIR/keychat-openclaw-linux-x64"
40
+ echo " ✅ linux-x64 (statically linked)"
41
+
42
+ # ── Linux ARM64 (musl static) ──
43
+ echo " [4/4] linux-arm64 (musl)..."
44
+ cargo zigbuild --target aarch64-unknown-linux-musl --release 2>&1 | grep -v "^warning:" || true
45
+ cp target/aarch64-unknown-linux-musl/release/keychat-openclaw "$OUT_DIR/keychat-openclaw-linux-arm64"
46
+ echo " ✅ linux-arm64 (statically linked)"
47
+
48
+ echo ""
49
+ echo "════════════════════════════════════════"
50
+ echo " 📦 All binaries in: $OUT_DIR"
51
+ ls -lh "$OUT_DIR"/keychat-openclaw-*
52
+ echo "════════════════════════════════════════"
53
+
54
+ # ── Optional: upload to GitHub Release ──
55
+ if [ "$1" = "--upload" ] && [ -n "$2" ]; then
56
+ TAG="$2"
57
+ echo ""
58
+ echo "🚀 Uploading to GitHub Release $TAG..."
59
+
60
+ if ! command -v gh &>/dev/null; then
61
+ echo "❌ gh CLI not found. Install: brew install gh"
62
+ exit 1
63
+ fi
64
+
65
+ # Create release if not exists
66
+ gh release view "$TAG" --repo "$REPO" &>/dev/null || \
67
+ gh release create "$TAG" --repo "$REPO" --title "$TAG" --notes "Release $TAG"
68
+
69
+ # Upload/overwrite artifacts
70
+ for f in "$OUT_DIR"/keychat-openclaw-*; do
71
+ echo " Uploading $(basename "$f")..."
72
+ gh release upload "$TAG" "$f" --repo "$REPO" --clobber
73
+ done
74
+
75
+ echo " ✅ All artifacts uploaded to $TAG"
76
+ fi