@openclaw/crabline 0.1.11 → 0.1.12
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 +99 -12
- 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 +264 -50
- 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/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 -2
- package/dist/src/openclaw/artifact-generation.js +399 -27
- package/dist/src/openclaw/artifact-generation.js.map +1 -1
- package/dist/src/openclaw/bridges/matrix.js +21 -4
- package/dist/src/openclaw/bridges/matrix.js.map +1 -1
- package/dist/src/openclaw/bridges/mattermost.js +13 -8
- 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 +15 -6
- package/dist/src/openclaw/bridges/signal.js.map +1 -1
- package/dist/src/openclaw/bridges/slack.js +116 -23
- package/dist/src/openclaw/bridges/slack.js.map +1 -1
- package/dist/src/openclaw/bridges/telegram.js +45 -19
- package/dist/src/openclaw/bridges/telegram.js.map +1 -1
- package/dist/src/openclaw/bridges/whatsapp.js +32 -16
- package/dist/src/openclaw/bridges/whatsapp.js.map +1 -1
- package/dist/src/openclaw/bridges/zalo.js +8 -3
- package/dist/src/openclaw/bridges/zalo.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/shared.d.ts +1 -0
- package/dist/src/openclaw/shared.js +48 -13
- package/dist/src/openclaw/shared.js.map +1 -1
- package/dist/src/openclaw/smoke-lock.d.ts +9 -0
- package/dist/src/openclaw/smoke-lock.js +626 -172
- package/dist/src/openclaw/smoke-lock.js.map +1 -1
- package/dist/src/openclaw.js +225 -42
- 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/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/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 +259 -51
- 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 +1169 -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 -0
- package/dist/src/servers/whatsapp.js +149 -24
- 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 +185 -43
- package/fixtures/examples/crabline.example.yaml +3 -2
- package/package.json +18 -10
- package/fixtures/examples/openclaw-bridge.yaml +0 -125
package/dist/src/cli/program.js
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
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 = {
|
|
16
27
|
mattermost: (shared, commandOptions) => ({
|
|
17
28
|
...shared,
|
|
@@ -126,9 +137,150 @@ async function writeOutput(stream, value) {
|
|
|
126
137
|
async function print(value) {
|
|
127
138
|
return await writeOutput(process.stdout, `${value}\n`);
|
|
128
139
|
}
|
|
140
|
+
class WatchShutdownDeadlineError extends CrablineError {
|
|
141
|
+
}
|
|
142
|
+
function createWatchShutdownDeadline() {
|
|
143
|
+
let deadlineAt;
|
|
144
|
+
const controller = new AbortController();
|
|
145
|
+
return {
|
|
146
|
+
async race(operation, operationName) {
|
|
147
|
+
let timer;
|
|
148
|
+
let removeStartListener;
|
|
149
|
+
const deadline = new Promise((_, reject) => {
|
|
150
|
+
const arm = () => {
|
|
151
|
+
timer = setTimeout(() => {
|
|
152
|
+
reject(new WatchShutdownDeadlineError(`Provider ${operationName} did not settle within ${WATCH_SHUTDOWN_GRACE_MS}ms during watch shutdown.`, { kind: "timeout" }));
|
|
153
|
+
}, Math.max(0, deadlineAt - Date.now()));
|
|
154
|
+
};
|
|
155
|
+
if (controller.signal.aborted) {
|
|
156
|
+
arm();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
controller.signal.addEventListener("abort", arm, { once: true });
|
|
160
|
+
removeStartListener = () => controller.signal.removeEventListener("abort", arm);
|
|
161
|
+
});
|
|
162
|
+
try {
|
|
163
|
+
return await Promise.race([Promise.resolve(operation), deadline]);
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
removeStartListener?.();
|
|
167
|
+
if (timer) {
|
|
168
|
+
clearTimeout(timer);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
start() {
|
|
173
|
+
if (deadlineAt !== undefined) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
deadlineAt = Date.now() + WATCH_SHUTDOWN_GRACE_MS;
|
|
177
|
+
controller.abort();
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function containsWatchShutdownDeadline(error, seen = new Set()) {
|
|
182
|
+
if (error instanceof WatchShutdownDeadlineError) {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
if (!error || typeof error !== "object" || seen.has(error)) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
seen.add(error);
|
|
189
|
+
if (error instanceof AggregateError) {
|
|
190
|
+
return error.errors.some((entry) => containsWatchShutdownDeadline(entry, seen));
|
|
191
|
+
}
|
|
192
|
+
return "cause" in error && containsWatchShutdownDeadline(error.cause, seen);
|
|
193
|
+
}
|
|
129
194
|
async function withManifest(options, action) {
|
|
130
195
|
return action(await loadManifest(options.config));
|
|
131
196
|
}
|
|
197
|
+
function rejectedSecretOption(flags, optionName, envName) {
|
|
198
|
+
return new Option(flags).hideHelp().argParser(() => {
|
|
199
|
+
throw new CrablineError(`${optionName} no longer accepts credential values in command-line arguments. Use ${envName} or --credentials-fd.`, { kind: "config" });
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function parseCredentialsFd(value) {
|
|
203
|
+
if (!/^[0-9]+$/u.test(value)) {
|
|
204
|
+
throw new CrablineError("Serve credentials file descriptor must be 0 or an integer greater than 2.", { kind: "config" });
|
|
205
|
+
}
|
|
206
|
+
const fd = Number(value);
|
|
207
|
+
if (!Number.isSafeInteger(fd) || fd === 1 || fd === 2 || fd > 2_147_483_647) {
|
|
208
|
+
throw new CrablineError("Serve credentials file descriptor must be 0 or an integer greater than 2.", { kind: "config" });
|
|
209
|
+
}
|
|
210
|
+
return fd;
|
|
211
|
+
}
|
|
212
|
+
async function readBoundedFileDescriptor(fd) {
|
|
213
|
+
const stream = createReadStream("", { autoClose: fd !== 0, fd });
|
|
214
|
+
const chunks = [];
|
|
215
|
+
let totalBytes = 0;
|
|
216
|
+
try {
|
|
217
|
+
for await (const chunk of stream) {
|
|
218
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
219
|
+
totalBytes += buffer.byteLength;
|
|
220
|
+
if (totalBytes > MAX_SERVE_CREDENTIALS_BYTES) {
|
|
221
|
+
throw new CrablineError(`Serve credentials JSON exceeds ${MAX_SERVE_CREDENTIALS_BYTES} bytes.`, { kind: "config" });
|
|
222
|
+
}
|
|
223
|
+
chunks.push(buffer);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
stream.destroy();
|
|
228
|
+
if (error instanceof CrablineError) {
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
throw new CrablineError(`Unable to read serve credentials from file descriptor ${fd}.`, {
|
|
232
|
+
cause: error,
|
|
233
|
+
kind: "config",
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
if (fd !== 0 && !stream.closed) {
|
|
238
|
+
stream.destroy();
|
|
239
|
+
await finished(stream).catch(() => undefined);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
243
|
+
}
|
|
244
|
+
function parseServeCredentialsJson(input) {
|
|
245
|
+
let parsed;
|
|
246
|
+
try {
|
|
247
|
+
parsed = JSON.parse(input);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
throw new CrablineError("Serve credentials input must be valid JSON.", {
|
|
251
|
+
cause: error,
|
|
252
|
+
kind: "config",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
256
|
+
throw new CrablineError("Serve credentials JSON must be an object.", { kind: "config" });
|
|
257
|
+
}
|
|
258
|
+
const values = {};
|
|
259
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
260
|
+
if (!SERVE_CREDENTIAL_ENV.some(([name]) => name === key)) {
|
|
261
|
+
throw new CrablineError("Serve credentials JSON contains unsupported fields.", {
|
|
262
|
+
kind: "config",
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
if (typeof value !== "string") {
|
|
266
|
+
throw new CrablineError("Serve credentials JSON values must be strings.", {
|
|
267
|
+
kind: "config",
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
values[key] = value;
|
|
271
|
+
}
|
|
272
|
+
return values;
|
|
273
|
+
}
|
|
274
|
+
async function resolveServeCredentials(credentialsFd) {
|
|
275
|
+
const overrides = credentialsFd === undefined
|
|
276
|
+
? {}
|
|
277
|
+
: parseServeCredentialsJson(await readBoundedFileDescriptor(parseCredentialsFd(credentialsFd)));
|
|
278
|
+
return Object.fromEntries(SERVE_CREDENTIAL_ENV.flatMap(([name, envName]) => {
|
|
279
|
+
const override = overrides[name];
|
|
280
|
+
const value = override ?? process.env[envName];
|
|
281
|
+
return value === undefined ? [] : [[name, value]];
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
132
284
|
export function createProgram(setExitCode = (code) => {
|
|
133
285
|
process.exitCode = code;
|
|
134
286
|
}, dependencies = {}) {
|
|
@@ -138,6 +290,13 @@ export function createProgram(setExitCode = (code) => {
|
|
|
138
290
|
const publish = dependencies.publishReadyFile ?? publishReadyFileUnlocked;
|
|
139
291
|
const removeReady = dependencies.removeReadyFile ?? removeReadyFileIfOwned;
|
|
140
292
|
const startServer = dependencies.startServer ?? startCrablineServer;
|
|
293
|
+
const formatCliJson = (value) => {
|
|
294
|
+
const formatted = formatJsonResult(value);
|
|
295
|
+
if (!formatted.ok) {
|
|
296
|
+
setExitCode(EXIT_CODES.FAILURE);
|
|
297
|
+
}
|
|
298
|
+
return formatted.output;
|
|
299
|
+
};
|
|
141
300
|
program
|
|
142
301
|
.exitOverride()
|
|
143
302
|
.name("crabline")
|
|
@@ -162,7 +321,7 @@ export function createProgram(setExitCode = (code) => {
|
|
|
162
321
|
})),
|
|
163
322
|
support: registry.catalog,
|
|
164
323
|
};
|
|
165
|
-
await print(options.json ?
|
|
324
|
+
await print(options.json ? formatCliJson(payload) : renderProvidersText(payload));
|
|
166
325
|
});
|
|
167
326
|
program
|
|
168
327
|
.command("fixtures")
|
|
@@ -171,7 +330,7 @@ export function createProgram(setExitCode = (code) => {
|
|
|
171
330
|
const options = program.opts();
|
|
172
331
|
const { manifest } = await loadManifest(options.config);
|
|
173
332
|
await print(options.json
|
|
174
|
-
?
|
|
333
|
+
? formatCliJson(manifest.fixtures)
|
|
175
334
|
: manifest.fixtures
|
|
176
335
|
.map((fixture) => `${sanitizeTerminalText(fixture.id, true)} ${sanitizeTerminalText(fixture.mode, true)} provider=${sanitizeTerminalText(fixture.provider, true)} target=${sanitizeTerminalText(fixture.target.id, true)}`)
|
|
177
336
|
.join("\n"));
|
|
@@ -194,8 +353,8 @@ export function createProgram(setExitCode = (code) => {
|
|
|
194
353
|
modeOverride: "probe",
|
|
195
354
|
registry,
|
|
196
355
|
});
|
|
197
|
-
await print(options.json ? formatJson(result) : formatRunResultText(result));
|
|
198
356
|
setExitCode(computeExitCode(result));
|
|
357
|
+
await print(options.json ? formatCliJson(result) : formatRunResultText(result));
|
|
199
358
|
});
|
|
200
359
|
for (const mode of ["send", "roundtrip", "agent"]) {
|
|
201
360
|
program
|
|
@@ -212,8 +371,8 @@ export function createProgram(setExitCode = (code) => {
|
|
|
212
371
|
modeOverride: mode,
|
|
213
372
|
registry,
|
|
214
373
|
});
|
|
215
|
-
await print(options.json ? formatJson(result) : formatRunResultText(result));
|
|
216
374
|
setExitCode(computeExitCode(result));
|
|
375
|
+
await print(options.json ? formatCliJson(result) : formatRunResultText(result));
|
|
217
376
|
});
|
|
218
377
|
}
|
|
219
378
|
program
|
|
@@ -229,8 +388,8 @@ export function createProgram(setExitCode = (code) => {
|
|
|
229
388
|
manifestPath: path,
|
|
230
389
|
registry,
|
|
231
390
|
});
|
|
232
|
-
await print(options.json ? formatJson(result) : formatRunResultText(result));
|
|
233
391
|
setExitCode(computeExitCode(result));
|
|
392
|
+
await print(options.json ? formatCliJson(result) : formatRunResultText(result));
|
|
234
393
|
});
|
|
235
394
|
program
|
|
236
395
|
.command("watch <fixtureId>")
|
|
@@ -250,12 +409,18 @@ export function createProgram(setExitCode = (code) => {
|
|
|
250
409
|
});
|
|
251
410
|
}
|
|
252
411
|
const controller = new AbortController();
|
|
412
|
+
const shutdownDeadline = createWatchShutdownDeadline();
|
|
253
413
|
let iterator;
|
|
254
414
|
const stopWatch = onceAsync(async () => {
|
|
415
|
+
shutdownDeadline.start();
|
|
255
416
|
controller.abort();
|
|
256
|
-
|
|
417
|
+
const returnResult = iterator?.return?.();
|
|
418
|
+
if (returnResult) {
|
|
419
|
+
await shutdownDeadline.race(returnResult, "watch iterator return");
|
|
420
|
+
}
|
|
257
421
|
});
|
|
258
422
|
const shutdown = installShutdownHandler(stopWatch);
|
|
423
|
+
const shutdownSettled = shutdown.wait().then(() => WATCH_SHUTDOWN_REACHED);
|
|
259
424
|
const lifecycleErrors = [];
|
|
260
425
|
try {
|
|
261
426
|
const watchContext = {
|
|
@@ -282,14 +447,17 @@ export function createProgram(setExitCode = (code) => {
|
|
|
282
447
|
const watch = provider.watch(watchContext);
|
|
283
448
|
iterator = watch[Symbol.asyncIterator]();
|
|
284
449
|
while (true) {
|
|
285
|
-
const result = await iterator.next();
|
|
450
|
+
const result = await Promise.race([iterator.next(), shutdownSettled]);
|
|
451
|
+
if (typeof result === "symbol") {
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
286
454
|
if (result.done) {
|
|
287
455
|
break;
|
|
288
456
|
}
|
|
289
457
|
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)}`);
|
|
458
|
+
const written = await shutdownDeadline.race(print(options.json
|
|
459
|
+
? formatCliJson(message)
|
|
460
|
+
: `${sanitizeTerminalText(message.sentAt, true)} ${sanitizeTerminalText(message.author, true)} ${sanitizeTerminalText(message.text, true)}`), "watch output write");
|
|
293
461
|
if (!written) {
|
|
294
462
|
break;
|
|
295
463
|
}
|
|
@@ -307,7 +475,10 @@ export function createProgram(setExitCode = (code) => {
|
|
|
307
475
|
}
|
|
308
476
|
}
|
|
309
477
|
try {
|
|
310
|
-
|
|
478
|
+
const cleanup = provider.cleanup?.();
|
|
479
|
+
if (cleanup) {
|
|
480
|
+
await shutdownDeadline.race(cleanup, "cleanup");
|
|
481
|
+
}
|
|
311
482
|
}
|
|
312
483
|
catch (error) {
|
|
313
484
|
if (!lifecycleErrors.includes(error)) {
|
|
@@ -327,17 +498,24 @@ export function createProgram(setExitCode = (code) => {
|
|
|
327
498
|
.description("Start a local provider server that OpenClaw live adapters can target")
|
|
328
499
|
.option("--host <host>", "Bind host", "127.0.0.1")
|
|
329
500
|
.option("--port <port>", "Bind port", "0")
|
|
330
|
-
.option("--admin-token <token>", "Admin token for inbound test messages (or CRABLINE_ADMIN_TOKEN)")
|
|
331
501
|
.option("--account <number>", "Signal account number")
|
|
332
|
-
.option("--access-token <token>", "Matrix or WhatsApp access token (or CRABLINE_ACCESS_TOKEN)")
|
|
333
|
-
.option("--bot-token <token>", "Mattermost, Slack, Telegram, or Zalo bot token (or CRABLINE_BOT_TOKEN)")
|
|
334
502
|
.option("--bot-username <username>", "Mattermost, Telegram, or Zalo bot username", "crabline_bot")
|
|
503
|
+
.option("--credentials-fd <fd>", "Read JSON credentials from fd 0 or an inherited fd; values override CRABLINE_* env")
|
|
335
504
|
.option("--recorder <path>", "JSONL recorder path")
|
|
336
505
|
.option("--ready-file <path>", "Write the server runtime manifest to this path")
|
|
337
506
|
.option("--self-jid <jid>", "WhatsApp self JID")
|
|
338
507
|
.option("--show-secrets", "Print provider credentials in text output", false)
|
|
339
|
-
.option("--signing-secret <secret>", "Slack signing secret (or CRABLINE_SIGNING_SECRET)")
|
|
340
508
|
.option("--once", "Start, print the runtime manifest, and stop immediately", false)
|
|
509
|
+
.addOption(rejectedSecretOption("--access-token <token>", "--access-token", "CRABLINE_ACCESS_TOKEN"))
|
|
510
|
+
.addOption(rejectedSecretOption("--admin-token <token>", "--admin-token", "CRABLINE_ADMIN_TOKEN"))
|
|
511
|
+
.addOption(rejectedSecretOption("--bot-token <token>", "--bot-token", "CRABLINE_BOT_TOKEN"))
|
|
512
|
+
.addOption(rejectedSecretOption("--signing-secret <secret>", "--signing-secret", "CRABLINE_SIGNING_SECRET"))
|
|
513
|
+
.addHelpText("after", [
|
|
514
|
+
"",
|
|
515
|
+
"Credential JSON fields: accessToken, adminToken, botToken, signingSecret",
|
|
516
|
+
"Environment fallbacks: CRABLINE_ACCESS_TOKEN, CRABLINE_ADMIN_TOKEN,",
|
|
517
|
+
" CRABLINE_BOT_TOKEN, CRABLINE_SIGNING_SECRET",
|
|
518
|
+
].join("\n"))
|
|
341
519
|
.action(async (provider, commandOptions) => {
|
|
342
520
|
const options = program.opts();
|
|
343
521
|
if (!isCrablineServerChannel(provider)) {
|
|
@@ -345,10 +523,6 @@ export function createProgram(setExitCode = (code) => {
|
|
|
345
523
|
kind: "config",
|
|
346
524
|
});
|
|
347
525
|
}
|
|
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
526
|
if (!/^[0-9]+$/u.test(commandOptions.port)) {
|
|
353
527
|
throw new CrablineError(`Invalid local server port: ${commandOptions.port}`, {
|
|
354
528
|
kind: "config",
|
|
@@ -366,6 +540,11 @@ export function createProgram(setExitCode = (code) => {
|
|
|
366
540
|
kind: "config",
|
|
367
541
|
});
|
|
368
542
|
}
|
|
543
|
+
const credentials = await resolveServeCredentials(commandOptions.credentialsFd);
|
|
544
|
+
commandOptions.accessToken = credentials.accessToken;
|
|
545
|
+
commandOptions.adminToken = credentials.adminToken;
|
|
546
|
+
commandOptions.botToken = credentials.botToken;
|
|
547
|
+
commandOptions.signingSecret = credentials.signingSecret;
|
|
369
548
|
let server;
|
|
370
549
|
let startupSettled = false;
|
|
371
550
|
let settleStartup;
|
|
@@ -389,14 +568,41 @@ export function createProgram(setExitCode = (code) => {
|
|
|
389
568
|
settlePublication?.();
|
|
390
569
|
}
|
|
391
570
|
};
|
|
571
|
+
let releaseReadyLease;
|
|
572
|
+
let publishedReady;
|
|
392
573
|
const close = onceAsync(async () => {
|
|
393
574
|
await startup;
|
|
394
575
|
await publication;
|
|
395
|
-
|
|
576
|
+
const lifecycleErrors = [];
|
|
577
|
+
if (commandOptions.readyFile && publishedReady) {
|
|
578
|
+
try {
|
|
579
|
+
await removeReady(commandOptions.readyFile, publishedReady.contents, publishedReady.identity);
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
lifecycleErrors.push(error);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
let serverClosed = false;
|
|
586
|
+
try {
|
|
587
|
+
await server?.close();
|
|
588
|
+
serverClosed = true;
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
lifecycleErrors.push(error);
|
|
592
|
+
}
|
|
593
|
+
if (serverClosed && releaseReadyLease) {
|
|
594
|
+
try {
|
|
595
|
+
await releaseReadyLease();
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
lifecycleErrors.push(error);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (lifecycleErrors.length > 0) {
|
|
602
|
+
throw combineLifecycleErrors(lifecycleErrors, "Crabline server shutdown failed.");
|
|
603
|
+
}
|
|
396
604
|
});
|
|
397
605
|
const shutdown = installShutdownHandler(close);
|
|
398
|
-
let releaseReadyLease;
|
|
399
|
-
let publishedReady;
|
|
400
606
|
let actionFailed = false;
|
|
401
607
|
let actionError;
|
|
402
608
|
try {
|
|
@@ -424,8 +630,13 @@ export function createProgram(setExitCode = (code) => {
|
|
|
424
630
|
await shutdown.wait();
|
|
425
631
|
}
|
|
426
632
|
else {
|
|
633
|
+
let outputWritten = true;
|
|
427
634
|
try {
|
|
428
|
-
const
|
|
635
|
+
const formatted = formatJsonResult(server.manifest);
|
|
636
|
+
if (!formatted.ok) {
|
|
637
|
+
throw new Error("Unable to serialize server manifest.");
|
|
638
|
+
}
|
|
639
|
+
const payload = formatted.output;
|
|
429
640
|
if (commandOptions.readyFile) {
|
|
430
641
|
const readyContents = `${payload}\n`;
|
|
431
642
|
publishedReady = {
|
|
@@ -433,14 +644,25 @@ export function createProgram(setExitCode = (code) => {
|
|
|
433
644
|
identity: await publish(commandOptions.readyFile, readyContents),
|
|
434
645
|
};
|
|
435
646
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
647
|
+
if (shutdown.requested()) {
|
|
648
|
+
outputWritten = false;
|
|
649
|
+
finishPublication();
|
|
650
|
+
await shutdown.wait();
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
outputWritten = await print(options.json
|
|
654
|
+
? payload
|
|
655
|
+
: renderServeText(server.manifest, commandOptions.showSecrets === true));
|
|
656
|
+
if (!outputWritten) {
|
|
657
|
+
finishPublication();
|
|
658
|
+
await close();
|
|
659
|
+
}
|
|
660
|
+
}
|
|
439
661
|
}
|
|
440
662
|
finally {
|
|
441
663
|
finishPublication();
|
|
442
664
|
}
|
|
443
|
-
if (shutdown.requested() || !commandOptions.once) {
|
|
665
|
+
if (outputWritten && (shutdown.requested() || !commandOptions.once)) {
|
|
444
666
|
await shutdown.wait();
|
|
445
667
|
}
|
|
446
668
|
}
|
|
@@ -453,23 +675,12 @@ export function createProgram(setExitCode = (code) => {
|
|
|
453
675
|
finishStartup();
|
|
454
676
|
finishPublication();
|
|
455
677
|
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
|
-
}
|
|
678
|
+
try {
|
|
679
|
+
await close();
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
if (!actionFailed || error !== actionError) {
|
|
683
|
+
cleanupErrors.push(error);
|
|
473
684
|
}
|
|
474
685
|
}
|
|
475
686
|
shutdown.dispose();
|
|
@@ -486,8 +697,8 @@ export function createProgram(setExitCode = (code) => {
|
|
|
486
697
|
const findings = diagnose(manifest);
|
|
487
698
|
const ok = findings.length === 0;
|
|
488
699
|
const payload = { findings, ok };
|
|
489
|
-
await print(options.json ? formatJson(payload) : ok ? "doctor ok" : findings.join("\n"));
|
|
490
700
|
setExitCode(ok ? 0 : 10);
|
|
701
|
+
await print(options.json ? formatCliJson(payload) : ok ? "doctor ok" : findings.join("\n"));
|
|
491
702
|
});
|
|
492
703
|
return program;
|
|
493
704
|
}
|
|
@@ -842,11 +1053,11 @@ function diagnose(manifest) {
|
|
|
842
1053
|
}
|
|
843
1054
|
return findings;
|
|
844
1055
|
}
|
|
845
|
-
export async function runCli(argv) {
|
|
1056
|
+
export async function runCli(argv, options = {}) {
|
|
846
1057
|
let exitCode = 0;
|
|
847
1058
|
const program = createProgram((code) => {
|
|
848
1059
|
exitCode = code;
|
|
849
|
-
});
|
|
1060
|
+
}, options.dependencies);
|
|
850
1061
|
const parserErrors = [];
|
|
851
1062
|
const parserOutput = [];
|
|
852
1063
|
const bufferParserErrors = (command) => {
|
|
@@ -896,6 +1107,9 @@ export async function runCli(argv) {
|
|
|
896
1107
|
else {
|
|
897
1108
|
await writeOutput(process.stderr, `${ensureErrorMessage(error)}\n`);
|
|
898
1109
|
}
|
|
1110
|
+
if (options.forceExit && containsWatchShutdownDeadline(error)) {
|
|
1111
|
+
options.forceExit(errorExitCode);
|
|
1112
|
+
}
|
|
899
1113
|
return errorExitCode;
|
|
900
1114
|
}
|
|
901
1115
|
}
|