@partme.ai/openclaw-web-stomp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PartMe-AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ <div align="center">
2
+
3
+ # OpenClaw Web STOMP
4
+
5
+ **OpenClaw plugin — STOMP 1.x over WebSocket (`stomp-ws`) with topic bindings and `session.dmScope` isolation**
6
+
7
+ ![npm](https://img.shields.io/badge/npm-@partme.ai%2Fopenclaw__web__stomp-blue)
8
+ ![Node](https://img.shields.io/badge/Node.js-22+-green)
9
+ ![License](https://img.shields.io/badge/License-MIT-green)
10
+
11
+ </div>
12
+
13
+ [English](./README.md) | [简体中文](./README_CN.md)
14
+
15
+ ## Introduction
16
+
17
+ `@partme.ai/openclaw-web-stomp` is an [OpenClaw](https://github.com/openclaw/openclaw) **channel plugin** that exposes **STOMP over WebSocket** and bridges browser or Spring-style clients to agents. It follows the official channel pattern: [`defineChannelPluginEntry`](https://docs.openclaw.ai/plugins/sdk-entrypoints#definechannelpluginentry) and `ChannelPlugin` (not `definePluginEntry`).
18
+
19
+ Behavior is aligned with **`openclaw-mqtt`**, **`openclaw-web-mqtt`**, and **`openclaw-stomp` (TCP)**:
20
+
21
+ - **`subscribeTopics`** — inbound **SEND** (and optional **SUBSCRIBE**) allowlist using `*` / `#` patterns on full STOMP destinations.
22
+ - **`topicBindings`** — explicit `topicPattern → agentId` (+ optional `accountId`, `replyTopic` as reply destination).
23
+ - **Standard fallback** — destinations matching `agent.<id>` under `/queue/` (same idea as TCP STOMP plugin).
24
+ - **Session keys** — only from OpenClaw global **`session.dmScope`** via shared `buildSessionKeyFromDmScope` (no channel-local duplicate scope config).
25
+
26
+ ## Capabilities
27
+
28
+ - **Gateway lifecycle** — server starts in `gateway.startAccount`, stops on abort (same model as `openclaw-web-mqtt`).
29
+ - **Status HTTP** — `GET /stomp-ws/status` (`auth: plugin`) — connections, subscription stats, ACK stats, redacted config snapshot.
30
+ - **Enterprise-style controls** — max connections, max frame / payload size, optional TLS (HTTPS server + WS upgrade), per-user `publishAllow` / `subscribeAllow` / `aclRules` (RabbitMQ-style topic ACL inspiration; see also [Web MQTT](https://www.rabbitmq.com/docs/web-mqtt) operational patterns).
31
+ - **`openclaw.setupEntry`** — `dist/setup-entry.js` for setup-only loads ([SDK setup](https://docs.openclaw.ai/plugins/sdk-setup)).
32
+
33
+ ## Message flow
34
+
35
+ 1. Client **CONNECT** (optional `login` / `passcode` per `channels["stomp-ws"].auth`).
36
+ 2. Client **SUBSCRIBE** to `/topic/session.<sessionKey>` (or your policy-approved destinations).
37
+ 3. Client **SEND** to a destination allowed by `subscribeTopics`.
38
+ 4. Plugin resolves **agent** via `topicBindings` first, then standard agent destination fallback.
39
+ 5. OpenClaw **`dispatchReplyFromConfig`** runs; replies publish to `replyTopic` or `/topic/session.<sessionKey>` derived from `dmScope`.
40
+
41
+ ## Quick start
42
+
43
+ ### Prerequisites
44
+
45
+ - OpenClaw `>= 2026.4.0`
46
+ - Node.js `22+`
47
+
48
+ ### Install
49
+
50
+ ```bash
51
+ openclaw plugins install @partme.ai/openclaw-web-stomp
52
+ ```
53
+
54
+ ### Minimal `openclaw.json`
55
+
56
+ ```json
57
+ {
58
+ "session": {
59
+ "dmScope": "per-channel-peer"
60
+ },
61
+ "channels": {
62
+ "stomp-ws": {
63
+ "wsPort": 15674,
64
+ "path": "/ws",
65
+ "subscribeTopics": ["/queue/devices/+/in", "/queue/agent.*"],
66
+ "topicBindings": [
67
+ {
68
+ "topicPattern": "/queue/devices/*/in",
69
+ "agentId": "iot-agent",
70
+ "accountId": "default",
71
+ "replyTopic": "/topic/devices/reply"
72
+ }
73
+ ],
74
+ "auth": {
75
+ "required": false,
76
+ "allowAnonymous": true
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ **Migration from legacy `channels.stomp`:** use **`channels["stomp-ws"]`** and channel id **`stomp-ws`**. Legacy keys under `channels.stomp` are merged for convenience but **`stomp-ws` overrides** on conflict.
84
+
85
+ ## Session and `dmScope`
86
+
87
+ Session isolation uses **only** the OpenClaw root key **`session.dmScope`** (same as `mqtt`, `mqtt-ws`, `stomp-tcp`). Allowed values: `main`, `per-peer`, `per-channel-peer`, `per-account-channel-peer`.
88
+
89
+ - The plugin **does not** read a channel-local `session.dmScope` under `channels["stomp-ws"]` for isolation (if present elsewhere, it is ignored for this purpose).
90
+ - Inbound `from` is the STOMP **peer id** (headers `x-peer-id` / `peer-id` / `sender`, else login / `client-id` / connection id).
91
+ - Replies go to `topicBindings[].replyTopic` when set, otherwise **`/topic/session.<sessionKey>`** where `sessionKey` is from `buildSessionKeyFromDmScope` with `channel: "stomp-ws"`.
92
+
93
+ Match **`mqtt-ws` / `stomp-tcp`** docs for how each `dmScope` affects the session key string.
94
+
95
+ ## Security
96
+
97
+ - **Production:** set `auth.required: true`, disable `allowAnonymous`, and use `auth.users` (plain `password` in config today — prefer env-backed secret injection or a reverse proxy for credential handling).
98
+ - **ACL:** optional `publishAllow`, `subscribeAllow`, and `aclRules` per user (same semantics as `openclaw-web-mqtt`).
99
+ - **TLS:** `tls.enabled` with `certFile` / `keyFile` starts HTTPS and attaches the WebSocket server; alternatively terminate TLS at your edge and proxy to plain `ws` internally.
100
+ - **Limits:** tune `maxConnections`, `limits.maxPayloadBytes`, `maxFrameSize`, and `limits.maxSubscriptionsPerClient` to match broker-style hardening (see [RabbitMQ Web MQTT](https://www.rabbitmq.com/docs/web-mqtt) for operational parallels).
101
+
102
+ ## Troubleshooting
103
+
104
+ | Symptom | Check |
105
+ | --- | --- |
106
+ | CONNECT fails with `Authentication failed` | `auth.users` / `defaultUser`+`defaultPass`, or set `allowAnonymous: true` for dev. |
107
+ | SEND accepted but no agent reply | Gateway running, agent route for `stomp-ws`, `topicBindings` / fallback agent id, and client **SUBSCRIBE** to the actual reply destination (often `/topic/session.<sessionKey>`). |
108
+ | SUBSCRIBE ignored | If `subscribeTopics` is non-empty, destination must match a pattern; check `maxSubscriptionsPerClient`. |
109
+ | `test:client` timeout | Wrong `STOMP_SUBSCRIBE_DESTINATION` for current `session.dmScope`; inspect `GET /stomp-ws/status` and logs. |
110
+
111
+ ## Testing
112
+
113
+ ```bash
114
+ pnpm test
115
+ pnpm run test:client
116
+ ```
117
+
118
+ Test client env vars: `STOMP_WS_URL`, `STOMP_LOGIN`, `STOMP_PASSCODE`, `STOMP_SEND_DESTINATION`, `STOMP_SUBSCRIBE_DESTINATION`, `STOMP_PAYLOAD`, `STOMP_TIMEOUT_MS`.
119
+
120
+ ## GitHub Actions
121
+
122
+ | Workflow | Trigger | Purpose |
123
+ | --- | --- | --- |
124
+ | [`.github/workflows/ci.yml`](./.github/workflows/ci.yml) | Push / PR `main` / `master` | pnpm install, typecheck, build, test, artifact `dist/` |
125
+ | [`.github/workflows/release.yml`](./.github/workflows/release.yml) | Tag `v*` / `workflow_dispatch` | Publish npm + GitHub Packages, GitHub Release |
126
+
127
+ ## Publishing
128
+
129
+ - Package: `@partme.ai/openclaw-web-stomp`
130
+ - Secret: `NPM_TOKEN`
131
+ - Details: [`RELEASING.md`](./RELEASING.md)
132
+
133
+ ## Project layout
134
+
135
+ ```text
136
+ src/
137
+ index.ts # defineChannelPluginEntry + registerFull
138
+ setup-entry.ts # defineSetupPluginEntry
139
+ channel.ts # ChannelPlugin + gateway.startAccount
140
+ stomp-server.ts # WS + STOMP frame handling
141
+ stomp-config.ts # resolve channels["stomp-ws"]
142
+ inbound.ts # dispatchReplyFromConfig + dmScope session key
143
+ dm-scope.ts # same logic as stomp-tcp / web-mqtt
144
+ route-inbound.ts # topicBindings + fallback
145
+ topic-router.ts # * / # matching
146
+ acl.ts # optional per-user destination ACL
147
+ outbound.ts # publish to /topic/session.<key>
148
+ ```
149
+
150
+ ## References
151
+
152
+ - [Building plugins](https://docs.openclaw.ai/plugins/building-plugins)
153
+ - [Channel plugins](https://docs.openclaw.ai/plugins/sdk-channel-plugins)
154
+ - [SDK entrypoints](https://docs.openclaw.ai/plugins/sdk-entrypoints)
155
+ - [Manifest](https://docs.openclaw.ai/plugins/manifest)
package/README_CN.md ADDED
@@ -0,0 +1,137 @@
1
+ <div align="center">
2
+
3
+ # OpenClaw Web STOMP
4
+
5
+ **OpenClaw 渠道插件 — WebSocket 上的 STOMP(`stomp-ws`),支持 topic 绑定与 `session.dmScope` 会话隔离**
6
+
7
+ ![npm](https://img.shields.io/badge/npm-@partme.ai%2Fopenclaw__web__stomp-blue)
8
+ ![Node](https://img.shields.io/badge/Node.js-22+-green)
9
+ ![License](https://img.shields.io/badge/License-MIT-green)
10
+
11
+ </div>
12
+
13
+ [English](./README.md) | [简体中文](./README_CN.md)
14
+
15
+ ## 简介
16
+
17
+ `@partme.ai/openclaw-web-stomp` 为 [OpenClaw](https://github.com/openclaw/openclaw) 提供 **STOMP over WebSocket** 渠道,便于浏览器、Spring 等客户端接入 Agent。实现上使用官方推荐的 [`defineChannelPluginEntry`](https://docs.openclaw.ai/plugins/sdk-entrypoints#definechannelpluginentry) 与 `ChannelPlugin`(**不要**用仅适用于非渠道的 `definePluginEntry`)。
18
+
19
+ 路由与 **`openclaw-mqtt` / `openclaw-web-mqtt` / `openclaw-stomp`(TCP)** 保持一致:
20
+
21
+ - **`subscribeTopics`**:对 **SEND** 的 destination 做白名单(`SUBSCRIBE` 在非空时同样校验);支持 `*`、`#`。
22
+ - **`topicBindings`**:`topicPattern` → `agentId`,可选 `accountId`、`replyTopic`(回复 STOMP destination)。
23
+ - **标准回退**:与 TCP 版相同的 `agent.<id>` destination 解析。
24
+ - **会话键**:仅使用 OpenClaw 全局 **`session.dmScope`** 与 `buildSessionKeyFromDmScope`,**不**维护渠道私有隔离配置。
25
+
26
+ ## 能力概览
27
+
28
+ - **网关生命周期**:在 `gateway.startAccount` 内启动 HTTP(S)+WS,abort 时停止(与 `openclaw-web-mqtt` 一致)。
29
+ - **状态接口**:`GET /stomp-ws/status`(`auth: plugin`)返回连接、订阅、ACK 统计与脱敏配置。
30
+ - **企业向控制**:连接数、帧/载荷上限、可选 TLS、用户级 `publishAllow` / `subscribeAllow` / `aclRules`(思路参考 RabbitMQ [Web MQTT](https://www.rabbitmq.com/docs/web-mqtt) 的运维与安全实践)。
31
+ - **setup 入口**:`package.json` → `openclaw.setupEntry` → `dist/setup-entry.js`(见 [SDK setup](https://docs.openclaw.ai/plugins/sdk-setup))。
32
+
33
+ ## 消息流
34
+
35
+ 1. 客户端 **CONNECT**(按 `auth` 校验 `login` / `passcode`)。
36
+ 2. **SUBSCRIBE** 到 `/topic/session.<sessionKey>` 等允许的 destination。
37
+ 3. **SEND** 到通过 `subscribeTopics` 的 destination。
38
+ 4. 插件按 **`topicBindings` 优先**,否则标准 agent destination 回退解析 **agent**。
39
+ 5. 调用 OpenClaw **`dispatchReplyFromConfig`**;回复发到绑定 **`replyTopic`** 或按 `dmScope` 生成的 **`/topic/session.<sessionKey>`**。
40
+
41
+ ## 快速开始
42
+
43
+ ### 环境
44
+
45
+ - OpenClaw `>= 2026.4.0`
46
+ - Node.js `22+`
47
+
48
+ ### 安装
49
+
50
+ ```bash
51
+ openclaw plugins install @partme.ai/openclaw-web-stomp
52
+ ```
53
+
54
+ ### 最小配置 `openclaw.json`
55
+
56
+ ```json
57
+ {
58
+ "session": {
59
+ "dmScope": "per-channel-peer"
60
+ },
61
+ "channels": {
62
+ "stomp-ws": {
63
+ "wsPort": 15674,
64
+ "path": "/ws",
65
+ "subscribeTopics": ["/queue/devices/+/in"],
66
+ "topicBindings": [
67
+ {
68
+ "topicPattern": "/queue/devices/*/in",
69
+ "agentId": "iot-agent",
70
+ "replyTopic": "/topic/devices/reply"
71
+ }
72
+ ],
73
+ "auth": {
74
+ "required": false,
75
+ "allowAnonymous": true
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ **从旧版迁移:** 旧渠道 id `stomp` 与 `channels.stomp` 请改为 **`stomp-ws`** 与 **`channels["stomp-ws"]`**。为兼容过渡期,解析配置时会合并 `channels.stomp`,但 **`stomp-ws` 字段优先**。
83
+
84
+ ## 会话与 `dmScope`
85
+
86
+ 会话隔离**仅**依赖 OpenClaw 根配置 **`session.dmScope`**(与 `mqtt`、`mqtt-ws`、`stomp-tcp` 一致)。合法取值:`main`、`per-peer`、`per-channel-peer`、`per-account-channel-peer`。
87
+
88
+ - 本插件**不会**使用 `channels["stomp-ws"]` 下的私有 `session.dmScope` 做隔离。
89
+ - 入站 `from` 使用 STOMP **peerId**(帧头 `x-peer-id` / `peer-id` / `sender`,否则为 login / `client-id` / 连接 id)。
90
+ - 回复优先发往 `topicBindings[].replyTopic`;否则为 **`/topic/session.<sessionKey>`**,其中 `sessionKey` 由 `buildSessionKeyFromDmScope`(`channel: stomp-ws`)生成。
91
+
92
+ 各 `dmScope` 档下会话键形态请对照 **`mqtt-ws` / `stomp-tcp`** 文档说明。
93
+
94
+ ## 安全
95
+
96
+ - **生产环境**:建议 `auth.required: true`、关闭 `allowAnonymous`,使用 `auth.users`;当前实现为配置内明文 `password` 比对,更敏感场景请用环境变量注入或前置代理鉴权。
97
+ - **ACL**:可选 `publishAllow`、`subscribeAllow`、`aclRules`(与 `openclaw-web-mqtt` 同类)。
98
+ - **TLS**:`tls.enabled` 且配置证书时由本插件起 HTTPS 并挂载 WS;也可在边缘终止 TLS 后反代到内网 `ws`。
99
+ - **限额**:按需调整 `maxConnections`、`limits.maxPayloadBytes`、`maxFrameSize`、`limits.maxSubscriptionsPerClient`(运维思路可参考 [RabbitMQ Web MQTT](https://www.rabbitmq.com/docs/web-mqtt))。
100
+
101
+ ## 排障
102
+
103
+ | 现象 | 排查 |
104
+ | --- | --- |
105
+ | CONNECT 报认证失败 | 检查 `auth.users` 或 `defaultUser`/`defaultPass`;开发环境可临时 `allowAnonymous: true`。 |
106
+ | SEND 有反应但无 Agent 回复 | 确认 Gateway 已起、路由包含 `stomp-ws`、`topicBindings`/默认 agent;客户端是否 **SUBSCRIBE** 了真实回复 destination(常为 `/topic/session.<sessionKey>`)。 |
107
+ | SUBSCRIBE 无效 | `subscribeTopics` 非空时 destination 必须匹配其一;是否触达 `maxSubscriptionsPerClient`。 |
108
+ | `test:client` 超时 | `STOMP_SUBSCRIBE_DESTINATION` 是否与当前 `session.dmScope` 下 session 一致;查看 `GET /stomp-ws/status` 与日志。 |
109
+
110
+ ## 测试
111
+
112
+ ```bash
113
+ pnpm test
114
+ pnpm run test:client
115
+ ```
116
+
117
+ 环境变量见英文 README「Testing」一节。
118
+
119
+ ## GitHub Actions 与发版
120
+
121
+ | 工作流 | 触发 | 说明 |
122
+ | --- | --- | --- |
123
+ | [`.github/workflows/ci.yml`](./.github/workflows/ci.yml) | push / PR | 安装、类型检查、构建、测试、上传 `dist` |
124
+ | [`.github/workflows/release.yml`](./.github/workflows/release.yml) | 标签 `v*` | 发布 npm 与 GitHub Packages、创建 Release |
125
+
126
+ 发版说明:[`RELEASING.md`](./RELEASING.md),需配置 Secret **`NPM_TOKEN`**。
127
+
128
+ ## 目录结构
129
+
130
+ 与英文 README「Project layout」一致:`index.ts`、`setup-entry.ts`、`channel.ts`、`stomp-server.ts`、`stomp-config.ts`、`inbound.ts`、`dm-scope.ts`、`route-inbound.ts`、`topic-router.ts`、`acl.ts`、`outbound.ts`。
131
+
132
+ ## 参考链接
133
+
134
+ - [Building plugins](https://docs.openclaw.ai/plugins/building-plugins)
135
+ - [Channel plugins](https://docs.openclaw.ai/plugins/sdk-channel-plugins)
136
+ - [SDK entrypoints](https://docs.openclaw.ai/plugins/sdk-entrypoints)
137
+ - [Manifest](https://docs.openclaw.ai/plugins/manifest)
package/RELEASING.md ADDED
@@ -0,0 +1,77 @@
1
+ # Releasing `@partme.ai/openclaw-web-stomp`
2
+
3
+ ## Prerequisites
4
+
5
+ - npm package name: `@partme.ai/openclaw-web-stomp`
6
+ - repository secret: `NPM_TOKEN`
7
+ - Node.js 22+ (see `package.json` → `engines`)
8
+
9
+ ## CI (continuous integration)
10
+
11
+ Workflow: `.github/workflows/ci.yml`
12
+
13
+ | Trigger | Behavior |
14
+ |---------|----------|
15
+ | **Push** to `main` or `master` | `pnpm install --frozen-lockfile` → typecheck → build → test → upload **`dist/`** artifact |
16
+ | **Pull request** targeting `main` or `master` | Same as push |
17
+
18
+ ## Version tag rule (required for Releases & Packages)
19
+
20
+ GitHub **Releases** and **Packages** are created only when you push a **git tag**, not a branch.
21
+
22
+ | Rule | Detail |
23
+ |------|--------|
24
+ | **Format** | `v` + **semver**, same as `package.json` → `version` |
25
+ | **Example** | If `"version": "0.2.0"` in `package.json`, the tag **must** be **`v0.2.0`** |
26
+ | **Not** | A branch name (e.g. `feature/v0.1.0`) does **not** trigger the `publish` job |
27
+ | **Workflow** | `.github/workflows/release.yml` runs the `publish` job only for refs like `refs/tags/v*` |
28
+
29
+ After pushing `v0.2.0`, Actions publishes to npmjs + GitHub Packages and creates a GitHub Release with the `.tgz`.
30
+
31
+ ## Local release check
32
+
33
+ ```bash
34
+ pnpm install --frozen-lockfile
35
+ pnpm run typecheck
36
+ pnpm run build
37
+ pnpm test
38
+ ```
39
+
40
+ ## Bump version and tag (recommended)
41
+
42
+ Use pnpm so `package.json` and git tag stay aligned:
43
+
44
+ ```bash
45
+ pnpm version patch # or minor / major — updates package.json and creates commit + tag vX.Y.Z
46
+ git push origin main --follow-tags
47
+ ```
48
+
49
+ Or set the version manually, then tag **exactly** `v` + that version:
50
+
51
+ ```bash
52
+ # Example: package.json already says "0.2.0"
53
+ git tag -a v0.2.0 -m "v0.2.0"
54
+ git push origin v0.2.0
55
+ ```
56
+
57
+ Release workflow publishes on `v*` tags. npm publish is skipped if that version already exists on npm.
58
+
59
+ ## GitHub Packages (`npm.pkg.github.com`)
60
+
61
+ GitHub’s npm registry requires the package scope to match the repository owner. The workflow publishes **`@partme.ai/openclaw-web-stomp`** to npmjs, and **`@<github-owner>/openclaw-web-stomp`** (for example **`@partme-ai/openclaw-web-stomp`**) to GitHub Packages. Install from GitHub Packages only when you intentionally use that registry and auth (PAT with `read:packages`).
62
+
63
+ ## Manual publish from GitHub Actions
64
+
65
+ 1. Open **Actions** → **Release**
66
+ 2. Click **Run workflow** (this runs the `package` job only; it does **not** publish to npm/GitHub Packages/Release unless the run is from a **`v*`** tag)
67
+ 3. For a real publish, push a **`vX.Y.Z`** tag as above, then confirm logs show:
68
+ - `Published @partme.ai/openclaw-web-stomp@x.y.z` (npmjs)
69
+ - `Published @<github-owner>/openclaw-web-stomp@x.y.z` (GitHub Packages)
70
+
71
+ ## Common issues
72
+
73
+ - **No Releases / Packages on GitHub**: No **`v*`** tag has been pushed yet — push `v` + semver matching `package.json`.
74
+ - **`ERR_PNPM_OUTDATED_LOCKFILE`**: Run `pnpm install` locally and commit the updated `pnpm-lock.yaml` whenever `package.json` dependencies change.
75
+ - `npm ERR! 403`: version already published or token permissions insufficient
76
+ - `NPM_TOKEN is not set`: add secret in repo settings
77
+ - `npm whoami` failed: token expired or wrong scope permission