@openclaw/crabline 0.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 +226 -0
- package/dist/src/bin/crabline.d.ts +2 -0
- package/dist/src/bin/crabline.js +7 -0
- package/dist/src/bin/crabline.js.map +1 -0
- package/dist/src/cli/program.d.ts +5 -0
- package/dist/src/cli/program.js +295 -0
- package/dist/src/cli/program.js.map +1 -0
- package/dist/src/config/load.d.ts +6 -0
- package/dist/src/config/load.js +41 -0
- package/dist/src/config/load.js.map +1 -0
- package/dist/src/config/schema.d.ts +1571 -0
- package/dist/src/config/schema.js +528 -0
- package/dist/src/config/schema.js.map +1 -0
- package/dist/src/core/errors.d.ts +12 -0
- package/dist/src/core/errors.js +28 -0
- package/dist/src/core/errors.js.map +1 -0
- package/dist/src/core/exit-codes.d.ts +13 -0
- package/dist/src/core/exit-codes.js +13 -0
- package/dist/src/core/exit-codes.js.map +1 -0
- package/dist/src/core/matcher.d.ts +2 -0
- package/dist/src/core/matcher.js +30 -0
- package/dist/src/core/matcher.js.map +1 -0
- package/dist/src/core/message-template.d.ts +2 -0
- package/dist/src/core/message-template.js +20 -0
- package/dist/src/core/message-template.js.map +1 -0
- package/dist/src/core/nonces.d.ts +2 -0
- package/dist/src/core/nonces.js +11 -0
- package/dist/src/core/nonces.js.map +1 -0
- package/dist/src/core/reporters.d.ts +3 -0
- package/dist/src/core/reporters.js +23 -0
- package/dist/src/core/reporters.js.map +1 -0
- package/dist/src/core/run.d.ts +30 -0
- package/dist/src/core/run.js +240 -0
- package/dist/src/core/run.js.map +1 -0
- package/dist/src/fake-servers/index.d.ts +10 -0
- package/dist/src/fake-servers/index.js +12 -0
- package/dist/src/fake-servers/index.js.map +1 -0
- package/dist/src/fake-servers/telegram.d.ts +29 -0
- package/dist/src/fake-servers/telegram.js +346 -0
- package/dist/src/fake-servers/telegram.js.map +1 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/openclaw.d.ts +102 -0
- package/dist/src/openclaw.js +289 -0
- package/dist/src/openclaw.js.map +1 -0
- package/dist/src/providers/builtin/discord.d.ts +15 -0
- package/dist/src/providers/builtin/discord.js +92 -0
- package/dist/src/providers/builtin/discord.js.map +1 -0
- package/dist/src/providers/builtin/feishu.d.ts +10 -0
- package/dist/src/providers/builtin/feishu.js +95 -0
- package/dist/src/providers/builtin/feishu.js.map +1 -0
- package/dist/src/providers/builtin/googlechat.d.ts +11 -0
- package/dist/src/providers/builtin/googlechat.js +83 -0
- package/dist/src/providers/builtin/googlechat.js.map +1 -0
- package/dist/src/providers/builtin/imessage.d.ts +11 -0
- package/dist/src/providers/builtin/imessage.js +67 -0
- package/dist/src/providers/builtin/imessage.js.map +1 -0
- package/dist/src/providers/builtin/loopback.d.ts +63 -0
- package/dist/src/providers/builtin/loopback.js +174 -0
- package/dist/src/providers/builtin/loopback.js.map +1 -0
- package/dist/src/providers/builtin/matrix.d.ts +21 -0
- package/dist/src/providers/builtin/matrix.js +84 -0
- package/dist/src/providers/builtin/matrix.js.map +1 -0
- package/dist/src/providers/builtin/mattermost.d.ts +11 -0
- package/dist/src/providers/builtin/mattermost.js +67 -0
- package/dist/src/providers/builtin/mattermost.js.map +1 -0
- package/dist/src/providers/builtin/msteams.d.ts +13 -0
- package/dist/src/providers/builtin/msteams.js +72 -0
- package/dist/src/providers/builtin/msteams.js.map +1 -0
- package/dist/src/providers/builtin/native-local-mock.d.ts +48 -0
- package/dist/src/providers/builtin/native-local-mock.js +119 -0
- package/dist/src/providers/builtin/native-local-mock.js.map +1 -0
- package/dist/src/providers/builtin/script.d.ts +39 -0
- package/dist/src/providers/builtin/script.js +206 -0
- package/dist/src/providers/builtin/script.js.map +1 -0
- package/dist/src/providers/builtin/slack.d.ts +6 -0
- package/dist/src/providers/builtin/slack.js +106 -0
- package/dist/src/providers/builtin/slack.js.map +1 -0
- package/dist/src/providers/builtin/telegram.d.ts +21 -0
- package/dist/src/providers/builtin/telegram.js +123 -0
- package/dist/src/providers/builtin/telegram.js.map +1 -0
- package/dist/src/providers/builtin/whatsapp.d.ts +19 -0
- package/dist/src/providers/builtin/whatsapp.js +90 -0
- package/dist/src/providers/builtin/whatsapp.js.map +1 -0
- package/dist/src/providers/builtin/zalo.d.ts +17 -0
- package/dist/src/providers/builtin/zalo.js +70 -0
- package/dist/src/providers/builtin/zalo.js.map +1 -0
- package/dist/src/providers/catalog.d.ts +69 -0
- package/dist/src/providers/catalog.js +95 -0
- package/dist/src/providers/catalog.js.map +1 -0
- package/dist/src/providers/local-mock.d.ts +41 -0
- package/dist/src/providers/local-mock.js +243 -0
- package/dist/src/providers/local-mock.js.map +1 -0
- package/dist/src/providers/recorder.d.ts +19 -0
- package/dist/src/providers/recorder.js +160 -0
- package/dist/src/providers/recorder.js.map +1 -0
- package/dist/src/providers/registry.d.ts +8 -0
- package/dist/src/providers/registry.js +171 -0
- package/dist/src/providers/registry.js.map +1 -0
- package/dist/src/providers/types.d.ts +94 -0
- package/dist/src/providers/types.js +2 -0
- package/dist/src/providers/types.js.map +1 -0
- package/dist/src/providers/webhook-server.d.ts +12 -0
- package/dist/src/providers/webhook-server.js +118 -0
- package/dist/src/providers/webhook-server.js.map +1 -0
- package/docs/channel-setup.md +181 -0
- package/fixtures/examples/crabline.example.yaml +195 -0
- package/fixtures/examples/openclaw-bridge.yaml +125 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 openclaw
|
|
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,226 @@
|
|
|
1
|
+
# crabline
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Deterministic local messaging-channel mocks for OpenClaw QA.
|
|
6
|
+
|
|
7
|
+
`crabline` is config-driven, CI-friendly, and deliberately has no `openclaw`
|
|
8
|
+
dependency. It can run fixture-level local mocks, and it can also serve fake
|
|
9
|
+
provider APIs that OpenClaw live adapters can target during deterministic QA.
|
|
10
|
+
|
|
11
|
+
## What It Provides
|
|
12
|
+
|
|
13
|
+
- local mock providers for `discord`, `feishu`, `googlechat`, `imessage`,
|
|
14
|
+
`loopback`, `matrix`, `mattermost`, `msteams`, `slack`, `telegram`,
|
|
15
|
+
`whatsapp`, and `zalo`
|
|
16
|
+
- a `script` bridge for channels that are still exercised by external commands
|
|
17
|
+
- per-provider local webhook endpoints for inbound events
|
|
18
|
+
- fake provider servers for live-adapter smoke tests, starting with Telegram
|
|
19
|
+
- JSONL recorder files for deterministic wait/watch behavior
|
|
20
|
+
- nonce-based `send`, `roundtrip`, `agent`, `probe`, `run`, `watch`, and
|
|
21
|
+
`doctor` commands
|
|
22
|
+
- text output by default and stable `--json` output for automation
|
|
23
|
+
|
|
24
|
+
Crabline fake servers are not live-provider coverage. They let OpenClaw run its
|
|
25
|
+
normal channel adapter code against a local provider-shaped API. Release lanes
|
|
26
|
+
still need the `live` driver and real provider credentials.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm install
|
|
32
|
+
pnpm build
|
|
33
|
+
pnpm verify
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Run locally:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm dev fixtures --config fixtures/examples/crabline.example.yaml
|
|
40
|
+
pnpm dev roundtrip telegram-dm --config fixtures/examples/crabline.example.yaml
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quality Gate
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm verify
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That enforces formatting, typecheck, type-aware lint, and Vitest coverage.
|
|
50
|
+
|
|
51
|
+
## Config
|
|
52
|
+
|
|
53
|
+
Config file search order:
|
|
54
|
+
|
|
55
|
+
1. `--config <path>`
|
|
56
|
+
2. `./crabline.yaml`
|
|
57
|
+
3. `./crabline.yml`
|
|
58
|
+
4. `./crabline.json`
|
|
59
|
+
|
|
60
|
+
Top-level shape:
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
configVersion: 1
|
|
64
|
+
userName: crabline
|
|
65
|
+
providers:
|
|
66
|
+
telegram:
|
|
67
|
+
adapter: telegram
|
|
68
|
+
telegram:
|
|
69
|
+
recorder:
|
|
70
|
+
path: ./.crabline/recorders/telegram.jsonl
|
|
71
|
+
webhook:
|
|
72
|
+
host: 127.0.0.1
|
|
73
|
+
port: 8790
|
|
74
|
+
path: /telegram/webhook
|
|
75
|
+
fixtures:
|
|
76
|
+
- id: telegram-dm
|
|
77
|
+
provider: telegram
|
|
78
|
+
mode: roundtrip
|
|
79
|
+
target:
|
|
80
|
+
id: "100000001"
|
|
81
|
+
behavior: agent
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Provider ids are local profile names. Fixtures reference them through
|
|
85
|
+
`provider`. Built-in adapters infer their platform from `adapter`; `platform` is
|
|
86
|
+
required only for `adapter: script`.
|
|
87
|
+
|
|
88
|
+
Built-in provider credentials are optional metadata only. `doctor` checks
|
|
89
|
+
explicit `env` declarations, script command availability, and config shape; it
|
|
90
|
+
does not require live Slack, Discord, Telegram, WhatsApp, Matrix, iMessage, or
|
|
91
|
+
other platform secrets for local mocks.
|
|
92
|
+
|
|
93
|
+
## Built-In Mock Channels
|
|
94
|
+
|
|
95
|
+
All built-in mock providers support:
|
|
96
|
+
|
|
97
|
+
- `probe`
|
|
98
|
+
- `send`
|
|
99
|
+
- `roundtrip`
|
|
100
|
+
- `agent`
|
|
101
|
+
- `watch`
|
|
102
|
+
|
|
103
|
+
The built-in providers are:
|
|
104
|
+
|
|
105
|
+
- `discord`
|
|
106
|
+
- `feishu`
|
|
107
|
+
- `googlechat`
|
|
108
|
+
- `imessage`
|
|
109
|
+
- `loopback`
|
|
110
|
+
- `matrix`
|
|
111
|
+
- `mattermost`
|
|
112
|
+
- `msteams`
|
|
113
|
+
- `slack`
|
|
114
|
+
- `telegram`
|
|
115
|
+
- `whatsapp`
|
|
116
|
+
- `zalo`
|
|
117
|
+
|
|
118
|
+
The `script` adapter can bridge any other OpenClaw channel by running local
|
|
119
|
+
commands for `probe`, `send`, `waitForInbound`, or `watch`.
|
|
120
|
+
|
|
121
|
+
## Fake Provider Servers
|
|
122
|
+
|
|
123
|
+
`serve` starts provider-shaped HTTP APIs for OpenClaw live adapters. This is the
|
|
124
|
+
preferred Smoke CI path because OpenClaw still uses its normal channel adapter,
|
|
125
|
+
but the provider endpoint is local and deterministic.
|
|
126
|
+
|
|
127
|
+
Telegram:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
crabline --json serve telegram --ready-file .crabline/telegram-server.json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The JSON manifest contains:
|
|
134
|
+
|
|
135
|
+
- `endpoints.apiRoot`: set OpenClaw `channels.telegram.apiRoot` to this value
|
|
136
|
+
- `botToken`: set OpenClaw `channels.telegram.botToken` to this value
|
|
137
|
+
- `adminToken`: send this as the `X-Crabline-Admin-Token` header when posting
|
|
138
|
+
test user messages
|
|
139
|
+
- `endpoints.adminInboundUrl`: authenticated POST endpoint for test user
|
|
140
|
+
messages; OpenClaw reads them through Telegram `getUpdates`
|
|
141
|
+
- `recorderPath`: JSONL file of fake provider API/admin traffic
|
|
142
|
+
|
|
143
|
+
The admin token is generated randomly unless `--admin-token <token>` is
|
|
144
|
+
provided. The inbound endpoint rejects requests without the matching admin
|
|
145
|
+
header (or `Authorization: Bearer <token>`).
|
|
146
|
+
|
|
147
|
+
Implemented Telegram Bot API endpoints include `getMe`, `sendMessage`,
|
|
148
|
+
`editMessageText`, `deleteMessage`, `setMessageReaction`, `createForumTopic`,
|
|
149
|
+
`editForumTopic`, `pinChatMessage`, `unpinChatMessage`, `getUpdates`,
|
|
150
|
+
`deleteWebhook`, `setWebhook`, `setMyCommands`, `deleteMyCommands`,
|
|
151
|
+
`sendChatAction`, and `answerCallbackQuery`.
|
|
152
|
+
|
|
153
|
+
## Target IDs
|
|
154
|
+
|
|
155
|
+
Built-in providers accept native channel identifiers. Crabline does not add
|
|
156
|
+
`telegram:`, `discord:`, `slack:`, or other local prefixes.
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
target:
|
|
160
|
+
id: "C1234567890"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Thread targets use the platform's native thread identifier:
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
target:
|
|
167
|
+
channelId: "C1234567890"
|
|
168
|
+
threadId: "1700000000.000100"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Examples:
|
|
172
|
+
|
|
173
|
+
- Slack conversations: `C1234567890`, `G1234567890`, or `D1234567890`
|
|
174
|
+
- Slack threads: `1700000000.000100`
|
|
175
|
+
- Telegram chats: `-1001234567890` or `@channelusername`
|
|
176
|
+
- Telegram topics: `42`
|
|
177
|
+
- Discord channels and threads: Discord snowflake ids such as
|
|
178
|
+
`123456789012345678`
|
|
179
|
+
|
|
180
|
+
## Webhooks
|
|
181
|
+
|
|
182
|
+
Each built-in provider starts a local webhook during `probe`, `waitForInbound`,
|
|
183
|
+
or `watch`. Webhook requests can use the provider's native event shape, or this
|
|
184
|
+
simple JSON shape with native thread ids:
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"id": "slack-inbound-1",
|
|
189
|
+
"threadId": "C1234567890",
|
|
190
|
+
"text": "reply nonce-123",
|
|
191
|
+
"author": "assistant"
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Nested message payloads are also accepted:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"message": {
|
|
200
|
+
"id": "slack-inbound-1",
|
|
201
|
+
"threadId": "C1234567890",
|
|
202
|
+
"text": "reply nonce-123"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Malformed webhooks return `400`, and non-JSON requests return `415`.
|
|
208
|
+
|
|
209
|
+
## Evidence Flow
|
|
210
|
+
|
|
211
|
+
`send` records an outbound user event in the provider recorder. For `roundtrip`
|
|
212
|
+
and `agent` modes, the local mock also records a deterministic assistant reply:
|
|
213
|
+
|
|
214
|
+
```text
|
|
215
|
+
[telegram mock] hello nonce-123
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`waitForInbound` reads the recorder until it finds a matching non-user event.
|
|
219
|
+
`watch` streams matching recorder events. This gives CI channel coverage without
|
|
220
|
+
live service latency, external credentials, webhooks exposed to the internet, or
|
|
221
|
+
provider SDK state.
|
|
222
|
+
|
|
223
|
+
## More Setup Detail
|
|
224
|
+
|
|
225
|
+
See [Channel Setup](docs/channel-setup.md) for the provider matrix, webhook
|
|
226
|
+
paths, and OpenClaw live-vs-mock guidance.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crabline.js","sourceRoot":"","sources":["../../../src/bin/crabline.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAC5C,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;IACnB,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import nodePath from "node:path";
|
|
4
|
+
import { loadManifest } from "../config/load.js";
|
|
5
|
+
import { createRegistry } from "../providers/registry.js";
|
|
6
|
+
import { formatJson, formatRunResultText } from "../core/reporters.js";
|
|
7
|
+
import { computeExitCode, runFixtureCommand, runSuite } from "../core/run.js";
|
|
8
|
+
import { CrablineError, ensureErrorMessage } from "../core/errors.js";
|
|
9
|
+
import { startTelegramFakeServer } from "../fake-servers/telegram.js";
|
|
10
|
+
function print(value) {
|
|
11
|
+
process.stdout.write(`${value}\n`);
|
|
12
|
+
}
|
|
13
|
+
async function withManifest(options, action) {
|
|
14
|
+
return action(await loadManifest(options.config));
|
|
15
|
+
}
|
|
16
|
+
export function createProgram(setExitCode = (code) => {
|
|
17
|
+
process.exitCode = code;
|
|
18
|
+
}) {
|
|
19
|
+
const program = new Command();
|
|
20
|
+
program
|
|
21
|
+
.name("crabline")
|
|
22
|
+
.description("Deterministic CLI harness for messaging provider E2E tests")
|
|
23
|
+
.option("-c, --config <path>", "Config file path")
|
|
24
|
+
.option("--json", "Machine-readable output", false)
|
|
25
|
+
.showHelpAfterError();
|
|
26
|
+
program
|
|
27
|
+
.command("providers")
|
|
28
|
+
.description("List configured providers and provider catalog coverage")
|
|
29
|
+
.action(async () => {
|
|
30
|
+
const options = program.opts();
|
|
31
|
+
const { manifest, path } = await loadManifest(options.config);
|
|
32
|
+
const registry = createRegistry(manifest, path);
|
|
33
|
+
const payload = {
|
|
34
|
+
configured: Object.entries(manifest.providers).map(([id, config]) => ({
|
|
35
|
+
adapter: config.adapter,
|
|
36
|
+
capabilities: config.capabilities,
|
|
37
|
+
id,
|
|
38
|
+
platform: config.platform,
|
|
39
|
+
status: config.status,
|
|
40
|
+
})),
|
|
41
|
+
support: registry.catalog,
|
|
42
|
+
};
|
|
43
|
+
print(options.json ? formatJson(payload) : renderProvidersText(payload));
|
|
44
|
+
});
|
|
45
|
+
program
|
|
46
|
+
.command("fixtures")
|
|
47
|
+
.description("List fixtures")
|
|
48
|
+
.action(async () => {
|
|
49
|
+
const options = program.opts();
|
|
50
|
+
const { manifest } = await loadManifest(options.config);
|
|
51
|
+
print(options.json
|
|
52
|
+
? formatJson(manifest.fixtures)
|
|
53
|
+
: manifest.fixtures
|
|
54
|
+
.map((fixture) => `${fixture.id} ${fixture.mode} provider=${fixture.provider} target=${fixture.target.id}`)
|
|
55
|
+
.join("\n"));
|
|
56
|
+
});
|
|
57
|
+
program
|
|
58
|
+
.command("probe <fixtureOrProvider>")
|
|
59
|
+
.description("Probe provider readiness using a fixture or provider id")
|
|
60
|
+
.action(async (fixtureOrProvider) => {
|
|
61
|
+
const options = program.opts();
|
|
62
|
+
const { manifest, path } = await loadManifest(options.config);
|
|
63
|
+
const fixture = manifest.fixtures.find((entry) => entry.id === fixtureOrProvider) ??
|
|
64
|
+
manifest.fixtures.find((entry) => entry.provider === fixtureOrProvider);
|
|
65
|
+
if (!fixture) {
|
|
66
|
+
throw new CrablineError(`No fixture found for "${fixtureOrProvider}"`, { kind: "config" });
|
|
67
|
+
}
|
|
68
|
+
const registry = createRegistry(manifest, path);
|
|
69
|
+
const result = await runFixtureCommand({
|
|
70
|
+
fixtureId: fixture.id,
|
|
71
|
+
manifest,
|
|
72
|
+
manifestPath: path,
|
|
73
|
+
modeOverride: "probe",
|
|
74
|
+
registry,
|
|
75
|
+
});
|
|
76
|
+
print(options.json ? formatJson(result) : formatRunResultText(result));
|
|
77
|
+
setExitCode(computeExitCode(result));
|
|
78
|
+
});
|
|
79
|
+
for (const mode of ["send", "roundtrip", "agent"]) {
|
|
80
|
+
program
|
|
81
|
+
.command(`${mode} <fixtureId>`)
|
|
82
|
+
.description(`${mode} a fixture`)
|
|
83
|
+
.action(async (fixtureId) => {
|
|
84
|
+
const options = program.opts();
|
|
85
|
+
const { manifest, path } = await loadManifest(options.config);
|
|
86
|
+
const registry = createRegistry(manifest, path);
|
|
87
|
+
const result = await runFixtureCommand({
|
|
88
|
+
fixtureId,
|
|
89
|
+
manifest,
|
|
90
|
+
manifestPath: path,
|
|
91
|
+
modeOverride: mode,
|
|
92
|
+
registry,
|
|
93
|
+
});
|
|
94
|
+
print(options.json ? formatJson(result) : formatRunResultText(result));
|
|
95
|
+
setExitCode(computeExitCode(result));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
program
|
|
99
|
+
.command("run <fixtureIds...>")
|
|
100
|
+
.description("Run one or more fixtures as a suite")
|
|
101
|
+
.action(async (fixtureIds) => {
|
|
102
|
+
const options = program.opts();
|
|
103
|
+
const { manifest, path } = await loadManifest(options.config);
|
|
104
|
+
const registry = createRegistry(manifest, path);
|
|
105
|
+
const result = await runSuite({
|
|
106
|
+
fixtureIds,
|
|
107
|
+
manifest,
|
|
108
|
+
manifestPath: path,
|
|
109
|
+
registry,
|
|
110
|
+
});
|
|
111
|
+
print(options.json ? formatJson(result) : formatRunResultText(result));
|
|
112
|
+
setExitCode(computeExitCode(result));
|
|
113
|
+
});
|
|
114
|
+
program
|
|
115
|
+
.command("watch <fixtureId>")
|
|
116
|
+
.alias("webhook")
|
|
117
|
+
.description("Watch inbound messages for one fixture using provider webhook/recorder mode")
|
|
118
|
+
.action(async (fixtureId) => {
|
|
119
|
+
const options = program.opts();
|
|
120
|
+
await withManifest(options, async ({ manifest, path }) => {
|
|
121
|
+
const fixture = manifest.fixtures.find((entry) => entry.id === fixtureId);
|
|
122
|
+
if (!fixture) {
|
|
123
|
+
throw new CrablineError(`Unknown fixture: ${fixtureId}`, { kind: "config" });
|
|
124
|
+
}
|
|
125
|
+
const provider = createRegistry(manifest, path).resolve(fixture.provider, fixture.id);
|
|
126
|
+
if (!provider.watch) {
|
|
127
|
+
throw new CrablineError(`Provider "${fixture.provider}" does not implement watch.`, {
|
|
128
|
+
kind: "config",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
for await (const message of provider.watch({
|
|
133
|
+
config: manifest.providers[fixture.provider],
|
|
134
|
+
fixture,
|
|
135
|
+
manifestPath: path,
|
|
136
|
+
providerId: fixture.provider,
|
|
137
|
+
userName: manifest.userName,
|
|
138
|
+
})) {
|
|
139
|
+
print(options.json
|
|
140
|
+
? formatJson(message)
|
|
141
|
+
: `${message.sentAt} ${message.author} ${message.text}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
await provider.cleanup?.();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
program
|
|
150
|
+
.command("serve <provider>")
|
|
151
|
+
.description("Start a fake provider server that OpenClaw live adapters can target")
|
|
152
|
+
.option("--host <host>", "Bind host", "127.0.0.1")
|
|
153
|
+
.option("--port <port>", "Bind port", "0")
|
|
154
|
+
.option("--admin-token <token>", "Admin token for inbound test messages")
|
|
155
|
+
.option("--bot-token <token>", "Fake Telegram bot token")
|
|
156
|
+
.option("--bot-username <username>", "Fake Telegram bot username", "crabline_bot")
|
|
157
|
+
.option("--recorder <path>", "JSONL recorder path")
|
|
158
|
+
.option("--ready-file <path>", "Write the server runtime manifest to this path")
|
|
159
|
+
.option("--once", "Start, print the runtime manifest, and stop immediately", false)
|
|
160
|
+
.action(async (provider, commandOptions) => {
|
|
161
|
+
const options = program.opts();
|
|
162
|
+
if (provider !== "telegram") {
|
|
163
|
+
throw new CrablineError(`Unsupported fake provider server: ${provider}`, {
|
|
164
|
+
kind: "config",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const port = Number(commandOptions.port);
|
|
168
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535) {
|
|
169
|
+
throw new CrablineError(`Invalid fake server port: ${commandOptions.port}`, {
|
|
170
|
+
kind: "config",
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
const server = await startTelegramFakeServer({
|
|
174
|
+
adminToken: commandOptions.adminToken,
|
|
175
|
+
botToken: commandOptions.botToken,
|
|
176
|
+
botUsername: commandOptions.botUsername,
|
|
177
|
+
host: commandOptions.host,
|
|
178
|
+
port,
|
|
179
|
+
recorderPath: commandOptions.recorder,
|
|
180
|
+
});
|
|
181
|
+
const payload = formatJson(server.manifest);
|
|
182
|
+
if (commandOptions.readyFile) {
|
|
183
|
+
await fs.mkdir(nodePath.dirname(commandOptions.readyFile), { recursive: true });
|
|
184
|
+
await fs.writeFile(commandOptions.readyFile, `${payload}\n`, "utf8");
|
|
185
|
+
}
|
|
186
|
+
print(options.json ? payload : renderServeText(server.manifest));
|
|
187
|
+
if (commandOptions.once) {
|
|
188
|
+
await server.close();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
await waitForShutdown(server.close);
|
|
192
|
+
});
|
|
193
|
+
program
|
|
194
|
+
.command("doctor")
|
|
195
|
+
.description("Diagnose common setup problems")
|
|
196
|
+
.action(async () => {
|
|
197
|
+
const options = program.opts();
|
|
198
|
+
const { manifest } = await loadManifest(options.config);
|
|
199
|
+
const findings = diagnose(manifest);
|
|
200
|
+
const ok = findings.length === 0;
|
|
201
|
+
const payload = { findings, ok };
|
|
202
|
+
print(options.json ? formatJson(payload) : ok ? "doctor ok" : findings.join("\n"));
|
|
203
|
+
setExitCode(ok ? 0 : 10);
|
|
204
|
+
});
|
|
205
|
+
return program;
|
|
206
|
+
}
|
|
207
|
+
function waitForShutdown(close) {
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
const shutdown = () => {
|
|
210
|
+
close().then(resolve, reject);
|
|
211
|
+
};
|
|
212
|
+
process.once("SIGINT", shutdown);
|
|
213
|
+
process.once("SIGTERM", shutdown);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function renderServeText(manifest) {
|
|
217
|
+
return [
|
|
218
|
+
`${manifest.provider} fake server ready`,
|
|
219
|
+
` apiRoot: ${manifest.endpoints.apiRoot}`,
|
|
220
|
+
` adminToken: ${manifest.adminToken}`,
|
|
221
|
+
` botToken: ${manifest.botToken}`,
|
|
222
|
+
` inbound: ${manifest.endpoints.adminInboundUrl}`,
|
|
223
|
+
` recorder: ${manifest.recorderPath}`,
|
|
224
|
+
].join("\n");
|
|
225
|
+
}
|
|
226
|
+
function renderProvidersText(payload) {
|
|
227
|
+
const lines = ["configured providers:"];
|
|
228
|
+
if (payload.configured.length === 0) {
|
|
229
|
+
lines.push(" none");
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
for (const provider of payload.configured) {
|
|
233
|
+
lines.push(` ${provider.id} platform=${provider.platform} adapter=${provider.adapter} status=${provider.status} supports=${provider.capabilities.join(",")}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
lines.push("support catalog:");
|
|
237
|
+
for (const entry of payload.support) {
|
|
238
|
+
lines.push(` ${entry.platform} status=${entry.status} supports=${entry.supports.join(",")} ${entry.notes}`);
|
|
239
|
+
}
|
|
240
|
+
return lines.join("\n");
|
|
241
|
+
}
|
|
242
|
+
function diagnose(manifest) {
|
|
243
|
+
const findings = [];
|
|
244
|
+
const seen = new Set();
|
|
245
|
+
for (const fixture of manifest.fixtures) {
|
|
246
|
+
if (seen.has(fixture.id)) {
|
|
247
|
+
findings.push(`duplicate fixture id: ${fixture.id}`);
|
|
248
|
+
}
|
|
249
|
+
seen.add(fixture.id);
|
|
250
|
+
if (!manifest.providers[fixture.provider]) {
|
|
251
|
+
findings.push(`fixture ${fixture.id} references unknown provider ${fixture.provider}`);
|
|
252
|
+
}
|
|
253
|
+
for (const envName of fixture.env) {
|
|
254
|
+
if (!process.env[envName]) {
|
|
255
|
+
findings.push(`fixture ${fixture.id} missing env ${envName}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const [providerId, provider] of Object.entries(manifest.providers)) {
|
|
260
|
+
for (const envName of provider.env) {
|
|
261
|
+
if (!process.env[envName]) {
|
|
262
|
+
findings.push(`provider ${providerId} missing env ${envName}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (provider.adapter === "script") {
|
|
266
|
+
if (!provider.script?.commands.send) {
|
|
267
|
+
findings.push(`provider ${providerId} missing script.commands.send`);
|
|
268
|
+
}
|
|
269
|
+
if (!provider.script?.commands.waitForInbound) {
|
|
270
|
+
findings.push(`provider ${providerId} missing script.commands.waitForInbound`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
void providerId;
|
|
274
|
+
}
|
|
275
|
+
return findings;
|
|
276
|
+
}
|
|
277
|
+
export async function runCli(argv) {
|
|
278
|
+
let exitCode = 0;
|
|
279
|
+
const program = createProgram((code) => {
|
|
280
|
+
exitCode = code;
|
|
281
|
+
});
|
|
282
|
+
try {
|
|
283
|
+
await program.parseAsync(argv);
|
|
284
|
+
return exitCode;
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
const message = ensureErrorMessage(error);
|
|
288
|
+
process.stderr.write(`${message}\n`);
|
|
289
|
+
if (error instanceof CrablineError) {
|
|
290
|
+
return error.exitCode;
|
|
291
|
+
}
|
|
292
|
+
return 1;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=program.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.js","sourceRoot":"","sources":["../../../src/cli/program.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import { CrablineError, ensureErrorMessage } from "../core/errors.js";
|
|
5
|
+
import { ManifestSchema } from "./schema.js";
|
|
6
|
+
const DEFAULT_CONFIG_CANDIDATES = ["crabline.yaml", "crabline.yml", "crabline.json"];
|
|
7
|
+
export async function resolveConfigPath(explicitPath) {
|
|
8
|
+
if (explicitPath) {
|
|
9
|
+
return path.resolve(explicitPath);
|
|
10
|
+
}
|
|
11
|
+
for (const candidate of DEFAULT_CONFIG_CANDIDATES) {
|
|
12
|
+
const resolved = path.resolve(candidate);
|
|
13
|
+
try {
|
|
14
|
+
await access(resolved);
|
|
15
|
+
return resolved;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (typeof error === "object" &&
|
|
19
|
+
error !== null &&
|
|
20
|
+
"code" in error &&
|
|
21
|
+
(error.code === "ENOENT" || error.code === "ENOTDIR")) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
throw new CrablineError("No config file found. Create crabline.yaml, crabline.yml, or crabline.json.", { kind: "config" });
|
|
28
|
+
}
|
|
29
|
+
export async function loadManifest(configPath) {
|
|
30
|
+
const resolvedPath = await resolveConfigPath(configPath);
|
|
31
|
+
try {
|
|
32
|
+
const raw = await readFile(resolvedPath, "utf8");
|
|
33
|
+
const parsed = resolvedPath.endsWith(".json") ? JSON.parse(raw) : YAML.parse(raw);
|
|
34
|
+
const manifest = ManifestSchema.parse(parsed);
|
|
35
|
+
return { manifest, path: resolvedPath };
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new CrablineError(`Unable to load config file "${resolvedPath}": ${ensureErrorMessage(error)}`, { cause: error, kind: "config" });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=load.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load.js","sourceRoot":"","sources":["../../../src/config/load.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAA2B,cAAc,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,yBAAyB,GAAG,CAAC,eAAe,EAAE,cAAc,EAAE,eAAe,CAAU,CAAC;AAE9F,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,YAAqB;IAC3D,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,yBAAyB,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;YACvB,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IACE,OAAO,KAAK,KAAK,QAAQ;gBACzB,KAAK,KAAK,IAAI;gBACd,MAAM,IAAI,KAAK;gBACf,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,EACrD,CAAC;gBACD,SAAS;YACX,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,MAAM,IAAI,aAAa,CACrB,6EAA6E,EAC7E,EAAE,IAAI,EAAE,QAAQ,EAAE,CACnB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAmB;IAEnB,MAAM,YAAY,GAAG,MAAM,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACzD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClF,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC9C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,aAAa,CACrB,+BAA+B,YAAY,MAAM,kBAAkB,CAAC,KAAK,CAAC,EAAE,EAC5E,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CACjC,CAAC;IACJ,CAAC;AACH,CAAC"}
|