@dailyflows/openclaw-dailyflows 1.0.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 +21 -0
- package/README.md +182 -0
- package/README.zh-CN.md +182 -0
- package/index.ts +33 -0
- package/openclaw.plugin.json +9 -0
- package/package.json +69 -0
- package/src/channel.ts +86 -0
- package/src/cli.ts +82 -0
- package/src/config.ts +68 -0
- package/src/inbound.ts +125 -0
- package/src/pairing-http.ts +250 -0
- package/src/pairing.test.ts +26 -0
- package/src/pairing.ts +75 -0
- package/src/qr.ts +131 -0
- package/src/qrcode-terminal.vendor.d.ts +9 -0
- package/src/runtime.ts +14 -0
- package/src/send.ts +63 -0
- package/src/signature.test.ts +27 -0
- package/src/signature.ts +22 -0
- package/src/types.ts +97 -0
- package/src/unpair-http.test.ts +74 -0
- package/src/unpair-http.ts +117 -0
- package/src/webhook.ts +114 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dailyflows Contributors
|
|
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,182 @@
|
|
|
1
|
+
# Dailyflows Plugin for OpenClaw
|
|
2
|
+
|
|
3
|
+
This directory is designed to be maintained as a standalone open-source repository (for example `dailyflows-plugin`). You can maintain only the plugin without maintaining a full OpenClaw fork.
|
|
4
|
+
|
|
5
|
+
For Chinese documentation, see [README.zh-CN.md](./README.zh-CN.md).
|
|
6
|
+
|
|
7
|
+
## 1) Install OpenClaw (official)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
curl -fsSL https://openclaw.ai/install.sh | bash
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g openclaw@latest
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Initial setup:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
openclaw onboard --install-daemon
|
|
23
|
+
openclaw gateway status
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If you see `Runtime: running` + `RPC probe: ok`, your Gateway is healthy.
|
|
27
|
+
|
|
28
|
+
## 2) Install the Dailyflows plugin
|
|
29
|
+
|
|
30
|
+
### Option A: Install from npm (recommended)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
openclaw plugins install @dailyflows/dailyflows
|
|
34
|
+
openclaw gateway restart
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Option B: Install from local source (development)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
openclaw plugins install -l ./dailyflows-plugin
|
|
41
|
+
openclaw gateway restart
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> You usually install the plugin only once. Reinstall only when you switch machines/profiles or reset config.
|
|
45
|
+
|
|
46
|
+
## 3) Minimal config
|
|
47
|
+
|
|
48
|
+
```json5
|
|
49
|
+
{
|
|
50
|
+
channels: {
|
|
51
|
+
dailyflows: {
|
|
52
|
+
webhookPath: "/dailyflows/webhook",
|
|
53
|
+
accounts: {
|
|
54
|
+
default: {
|
|
55
|
+
enabled: true,
|
|
56
|
+
outboundUrl: "https://dailyflows.example.com/openclaw/outbound",
|
|
57
|
+
outboundToken: "REPLACE_ME"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
plugins: {
|
|
63
|
+
entries: {
|
|
64
|
+
dailyflows: { enabled: true }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use environment variables for webhook secrets:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
export DAILYFLOWS_WEBHOOK_SECRET="replace-with-random"
|
|
74
|
+
# Optional: per-account override
|
|
75
|
+
export DAILYFLOWS_WEBHOOK_SECRET_DEFAULT="replace-with-random"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 4) Pair Dailyflows App with OpenClaw
|
|
79
|
+
|
|
80
|
+
The Dailyflows cloud must call back into your Gateway, so you need a public HTTPS URL (Tailscale Funnel is a common choice).
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
openclaw config set gateway.mode local
|
|
86
|
+
openclaw config set gateway.bind loopback
|
|
87
|
+
openclaw config set gateway.auth.mode password
|
|
88
|
+
openclaw config set gateway.auth.password "<strong-password>"
|
|
89
|
+
openclaw config set gateway.tailscale.mode funnel
|
|
90
|
+
openclaw gateway restart
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Pairing flow:
|
|
94
|
+
|
|
95
|
+
1. Open `https://<gateway-host>/dailyflows/pair`
|
|
96
|
+
2. In Dailyflows App, go to `Voice Assistant -> OpenClaw`
|
|
97
|
+
3. Scan the QR code
|
|
98
|
+
|
|
99
|
+
You can also print the QR in CLI:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
openclaw dailyflows pair --gateway-url https://<your-funnel-url>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 5) Send messages
|
|
106
|
+
|
|
107
|
+
After pairing, send text or attachments to OpenClaw in the Dailyflows chat.
|
|
108
|
+
|
|
109
|
+
Message path:
|
|
110
|
+
|
|
111
|
+
1. Dailyflows -> `POST /dailyflows/webhook`
|
|
112
|
+
2. OpenClaw agent processes the message
|
|
113
|
+
3. Gateway -> `outboundUrl` sends the reply back to Dailyflows
|
|
114
|
+
|
|
115
|
+
Manual outbound check from CLI:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
openclaw message send --channel dailyflows --target <conversationId> --message "hello from openclaw"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 6) Troubleshooting commands
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
openclaw gateway status
|
|
125
|
+
openclaw health
|
|
126
|
+
openclaw status --deep
|
|
127
|
+
openclaw plugins list
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Focus checks:
|
|
131
|
+
|
|
132
|
+
- Model authentication is completed (`onboard`)
|
|
133
|
+
- Gateway is publicly reachable via HTTPS
|
|
134
|
+
- `plugins.entries.dailyflows.enabled` is `true`
|
|
135
|
+
- `channels.dailyflows.accounts.default` has valid `outboundUrl/outboundToken/webhookSecret`
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 7) Maintainer workflow (standalone repo)
|
|
140
|
+
|
|
141
|
+
Recommended structure (already included):
|
|
142
|
+
|
|
143
|
+
- `src/` plugin source
|
|
144
|
+
- `openclaw.plugin.json` plugin manifest
|
|
145
|
+
- `package.json` npm + OpenClaw plugin metadata
|
|
146
|
+
- `.github/workflows/ci.yml` CI workflow
|
|
147
|
+
- `scripts/release.sh` one-command publish script
|
|
148
|
+
|
|
149
|
+
Install and verify:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pnpm install
|
|
153
|
+
pnpm check
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Publish to npm:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm login
|
|
160
|
+
pnpm release
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
If CI already validated and you want to skip local tests:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
SKIP_TESTS=1 pnpm release
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 8) One-click export as standalone Git repo
|
|
170
|
+
|
|
171
|
+
This directory includes two helper scripts:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Local-only init (git init + first commit)
|
|
175
|
+
./scripts/init-standalone-local.sh
|
|
176
|
+
|
|
177
|
+
# Init and attach GitHub remote (optional --push)
|
|
178
|
+
./scripts/init-standalone-with-remote.sh git@github.com:<you>/<repo>.git main
|
|
179
|
+
./scripts/init-standalone-with-remote.sh git@github.com:<you>/<repo>.git main --push
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
If you already ran `git init` elsewhere, the second script reuses the existing repository and only updates `origin`.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Dailyflows Plugin for OpenClaw
|
|
2
|
+
|
|
3
|
+
英文版文档请见 [README.md](./README.md)。
|
|
4
|
+
|
|
5
|
+
这个目录可以直接独立成一个开源仓库(例如 `dailyflows-plugin`),只维护插件,不需要维护整份 OpenClaw fork。
|
|
6
|
+
|
|
7
|
+
## 1) 用户侧:安装 OpenClaw(官方)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
curl -fsSL https://openclaw.ai/install.sh | bash
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
或:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g openclaw@latest
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
首次初始化:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
openclaw onboard --install-daemon
|
|
23
|
+
openclaw gateway status
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
看到 `Runtime: running` + `RPC probe: ok`,说明 Gateway 正常。
|
|
27
|
+
|
|
28
|
+
## 2) 用户侧:安装 Dailyflows 插件
|
|
29
|
+
|
|
30
|
+
### 方式 A:npm 安装(推荐)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
openclaw plugins install @dailyflows/dailyflows
|
|
34
|
+
openclaw gateway restart
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 方式 B:本地源码安装(开发调试)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
openclaw plugins install -l ./dailyflows-plugin
|
|
41
|
+
openclaw gateway restart
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> 插件一般只安装一次。除非换机器、清理配置、切 profile,平时不需要重复安装。
|
|
45
|
+
|
|
46
|
+
## 3) 用户侧:最小配置
|
|
47
|
+
|
|
48
|
+
```json5
|
|
49
|
+
{
|
|
50
|
+
channels: {
|
|
51
|
+
dailyflows: {
|
|
52
|
+
webhookPath: "/dailyflows/webhook",
|
|
53
|
+
accounts: {
|
|
54
|
+
default: {
|
|
55
|
+
enabled: true,
|
|
56
|
+
outboundUrl: "https://dailyflows.example.com/openclaw/outbound",
|
|
57
|
+
outboundToken: "REPLACE_ME"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
plugins: {
|
|
63
|
+
entries: {
|
|
64
|
+
dailyflows: { enabled: true }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
建议用环境变量配置 webhook secret:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
export DAILYFLOWS_WEBHOOK_SECRET="replace-with-random"
|
|
74
|
+
# 可选,按 accountId 覆盖
|
|
75
|
+
export DAILYFLOWS_WEBHOOK_SECRET_DEFAULT="replace-with-random"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 4) 用户侧:扫码绑定(Dailyflows App -> OpenClaw)
|
|
79
|
+
|
|
80
|
+
Dailyflows 云端需要回调你的 Gateway,所以要有公网 HTTPS 地址(常见方案:Tailscale Funnel)。
|
|
81
|
+
|
|
82
|
+
示例:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
openclaw config set gateway.mode local
|
|
86
|
+
openclaw config set gateway.bind loopback
|
|
87
|
+
openclaw config set gateway.auth.mode password
|
|
88
|
+
openclaw config set gateway.auth.password "<strong-password>"
|
|
89
|
+
openclaw config set gateway.tailscale.mode funnel
|
|
90
|
+
openclaw gateway restart
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
绑定流程:
|
|
94
|
+
|
|
95
|
+
1. 打开 `https://<gateway-host>/dailyflows/pair`
|
|
96
|
+
2. Dailyflows App 进入 `Voice Assistant -> OpenClaw`
|
|
97
|
+
3. 扫码完成绑定
|
|
98
|
+
|
|
99
|
+
也可以在终端生成二维码:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
openclaw dailyflows pair --gateway-url https://<your-funnel-url>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 5) 用户侧:发送消息
|
|
106
|
+
|
|
107
|
+
绑定后,直接在 Dailyflows 聊天中给 OpenClaw 发文字/附件即可。
|
|
108
|
+
|
|
109
|
+
链路:
|
|
110
|
+
|
|
111
|
+
1. Dailyflows -> `POST /dailyflows/webhook`
|
|
112
|
+
2. OpenClaw Agent 处理
|
|
113
|
+
3. Gateway -> `outboundUrl` 回发 Dailyflows
|
|
114
|
+
|
|
115
|
+
手动验证(从 OpenClaw 主动发消息):
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
openclaw message send --channel dailyflows --target <conversationId> --message "hello from openclaw"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 6) 排查命令
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
openclaw gateway status
|
|
125
|
+
openclaw health
|
|
126
|
+
openclaw status --deep
|
|
127
|
+
openclaw plugins list
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
重点检查:
|
|
131
|
+
|
|
132
|
+
- 模型认证是否完成(onboard)
|
|
133
|
+
- Gateway 是否可公网 HTTPS 访问
|
|
134
|
+
- `plugins.entries.dailyflows.enabled` 是否为 `true`
|
|
135
|
+
- `channels.dailyflows.accounts.default` 的 `outboundUrl/outboundToken/webhookSecret` 是否有效
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 7) 维护者侧:把本目录作为独立仓库
|
|
140
|
+
|
|
141
|
+
建议结构(本目录已包含):
|
|
142
|
+
|
|
143
|
+
- `src/` 插件源码
|
|
144
|
+
- `openclaw.plugin.json` 插件声明
|
|
145
|
+
- `package.json` 发布配置(npm + OpenClaw 元信息)
|
|
146
|
+
- `.github/workflows/ci.yml` 持续集成
|
|
147
|
+
- `scripts/release.sh` 一键发布脚本
|
|
148
|
+
|
|
149
|
+
安装依赖与测试:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pnpm install
|
|
153
|
+
pnpm check
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
发布到 npm:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm login
|
|
160
|
+
pnpm release
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
如果你已经在 CI 里跑过测试,想跳过本地测试:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
SKIP_TESTS=1 pnpm release
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 8) 一键导出为独立 Git 仓库
|
|
170
|
+
|
|
171
|
+
本目录已内置两个脚本:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# 仅本地初始化(git init + 首次 commit)
|
|
175
|
+
./scripts/init-standalone-local.sh
|
|
176
|
+
|
|
177
|
+
# 初始化并绑定 GitHub 远端(可选 --push 直接推送)
|
|
178
|
+
./scripts/init-standalone-with-remote.sh git@github.com:<you>/<repo>.git main
|
|
179
|
+
./scripts/init-standalone-with-remote.sh git@github.com:<you>/<repo>.git main --push
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
如果你已经在别处 `git init` 过,第二个脚本会复用已有仓库,只更新 `origin`。
|
package/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
3
|
+
import { dailyflowsPlugin } from "./src/channel.js";
|
|
4
|
+
import { registerDailyflowsCli } from "./src/cli.js";
|
|
5
|
+
import { createDailyflowsPairingRoute } from "./src/pairing-http.js";
|
|
6
|
+
import { setDailyflowsRuntime } from "./src/runtime.js";
|
|
7
|
+
import { createDailyflowsUnpairRoute } from "./src/unpair-http.js";
|
|
8
|
+
import { createDailyflowsWebhookHandler } from "./src/webhook.js";
|
|
9
|
+
|
|
10
|
+
const plugin = {
|
|
11
|
+
id: "dailyflows",
|
|
12
|
+
name: "Dailyflows",
|
|
13
|
+
description: "Dailyflows webhook channel plugin",
|
|
14
|
+
configSchema: emptyPluginConfigSchema(),
|
|
15
|
+
register(api: OpenClawPluginApi) {
|
|
16
|
+
setDailyflowsRuntime(api.runtime);
|
|
17
|
+
api.registerChannel({ plugin: dailyflowsPlugin });
|
|
18
|
+
api.registerHttpHandler(createDailyflowsWebhookHandler(api));
|
|
19
|
+
api.registerHttpRoute({
|
|
20
|
+
path: "/dailyflows/pair",
|
|
21
|
+
handler: createDailyflowsPairingRoute(api),
|
|
22
|
+
});
|
|
23
|
+
api.registerHttpRoute({
|
|
24
|
+
path: "/dailyflows/unpair",
|
|
25
|
+
handler: createDailyflowsUnpairRoute(api),
|
|
26
|
+
});
|
|
27
|
+
api.registerCli(({ program, config }) => registerDailyflowsCli({ program, config }), {
|
|
28
|
+
commands: ["dailyflows"],
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default plugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dailyflows/openclaw-dailyflows",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Dailyflows webhook channel plugin for OpenClaw",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "index.ts",
|
|
8
|
+
"packageManager": "pnpm@8.14.3",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"openclaw",
|
|
11
|
+
"dailyflows",
|
|
12
|
+
"plugin",
|
|
13
|
+
"channel",
|
|
14
|
+
"webhook"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"index.ts",
|
|
21
|
+
"openclaw.plugin.json",
|
|
22
|
+
"src/**",
|
|
23
|
+
"README.md",
|
|
24
|
+
"README.zh-CN.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"registry": "https://registry.npmjs.org"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"qrcode-terminal": "^0.12.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"openclaw": ">=2026.2.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"openclaw": "^2026.2.1",
|
|
39
|
+
"vitest": "^4.0.18"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"test:watch": "vitest",
|
|
44
|
+
"pack:check": "npm pack --dry-run",
|
|
45
|
+
"check": "pnpm test && pnpm pack:check",
|
|
46
|
+
"release": "bash ./scripts/release.sh"
|
|
47
|
+
},
|
|
48
|
+
"openclaw": {
|
|
49
|
+
"extensions": [
|
|
50
|
+
"./index.ts"
|
|
51
|
+
],
|
|
52
|
+
"channel": {
|
|
53
|
+
"id": "dailyflows",
|
|
54
|
+
"label": "Dailyflows",
|
|
55
|
+
"selectionLabel": "Dailyflows (Webhook)",
|
|
56
|
+
"docsPath": "/channels/dailyflows",
|
|
57
|
+
"docsLabel": "dailyflows",
|
|
58
|
+
"blurb": "Dailyflows webhook channel.",
|
|
59
|
+
"order": 90,
|
|
60
|
+
"aliases": [
|
|
61
|
+
"dailyflow"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"install": {
|
|
65
|
+
"npmSpec": "@dailyflows/openclaw-dailyflows",
|
|
66
|
+
"defaultChoice": "npm"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/channel.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
|
|
2
|
+
import type {
|
|
3
|
+
DailyflowsChannelPlugin,
|
|
4
|
+
DailyflowsOutboundContext,
|
|
5
|
+
DailyflowsOutboundResult,
|
|
6
|
+
} from "./types.js";
|
|
7
|
+
import { listDailyflowsAccountIds, resolveDailyflowsAccount } from "./config.js";
|
|
8
|
+
import { sendDailyflowsMessage } from "./send.js";
|
|
9
|
+
|
|
10
|
+
const meta = {
|
|
11
|
+
id: "dailyflows",
|
|
12
|
+
label: "Dailyflows",
|
|
13
|
+
selectionLabel: "Dailyflows (Webhook)",
|
|
14
|
+
detailLabel: "Dailyflows",
|
|
15
|
+
docsPath: "/channels/dailyflows",
|
|
16
|
+
docsLabel: "dailyflows",
|
|
17
|
+
blurb: "Dailyflows webhook channel.",
|
|
18
|
+
systemImage: "message.fill",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const dailyflowsPlugin: DailyflowsChannelPlugin = {
|
|
22
|
+
id: "dailyflows",
|
|
23
|
+
meta,
|
|
24
|
+
capabilities: {
|
|
25
|
+
chatTypes: ["direct", "group"],
|
|
26
|
+
reactions: false,
|
|
27
|
+
threads: false,
|
|
28
|
+
media: true,
|
|
29
|
+
nativeCommands: false,
|
|
30
|
+
blockStreaming: true,
|
|
31
|
+
},
|
|
32
|
+
reload: { configPrefixes: ["channels.dailyflows"] },
|
|
33
|
+
config: {
|
|
34
|
+
listAccountIds: (cfg) => listDailyflowsAccountIds(cfg),
|
|
35
|
+
resolveAccount: (cfg, accountId) => resolveDailyflowsAccount(cfg, accountId),
|
|
36
|
+
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
37
|
+
isEnabled: (account) => account.enabled,
|
|
38
|
+
isConfigured: (account) => Boolean(account.webhookSecret || account.outboundUrl),
|
|
39
|
+
unconfiguredReason: () => "Set channels.dailyflows.accounts.<id>.outboundUrl or webhookSecret",
|
|
40
|
+
describeAccount: (account) => ({
|
|
41
|
+
accountId: account.accountId,
|
|
42
|
+
name: account.name,
|
|
43
|
+
enabled: account.enabled,
|
|
44
|
+
configured: Boolean(account.webhookSecret || account.outboundUrl),
|
|
45
|
+
meta: {
|
|
46
|
+
outboundUrl: account.outboundUrl ?? null,
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
outbound: {
|
|
51
|
+
deliveryMode: "direct",
|
|
52
|
+
resolveTarget: ({ to }) => {
|
|
53
|
+
const trimmed = to?.trim();
|
|
54
|
+
if (!trimmed) {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
error: new Error("Dailyflows target required. Provide a conversationId or senderId."),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { ok: true, to: trimmed };
|
|
61
|
+
},
|
|
62
|
+
sendText: async (params: DailyflowsOutboundContext): Promise<DailyflowsOutboundResult> => {
|
|
63
|
+
const { cfg, to, text, accountId } = params;
|
|
64
|
+
return sendDailyflowsMessage({
|
|
65
|
+
cfg,
|
|
66
|
+
payload: {
|
|
67
|
+
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
|
|
68
|
+
conversationId: to,
|
|
69
|
+
text,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
sendMedia: async (params: DailyflowsOutboundContext): Promise<DailyflowsOutboundResult> => {
|
|
74
|
+
const { cfg, to, text, mediaUrl, accountId } = params;
|
|
75
|
+
return sendDailyflowsMessage({
|
|
76
|
+
cfg,
|
|
77
|
+
payload: {
|
|
78
|
+
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
|
|
79
|
+
conversationId: to,
|
|
80
|
+
text: text?.trim() ? text : undefined,
|
|
81
|
+
attachments: mediaUrl ? [{ type: "file", url: mediaUrl }] : undefined,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import qrcode from "qrcode-terminal";
|
|
3
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
4
|
+
|
|
5
|
+
import { normalizeGatewayUrl } from "./pairing.js";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_GATEWAY_PORT = 18789;
|
|
8
|
+
|
|
9
|
+
function resolveLocalGatewayUrl(config: OpenClawConfig, override?: string) {
|
|
10
|
+
if (override?.trim()) {
|
|
11
|
+
return override.trim();
|
|
12
|
+
}
|
|
13
|
+
const port = typeof config.gateway?.port === "number" ? config.gateway.port : DEFAULT_GATEWAY_PORT;
|
|
14
|
+
return `http://127.0.0.1:${port}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function registerDailyflowsCli(params: {
|
|
18
|
+
program: Command;
|
|
19
|
+
config: OpenClawConfig;
|
|
20
|
+
}) {
|
|
21
|
+
const { program, config } = params;
|
|
22
|
+
|
|
23
|
+
const root = program
|
|
24
|
+
.command("dailyflows")
|
|
25
|
+
.description("Dailyflows pairing helpers")
|
|
26
|
+
.addHelpText("after", () => "\nDocs: https://docs.openclaw.ai/channels/dailyflows\n");
|
|
27
|
+
|
|
28
|
+
root
|
|
29
|
+
.command("pair")
|
|
30
|
+
.description("Generate a Dailyflows pairing QR from the running gateway")
|
|
31
|
+
.requiredOption("--gateway-url <url>", "Public HTTPS gateway URL (Tailscale Funnel)")
|
|
32
|
+
.option("--account <id>", "Dailyflows account id", "default")
|
|
33
|
+
.option("--gateway-http <url>", "Local gateway HTTP base URL")
|
|
34
|
+
.option("--json", "Print JSON only")
|
|
35
|
+
.action(async (options: { gatewayUrl: string; account: string; gatewayHttp?: string; json?: boolean }) => {
|
|
36
|
+
const publicUrl = normalizeGatewayUrl(options.gatewayUrl);
|
|
37
|
+
if (!publicUrl) {
|
|
38
|
+
throw new Error("--gateway-url must be a public https:// URL (Tailscale Funnel)");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const localGateway = resolveLocalGatewayUrl(config, options.gatewayHttp);
|
|
42
|
+
const pairingUrl = new URL("/dailyflows/pair", localGateway);
|
|
43
|
+
pairingUrl.searchParams.set("gatewayUrl", publicUrl);
|
|
44
|
+
pairingUrl.searchParams.set("accountId", options.account);
|
|
45
|
+
pairingUrl.searchParams.set("format", "json");
|
|
46
|
+
|
|
47
|
+
const res = await fetch(pairingUrl.toString());
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
const text = await res.text();
|
|
50
|
+
throw new Error(`Gateway pairing endpoint failed: ${res.status} ${res.statusText} ${text}`);
|
|
51
|
+
}
|
|
52
|
+
const payload = (await res.json()) as {
|
|
53
|
+
ok?: boolean;
|
|
54
|
+
error?: string;
|
|
55
|
+
gatewayUrl?: string;
|
|
56
|
+
accountId?: string;
|
|
57
|
+
pairCode?: string;
|
|
58
|
+
payload?: string;
|
|
59
|
+
expiresAt?: number;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (!payload.ok || !payload.payload) {
|
|
63
|
+
throw new Error(payload.error || "Failed to generate pairing payload");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (options.json) {
|
|
67
|
+
// eslint-disable-next-line no-console
|
|
68
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
qrcode.generate(payload.payload, { small: true });
|
|
73
|
+
// eslint-disable-next-line no-console
|
|
74
|
+
console.log("\nDailyflows pairing payload:");
|
|
75
|
+
// eslint-disable-next-line no-console
|
|
76
|
+
console.log(payload.payload);
|
|
77
|
+
// eslint-disable-next-line no-console
|
|
78
|
+
console.log(`\nGateway URL: ${payload.gatewayUrl}`);
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.log(`Pair code: ${payload.pairCode}`);
|
|
81
|
+
});
|
|
82
|
+
}
|