@keychat-io/keychat-openclaw 0.1.14 → 0.1.16

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 CHANGED
@@ -10,115 +10,94 @@ Your agent becomes a full Keychat citizen: it can receive friend requests, estab
10
10
 
11
11
  ## Install
12
12
 
13
- ### Option A: OpenClaw plugin (recommended)
14
-
15
13
  ```bash
16
14
  openclaw plugins install @keychat-io/keychat-openclaw
17
15
  openclaw gateway restart
18
16
  ```
19
17
 
18
+ That's it. The plugin automatically downloads the bridge binary and initializes the config on first load.
19
+
20
20
  Supported platforms: macOS (ARM/x64), Linux (x64/ARM64).
21
21
 
22
- ### Option B: Shell script
22
+ Alternatively, install via shell script:
23
23
 
24
24
  ```bash
25
25
  curl -fsSL https://raw.githubusercontent.com/keychat-io/keychat-openclaw/main/scripts/install.sh | bash
26
26
  ```
27
27
 
28
- This clones the repo, downloads the binary, registers the plugin, and restarts the gateway in one step.
29
-
30
28
  ### Security Warnings
31
29
 
32
30
  During installation, OpenClaw's security scanner may show three warnings. All are expected:
33
31
 
34
32
  | Warning | Reason |
35
33
  |---------|--------|
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
- | Shell command execution (notify.ts) | After startup, the plugin notifies the agent so it can proactively send the Keychat ID and QR code to the user on their active channel (Telegram, webchat, etc). |
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. |
39
37
 
40
38
  Source code is fully open: [github.com/keychat-io/keychat-openclaw](https://github.com/keychat-io/keychat-openclaw)
41
39
 
42
40
  ### Upgrade
43
41
 
44
- **Easiest way:** Just tell your agent "upgrade keychat" in any chat. The agent will handle it and reconnect automatically.
45
-
46
- Or manually:
47
-
48
- If you installed via **Option A**:
42
+ Tell your agent "upgrade keychat" in any chat, or manually:
49
43
 
50
44
  ```bash
51
45
  openclaw plugins install @keychat-io/keychat-openclaw@latest
52
46
  openclaw gateway restart
53
47
  ```
54
48
 
55
- If you installed via **Option B**:
49
+ ## Add Your Agent as a Keychat Contact
56
50
 
57
- ```bash
58
- curl -fsSL https://raw.githubusercontent.com/keychat-io/keychat-openclaw/main/scripts/install.sh | bash
59
- ```
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.):
60
52
 
61
- ### Connect
53
+ ```
54
+ 🔑 Keychat ID: npub1...
55
+ 📱 Add contact: https://www.keychat.io/u/?k=npub1...
56
+ 🖼️ QR code image
57
+ ```
62
58
 
