@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
230 lines (227 loc) • 8.33 kB
JavaScript
import "./paths-BDd7_JUB.js";
import "./agent-scope-CrgUOY3f.js";
import { I as colorize, L as isRich, R as theme, u as clearActiveProgressLine } from "./subsystem-46MXi6Ip.js";
import "./utils-Dg0Xbl6w.js";
import "./exec-CTo4hK94.js";
import "./model-selection-Cp1maz7B.js";
import "./github-copilot-token-VZsS4013.js";
import { t as formatCliCommand } from "./command-format-BQK1OIvH.js";
import "./boolean-CE7i9tBR.js";
import "./env-DOcCob95.js";
import "./config-qgIz1lbh.js";
import "./manifest-registry-BFpLJJDB.js";
import "./message-channel-CQGWXVL4.js";
import "./client-zqMhLTAX.js";
import { t as buildGatewayConnectionDetails } from "./call-CPBhMXxo.js";
import { t as formatDocsLink } from "./links-C9fyAH-V.js";
import "./progress-uNDQDtGB.js";
import { n as callGatewayFromCli, t as addGatewayClientOptions } from "./gateway-rpc-SvVB4Fmv.js";
import { t as parseLogLine } from "./parse-log-line-BmyeB3J2.js";
import { setTimeout } from "node:timers/promises";
//#region src/terminal/stream-writer.ts
function isBrokenPipeError(err) {
const code = err?.code;
return code === "EPIPE" || code === "EIO";
}
function createSafeStreamWriter(options = {}) {
let closed = false;
let notified = false;
const noteBrokenPipe = (err, stream) => {
if (notified) return;
notified = true;
options.onBrokenPipe?.(err, stream);
};
const handleError = (err, stream) => {
if (!isBrokenPipeError(err)) throw err;
closed = true;
noteBrokenPipe(err, stream);
return false;
};
const write = (stream, text) => {
if (closed) return false;
try {
options.beforeWrite?.();
} catch (err) {
return handleError(err, process.stderr);
}
try {
stream.write(text);
return !closed;
} catch (err) {
return handleError(err, stream);
}
};
const writeLine = (stream, text) => write(stream, `${text}\n`);
return {
write,
writeLine,
reset: () => {
closed = false;
notified = false;
},
isClosed: () => closed
};
}
//#endregion
//#region src/cli/logs-cli.ts
function parsePositiveInt(value, fallback) {
if (!value) return fallback;
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}
async function fetchLogs(opts, cursor, showProgress) {
const payload = await callGatewayFromCli("logs.tail", opts, {
cursor,
limit: parsePositiveInt(opts.limit, 200),
maxBytes: parsePositiveInt(opts.maxBytes, 25e4)
}, { progress: showProgress });
if (!payload || typeof payload !== "object") throw new Error("Unexpected logs.tail response");
return payload;
}
function formatLogTimestamp(value, mode = "plain") {
if (!value) return "";
const parsed = new Date(value);
if (Number.isNaN(parsed.getTime())) return value;
if (mode === "pretty") return parsed.toISOString().slice(11, 19);
return parsed.toISOString();
}
function formatLogLine(raw, opts) {
const parsed = parseLogLine(raw);
if (!parsed) return raw;
const label = parsed.subsystem ?? parsed.module ?? "";
const time = formatLogTimestamp(parsed.time, opts.pretty ? "pretty" : "plain");
const level = parsed.level ?? "";
const levelLabel = level.padEnd(5).trim();
const message = parsed.message || parsed.raw;
if (!opts.pretty) return [
time,
level,
label,
message
].filter(Boolean).join(" ").trim();
const timeLabel = colorize(opts.rich, theme.muted, time);
const labelValue = colorize(opts.rich, theme.accent, label);
const levelValue = level === "error" || level === "fatal" ? colorize(opts.rich, theme.error, levelLabel) : level === "warn" ? colorize(opts.rich, theme.warn, levelLabel) : level === "debug" || level === "trace" ? colorize(opts.rich, theme.muted, levelLabel) : colorize(opts.rich, theme.info, levelLabel);
const messageValue = level === "error" || level === "fatal" ? colorize(opts.rich, theme.error, message) : level === "warn" ? colorize(opts.rich, theme.warn, message) : level === "debug" || level === "trace" ? colorize(opts.rich, theme.muted, message) : colorize(opts.rich, theme.info, message);
return [[
timeLabel,
levelValue,
labelValue
].filter(Boolean).join(" "), messageValue].filter(Boolean).join(" ").trim();
}
function createLogWriters() {
const writer = createSafeStreamWriter({
beforeWrite: () => clearActiveProgressLine(),
onBrokenPipe: (err, stream) => {
const code = err.code ?? "EPIPE";
const message = `openclaw logs: output ${stream === process.stdout ? "stdout" : "stderr"} closed (${code}). Stopping tail.`;
try {
clearActiveProgressLine();
process.stderr.write(`${message}\n`);
} catch {}
}
});
return {
logLine: (text) => writer.writeLine(process.stdout, text),
errorLine: (text) => writer.writeLine(process.stderr, text),
emitJsonLine: (payload, toStdErr = false) => writer.write(toStdErr ? process.stderr : process.stdout, `${JSON.stringify(payload)}\n`)
};
}
function emitGatewayError(err, opts, mode, rich, emitJsonLine, errorLine) {
const details = buildGatewayConnectionDetails({ url: opts.url });
const message = "Gateway not reachable. Is it running and accessible?";
const hint = `Hint: run \`${formatCliCommand("openclaw doctor")}\`.`;
const errorText = err instanceof Error ? err.message : String(err);
if (mode === "json") {
if (!emitJsonLine({
type: "error",
message,
error: errorText,
details,
hint
}, true)) return;
return;
}
if (!errorLine(colorize(rich, theme.error, message))) return;
if (!errorLine(details.message)) return;
errorLine(colorize(rich, theme.muted, hint));
}
function registerLogsCli(program) {
const logs = program.command("logs").description("Tail gateway file logs via RPC").option("--limit <n>", "Max lines to return", "200").option("--max-bytes <n>", "Max bytes to read", "250000").option("--follow", "Follow log output", false).option("--interval <ms>", "Polling interval in ms", "1000").option("--json", "Emit JSON log lines", false).option("--plain", "Plain text output (no ANSI styling)", false).option("--no-color", "Disable ANSI colors").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/logs", "docs.openclaw.ai/cli/logs")}\n`);
addGatewayClientOptions(logs);
logs.action(async (opts) => {
const { logLine, errorLine, emitJsonLine } = createLogWriters();
const interval = parsePositiveInt(opts.interval, 1e3);
let cursor;
let first = true;
const jsonMode = Boolean(opts.json);
const pretty = !jsonMode && Boolean(process.stdout.isTTY) && !opts.plain;
const rich = isRich() && opts.color !== false;
while (true) {
let payload;
const showProgress = first && !opts.follow;
try {
payload = await fetchLogs(opts, cursor, showProgress);
} catch (err) {
emitGatewayError(err, opts, jsonMode ? "json" : "text", rich, emitJsonLine, errorLine);
process.exit(1);
return;
}
const lines = Array.isArray(payload.lines) ? payload.lines : [];
if (jsonMode) {
if (first) {
if (!emitJsonLine({
type: "meta",
file: payload.file,
cursor: payload.cursor,
size: payload.size
})) return;
}
for (const line of lines) {
const parsed = parseLogLine(line);
if (parsed) {
if (!emitJsonLine({
type: "log",
...parsed
})) return;
} else if (!emitJsonLine({
type: "raw",
raw: line
})) return;
}
if (payload.truncated) {
if (!emitJsonLine({
type: "notice",
message: "Log tail truncated (increase --max-bytes)."
})) return;
}
if (payload.reset) {
if (!emitJsonLine({
type: "notice",
message: "Log cursor reset (file rotated)."
})) return;
}
} else {
if (first && payload.file) {
if (!logLine(`${pretty ? colorize(rich, theme.muted, "Log file:") : "Log file:"} ${payload.file}`)) return;
}
for (const line of lines) if (!logLine(formatLogLine(line, {
pretty,
rich
}))) return;
if (payload.truncated) {
if (!errorLine("Log tail truncated (increase --max-bytes).")) return;
}
if (payload.reset) {
if (!errorLine("Log cursor reset (file rotated).")) return;
}
}
cursor = typeof payload.cursor === "number" && Number.isFinite(payload.cursor) ? payload.cursor : cursor;
first = false;
if (!opts.follow) return;
await setTimeout(interval);
}
});
}
//#endregion
export { registerLogsCli };