mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-02 14:10:21 +00:00
refactor(cli): separate json payload output from logging
This commit is contained in:
@@ -38,7 +38,7 @@ async function runBrowserPostAction<T>(params: {
|
||||
{ timeoutMs: params.timeoutMs },
|
||||
);
|
||||
if (params.parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(params.describeSuccess(result));
|
||||
|
||||
@@ -116,10 +116,10 @@ export function registerBrowserFormWaitEvalCommands(
|
||||
},
|
||||
});
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(JSON.stringify(result.result ?? null, null, 2));
|
||||
defaultRuntime.writeJson(result.result ?? null);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
|
||||
@@ -31,7 +31,7 @@ export function registerBrowserNavigationCommands(
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`navigated to ${result.url ?? url}`);
|
||||
|
||||
@@ -46,7 +46,7 @@ export function logBrowserActionResult(
|
||||
successMessage: string,
|
||||
) {
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(successMessage);
|
||||
|
||||
@@ -39,10 +39,10 @@ export function registerBrowserActionObserveCommands(
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(JSON.stringify(result.messages, null, 2));
|
||||
defaultRuntime.writeJson(result.messages);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,7 +65,7 @@ export function registerBrowserActionObserveCommands(
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`PDF: ${shortenHomePath(result.path)}`);
|
||||
@@ -107,7 +107,7 @@ export function registerBrowserActionObserveCommands(
|
||||
{ timeoutMs: timeoutMs ?? 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(result.response.body);
|
||||
|
||||
@@ -39,7 +39,7 @@ function printJsonResult(parent: BrowserParentOpts, result: unknown): boolean {
|
||||
if (!parent.json) {
|
||||
return false;
|
||||
}
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export function registerBrowserInspectCommands(
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`MEDIA:${shortenHomePath(result.path)}`);
|
||||
@@ -106,19 +106,13 @@ export function registerBrowserInspectCommands(
|
||||
await fs.writeFile(opts.out, payload, "utf8");
|
||||
}
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
ok: true,
|
||||
out: opts.out,
|
||||
...(result.format === "ai" && result.imagePath
|
||||
? { imagePath: result.imagePath }
|
||||
: {}),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
defaultRuntime.writeJson({
|
||||
ok: true,
|
||||
out: opts.out,
|
||||
...(result.format === "ai" && result.imagePath
|
||||
? { imagePath: result.imagePath }
|
||||
: {}),
|
||||
});
|
||||
} else {
|
||||
defaultRuntime.log(shortenHomePath(opts.out));
|
||||
if (result.format === "ai" && result.imagePath) {
|
||||
@@ -129,7 +123,7 @@ export function registerBrowserInspectCommands(
|
||||
}
|
||||
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ function printJsonResult(parent: BrowserParentOpts, payload: unknown): boolean {
|
||||
if (!parent?.json) {
|
||||
return false;
|
||||
}
|
||||
defaultRuntime.log(JSON.stringify(payload, null, 2));
|
||||
defaultRuntime.writeJson(payload);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ function runBrowserCommand(action: () => Promise<void>) {
|
||||
|
||||
function logBrowserTabs(tabs: BrowserTab[], json?: boolean) {
|
||||
if (json) {
|
||||
defaultRuntime.log(JSON.stringify({ tabs }, null, 2));
|
||||
defaultRuntime.writeJson({ tabs });
|
||||
return;
|
||||
}
|
||||
if (tabs.length === 0) {
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function runBrowserResizeWithOutput(params: {
|
||||
);
|
||||
|
||||
if (params.parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(params.successMessage);
|
||||
|
||||
@@ -36,7 +36,7 @@ async function runMutationRequest(params: {
|
||||
try {
|
||||
const result = await callBrowserRequest(params.parent, params.request, { timeoutMs: 20000 });
|
||||
if (params.parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(params.successMessage);
|
||||
@@ -72,10 +72,10 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(JSON.stringify(result.cookies ?? [], null, 2));
|
||||
defaultRuntime.writeJson(result.cookies ?? []);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
@@ -165,10 +165,10 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(JSON.stringify(result.values ?? {}, null, 2));
|
||||
defaultRuntime.writeJson(result.values ?? {});
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
|
||||
@@ -38,7 +38,7 @@ async function runBrowserSetRequest(params: {
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (params.parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(params.successMessage);
|
||||
@@ -139,7 +139,7 @@ export function registerBrowserStateCommands(
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log("headers set");
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { validateConfigObjectRaw } from "../config/validation.js";
|
||||
import { SecretProviderSchema } from "../config/zod-schema.core.js";
|
||||
import { danger, info, success } from "../globals.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import {
|
||||
formatExecSecretRefIdValidationMessage,
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
type ConfigSetOptions,
|
||||
} from "./config-set-input.js";
|
||||
import { resolveConfigSetMode } from "./config-set-parser.js";
|
||||
import { setCommandJsonMode } from "./program/json-mode.js";
|
||||
|
||||
type PathSegment = string;
|
||||
type ConfigSetParseOpts = {
|
||||
@@ -1072,7 +1073,7 @@ export async function runConfigSet(opts: {
|
||||
);
|
||||
}
|
||||
if (opts.cliOptions.json) {
|
||||
runtime.log(JSON.stringify(dryRunResult, null, 2));
|
||||
writeRuntimeJson(runtime, dryRunResult);
|
||||
} else {
|
||||
if (!dryRunResult.checks.schema && !dryRunResult.checks.resolvability) {
|
||||
runtime.log(
|
||||
@@ -1120,7 +1121,7 @@ export async function runConfigSet(opts: {
|
||||
opts.cliOptions.json &&
|
||||
err instanceof ConfigSetDryRunValidationError
|
||||
) {
|
||||
runtime.log(JSON.stringify(err.result, null, 2));
|
||||
writeRuntimeJson(runtime, err.result);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
@@ -1142,7 +1143,7 @@ export async function runConfigGet(opts: { path: string; json?: boolean; runtime
|
||||
return;
|
||||
}
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(res.value ?? null, null, 2));
|
||||
writeRuntimeJson(runtime, res.value ?? null);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
@@ -1153,7 +1154,7 @@ export async function runConfigGet(opts: { path: string; json?: boolean; runtime
|
||||
runtime.log(String(res.value));
|
||||
return;
|
||||
}
|
||||
runtime.log(JSON.stringify(res.value ?? null, null, 2));
|
||||
writeRuntimeJson(runtime, res.value ?? null);
|
||||
} catch (err) {
|
||||
runtime.error(danger(String(err)));
|
||||
runtime.exit(1);
|
||||
@@ -1205,7 +1206,7 @@ export async function runConfigValidate(opts: { json?: boolean; runtime?: Runtim
|
||||
|
||||
if (!snapshot.exists) {
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify({ valid: false, path: outputPath, error: "file not found" }));
|
||||
writeRuntimeJson(runtime, { valid: false, path: outputPath, error: "file not found" }, 0);
|
||||
} else {
|
||||
runtime.error(danger(`Config file not found: ${shortPath}`));
|
||||
}
|
||||
@@ -1217,7 +1218,7 @@ export async function runConfigValidate(opts: { json?: boolean; runtime?: Runtim
|
||||
const issues = normalizeConfigIssues(snapshot.issues);
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify({ valid: false, path: outputPath, issues }, null, 2));
|
||||
writeRuntimeJson(runtime, { valid: false, path: outputPath, issues });
|
||||
} else {
|
||||
runtime.error(danger(`Config invalid at ${shortPath}:`));
|
||||
for (const line of formatConfigIssueLines(issues, danger("×"), { normalizeRoot: true })) {
|
||||
@@ -1231,13 +1232,13 @@ export async function runConfigValidate(opts: { json?: boolean; runtime?: Runtim
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify({ valid: true, path: outputPath }));
|
||||
writeRuntimeJson(runtime, { valid: true, path: outputPath }, 0);
|
||||
} else {
|
||||
runtime.log(success(`Config valid: ${shortPath}`));
|
||||
}
|
||||
} catch (err) {
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify({ valid: false, path: outputPath, error: String(err) }));
|
||||
writeRuntimeJson(runtime, { valid: false, path: outputPath, error: String(err) }, 0);
|
||||
} else {
|
||||
runtime.error(danger(`Config validation error: ${String(err)}`));
|
||||
}
|
||||
@@ -1276,8 +1277,7 @@ export function registerConfigCli(program: Command) {
|
||||
await runConfigGet({ path, json: Boolean(opts.json) });
|
||||
});
|
||||
|
||||
cmd
|
||||
.command("set")
|
||||
setCommandJsonMode(cmd.command("set"), "parse-only")
|
||||
.description(CONFIG_SET_DESCRIPTION)
|
||||
.argument("[path]", "Config path (dot or bracket notation)")
|
||||
.argument("[value]", "Value (JSON/JSON5 or raw string)")
|
||||
|
||||
@@ -341,7 +341,7 @@ export function registerCronEditCommand(cron: Command) {
|
||||
id,
|
||||
patch,
|
||||
});
|
||||
defaultRuntime.log(JSON.stringify(res, null, 2));
|
||||
defaultRuntime.writeJson(res);
|
||||
await warnIfCronSchedulerDisabled(opts);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
|
||||
@@ -4,7 +4,7 @@ import { resolveCronStaggerMs } from "../../cron/stagger.js";
|
||||
import type { CronJob, CronSchedule } from "../../cron/types.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { formatDurationHuman } from "../../infra/format-time/format-duration.ts";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
||||
import { colorize, isRich, theme } from "../../terminal/theme.js";
|
||||
import type { GatewayRpcOpts } from "../gateway-rpc.js";
|
||||
import { callGatewayFromCli } from "../gateway-rpc.js";
|
||||
@@ -13,7 +13,7 @@ export const getCronChannelOptions = () =>
|
||||
["last", ...listChannelPlugins().map((plugin) => plugin.id)].join("|");
|
||||
|
||||
export function printCronJson(value: unknown) {
|
||||
defaultRuntime.log(JSON.stringify(value, null, 2));
|
||||
defaultRuntime.writeJson(value);
|
||||
}
|
||||
|
||||
export function handleCronCliError(err: unknown) {
|
||||
@@ -184,7 +184,7 @@ const formatStatus = (job: CronJob) => {
|
||||
return job.state.lastStatus ?? "idle";
|
||||
};
|
||||
|
||||
export function printCronList(jobs: CronJob[], runtime = defaultRuntime) {
|
||||
export function printCronList(jobs: CronJob[], runtime: RuntimeEnv = defaultRuntime) {
|
||||
if (jobs.length === 0) {
|
||||
runtime.log("No cron jobs.");
|
||||
return;
|
||||
|
||||
@@ -21,7 +21,7 @@ export type DaemonActionResponse = {
|
||||
};
|
||||
|
||||
export function emitDaemonActionJson(payload: DaemonActionResponse) {
|
||||
defaultRuntime.log(JSON.stringify(payload, null, 2));
|
||||
defaultRuntime.writeJson(payload);
|
||||
}
|
||||
|
||||
export function buildDaemonServiceSnapshot(service: GatewayService, loaded: boolean) {
|
||||
|
||||
@@ -52,7 +52,7 @@ function sanitizeDaemonStatusForJson(status: DaemonStatus): DaemonStatus {
|
||||
export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
|
||||
if (opts.json) {
|
||||
const sanitized = sanitizeDaemonStatusForJson(status);
|
||||
defaultRuntime.log(JSON.stringify(sanitized, null, 2));
|
||||
defaultRuntime.writeJson(sanitized);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ export function registerDevicesCli(program: Command) {
|
||||
.action(async (opts: DevicesRpcOpts) => {
|
||||
const list = await listPairingWithFallback(opts);
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(list, null, 2));
|
||||
defaultRuntime.writeJson(list);
|
||||
return;
|
||||
}
|
||||
if (list.pending?.length) {
|
||||
@@ -323,7 +323,7 @@ export function registerDevicesCli(program: Command) {
|
||||
}
|
||||
const result = await callGatewayCli("device.pair.remove", opts, { deviceId: trimmed });
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`${theme.warn("Removed")} ${theme.command(trimmed)}`);
|
||||
@@ -366,16 +366,10 @@ export function registerDevicesCli(program: Command) {
|
||||
}
|
||||
}
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
removedDevices: removedDeviceIds,
|
||||
rejectedPending: rejectedRequestIds,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
defaultRuntime.writeJson({
|
||||
removedDevices: removedDeviceIds,
|
||||
rejectedPending: rejectedRequestIds,
|
||||
});
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(
|
||||
@@ -413,7 +407,7 @@ export function registerDevicesCli(program: Command) {
|
||||
return;
|
||||
}
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
const deviceId = (result as { device?: { deviceId?: string } })?.device?.deviceId;
|
||||
@@ -431,7 +425,7 @@ export function registerDevicesCli(program: Command) {
|
||||
.action(async (requestId: string, opts: DevicesRpcOpts) => {
|
||||
const result = await callGatewayCli("device.pair.reject", opts, { requestId });
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
const deviceId = (result as { deviceId?: string })?.deviceId;
|
||||
@@ -456,7 +450,7 @@ export function registerDevicesCli(program: Command) {
|
||||
role: required.role,
|
||||
scopes: Array.isArray(opts.scope) ? opts.scope : undefined,
|
||||
});
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -475,7 +469,7 @@ export function registerDevicesCli(program: Command) {
|
||||
deviceId: required.deviceId,
|
||||
role: required.role,
|
||||
});
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ export function registerDirectoryCli(program: Command) {
|
||||
runtime: defaultRuntime,
|
||||
});
|
||||
if (params.opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
printDirectoryList({ title: params.title, emptyMessage: params.emptyMessage, entries: result });
|
||||
@@ -179,7 +179,7 @@ export function registerDirectoryCli(program: Command) {
|
||||
}
|
||||
const result = await fn({ cfg, accountId, runtime: defaultRuntime });
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
if (!result) {
|
||||
@@ -272,7 +272,7 @@ export function registerDirectoryCli(program: Command) {
|
||||
runtime: defaultRuntime,
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
printDirectoryList({
|
||||
|
||||
@@ -154,16 +154,10 @@ export function registerDnsCli(program: Command) {
|
||||
);
|
||||
defaultRuntime.log("");
|
||||
defaultRuntime.log(theme.heading("Recommended ~/.openclaw/openclaw.json:"));
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
gateway: { bind: "auto" },
|
||||
discovery: { wideArea: { enabled: true, domain: wideAreaDomain } },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
defaultRuntime.writeJson({
|
||||
gateway: { bind: "auto" },
|
||||
discovery: { wideArea: { enabled: true, domain: wideAreaDomain } },
|
||||
});
|
||||
defaultRuntime.log("");
|
||||
defaultRuntime.log(theme.heading("Tailscale admin (DNS → Nameservers):"));
|
||||
defaultRuntime.log(
|
||||
|
||||
@@ -135,7 +135,7 @@ async function saveSnapshotTargeted(params: {
|
||||
? saveSnapshotLocal(params.file)
|
||||
: await saveSnapshot(params.opts, params.nodeId, params.file, params.baseHash);
|
||||
if (params.opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(next));
|
||||
defaultRuntime.writeJson(next, 0);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(theme.muted(`Target: ${params.targetLabel}`));
|
||||
@@ -365,7 +365,7 @@ export function registerExecApprovalsCli(program: Command) {
|
||||
try {
|
||||
const { snapshot, nodeId, source } = await loadSnapshotTarget(opts);
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(snapshot));
|
||||
defaultRuntime.writeJson(snapshot, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -124,14 +124,14 @@ export function registerGatewayCli(program: Command) {
|
||||
const params = JSON.parse(String(opts.params ?? "{}"));
|
||||
const result = await callGatewayCli(method, { ...rpcOpts, config }, params);
|
||||
if (rpcOpts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
const rich = isRich();
|
||||
defaultRuntime.log(
|
||||
`${colorize(rich, theme.heading, "Gateway call")}: ${colorize(rich, theme.muted, String(method))}`,
|
||||
);
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
}, "Gateway call failed");
|
||||
}),
|
||||
);
|
||||
@@ -148,7 +148,7 @@ export function registerGatewayCli(program: Command) {
|
||||
const config = await readBestEffortConfig();
|
||||
const result = await callGatewayCli("usage.cost", { ...rpcOpts, config }, { days });
|
||||
if (rpcOpts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
const rich = isRich();
|
||||
@@ -170,7 +170,7 @@ export function registerGatewayCli(program: Command) {
|
||||
const config = await readBestEffortConfig();
|
||||
const result = await callGatewayCli("health", { ...rpcOpts, config });
|
||||
if (rpcOpts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
const rich = isRich();
|
||||
@@ -242,18 +242,12 @@ export function registerGatewayCli(program: Command) {
|
||||
const port = pickGatewayPort(b);
|
||||
return { ...b, wsUrl: host ? `ws://${host}:${port}` : null };
|
||||
});
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
timeoutMs,
|
||||
domains,
|
||||
count: enriched.length,
|
||||
beacons: enriched,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
defaultRuntime.writeJson({
|
||||
timeoutMs,
|
||||
domains,
|
||||
count: enriched.length,
|
||||
beacons: enriched,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
waitForActiveTasks,
|
||||
} from "../../process/command-queue.js";
|
||||
import { createRestartIterationHook } from "../../process/restart-recovery.js";
|
||||
import type { defaultRuntime } from "../../runtime.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
|
||||
const gatewayLog = createSubsystemLogger("gateway");
|
||||
|
||||
@@ -28,7 +28,7 @@ type GatewayRunSignalAction = "stop" | "restart";
|
||||
|
||||
export async function runGatewayLoop(params: {
|
||||
start: () => Promise<Awaited<ReturnType<typeof startGatewayServer>>>;
|
||||
runtime: typeof defaultRuntime;
|
||||
runtime: RuntimeEnv;
|
||||
lockPort?: number;
|
||||
}) {
|
||||
let lock = await acquireGatewayLock({ port: params.lockPort });
|
||||
|
||||
@@ -145,6 +145,14 @@ function exitHooksCliWithError(err: unknown): never {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function writeHooksOutput(value: string, json: boolean | undefined): void {
|
||||
if (json) {
|
||||
defaultRuntime.writeStdout(value);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(value);
|
||||
}
|
||||
|
||||
async function runHooksCliAction(action: () => Promise<void> | void): Promise<void> {
|
||||
try {
|
||||
await action();
|
||||
@@ -455,7 +463,7 @@ export function registerHooksCli(program: Command): void {
|
||||
runHooksCliAction(async () => {
|
||||
const config = loadConfig();
|
||||
const report = buildHooksReport(config);
|
||||
defaultRuntime.log(formatHooksList(report, opts));
|
||||
writeHooksOutput(formatHooksList(report, opts), opts.json);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -467,7 +475,7 @@ export function registerHooksCli(program: Command): void {
|
||||
runHooksCliAction(async () => {
|
||||
const config = loadConfig();
|
||||
const report = buildHooksReport(config);
|
||||
defaultRuntime.log(formatHookInfo(report, name, opts));
|
||||
writeHooksOutput(formatHookInfo(report, name, opts), opts.json);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -479,7 +487,7 @@ export function registerHooksCli(program: Command): void {
|
||||
runHooksCliAction(async () => {
|
||||
const config = loadConfig();
|
||||
const report = buildHooksReport(config);
|
||||
defaultRuntime.log(formatHooksCheck(report, opts));
|
||||
writeHooksOutput(formatHooksCheck(report, opts), opts.json);
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ function fail(message: string): never {
|
||||
}
|
||||
|
||||
function printJson(value: unknown): void {
|
||||
defaultRuntime.log(JSON.stringify(value, null, 2));
|
||||
defaultRuntime.writeJson(value);
|
||||
}
|
||||
|
||||
export function registerMcpCli(program: Command) {
|
||||
|
||||
@@ -414,7 +414,7 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(allResults, null, 2));
|
||||
defaultRuntime.writeJson(allResults);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -723,7 +723,7 @@ export async function runMemorySearch(
|
||||
return;
|
||||
}
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify({ results }, null, 2));
|
||||
defaultRuntime.writeJson({ results });
|
||||
return;
|
||||
}
|
||||
if (results.length === 0) {
|
||||
|
||||
@@ -223,7 +223,7 @@ export async function runNodeDaemonStatus(opts: NodeDaemonStatusOptions = {}) {
|
||||
};
|
||||
|
||||
if (json) {
|
||||
defaultRuntime.log(JSON.stringify(payload, null, 2));
|
||||
defaultRuntime.writeJson(payload);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ export function registerNodesCameraCommands(nodes: Command) {
|
||||
const devices = Array.isArray(payload.devices) ? payload.devices : [];
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(devices, null, 2));
|
||||
defaultRuntime.writeJson(devices);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ export function registerNodesCameraCommands(nodes: Command) {
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify({ files: results }, null, 2));
|
||||
defaultRuntime.writeJson({ files: results });
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(results.map((r) => `MEDIA:${shortenHomePath(r.path)}`).join("\n"));
|
||||
@@ -241,20 +241,14 @@ export function registerNodesCameraCommands(nodes: Command) {
|
||||
});
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
file: {
|
||||
facing,
|
||||
path: filePath,
|
||||
durationMs: payload.durationMs,
|
||||
hasAudio: payload.hasAudio,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
defaultRuntime.writeJson({
|
||||
file: {
|
||||
facing,
|
||||
path: filePath,
|
||||
durationMs: payload.durationMs,
|
||||
hasAudio: payload.hasAudio,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`MEDIA:${shortenHomePath(filePath)}`);
|
||||
|
||||
@@ -65,9 +65,7 @@ export function registerNodesCanvasCommands(nodes: Command) {
|
||||
await writeBase64ToFile(filePath, payload.base64);
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify({ file: { path: filePath, format: payload.format } }, null, 2),
|
||||
);
|
||||
defaultRuntime.writeJson({ file: { path: filePath, format: payload.format } });
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`MEDIA:${shortenHomePath(filePath)}`);
|
||||
@@ -169,7 +167,7 @@ export function registerNodesCanvasCommands(nodes: Command) {
|
||||
javaScript: js,
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(raw, null, 2));
|
||||
defaultRuntime.writeJson(raw);
|
||||
return;
|
||||
}
|
||||
const payload =
|
||||
|
||||
@@ -333,7 +333,7 @@ export function registerNodesInvokeCommands(nodes: Command) {
|
||||
}
|
||||
|
||||
const result = await callGatewayCli("node.invoke", opts, invokeParams);
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
});
|
||||
}),
|
||||
{ timeoutMs: 30_000 },
|
||||
@@ -421,7 +421,7 @@ export function registerNodesInvokeCommands(nodes: Command) {
|
||||
|
||||
const result = await callGatewayCli("node.invoke", opts, invokeParams);
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export function registerNodesLocationCommands(nodes: Command) {
|
||||
: {};
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(payload, null, 2));
|
||||
defaultRuntime.writeJson(payload);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export function registerNodesLocationCommands(nodes: Command) {
|
||||
defaultRuntime.log(`${lat},${lon}${accText}`);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(JSON.stringify(payload));
|
||||
defaultRuntime.writeJson(payload, 0);
|
||||
});
|
||||
}),
|
||||
{ timeoutMs: 30_000 },
|
||||
|
||||
@@ -46,7 +46,7 @@ export function registerNodesNotifyCommand(nodes: Command) {
|
||||
|
||||
const result = await callGatewayCli("node.invoke", opts, invokeParams);
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
const { ok } = getNodesTheme();
|
||||
|
||||
@@ -17,7 +17,7 @@ export function registerNodesPairingCommands(nodes: Command) {
|
||||
const result = await callGatewayCli("node.pair.list", opts, {});
|
||||
const { pending } = parsePairingList(result);
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(pending, null, 2));
|
||||
defaultRuntime.writeJson(pending);
|
||||
return;
|
||||
}
|
||||
if (pending.length === 0) {
|
||||
@@ -50,7 +50,7 @@ export function registerNodesPairingCommands(nodes: Command) {
|
||||
const result = await callGatewayCli("node.pair.approve", opts, {
|
||||
requestId,
|
||||
});
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
});
|
||||
}),
|
||||
);
|
||||
@@ -65,7 +65,7 @@ export function registerNodesPairingCommands(nodes: Command) {
|
||||
const result = await callGatewayCli("node.pair.reject", opts, {
|
||||
requestId,
|
||||
});
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
});
|
||||
}),
|
||||
);
|
||||
@@ -90,7 +90,7 @@ export function registerNodesPairingCommands(nodes: Command) {
|
||||
displayName: name,
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
const { ok } = getNodesTheme();
|
||||
|
||||
@@ -52,7 +52,7 @@ export function registerNodesPushCommand(nodes: Command) {
|
||||
|
||||
const result = await callGatewayCli("push.test", opts, params);
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,21 +57,15 @@ export function registerNodesScreenCommands(nodes: Command) {
|
||||
const written = await writeScreenRecordToFile(filePath, parsed.base64);
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
file: {
|
||||
path: written.path,
|
||||
durationMs: parsed.durationMs,
|
||||
fps: parsed.fps,
|
||||
screenIndex: parsed.screenIndex,
|
||||
hasAudio: parsed.hasAudio,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
defaultRuntime.writeJson({
|
||||
file: {
|
||||
path: written.path,
|
||||
durationMs: parsed.durationMs,
|
||||
fps: parsed.fps,
|
||||
screenIndex: parsed.screenIndex,
|
||||
hasAudio: parsed.hasAudio,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`MEDIA:${shortenHomePath(written.path)}`);
|
||||
|
||||
@@ -147,7 +147,7 @@ export function registerNodesStatusCommands(nodes: Command) {
|
||||
|
||||
if (opts.json) {
|
||||
const ts = typeof obj.ts === "number" ? obj.ts : Date.now();
|
||||
defaultRuntime.log(JSON.stringify({ ...obj, ts, nodes: filtered }, null, 2));
|
||||
defaultRuntime.writeJson({ ...obj, ts, nodes: filtered });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ export function registerNodesStatusCommands(nodes: Command) {
|
||||
nodeId,
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -350,9 +350,7 @@ export function registerNodesStatusCommands(nodes: Command) {
|
||||
);
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify({ pending: pendingRows, paired: filteredPaired }, null, 2),
|
||||
);
|
||||
defaultRuntime.writeJson({ pending: pendingRows, paired: filteredPaired });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ export function registerPairingCli(program: Command) {
|
||||
? await listChannelPairingRequests(channel, process.env, accountId)
|
||||
: await listChannelPairingRequests(channel);
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify({ channel, requests }, null, 2));
|
||||
defaultRuntime.writeJson({ channel, requests });
|
||||
return;
|
||||
}
|
||||
if (requests.length === 0) {
|
||||
|
||||
@@ -191,7 +191,7 @@ export function registerPluginsCli(program: Command) {
|
||||
plugins: list,
|
||||
diagnostics: report.diagnostics,
|
||||
};
|
||||
defaultRuntime.log(JSON.stringify(payload, null, 2));
|
||||
defaultRuntime.writeJson(payload);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ export function registerPluginsCli(program: Command) {
|
||||
}));
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(inspectAllWithInstall, null, 2));
|
||||
defaultRuntime.writeJson(inspectAllWithInstall);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -367,16 +367,10 @@ export function registerPluginsCli(program: Command) {
|
||||
const install = cfg.plugins?.installs?.[inspect.plugin.id];
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
...inspect,
|
||||
install,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
defaultRuntime.writeJson({
|
||||
...inspect,
|
||||
install,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -760,18 +754,12 @@ export function registerPluginsCli(program: Command) {
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
source: result.sourceLabel,
|
||||
name: result.manifest.name,
|
||||
version: result.manifest.version,
|
||||
plugins: result.manifest.plugins,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
defaultRuntime.writeJson({
|
||||
source: result.sourceLabel,
|
||||
name: result.manifest.name,
|
||||
version: result.manifest.version,
|
||||
plugins: result.manifest.plugins,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
58
src/cli/program/json-mode.ts
Normal file
58
src/cli/program/json-mode.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { Command } from "commander";
|
||||
import { hasFlag } from "../argv.js";
|
||||
|
||||
const jsonModeSymbol = Symbol("openclaw.cli.jsonMode");
|
||||
|
||||
type JsonMode = "output" | "parse-only";
|
||||
type JsonModeCommand = Command & {
|
||||
[jsonModeSymbol]?: JsonMode;
|
||||
};
|
||||
|
||||
function commandDefinesJsonOption(command: Command): boolean {
|
||||
return command.options.some((option) => option.long === "--json");
|
||||
}
|
||||
|
||||
function getDeclaredCommandJsonMode(command: Command): JsonMode | null {
|
||||
for (let current: Command | null = command; current; current = current.parent ?? null) {
|
||||
const metadata = (current as JsonModeCommand)[jsonModeSymbol];
|
||||
if (metadata) {
|
||||
return metadata;
|
||||
}
|
||||
if (commandDefinesJsonOption(current)) {
|
||||
return "output";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function commandSelectedJsonFlag(command: Command, argv: string[]): boolean {
|
||||
const commandWithGlobals = command as Command & {
|
||||
optsWithGlobals?: <T extends Record<string, unknown>>() => T;
|
||||
};
|
||||
if (typeof commandWithGlobals.optsWithGlobals === "function") {
|
||||
const resolved = commandWithGlobals.optsWithGlobals<Record<string, unknown>>().json;
|
||||
if (resolved === true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return hasFlag(argv, "--json");
|
||||
}
|
||||
|
||||
export function setCommandJsonMode(command: Command, mode: JsonMode): Command {
|
||||
(command as JsonModeCommand)[jsonModeSymbol] = mode;
|
||||
return command;
|
||||
}
|
||||
|
||||
export function getCommandJsonMode(
|
||||
command: Command,
|
||||
argv: string[] = process.argv,
|
||||
): JsonMode | null {
|
||||
if (!commandSelectedJsonFlag(command, argv)) {
|
||||
return null;
|
||||
}
|
||||
return getDeclaredCommandJsonMode(command);
|
||||
}
|
||||
|
||||
export function isCommandJsonOutputMode(command: Command, argv: string[] = process.argv): boolean {
|
||||
return getCommandJsonMode(command, argv) === "output";
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Command } from "commander";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { setCommandJsonMode } from "./json-mode.js";
|
||||
|
||||
const setVerboseMock = vi.fn();
|
||||
const emitCliBannerMock = vi.fn();
|
||||
@@ -10,6 +11,8 @@ const routeLogsToStderrMock = vi.fn();
|
||||
const runtimeMock = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
writeStdout: vi.fn(),
|
||||
writeJson: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
};
|
||||
|
||||
@@ -100,7 +103,10 @@ describe("registerPreActionHooks", () => {
|
||||
|
||||
function buildProgram() {
|
||||
const program = new Command().name("openclaw");
|
||||
program.command("status").action(() => {});
|
||||
program
|
||||
.command("status")
|
||||
.option("--json")
|
||||
.action(() => {});
|
||||
program
|
||||
.command("backup")
|
||||
.command("create")
|
||||
@@ -109,7 +115,11 @@ describe("registerPreActionHooks", () => {
|
||||
program.command("doctor").action(() => {});
|
||||
program.command("completion").action(() => {});
|
||||
program.command("secrets").action(() => {});
|
||||
program.command("agents").action(() => {});
|
||||
program
|
||||
.command("agents")
|
||||
.command("list")
|
||||
.option("--json")
|
||||
.action(() => {});
|
||||
program.command("configure").action(() => {});
|
||||
program.command("onboard").action(() => {});
|
||||
const channels = program.command("channels");
|
||||
@@ -125,8 +135,7 @@ describe("registerPreActionHooks", () => {
|
||||
.option("--json")
|
||||
.action(() => {});
|
||||
const config = program.command("config");
|
||||
config
|
||||
.command("set")
|
||||
setCommandJsonMode(config.command("set"), "parse-only")
|
||||
.argument("<path>")
|
||||
.argument("<value>")
|
||||
.option("--json")
|
||||
@@ -277,8 +286,8 @@ describe("registerPreActionHooks", () => {
|
||||
|
||||
it("routes logs to stderr in --json mode so stdout stays clean", async () => {
|
||||
await runPreAction({
|
||||
parseArgv: ["agents"],
|
||||
processArgv: ["node", "openclaw", "agents", "--json"],
|
||||
parseArgv: ["agents", "list"],
|
||||
processArgv: ["node", "openclaw", "agents", "list", "--json"],
|
||||
});
|
||||
|
||||
expect(routeLogsToStderrMock).toHaveBeenCalledOnce();
|
||||
@@ -297,8 +306,8 @@ describe("registerPreActionHooks", () => {
|
||||
|
||||
// non-json command should not route
|
||||
await runPreAction({
|
||||
parseArgv: ["agents"],
|
||||
processArgv: ["node", "openclaw", "agents"],
|
||||
parseArgv: ["agents", "list"],
|
||||
processArgv: ["node", "openclaw", "agents", "list"],
|
||||
});
|
||||
|
||||
expect(routeLogsToStderrMock).not.toHaveBeenCalled();
|
||||
|
||||
@@ -4,14 +4,10 @@ import { isTruthyEnvValue } from "../../infra/env.js";
|
||||
import { routeLogsToStderr } from "../../logging/console.js";
|
||||
import type { LogLevel } from "../../logging/levels.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import {
|
||||
getCommandPathWithRootOptions,
|
||||
getVerboseFlag,
|
||||
hasFlag,
|
||||
hasHelpOrVersion,
|
||||
} from "../argv.js";
|
||||
import { getCommandPathWithRootOptions, getVerboseFlag, hasHelpOrVersion } from "../argv.js";
|
||||
import { emitCliBanner } from "../banner.js";
|
||||
import { resolveCliName } from "../cli-name.js";
|
||||
import { isCommandJsonOutputMode } from "./json-mode.js";
|
||||
|
||||
function setProcessTitleForCommand(actionCommand: Command) {
|
||||
let current: Command = actionCommand;
|
||||
@@ -37,7 +33,6 @@ const PLUGIN_REQUIRED_COMMANDS = new Set([
|
||||
"health",
|
||||
]);
|
||||
const CONFIG_GUARD_BYPASS_COMMANDS = new Set(["backup", "doctor", "completion", "secrets"]);
|
||||
const JSON_PARSE_ONLY_COMMANDS = new Set(["config set"]);
|
||||
let configGuardModulePromise: Promise<typeof import("./config-guard.js")> | undefined;
|
||||
let pluginRegistryModulePromise: Promise<typeof import("../plugin-registry.js")> | undefined;
|
||||
|
||||
@@ -71,12 +66,12 @@ function resolvePluginRegistryScope(commandPath: string[]): "channels" | "all" {
|
||||
return commandPath[0] === "status" || commandPath[0] === "health" ? "channels" : "all";
|
||||
}
|
||||
|
||||
function shouldLoadPluginsForCommand(commandPath: string[], argv: string[]): boolean {
|
||||
function shouldLoadPluginsForCommand(commandPath: string[], jsonOutputMode: boolean): boolean {
|
||||
const [primary, secondary] = commandPath;
|
||||
if (!primary || !PLUGIN_REQUIRED_COMMANDS.has(primary)) {
|
||||
return false;
|
||||
}
|
||||
if ((primary === "status" || primary === "health") && hasFlag(argv, "--json")) {
|
||||
if ((primary === "status" || primary === "health") && jsonOutputMode) {
|
||||
return false;
|
||||
}
|
||||
// Setup wizard and channels add should stay manifest-first and load selected plugins on demand.
|
||||
@@ -105,17 +100,6 @@ function getCliLogLevel(actionCommand: Command): LogLevel | undefined {
|
||||
return typeof logLevel === "string" ? (logLevel as LogLevel) : undefined;
|
||||
}
|
||||
|
||||
function isJsonOutputMode(commandPath: string[], argv: string[]): boolean {
|
||||
if (!hasFlag(argv, "--json")) {
|
||||
return false;
|
||||
}
|
||||
const key = `${commandPath[0] ?? ""} ${commandPath[1] ?? ""}`.trim();
|
||||
if (JSON_PARSE_ONLY_COMMANDS.has(key)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function registerPreActionHooks(program: Command, programVersion: string) {
|
||||
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
||||
setProcessTitleForCommand(actionCommand);
|
||||
@@ -124,7 +108,8 @@ export function registerPreActionHooks(program: Command, programVersion: string)
|
||||
return;
|
||||
}
|
||||
const commandPath = getCommandPathWithRootOptions(argv, 2);
|
||||
if (isJsonOutputMode(commandPath, argv)) {
|
||||
const jsonOutputMode = isCommandJsonOutputMode(actionCommand, argv);
|
||||
if (jsonOutputMode) {
|
||||
routeLogsToStderr();
|
||||
}
|
||||
const hideBanner =
|
||||
@@ -147,15 +132,14 @@ export function registerPreActionHooks(program: Command, programVersion: string)
|
||||
if (shouldBypassConfigGuard(commandPath)) {
|
||||
return;
|
||||
}
|
||||
const suppressDoctorStdout = isJsonOutputMode(commandPath, argv);
|
||||
const { ensureConfigReady } = await loadConfigGuardModule();
|
||||
await ensureConfigReady({
|
||||
runtime: defaultRuntime,
|
||||
commandPath,
|
||||
...(suppressDoctorStdout ? { suppressDoctorStdout: true } : {}),
|
||||
...(jsonOutputMode ? { suppressDoctorStdout: true } : {}),
|
||||
});
|
||||
// Load plugins for commands that need channel access
|
||||
if (shouldLoadPluginsForCommand(commandPath, argv)) {
|
||||
if (shouldLoadPluginsForCommand(commandPath, jsonOutputMode)) {
|
||||
const { ensurePluginRegistryLoaded } = await loadPluginRegistryModule();
|
||||
ensurePluginRegistryLoaded({ scope: resolvePluginRegistryScope(commandPath) });
|
||||
}
|
||||
|
||||
@@ -225,18 +225,12 @@ export function registerQrCli(program: Command) {
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
setupCode,
|
||||
gatewayUrl: resolved.payload.url,
|
||||
auth: resolved.authLabel,
|
||||
urlSource: resolved.urlSource,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
defaultRuntime.writeJson({
|
||||
setupCode,
|
||||
gatewayUrl: resolved.payload.url,
|
||||
auth: resolved.authLabel,
|
||||
urlSource: resolved.urlSource,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ export function registerSecretsCli(program: Command) {
|
||||
expectFinal: false,
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
const warningCount = Number(
|
||||
@@ -97,7 +97,7 @@ export function registerSecretsCli(program: Command) {
|
||||
allowExec: Boolean(opts.allowExec),
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(report, null, 2));
|
||||
defaultRuntime.writeJson(report);
|
||||
} else {
|
||||
defaultRuntime.log(
|
||||
`Secrets audit: ${report.status}. plaintext=${report.summary.plaintextCount}, unresolved=${report.summary.unresolvedRefCount}, shadowed=${report.summary.shadowedRefCount}, legacy=${report.summary.legacyResidueCount}.`,
|
||||
@@ -162,16 +162,10 @@ export function registerSecretsCli(program: Command) {
|
||||
fs.writeFileSync(opts.planOut, `${JSON.stringify(configured.plan, null, 2)}\n`, "utf8");
|
||||
}
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
plan: configured.plan,
|
||||
preflight: configured.preflight,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
defaultRuntime.writeJson({
|
||||
plan: configured.plan,
|
||||
preflight: configured.preflight,
|
||||
});
|
||||
} else {
|
||||
defaultRuntime.log(
|
||||
`Preflight: changed=${configured.preflight.changed}, files=${configured.preflight.changedFiles.length}, warnings=${configured.preflight.warningCount}.`,
|
||||
@@ -228,7 +222,7 @@ export function registerSecretsCli(program: Command) {
|
||||
allowExec: Boolean(opts.allowExec),
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(
|
||||
@@ -259,7 +253,7 @@ export function registerSecretsCli(program: Command) {
|
||||
allowExec: Boolean(opts.allowExec),
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
if (opts.dryRun) {
|
||||
|
||||
@@ -86,14 +86,10 @@ export function registerSecurityCli(program: Command) {
|
||||
});
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
fixResult
|
||||
? { fix: fixResult, report, secretDiagnostics }
|
||||
: { ...report, secretDiagnostics },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
defaultRuntime.writeJson(
|
||||
fixResult
|
||||
? { fix: fixResult, report, secretDiagnostics }
|
||||
: { ...report, secretDiagnostics },
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export function registerSkillsCli(program: Command) {
|
||||
limit: opts.limit,
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify({ results }, null, 2));
|
||||
defaultRuntime.writeJson({ results });
|
||||
return;
|
||||
}
|
||||
if (results.length === 0) {
|
||||
|
||||
@@ -28,7 +28,7 @@ async function runSystemGatewayCommand(
|
||||
try {
|
||||
const result = await action();
|
||||
if (opts.json || successText === undefined) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
} else {
|
||||
defaultRuntime.log(successText);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ type PrintResultOptions = UpdateCommandOptions & {
|
||||
|
||||
export function printResult(result: UpdateRunResult, opts: PrintResultOptions): void {
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,22 +70,16 @@ export async function updateStatusCommand(opts: UpdateStatusOptions): Promise<vo
|
||||
const updateLine = formatUpdateOneLiner(update).replace(/^Update:\s*/i, "");
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
update,
|
||||
channel: {
|
||||
value: channelInfo.channel,
|
||||
source: channelInfo.source,
|
||||
label: channelLabel,
|
||||
config: configChannel,
|
||||
},
|
||||
availability: updateAvailability,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
defaultRuntime.writeJson({
|
||||
update,
|
||||
channel: {
|
||||
value: channelInfo.channel,
|
||||
source: channelInfo.source,
|
||||
label: channelLabel,
|
||||
config: configChannel,
|
||||
},
|
||||
availability: updateAvailability,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ type UpdateDryRunPreview = {
|
||||
|
||||
function printDryRunPreview(preview: UpdateDryRunPreview, jsonMode: boolean): void {
|
||||
if (jsonMode) {
|
||||
defaultRuntime.log(JSON.stringify(preview, null, 2));
|
||||
defaultRuntime.writeJson(preview);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { withProgress } from "../cli/progress.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { callGateway, randomIdempotencyKey } from "../gateway/call.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import {
|
||||
GATEWAY_CLIENT_MODES,
|
||||
GATEWAY_CLIENT_NAMES,
|
||||
@@ -156,7 +156,7 @@ export async function agentViaGatewayCommand(opts: AgentCliOpts, runtime: Runtim
|
||||
);
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(response, null, 2));
|
||||
writeRuntimeJson(runtime, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { resolveAuthStorePath } from "../agents/auth-profiles/paths.js";
|
||||
import { writeConfigFile } from "../config/config.js";
|
||||
import { logConfigUpdated } from "../config/logging.js";
|
||||
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { resolveUserPath, shortenHomePath } from "../utils.js";
|
||||
import { createClackPrompter } from "../wizard/clack-prompter.js";
|
||||
@@ -153,7 +153,7 @@ export async function agentsAddCommand(
|
||||
},
|
||||
};
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(payload, null, 2));
|
||||
writeRuntimeJson(runtime, payload);
|
||||
} else {
|
||||
runtime.log(`Agent: ${agentId}`);
|
||||
runtime.log(`Workspace: ${shortenHomePath(workspaceDir)}`);
|
||||
@@ -356,7 +356,7 @@ export async function agentsAddCommand(
|
||||
agentDir,
|
||||
};
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(payload, null, 2));
|
||||
writeRuntimeJson(runtime, payload);
|
||||
}
|
||||
await prompter.outro(`Agent "${agentId}" ready.`);
|
||||
} catch (err) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { writeConfigFile } from "../config/config.js";
|
||||
import { logConfigUpdated } from "../config/logging.js";
|
||||
import type { AgentRouteBinding } from "../config/types.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import {
|
||||
applyAgentBindings,
|
||||
@@ -122,7 +122,7 @@ function emitJsonPayload(params: {
|
||||
if (!params.json) {
|
||||
return false;
|
||||
}
|
||||
params.runtime.log(JSON.stringify(params.payload, null, 2));
|
||||
writeRuntimeJson(params.runtime, params.payload);
|
||||
if ((params.conflictCount ?? 0) > 0) {
|
||||
params.runtime.exit(1);
|
||||
}
|
||||
@@ -176,16 +176,13 @@ export async function agentsBindingsCommand(
|
||||
(binding) => !filterAgentId || normalizeAgentId(binding.agentId) === filterAgentId,
|
||||
);
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
filtered.map((binding) => ({
|
||||
agentId: normalizeAgentId(binding.agentId),
|
||||
match: binding.match,
|
||||
description: describeBinding(binding),
|
||||
})),
|
||||
null,
|
||||
2,
|
||||
),
|
||||
writeRuntimeJson(
|
||||
runtime,
|
||||
filtered.map((binding) => ({
|
||||
agentId: normalizeAgentId(binding.agentId),
|
||||
match: binding.match,
|
||||
description: describeBinding(binding),
|
||||
})),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { writeConfigFile } from "../config/config.js";
|
||||
import { logConfigUpdated } from "../config/logging.js";
|
||||
import { resolveSessionTranscriptsDirForAgent } from "../config/sessions.js";
|
||||
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { createClackPrompter } from "../wizard/clack-prompter.js";
|
||||
import { createQuietRuntime, requireValidConfig } from "./agents.command-shared.js";
|
||||
@@ -81,20 +81,14 @@ export async function agentsDeleteCommand(
|
||||
await moveToTrash(sessionsDir, quietRuntime);
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
agentId,
|
||||
workspace: workspaceDir,
|
||||
agentDir,
|
||||
sessionsDir,
|
||||
removedBindings: result.removedBindings,
|
||||
removedAllow: result.removedAllow,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(runtime, {
|
||||
agentId,
|
||||
workspace: workspaceDir,
|
||||
agentDir,
|
||||
sessionsDir,
|
||||
removedBindings: result.removedBindings,
|
||||
removedAllow: result.removedAllow,
|
||||
});
|
||||
} else {
|
||||
runtime.log(`Deleted agent: ${agentId}`);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { writeConfigFile } from "../config/config.js";
|
||||
import { logConfigUpdated } from "../config/logging.js";
|
||||
import type { IdentityConfig } from "../config/types.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { resolveUserPath, shortenHomePath } from "../utils.js";
|
||||
import { requireValidConfig } from "./agents.command-shared.js";
|
||||
@@ -198,18 +198,12 @@ export async function agentsSetIdentityCommand(
|
||||
await writeConfigFile(nextConfig);
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
agentId,
|
||||
identity: nextIdentity,
|
||||
workspace: workspaceDir ?? null,
|
||||
identityFile: identityFilePath ?? null,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(runtime, {
|
||||
agentId,
|
||||
identity: nextIdentity,
|
||||
workspace: workspaceDir ?? null,
|
||||
identityFile: identityFilePath ?? null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { listRouteBindings } from "../config/bindings.js";
|
||||
import type { AgentRouteBinding } from "../config/types.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
import { describeBinding } from "./agents.bindings.js";
|
||||
@@ -122,7 +122,7 @@ export async function agentsListCommand(
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(summaries, null, 2));
|
||||
writeRuntimeJson(runtime, summaries);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "node:path";
|
||||
import * as tar from "tar";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
|
||||
const WINDOWS_ABSOLUTE_ARCHIVE_PATH_RE = /^[A-Za-z]:[\\/]/;
|
||||
@@ -319,6 +319,10 @@ export async function backupVerifyCommand(
|
||||
entryCount: rawEntries.length,
|
||||
};
|
||||
|
||||
runtime.log(opts.json ? JSON.stringify(result, null, 2) : formatResult(result));
|
||||
if (opts.json) {
|
||||
writeRuntimeJson(runtime, result);
|
||||
} else {
|
||||
runtime.log(formatResult(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
type BackupCreateOptions,
|
||||
type BackupCreateResult,
|
||||
} from "../infra/backup-create.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { backupVerifyCommand } from "./backup-verify.js";
|
||||
export type { BackupCreateOptions, BackupCreateResult } from "../infra/backup-create.js";
|
||||
|
||||
@@ -23,9 +23,10 @@ export async function backupCreateCommand(
|
||||
);
|
||||
result.verified = true;
|
||||
}
|
||||
const output = opts.json
|
||||
? JSON.stringify(result, null, 2)
|
||||
: formatBackupCreateSummary(result).join("\n");
|
||||
runtime.log(output);
|
||||
if (opts.json) {
|
||||
writeRuntimeJson(runtime, result);
|
||||
} else {
|
||||
runtime.log(formatBackupCreateSummary(result).join("\n"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
} from "../../channels/plugins/types.js";
|
||||
import { writeConfigFile, type OpenClawConfig } from "../../config/config.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
||||
import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { theme } from "../../terminal/theme.js";
|
||||
import { resolveInstallableChannelPlugin } from "../channel-setup/channel-plugin-resolution.js";
|
||||
import { formatChannelAccountLabel, requireValidConfig } from "./shared.js";
|
||||
@@ -266,7 +266,7 @@ export async function channelsCapabilitiesCommand(
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify({ channels: reports }, null, 2));
|
||||
writeRuntimeJson(runtime, { channels: reports });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { buildChannelAccountSnapshot } from "../../channels/plugins/status.js";
|
||||
import type { ChannelAccountSnapshot, ChannelPlugin } from "../../channels/plugins/types.js";
|
||||
import { withProgress } from "../../cli/progress.js";
|
||||
import { formatUsageReportLines, loadProviderUsageSummary } from "../../infra/provider-usage.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
||||
import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { formatDocsLink } from "../../terminal/links.js";
|
||||
import { theme } from "../../terminal/theme.js";
|
||||
import { formatChannelAccountLabel, requireValidConfig } from "./shared.js";
|
||||
@@ -126,7 +126,7 @@ export async function channelsListCommand(
|
||||
chat[plugin.id] = plugin.config.listAccountIds(cfg);
|
||||
}
|
||||
const payload = { chat, auth: authProfiles, ...(usage ? { usage } : {}) };
|
||||
runtime.log(JSON.stringify(payload, null, 2));
|
||||
writeRuntimeJson(runtime, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
||||
import { listChannelPlugins } from "../../channels/plugins/index.js";
|
||||
import { getResolvedLoggerSettings } from "../../logging.js";
|
||||
import { parseLogLine } from "../../logging/parse-log-line.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
||||
import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { theme } from "../../terminal/theme.js";
|
||||
|
||||
export type ChannelsLogsOptions = {
|
||||
@@ -93,7 +93,7 @@ export async function channelsLogsCommand(
|
||||
const lines = filtered.slice(Math.max(0, filtered.length - limit));
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify({ file, channel, lines }, null, 2));
|
||||
writeRuntimeJson(runtime, { file, channel, lines });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getChannelsCommandSecretTargetIds } from "../../cli/command-secret-targ
|
||||
import { loadConfig, writeConfigFile } from "../../config/config.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { resolveMessageChannelSelection } from "../../infra/outbound/channel-selection.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { resolveInstallableChannelPlugin } from "../channel-setup/channel-plugin-resolution.js";
|
||||
|
||||
export type ChannelsResolveOptions = {
|
||||
@@ -166,7 +166,7 @@ export async function channelsResolveCommand(opts: ChannelsResolveOptions, runti
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(results, null, 2));
|
||||
writeRuntimeJson(runtime, results);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { type OpenClawConfig, readConfigFileSnapshot } from "../../config/config
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
import { collectChannelStatusIssues } from "../../infra/channels-status-issues.js";
|
||||
import { formatTimeAgo } from "../../infra/format-time/format-relative.ts";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
||||
import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { formatDocsLink } from "../../terminal/links.js";
|
||||
import { theme } from "../../terminal/theme.js";
|
||||
import {
|
||||
@@ -301,7 +301,7 @@ export async function channelsStatusCommand(
|
||||
}),
|
||||
);
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(payload, null, 2));
|
||||
writeRuntimeJson(runtime, payload);
|
||||
return;
|
||||
}
|
||||
runtime.log(formatGatewayChannelsStatusLines(payload).join("\n"));
|
||||
|
||||
@@ -3,7 +3,7 @@ import { readBestEffortConfig, resolveGatewayPort } from "../config/config.js";
|
||||
import { probeGateway } from "../gateway/probe.js";
|
||||
import { discoverGatewayBeacons } from "../infra/bonjour-discovery.js";
|
||||
import { resolveWideAreaDiscoveryDomain } from "../infra/widearea-dns.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { colorize, isRich, theme } from "../terminal/theme.js";
|
||||
import {
|
||||
buildNetworkHints,
|
||||
@@ -264,61 +264,55 @@ export async function gatewayStatusCommand(
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
ok,
|
||||
degraded,
|
||||
ts: Date.now(),
|
||||
durationMs: Date.now() - startedAt,
|
||||
timeoutMs: overallTimeoutMs,
|
||||
primaryTargetId: primary?.target.id ?? null,
|
||||
warnings,
|
||||
network,
|
||||
discovery: {
|
||||
timeoutMs: discoveryTimeoutMs,
|
||||
count: discovery.length,
|
||||
beacons: discovery.map((b) => ({
|
||||
instanceName: b.instanceName,
|
||||
displayName: b.displayName ?? null,
|
||||
domain: b.domain ?? null,
|
||||
host: b.host ?? null,
|
||||
lanHost: b.lanHost ?? null,
|
||||
tailnetDns: b.tailnetDns ?? null,
|
||||
gatewayPort: b.gatewayPort ?? null,
|
||||
sshPort: b.sshPort ?? null,
|
||||
wsUrl: (() => {
|
||||
const host = b.tailnetDns || b.lanHost || b.host;
|
||||
const port = b.gatewayPort ?? 18789;
|
||||
return host ? `ws://${host}:${port}` : null;
|
||||
})(),
|
||||
})),
|
||||
},
|
||||
targets: probed.map((p) => ({
|
||||
id: p.target.id,
|
||||
kind: p.target.kind,
|
||||
url: p.target.url,
|
||||
active: p.target.active,
|
||||
tunnel: p.target.tunnel ?? null,
|
||||
connect: {
|
||||
ok: isProbeReachable(p.probe),
|
||||
rpcOk: p.probe.ok,
|
||||
scopeLimited: isScopeLimitedProbeFailure(p.probe),
|
||||
latencyMs: p.probe.connectLatencyMs,
|
||||
error: p.probe.error,
|
||||
close: p.probe.close,
|
||||
},
|
||||
self: p.self,
|
||||
config: p.configSummary,
|
||||
health: p.probe.health,
|
||||
summary: p.probe.status,
|
||||
presence: p.probe.presence,
|
||||
})),
|
||||
writeRuntimeJson(runtime, {
|
||||
ok,
|
||||
degraded,
|
||||
ts: Date.now(),
|
||||
durationMs: Date.now() - startedAt,
|
||||
timeoutMs: overallTimeoutMs,
|
||||
primaryTargetId: primary?.target.id ?? null,
|
||||
warnings,
|
||||
network,
|
||||
discovery: {
|
||||
timeoutMs: discoveryTimeoutMs,
|
||||
count: discovery.length,
|
||||
beacons: discovery.map((b) => ({
|
||||
instanceName: b.instanceName,
|
||||
displayName: b.displayName ?? null,
|
||||
domain: b.domain ?? null,
|
||||
host: b.host ?? null,
|
||||
lanHost: b.lanHost ?? null,
|
||||
tailnetDns: b.tailnetDns ?? null,
|
||||
gatewayPort: b.gatewayPort ?? null,
|
||||
sshPort: b.sshPort ?? null,
|
||||
wsUrl: (() => {
|
||||
const host = b.tailnetDns || b.lanHost || b.host;
|
||||
const port = b.gatewayPort ?? 18789;
|
||||
return host ? `ws://${host}:${port}` : null;
|
||||
})(),
|
||||
})),
|
||||
},
|
||||
targets: probed.map((p) => ({
|
||||
id: p.target.id,
|
||||
kind: p.target.kind,
|
||||
url: p.target.url,
|
||||
active: p.target.active,
|
||||
tunnel: p.target.tunnel ?? null,
|
||||
connect: {
|
||||
ok: isProbeReachable(p.probe),
|
||||
rpcOk: p.probe.ok,
|
||||
scopeLimited: isScopeLimitedProbeFailure(p.probe),
|
||||
latencyMs: p.probe.connectLatencyMs,
|
||||
error: p.probe.error,
|
||||
close: p.probe.close,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
self: p.self,
|
||||
config: p.configSummary,
|
||||
health: p.probe.health,
|
||||
summary: p.probe.status,
|
||||
presence: p.probe.presence,
|
||||
})),
|
||||
});
|
||||
if (!ok) {
|
||||
runtime.exit(1);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "../infra/heartbeat-summary.js";
|
||||
import { buildChannelAccountBindings, resolvePreferredAccountId } from "../routing/bindings.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { styleHealthChannelLine } from "../terminal/health-style.js";
|
||||
import { isRich } from "../terminal/theme.js";
|
||||
|
||||
@@ -618,7 +618,7 @@ export async function healthCommand(
|
||||
const fatal = false;
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(summary, null, 2));
|
||||
writeRuntimeJson(runtime, summary);
|
||||
} else {
|
||||
const debugEnabled = isTruthyEnvValue(process.env.OPENCLAW_DEBUG_HEALTH);
|
||||
const rich = isRich();
|
||||
|
||||
@@ -10,7 +10,7 @@ import { withProgress } from "../cli/progress.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import type { OutboundSendDeps } from "../infra/outbound/deliver.js";
|
||||
import { runMessageAction } from "../infra/outbound/message-action-runner.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||
import { buildMessageCliJson, formatMessageCliText } from "./message-format.js";
|
||||
|
||||
@@ -80,7 +80,7 @@ export async function messageCommand(
|
||||
: await run();
|
||||
|
||||
if (json) {
|
||||
runtime.log(JSON.stringify(buildMessageCliJson(result), null, 2));
|
||||
writeRuntimeJson(runtime, buildMessageCliJson(result));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { logConfigUpdated } from "../../config/logging.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { loadModelsConfig } from "./load-config.js";
|
||||
import {
|
||||
ensureFlagCompatibility,
|
||||
@@ -27,7 +27,7 @@ export async function modelsAliasesListCommand(
|
||||
);
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify({ aliases }, null, 2));
|
||||
writeRuntimeJson(runtime, { aliases });
|
||||
return;
|
||||
}
|
||||
if (opts.plain) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
setAuthProfileOrder,
|
||||
} from "../../agents/auth-profiles.js";
|
||||
import { normalizeProviderId } from "../../agents/model-selection.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { normalizeStringEntries } from "../../shared/string-normalization.js";
|
||||
import { shortenHomePath } from "../../utils.js";
|
||||
import { loadModelsConfig } from "./load-config.js";
|
||||
@@ -54,19 +54,13 @@ export async function modelsAuthOrderGetCommand(
|
||||
const order = describeOrder(store, provider);
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
agentId,
|
||||
agentDir,
|
||||
provider,
|
||||
authStorePath: shortenHomePath(`${agentDir}/auth-profiles.json`),
|
||||
order: order.length > 0 ? order : null,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(runtime, {
|
||||
agentId,
|
||||
agentDir,
|
||||
provider,
|
||||
authStorePath: shortenHomePath(`${agentDir}/auth-profiles.json`),
|
||||
order: order.length > 0 ? order : null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { logConfigUpdated } from "../../config/logging.js";
|
||||
import { resolveAgentModelFallbackValues, toAgentModelListLike } from "../../config/model-input.js";
|
||||
import type { AgentModelEntryConfig } from "../../config/types.agent-defaults.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { loadModelsConfig } from "./load-config.js";
|
||||
import {
|
||||
DEFAULT_PROVIDER,
|
||||
@@ -50,7 +50,7 @@ export async function listFallbacksCommand(
|
||||
const fallbacks = getFallbacks(cfg, params.key);
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify({ fallbacks }, null, 2));
|
||||
writeRuntimeJson(runtime, { fallbacks });
|
||||
return;
|
||||
}
|
||||
if (opts.plain) {
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
type UsageProviderId,
|
||||
} from "../../infra/provider-usage.js";
|
||||
import { getShellEnvAppliedKeys, shouldEnableShellEnvFallback } from "../../infra/shell-env.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { getTerminalTableWidth, renderTable } from "../../terminal/table.js";
|
||||
import { colorize, theme } from "../../terminal/theme.js";
|
||||
import { shortenHomePath } from "../../utils.js";
|
||||
@@ -324,49 +324,43 @@ export async function modelsStatusCommand(
|
||||
})();
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
configPath,
|
||||
...(agentId ? { agentId } : {}),
|
||||
agentDir,
|
||||
defaultModel: defaultLabel,
|
||||
resolvedDefault: resolvedLabel,
|
||||
fallbacks,
|
||||
imageModel: imageModel || null,
|
||||
imageFallbacks,
|
||||
...(agentId
|
||||
? {
|
||||
modelConfig: {
|
||||
defaultSource: agentModelPrimary ? "agent" : "defaults",
|
||||
fallbacksSource: agentFallbacksOverride !== undefined ? "agent" : "defaults",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
aliases,
|
||||
allowed,
|
||||
auth: {
|
||||
storePath: resolveAuthStorePathForDisplay(agentDir),
|
||||
shellEnvFallback: {
|
||||
enabled: shellFallbackEnabled,
|
||||
appliedKeys: applied,
|
||||
writeRuntimeJson(runtime, {
|
||||
configPath,
|
||||
...(agentId ? { agentId } : {}),
|
||||
agentDir,
|
||||
defaultModel: defaultLabel,
|
||||
resolvedDefault: resolvedLabel,
|
||||
fallbacks,
|
||||
imageModel: imageModel || null,
|
||||
imageFallbacks,
|
||||
...(agentId
|
||||
? {
|
||||
modelConfig: {
|
||||
defaultSource: agentModelPrimary ? "agent" : "defaults",
|
||||
fallbacksSource: agentFallbacksOverride !== undefined ? "agent" : "defaults",
|
||||
},
|
||||
providersWithOAuth: providersWithOauth,
|
||||
missingProvidersInUse,
|
||||
providers: providerAuth,
|
||||
unusableProfiles,
|
||||
oauth: {
|
||||
warnAfterMs: authHealth.warnAfterMs,
|
||||
profiles: authHealth.profiles,
|
||||
providers: authHealth.providers,
|
||||
},
|
||||
probes: probeSummary,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
aliases,
|
||||
allowed,
|
||||
auth: {
|
||||
storePath: resolveAuthStorePathForDisplay(agentDir),
|
||||
shellEnvFallback: {
|
||||
enabled: shellFallbackEnabled,
|
||||
appliedKeys: applied,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
providersWithOAuth: providersWithOauth,
|
||||
missingProvidersInUse,
|
||||
providers: providerAuth,
|
||||
unusableProfiles,
|
||||
oauth: {
|
||||
warnAfterMs: authHealth.warnAfterMs,
|
||||
profiles: authHealth.profiles,
|
||||
providers: authHealth.providers,
|
||||
},
|
||||
probes: probeSummary,
|
||||
},
|
||||
});
|
||||
if (opts.check) {
|
||||
runtime.exit(checkStatus);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { colorize, theme } from "../../terminal/theme.js";
|
||||
import { formatTag, isRich, pad, truncate } from "./list.format.js";
|
||||
import type { ModelRow } from "./list.types.js";
|
||||
@@ -16,16 +16,10 @@ export function printModelTable(
|
||||
opts: { json?: boolean; plain?: boolean } = {},
|
||||
) {
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
count: rows.length,
|
||||
models: rows,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(runtime, {
|
||||
count: rows.length,
|
||||
models: rows,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { type ModelScanResult, scanOpenRouterModels } from "../../agents/model-s
|
||||
import { withProgressTotals } from "../../cli/progress.js";
|
||||
import { logConfigUpdated } from "../../config/logging.js";
|
||||
import { toAgentModelListLike } from "../../config/model-input.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import {
|
||||
stylePromptHint,
|
||||
stylePromptMessage,
|
||||
@@ -217,7 +217,7 @@ export async function modelsScanCommand(
|
||||
);
|
||||
printScanTable(sortScanResults(results), runtime);
|
||||
} else {
|
||||
runtime.log(JSON.stringify(results, null, 2));
|
||||
writeRuntimeJson(runtime, results);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -328,20 +328,14 @@ export async function modelsScanCommand(
|
||||
});
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
selected,
|
||||
selectedImages,
|
||||
setDefault: Boolean(opts.setDefault),
|
||||
setImage: Boolean(opts.setImage),
|
||||
results,
|
||||
warnings: [],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(runtime, {
|
||||
selected,
|
||||
selectedImages,
|
||||
setDefault: Boolean(opts.setDefault),
|
||||
setImage: Boolean(opts.setImage),
|
||||
results,
|
||||
warnings: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RuntimeEnv } from "../../../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../../runtime.js";
|
||||
import type { OnboardOptions } from "../../onboard-types.js";
|
||||
|
||||
export type GatewayHealthFailureDiagnostics = {
|
||||
@@ -41,24 +41,18 @@ export function logNonInteractiveOnboardingJson(params: {
|
||||
if (!params.opts.json) {
|
||||
return;
|
||||
}
|
||||
params.runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
ok: true,
|
||||
mode: params.mode,
|
||||
workspace: params.workspaceDir,
|
||||
authChoice: params.authChoice,
|
||||
gateway: params.gateway,
|
||||
installDaemon: Boolean(params.installDaemon),
|
||||
daemonInstall: params.daemonInstall,
|
||||
daemonRuntime: params.daemonRuntime,
|
||||
skipSkills: Boolean(params.skipSkills),
|
||||
skipHealth: Boolean(params.skipHealth),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(params.runtime, {
|
||||
ok: true,
|
||||
mode: params.mode,
|
||||
workspace: params.workspaceDir,
|
||||
authChoice: params.authChoice,
|
||||
gateway: params.gateway,
|
||||
installDaemon: Boolean(params.installDaemon),
|
||||
daemonInstall: params.daemonInstall,
|
||||
daemonRuntime: params.daemonRuntime,
|
||||
skipSkills: Boolean(params.skipSkills),
|
||||
skipHealth: Boolean(params.skipHealth),
|
||||
});
|
||||
}
|
||||
|
||||
function formatGatewayRuntimeSummary(
|
||||
@@ -109,25 +103,19 @@ export function logNonInteractiveOnboardingFailure(params: {
|
||||
const gatewayRuntime = formatGatewayRuntimeSummary(params.diagnostics);
|
||||
|
||||
if (params.opts.json) {
|
||||
params.runtime.error(
|
||||
JSON.stringify(
|
||||
{
|
||||
ok: false,
|
||||
mode: params.mode,
|
||||
phase: params.phase,
|
||||
message: params.message,
|
||||
detail: params.detail,
|
||||
gateway: params.gateway,
|
||||
installDaemon: Boolean(params.installDaemon),
|
||||
daemonInstall: params.daemonInstall,
|
||||
daemonRuntime: params.daemonRuntime,
|
||||
diagnostics: params.diagnostics,
|
||||
hints: hints.length > 0 ? hints : undefined,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(params.runtime, {
|
||||
ok: false,
|
||||
mode: params.mode,
|
||||
phase: params.phase,
|
||||
message: params.message,
|
||||
detail: params.detail,
|
||||
gateway: params.gateway,
|
||||
installDaemon: Boolean(params.installDaemon),
|
||||
daemonInstall: params.daemonInstall,
|
||||
daemonRuntime: params.daemonRuntime,
|
||||
diagnostics: params.diagnostics,
|
||||
hints: hints.length > 0 ? hints : undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { formatCliCommand } from "../../cli/command-format.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { writeConfigFile } from "../../config/config.js";
|
||||
import { logConfigUpdated } from "../../config/logging.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { applyWizardMetadata } from "../onboard-helpers.js";
|
||||
import type { OnboardOptions } from "../onboard-types.js";
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function runNonInteractiveRemoteSetup(params: {
|
||||
auth: opts.remoteToken ? "token" : "none",
|
||||
};
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(payload, null, 2));
|
||||
writeRuntimeJson(runtime, payload);
|
||||
} else {
|
||||
runtime.log(`Remote gateway: ${remoteUrl}`);
|
||||
runtime.log(`Auth: ${payload.auth}`);
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
parseAgentSessionKey,
|
||||
resolveAgentIdFromSessionKey,
|
||||
} from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { formatDocsLink } from "../terminal/links.js";
|
||||
import { colorize, isRich, theme } from "../terminal/theme.js";
|
||||
import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel.js";
|
||||
@@ -262,7 +262,7 @@ export async function sandboxExplainCommand(
|
||||
} as const;
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(`${JSON.stringify(payload, null, 2)}\n`);
|
||||
writeRuntimeJson(runtime, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type SandboxBrowserInfo,
|
||||
type SandboxContainerInfo,
|
||||
} from "../agents/sandbox.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import {
|
||||
displayBrowsers,
|
||||
displayContainers,
|
||||
@@ -48,7 +48,7 @@ export async function sandboxListCommand(
|
||||
const browsers = opts.browser ? await listSandboxBrowsers().catch(() => []) : [];
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify({ containers, browsers }, null, 2));
|
||||
writeRuntimeJson(runtime, { containers, browsers });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
type SessionEntry,
|
||||
type SessionMaintenanceApplyReport,
|
||||
} from "../config/sessions.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { isRich, theme } from "../terminal/theme.js";
|
||||
import {
|
||||
resolveSessionStoreTargetsOrExit,
|
||||
@@ -325,21 +325,15 @@ export async function sessionsCleanupCommand(opts: SessionsCleanupOptions, runti
|
||||
if (opts.dryRun) {
|
||||
if (opts.json) {
|
||||
if (previewResults.length === 1) {
|
||||
runtime.log(JSON.stringify(previewResults[0]?.summary ?? {}, null, 2));
|
||||
writeRuntimeJson(runtime, previewResults[0]?.summary ?? {});
|
||||
return;
|
||||
}
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
allAgents: true,
|
||||
mode,
|
||||
dryRun: true,
|
||||
stores: previewResults.map((result) => result.summary),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(runtime, {
|
||||
allAgents: true,
|
||||
mode,
|
||||
dryRun: true,
|
||||
stores: previewResults.map((result) => result.summary),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -436,21 +430,15 @@ export async function sessionsCleanupCommand(opts: SessionsCleanupOptions, runti
|
||||
|
||||
if (opts.json) {
|
||||
if (appliedSummaries.length === 1) {
|
||||
runtime.log(JSON.stringify(appliedSummaries[0] ?? {}, null, 2));
|
||||
writeRuntimeJson(runtime, appliedSummaries[0] ?? {});
|
||||
return;
|
||||
}
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
allAgents: true,
|
||||
mode,
|
||||
dryRun: false,
|
||||
stores: appliedSummaries,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(runtime, {
|
||||
allAgents: true,
|
||||
mode,
|
||||
dryRun: false,
|
||||
stores: appliedSummaries,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { loadSessionStore, resolveFreshSessionTotalTokens } from "../config/sess
|
||||
import { classifySessionKey } from "../gateway/session-utils.js";
|
||||
import { info } from "../globals.js";
|
||||
import { parseAgentSessionKey } from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { isRich, theme } from "../terminal/theme.js";
|
||||
import { resolveSessionStoreTargetsOrExit } from "./session-store-targets.js";
|
||||
import {
|
||||
@@ -142,36 +142,30 @@ export async function sessionsCommand(
|
||||
if (opts.json) {
|
||||
const multi = targets.length > 1;
|
||||
const aggregate = aggregateAgents || multi;
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
path: aggregate ? null : (targets[0]?.storePath ?? null),
|
||||
stores: aggregate
|
||||
? targets.map((target) => ({
|
||||
agentId: target.agentId,
|
||||
path: target.storePath,
|
||||
}))
|
||||
: undefined,
|
||||
allAgents: aggregateAgents ? true : undefined,
|
||||
count: rows.length,
|
||||
activeMinutes: activeMinutes ?? null,
|
||||
sessions: rows.map((r) => {
|
||||
const model = resolveSessionDisplayModel(cfg, r, displayDefaults);
|
||||
return {
|
||||
...r,
|
||||
totalTokens: resolveFreshSessionTotalTokens(r) ?? null,
|
||||
totalTokensFresh:
|
||||
typeof r.totalTokens === "number" ? r.totalTokensFresh !== false : false,
|
||||
contextTokens:
|
||||
r.contextTokens ?? lookupContextTokens(model) ?? configContextTokens ?? null,
|
||||
model,
|
||||
};
|
||||
}),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(runtime, {
|
||||
path: aggregate ? null : (targets[0]?.storePath ?? null),
|
||||
stores: aggregate
|
||||
? targets.map((target) => ({
|
||||
agentId: target.agentId,
|
||||
path: target.storePath,
|
||||
}))
|
||||
: undefined,
|
||||
allAgents: aggregateAgents ? true : undefined,
|
||||
count: rows.length,
|
||||
activeMinutes: activeMinutes ?? null,
|
||||
sessions: rows.map((r) => {
|
||||
const model = resolveSessionDisplayModel(cfg, r, displayDefaults);
|
||||
return {
|
||||
...r,
|
||||
totalTokens: resolveFreshSessionTotalTokens(r) ?? null,
|
||||
totalTokensFresh:
|
||||
typeof r.totalTokens === "number" ? r.totalTokensFresh !== false : false,
|
||||
contextTokens:
|
||||
r.contextTokens ?? lookupContextTokens(model) ?? configContextTokens ?? null,
|
||||
model,
|
||||
};
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js";
|
||||
import { normalizeUpdateChannel, resolveUpdateChannelDisplay } from "../infra/update-channels.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { getDaemonStatusSummary, getNodeDaemonStatusSummary } from "./status.daemon.js";
|
||||
import { scanStatusJsonFast } from "./status.scan.fast-json.js";
|
||||
|
||||
@@ -83,36 +83,30 @@ export async function statusJsonCommand(
|
||||
gitBranch: scan.update.git?.branch ?? null,
|
||||
});
|
||||
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
...scan.summary,
|
||||
os: scan.osSummary,
|
||||
update: scan.update,
|
||||
updateChannel: channelInfo.channel,
|
||||
updateChannelSource: channelInfo.source,
|
||||
memory: scan.memory,
|
||||
memoryPlugin: scan.memoryPlugin,
|
||||
gateway: {
|
||||
mode: scan.gatewayMode,
|
||||
url: scan.gatewayConnection.url,
|
||||
urlSource: scan.gatewayConnection.urlSource,
|
||||
misconfigured: scan.remoteUrlMissing,
|
||||
reachable: scan.gatewayReachable,
|
||||
connectLatencyMs: scan.gatewayProbe?.connectLatencyMs ?? null,
|
||||
self: scan.gatewaySelf,
|
||||
error: scan.gatewayProbe?.error ?? null,
|
||||
authWarning: scan.gatewayProbeAuthWarning ?? null,
|
||||
},
|
||||
gatewayService: daemon,
|
||||
nodeService: nodeDaemon,
|
||||
agents: scan.agentStatus,
|
||||
secretDiagnostics: scan.secretDiagnostics,
|
||||
...(securityAudit ? { securityAudit } : {}),
|
||||
...(health || usage || lastHeartbeat ? { health, usage, lastHeartbeat } : {}),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(runtime, {
|
||||
...scan.summary,
|
||||
os: scan.osSummary,
|
||||
update: scan.update,
|
||||
updateChannel: channelInfo.channel,
|
||||
updateChannelSource: channelInfo.source,
|
||||
memory: scan.memory,
|
||||
memoryPlugin: scan.memoryPlugin,
|
||||
gateway: {
|
||||
mode: scan.gatewayMode,
|
||||
url: scan.gatewayConnection.url,
|
||||
urlSource: scan.gatewayConnection.urlSource,
|
||||
misconfigured: scan.remoteUrlMissing,
|
||||
reachable: scan.gatewayReachable,
|
||||
connectLatencyMs: scan.gatewayProbe?.connectLatencyMs ?? null,
|
||||
self: scan.gatewaySelf,
|
||||
error: scan.gatewayProbe?.error ?? null,
|
||||
authWarning: scan.gatewayProbeAuthWarning ?? null,
|
||||
},
|
||||
gatewayService: daemon,
|
||||
nodeService: nodeDaemon,
|
||||
agents: scan.agentStatus,
|
||||
secretDiagnostics: scan.secretDiagnostics,
|
||||
...(securityAudit ? { securityAudit } : {}),
|
||||
...(health || usage || lastHeartbeat ? { health, usage, lastHeartbeat } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
formatPluginCompatibilityNotice,
|
||||
summarizePluginCompatibility,
|
||||
} from "../plugins/status.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { getTerminalTableWidth, renderTable } from "../terminal/table.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
import { formatHealthChannelLines, type HealthSummary } from "./health.js";
|
||||
@@ -196,42 +196,36 @@ export async function statusCommand(
|
||||
getDaemonStatusSummary(),
|
||||
getNodeDaemonStatusSummary(),
|
||||
]);
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
...summary,
|
||||
os: osSummary,
|
||||
update,
|
||||
updateChannel: channelInfo.channel,
|
||||
updateChannelSource: channelInfo.source,
|
||||
memory,
|
||||
memoryPlugin,
|
||||
gateway: {
|
||||
mode: gatewayMode,
|
||||
url: gatewayConnection.url,
|
||||
urlSource: gatewayConnection.urlSource,
|
||||
misconfigured: remoteUrlMissing,
|
||||
reachable: gatewayReachable,
|
||||
connectLatencyMs: gatewayProbe?.connectLatencyMs ?? null,
|
||||
self: gatewaySelf,
|
||||
error: gatewayProbe?.error ?? null,
|
||||
authWarning: gatewayProbeAuthWarning ?? null,
|
||||
},
|
||||
gatewayService: daemon,
|
||||
nodeService: nodeDaemon,
|
||||
agents: agentStatus,
|
||||
securityAudit,
|
||||
secretDiagnostics,
|
||||
pluginCompatibility: {
|
||||
count: pluginCompatibility.length,
|
||||
warnings: pluginCompatibility,
|
||||
},
|
||||
...(health || usage || lastHeartbeat ? { health, usage, lastHeartbeat } : {}),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
writeRuntimeJson(runtime, {
|
||||
...summary,
|
||||
os: osSummary,
|
||||
update,
|
||||
updateChannel: channelInfo.channel,
|
||||
updateChannelSource: channelInfo.source,
|
||||
memory,
|
||||
memoryPlugin,
|
||||
gateway: {
|
||||
mode: gatewayMode,
|
||||
url: gatewayConnection.url,
|
||||
urlSource: gatewayConnection.urlSource,
|
||||
misconfigured: remoteUrlMissing,
|
||||
reachable: gatewayReachable,
|
||||
connectLatencyMs: gatewayProbe?.connectLatencyMs ?? null,
|
||||
self: gatewaySelf,
|
||||
error: gatewayProbe?.error ?? null,
|
||||
authWarning: gatewayProbeAuthWarning ?? null,
|
||||
},
|
||||
gatewayService: daemon,
|
||||
nodeService: nodeDaemon,
|
||||
agents: agentStatus,
|
||||
securityAudit,
|
||||
secretDiagnostics,
|
||||
pluginCompatibility: {
|
||||
count: pluginCompatibility.length,
|
||||
warnings: pluginCompatibility,
|
||||
},
|
||||
...(health || usage || lastHeartbeat ? { health, usage, lastHeartbeat } : {}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ function createManager(options?: {
|
||||
const log = createSubsystemLogger("gateway/server-channels-test");
|
||||
const channelLogs = { discord: log } as Record<ChannelId, SubsystemLogger>;
|
||||
const runtime = runtimeForLogger(log);
|
||||
const channelRuntimeEnvs = { discord: runtime } as Record<ChannelId, RuntimeEnv>;
|
||||
const channelRuntimeEnvs = { discord: runtime } as unknown as Record<ChannelId, RuntimeEnv>;
|
||||
return createChannelManager({
|
||||
loadConfig: () => options?.loadConfig?.() ?? {},
|
||||
channelLogs,
|
||||
|
||||
@@ -575,7 +575,7 @@ export async function startGatewayServer(
|
||||
) as Record<ChannelId, ReturnType<typeof createSubsystemLogger>>;
|
||||
const channelRuntimeEnvs = Object.fromEntries(
|
||||
Object.entries(channelLogs).map(([id, logger]) => [id, runtimeForLogger(logger)]),
|
||||
) as Record<ChannelId, RuntimeEnv>;
|
||||
) as unknown as Record<ChannelId, RuntimeEnv>;
|
||||
const channelMethods = listChannelPlugins().flatMap((plugin) => plugin.gatewayMethods ?? []);
|
||||
const gatewayMethods = Array.from(new Set([...baseGatewayMethods, ...channelMethods]));
|
||||
let pluginServices: PluginServicesHandle | null = null;
|
||||
|
||||
@@ -254,7 +254,7 @@ export async function runGmailSetup(opts: GmailSetupOptions) {
|
||||
};
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(summary, null, 2));
|
||||
defaultRuntime.writeJson(summary);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
setConsoleTimestampPrefix,
|
||||
setLoggerOverride,
|
||||
} from "../logging.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { loggingState } from "./state.js";
|
||||
import {
|
||||
captureConsoleSnapshot,
|
||||
@@ -101,7 +102,7 @@ describe("enableConsoleCapture", () => {
|
||||
expect(warn).toHaveBeenCalledWith("12:34:56 [exec] hello");
|
||||
});
|
||||
|
||||
it("leaves JSON output unchanged when timestamp prefix is enabled", () => {
|
||||
it("prefixes JSON console output when timestamp prefix is enabled", () => {
|
||||
setLoggerOverride({ level: "info", file: tempLogPath() });
|
||||
const log = vi.fn();
|
||||
console.log = log;
|
||||
@@ -109,7 +110,24 @@ describe("enableConsoleCapture", () => {
|
||||
enableConsoleCapture();
|
||||
const payload = JSON.stringify({ ok: true });
|
||||
console.log(payload);
|
||||
expect(log).toHaveBeenCalledWith(payload);
|
||||
expect(log).toHaveBeenCalledTimes(1);
|
||||
const firstArg = String(log.mock.calls[0]?.[0] ?? "");
|
||||
expect(firstArg).toMatch(/^(?:\d{2}:\d{2}:\d{2}|\d{4}-\d{2}-\d{2}T)/);
|
||||
expect(firstArg.endsWith(` ${payload}`)).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps diagnostics on stderr while runtime JSON stays on stdout", () => {
|
||||
setLoggerOverride({ level: "info", file: tempLogPath() });
|
||||
const stdoutWrite = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
||||
const stderrWrite = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
||||
routeLogsToStderr();
|
||||
enableConsoleCapture();
|
||||
|
||||
console.log("diag");
|
||||
defaultRuntime.writeJson({ ok: true });
|
||||
|
||||
expect(stderrWrite).toHaveBeenCalledWith("diag\n");
|
||||
expect(stdoutWrite).toHaveBeenCalledWith('{\n "ok": true\n}\n');
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -189,19 +189,6 @@ function hasTimestampPrefix(value: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isJsonPayload(value: string): boolean {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
JSON.parse(trimmed);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Route console.* calls through file logging while still emitting to stdout/stderr.
|
||||
* This keeps user-facing output unchanged but guarantees every console call is captured in log files.
|
||||
@@ -262,10 +249,7 @@ export function enableConsoleCapture(): void {
|
||||
}
|
||||
const trimmed = stripAnsi(formatted).trimStart();
|
||||
const shouldPrefixTimestamp =
|
||||
loggingState.consoleTimestampPrefix &&
|
||||
trimmed.length > 0 &&
|
||||
!hasTimestampPrefix(trimmed) &&
|
||||
!isJsonPayload(trimmed);
|
||||
loggingState.consoleTimestampPrefix && trimmed.length > 0 && !hasTimestampPrefix(trimmed);
|
||||
const timestamp = shouldPrefixTimestamp
|
||||
? formatConsoleTimestamp(getConsoleSettings().style)
|
||||
: "";
|
||||
@@ -288,9 +272,8 @@ export function enableConsoleCapture(): void {
|
||||
} catch {
|
||||
// never block console output on logging failures
|
||||
}
|
||||
if (loggingState.forceConsoleToStderr && !isJsonPayload(trimmed)) {
|
||||
// In --json mode, route diagnostic logs to stderr but let JSON
|
||||
// payloads (the actual command output) through to stdout via orig().
|
||||
if (loggingState.forceConsoleToStderr) {
|
||||
// In --json mode, all console.* writes are diagnostics and should stay off stdout.
|
||||
try {
|
||||
const line = timestamp ? `${timestamp} ${formatted}` : formatted;
|
||||
process.stderr.write(`${line}\n`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Chalk } from "chalk";
|
||||
import type { Logger as TsLogger } from "tslog";
|
||||
import { isVerbose } from "../globals.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||
import { defaultRuntime, type OutputRuntimeEnv, type RuntimeEnv } from "../runtime.js";
|
||||
import { clearActiveProgressLine } from "../terminal/progress-line.js";
|
||||
import {
|
||||
formatConsoleTimestamp,
|
||||
@@ -404,7 +404,7 @@ export function createSubsystemLogger(subsystem: string): SubsystemLogger {
|
||||
export function runtimeForLogger(
|
||||
logger: SubsystemLogger,
|
||||
exit: RuntimeEnv["exit"] = defaultRuntime.exit,
|
||||
): RuntimeEnv {
|
||||
): OutputRuntimeEnv {
|
||||
const formatArgs = (...args: unknown[]) =>
|
||||
args
|
||||
.map((arg) => formatRuntimeArg(arg))
|
||||
@@ -413,6 +413,10 @@ export function runtimeForLogger(
|
||||
return {
|
||||
log: (...args: unknown[]) => logger.info(formatArgs(...args)),
|
||||
error: (...args: unknown[]) => logger.error(formatArgs(...args)),
|
||||
writeStdout: (value: string) => logger.info(value),
|
||||
writeJson: (value: unknown, space = 2) => {
|
||||
logger.info(JSON.stringify(value, null, space > 0 ? space : undefined));
|
||||
},
|
||||
exit,
|
||||
};
|
||||
}
|
||||
@@ -420,6 +424,6 @@ export function runtimeForLogger(
|
||||
export function createSubsystemRuntime(
|
||||
subsystem: string,
|
||||
exit: RuntimeEnv["exit"] = defaultRuntime.exit,
|
||||
): RuntimeEnv {
|
||||
): OutputRuntimeEnv {
|
||||
return runtimeForLogger(createSubsystemLogger(subsystem), exit);
|
||||
}
|
||||
|
||||
@@ -32,8 +32,12 @@ describe("resolveRuntimeEnv", () => {
|
||||
const resolved = resolveRuntimeEnv({ logger });
|
||||
resolved.log?.("hello %s", "world");
|
||||
resolved.error?.("bad %d", 7);
|
||||
resolved.writeStdout("plain");
|
||||
resolved.writeJson({ ok: true });
|
||||
|
||||
expect(logger.info).toHaveBeenCalledWith("hello world");
|
||||
expect(logger.error).toHaveBeenCalledWith("bad 7");
|
||||
expect(logger.info).toHaveBeenCalledWith("plain");
|
||||
expect(logger.info).toHaveBeenCalledWith('{\n "ok": true\n}');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { format } from "node:util";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
export type { RuntimeEnv } from "../runtime.js";
|
||||
import type { OutputRuntimeEnv, RuntimeEnv } from "../runtime.js";
|
||||
export type { OutputRuntimeEnv, RuntimeEnv } from "../runtime.js";
|
||||
export { createNonExitingRuntime, defaultRuntime } from "../runtime.js";
|
||||
export {
|
||||
danger,
|
||||
@@ -29,7 +29,7 @@ type LoggerLike = {
|
||||
export function createLoggerBackedRuntime(params: {
|
||||
logger: LoggerLike;
|
||||
exitError?: (code: number) => Error;
|
||||
}): RuntimeEnv {
|
||||
}): OutputRuntimeEnv {
|
||||
return {
|
||||
log: (...args) => {
|
||||
params.logger.info(format(...args));
|
||||
@@ -37,6 +37,12 @@ export function createLoggerBackedRuntime(params: {
|
||||
error: (...args) => {
|
||||
params.logger.error(format(...args));
|
||||
},
|
||||
writeStdout: (value) => {
|
||||
params.logger.info(value);
|
||||
},
|
||||
writeJson: (value, space = 2) => {
|
||||
params.logger.info(JSON.stringify(value, null, space > 0 ? space : undefined));
|
||||
},
|
||||
exit: (code: number): never => {
|
||||
throw params.exitError?.(code) ?? new Error(`exit ${code}`);
|
||||
},
|
||||
@@ -44,22 +50,48 @@ export function createLoggerBackedRuntime(params: {
|
||||
}
|
||||
|
||||
/** Reuse an existing runtime when present, otherwise synthesize one from the provided logger. */
|
||||
export function resolveRuntimeEnv(params: {
|
||||
runtime: RuntimeEnv;
|
||||
logger: LoggerLike;
|
||||
exitError?: (code: number) => Error;
|
||||
}): RuntimeEnv;
|
||||
export function resolveRuntimeEnv(params: {
|
||||
runtime?: undefined;
|
||||
logger: LoggerLike;
|
||||
exitError?: (code: number) => Error;
|
||||
}): OutputRuntimeEnv;
|
||||
export function resolveRuntimeEnv(params: {
|
||||
runtime?: RuntimeEnv;
|
||||
logger: LoggerLike;
|
||||
exitError?: (code: number) => Error;
|
||||
}): RuntimeEnv {
|
||||
}): RuntimeEnv | OutputRuntimeEnv {
|
||||
return params.runtime ?? createLoggerBackedRuntime(params);
|
||||
}
|
||||
|
||||
/** Resolve a runtime that treats exit requests as unsupported errors instead of process termination. */
|
||||
export function resolveRuntimeEnvWithUnavailableExit(params: {
|
||||
runtime: RuntimeEnv;
|
||||
logger: LoggerLike;
|
||||
unavailableMessage?: string;
|
||||
}): RuntimeEnv;
|
||||
export function resolveRuntimeEnvWithUnavailableExit(params: {
|
||||
runtime?: undefined;
|
||||
logger: LoggerLike;
|
||||
unavailableMessage?: string;
|
||||
}): OutputRuntimeEnv;
|
||||
export function resolveRuntimeEnvWithUnavailableExit(params: {
|
||||
runtime?: RuntimeEnv;
|
||||
logger: LoggerLike;
|
||||
unavailableMessage?: string;
|
||||
}): RuntimeEnv {
|
||||
}): RuntimeEnv | OutputRuntimeEnv {
|
||||
if (params.runtime) {
|
||||
return resolveRuntimeEnv({
|
||||
runtime: params.runtime,
|
||||
logger: params.logger,
|
||||
exitError: () => new Error(params.unavailableMessage ?? "Runtime exit not available"),
|
||||
});
|
||||
}
|
||||
return resolveRuntimeEnv({
|
||||
runtime: params.runtime,
|
||||
logger: params.logger,
|
||||
exitError: () => new Error(params.unavailableMessage ?? "Runtime exit not available"),
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ export { removeAckReactionAfterReply, shouldAckReaction } from "../channels/ack-
|
||||
export type { ChannelAccountSnapshot, ChannelGatewayContext } from "../channels/plugins/types.js";
|
||||
export type { OpenClawConfig } from "../config/config.js";
|
||||
export type { PluginRuntime } from "../plugins/runtime/types.js";
|
||||
export type { RuntimeEnv } from "../runtime.js";
|
||||
export type { OutputRuntimeEnv, RuntimeEnv } from "../runtime.js";
|
||||
export type { MockFn } from "../test-utils/vitest-mock-fn.js";
|
||||
|
||||
/** Create a tiny Windows `.cmd` shim fixture for plugin tests that spawn CLIs. */
|
||||
|
||||
@@ -7,6 +7,11 @@ export type RuntimeEnv = {
|
||||
exit: (code: number) => void;
|
||||
};
|
||||
|
||||
export type OutputRuntimeEnv = RuntimeEnv & {
|
||||
writeStdout: (value: string) => void;
|
||||
writeJson: (value: unknown, space?: number) => void;
|
||||
};
|
||||
|
||||
function shouldEmitRuntimeLog(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
if (env.VITEST !== "true") {
|
||||
return true;
|
||||
@@ -18,7 +23,49 @@ function shouldEmitRuntimeLog(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
return typeof maybeMockedLog.mock === "object";
|
||||
}
|
||||
|
||||
function createRuntimeIo(): Pick<RuntimeEnv, "log" | "error"> {
|
||||
function shouldEmitRuntimeStdout(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
if (env.VITEST !== "true") {
|
||||
return true;
|
||||
}
|
||||
if (env.OPENCLAW_TEST_RUNTIME_LOG === "1") {
|
||||
return true;
|
||||
}
|
||||
const stdout = process.stdout as NodeJS.WriteStream & {
|
||||
write: {
|
||||
mock?: unknown;
|
||||
};
|
||||
};
|
||||
return typeof stdout.write.mock === "object";
|
||||
}
|
||||
|
||||
function isPipeClosedError(err: unknown): boolean {
|
||||
const code = (err as { code?: string })?.code;
|
||||
return code === "EPIPE" || code === "EIO";
|
||||
}
|
||||
|
||||
function hasRuntimeOutputWriter(
|
||||
runtime: RuntimeEnv | OutputRuntimeEnv,
|
||||
): runtime is OutputRuntimeEnv {
|
||||
return typeof (runtime as Partial<OutputRuntimeEnv>).writeStdout === "function";
|
||||
}
|
||||
|
||||
function writeStdout(value: string): void {
|
||||
if (!shouldEmitRuntimeStdout()) {
|
||||
return;
|
||||
}
|
||||
clearActiveProgressLine();
|
||||
const line = value.endsWith("\n") ? value : `${value}\n`;
|
||||
try {
|
||||
process.stdout.write(line);
|
||||
} catch (err) {
|
||||
if (isPipeClosedError(err)) {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function createRuntimeIo(): Pick<OutputRuntimeEnv, "log" | "error" | "writeStdout" | "writeJson"> {
|
||||
return {
|
||||
log: (...args: Parameters<typeof console.log>) => {
|
||||
if (!shouldEmitRuntimeLog()) {
|
||||
@@ -31,10 +78,14 @@ function createRuntimeIo(): Pick<RuntimeEnv, "log" | "error"> {
|
||||
clearActiveProgressLine();
|
||||
console.error(...args);
|
||||
},
|
||||
writeStdout,
|
||||
writeJson: (value: unknown, space = 2) => {
|
||||
writeStdout(JSON.stringify(value, null, space > 0 ? space : undefined));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const defaultRuntime: RuntimeEnv = {
|
||||
export const defaultRuntime: OutputRuntimeEnv = {
|
||||
...createRuntimeIo(),
|
||||
exit: (code) => {
|
||||
restoreTerminalState("runtime exit", { resumeStdinIfPaused: false });
|
||||
@@ -43,7 +94,7 @@ export const defaultRuntime: RuntimeEnv = {
|
||||
},
|
||||
};
|
||||
|
||||
export function createNonExitingRuntime(): RuntimeEnv {
|
||||
export function createNonExitingRuntime(): OutputRuntimeEnv {
|
||||
return {
|
||||
...createRuntimeIo(),
|
||||
exit: (code: number) => {
|
||||
@@ -51,3 +102,23 @@ export function createNonExitingRuntime(): RuntimeEnv {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function writeRuntimeStdout(runtime: RuntimeEnv | OutputRuntimeEnv, value: string): void {
|
||||
if (hasRuntimeOutputWriter(runtime)) {
|
||||
runtime.writeStdout(value);
|
||||
return;
|
||||
}
|
||||
runtime.log(value);
|
||||
}
|
||||
|
||||
export function writeRuntimeJson(
|
||||
runtime: RuntimeEnv | OutputRuntimeEnv,
|
||||
value: unknown,
|
||||
space = 2,
|
||||
): void {
|
||||
if (hasRuntimeOutputWriter(runtime)) {
|
||||
runtime.writeJson(value, space);
|
||||
return;
|
||||
}
|
||||
runtime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user