@openclaw/crabline 0.1.11 → 0.1.13
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/README.md +137 -23
- package/dist/src/bin/crabline.js +3 -1
- package/dist/src/bin/crabline.js.map +1 -1
- package/dist/src/cli/program.d.ts +5 -1
- package/dist/src/cli/program.js +281 -52
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/config/load.js +39 -3
- package/dist/src/config/load.js.map +1 -1
- package/dist/src/config/schema.d.ts +20 -0
- package/dist/src/config/schema.js +163 -35
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/core/errors.d.ts +1 -0
- package/dist/src/core/errors.js +6 -3
- package/dist/src/core/errors.js.map +1 -1
- package/dist/src/core/http-path.d.ts +1 -0
- package/dist/src/core/http-path.js +14 -0
- package/dist/src/core/http-path.js.map +1 -0
- package/dist/src/core/matcher.d.ts +3 -1
- package/dist/src/core/matcher.js +58 -2
- package/dist/src/core/matcher.js.map +1 -1
- package/dist/src/core/nonces.d.ts +2 -0
- package/dist/src/core/nonces.js +11 -1
- package/dist/src/core/nonces.js.map +1 -1
- package/dist/src/core/reporters.d.ts +5 -0
- package/dist/src/core/reporters.js +26 -2
- package/dist/src/core/reporters.js.map +1 -1
- package/dist/src/core/run.d.ts +3 -1
- package/dist/src/core/run.js +178 -66
- package/dist/src/core/run.js.map +1 -1
- package/dist/src/index.d.ts +12 -10
- package/dist/src/index.js +6 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/matrix-ids.d.ts +5 -0
- package/dist/src/matrix-ids.js +83 -0
- package/dist/src/matrix-ids.js.map +1 -0
- package/dist/src/openclaw/artifact-generation.d.ts +7 -6
- package/dist/src/openclaw/artifact-generation.js +385 -41
- package/dist/src/openclaw/artifact-generation.js.map +1 -1
- package/dist/src/openclaw/bridges/discord.d.ts +1 -0
- package/dist/src/openclaw/bridges/discord.js +149 -0
- package/dist/src/openclaw/bridges/discord.js.map +1 -0
- package/dist/src/openclaw/bridges/matrix.js +22 -5
- package/dist/src/openclaw/bridges/matrix.js.map +1 -1
- package/dist/src/openclaw/bridges/mattermost.js +14 -9
- package/dist/src/openclaw/bridges/mattermost.js.map +1 -1
- package/dist/src/openclaw/bridges/probe-response.d.ts +1 -0
- package/dist/src/openclaw/bridges/probe-response.js +5 -0
- package/dist/src/openclaw/bridges/probe-response.js.map +1 -0
- package/dist/src/openclaw/bridges/signal.js +16 -7
- package/dist/src/openclaw/bridges/signal.js.map +1 -1
- package/dist/src/openclaw/bridges/slack.js +117 -24
- package/dist/src/openclaw/bridges/slack.js.map +1 -1
- package/dist/src/openclaw/bridges/telegram.js +46 -20
- package/dist/src/openclaw/bridges/telegram.js.map +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js +33 -17
- package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
- package/dist/src/openclaw/bridges/zalo.js +9 -4
- package/dist/src/openclaw/bridges/zalo.js.map +1 -1
- package/dist/src/openclaw/outbound-contract.js +3 -0
- package/dist/src/openclaw/outbound-contract.js.map +1 -1
- package/dist/src/openclaw/private-file.d.ts +38 -3
- package/dist/src/openclaw/private-file.js +2008 -87
- package/dist/src/openclaw/private-file.js.map +1 -1
- package/dist/src/openclaw/{smoke-lock.d.ts → provider-readiness-lock.d.ts} +14 -5
- package/dist/src/openclaw/provider-readiness-lock.js +1320 -0
- package/dist/src/openclaw/provider-readiness-lock.js.map +1 -0
- package/dist/src/openclaw/shared.d.ts +10 -17
- package/dist/src/openclaw/shared.js +53 -19
- package/dist/src/openclaw/shared.js.map +1 -1
- package/dist/src/openclaw.d.ts +3 -5
- package/dist/src/openclaw.js +245 -60
- package/dist/src/openclaw.js.map +1 -1
- package/dist/src/platform/process-owned-lock.d.ts +11 -0
- package/dist/src/platform/process-owned-lock.js +1698 -0
- package/dist/src/platform/process-owned-lock.js.map +1 -0
- package/dist/src/platform/recorder-directory.d.ts +1 -0
- package/dist/src/platform/recorder-directory.js +14 -0
- package/dist/src/platform/recorder-directory.js.map +1 -0
- package/dist/src/platform/windows-acl.d.ts +18 -0
- package/dist/src/platform/windows-acl.js +1367 -0
- package/dist/src/platform/windows-acl.js.map +1 -0
- package/dist/src/platform/windows-lock-root.d.ts +13 -0
- package/dist/src/platform/windows-lock-root.js +98 -0
- package/dist/src/platform/windows-lock-root.js.map +1 -0
- package/dist/src/providers/builtin/discord.d.ts +4 -0
- package/dist/src/providers/builtin/discord.js +52 -9
- package/dist/src/providers/builtin/discord.js.map +1 -1
- package/dist/src/providers/builtin/external-webhook-auth.d.ts +1 -0
- package/dist/src/providers/builtin/external-webhook-auth.js +38 -1
- package/dist/src/providers/builtin/external-webhook-auth.js.map +1 -1
- package/dist/src/providers/builtin/feishu.d.ts +1 -0
- package/dist/src/providers/builtin/feishu.js +96 -26
- package/dist/src/providers/builtin/feishu.js.map +1 -1
- package/dist/src/providers/builtin/googlechat.d.ts +1 -0
- package/dist/src/providers/builtin/googlechat.js +95 -26
- package/dist/src/providers/builtin/googlechat.js.map +1 -1
- package/dist/src/providers/builtin/imessage.js +60 -17
- package/dist/src/providers/builtin/imessage.js.map +1 -1
- package/dist/src/providers/builtin/loopback.js +88 -35
- package/dist/src/providers/builtin/loopback.js.map +1 -1
- package/dist/src/providers/builtin/matrix.d.ts +2 -2
- package/dist/src/providers/builtin/matrix.js +78 -21
- package/dist/src/providers/builtin/matrix.js.map +1 -1
- package/dist/src/providers/builtin/mattermost.d.ts +11 -7
- package/dist/src/providers/builtin/mattermost.js +138 -24
- package/dist/src/providers/builtin/mattermost.js.map +1 -1
- package/dist/src/providers/builtin/msteams.d.ts +1 -0
- package/dist/src/providers/builtin/msteams.js +118 -38
- package/dist/src/providers/builtin/msteams.js.map +1 -1
- package/dist/src/providers/builtin/native-local-mock.js +4 -4
- package/dist/src/providers/builtin/native-local-mock.js.map +1 -1
- package/dist/src/providers/builtin/script.js +1057 -146
- package/dist/src/providers/builtin/script.js.map +1 -1
- package/dist/src/providers/builtin/slack.d.ts +8 -1
- package/dist/src/providers/builtin/slack.js +327 -24
- package/dist/src/providers/builtin/slack.js.map +1 -1
- package/dist/src/providers/builtin/telegram.d.ts +1 -1
- package/dist/src/providers/builtin/telegram.js +55 -14
- package/dist/src/providers/builtin/telegram.js.map +1 -1
- package/dist/src/providers/builtin/whatsapp.d.ts +4 -3
- package/dist/src/providers/builtin/whatsapp.js +157 -29
- package/dist/src/providers/builtin/whatsapp.js.map +1 -1
- package/dist/src/providers/builtin/zalo.d.ts +5 -2
- package/dist/src/providers/builtin/zalo.js +31 -7
- package/dist/src/providers/builtin/zalo.js.map +1 -1
- package/dist/src/providers/catalog.d.ts +1 -1
- package/dist/src/providers/catalog.js +1 -1
- package/dist/src/providers/catalog.js.map +1 -1
- package/dist/src/providers/local-mock.d.ts +4 -0
- package/dist/src/providers/local-mock.js +162 -35
- package/dist/src/providers/local-mock.js.map +1 -1
- package/dist/src/providers/native-ids.d.ts +2 -0
- package/dist/src/providers/native-ids.js +3 -0
- package/dist/src/providers/native-ids.js.map +1 -1
- package/dist/src/providers/recorder.d.ts +14 -3
- package/dist/src/providers/recorder.js +521 -41
- package/dist/src/providers/recorder.js.map +1 -1
- package/dist/src/providers/registry.d.ts +1 -0
- package/dist/src/providers/registry.js +65 -20
- package/dist/src/providers/registry.js.map +1 -1
- package/dist/src/providers/signed-jwt.d.ts +6 -1
- package/dist/src/providers/signed-jwt.js +241 -50
- package/dist/src/providers/signed-jwt.js.map +1 -1
- package/dist/src/providers/slack-ids.d.ts +1 -0
- package/dist/src/providers/slack-ids.js +5 -0
- package/dist/src/providers/slack-ids.js.map +1 -1
- package/dist/src/providers/target-normalizers.d.ts +9 -1
- package/dist/src/providers/target-normalizers.js +177 -58
- package/dist/src/providers/target-normalizers.js.map +1 -1
- package/dist/src/providers/webhook-server.d.ts +3 -0
- package/dist/src/providers/webhook-server.js +268 -47
- package/dist/src/providers/webhook-server.js.map +1 -1
- package/dist/src/servers/discord.d.ts +40 -0
- package/dist/src/servers/discord.js +842 -0
- package/dist/src/servers/discord.js.map +1 -0
- package/dist/src/servers/http.d.ts +14 -2
- package/dist/src/servers/http.js +229 -58
- package/dist/src/servers/http.js.map +1 -1
- package/dist/src/servers/index.d.ts +10 -4
- package/dist/src/servers/index.js +4 -0
- package/dist/src/servers/index.js.map +1 -1
- package/dist/src/servers/matrix.d.ts +3 -0
- package/dist/src/servers/matrix.js +268 -110
- package/dist/src/servers/matrix.js.map +1 -1
- package/dist/src/servers/mattermost.d.ts +5 -0
- package/dist/src/servers/mattermost.js +389 -91
- package/dist/src/servers/mattermost.js.map +1 -1
- package/dist/src/servers/recorder.d.ts +11 -0
- package/dist/src/servers/recorder.js +1072 -69
- package/dist/src/servers/recorder.js.map +1 -1
- package/dist/src/servers/signal.d.ts +1 -0
- package/dist/src/servers/signal.js +279 -44
- package/dist/src/servers/signal.js.map +1 -1
- package/dist/src/servers/slack.d.ts +15 -0
- package/dist/src/servers/slack.js +270 -54
- package/dist/src/servers/slack.js.map +1 -1
- package/dist/src/servers/telegram-identity.d.ts +6 -0
- package/dist/src/servers/telegram-identity.js +27 -0
- package/dist/src/servers/telegram-identity.js.map +1 -0
- package/dist/src/servers/telegram.d.ts +17 -6
- package/dist/src/servers/telegram.js +1218 -107
- package/dist/src/servers/telegram.js.map +1 -1
- package/dist/src/servers/webhook-target.d.ts +12 -4
- package/dist/src/servers/webhook-target.js +226 -28
- package/dist/src/servers/webhook-target.js.map +1 -1
- package/dist/src/servers/whatsapp-baileys-websocket.d.ts +60 -1
- package/dist/src/servers/whatsapp-baileys-websocket.js +782 -112
- package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
- package/dist/src/servers/whatsapp-jid.d.ts +1 -0
- package/dist/src/servers/whatsapp-jid.js +9 -5
- package/dist/src/servers/whatsapp-jid.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/binary-node.d.ts +1 -0
- package/dist/src/servers/whatsapp-wire/binary-node.js +37 -13
- package/dist/src/servers/whatsapp-wire/binary-node.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/crypto.d.ts +3 -0
- package/dist/src/servers/whatsapp-wire/crypto.js +23 -8
- package/dist/src/servers/whatsapp-wire/crypto.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/handshake.d.ts +9 -3
- package/dist/src/servers/whatsapp-wire/handshake.js +173 -12
- package/dist/src/servers/whatsapp-wire/handshake.js.map +1 -1
- package/dist/src/servers/whatsapp-wire/signal.js +3 -3
- package/dist/src/servers/whatsapp-wire/signal.js.map +1 -1
- package/dist/src/servers/whatsapp.d.ts +7 -1
- package/dist/src/servers/whatsapp.js +149 -25
- package/dist/src/servers/whatsapp.js.map +1 -1
- package/dist/src/servers/zalo.d.ts +1 -0
- package/dist/src/servers/zalo.js +193 -82
- package/dist/src/servers/zalo.js.map +1 -1
- package/docs/channel-setup.md +251 -64
- package/fixtures/examples/crabline.example.yaml +3 -2
- package/package.json +18 -10
- package/dist/src/openclaw/smoke-lock.js +0 -1004
- package/dist/src/openclaw/smoke-lock.js.map +0 -1
- package/fixtures/examples/openclaw-bridge.yaml +0 -125
package/dist/src/cli/program.js
CHANGED
|
@@ -1,18 +1,36 @@
|
|
|
1
|
-
import { Command, CommanderError } from "commander";
|
|
1
|
+
import { Command, CommanderError, Option } from "commander";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { constants as fsConstants } from "node:fs";
|
|
3
|
+
import { constants as fsConstants, createReadStream } from "node:fs";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import nodePath from "node:path";
|
|
6
|
+
import { finished } from "node:stream/promises";
|
|
6
7
|
import { lock } from "proper-lockfile";
|
|
7
8
|
import { loadManifest } from "../config/load.js";
|
|
9
|
+
import { EXIT_CODES } from "../core/exit-codes.js";
|
|
8
10
|
import { createRegistry } from "../providers/registry.js";
|
|
9
|
-
import { formatJson, formatRunResultText, sanitizeTerminalText } from "../core/reporters.js";
|
|
11
|
+
import { formatJson, formatJsonResult, formatRunResultText, sanitizeTerminalText, } from "../core/reporters.js";
|
|
10
12
|
import { assertScriptStdinPayloadSize, computeExitCode, runFixtureCommand, runSuite, } from "../core/run.js";
|
|
11
13
|
import { CrablineError, ensureErrorMessage } from "../core/errors.js";
|
|
12
14
|
import { isCrablineServerChannel, startCrablineServer, } from "../servers/index.js";
|
|
13
15
|
const READY_FILE_LEASE_STALE_MS = 60_000;
|
|
14
16
|
const READY_FILE_LEASE_UPDATE_MS = 1_000;
|
|
17
|
+
const MAX_SERVE_CREDENTIALS_BYTES = 64 * 1024;
|
|
18
|
+
const WATCH_SHUTDOWN_GRACE_MS = 250;
|
|
19
|
+
const WATCH_SHUTDOWN_REACHED = Symbol("watch shutdown reached");
|
|
20
|
+
const SERVE_CREDENTIAL_ENV = [
|
|
21
|
+
["accessToken", "CRABLINE_ACCESS_TOKEN"],
|
|
22
|
+
["adminToken", "CRABLINE_ADMIN_TOKEN"],
|
|
23
|
+
["botToken", "CRABLINE_BOT_TOKEN"],
|
|
24
|
+
["signingSecret", "CRABLINE_SIGNING_SECRET"],
|
|
25
|
+
];
|
|
15
26
|
const SERVE_PARAM_FACTORIES = {
|
|
27
|
+
discord: (shared, commandOptions) => ({
|
|
28
|
+
...shared,
|
|
29
|
+
adminToken: commandOptions.adminToken,
|
|
30
|
+
botToken: commandOptions.botToken,
|
|
31
|
+
botUsername: commandOptions.botUsername,
|
|
32
|
+
channel: "discord",
|
|
33
|
+
}),
|
|
16
34
|
mattermost: (shared, commandOptions) => ({
|
|
17
35
|
...shared,
|
|
18
36
|
adminToken: commandOptions.adminToken,
|
|
@@ -126,9 +144,150 @@ async function writeOutput(stream, value) {
|
|
|
126
144
|
async function print(value) {
|
|
127
145
|
return await writeOutput(process.stdout, `${value}\n`);
|
|
128
146
|
}
|
|
147
|
+
class WatchShutdownDeadlineError extends CrablineError {
|
|
148
|
+
}
|
|
149
|
+
function createWatchShutdownDeadline() {
|
|
150
|
+
let deadlineAt;
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
return {
|
|
153
|
+
async race(operation, operationName) {
|
|
154
|
+
let timer;
|
|
155
|
+
let removeStartListener;
|
|
156
|
+
const deadline = new Promise((_, reject) => {
|
|
157
|
+
const arm = () => {
|
|
158
|
+
timer = setTimeout(() => {
|
|
159
|
+
reject(new WatchShutdownDeadlineError(`Provider ${operationName} did not settle within ${WATCH_SHUTDOWN_GRACE_MS}ms during watch shutdown.`, { kind: "timeout" }));
|
|
160
|
+
}, Math.max(0, deadlineAt - Date.now()));
|
|
161
|
+
};
|
|
162
|
+
if (controller.signal.aborted) {
|
|
163
|
+
arm();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
controller.signal.addEventListener("abort", arm, { once: true });
|
|
167
|
+
removeStartListener = () => controller.signal.removeEventListener("abort", arm);
|
|
168
|
+
});
|
|
169
|
+
try {
|
|
170
|
+
return await Promise.race([Promise.resolve(operation), deadline]);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
removeStartListener?.();
|
|
174
|
+
if (timer) {
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
start() {
|
|
180
|
+
if (deadlineAt !== undefined) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
deadlineAt = Date.now() + WATCH_SHUTDOWN_GRACE_MS;
|
|
184
|
+
controller.abort();
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function containsWatchShutdownDeadline(error, seen = new Set()) {
|
|
189
|
+
if (error instanceof WatchShutdownDeadlineError) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
if (!error || typeof error !== "object" || seen.has(error)) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
seen.add(error);
|
|
196
|
+
if (error instanceof AggregateError) {
|
|
197
|
+
return error.errors.some((entry) => containsWatchShutdownDeadline(entry, seen));
|
|
198
|
+
}
|
|
199
|
+
return "cause" in error && containsWatchShutdownDeadline(error.cause, seen);
|
|
200
|
+
}
|
|
129
201
|
async function withManifest(options, action) {
|
|
130
202
|
return action(await loadManifest(options.config));
|
|
131
203
|
}
|
|
204
|
+
function rejectedSecretOption(flags, optionName, envName) {
|
|
205
|
+
return new Option(flags).hideHelp().argParser(() => {
|
|
206
|
+
throw new CrablineError(`${optionName} no longer accepts credential values in command-line arguments. Use ${envName} or --credentials-fd.`, { kind: "config" });
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function parseCredentialsFd(value) {
|
|
210
|
+
if (!/^[0-9]+$/u.test(value)) {
|
|
211
|
+
throw new CrablineError("Serve credentials file descriptor must be 0 or an integer greater than 2.", { kind: "config" });
|
|
212
|
+
}
|
|
213
|
+
const fd = Number(value);
|
|
214
|
+
if (!Number.isSafeInteger(fd) || fd === 1 || fd === 2 || fd > 2_147_483_647) {
|
|
215
|
+
throw new CrablineError("Serve credentials file descriptor must be 0 or an integer greater than 2.", { kind: "config" });
|
|
216
|
+
}
|
|
217
|
+
return fd;
|
|
218
|
+
}
|
|
219
|
+
async function readBoundedFileDescriptor(fd) {
|
|
220
|
+
const stream = createReadStream("", { autoClose: fd !== 0, fd });
|
|
221
|
+
const chunks = [];
|
|
222
|
+
let totalBytes = 0;
|
|
223
|
+
try {
|
|
224
|
+
for await (const chunk of stream) {
|
|
225
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
226
|
+
totalBytes += buffer.byteLength;
|
|
227
|
+
if (totalBytes > MAX_SERVE_CREDENTIALS_BYTES) {
|
|
228
|
+
throw new CrablineError(`Serve credentials JSON exceeds ${MAX_SERVE_CREDENTIALS_BYTES} bytes.`, { kind: "config" });
|
|
229
|
+
}
|
|
230
|
+
chunks.push(buffer);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
stream.destroy();
|
|
235
|
+
if (error instanceof CrablineError) {
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
throw new CrablineError(`Unable to read serve credentials from file descriptor ${fd}.`, {
|
|
239
|
+
cause: error,
|
|
240
|
+
kind: "config",
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
if (fd !== 0 && !stream.closed) {
|
|
245
|
+
stream.destroy();
|
|
246
|
+
await finished(stream).catch(() => undefined);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
250
|
+
}
|
|
251
|
+
function parseServeCredentialsJson(input) {
|
|
252
|
+
let parsed;
|
|
253
|
+
try {
|
|
254
|
+
parsed = JSON.parse(input);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
throw new CrablineError("Serve credentials input must be valid JSON.", {
|
|
258
|
+
cause: error,
|
|
259
|
+
kind: "config",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
263
|
+
throw new CrablineError("Serve credentials JSON must be an object.", { kind: "config" });
|
|
264
|
+
}
|
|
265
|
+
const values = {};
|
|
266
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
267
|
+
if (!SERVE_CREDENTIAL_ENV.some(([name]) => name === key)) {
|
|
268
|
+
throw new CrablineError("Serve credentials JSON contains unsupported fields.", {
|
|
269
|
+
kind: "config",
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (typeof value !== "string") {
|
|
273
|
+
throw new CrablineError("Serve credentials JSON values must be strings.", {
|
|
274
|
+
kind: "config",
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
values[key] = value;
|
|
278
|
+
}
|
|
279
|
+
return values;
|
|
280
|
+
}
|
|
281
|
+
async function resolveServeCredentials(credentialsFd) {
|
|
282
|
+
const overrides = credentialsFd === undefined
|
|
283
|
+
? {}
|
|
284
|
+
: parseServeCredentialsJson(await readBoundedFileDescriptor(parseCredentialsFd(credentialsFd)));
|
|
285
|
+
return Object.fromEntries(SERVE_CREDENTIAL_ENV.flatMap(([name, envName]) => {
|
|
286
|
+
const override = overrides[name];
|
|
287
|
+
const value = override ?? process.env[envName];
|
|
288
|
+
return value === undefined ? [] : [[name, value]];
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
132
291
|
export function createProgram(setExitCode = (code) => {
|
|
133
292
|
process.exitCode = code;
|
|
134
293
|
}, dependencies = {}) {
|
|
@@ -138,6 +297,13 @@ export function createProgram(setExitCode = (code) => {
|
|
|
138
297
|
const publish = dependencies.publishReadyFile ?? publishReadyFileUnlocked;
|
|
139
298
|
const removeReady = dependencies.removeReadyFile ?? removeReadyFileIfOwned;
|
|
140
299
|
const startServer = dependencies.startServer ?? startCrablineServer;
|
|
300
|
+
const formatCliJson = (value) => {
|
|
301
|
+
const formatted = formatJsonResult(value);
|
|
302
|
+
if (!formatted.ok) {
|
|
303
|
+
setExitCode(EXIT_CODES.FAILURE);
|
|
304
|
+
}
|
|
305
|
+
return formatted.output;
|
|
306
|
+
};
|
|
141
307
|
program
|
|
142
308
|
.exitOverride()
|
|
143
309
|
.name("crabline")
|
|
@@ -162,7 +328,7 @@ export function createProgram(setExitCode = (code) => {
|
|
|
162
328
|
})),
|
|
163
329
|
support: registry.catalog,
|
|
164
330
|
};
|
|
165
|
-
await print(options.json ?
|
|
331
|
+
await print(options.json ? formatCliJson(payload) : renderProvidersText(payload));
|
|
166
332
|
});
|
|
167
333
|
program
|
|
168
334
|
.command("fixtures")
|
|
@@ -171,7 +337,7 @@ export function createProgram(setExitCode = (code) => {
|
|
|
171
337
|
const options = program.opts();
|
|
172
338
|
const { manifest } = await loadManifest(options.config);
|
|
173
339
|
await print(options.json
|
|
174
|
-
?
|
|
340
|
+
? formatCliJson(manifest.fixtures)
|
|
175
341
|
: manifest.fixtures
|
|
176
342
|
.map((fixture) => `${sanitizeTerminalText(fixture.id, true)} ${sanitizeTerminalText(fixture.mode, true)} provider=${sanitizeTerminalText(fixture.provider, true)} target=${sanitizeTerminalText(fixture.target.id, true)}`)
|
|
177
343
|
.join("\n"));
|
|
@@ -194,8 +360,8 @@ export function createProgram(setExitCode = (code) => {
|
|
|
194
360
|
modeOverride: "probe",
|
|
195
361
|
registry,
|
|
196
362
|
});
|
|
197
|
-
await print(options.json ? formatJson(result) : formatRunResultText(result));
|
|
198
363
|
setExitCode(computeExitCode(result));
|
|
364
|
+
await print(options.json ? formatCliJson(result) : formatRunResultText(result));
|
|
199
365
|
});
|
|
200
366
|
for (const mode of ["send", "roundtrip", "agent"]) {
|
|
201
367
|
program
|
|
@@ -212,8 +378,8 @@ export function createProgram(setExitCode = (code) => {
|
|
|
212
378
|
modeOverride: mode,
|
|
213
379
|
registry,
|
|
214
380
|
});
|
|
215
|
-
await print(options.json ? formatJson(result) : formatRunResultText(result));
|
|
216
381
|
setExitCode(computeExitCode(result));
|
|
382
|
+
await print(options.json ? formatCliJson(result) : formatRunResultText(result));
|
|
217
383
|
});
|
|
218
384
|
}
|
|
219
385
|
program
|
|
@@ -229,8 +395,8 @@ export function createProgram(setExitCode = (code) => {
|
|
|
229
395
|
manifestPath: path,
|
|
230
396
|
registry,
|
|
231
397
|
});
|
|
232
|
-
await print(options.json ? formatJson(result) : formatRunResultText(result));
|
|
233
398
|
setExitCode(computeExitCode(result));
|
|
399
|
+
await print(options.json ? formatCliJson(result) : formatRunResultText(result));
|
|
234
400
|
});
|
|
235
401
|
program
|
|
236
402
|
.command("watch <fixtureId>")
|
|
@@ -250,12 +416,18 @@ export function createProgram(setExitCode = (code) => {
|
|
|
250
416
|
});
|
|
251
417
|
}
|
|
252
418
|
const controller = new AbortController();
|
|
419
|
+
const shutdownDeadline = createWatchShutdownDeadline();
|
|
253
420
|
let iterator;
|
|
254
421
|
const stopWatch = onceAsync(async () => {
|
|
422
|
+
shutdownDeadline.start();
|
|
255
423
|
controller.abort();
|
|
256
|
-
|
|
424
|
+
const returnResult = iterator?.return?.();
|
|
425
|
+
if (returnResult) {
|
|
426
|
+
await shutdownDeadline.race(returnResult, "watch iterator return");
|
|
427
|
+
}
|
|
257
428
|
});
|
|
258
429
|
const shutdown = installShutdownHandler(stopWatch);
|
|
430
|
+
const shutdownSettled = shutdown.wait().then(() => WATCH_SHUTDOWN_REACHED);
|
|
259
431
|
const lifecycleErrors = [];
|
|
260
432
|
try {
|
|
261
433
|
const watchContext = {
|
|
@@ -282,14 +454,17 @@ export function createProgram(setExitCode = (code) => {
|
|
|
282
454
|
const watch = provider.watch(watchContext);
|
|
283
455
|
iterator = watch[Symbol.asyncIterator]();
|
|
284
456
|
while (true) {
|
|
285
|
-
const result = await iterator.next();
|
|
457
|
+
const result = await Promise.race([iterator.next(), shutdownSettled]);
|
|
458
|
+
if (typeof result === "symbol") {
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
286
461
|
if (result.done) {
|
|
287
462
|
break;
|
|
288
463
|
}
|
|
289
464
|
const message = result.value;
|
|
290
|
-
const written = await print(options.json
|
|
291
|
-
?
|
|
292
|
-
: `${sanitizeTerminalText(message.sentAt, true)} ${sanitizeTerminalText(message.author, true)} ${sanitizeTerminalText(message.text, true)}`);
|
|
465
|
+
const written = await shutdownDeadline.race(print(options.json
|
|
466
|
+
? formatCliJson(message)
|
|
467
|
+
: `${sanitizeTerminalText(message.sentAt, true)} ${sanitizeTerminalText(message.author, true)} ${sanitizeTerminalText(message.text, true)}`), "watch output write");
|
|
293
468
|
if (!written) {
|
|
294
469
|
break;
|
|
295
470
|
}
|
|
@@ -307,7 +482,10 @@ export function createProgram(setExitCode = (code) => {
|
|
|
307
482
|
}
|
|
308
483
|
}
|
|
309
484
|
try {
|
|
310
|
-
|
|
485
|
+
const cleanup = provider.cleanup?.();
|
|
486
|
+
if (cleanup) {
|
|
487
|
+
await shutdownDeadline.race(cleanup, "cleanup");
|
|
488
|
+
}
|
|
311
489
|
}
|
|
312
490
|
catch (error) {
|
|
313
491
|
if (!lifecycleErrors.includes(error)) {
|
|
@@ -327,17 +505,24 @@ export function createProgram(setExitCode = (code) => {
|
|
|
327
505
|
.description("Start a local provider server that OpenClaw live adapters can target")
|
|
328
506
|
.option("--host <host>", "Bind host", "127.0.0.1")
|
|
329
507
|
.option("--port <port>", "Bind port", "0")
|
|
330
|
-
.option("--admin-token <token>", "Admin token for inbound test messages (or CRABLINE_ADMIN_TOKEN)")
|
|
331
508
|
.option("--account <number>", "Signal account number")
|
|
332
|
-
.option("--
|
|
333
|
-
.option("--
|
|
334
|
-
.option("--bot-username <username>", "Mattermost, Telegram, or Zalo bot username", "crabline_bot")
|
|
509
|
+
.option("--bot-username <username>", "Discord, Mattermost, Telegram, or Zalo bot username", "crabline_bot")
|
|
510
|
+
.option("--credentials-fd <fd>", "Read JSON credentials from fd 0 or an inherited fd; values override CRABLINE_* env")
|
|
335
511
|
.option("--recorder <path>", "JSONL recorder path")
|
|
336
512
|
.option("--ready-file <path>", "Write the server runtime manifest to this path")
|
|
337
513
|
.option("--self-jid <jid>", "WhatsApp self JID")
|
|
338
514
|
.option("--show-secrets", "Print provider credentials in text output", false)
|
|
339
|
-
.option("--signing-secret <secret>", "Slack signing secret (or CRABLINE_SIGNING_SECRET)")
|
|
340
515
|
.option("--once", "Start, print the runtime manifest, and stop immediately", false)
|
|
516
|
+
.addOption(rejectedSecretOption("--access-token <token>", "--access-token", "CRABLINE_ACCESS_TOKEN"))
|
|
517
|
+
.addOption(rejectedSecretOption("--admin-token <token>", "--admin-token", "CRABLINE_ADMIN_TOKEN"))
|
|
518
|
+
.addOption(rejectedSecretOption("--bot-token <token>", "--bot-token", "CRABLINE_BOT_TOKEN"))
|
|
519
|
+
.addOption(rejectedSecretOption("--signing-secret <secret>", "--signing-secret", "CRABLINE_SIGNING_SECRET"))
|
|
520
|
+
.addHelpText("after", [
|
|
521
|
+
"",
|
|
522
|
+
"Credential JSON fields: accessToken, adminToken, botToken, signingSecret",
|
|
523
|
+
"Environment fallbacks: CRABLINE_ACCESS_TOKEN, CRABLINE_ADMIN_TOKEN,",
|
|
524
|
+
" CRABLINE_BOT_TOKEN, CRABLINE_SIGNING_SECRET",
|
|
525
|
+
].join("\n"))
|
|
341
526
|
.action(async (provider, commandOptions) => {
|
|
342
527
|
const options = program.opts();
|
|
343
528
|
if (!isCrablineServerChannel(provider)) {
|
|
@@ -345,10 +530,6 @@ export function createProgram(setExitCode = (code) => {
|
|
|
345
530
|
kind: "config",
|
|
346
531
|
});
|
|
347
532
|
}
|
|
348
|
-
commandOptions.accessToken ??= process.env.CRABLINE_ACCESS_TOKEN;
|
|
349
|
-
commandOptions.adminToken ??= process.env.CRABLINE_ADMIN_TOKEN;
|
|
350
|
-
commandOptions.botToken ??= process.env.CRABLINE_BOT_TOKEN;
|
|
351
|
-
commandOptions.signingSecret ??= process.env.CRABLINE_SIGNING_SECRET;
|
|
352
533
|
if (!/^[0-9]+$/u.test(commandOptions.port)) {
|
|
353
534
|
throw new CrablineError(`Invalid local server port: ${commandOptions.port}`, {
|
|
354
535
|
kind: "config",
|
|
@@ -366,6 +547,11 @@ export function createProgram(setExitCode = (code) => {
|
|
|
366
547
|
kind: "config",
|
|
367
548
|
});
|
|
368
549
|
}
|
|
550
|
+
const credentials = await resolveServeCredentials(commandOptions.credentialsFd);
|
|
551
|
+
commandOptions.accessToken = credentials.accessToken;
|
|
552
|
+
commandOptions.adminToken = credentials.adminToken;
|
|
553
|
+
commandOptions.botToken = credentials.botToken;
|
|
554
|
+
commandOptions.signingSecret = credentials.signingSecret;
|
|
369
555
|
let server;
|
|
370
556
|
let startupSettled = false;
|
|
371
557
|
let settleStartup;
|
|
@@ -389,14 +575,41 @@ export function createProgram(setExitCode = (code) => {
|
|
|
389
575
|
settlePublication?.();
|
|
390
576
|
}
|
|
391
577
|
};
|
|
578
|
+
let releaseReadyLease;
|
|
579
|
+
let publishedReady;
|
|
392
580
|
const close = onceAsync(async () => {
|
|
393
581
|
await startup;
|
|
394
582
|
await publication;
|
|
395
|
-
|
|
583
|
+
const lifecycleErrors = [];
|
|
584
|
+
if (commandOptions.readyFile && publishedReady) {
|
|
585
|
+
try {
|
|
586
|
+
await removeReady(commandOptions.readyFile, publishedReady.contents, publishedReady.identity);
|
|
587
|
+
}
|
|
588
|
+
catch (error) {
|
|
589
|
+
lifecycleErrors.push(error);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
let serverClosed = false;
|
|
593
|
+
try {
|
|
594
|
+
await server?.close();
|
|
595
|
+
serverClosed = true;
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
lifecycleErrors.push(error);
|
|
599
|
+
}
|
|
600
|
+
if (serverClosed && releaseReadyLease) {
|
|
601
|
+
try {
|
|
602
|
+
await releaseReadyLease();
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
lifecycleErrors.push(error);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (lifecycleErrors.length > 0) {
|
|
609
|
+
throw combineLifecycleErrors(lifecycleErrors, "Crabline server shutdown failed.");
|
|
610
|
+
}
|
|
396
611
|
});
|
|
397
612
|
const shutdown = installShutdownHandler(close);
|
|
398
|
-
let releaseReadyLease;
|
|
399
|
-
let publishedReady;
|
|
400
613
|
let actionFailed = false;
|
|
401
614
|
let actionError;
|
|
402
615
|
try {
|
|
@@ -424,8 +637,13 @@ export function createProgram(setExitCode = (code) => {
|
|
|
424
637
|
await shutdown.wait();
|
|
425
638
|
}
|
|
426
639
|
else {
|
|
640
|
+
let outputWritten = true;
|
|
427
641
|
try {
|
|
428
|
-
const
|
|
642
|
+
const formatted = formatJsonResult(server.manifest);
|
|
643
|
+
if (!formatted.ok) {
|
|
644
|
+
throw new Error("Unable to serialize server manifest.");
|
|
645
|
+
}
|
|
646
|
+
const payload = formatted.output;
|
|
429
647
|
if (commandOptions.readyFile) {
|
|
430
648
|
const readyContents = `${payload}\n`;
|
|
431
649
|
publishedReady = {
|
|
@@ -433,14 +651,25 @@ export function createProgram(setExitCode = (code) => {
|
|
|
433
651
|
identity: await publish(commandOptions.readyFile, readyContents),
|
|
434
652
|
};
|
|
435
653
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
654
|
+
if (shutdown.requested()) {
|
|
655
|
+
outputWritten = false;
|
|
656
|
+
finishPublication();
|
|
657
|
+
await shutdown.wait();
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
outputWritten = await print(options.json
|
|
661
|
+
? payload
|
|
662
|
+
: renderServeText(server.manifest, commandOptions.showSecrets === true));
|
|
663
|
+
if (!outputWritten) {
|
|
664
|
+
finishPublication();
|
|
665
|
+
await close();
|
|
666
|
+
}
|
|
667
|
+
}
|
|
439
668
|
}
|
|
440
669
|
finally {
|
|
441
670
|
finishPublication();
|
|
442
671
|
}
|
|
443
|
-
if (shutdown.requested() || !commandOptions.once) {
|
|
672
|
+
if (outputWritten && (shutdown.requested() || !commandOptions.once)) {
|
|
444
673
|
await shutdown.wait();
|
|
445
674
|
}
|
|
446
675
|
}
|
|
@@ -453,23 +682,12 @@ export function createProgram(setExitCode = (code) => {
|
|
|
453
682
|
finishStartup();
|
|
454
683
|
finishPublication();
|
|
455
684
|
const cleanupErrors = [];
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
]
|
|
463
|
-
: []),
|
|
464
|
-
...(releaseReadyLease ? [releaseReadyLease] : []),
|
|
465
|
-
]) {
|
|
466
|
-
try {
|
|
467
|
-
await cleanup();
|
|
468
|
-
}
|
|
469
|
-
catch (error) {
|
|
470
|
-
if (!actionFailed || error !== actionError) {
|
|
471
|
-
cleanupErrors.push(error);
|
|
472
|
-
}
|
|
685
|
+
try {
|
|
686
|
+
await close();
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
if (!actionFailed || error !== actionError) {
|
|
690
|
+
cleanupErrors.push(error);
|
|
473
691
|
}
|
|
474
692
|
}
|
|
475
693
|
shutdown.dispose();
|
|
@@ -486,8 +704,8 @@ export function createProgram(setExitCode = (code) => {
|
|
|
486
704
|
const findings = diagnose(manifest);
|
|
487
705
|
const ok = findings.length === 0;
|
|
488
706
|
const payload = { findings, ok };
|
|
489
|
-
await print(options.json ? formatJson(payload) : ok ? "doctor ok" : findings.join("\n"));
|
|
490
707
|
setExitCode(ok ? 0 : 10);
|
|
708
|
+
await print(options.json ? formatCliJson(payload) : ok ? "doctor ok" : findings.join("\n"));
|
|
491
709
|
});
|
|
492
710
|
return program;
|
|
493
711
|
}
|
|
@@ -732,6 +950,15 @@ function renderServeText(manifest, showSecrets) {
|
|
|
732
950
|
}
|
|
733
951
|
function renderServeProviderFields(manifest, showSecrets) {
|
|
734
952
|
const secret = (value) => (showSecrets ? value : "<redacted>");
|
|
953
|
+
if (manifest.provider === "discord") {
|
|
954
|
+
return [
|
|
955
|
+
` adminToken: ${secret(manifest.adminToken)}`,
|
|
956
|
+
` botToken: ${secret(manifest.botToken)}`,
|
|
957
|
+
` applicationId: ${manifest.applicationId}`,
|
|
958
|
+
` botUserId: ${manifest.botUserId}`,
|
|
959
|
+
` gateway: ${manifest.endpoints.gatewayUrl}`,
|
|
960
|
+
];
|
|
961
|
+
}
|
|
735
962
|
if (manifest.provider === "mattermost") {
|
|
736
963
|
return [
|
|
737
964
|
` adminToken: ${secret(manifest.adminToken)}`,
|
|
@@ -773,7 +1000,6 @@ function renderServeProviderFields(manifest, showSecrets) {
|
|
|
773
1000
|
` baileysWebSocket: ${baileysWebSocketUrl}`,
|
|
774
1001
|
` messages: ${manifest.endpoints.messagesUrl}`,
|
|
775
1002
|
` phoneNumber: ${manifest.endpoints.phoneNumberUrl}`,
|
|
776
|
-
` status: ${manifest.endpoints.statusUrl}`,
|
|
777
1003
|
` selfJid: ${manifest.selfJid}`,
|
|
778
1004
|
];
|
|
779
1005
|
}
|
|
@@ -842,11 +1068,11 @@ function diagnose(manifest) {
|
|
|
842
1068
|
}
|
|
843
1069
|
return findings;
|
|
844
1070
|
}
|
|
845
|
-
export async function runCli(argv) {
|
|
1071
|
+
export async function runCli(argv, options = {}) {
|
|
846
1072
|
let exitCode = 0;
|
|
847
1073
|
const program = createProgram((code) => {
|
|
848
1074
|
exitCode = code;
|
|
849
|
-
});
|
|
1075
|
+
}, options.dependencies);
|
|
850
1076
|
const parserErrors = [];
|
|
851
1077
|
const parserOutput = [];
|
|
852
1078
|
const bufferParserErrors = (command) => {
|
|
@@ -896,6 +1122,9 @@ export async function runCli(argv) {
|
|
|
896
1122
|
else {
|
|
897
1123
|
await writeOutput(process.stderr, `${ensureErrorMessage(error)}\n`);
|
|
898
1124
|
}
|
|
1125
|
+
if (options.forceExit && containsWatchShutdownDeadline(error)) {
|
|
1126
|
+
options.forceExit(errorExitCode);
|
|
1127
|
+
}
|
|
899
1128
|
return errorExitCode;
|
|
900
1129
|
}
|
|
901
1130
|
}
|