63
- 1. Run `openclaw status` your agent's **npub** and **QR code** will be displayed
64
- 2. Open the [Keychat app](https://keychat.io) → tap **Add Contact** on the home page
65
- 3. Scan the QR code, or paste the npub
66
- 4. The agent will automatically accept the friend request and establish an encrypted session
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.
67
60
 
68
61
  ## Configuration
69
62
 
70
- All options go under `channels.keychat` in your OpenClaw config:
63
+ All options go under `channels.keychat` in your OpenClaw config (`~/.openclaw/openclaw.json`):
71
64
 
72
65
  | Option | Type | Default | Description |
73
66
  | ------------------ | -------- | ---------------------------- | --------------------------------------------------------- |
74
- | `enabled` | boolean | `false` | Enable/disable the Keychat channel |
67
+ | `enabled` | boolean | `true` | Enable/disable the Keychat channel |
75
68
  | `name` | string | — | Display name for this account |
76
69
  | `relays` | string[] | `["wss://relay.keychat.io"]` | Nostr relay WebSocket URLs |
77
- | `dmPolicy` | enum | `"pairing"` | Access policy: `pairing`, `allowlist`, `open`, `disabled` |
70
+ | `dmPolicy` | enum | `"open"` | Access policy: `pairing`, `allowlist`, `open`, `disabled` |
78
71
  | `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
72
  | `lightningAddress` | string | — | Lightning address for receiving payments |
83
73
  | `nwcUri` | string | — | Nostr Wallet Connect URI for wallet access |
84
- | `markdown` | object | — | Markdown formatting overrides |
85
74
 
86
75
  ### DM Policies
87
76
 
88
- - **`pairing`** (default): New contacts require owner approval via OpenClaw's pairing system
77
+ - **`open`**: Anyone can message the agent (default)
78
+ - **`pairing`**: New contacts require owner approval via OpenClaw
89
79
  - **`allowlist`**: Only pubkeys in `allowFrom` can communicate
90
- - **`open`**: Anyone can message the agent
91
80
  - **`disabled`**: No inbound messages accepted
92
81
 
93
82
  ## Lightning Wallet
94
83
 
95
- Keychat supports Lightning payments:
96
-
97
84
  ### Lightning Address (receive-only)
98
85
 
99
- Configure a Lightning address so your agent can generate invoices:
100
-
101
86
  ```json
102
- {
103
- "lightningAddress": "user@walletofsatoshi.com"
104
- }
87
+ { "lightningAddress": "user@walletofsatoshi.com" }
105
88
  ```
106
89
 
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
90
  ### Nostr Wallet Connect (NWC)
110
91
 
111
- For full wallet access (create invoices, check balance, verify payments), configure NWC:
92
+ For full wallet access (create invoices, check balance, verify payments):
112
93
 
113
94
  ```json
114
- {
115
- "nwcUri": "nostr+walletconnect://pubkey?relay=wss://...&secret=..."
116
- }
95
+ { "nwcUri": "nostr+walletconnect://pubkey?relay=wss://...&secret=..." }
117
96
  ```
118
97
 
119
98
  Generate an NWC connection string from your wallet app (Keychat, Alby Hub, Mutiny, Coinos, etc.).
120
99
 
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.
100
+ **Security note**: The agent can receive payments freely. Outbound payments require owner approval.
122
101
 
123
102
  ## Architecture
124
103
 
@@ -133,71 +112,32 @@ Generate an NWC connection string from your wallet app (Keychat, Alby Hub, Mutin
133
112
  └────────────────────┘
134
113
  ```
135
114
 
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
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
140
119
  - **Transport**: Nostr relays (kind:4 DMs + kind:1059 Gift Wrap for friend requests)
141
120
 
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
121
  ## Security
152
122
 
153
123
  - **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
124
+ - **Forward & Backward Secrecy**: Double Ratchet ensures compromising current keys reveals neither past nor future messages
156
125
  - **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
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
160
129
 
161
130
  ## Troubleshooting
162
131
 
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
132
+ - **Bridge not starting**: Check `ls ~/.openclaw/extensions/keychat-openclaw/bridge/target/release/keychat-openclaw`. 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
191
136
 
192
137
  ## Development
193
138
 
194
- ### Building
195
-
196
139
  ```bash
197
- # Build the Rust sidecar
198
140
  cd bridge && cargo build --release
199
-
200
- # Run tests
201
141
  cargo test
202
142
  ```
203
143
 
@@ -205,24 +145,23 @@ cargo test
205
145
 
206
146
  ```
207
147
  ├── src/
208
- │ ├── channel.ts # Main channel plugin (OpenClaw integration)
209
- │ ├── bridge-client.ts # TypeScript RPC client for the Rust sidecar
148
+ │ ├── channel.ts # Main channel plugin
149
+ │ ├── bridge-client.ts # RPC client for Rust sidecar
210
150
  │ ├── config-schema.ts # Zod config schema
211
151
  │ ├── keychain.ts # System keychain integration
212
- │ ├── lightning.ts # Lightning address (LNURL-pay) support
213
- │ ├── nwc.ts # Nostr Wallet Connect (NIP-47) client
152
+ │ ├── lightning.ts # LNURL-pay support
153
+ │ ├── nwc.ts # Nostr Wallet Connect (NIP-47)
214
154
  │ ├── media.ts # Blossom media encryption/upload
215
155
  │ ├── qrcode.ts # QR code generation
216
156
  │ ├── runtime.ts # Plugin runtime accessor
217
157
  │ └── 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
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
226
165
  ├── scripts/
227
166
  │ └── install.sh # One-line installer
228
167
  ├── index.ts # Plugin entry point
package/index.ts CHANGED
@@ -2,7 +2,87 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
3
3
  import { keychatPlugin, getAgentKeychatId, getAgentKeychatUrl, getAllAgentContacts } from "./src/channel.js";
4
4
  import { setKeychatRuntime } from "./src/runtime.js";
5
- import { existsSync } from "node:fs";
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-openclaw");
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
+ }
6
86
 
7
87
  const plugin = {
8
88
  id: "keychat",
@@ -14,6 +94,9 @@ const plugin = {
14
94
  register(api: OpenClawPluginApi) {
15
95
  console.log("[keychat] register() called");
16
96
  try {
97
+ // Auto-setup: download binary + init config on first load
98
+ ensureConfig();
99
+ ensureBinary().catch((err) => console.warn("[keychat] ensureBinary error:", err));
17
100
  setKeychatRuntime(api.runtime);
18
101
  console.log("[keychat] runtime set, registering channel...");
19
102
  api.registerChannel({ plugin: keychatPlugin });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keychat-io/keychat-openclaw",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Keychat — E2E encrypted chat + Lightning wallet for OpenClaw agents",
5
5
  "license": "AGPL-3.0",
6
6
  "repository": {
@@ -10,6 +10,13 @@ BINARY="$INSTALL_DIR/bridge/target/release/keychat-openclaw"
10
10
  echo "🔑 Installing Keychat"
11
11
  echo ""
12
12
 
13
+ # ── Clean up conflicting installs ──
14
+ NPM_DIR="${OPENCLAW_EXTENSIONS:-$HOME/.openclaw/extensions}/keychat-openclaw"
15
+ if [ -d "$NPM_DIR" ] && [ "$INSTALL_DIR" != "$NPM_DIR" ]; then
16
+ echo "🧹 Removing npm-installed copy ($NPM_DIR)..."
17
+ rm -rf "$NPM_DIR"
18
+ fi
19
+
13
20
  # ── Check OpenClaw ──
14
21
  if ! command -v openclaw &>/dev/null; then
15
22
  echo "❌ OpenClaw not found. Install it first: https://docs.openclaw.ai"
@@ -136,7 +143,8 @@ fi
136
143
  # ── Restart gateway ──
137
144
  echo ""
138
145
  echo "🔄 Restarting gateway..."
139
- openclaw gateway restart 2>&1 || true
146
+ openclaw gateway install 2>&1 || true
147
+ openclaw gateway start 2>&1 || true
140
148
 
141
149
  # ── Done ──
142
150
  echo ""
@@ -149,6 +157,6 @@ echo "To connect: open the Keychat app and scan the QR code."
149
157
  echo ""
150
158
  echo "View QR code in terminal:"
151
159
  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={}')\""
160
+ echo " # or: qrencode -t ANSIUTF8 \"\$(openclaw status 2>/dev/null | grep -o 'npub[a-z0-9]*' | head -1 | xargs -I{} echo 'https://www.keychat.io/u/?k={}')\""
153
161
  echo ""
154
162
  echo "Docs: https://github.com/$REPO"
@@ -4,7 +4,7 @@
4
4
  * Runs automatically after `npm install` / `openclaw plugins install`.
5
5
  * Uses native fetch/https — no child_process dependency.
6
6
  */
7
- import { existsSync, mkdirSync, chmodSync, writeFileSync } from "node:fs";
7
+ import { existsSync, mkdirSync, chmodSync, writeFileSync, readFileSync, rmSync } from "node:fs";
8
8
  import { join, dirname } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import https from "node:https";
@@ -14,11 +14,35 @@ const REPO = "keychat-io/keychat-openclaw";
14
14
  const BINARY_DIR = join(__dirname, "..", "bridge", "target", "release");
15
15
  const BINARY_PATH = join(BINARY_DIR, "keychat-openclaw");
16
16
 
17
- if (existsSync(BINARY_PATH)) {
18
- console.log("[keychat] Binary already exists, skipping download");
17
+ import { statSync } from "node:fs";
18
+
19
+ // Read expected version from package.json
20
+ const pkgPath = join(__dirname, "..", "package.json");
21
+ const pkgVersion = JSON.parse(readFileSync(pkgPath, "utf-8")).version;
22
+ const versionFile = join(BINARY_DIR, ".version");
23
+
24
+ const currentVersion = existsSync(versionFile)
25
+ ? readFileSync(versionFile, "utf-8").trim()
26
+ : null;
27
+
28
+ // Clean up conflicting script-installed copy (extensions/keychat vs extensions/keychat-openclaw)
29
+ const pluginDir = join(__dirname, "..");
30
+ const pluginDirName = pluginDir.split("/").pop();
31
+ const scriptInstallDir = join(pluginDir, "..", "keychat");
32
+ if (pluginDirName === "keychat-openclaw" && existsSync(scriptInstallDir)) {
33
+ console.log(`[keychat] Removing conflicting script-installed copy...`);
34
+ try { rmSync(scriptInstallDir, { recursive: true, force: true }); } catch {}
35
+ }
36
+
37
+ if (existsSync(BINARY_PATH) && currentVersion === pkgVersion) {
38
+ console.log(`[keychat] Binary already exists (v${pkgVersion}), skipping download`);
19
39
  process.exit(0);
20
40
  }
21
41
 
42
+ if (existsSync(BINARY_PATH)) {
43
+ console.log(`[keychat] Binary exists but version mismatch (${currentVersion || "unknown"} → ${pkgVersion}), re-downloading...`);
44
+ }
45
+
22
46
  const platform = process.platform; // darwin, linux
23
47
  const arch = process.arch; // arm64, x64
24
48
 
@@ -64,7 +88,8 @@ try {
64
88
  const buffer = await download(url);
65
89
  writeFileSync(BINARY_PATH, buffer);
66
90
  chmodSync(BINARY_PATH, 0o755);
67
- console.log("[keychat] Binary installed");
91
+ writeFileSync(versionFile, pkgVersion + "\n");
92
+ console.log(`[keychat] ✅ Binary installed (v${pkgVersion})`);
68
93
  } catch (err) {
69
94
  console.warn(`[keychat] Download failed: ${err.message}`);
70
95
  console.warn("[keychat] Build from source: cd bridge && cargo build --release");
@@ -72,7 +97,6 @@ try {
72
97
  }
73
98
 
74
99
  // Auto-initialize config if channels.keychat not set
75
- import { readFileSync } from "node:fs";
76
100
  import { homedir } from "node:os";
77
101
 
78
102
  const configPath = join(homedir(), ".openclaw", "openclaw.json");
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ # Keychat OpenClaw plugin setup
3
+ # Downloads the bridge binary and initializes config.
4
+ # Run after: openclaw plugins install @keychat-io/keychat-openclaw
5
+ set -euo pipefail
6
+
7
+ # --- Locate plugin directory ---
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ PLUGIN_DIR="$(dirname "$SCRIPT_DIR")"
10
+
11
+ # Also check common install locations
12
+ if [ ! -f "$PLUGIN_DIR/package.json" ]; then
13
+ for d in \
14
+ "$HOME/.openclaw/extensions/keychat-openclaw" \
15
+ "$HOME/.openclaw/plugins/@keychat-io/keychat-openclaw"; do
16
+ if [ -f "$d/package.json" ]; then
17
+ PLUGIN_DIR="$d"
18
+ break
19
+ fi
20
+ done
21
+ fi
22
+
23
+ BINARY_DIR="$PLUGIN_DIR/bridge/target/release"
24
+ BINARY_PATH="$BINARY_DIR/keychat-openclaw"
25
+
26
+ # --- Download binary ---
27
+ if [ -f "$BINARY_PATH" ]; then
28
+ echo "[keychat] ✅ Binary already exists: $BINARY_PATH"
29
+ else
30
+ REPO="keychat-io/keychat-openclaw"
31
+ OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
32
+ ARCH="$(uname -m)"
33
+
34
+ case "$OS-$ARCH" in
35
+ darwin-arm64) ARTIFACT="keychat-openclaw-darwin-arm64" ;;
36
+ darwin-x86_64) ARTIFACT="keychat-openclaw-darwin-x64" ;;
37
+ linux-x86_64) ARTIFACT="keychat-openclaw-linux-x64" ;;
38
+ linux-aarch64) ARTIFACT="keychat-openclaw-linux-arm64" ;;
39
+ *)
40
+ echo "[keychat] ❌ No pre-compiled binary for $OS-$ARCH"
41
+ echo "[keychat] Build from source: cd $PLUGIN_DIR/bridge && cargo build --release"
42
+ exit 1
43
+ ;;
44
+ esac
45
+
46
+ URL="https://github.com/$REPO/releases/latest/download/$ARTIFACT"
47
+ echo "[keychat] Downloading $ARTIFACT..."
48
+ mkdir -p "$BINARY_DIR"
49
+ curl -fsSL -o "$BINARY_PATH" "$URL"
50
+ chmod +x "$BINARY_PATH"
51
+ echo "[keychat] ✅ Binary installed: $BINARY_PATH"
52
+ fi
53
+
54
+ # --- Initialize config ---
55
+ CONFIG_PATH="$HOME/.openclaw/openclaw.json"
56
+
57
+ if [ ! -f "$CONFIG_PATH" ]; then
58
+ echo '{}' > "$CONFIG_PATH"
59
+ fi
60
+
61
+ # Check if channels.keychat already exists
62
+ if node -e "
63
+ const c = JSON.parse(require('fs').readFileSync('$CONFIG_PATH','utf-8'));
64
+ process.exit(c.channels?.keychat ? 0 : 1);
65
+ " 2>/dev/null; then
66
+ echo "[keychat] ✅ Config already has channels.keychat"
67
+ else
68
+ node -e "
69
+ const fs = require('fs');
70
+ const c = JSON.parse(fs.readFileSync('$CONFIG_PATH','utf-8'));
71
+ if (!c.channels) c.channels = {};
72
+ c.channels.keychat = { enabled: true, dmPolicy: 'open' };
73
+ fs.writeFileSync('$CONFIG_PATH', JSON.stringify(c, null, 2) + '\n');
74
+ "
75
+ echo "[keychat] ✅ Config initialized (channels.keychat.enabled = true, dmPolicy = open)"
76
+ fi
77
+
78
+ echo ""
79
+ echo "[keychat] Setup complete! Restart gateway to activate:"
80
+ echo " openclaw gateway install && openclaw gateway start"