Use the Railway one-click entry above to deploy the PrivateClaw relay quickly.
PrivateClaw is a lightweight, end-to-end encrypted private channel for OpenClaw. It lets a user leave a public bot surface, scan a one-time QR code, and continue the conversation inside a dedicated mobile app without giving the relay access to plaintext.
Sessions can stay one-to-one by default, or opt into a small group mode where multiple paired apps share the same encrypted OpenClaw conversation while keeping stable per-install display names inside the chat UI.
Join the community on Telegram: PrivateClaw Telegram group
Mobile beta access:
- iOS public beta (TestFlight): https://testflight.apple.com/join/XvgJ9c33
- Android closed alpha tester group: https://groups.google.com/g/gg-studio-ai-products
- Android closed alpha (Google Play): https://play.google.com/store/apps/details?id=gg.ai.privateclaw
For Android closed alpha, Google Play only grants access after the tester has joined the Google Group above.
The repository contains:
services/relay-server: the blind WebSocket relay source, published as@privateclaw/privateclaw-relay.packages/privateclaw-provider: the OpenClaw-facing provider runtime and plugin package published as@privateclaw/privateclaw.packages/privateclaw-protocol: the shared invite, envelope, and control-message types.apps/privateclaw_app: the Flutter mobile client.
flowchart LR
User["User"] --> Public["Telegram / Discord / QQ / ..."]
Public -->|/privateclaw| OpenClaw["OpenClaw + PrivateClaw provider"]
OpenClaw -->|create session + QR| Public
User -->|scan QR| App["PrivateClaw app"]
OpenClaw <-->|ciphertext only| Relay["PrivateClaw relay"]
App <-->|ciphertext only| Relay
- The provider connects to the relay at
/ws/provider. /privateclawcreates a relay session ID and a local 32-byte session key.- The provider returns a one-time
privateclaw://connect?payload=...QR invite. - The mobile app scans the QR code and connects to
/ws/app?sessionId=.... - The app and provider exchange encrypted envelopes using AES-256-GCM.
- The relay only routes ciphertext plus the metadata needed to deliver it.
- The provider forwards user messages into OpenClaw and encrypts responses back to the app.
In optional group mode, the same invite can be scanned by more than one app install. The provider assigns each participant a short stable nickname, broadcasts join/leave notices as gray system messages, keeps the shared OpenClaw conversation alive when one participant leaves, and broadcasts participant messages back to every connected app so the conversation feels like a lightweight encrypted group room.
Provider-generated invite announcements, local pairing output, and built-in PrivateClaw slash-command descriptions are emitted in bilingual Chinese + English text so the same session can be operated from either language context.
- AES-256-GCM for message envelopes.
- Session key stays local to the provider and the app.
sessionIdis bound as additional authenticated data.- Relay never receives plaintext message contents.
- Session renewal rotates the key without creating a new public invite.
sequenceDiagram
participant A as Participant A
participant P as PrivateClaw provider
participant B as Participant B
participant O as OpenClaw bridge
A->>P: client_hello
P-->>A: nickname + capabilities
B->>P: client_hello
P-->>A: "B joined" system notice
P-->>B: history + capabilities
A->>P: /mute-bot
P-->>A: muted notice + capabilities
P-->>B: muted notice + capabilities
B->>P: normal group message
P-->>A: participant broadcast
P-->>B: participant broadcast
Note over P,O: assistant bridge is skipped while muted
B->>P: /unmute-bot
P-->>A: unmuted notice + capabilities
P-->>B: unmuted notice + capabilities
A->>P: normal group message
P->>O: forward shared history + message
O-->>P: assistant reply
P-->>A: assistant message
P-->>B: assistant message
.
├── apps/privateclaw_app
├── packages/privateclaw-protocol
├── packages/privateclaw-provider
└── services/relay-server
PrivateClaw's public production relay is:
https://relay.privateclaw.us
The repo code now defaults to that relay, so production installs can use it without any extra relay configuration unless you want to override it.
openclaw plugins install @privateclaw/privateclaw@latest
openclaw plugins enable privateclawopenclaw plugins install is the correct production entrypoint here. Its built-in help explicitly says it installs a plugin from a path, archive, or npm spec, so @privateclaw/privateclaw@latest is a first-class npm install path rather than a local-only shortcut.
If you are using the default public relay at https://relay.privateclaw.us, the relayBaseUrl config step is optional and can be skipped. Only run openclaw config set plugins.entries.privateclaw.config.relayBaseUrl ... when you want to change the default relay for the whole plugin. For one-off invites, you can now override the relay per command instead of changing persistent config.
PrivateClaw is an OpenClaw plugin command provider, not a built-in chat transport. That means you do not configure it with openclaw channels add privateclaw. Instead:
- use
openclaw plugins install ...to install it, - use
openclaw plugins enable privateclawto enable it, - and configure it under
plugins.entries.privateclaw.config.
If npm is temporarily behind the GitHub repo and you need the newest GitHub checkout immediately, install the packed workspace archive instead:
TARBALL="$(npm pack --workspace @privateclaw/privateclaw | tail -n 1)"
openclaw plugins install "./${TARBALL}"
openclaw plugins enable privateclawAfter openclaw plugins install, openclaw plugins enable, or any openclaw config set plugins.entries.privateclaw.config... change, restart the running OpenClaw gateway/service before testing so it reloads the plugin and config. In practice, that means restarting the running openclaw start process or whichever service unit is hosting your gateway.
Add a normal OpenClaw transport such as Telegram, Discord, or QQ:
openclaw channels add --channel telegram --token <token>Then send /privateclaw from that chat surface and scan the returned QR code in the app.
If you want the session to behave like an encrypted group chat, use /privateclaw group instead. That keeps one OpenClaw conversation state for the session while allowing multiple app clients to join with distinct participant labels. Individual participants can leave and later rejoin the same invite until the session TTL expires, any connected participant can run /session-qr to re-share the current invite QR, and once less than 30 minutes remain the provider emits a reminder to run /renew-session. While the group is active, any participant can also use /mute-bot and /unmute-bot to pause or resume assistant replies without stopping participant-to-participant chat delivery.
If you want just one invite to use a different relay, pass the relay on the slash command itself. Both forms below work:
/privateclaw relay=https://your-relay.example.com
/privateclaw group --relay https://your-relay.example.com
The mobile app and web chat both warn before connecting when the QR/invite points at a non-default relay.
If you want to start a session without another chat app, use the provider CLI that the plugin adds to OpenClaw. The same public subcommands also exist on the standalone npm binary as privateclaw-provider <subcommand>; the examples below use the OpenClaw alias:
| Command | Purpose | Notes |
|---|---|---|
openclaw privateclaw pair |
Create a local PrivateClaw session and render the pairing QR in the terminal. | By default the command returns to the shell after printing while the provider keeps the session alive in a background daemon until expiry. Use --relay <url> to override the relay just for this session. |
openclaw privateclaw sessions |
List locally managed active sessions. | The output includes the total count plus each session's type, participants, state, expires, host, and optional label. host is one of plugin-service, pair-foreground, or pair-daemon. |
openclaw privateclaw sessions follow <sessionId> |
Follow the OpenClaw session log for one managed session. | This tails the session JSONL written by OpenClaw so you can watch the agent-side run in real time while keeping provider logs in the current terminal. |
openclaw privateclaw sessions qr <sessionId> |
Reprint the QR for a currently managed session. | The QR is rendered in the terminal by default. Add --open to also launch the local browser preview, or --notify to broadcast the same QR back to the session's currently connected participants as an ephemeral assistant message. |
openclaw privateclaw sessions kill <sessionId> |
Terminate a locally managed session. | On current hosts this closes just the selected session. If an already-running older foreground/background host does not support per-session shutdown yet, the command falls back to terminating that legacy host process. |
openclaw privateclaw sessions killall |
Terminate every background daemon-managed session. | This only targets pair-daemon sessions so it does not interrupt foreground hosts or plugin-service sessions. The standalone binary also exposes the same shortcut as privateclaw-provider killall. |
openclaw privateclaw kick <sessionId> <appId> |
Remove one participant from a group session. | This closes that app's relay connection and blocks the same appId from rejoining the current session. |
pair accepts these public flags:
| Flag | Effect |
|---|---|
--ttl-ms <ms> |
Override the session TTL. The default remains 24 hours. |
--label <label> |
Attach an optional relay-side label that also appears in session-management output. |
--relay <url> |
Override the relay base URL just for this command without changing plugin config. |
--group |
Allow multiple PrivateClaw app clients to join the same session. |
--print-only |
Print the invite URI and QR, then exit immediately. This also closes the session instead of leaving it alive. |
--open |
Open a local browser preview page for the generated QR. |
--foreground |
Keep the session in the current terminal until it ends or you press Ctrl+C. On supported runtimes, pressing Ctrl+D hands the live session off to a detached background daemon without invalidating the QR. |
--verbose |
Emit more detailed provider / bridge debug logs. For live diagnosis, combine it with --foreground so the extra logs stay visible in the current terminal. |
For example, to start a local group session and keep it in the foreground until you hand it off or stop it:
openclaw privateclaw pair --group --foreground
openclaw privateclaw pair --foreground --verbose
openclaw privateclaw pair --relay https://your-relay.example.com
openclaw privateclaw sessions follow <sessionId>
openclaw privateclaw sessions qr <sessionId> --notify
openclaw privateclaw sessions kill <sessionId>
openclaw privateclaw sessions killall
privateclaw-provider sessions qr <sessionId> --open
privateclaw-provider sessions follow <sessionId>
privateclaw-provider killall
privateclaw-provider pair --relay https://your-relay.example.com --foreground
privateclaw-provider pair --foreground --verboseBackground daemon sessions can outlive OpenClaw main-process restarts. Use openclaw privateclaw sessions or privateclaw-provider sessions to inspect them, sessions kill <sessionId> when you want to shut one down explicitly, and sessions killall (or standalone privateclaw-provider killall) when you want to clear every background daemon session at once.
When a user sends a voice attachment, PrivateClaw now tries to transcribe it on the provider side before the message enters the normal OpenClaw text flow.
The runtime order is:
- local
whisperCLI fromopenai-whisper, whenwhisperis available on the host - provider-side direct STT from the configured OpenClaw audio model or
PRIVATECLAW_STT_*overrides - the bridge
transcribeAudioAttachments(...)path as the final fallback
If one provider-side layer fails, PrivateClaw logs the fallback and continues to the next layer instead of failing the whole voice turn immediately. For live diagnosis, use openclaw privateclaw pair --foreground --verbose or privateclaw-provider pair --foreground --verbose.
If you want provider-side network STT from OpenClaw config, configure the default audio model, for example:
openclaw config set tools.media.audio.models '[{"baseUrl":"http://127.0.0.1:8090","model":"whisper-1","headers":{"Authorization":"Bearer local"}}]' --strict-json
openclaw config validateOptional local whisper overrides:
PRIVATECLAW_WHISPER_BINPRIVATECLAW_WHISPER_MODELPRIVATECLAW_WHISPER_LANGUAGEPRIVATECLAW_WHISPER_DEVICEPRIVATECLAW_WHISPER_MODEL_DIR
cd apps/privateclaw_app
flutter runThe mobile app's native Firebase files are intentionally local-only:
apps/privateclaw_app/android/app/google-services.jsonapps/privateclaw_app/ios/Runner/GoogleService-Info.plist
If those files are present on your machine, the app can exercise the full Firebase push/background-wake flow. If they are absent, the app still builds and runs, but push stays disabled until you add your own Firebase project configuration.
Then either scan the QR code returned by /privateclaw from your existing channel, or scan the QR code rendered by openclaw privateclaw pair.
After the session attaches, the app session panel also shows the current relay server so users can confirm which relay endpoint they are connected to.
If the scanned or pasted invite targets a non-default relay, the app now shows a confirmation warning before it connects.
If you are pairing on a simulator or desktop, you can also paste either the raw privateclaw://connect?... link or the full Invite URI: ... / 邀请链接 / Invite URI: ... line into the app.
npm install
cd apps/privateclaw_app && flutter pub get
cd ../..To enable the repo-managed Git hooks that block accidental commits/pushes of local-only Firebase, relay, and signing credentials, run npm run hooks:install once in your clone.
For local Docker development:
npm run docker:relayThat script auto-loads ignored local overrides from services/relay-server/.env when the file exists, which is the recommended place to keep your own FCM relay credentials for local push debugging.
For a direct Node.js run:
npm run dev:relay
npx @privateclaw/privateclaw-relay
privateclaw-relay --web
privateclaw-relay --public cloudflare
privateclaw-relay --public tailscaleThe published relay package exposes the privateclaw-relay binary, so you can boot a local relay without cloning the whole repository. Adding --public cloudflare starts a temporary Cloudflare quick tunnel, and --public tailscale enables Tailscale Funnel for the relay port. If the default local port 8787 is already in use, the CLI automatically retries the next free port and prints the final listening URL.
Adding --web also serves the bundled PrivateClaw website from the same process, with the homepage on /, the web chat on /chat/, and the relay WebSocket endpoints still isolated under /ws/*.
If tailscale or cloudflared is missing, the CLI prints OS-aware install commands and, in an interactive terminal on supported setups, can offer to install/configure the dependency before retrying the public tunnel.
After a public relay URL is ready, the CLI also prints the exact OpenClaw + PrivateClaw provider setup commands. If openclaw is available locally, it can offer to run the local provider install-or-update/enable/config flow, restart the gateway, verify that privateclaw is registered, and then optionally start a new group pairing. With --web, it can also open the bundled web chat with that fresh invite already embedded in the URL.
openclaw plugins install --link ./packages/privateclaw-provider
openclaw plugins enable privateclaw
openclaw config set plugins.entries.privateclaw.config.relayBaseUrl ws://127.0.0.1:8787Because this flow changes both the installed plugin and the relay target, restart the running OpenClaw gateway/service before testing.
The provider package also exports resolveRelayEndpoints(...) if you want to derive provider and app socket URLs from one base URL:
import { resolveRelayEndpoints } from "@privateclaw/privateclaw";
const relay = resolveRelayEndpoints("ws://127.0.0.1:8787");The relay is intentionally small. It can run with only Node.js, or as a Docker container with optional Redis-backed session persistence, distributed frame buffering, and multi-instance coordination.
If you use your own relay instead of https://relay.privateclaw.us, point OpenClaw at it with openclaw config set plugins.entries.privateclaw.config.relayBaseUrl <your-relay-base-url> and then restart the running OpenClaw gateway/service.
docker compose up --build relayTo enable relay-side wake push locally, copy services/relay-server/.env.example to the ignored services/relay-server/.env and fill in your own FCM credentials first. Public clones can run the relay without that file, but wake notifications stay disabled until they provide their own config.
With the optional Redis profile:
PRIVATECLAW_REDIS_URL=redis://redis:6379 docker compose --profile redis up --buildWhen PRIVATECLAW_REDIS_URL / REDIS_URL is set, the relay persists session metadata in Redis, keeps buffered encrypted frames there, and lets multiple relay instances share sessions behind a load balancer. Without Redis, relay restarts still drop the in-memory session registry.
This repository now includes Railway-ready root configs for both relay flavors:
railway.toml+Dockerfile.multiarch: standalone relay containerrailway.redis.toml+Dockerfile.multiarch.redis: relay container that boots Redis inside the same container
The relay answers both /healthz and /api/health, and it honors Railway's injected PORT automatically when PRIVATECLAW_RELAY_PORT is not set.
The Railway images intentionally do not bake PRIVATECLAW_RELAY_PORT into the container, so the runtime can bind the platform-provided PORT correctly.
Standalone Railway deploy:
- Create a Railway service from this repository.
- Keep the default root
railway.toml. - Deploy.
If you want relay restart recovery or multiple relay replicas on Railway, attach a shared Redis service and set PRIVATECLAW_REDIS_URL or REDIS_URL in the relay service variables.
Embedded-Redis Railway deploy:
- Replace
railway.tomlwithrailway.redis.toml, or set Railway's Dockerfile path toDockerfile.multiarch.redis. - Deploy.
The embedded-Redis image starts Redis on 127.0.0.1:6379 inside the relay container and wires PRIVATECLAW_REDIS_URL automatically. It is convenient for a single relay replica, but it does not provide cross-instance HA because each container gets its own private Redis.
If your Railway relay service is pinned to the railway-relay branch, you can promote a committed relay change without touching your current worktree:
npm run relay:promoteThat command checks out the chosen source ref in a temporary git worktree, then force-syncs railway-relay to that exact commit on both origin and upstream.
To sync railway-relay to a specific committed ref instead of HEAD:
npm run relay:promote -- <commit>Set the Railway relay service source branch to railway-relay, and keep regular app, site, and provider work on main.
This repository includes .github/workflows/relay-image.yml, which builds and publishes a multi-architecture relay image to GHCR on pushes to main, version tags, and manual runs.
Example:
docker run --rm \
-p 8787:8787 \
-e PRIVATECLAW_RELAY_HOST=0.0.0.0 \
ghcr.io/topcheer/privateclaw-relay:mainThe relay is also packaged for npm as @privateclaw/privateclaw-relay.
npm install -g @privateclaw/privateclaw-relay
privateclaw-relay
privateclaw-relay --public cloudflare
privateclaw-relay --public tailscale--public cloudflare relies on a local cloudflared install and creates a temporary quick tunnel. --public tailscale relies on a local tailscale install plus Funnel being enabled for your tailnet. If 8787 is already occupied during local startup, privateclaw-relay automatically retries the next free port.
When either tunnel CLI is missing, privateclaw-relay now prints OS-aware install guidance and can offer an interactive install flow on supported platforms.
The relay reads the following variables directly from the process environment:
| Variable | Default | Purpose |
|---|---|---|
PRIVATECLAW_RELAY_HOST |
127.0.0.1 |
Host interface to bind |
PRIVATECLAW_RELAY_PORT |
8787 |
Relay HTTP/WebSocket port; falls back to Railway PORT when unset |
PRIVATECLAW_SESSION_TTL_MS |
900000 |
Session lifetime in milliseconds |
PRIVATECLAW_FRAME_CACHE_SIZE |
25 |
Buffered ciphertext frames per direction |
PRIVATECLAW_RELAY_INSTANCE_ID |
RAILWAY_REPLICA_ID or auto-generated |
Optional stable node ID for multi-instance relay logging and handoff coordination |
PRIVATECLAW_REDIS_URL |
unset | Optional shared Redis URL for session persistence, distributed frame buffering, and multi-instance relay coordination |
REDIS_URL |
unset | Fallback Redis URL alias used when PRIVATECLAW_REDIS_URL is unset |
The relay exposes both /healthz and /api/health for container and platform health checks.
@privateclaw/privateclaw is the published provider/runtime package. It supports:
- OpenClaw plugin registration through
api.registerCommand(...) - QR invite generation for
/privateclaw,/privateclaw group, andopenclaw privateclaw pair --group - bilingual invite / CLI / command output for built-in PrivateClaw text
- OpenClaw agent, webhook, echo, and OpenAI-compatible bridge modes
- reconnect, heartbeat, session renewal, 30-minute renewal reminders, dynamic slash-command sync, valid derived bridge session IDs for nickname generation, and optional multi-app group sessions with join/leave notices plus
/mute-bot//unmute-bot
See packages/privateclaw-provider/README.md for package-level usage examples.
The Flutter app supports:
- QR scanning and manual invite paste, including full pasted
Invite URI: ...announcement lines - encrypted chat over the relay
- stable per-install app identity with provider-assigned participant nicknames
- markdown rendering, best-effort Mermaid support, and media/file rendering
- reconnect, session renewal, 30-minute renewal reminders, slash-command sync, localized UI chrome, and optional group chat rendering for join/leave notices plus bot mute/unmute controls
See apps/privateclaw_app/README.md for app-specific commands.
npm run build
npm test
npm run dev:relay
npm run demo:providerFlutter app:
cd apps/privateclaw_app
flutter test
flutter build apk --debug
flutter build ios --simulator- Clone the repository.
- Install Node.js dependencies and Flutter dependencies.
- Run
npm run buildandnpm testbefore opening a PR. - If you change relay packaging, validate with
docker compose build relay.
- npm provider package:
@privateclaw/privateclaw - npm protocol package:
@privateclaw/protocol - relay npm package:
@privateclaw/privateclaw-relay - relay container image:
ghcr.io/topcheer/privateclaw-relay
Maintainership note: @privateclaw/privateclaw pulls @privateclaw/protocol during plugin installation, and the relay package ships alongside the provider on version tags, so the usual release order is protocol -> relay -> provider:
npm run publish:npm:dry-run
npm run publish:npmRepository-level shortcuts:
npm run store:version
npm run store:version:shell
npm run store:check
npm run ios:testflight
npm run ios:testflight:upload
npm run ios:testflight:external
npm run ios:release
npm run ios:release:upload
npm run android:internal
npm run android:internal:upload
npm run android:closed
npm run android:closed:promoteSupporting metadata-only lanes:
npm run ios:metadata
npm run android:metadataThese commands assume your App Store Connect and Play Console credentials are exported as described in apps/privateclaw_app/fastlane.env.example.
Run npm run store:check first to confirm that the expected credential environment variables, referenced key files, and existing IPA / AAB artifacts are all visible from your current shell before attempting an upload.
Keep any real secret exports in an untracked shell file, .env.local, or your preferred secret manager. A generic example looks like this:
# Example only. Keep the real values outside Git.
export PRIVATECLAW_APP_STORE_CONNECT_KEY_ID=ABC123XYZ
export PRIVATECLAW_APP_STORE_CONNECT_ISSUER_ID=00000000-0000-0000-0000-000000000000
export PRIVATECLAW_APP_STORE_CONNECT_KEY_FILE="$HOME/secrets/AuthKey_ABC123XYZ.p8"
export PRIVATECLAW_PLAY_STORE_JSON_KEY="$HOME/secrets/play-store-service-account.json"
export PRIVATECLAW_ANDROID_KEYSTORE_PATH="$HOME/secrets/upload.jks"
export PRIVATECLAW_ANDROID_KEYSTORE_PASSWORD=...
export PRIVATECLAW_ANDROID_KEY_ALIAS=upload
export PRIVATECLAW_ANDROID_KEY_PASSWORD=...
npm run store:check
eval "$(npm run -s store:version:shell)"
npm run ios:testflight
npm run android:closedIf you prefer local wrapper scripts around these commands, keep them in a gitignored location rather than documenting personal paths or machine-specific setup in the repository README.
The *:upload variants skip the rebuild step and upload the existing apps/privateclaw_app/builds/ios/PrivateClaw.ipa or apps/privateclaw_app/build/app/outputs/bundle/release/app-release.aab directly, which is useful for retrying failed store submissions quickly.
For iOS specifically, ios:release:upload submits the already-uploaded App Store Connect build identified by PRIVATECLAW_BUILD_NAME / PRIVATECLAW_BUILD_NUMBER for review instead of re-uploading the IPA. Use ios:release when you need to build and upload a fresh binary first.
The TestFlight external promote step defaults to the external tester group ext. Override PRIVATECLAW_TESTFLIGHT_EXTERNAL_GROUPS with a comma-separated list if you need a different target group set. Set PRIVATECLAW_TESTFLIGHT_NOTIFY_EXTERNAL_TESTERS=true if you want the promote step to notify testers immediately, and optionally set PRIVATECLAW_TESTFLIGHT_CHANGELOG to attach beta release notes during the external promotion.
Versioning rules:
versionName/ iOS marketing version stays on the semanticmajor.minor.patchvalue inapps/privateclaw_app/pubspec.yaml.buildNumber/ AndroidversionCodeauto-increments for each fresh store build as the current UTC seconds offset from2024-01-01T00:00:00Z, clamped above the build suffix stored inpubspec.yaml.- If you want iOS and Android to share the exact same auto-generated build number in one shell session, run
eval "$(npm run -s store:version:shell)"once before kicking off both upload commands. - Optional overrides: set
PRIVATECLAW_BUILD_NAMEand/orPRIVATECLAW_BUILD_NUMBERif you need to force a specific version for a one-off release.
Important Play Console note: for a brand-new Android app, Google requires the first binary upload to be completed manually in Play Console before automated uploads to the internal track can take over.
If the Play API returns Package not found: gg.ai.privateclaw, finish the first manual Play Console upload for that package and make sure the service account behind your Play JSON key has access to the app.
If the Play API returns The apk has permissions that require a privacy policy set for the app, add a public HTTPS privacy policy URL in Play Console before retrying the upload. A project policy document is included at PRIVACY.md.
If the Play app itself is still in draft state, upload with PRIVATECLAW_PLAY_RELEASE_STATUS=draft so the internal release is created as a draft as well.
Google Play closed testing uses the legacy alpha track name by default in the API. Override the target with PRIVATECLAW_PLAY_CLOSED_TRACK if needed, or override the source track from internal with PRIVATECLAW_PLAY_PROMOTE_FROM_TRACK.
The provider publish flow is available from the repository root:
npm run publish:provider:dry-run
npm run publish:providerThe OPENCLAW_* documents in the repository are preserved research and implementation notes from the original integration work. They are useful background material, but the current source of truth is:
- this
README.md README.zh-CN.md- the current source code in
packages/,services/, andapps/