@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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/dist/src/bin/crabline.d.ts +2 -0
  4. package/dist/src/bin/crabline.js +7 -0
  5. package/dist/src/bin/crabline.js.map +1 -0
  6. package/dist/src/cli/program.d.ts +5 -0
  7. package/dist/src/cli/program.js +295 -0
  8. package/dist/src/cli/program.js.map +1 -0
  9. package/dist/src/config/load.d.ts +6 -0
  10. package/dist/src/config/load.js +41 -0
  11. package/dist/src/config/load.js.map +1 -0
  12. package/dist/src/config/schema.d.ts +1571 -0
  13. package/dist/src/config/schema.js +528 -0
  14. package/dist/src/config/schema.js.map +1 -0
  15. package/dist/src/core/errors.d.ts +12 -0
  16. package/dist/src/core/errors.js +28 -0
  17. package/dist/src/core/errors.js.map +1 -0
  18. package/dist/src/core/exit-codes.d.ts +13 -0
  19. package/dist/src/core/exit-codes.js +13 -0
  20. package/dist/src/core/exit-codes.js.map +1 -0
  21. package/dist/src/core/matcher.d.ts +2 -0
  22. package/dist/src/core/matcher.js +30 -0
  23. package/dist/src/core/matcher.js.map +1 -0
  24. package/dist/src/core/message-template.d.ts +2 -0
  25. package/dist/src/core/message-template.js +20 -0
  26. package/dist/src/core/message-template.js.map +1 -0
  27. package/dist/src/core/nonces.d.ts +2 -0
  28. package/dist/src/core/nonces.js +11 -0
  29. package/dist/src/core/nonces.js.map +1 -0
  30. package/dist/src/core/reporters.d.ts +3 -0
  31. package/dist/src/core/reporters.js +23 -0
  32. package/dist/src/core/reporters.js.map +1 -0
  33. package/dist/src/core/run.d.ts +30 -0
  34. package/dist/src/core/run.js +240 -0
  35. package/dist/src/core/run.js.map +1 -0
  36. package/dist/src/fake-servers/index.d.ts +10 -0
  37. package/dist/src/fake-servers/index.js +12 -0
  38. package/dist/src/fake-servers/index.js.map +1 -0
  39. package/dist/src/fake-servers/telegram.d.ts +29 -0
  40. package/dist/src/fake-servers/telegram.js +346 -0
  41. package/dist/src/fake-servers/telegram.js.map +1 -0
  42. package/dist/src/index.d.ts +15 -0
  43. package/dist/src/index.js +9 -0
  44. package/dist/src/index.js.map +1 -0
  45. package/dist/src/openclaw.d.ts +102 -0
  46. package/dist/src/openclaw.js +289 -0
  47. package/dist/src/openclaw.js.map +1 -0
  48. package/dist/src/providers/builtin/discord.d.ts +15 -0
  49. package/dist/src/providers/builtin/discord.js +92 -0
  50. package/dist/src/providers/builtin/discord.js.map +1 -0
  51. package/dist/src/providers/builtin/feishu.d.ts +10 -0
  52. package/dist/src/providers/builtin/feishu.js +95 -0
  53. package/dist/src/providers/builtin/feishu.js.map +1 -0
  54. package/dist/src/providers/builtin/googlechat.d.ts +11 -0
  55. package/dist/src/providers/builtin/googlechat.js +83 -0
  56. package/dist/src/providers/builtin/googlechat.js.map +1 -0
  57. package/dist/src/providers/builtin/imessage.d.ts +11 -0
  58. package/dist/src/providers/builtin/imessage.js +67 -0
  59. package/dist/src/providers/builtin/imessage.js.map +1 -0
  60. package/dist/src/providers/builtin/loopback.d.ts +63 -0
  61. package/dist/src/providers/builtin/loopback.js +174 -0
  62. package/dist/src/providers/builtin/loopback.js.map +1 -0
  63. package/dist/src/providers/builtin/matrix.d.ts +21 -0
  64. package/dist/src/providers/builtin/matrix.js +84 -0
  65. package/dist/src/providers/builtin/matrix.js.map +1 -0
  66. package/dist/src/providers/builtin/mattermost.d.ts +11 -0
  67. package/dist/src/providers/builtin/mattermost.js +67 -0
  68. package/dist/src/providers/builtin/mattermost.js.map +1 -0
  69. package/dist/src/providers/builtin/msteams.d.ts +13 -0
  70. package/dist/src/providers/builtin/msteams.js +72 -0
  71. package/dist/src/providers/builtin/msteams.js.map +1 -0
  72. package/dist/src/providers/builtin/native-local-mock.d.ts +48 -0
  73. package/dist/src/providers/builtin/native-local-mock.js +119 -0
  74. package/dist/src/providers/builtin/native-local-mock.js.map +1 -0
  75. package/dist/src/providers/builtin/script.d.ts +39 -0
  76. package/dist/src/providers/builtin/script.js +206 -0
  77. package/dist/src/providers/builtin/script.js.map +1 -0
  78. package/dist/src/providers/builtin/slack.d.ts +6 -0
  79. package/dist/src/providers/builtin/slack.js +106 -0
  80. package/dist/src/providers/builtin/slack.js.map +1 -0
  81. package/dist/src/providers/builtin/telegram.d.ts +21 -0
  82. package/dist/src/providers/builtin/telegram.js +123 -0
  83. package/dist/src/providers/builtin/telegram.js.map +1 -0
  84. package/dist/src/providers/builtin/whatsapp.d.ts +19 -0
  85. package/dist/src/providers/builtin/whatsapp.js +90 -0
  86. package/dist/src/providers/builtin/whatsapp.js.map +1 -0
  87. package/dist/src/providers/builtin/zalo.d.ts +17 -0
  88. package/dist/src/providers/builtin/zalo.js +70 -0
  89. package/dist/src/providers/builtin/zalo.js.map +1 -0
  90. package/dist/src/providers/catalog.d.ts +69 -0
  91. package/dist/src/providers/catalog.js +95 -0
  92. package/dist/src/providers/catalog.js.map +1 -0
  93. package/dist/src/providers/local-mock.d.ts +41 -0
  94. package/dist/src/providers/local-mock.js +243 -0
  95. package/dist/src/providers/local-mock.js.map +1 -0
  96. package/dist/src/providers/recorder.d.ts +19 -0
  97. package/dist/src/providers/recorder.js +160 -0
  98. package/dist/src/providers/recorder.js.map +1 -0
  99. package/dist/src/providers/registry.d.ts +8 -0
  100. package/dist/src/providers/registry.js +171 -0
  101. package/dist/src/providers/registry.js.map +1 -0
  102. package/dist/src/providers/types.d.ts +94 -0
  103. package/dist/src/providers/types.js +2 -0
  104. package/dist/src/providers/types.js.map +1 -0
  105. package/dist/src/providers/webhook-server.d.ts +12 -0
  106. package/dist/src/providers/webhook-server.js +118 -0
  107. package/dist/src/providers/webhook-server.js.map +1 -0
  108. package/docs/channel-setup.md +181 -0
  109. package/fixtures/examples/crabline.example.yaml +195 -0
  110. package/fixtures/examples/openclaw-bridge.yaml +125 -0
  111. 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
+ ![Crabline deterministic messaging-channel QA](assets/crabline-banner.svg)
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,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "../cli/program.js";
3
+ const exitCode = await runCli(process.argv);
4
+ if (exitCode !== 0) {
5
+ process.exitCode = exitCode;
6
+ }
7
+ //# sourceMappingURL=crabline.js.map
@@ -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,5 @@
1
+ import { Command } from "commander";
2
+ type SetExitCode = (code: number) => void;
3
+ export declare function createProgram(setExitCode?: SetExitCode): Command;
4
+ export declare function runCli(argv: string[]): Promise<number>;
5
+ export {};
@@ -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,6 @@
1
+ import { type ManifestDefinition } from "./schema.js";
2
+ export declare function resolveConfigPath(explicitPath?: string): Promise<string>;
3
+ export declare function loadManifest(configPath?: string): Promise<{
4
+ manifest: ManifestDefinition;
5
+ path: string;
6
+ }>;
@@ -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"}