chore: Enable "curly" rule to avoid single-statement if confusion/errors.

This commit is contained in:
cpojer
2026-01-31 16:19:20 +09:00
parent 009b16fab8
commit 5ceff756e1
1266 changed files with 27871 additions and 9393 deletions

View File

@@ -7,23 +7,35 @@ export function hasHelpOrVersion(argv: string[]): boolean {
}
function isValueToken(arg: string | undefined): boolean {
if (!arg) return false;
if (arg === FLAG_TERMINATOR) return false;
if (!arg.startsWith("-")) return true;
if (!arg) {
return false;
}
if (arg === FLAG_TERMINATOR) {
return false;
}
if (!arg.startsWith("-")) {
return true;
}
return /^-\d+(?:\.\d+)?$/.test(arg);
}
function parsePositiveInt(value: string): number | undefined {
const parsed = Number.parseInt(value, 10);
if (Number.isNaN(parsed) || parsed <= 0) return undefined;
if (Number.isNaN(parsed) || parsed <= 0) {
return undefined;
}
return parsed;
}
export function hasFlag(argv: string[], name: string): boolean {
const args = argv.slice(2);
for (const arg of args) {
if (arg === FLAG_TERMINATOR) break;
if (arg === name) return true;
if (arg === FLAG_TERMINATOR) {
break;
}
if (arg === name) {
return true;
}
}
return false;
}
@@ -32,7 +44,9 @@ export function getFlagValue(argv: string[], name: string): string | null | unde
const args = argv.slice(2);
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (arg === FLAG_TERMINATOR) break;
if (arg === FLAG_TERMINATOR) {
break;
}
if (arg === name) {
const next = args[i + 1];
return isValueToken(next) ? next : null;
@@ -46,14 +60,20 @@ export function getFlagValue(argv: string[], name: string): string | null | unde
}
export function getVerboseFlag(argv: string[], options?: { includeDebug?: boolean }): boolean {
if (hasFlag(argv, "--verbose")) return true;
if (options?.includeDebug && hasFlag(argv, "--debug")) return true;
if (hasFlag(argv, "--verbose")) {
return true;
}
if (options?.includeDebug && hasFlag(argv, "--debug")) {
return true;
}
return false;
}
export function getPositiveIntFlagValue(argv: string[], name: string): number | null | undefined {
const raw = getFlagValue(argv, name);
if (raw === null || raw === undefined) return raw;
if (raw === null || raw === undefined) {
return raw;
}
return parsePositiveInt(raw);
}
@@ -62,11 +82,19 @@ export function getCommandPath(argv: string[], depth = 2): string[] {
const path: string[] = [];
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (!arg) continue;
if (arg === "--") break;
if (arg.startsWith("-")) continue;
if (!arg) {
continue;
}
if (arg === "--") {
break;
}
if (arg.startsWith("-")) {
continue;
}
path.push(arg);
if (path.length >= depth) break;
if (path.length >= depth) {
break;
}
}
return path;
}
@@ -97,7 +125,9 @@ export function buildParseArgv(params: {
const executable = (normalizedArgv[0]?.split(/[/\\]/).pop() ?? "").toLowerCase();
const looksLikeNode =
normalizedArgv.length >= 2 && (isNodeExecutable(executable) || isBunExecutable(executable));
if (looksLikeNode) return normalizedArgv;
if (looksLikeNode) {
return normalizedArgv;
}
return ["node", programName || "openclaw", ...normalizedArgv];
}
@@ -118,11 +148,19 @@ function isBunExecutable(executable: string): boolean {
}
export function shouldMigrateStateFromPath(path: string[]): boolean {
if (path.length === 0) return true;
if (path.length === 0) {
return true;
}
const [primary, secondary] = path;
if (primary === "health" || primary === "status" || primary === "sessions") return false;
if (primary === "memory" && secondary === "status") return false;
if (primary === "agent") return false;
if (primary === "health" || primary === "status" || primary === "sessions") {
return false;
}
if (primary === "memory" && secondary === "status") {
return false;
}
if (primary === "agent") {
return false;
}
return true;
}

View File

@@ -18,7 +18,9 @@ const graphemeSegmenter =
: null;
function splitGraphemes(value: string): string[] {
if (!graphemeSegmenter) return Array.from(value);
if (!graphemeSegmenter) {
return Array.from(value);
}
try {
return Array.from(graphemeSegmenter.segment(value), (seg) => seg.segment);
} catch {
@@ -74,12 +76,20 @@ const LOBSTER_ASCII = [
export function formatCliBannerArt(options: BannerOptions = {}): string {
const rich = options.richTty ?? isRich();
if (!rich) return LOBSTER_ASCII.join("\n");
if (!rich) {
return LOBSTER_ASCII.join("\n");
}
const colorChar = (ch: string) => {
if (ch === "█") return theme.accentBright(ch);
if (ch === "░") return theme.accentDim(ch);
if (ch === "▀") return theme.accent(ch);
if (ch === "█") {
return theme.accentBright(ch);
}
if (ch === "░") {
return theme.accentDim(ch);
}
if (ch === "▀") {
return theme.accent(ch);
}
return theme.muted(ch);
};
@@ -99,11 +109,19 @@ export function formatCliBannerArt(options: BannerOptions = {}): string {
}
export function emitCliBanner(version: string, options: BannerOptions = {}) {
if (bannerEmitted) return;
if (bannerEmitted) {
return;
}
const argv = options.argv ?? process.argv;
if (!process.stdout.isTTY) return;
if (hasJsonFlag(argv)) return;
if (hasVersionFlag(argv)) return;
if (!process.stdout.isTTY) {
return;
}
if (hasJsonFlag(argv)) {
return;
}
if (hasVersionFlag(argv)) {
return;
}
const line = formatCliBannerLine(version, options);
process.stdout.write(`\n${line}\n\n`);
bannerEmitted = true;

View File

@@ -19,7 +19,9 @@ export function registerBrowserElementCommands(
.action(async (ref: string | undefined, opts, cmd) => {
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
const refValue = requireRef(ref);
if (!refValue) return;
if (!refValue) {
return;
}
const modifiers = opts.modifiers
? String(opts.modifiers)
.split(",")
@@ -62,7 +64,9 @@ export function registerBrowserElementCommands(
.action(async (ref: string | undefined, text: string, opts, cmd) => {
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
const refValue = requireRef(ref);
if (!refValue) return;
if (!refValue) {
return;
}
try {
const result = await callBrowserAct({
parent,
@@ -146,7 +150,9 @@ export function registerBrowserElementCommands(
.action(async (ref: string | undefined, opts, cmd) => {
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
const refValue = requireRef(ref);
if (!refValue) return;
if (!refValue) {
return;
}
try {
const result = await callBrowserAct({
parent,

View File

@@ -56,9 +56,13 @@ export async function readFields(opts: {
fieldsFile?: string;
}): Promise<BrowserFormField[]> {
const payload = opts.fieldsFile ? await readFile(opts.fieldsFile) : (opts.fields ?? "");
if (!payload.trim()) throw new Error("fields are required");
if (!payload.trim()) {
throw new Error("fields are required");
}
const parsed = JSON.parse(payload) as unknown;
if (!Array.isArray(parsed)) throw new Error("fields must be an array");
if (!Array.isArray(parsed)) {
throw new Error("fields must be an array");
}
return parsed.map((entry, index) => {
if (!entry || typeof entry !== "object") {
throw new Error(`fields[${index}] must be an object`);

View File

@@ -63,8 +63,11 @@ describe("browser extension install", () => {
expect(copyToClipboard).toHaveBeenCalledWith(dir);
} finally {
if (prev === undefined) delete process.env.OPENCLAW_STATE_DIR;
else process.env.OPENCLAW_STATE_DIR = prev;
if (prev === undefined) {
delete process.env.OPENCLAW_STATE_DIR;
} else {
process.env.OPENCLAW_STATE_DIR = prev;
}
}
});
});

View File

@@ -120,6 +120,8 @@ export function registerBrowserExtensionCommands(
const displayPath = shortenHomePath(dir);
defaultRuntime.log(displayPath);
const copied = await copyToClipboard(dir).catch(() => false);
if (copied) defaultRuntime.error(info("Copied to clipboard."));
if (copied) {
defaultRuntime.error(info("Copied to clipboard."));
}
});
}

View File

@@ -14,10 +14,14 @@ type BrowserRequestParams = {
};
function normalizeQuery(query: BrowserRequestParams["query"]): Record<string, string> | undefined {
if (!query) return undefined;
if (!query) {
return undefined;
}
const out: Record<string, string> = {};
for (const [key, value] of Object.entries(query)) {
if (value === undefined) continue;
if (value === undefined) {
continue;
}
out[key] = String(value);
}
return Object.keys(out).length ? out : undefined;

View File

@@ -116,7 +116,9 @@ export function registerBrowserStateCommands(
}
const headers: Record<string, string> = {};
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
if (typeof v === "string") headers[k] = v;
if (typeof v === "string") {
headers[k] = v;
}
}
const result = await callBrowserRequest(
parent,

View File

@@ -8,7 +8,9 @@ function dedupe(values: string[]): string[] {
const seen = new Set<string>();
const resolved: string[] = [];
for (const value of values) {
if (!value || seen.has(value)) continue;
if (!value || seen.has(value)) {
continue;
}
seen.add(value);
resolved.push(value);
}

View File

@@ -7,15 +7,23 @@ const CLI_PREFIX_RE = /^(?:((?:pnpm|npm|bunx|npx)\s+))?(openclaw)\b/;
export function resolveCliName(argv: string[] = process.argv): string {
const argv1 = argv[1];
if (!argv1) return DEFAULT_CLI_NAME;
if (!argv1) {
return DEFAULT_CLI_NAME;
}
const base = path.basename(argv1).trim();
if (KNOWN_CLI_NAMES.has(base)) return base;
if (KNOWN_CLI_NAMES.has(base)) {
return base;
}
return DEFAULT_CLI_NAME;
}
export function replaceCliName(command: string, cliName = resolveCliName()): string {
if (!command.trim()) return command;
if (!CLI_PREFIX_RE.test(command)) return command;
if (!command.trim()) {
return command;
}
if (!CLI_PREFIX_RE.test(command)) {
return command;
}
return command.replace(CLI_PREFIX_RE, (_match, runner: string | undefined) => {
return `${runner ?? ""}${cliName}`;
});

View File

@@ -56,7 +56,9 @@ export function resolveOptionFromCommand<T>(
let current: Command | null | undefined = command;
while (current) {
const opts = current.opts?.() ?? {};
if (opts[key] !== undefined) return opts[key];
if (opts[key] !== undefined) {
return opts[key];
}
current = current.parent ?? undefined;
}
return undefined;

View File

@@ -12,8 +12,12 @@ export function formatCliCommand(
const cliName = resolveCliName();
const normalizedCommand = replaceCliName(command, cliName);
const profile = normalizeProfileName(env.OPENCLAW_PROFILE);
if (!profile) return normalizedCommand;
if (!CLI_PREFIX_RE.test(normalizedCommand)) return normalizedCommand;
if (!profile) {
return normalizedCommand;
}
if (!CLI_PREFIX_RE.test(normalizedCommand)) {
return normalizedCommand;
}
if (PROFILE_FLAG_RE.test(normalizedCommand) || DEV_FLAG_RE.test(normalizedCommand)) {
return normalizedCommand;
}

View File

@@ -21,7 +21,9 @@ export function registerCompletionCli(program: Command) {
const entries = getSubCliEntries();
for (const entry of entries) {
// Skip completion command itself to avoid cycle if we were to add it to the list
if (entry.name === "completion") continue;
if (entry.name === "completion") {
continue;
}
await registerSubCliByName(program, entry.name);
}
@@ -84,7 +86,9 @@ export async function installCompletion(shell: string, yes: boolean, binName = "
const content = await fs.readFile(profilePath, "utf-8");
if (content.includes(`${binName} completion`)) {
if (!yes) console.log(`Completion already installed in ${profilePath}`);
if (!yes) {
console.log(`Completion already installed in ${profilePath}`);
}
return;
}
@@ -318,7 +322,9 @@ function generateFishCompletion(program: Command): string {
const visit = (cmd: Command, parents: string[]) => {
const cmdName = cmd.name();
const fullPath = [...parents];
if (parents.length > 0) fullPath.push(cmdName); // Only push if not root, or consistent root handling
if (parents.length > 0) {
fullPath.push(cmdName);
} // Only push if not root, or consistent root handling
// Fish uses 'seen_subcommand_from' to determine context.
// For root: complete -c openclaw -n "__fish_use_subcommand" -a "subcmd" -d "desc"
@@ -339,8 +345,12 @@ function generateFishCompletion(program: Command): string {
?.replace(/^-/, "");
const desc = opt.description.replace(/'/g, "'\\''");
let line = `complete -c ${rootCmd} -n "__fish_use_subcommand"`;
if (short) line += ` -s ${short}`;
if (long) line += ` -l ${long}`;
if (short) {
line += ` -s ${short}`;
}
if (long) {
line += ` -l ${long}`;
}
line += ` -d '${desc}'\n`;
script += line;
}
@@ -368,8 +378,12 @@ function generateFishCompletion(program: Command): string {
?.replace(/^-/, "");
const desc = opt.description.replace(/'/g, "'\\''");
let line = `complete -c ${rootCmd} -n "__fish_seen_subcommand_from ${cmdName}"`;
if (short) line += ` -s ${short}`;
if (long) line += ` -l ${long}`;
if (short) {
line += ` -s ${short}`;
}
if (long) {
line += ` -l ${long}`;
}
line += ` -d '${desc}'\n`;
script += line;
}

View File

@@ -17,7 +17,9 @@ function isIndexSegment(raw: string): boolean {
function parsePath(raw: string): PathSegment[] {
const trimmed = raw.trim();
if (!trimmed) return [];
if (!trimmed) {
return [];
}
const parts: string[] = [];
let current = "";
let i = 0;
@@ -25,23 +27,33 @@ function parsePath(raw: string): PathSegment[] {
const ch = trimmed[i];
if (ch === "\\") {
const next = trimmed[i + 1];
if (next) current += next;
if (next) {
current += next;
}
i += 2;
continue;
}
if (ch === ".") {
if (current) parts.push(current);
if (current) {
parts.push(current);
}
current = "";
i += 1;
continue;
}
if (ch === "[") {
if (current) parts.push(current);
if (current) {
parts.push(current);
}
current = "";
const close = trimmed.indexOf("]", i);
if (close === -1) throw new Error(`Invalid path (missing "]"): ${raw}`);
if (close === -1) {
throw new Error(`Invalid path (missing "]"): ${raw}`);
}
const inside = trimmed.slice(i + 1, close).trim();
if (!inside) throw new Error(`Invalid path (empty "[]"): ${raw}`);
if (!inside) {
throw new Error(`Invalid path (empty "[]"): ${raw}`);
}
parts.push(inside);
i = close + 1;
continue;
@@ -49,7 +61,9 @@ function parsePath(raw: string): PathSegment[] {
current += ch;
i += 1;
}
if (current) parts.push(current);
if (current) {
parts.push(current);
}
return parts.map((part) => part.trim()).filter(Boolean);
}
@@ -73,9 +87,13 @@ function parseValue(raw: string, opts: { json?: boolean }): unknown {
function getAtPath(root: unknown, path: PathSegment[]): { found: boolean; value?: unknown } {
let current: unknown = root;
for (const segment of path) {
if (!current || typeof current !== "object") return { found: false };
if (!current || typeof current !== "object") {
return { found: false };
}
if (Array.isArray(current)) {
if (!isIndexSegment(segment)) return { found: false };
if (!isIndexSegment(segment)) {
return { found: false };
}
const index = Number.parseInt(segment, 10);
if (!Number.isFinite(index) || index < 0 || index >= current.length) {
return { found: false };
@@ -84,7 +102,9 @@ function getAtPath(root: unknown, path: PathSegment[]): { found: boolean; value?
continue;
}
const record = current as Record<string, unknown>;
if (!(segment in record)) return { found: false };
if (!(segment in record)) {
return { found: false };
}
current = record[segment];
}
return { found: true, value: current };
@@ -138,37 +158,55 @@ function unsetAtPath(root: Record<string, unknown>, path: PathSegment[]): boolea
let current: unknown = root;
for (let i = 0; i < path.length - 1; i += 1) {
const segment = path[i];
if (!current || typeof current !== "object") return false;
if (!current || typeof current !== "object") {
return false;
}
if (Array.isArray(current)) {
if (!isIndexSegment(segment)) return false;
if (!isIndexSegment(segment)) {
return false;
}
const index = Number.parseInt(segment, 10);
if (!Number.isFinite(index) || index < 0 || index >= current.length) return false;
if (!Number.isFinite(index) || index < 0 || index >= current.length) {
return false;
}
current = current[index];
continue;
}
const record = current as Record<string, unknown>;
if (!(segment in record)) return false;
if (!(segment in record)) {
return false;
}
current = record[segment];
}
const last = path[path.length - 1];
if (Array.isArray(current)) {
if (!isIndexSegment(last)) return false;
if (!isIndexSegment(last)) {
return false;
}
const index = Number.parseInt(last, 10);
if (!Number.isFinite(index) || index < 0 || index >= current.length) return false;
if (!Number.isFinite(index) || index < 0 || index >= current.length) {
return false;
}
current.splice(index, 1);
return true;
}
if (!current || typeof current !== "object") return false;
if (!current || typeof current !== "object") {
return false;
}
const record = current as Record<string, unknown>;
if (!(last in record)) return false;
if (!(last in record)) {
return false;
}
delete record[last];
return true;
}
async function loadValidConfig() {
const snapshot = await readConfigFileSnapshot();
if (snapshot.valid) return snapshot;
if (snapshot.valid) {
return snapshot;
}
defaultRuntime.error(`Config invalid at ${shortenHomePath(snapshot.path)}.`);
for (const issue of snapshot.issues) {
defaultRuntime.error(`- ${issue.path || "<root>"}: ${issue.message}`);
@@ -264,7 +302,9 @@ export function registerConfigCli(program: Command) {
.action(async (path: string, value: string, opts) => {
try {
const parsedPath = parsePath(path);
if (parsedPath.length === 0) throw new Error("Path is empty.");
if (parsedPath.length === 0) {
throw new Error("Path is empty.");
}
const parsedValue = parseValue(value, opts);
const snapshot = await loadValidConfig();
const next = snapshot.config as Record<string, unknown>;
@@ -284,7 +324,9 @@ export function registerConfigCli(program: Command) {
.action(async (path: string) => {
try {
const parsedPath = parsePath(path);
if (parsedPath.length === 0) throw new Error("Path is empty.");
if (parsedPath.length === 0) {
throw new Error("Path is empty.");
}
const snapshot = await loadValidConfig();
const next = snapshot.config as Record<string, unknown>;
const removed = unsetAtPath(next, parsedPath);

View File

@@ -2,7 +2,9 @@ import { Command } from "commander";
import { describe, expect, it, vi } from "vitest";
const callGatewayFromCli = vi.fn(async (method: string, _opts: unknown, params?: unknown) => {
if (method === "cron.status") return { enabled: true };
if (method === "cron.status") {
return { enabled: true };
}
return { ok: true, params };
});

View File

@@ -111,12 +111,16 @@ export function registerCronAddCommand(cron: Command) {
}
if (at) {
const atMs = parseAtMs(at);
if (!atMs) throw new Error("Invalid --at; use ISO time or duration like 20m");
if (!atMs) {
throw new Error("Invalid --at; use ISO time or duration like 20m");
}
return { kind: "at" as const, atMs };
}
if (every) {
const everyMs = parseDurationMs(every);
if (!everyMs) throw new Error("Invalid --every; use e.g. 10m, 1h, 1d");
if (!everyMs) {
throw new Error("Invalid --every; use e.g. 10m, 1h, 1d");
}
return { kind: "every" as const, everyMs };
}
return {
@@ -150,7 +154,9 @@ export function registerCronAddCommand(cron: Command) {
if (chosen !== 1) {
throw new Error("Choose exactly one payload: --system-event or --message");
}
if (systemEvent) return { kind: "systemEvent" as const, text: systemEvent };
if (systemEvent) {
return { kind: "systemEvent" as const, text: systemEvent };
}
const timeoutSeconds = parsePositiveIntOrUndefined(opts.timeoutSeconds);
return {
kind: "agentTurn" as const,
@@ -197,7 +203,9 @@ export function registerCronAddCommand(cron: Command) {
const nameRaw = typeof opts.name === "string" ? opts.name : "";
const name = nameRaw.trim();
if (!name) throw new Error("--name is required");
if (!name) {
throw new Error("--name is required");
}
const description =
typeof opts.description === "string" && opts.description.trim()

View File

@@ -16,7 +16,9 @@ const assignIf = (
value: unknown,
shouldAssign: boolean,
) => {
if (shouldAssign) target[key] = value;
if (shouldAssign) {
target[key] = value;
}
};
export function registerCronEditCommand(cron: Command) {
@@ -74,19 +76,36 @@ export function registerCronEditCommand(cron: Command) {
}
const patch: Record<string, unknown> = {};
if (typeof opts.name === "string") patch.name = opts.name;
if (typeof opts.description === "string") patch.description = opts.description;
if (opts.enable && opts.disable)
if (typeof opts.name === "string") {
patch.name = opts.name;
}
if (typeof opts.description === "string") {
patch.description = opts.description;
}
if (opts.enable && opts.disable) {
throw new Error("Choose --enable or --disable, not both");
if (opts.enable) patch.enabled = true;
if (opts.disable) patch.enabled = false;
}
if (opts.enable) {
patch.enabled = true;
}
if (opts.disable) {
patch.enabled = false;
}
if (opts.deleteAfterRun && opts.keepAfterRun) {
throw new Error("Choose --delete-after-run or --keep-after-run, not both");
}
if (opts.deleteAfterRun) patch.deleteAfterRun = true;
if (opts.keepAfterRun) patch.deleteAfterRun = false;
if (typeof opts.session === "string") patch.sessionTarget = opts.session;
if (typeof opts.wake === "string") patch.wakeMode = opts.wake;
if (opts.deleteAfterRun) {
patch.deleteAfterRun = true;
}
if (opts.keepAfterRun) {
patch.deleteAfterRun = false;
}
if (typeof opts.session === "string") {
patch.sessionTarget = opts.session;
}
if (typeof opts.wake === "string") {
patch.wakeMode = opts.wake;
}
if (opts.agent && opts.clearAgent) {
throw new Error("Use --agent or --clear-agent, not both");
}
@@ -98,14 +117,20 @@ export function registerCronEditCommand(cron: Command) {
}
const scheduleChosen = [opts.at, opts.every, opts.cron].filter(Boolean).length;
if (scheduleChosen > 1) throw new Error("Choose at most one schedule change");
if (scheduleChosen > 1) {
throw new Error("Choose at most one schedule change");
}
if (opts.at) {
const atMs = parseAtMs(String(opts.at));
if (!atMs) throw new Error("Invalid --at");
if (!atMs) {
throw new Error("Invalid --at");
}
patch.schedule = { kind: "at", atMs };
} else if (opts.every) {
const everyMs = parseDurationMs(String(opts.every));
if (!everyMs) throw new Error("Invalid --every");
if (!everyMs) {
throw new Error("Invalid --every");
}
patch.schedule = { kind: "every", everyMs };
} else if (opts.cron) {
patch.schedule = {

View File

@@ -15,7 +15,9 @@ export async function warnIfCronSchedulerDisabled(opts: GatewayRpcOpts) {
enabled?: boolean;
storePath?: string;
};
if (res?.enabled === true) return;
if (res?.enabled === true) {
return;
}
const store = typeof res?.storePath === "string" ? res.storePath : "";
defaultRuntime.error(
[
@@ -33,11 +35,17 @@ export async function warnIfCronSchedulerDisabled(opts: GatewayRpcOpts) {
export function parseDurationMs(input: string): number | null {
const raw = input.trim();
if (!raw) return null;
if (!raw) {
return null;
}
const match = raw.match(/^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/i);
if (!match) return null;
if (!match) {
return null;
}
const n = Number.parseFloat(match[1] ?? "");
if (!Number.isFinite(n) || n <= 0) return null;
if (!Number.isFinite(n) || n <= 0) {
return null;
}
const unit = (match[2] ?? "").toLowerCase();
const factor =
unit === "ms"
@@ -54,11 +62,17 @@ export function parseDurationMs(input: string): number | null {
export function parseAtMs(input: string): number | null {
const raw = input.trim();
if (!raw) return null;
if (!raw) {
return null;
}
const absolute = parseAbsoluteTimeMs(raw);
if (absolute) return absolute;
if (absolute) {
return absolute;
}
const dur = parseDurationMs(raw);
if (dur) return Date.now() + dur;
if (dur) {
return Date.now() + dur;
}
return null;
}
@@ -74,48 +88,76 @@ const CRON_AGENT_PAD = 10;
const pad = (value: string, width: number) => value.padEnd(width);
const truncate = (value: string, width: number) => {
if (value.length <= width) return value;
if (width <= 3) return value.slice(0, width);
if (value.length <= width) {
return value;
}
if (width <= 3) {
return value.slice(0, width);
}
return `${value.slice(0, width - 3)}...`;
};
const formatIsoMinute = (ms: number) => {
const d = new Date(ms);
if (Number.isNaN(d.getTime())) return "-";
if (Number.isNaN(d.getTime())) {
return "-";
}
const iso = d.toISOString();
return `${iso.slice(0, 10)} ${iso.slice(11, 16)}Z`;
};
const formatDuration = (ms: number) => {
if (ms < 60_000) return `${Math.max(1, Math.round(ms / 1000))}s`;
if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`;
if (ms < 86_400_000) return `${Math.round(ms / 3_600_000)}h`;
if (ms < 60_000) {
return `${Math.max(1, Math.round(ms / 1000))}s`;
}
if (ms < 3_600_000) {
return `${Math.round(ms / 60_000)}m`;
}
if (ms < 86_400_000) {
return `${Math.round(ms / 3_600_000)}h`;
}
return `${Math.round(ms / 86_400_000)}d`;
};
const formatSpan = (ms: number) => {
if (ms < 60_000) return "<1m";
if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`;
if (ms < 86_400_000) return `${Math.round(ms / 3_600_000)}h`;
if (ms < 60_000) {
return "<1m";
}
if (ms < 3_600_000) {
return `${Math.round(ms / 60_000)}m`;
}
if (ms < 86_400_000) {
return `${Math.round(ms / 3_600_000)}h`;
}
return `${Math.round(ms / 86_400_000)}d`;
};
const formatRelative = (ms: number | null | undefined, nowMs: number) => {
if (!ms) return "-";
if (!ms) {
return "-";
}
const delta = ms - nowMs;
const label = formatSpan(Math.abs(delta));
return delta >= 0 ? `in ${label}` : `${label} ago`;
};
const formatSchedule = (schedule: CronSchedule) => {
if (schedule.kind === "at") return `at ${formatIsoMinute(schedule.atMs)}`;
if (schedule.kind === "every") return `every ${formatDuration(schedule.everyMs)}`;
if (schedule.kind === "at") {
return `at ${formatIsoMinute(schedule.atMs)}`;
}
if (schedule.kind === "every") {
return `every ${formatDuration(schedule.everyMs)}`;
}
return schedule.tz ? `cron ${schedule.expr} @ ${schedule.tz}` : `cron ${schedule.expr}`;
};
const formatStatus = (job: CronJob) => {
if (!job.enabled) return "disabled";
if (job.state.runningAtMs) return "running";
if (!job.enabled) {
return "disabled";
}
if (job.state.runningAtMs) {
return "running";
}
return job.state.lastStatus ?? "idle";
};
@@ -158,10 +200,18 @@ export function printCronList(jobs: CronJob[], runtime = defaultRuntime) {
const agentLabel = pad(truncate(job.agentId ?? "default", CRON_AGENT_PAD), CRON_AGENT_PAD);
const coloredStatus = (() => {
if (statusRaw === "ok") return colorize(rich, theme.success, statusLabel);
if (statusRaw === "error") return colorize(rich, theme.error, statusLabel);
if (statusRaw === "running") return colorize(rich, theme.warn, statusLabel);
if (statusRaw === "skipped") return colorize(rich, theme.muted, statusLabel);
if (statusRaw === "ok") {
return colorize(rich, theme.success, statusLabel);
}
if (statusRaw === "error") {
return colorize(rich, theme.error, statusLabel);
}
if (statusRaw === "running") {
return colorize(rich, theme.warn, statusLabel);
}
if (statusRaw === "skipped") {
return colorize(rich, theme.muted, statusLabel);
}
return colorize(rich, theme.muted, statusLabel);
})();

View File

@@ -96,21 +96,29 @@ describe("daemon-cli coverage", () => {
});
afterEach(() => {
if (originalEnv.OPENCLAW_STATE_DIR !== undefined)
if (originalEnv.OPENCLAW_STATE_DIR !== undefined) {
process.env.OPENCLAW_STATE_DIR = originalEnv.OPENCLAW_STATE_DIR;
else delete process.env.OPENCLAW_STATE_DIR;
} else {
delete process.env.OPENCLAW_STATE_DIR;
}
if (originalEnv.OPENCLAW_CONFIG_PATH !== undefined)
if (originalEnv.OPENCLAW_CONFIG_PATH !== undefined) {
process.env.OPENCLAW_CONFIG_PATH = originalEnv.OPENCLAW_CONFIG_PATH;
else delete process.env.OPENCLAW_CONFIG_PATH;
} else {
delete process.env.OPENCLAW_CONFIG_PATH;
}
if (originalEnv.OPENCLAW_GATEWAY_PORT !== undefined)
if (originalEnv.OPENCLAW_GATEWAY_PORT !== undefined) {
process.env.OPENCLAW_GATEWAY_PORT = originalEnv.OPENCLAW_GATEWAY_PORT;
else delete process.env.OPENCLAW_GATEWAY_PORT;
} else {
delete process.env.OPENCLAW_GATEWAY_PORT;
}
if (originalEnv.OPENCLAW_PROFILE !== undefined)
if (originalEnv.OPENCLAW_PROFILE !== undefined) {
process.env.OPENCLAW_PROFILE = originalEnv.OPENCLAW_PROFILE;
else delete process.env.OPENCLAW_PROFILE;
} else {
delete process.env.OPENCLAW_PROFILE;
}
});
it("probes gateway status by default", async () => {

View File

@@ -30,7 +30,9 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
hints?: string[];
warnings?: string[];
}) => {
if (!json) return;
if (!json) {
return;
}
emitDaemonActionJson({ action: "install", ...payload });
};
const fail = (message: string) => {
@@ -97,8 +99,11 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
token: opts.token || cfg.gateway?.auth?.token || process.env.OPENCLAW_GATEWAY_TOKEN,
runtime: runtimeRaw,
warn: (message) => {
if (json) warnings.push(message);
else defaultRuntime.log(message);
if (json) {
warnings.push(message);
} else {
defaultRuntime.log(message);
}
},
config: cfg,
});

View File

@@ -23,12 +23,17 @@ export async function runDaemonUninstall(opts: DaemonLifecycleOptions = {}) {
notLoadedText: string;
};
}) => {
if (!json) return;
if (!json) {
return;
}
emitDaemonActionJson({ action: "uninstall", ...payload });
};
const fail = (message: string) => {
if (json) emit({ ok: false, error: message });
else defaultRuntime.error(message);
if (json) {
emit({ ok: false, error: message });
} else {
defaultRuntime.error(message);
}
defaultRuntime.exit(1);
};
@@ -91,12 +96,17 @@ export async function runDaemonStart(opts: DaemonLifecycleOptions = {}) {
notLoadedText: string;
};
}) => {
if (!json) return;
if (!json) {
return;
}
emitDaemonActionJson({ action: "start", ...payload });
};
const fail = (message: string, hints?: string[]) => {
if (json) emit({ ok: false, error: message, hints });
else defaultRuntime.error(message);
if (json) {
emit({ ok: false, error: message, hints });
} else {
defaultRuntime.error(message);
}
defaultRuntime.exit(1);
};
@@ -167,12 +177,17 @@ export async function runDaemonStop(opts: DaemonLifecycleOptions = {}) {
notLoadedText: string;
};
}) => {
if (!json) return;
if (!json) {
return;
}
emitDaemonActionJson({ action: "stop", ...payload });
};
const fail = (message: string) => {
if (json) emit({ ok: false, error: message });
else defaultRuntime.error(message);
if (json) {
emit({ ok: false, error: message });
} else {
defaultRuntime.error(message);
}
defaultRuntime.exit(1);
};
@@ -237,12 +252,17 @@ export async function runDaemonRestart(opts: DaemonLifecycleOptions = {}): Promi
notLoadedText: string;
};
}) => {
if (!json) return;
if (!json) {
return;
}
emitDaemonActionJson({ action: "restart", ...payload });
};
const fail = (message: string, hints?: string[]) => {
if (json) emit({ ok: false, error: message, hints });
else defaultRuntime.error(message);
if (json) {
emit({ ok: false, error: message, hints });
} else {
defaultRuntime.error(message);
}
defaultRuntime.exit(1);
};

View File

@@ -8,31 +8,43 @@ import { getResolvedLoggerSettings } from "../../logging.js";
import { formatCliCommand } from "../command-format.js";
export function parsePort(raw: unknown): number | null {
if (raw === undefined || raw === null) return null;
if (raw === undefined || raw === null) {
return null;
}
const value =
typeof raw === "string"
? raw
: typeof raw === "number" || typeof raw === "bigint"
? raw.toString()
: null;
if (value === null) return null;
if (value === null) {
return null;
}
const parsed = Number.parseInt(value, 10);
if (!Number.isFinite(parsed) || parsed <= 0) return null;
if (!Number.isFinite(parsed) || parsed <= 0) {
return null;
}
return parsed;
}
export function parsePortFromArgs(programArguments: string[] | undefined): number | null {
if (!programArguments?.length) return null;
if (!programArguments?.length) {
return null;
}
for (let i = 0; i < programArguments.length; i += 1) {
const arg = programArguments[i];
if (arg === "--port") {
const next = programArguments[i + 1];
const parsed = parsePort(next);
if (parsed) return parsed;
if (parsed) {
return parsed;
}
}
if (arg?.startsWith("--port=")) {
const parsed = parsePort(arg.split("=", 2)[1]);
if (parsed) return parsed;
if (parsed) {
return parsed;
}
}
}
return null;
@@ -46,7 +58,9 @@ export function pickProbeHostForBind(
if (bindMode === "custom" && customBindHost?.trim()) {
return customBindHost.trim();
}
if (bindMode === "tailnet") return tailnetIPv4 ?? "127.0.0.1";
if (bindMode === "tailnet") {
return tailnetIPv4 ?? "127.0.0.1";
}
return "127.0.0.1";
}
@@ -59,11 +73,15 @@ const SAFE_DAEMON_ENV_KEYS = [
];
export function filterDaemonEnv(env: Record<string, string> | undefined): Record<string, string> {
if (!env) return {};
if (!env) {
return {};
}
const filtered: Record<string, string> = {};
for (const key of SAFE_DAEMON_ENV_KEYS) {
const value = env[key];
if (!value?.trim()) continue;
if (!value?.trim()) {
continue;
}
filtered[key] = value.trim();
}
return filtered;
@@ -76,7 +94,9 @@ export function safeDaemonEnv(env: Record<string, string> | undefined): string[]
export function normalizeListenerAddress(raw: string): string {
let value = raw.trim();
if (!value) return value;
if (!value) {
return value;
}
value = value.replace(/^TCP\s+/i, "");
value = value.replace(/\s+\(LISTEN\)\s*$/i, "");
return value.trim();
@@ -97,21 +117,35 @@ export function formatRuntimeStatus(
}
| undefined,
) {
if (!runtime) return null;
if (!runtime) {
return null;
}
const status = runtime.status ?? "unknown";
const details: string[] = [];
if (runtime.pid) details.push(`pid ${runtime.pid}`);
if (runtime.pid) {
details.push(`pid ${runtime.pid}`);
}
if (runtime.state && runtime.state.toLowerCase() !== status) {
details.push(`state ${runtime.state}`);
}
if (runtime.subState) details.push(`sub ${runtime.subState}`);
if (runtime.subState) {
details.push(`sub ${runtime.subState}`);
}
if (runtime.lastExitStatus !== undefined) {
details.push(`last exit ${runtime.lastExitStatus}`);
}
if (runtime.lastExitReason) details.push(`reason ${runtime.lastExitReason}`);
if (runtime.lastRunResult) details.push(`last run ${runtime.lastRunResult}`);
if (runtime.lastRunTime) details.push(`last run time ${runtime.lastRunTime}`);
if (runtime.detail) details.push(runtime.detail);
if (runtime.lastExitReason) {
details.push(`reason ${runtime.lastExitReason}`);
}
if (runtime.lastRunResult) {
details.push(`last run ${runtime.lastRunResult}`);
}
if (runtime.lastRunTime) {
details.push(`last run time ${runtime.lastRunTime}`);
}
if (runtime.detail) {
details.push(runtime.detail);
}
return details.length > 0 ? `${status} (${details.join(", ")})` : status;
}
@@ -119,7 +153,9 @@ export function renderRuntimeHints(
runtime: { missingUnit?: boolean; status?: string } | undefined,
env: NodeJS.ProcessEnv = process.env,
): string[] {
if (!runtime) return [];
if (!runtime) {
return [];
}
const hints: string[] = [];
const fileLog = (() => {
try {
@@ -130,11 +166,15 @@ export function renderRuntimeHints(
})();
if (runtime.missingUnit) {
hints.push(`Service not installed. Run: ${formatCliCommand("openclaw gateway install", env)}`);
if (fileLog) hints.push(`File logs: ${fileLog}`);
if (fileLog) {
hints.push(`File logs: ${fileLog}`);
}
return hints;
}
if (runtime.status === "stopped") {
if (fileLog) hints.push(`File logs: ${fileLog}`);
if (fileLog) {
hints.push(`File logs: ${fileLog}`);
}
if (process.platform === "darwin") {
const logs = resolveGatewayLogPaths(env);
hints.push(`Launchd stdout (if installed): ${logs.stdoutPath}`);

View File

@@ -96,8 +96,12 @@ export type DaemonStatus = {
};
function shouldReportPortUsage(status: PortUsageStatus | undefined, rpcOk?: boolean) {
if (status !== "busy") return false;
if (rpcOk === true) return false;
if (status !== "busy") {
return false;
}
if (rpcOk === true) {
return false;
}
return true;
}
@@ -271,7 +275,9 @@ export async function gatherDaemonStatus(
}
export function renderPortDiagnosticsForCli(status: DaemonStatus, rpcOk?: boolean): string[] {
if (!status.port || !shouldReportPortUsage(status.port.status, rpcOk)) return [];
if (!status.port || !shouldReportPortUsage(status.port.status, rpcOk)) {
return [];
}
return formatPortDiagnostics({
port: status.port.port,
status: status.port.status,

View File

@@ -29,7 +29,9 @@ import {
function sanitizeDaemonStatusForJson(status: DaemonStatus): DaemonStatus {
const command = status.service.command;
if (!command?.environment) return status;
if (!command?.environment) {
return status;
}
const safeEnv = filterDaemonEnv(command.environment);
const nextCommand = {
...command,
@@ -189,7 +191,9 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
defaultRuntime.log(`${label("RPC probe:")} ${okText("ok")}`);
} else {
defaultRuntime.error(`${label("RPC probe:")} ${errorText("failed")}`);
if (rpc.url) defaultRuntime.error(`${label("RPC target:")} ${rpc.url}`);
if (rpc.url) {
defaultRuntime.error(`${label("RPC target:")} ${rpc.url}`);
}
const lines = String(rpc.error ?? "unknown")
.split(/\r?\n/)
.filter(Boolean);

View File

@@ -52,11 +52,17 @@ type DevicePairingList = {
function formatAge(msAgo: number) {
const s = Math.max(0, Math.floor(msAgo / 1000));
if (s < 60) return `${s}s`;
if (s < 60) {
return `${s}s`;
}
const m = Math.floor(s / 60);
if (m < 60) return `${m}m`;
if (m < 60) {
return `${m}m`;
}
const h = Math.floor(m / 60);
if (h < 24) return `${h}h`;
if (h < 24) {
return `${h}h`;
}
const d = Math.floor(h / 24);
return `${d}d`;
}
@@ -98,7 +104,9 @@ function parseDevicePairingList(value: unknown): DevicePairingList {
}
function formatTokenSummary(tokens: DeviceTokenSummary[] | undefined) {
if (!tokens || tokens.length === 0) return "none";
if (!tokens || tokens.length === 0) {
return "none";
}
const parts = tokens
.map((t) => `${t.role}${t.revokedAtMs ? " (revoked)" : ""}`)
.toSorted((a, b) => a.localeCompare(b));

View File

@@ -12,14 +12,22 @@ import { renderTable } from "../terminal/table.js";
function parseLimit(value: unknown): number | null {
if (typeof value === "number" && Number.isFinite(value)) {
if (value <= 0) return null;
if (value <= 0) {
return null;
}
return Math.floor(value);
}
if (typeof value !== "string") return null;
if (typeof value !== "string") {
return null;
}
const raw = value.trim();
if (!raw) return null;
if (!raw) {
return null;
}
const parsed = Number.parseInt(raw, 10);
if (!Number.isFinite(parsed) || parsed <= 0) return null;
if (!Number.isFinite(parsed) || parsed <= 0) {
return null;
}
return parsed;
}
@@ -60,7 +68,9 @@ export function registerDirectoryCli(program: Command) {
});
const channelId = selection.channel;
const plugin = getChannelPlugin(channelId);
if (!plugin) throw new Error(`Unsupported channel: ${String(channelId)}`);
if (!plugin) {
throw new Error(`Unsupported channel: ${String(channelId)}`);
}
const accountId = opts.account?.trim() || resolveChannelDefaultAccountId({ plugin, cfg });
return { cfg, channelId, accountId, plugin };
};
@@ -73,7 +83,9 @@ export function registerDirectoryCli(program: Command) {
account: opts.account as string | undefined,
});
const fn = plugin.directory?.self;
if (!fn) throw new Error(`Channel ${channelId} does not support directory self`);
if (!fn) {
throw new Error(`Channel ${channelId} does not support directory self`);
}
const result = await fn({ cfg, accountId, runtime: defaultRuntime });
if (opts.json) {
defaultRuntime.log(JSON.stringify(result, null, 2));
@@ -113,7 +125,9 @@ export function registerDirectoryCli(program: Command) {
account: opts.account as string | undefined,
});
const fn = plugin.directory?.listPeers;
if (!fn) throw new Error(`Channel ${channelId} does not support directory peers`);
if (!fn) {
throw new Error(`Channel ${channelId} does not support directory peers`);
}
const result = await fn({
cfg,
accountId,
@@ -158,7 +172,9 @@ export function registerDirectoryCli(program: Command) {
account: opts.account as string | undefined,
});
const fn = plugin.directory?.listGroups;
if (!fn) throw new Error(`Channel ${channelId} does not support directory groups`);
if (!fn) {
throw new Error(`Channel ${channelId} does not support directory groups`);
}
const result = await fn({
cfg,
accountId,
@@ -206,9 +222,13 @@ export function registerDirectoryCli(program: Command) {
account: opts.account as string | undefined,
});
const fn = plugin.directory?.listGroupMembers;
if (!fn) throw new Error(`Channel ${channelId} does not support group members listing`);
if (!fn) {
throw new Error(`Channel ${channelId} does not support group members listing`);
}
const groupId = String(opts.groupId ?? "").trim();
if (!groupId) throw new Error("Missing --group-id");
if (!groupId) {
throw new Error("Missing --group-id");
}
const result = await fn({
cfg,
accountId,

View File

@@ -19,7 +19,9 @@ function run(cmd: string, args: string[], opts?: RunOpts): string {
encoding: "utf-8",
stdio: opts?.inherit ? "inherit" : "pipe",
});
if (res.error) throw res.error;
if (res.error) {
throw res.error;
}
if (!opts?.allowFailure && res.status !== 0) {
const errText =
typeof res.stderr === "string" && res.stderr.trim()
@@ -46,7 +48,9 @@ function writeFileSudoIfNeeded(filePath: string, content: string): void {
encoding: "utf-8",
stdio: ["pipe", "ignore", "inherit"],
});
if (res.error) throw res.error;
if (res.error) {
throw res.error;
}
if (res.status !== 0) {
throw new Error(`sudo tee ${filePath} failed: exit ${res.status ?? "unknown"}`);
}
@@ -67,7 +71,9 @@ function mkdirSudoIfNeeded(dirPath: string): void {
}
function zoneFileNeedsBootstrap(zonePath: string): boolean {
if (!fs.existsSync(zonePath)) return true;
if (!fs.existsSync(zonePath)) {
return true;
}
try {
const content = fs.readFileSync(zonePath, "utf-8");
return !/\bSOA\b/.test(content) || !/\bNS\b/.test(content);
@@ -79,13 +85,17 @@ function zoneFileNeedsBootstrap(zonePath: string): boolean {
function detectBrewPrefix(): string {
const out = run("brew", ["--prefix"]);
const prefix = out.trim();
if (!prefix) throw new Error("failed to resolve Homebrew prefix");
if (!prefix) {
throw new Error("failed to resolve Homebrew prefix");
}
return prefix;
}
function ensureImportLine(corefilePath: string, importGlob: string): boolean {
const existing = fs.readFileSync(corefilePath, "utf-8");
if (existing.includes(importGlob)) return false;
if (existing.includes(importGlob)) {
return false;
}
const next = `${existing.replace(/\s*$/, "")}\n\nimport ${importGlob}\n`;
writeFileSudoIfNeeded(corefilePath, next);
return true;

View File

@@ -34,11 +34,17 @@ type ExecApprovalsCliOpts = NodesRpcOpts & {
function formatAge(msAgo: number) {
const s = Math.max(0, Math.floor(msAgo / 1000));
if (s < 60) return `${s}s`;
if (s < 60) {
return `${s}s`;
}
const m = Math.floor(s / 60);
if (m < 60) return `${m}m`;
if (m < 60) {
return `${m}m`;
}
const h = Math.floor(m / 60);
if (h < 24) return `${h}h`;
if (h < 24) {
return `${h}h`;
}
const d = Math.floor(h / 24);
return `${d}d`;
}
@@ -52,9 +58,13 @@ async function readStdin(): Promise<string> {
}
async function resolveTargetNodeId(opts: ExecApprovalsCliOpts): Promise<string | null> {
if (opts.gateway) return null;
if (opts.gateway) {
return null;
}
const raw = opts.node?.trim() ?? "";
if (!raw) return null;
if (!raw) {
return null;
}
return await resolveNodeId(opts as NodesRpcOpts, raw);
}
@@ -125,7 +135,9 @@ function renderApprovalsSnapshot(snapshot: ExecApprovalsSnapshot, targetLabel: s
const allowlist = Array.isArray(agent.allowlist) ? agent.allowlist : [];
for (const entry of allowlist) {
const pattern = entry?.pattern?.trim() ?? "";
if (!pattern) continue;
if (!pattern) {
continue;
}
const lastUsedAt = typeof entry.lastUsedAt === "number" ? entry.lastUsedAt : null;
allowlistRows.push({
Target: targetLabel,

View File

@@ -32,16 +32,22 @@ async function withEnvOverride<T>(
const saved: Record<string, string | undefined> = {};
for (const key of Object.keys(overrides)) {
saved[key] = process.env[key];
if (overrides[key] === undefined) delete process.env[key];
else process.env[key] = overrides[key];
if (overrides[key] === undefined) {
delete process.env[key];
} else {
process.env[key] = overrides[key];
}
}
vi.resetModules();
try {
return await fn();
} finally {
for (const key of Object.keys(saved)) {
if (saved[key] === undefined) delete process.env[key];
else process.env[key] = saved[key];
if (saved[key] === undefined) {
delete process.env[key];
} else {
process.env[key] = saved[key];
}
}
vi.resetModules();
}
@@ -284,10 +290,14 @@ describe("gateway-cli coverage", () => {
),
).rejects.toThrow("__exit__:1");
for (const listener of process.listeners("SIGTERM")) {
if (!beforeSigterm.has(listener)) process.removeListener("SIGTERM", listener);
if (!beforeSigterm.has(listener)) {
process.removeListener("SIGTERM", listener);
}
}
for (const listener of process.listeners("SIGINT")) {
if (!beforeSigint.has(listener)) process.removeListener("SIGINT", listener);
if (!beforeSigint.has(listener)) {
process.removeListener("SIGINT", listener);
}
}
});

View File

@@ -21,9 +21,13 @@ const DEV_TEMPLATE_DIR = path.resolve(
async function loadDevTemplate(name: string, fallback: string): Promise<string> {
try {
const raw = await fs.promises.readFile(path.join(DEV_TEMPLATE_DIR, name), "utf-8");
if (!raw.startsWith("---")) return raw;
if (!raw.startsWith("---")) {
return raw;
}
const endIndex = raw.indexOf("\n---", 3);
if (endIndex === -1) return raw;
if (endIndex === -1) {
return raw;
}
return raw.slice(endIndex + "\n---".length).replace(/^\s+/, "");
} catch {
return fallback;
@@ -33,7 +37,9 @@ async function loadDevTemplate(name: string, fallback: string): Promise<string>
const resolveDevWorkspaceDir = (env: NodeJS.ProcessEnv = process.env): string => {
const baseDir = resolveDefaultAgentWorkspaceDir(env, os.homedir);
const profile = env.OPENCLAW_PROFILE?.trim().toLowerCase();
if (profile === "dev") return baseDir;
if (profile === "dev") {
return baseDir;
}
return `${baseDir}-${DEV_AGENT_WORKSPACE_SUFFIX}`;
};
@@ -45,7 +51,9 @@ async function writeFileIfMissing(filePath: string, content: string) {
});
} catch (err) {
const anyErr = err as { code?: string };
if (anyErr.code !== "EEXIST") throw err;
if (anyErr.code !== "EEXIST") {
throw err;
}
}
}
@@ -92,7 +100,9 @@ export async function ensureDevGatewayConfig(opts: { reset?: boolean }) {
const io = createConfigIO();
const configPath = io.configPath;
const configExists = fs.existsSync(configPath);
if (!opts.reset && configExists) return;
if (!opts.reset && configExists) {
return;
}
await writeConfigFile({
gateway: {

View File

@@ -7,7 +7,9 @@ export type GatewayDiscoverOpts = {
};
export function parseDiscoverTimeoutMs(raw: unknown, fallbackMs: number): number {
if (raw === undefined || raw === null) return fallbackMs;
if (raw === undefined || raw === null) {
return fallbackMs;
}
const value =
typeof raw === "string"
? raw.trim()
@@ -17,7 +19,9 @@ export function parseDiscoverTimeoutMs(raw: unknown, fallbackMs: number): number
if (value === null) {
throw new Error("invalid --timeout");
}
if (!value) return fallbackMs;
if (!value) {
return fallbackMs;
}
const parsed = Number.parseInt(value, 10);
if (!Number.isFinite(parsed) || parsed <= 0) {
throw new Error(`invalid --timeout: ${value}`);
@@ -48,7 +52,9 @@ export function dedupeBeacons(beacons: GatewayBonjourBeacon[]): GatewayBonjourBe
String(b.port ?? ""),
String(b.gatewayPort ?? ""),
].join("|");
if (seen.has(key)) continue;
if (seen.has(key)) {
continue;
}
seen.add(key);
out.push(b);
}

View File

@@ -31,9 +31,13 @@ import {
import { addGatewayRunCommand } from "./run.js";
function styleHealthChannelLine(line: string, rich: boolean): string {
if (!rich) return line;
if (!rich) {
return line;
}
const colon = line.indexOf(":");
if (colon === -1) return line;
if (colon === -1) {
return line;
}
const label = line.slice(0, colon + 1);
const detail = line.slice(colon + 1).trimStart();
@@ -42,13 +46,27 @@ function styleHealthChannelLine(line: string, rich: boolean): string {
const applyPrefix = (prefix: string, color: (value: string) => string) =>
`${label} ${color(detail.slice(0, prefix.length))}${detail.slice(prefix.length)}`;
if (normalized.startsWith("failed")) return applyPrefix("failed", theme.error);
if (normalized.startsWith("ok")) return applyPrefix("ok", theme.success);
if (normalized.startsWith("linked")) return applyPrefix("linked", theme.success);
if (normalized.startsWith("configured")) return applyPrefix("configured", theme.success);
if (normalized.startsWith("not linked")) return applyPrefix("not linked", theme.warn);
if (normalized.startsWith("not configured")) return applyPrefix("not configured", theme.muted);
if (normalized.startsWith("unknown")) return applyPrefix("unknown", theme.warn);
if (normalized.startsWith("failed")) {
return applyPrefix("failed", theme.error);
}
if (normalized.startsWith("ok")) {
return applyPrefix("ok", theme.success);
}
if (normalized.startsWith("linked")) {
return applyPrefix("linked", theme.success);
}
if (normalized.startsWith("configured")) {
return applyPrefix("configured", theme.success);
}
if (normalized.startsWith("not linked")) {
return applyPrefix("not linked", theme.warn);
}
if (normalized.startsWith("not configured")) {
return applyPrefix("not configured", theme.muted);
}
if (normalized.startsWith("unknown")) {
return applyPrefix("unknown", theme.warn);
}
return line;
}
@@ -62,10 +80,14 @@ function runGatewayCommand(action: () => Promise<void>, label?: string) {
}
function parseDaysOption(raw: unknown, fallback = 30): number {
if (typeof raw === "number" && Number.isFinite(raw)) return Math.max(1, Math.floor(raw));
if (typeof raw === "number" && Number.isFinite(raw)) {
return Math.max(1, Math.floor(raw));
}
if (typeof raw === "string" && raw.trim() !== "") {
const parsed = Number(raw);
if (Number.isFinite(parsed)) return Math.max(1, Math.floor(parsed));
if (Number.isFinite(parsed)) {
return Math.max(1, Math.floor(parsed));
}
}
return fallback;
}
@@ -324,7 +346,9 @@ export function registerGatewayCli(program: Command) {
`Found ${deduped.length} gateway(s) · domains: ${domains.join(", ")}`,
),
);
if (deduped.length === 0) return;
if (deduped.length === 0) {
return;
}
for (const beacon of deduped) {
for (const line of renderBeaconLines(beacon, rich)) {

View File

@@ -8,30 +8,48 @@ import { defaultRuntime } from "../../runtime.js";
import { formatCliCommand } from "../command-format.js";
export function parsePort(raw: unknown): number | null {
if (raw === undefined || raw === null) return null;
if (raw === undefined || raw === null) {
return null;
}
const value =
typeof raw === "string"
? raw
: typeof raw === "number" || typeof raw === "bigint"
? raw.toString()
: null;
if (value === null) return null;
if (value === null) {
return null;
}
const parsed = Number.parseInt(value, 10);
if (!Number.isFinite(parsed) || parsed <= 0) return null;
if (!Number.isFinite(parsed) || parsed <= 0) {
return null;
}
return parsed;
}
export const toOptionString = (value: unknown): string | undefined => {
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "bigint") return value.toString();
if (typeof value === "string") {
return value;
}
if (typeof value === "number" || typeof value === "bigint") {
return value.toString();
}
return undefined;
};
export function describeUnknownError(err: unknown): string {
if (err instanceof Error) return err.message;
if (typeof err === "string") return err;
if (typeof err === "number" || typeof err === "bigint") return err.toString();
if (typeof err === "boolean") return err ? "true" : "false";
if (err instanceof Error) {
return err.message;
}
if (typeof err === "string") {
return err;
}
if (typeof err === "number" || typeof err === "bigint") {
return err.toString();
}
if (typeof err === "boolean") {
return err ? "true" : "false";
}
if (err && typeof err === "object") {
if ("message" in err && typeof err.message === "string") {
return err.message;
@@ -94,7 +112,9 @@ export async function maybeExplainGatewayServiceStop() {
} catch {
loaded = null;
}
if (loaded === false) return;
if (loaded === false) {
return;
}
defaultRuntime.error(
loaded
? `Gateway service appears ${service.loadedText}. Stop it first.`

View File

@@ -67,7 +67,9 @@ describe("gateway SIGTERM", () => {
let child: ReturnType<typeof spawn> | null = null;
afterEach(() => {
if (!child || child.killed) return;
if (!child || child.killed) {
return;
}
try {
child.kill("SIGKILL");
} catch {
@@ -124,7 +126,9 @@ describe("gateway SIGTERM", () => {
});
const proc = child;
if (!proc) throw new Error("failed to spawn gateway");
if (!proc) {
throw new Error("failed to spawn gateway");
}
child.stdout?.setEncoding("utf8");
child.stderr?.setEncoding("utf8");
@@ -148,7 +152,9 @@ describe("gateway SIGTERM", () => {
`--- stdout ---\n${stdout}\n--- stderr ---\n${stderr}`,
);
}
if (result.code === null && result.signal === "SIGTERM") return;
if (result.code === null && result.signal === "SIGTERM") {
return;
}
expect(result.signal).toBeNull();
});
});

View File

@@ -7,7 +7,9 @@ export function formatHelpExample(command: string, description: string): string
}
export function formatHelpExampleLine(command: string, description: string): string {
if (!description) return ` ${theme.command(command)}`;
if (!description) {
return ` ${theme.command(command)}`;
}
return ` ${theme.command(command)} ${theme.muted(`# ${description}`)}`;
}

View File

@@ -67,8 +67,12 @@ function buildHooksReport(config: OpenClawConfig): HookStatusReport {
}
function formatHookStatus(hook: HookStatusEntry): string {
if (hook.eligible) return theme.success("✓ ready");
if (hook.disabled) return theme.warn("⏸ disabled");
if (hook.eligible) {
return theme.success("✓ ready");
}
if (hook.disabled) {
return theme.warn("⏸ disabled");
}
return theme.error("✗ missing");
}
@@ -78,7 +82,9 @@ function formatHookName(hook: HookStatusEntry): string {
}
function formatHookSource(hook: HookStatusEntry): string {
if (!hook.managedByPlugin) return hook.source;
if (!hook.managedByPlugin) {
return hook.source;
}
return `plugin:${hook.pluginId ?? "unknown"}`;
}
@@ -326,13 +332,24 @@ export function formatHooksCheck(report: HookStatusReport, opts: HooksCheckOptio
lines.push(theme.heading("Hooks not ready:"));
for (const hook of notEligible) {
const reasons = [];
if (hook.disabled) reasons.push("disabled");
if (hook.missing.bins.length > 0) reasons.push(`bins: ${hook.missing.bins.join(", ")}`);
if (hook.missing.anyBins.length > 0)
if (hook.disabled) {
reasons.push("disabled");
}
if (hook.missing.bins.length > 0) {
reasons.push(`bins: ${hook.missing.bins.join(", ")}`);
}
if (hook.missing.anyBins.length > 0) {
reasons.push(`anyBins: ${hook.missing.anyBins.join(", ")}`);
if (hook.missing.env.length > 0) reasons.push(`env: ${hook.missing.env.join(", ")}`);
if (hook.missing.config.length > 0) reasons.push(`config: ${hook.missing.config.join(", ")}`);
if (hook.missing.os.length > 0) reasons.push(`os: ${hook.missing.os.join(", ")}`);
}
if (hook.missing.env.length > 0) {
reasons.push(`env: ${hook.missing.env.join(", ")}`);
}
if (hook.missing.config.length > 0) {
reasons.push(`config: ${hook.missing.config.join(", ")}`);
}
if (hook.missing.os.length > 0) {
reasons.push(`os: ${hook.missing.os.join(", ")}`);
}
lines.push(` ${hook.emoji ?? "🔗"} ${hook.name} - ${reasons.join("; ")}`);
}
}

View File

@@ -33,7 +33,9 @@ type LogsCliOptions = {
};
function parsePositiveInt(value: string | undefined, fallback: number): number {
if (!value) return fallback;
if (!value) {
return fallback;
}
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}
@@ -58,10 +60,16 @@ async function fetchLogs(
}
function formatLogTimestamp(value?: string, mode: "pretty" | "plain" = "plain") {
if (!value) return "";
if (!value) {
return "";
}
const parsed = new Date(value);
if (Number.isNaN(parsed.getTime())) return value;
if (mode === "pretty") return parsed.toISOString().slice(11, 19);
if (Number.isNaN(parsed.getTime())) {
return value;
}
if (mode === "pretty") {
return parsed.toISOString().slice(11, 19);
}
return parsed.toISOString();
}
@@ -73,7 +81,9 @@ function formatLogLine(
},
): string {
const parsed = parseLogLine(raw);
if (!parsed) return raw;
if (!parsed) {
return raw;
}
const label = parsed.subsystem ?? parsed.module ?? "";
const time = formatLogTimestamp(parsed.time, opts.pretty ? "pretty" : "plain");
const level = parsed.level ?? "";
@@ -162,8 +172,12 @@ function emitGatewayError(
return;
}
if (!errorLine(colorize(rich, theme.error, message))) return;
if (!errorLine(details.message)) return;
if (!errorLine(colorize(rich, theme.error, message))) {
return;
}
if (!errorLine(details.message)) {
return;
}
errorLine(colorize(rich, theme.muted, hint));
}
@@ -288,7 +302,9 @@ export function registerLogsCli(program: Command) {
: cursor;
first = false;
if (!opts.follow) return;
if (!opts.follow) {
return;
}
await delay(interval);
}
});

View File

@@ -60,13 +60,17 @@ function formatSourceLabel(source: string, workspaceDir: string, agentId: string
function resolveAgent(cfg: ReturnType<typeof loadConfig>, agent?: string) {
const trimmed = agent?.trim();
if (trimmed) return trimmed;
if (trimmed) {
return trimmed;
}
return resolveDefaultAgentId(cfg);
}
function resolveAgentIds(cfg: ReturnType<typeof loadConfig>, agent?: string): string[] {
const trimmed = agent?.trim();
if (trimmed) return [trimmed];
if (trimmed) {
return [trimmed];
}
const list = cfg.agents?.list ?? [];
if (list.length > 0) {
return list.map((entry) => entry.id).filter(Boolean);
@@ -84,7 +88,9 @@ async function checkReadableFile(pathname: string): Promise<{ exists: boolean; i
return { exists: true };
} catch (err) {
const code = (err as NodeJS.ErrnoException).code;
if (code === "ENOENT") return { exists: false };
if (code === "ENOENT") {
return { exists: false };
}
return {
exists: true,
issue: `${shortenHomePath(pathname)} not readable (${code ?? "error"})`,
@@ -125,16 +131,24 @@ async function scanMemoryFiles(
const primary = await checkReadableFile(memoryFile);
const alt = await checkReadableFile(altMemoryFile);
if (primary.issue) issues.push(primary.issue);
if (alt.issue) issues.push(alt.issue);
if (primary.issue) {
issues.push(primary.issue);
}
if (alt.issue) {
issues.push(alt.issue);
}
const resolvedExtraPaths = normalizeExtraMemoryPaths(workspaceDir, extraPaths);
for (const extraPath of resolvedExtraPaths) {
try {
const stat = await fs.lstat(extraPath);
if (stat.isSymbolicLink()) continue;
if (stat.isSymbolicLink()) {
continue;
}
const extraCheck = await checkReadableFile(extraPath);
if (extraCheck.issue) issues.push(extraCheck.issue);
if (extraCheck.issue) {
issues.push(extraCheck.issue);
}
} catch (err) {
const code = (err as NodeJS.ErrnoException).code;
if (code === "ENOENT") {
@@ -185,8 +199,12 @@ async function scanMemoryFiles(
} else {
const files = new Set<string>(listedOk ? listed : []);
if (!listedOk) {
if (primary.exists) files.add(memoryFile);
if (alt.exists) files.add(altMemoryFile);
if (primary.exists) {
files.add(memoryFile);
}
if (alt.exists) {
files.add(altMemoryFile);
}
}
totalFiles = files.size;
}
@@ -274,7 +292,9 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
total: syncUpdate.total,
label: syncUpdate.label,
});
if (syncUpdate.label) progress.setLabel(syncUpdate.label);
if (syncUpdate.label) {
progress.setLabel(syncUpdate.label);
}
},
});
} catch (err) {
@@ -532,10 +552,14 @@ export function registerMemoryCli(program: Command) {
return `${minutes}:${String(remainingSeconds).padStart(2, "0")}`;
};
const formatEta = () => {
if (lastTotal <= 0 || lastCompleted <= 0) return null;
if (lastTotal <= 0 || lastCompleted <= 0) {
return null;
}
const elapsedMs = Math.max(1, Date.now() - startedAt);
const rate = lastCompleted / elapsedMs;
if (!Number.isFinite(rate) || rate <= 0) return null;
if (!Number.isFinite(rate) || rate <= 0) {
return null;
}
const remainingMs = Math.max(0, (lastTotal - lastCompleted) / rate);
const seconds = Math.floor(remainingMs / 1000);
const minutes = Math.floor(seconds / 60);
@@ -564,7 +588,9 @@ export function registerMemoryCli(program: Command) {
reason: "cli",
force: opts.force,
progress: (syncUpdate) => {
if (syncUpdate.label) lastLabel = syncUpdate.label;
if (syncUpdate.label) {
lastLabel = syncUpdate.label;
}
lastCompleted = syncUpdate.completed;
lastTotal = syncUpdate.total;
update({

View File

@@ -113,7 +113,9 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) {
hints?: string[];
warnings?: string[];
}) => {
if (!json) return;
if (!json) {
return;
}
emitDaemonActionJson({ action: "install", ...payload });
};
const fail = (message: string, hints?: string[]) => {
@@ -127,7 +129,9 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) {
} else {
defaultRuntime.error(message);
if (hints?.length) {
for (const hint of hints) defaultRuntime.log(`Tip: ${hint}`);
for (const hint of hints) {
defaultRuntime.log(`Tip: ${hint}`);
}
}
}
defaultRuntime.exit(1);
@@ -187,8 +191,11 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) {
displayName: opts.displayName,
runtime: runtimeRaw,
warn: (message) => {
if (json) warnings.push(message);
else defaultRuntime.log(message);
if (json) {
warnings.push(message);
} else {
defaultRuntime.log(message);
}
},
});
@@ -235,12 +242,17 @@ export async function runNodeDaemonUninstall(opts: NodeDaemonLifecycleOptions =
notLoadedText: string;
};
}) => {
if (!json) return;
if (!json) {
return;
}
emitDaemonActionJson({ action: "uninstall", ...payload });
};
const fail = (message: string) => {
if (json) emit({ ok: false, error: message });
else defaultRuntime.error(message);
if (json) {
emit({ ok: false, error: message });
} else {
defaultRuntime.error(message);
}
defaultRuntime.exit(1);
};
@@ -286,12 +298,17 @@ export async function runNodeDaemonStart(opts: NodeDaemonLifecycleOptions = {})
notLoadedText: string;
};
}) => {
if (!json) return;
if (!json) {
return;
}
emitDaemonActionJson({ action: "start", ...payload });
};
const fail = (message: string, hints?: string[]) => {
if (json) emit({ ok: false, error: message, hints });
else defaultRuntime.error(message);
if (json) {
emit({ ok: false, error: message, hints });
} else {
defaultRuntime.error(message);
}
defaultRuntime.exit(1);
};
@@ -363,12 +380,17 @@ export async function runNodeDaemonRestart(opts: NodeDaemonLifecycleOptions = {}
notLoadedText: string;
};
}) => {
if (!json) return;
if (!json) {
return;
}
emitDaemonActionJson({ action: "restart", ...payload });
};
const fail = (message: string, hints?: string[]) => {
if (json) emit({ ok: false, error: message, hints });
else defaultRuntime.error(message);
if (json) {
emit({ ok: false, error: message, hints });
} else {
defaultRuntime.error(message);
}
defaultRuntime.exit(1);
};
@@ -439,12 +461,17 @@ export async function runNodeDaemonStop(opts: NodeDaemonLifecycleOptions = {}) {
notLoadedText: string;
};
}) => {
if (!json) return;
if (!json) {
return;
}
emitDaemonActionJson({ action: "stop", ...payload });
};
const fail = (message: string) => {
if (json) emit({ ok: false, error: message });
else defaultRuntime.error(message);
if (json) {
emit({ ok: false, error: message });
} else {
defaultRuntime.error(message);
}
defaultRuntime.exit(1);
};

View File

@@ -44,7 +44,9 @@ export function validateA2UIJsonl(jsonl: string) {
lines.forEach((line, idx) => {
const trimmed = line.trim();
if (!trimmed) return;
if (!trimmed) {
return;
}
messageCount += 1;
let obj: unknown;
try {

View File

@@ -22,7 +22,9 @@ export function runNodesCommand(label: string, action: () => Promise<void>) {
const { error, warn } = getNodesTheme();
defaultRuntime.error(error(`nodes ${label} failed: ${message}`));
const hint = unauthorizedHintForMessage(message);
if (hint) defaultRuntime.error(warn(hint));
if (hint) {
defaultRuntime.error(warn(hint));
}
defaultRuntime.exit(1);
});
}

View File

@@ -2,11 +2,17 @@ import type { NodeListNode, PairedNode, PairingList, PendingRequest } from "./ty
export function formatAge(msAgo: number) {
const s = Math.max(0, Math.floor(msAgo / 1000));
if (s < 60) return `${s}s`;
if (s < 60) {
return `${s}s`;
}
const m = Math.floor(s / 60);
if (m < 60) return `${m}m`;
if (m < 60) {
return `${m}m`;
}
const h = Math.floor(m / 60);
if (h < 24) return `${h}h`;
if (h < 24) {
return `${h}h`;
}
const d = Math.floor(h / 24);
return `${d}d`;
}
@@ -24,12 +30,16 @@ export function parseNodeList(value: unknown): NodeListNode[] {
}
export function formatPermissions(raw: unknown) {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
return null;
}
const entries = Object.entries(raw as Record<string, unknown>)
.map(([key, value]) => [String(key).trim(), value === true] as const)
.filter(([key]) => key.length > 0)
.toSorted((a, b) => a[0].localeCompare(b[0]));
if (entries.length === 0) return null;
if (entries.length === 0) {
return null;
}
const parts = entries.map(([key, granted]) => `${key}=${granted ? "yes" : "no"}`);
return `[${parts.join(", ")}]`;
}

View File

@@ -19,7 +19,9 @@ const parseFacing = (value: string): CameraFacing => {
const v = String(value ?? "")
.trim()
.toLowerCase();
if (v === "front" || v === "back") return v;
if (v === "front" || v === "back") {
return v;
}
throw new Error(`invalid facing: ${value} (expected front|back)`);
};

View File

@@ -112,7 +112,9 @@ export function registerNodesCanvasCommands(nodes: Command) {
height: opts.height ? Number.parseFloat(opts.height) : undefined,
};
const params: Record<string, unknown> = {};
if (opts.target) params.url = String(opts.target);
if (opts.target) {
params.url = String(opts.target);
}
if (
Number.isFinite(placement.x) ||
Number.isFinite(placement.y) ||
@@ -176,7 +178,9 @@ export function registerNodesCanvasCommands(nodes: Command) {
.action(async (jsArg: string | undefined, opts: NodesRpcOpts) => {
await runNodesCommand("canvas eval", async () => {
const js = opts.js ?? jsArg;
if (!js) throw new Error("missing --js or <js>");
if (!js) {
throw new Error("missing --js or <js>");
}
const raw = await invokeCanvas(opts, "canvas.eval", {
javaScript: js,
});
@@ -188,8 +192,9 @@ export function registerNodesCanvasCommands(nodes: Command) {
typeof raw === "object" && raw !== null
? (raw as { payload?: { result?: string } }).payload
: undefined;
if (payload?.result) defaultRuntime.log(payload.result);
else {
if (payload?.result) {
defaultRuntime.log(payload.result);
} else {
const { ok } = getNodesTheme();
defaultRuntime.log(ok("canvas eval ok"));
}

View File

@@ -58,7 +58,9 @@ function normalizeExecAsk(value?: string | null): ExecAsk | null {
}
function mergePathPrepend(existing: string | undefined, prepend: string[]) {
if (prepend.length === 0) return existing;
if (prepend.length === 0) {
return existing;
}
const partsExisting = (existing ?? "")
.split(path.delimiter)
.map((part) => part.trim())
@@ -66,7 +68,9 @@ function mergePathPrepend(existing: string | undefined, prepend: string[]) {
const merged: string[] = [];
const seen = new Set<string>();
for (const part of [...prepend, ...partsExisting]) {
if (seen.has(part)) continue;
if (seen.has(part)) {
continue;
}
seen.add(part);
merged.push(part);
}
@@ -78,10 +82,16 @@ function applyPathPrepend(
prepend: string[] | undefined,
options?: { requireExisting?: boolean },
) {
if (!Array.isArray(prepend) || prepend.length === 0) return;
if (options?.requireExisting && !env.PATH) return;
if (!Array.isArray(prepend) || prepend.length === 0) {
return;
}
if (options?.requireExisting && !env.PATH) {
return;
}
const merged = mergePathPrepend(env.PATH, prepend);
if (merged) env.PATH = merged;
if (merged) {
env.PATH = merged;
}
}
function resolveExecDefaults(
@@ -341,8 +351,12 @@ export function registerNodesInvokeCommands(nodes: Command) {
const timedOut = payload?.timedOut === true;
const success = payload?.success === true;
if (stdout) process.stdout.write(stdout);
if (stderr) process.stderr.write(stderr);
if (stdout) {
process.stdout.write(stdout);
}
if (stderr) {
process.stderr.write(stderr);
}
if (timedOut) {
const { error } = getNodesTheme();
defaultRuntime.error(error("run timed out"));

View File

@@ -10,8 +10,12 @@ import { shortenHomeInString } from "../../utils.js";
function formatVersionLabel(raw: string) {
const trimmed = raw.trim();
if (!trimmed) return raw;
if (trimmed.toLowerCase().startsWith("v")) return trimmed;
if (!trimmed) {
return raw;
}
if (trimmed.toLowerCase().startsWith("v")) {
return trimmed;
}
return /^\d/.test(trimmed) ? `v${trimmed}` : trimmed;
}
@@ -23,9 +27,13 @@ function resolveNodeVersions(node: {
}) {
const core = node.coreVersion?.trim() || undefined;
const ui = node.uiVersion?.trim() || undefined;
if (core || ui) return { core, ui };
if (core || ui) {
return { core, ui };
}
const legacy = node.version?.trim();
if (!legacy) return { core: undefined, ui: undefined };
if (!legacy) {
return { core: undefined, ui: undefined };
}
const platform = node.platform?.trim().toLowerCase() ?? "";
const headless =
platform === "darwin" || platform === "linux" || platform === "win32" || platform === "windows";
@@ -40,15 +48,23 @@ function formatNodeVersions(node: {
}) {
const { core, ui } = resolveNodeVersions(node);
const parts: string[] = [];
if (core) parts.push(`core ${formatVersionLabel(core)}`);
if (ui) parts.push(`ui ${formatVersionLabel(ui)}`);
if (core) {
parts.push(`core ${formatVersionLabel(core)}`);
}
if (ui) {
parts.push(`ui ${formatVersionLabel(ui)}`);
}
return parts.length > 0 ? parts.join(" · ") : null;
}
function formatPathEnv(raw?: string): string | null {
if (typeof raw !== "string") return null;
if (typeof raw !== "string") {
return null;
}
const trimmed = raw.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const parts = trimmed.split(":").filter(Boolean);
const display =
parts.length <= 3 ? trimmed : `${parts.slice(0, 2).join(":")}:…:${parts.slice(-1)[0]}`;
@@ -56,7 +72,9 @@ function formatPathEnv(raw?: string): string | null {
}
function parseSinceMs(raw: unknown, label: string): number | undefined {
if (raw === undefined || raw === null) return undefined;
if (raw === undefined || raw === null) {
return undefined;
}
const value =
typeof raw === "string" ? raw.trim() : typeof raw === "number" ? String(raw).trim() : null;
if (value === null) {
@@ -64,7 +82,9 @@ function parseSinceMs(raw: unknown, label: string): number | undefined {
defaultRuntime.exit(1);
return undefined;
}
if (!value) return undefined;
if (!value) {
return undefined;
}
try {
return parseDurationMs(value);
} catch (err) {
@@ -104,7 +124,9 @@ export function registerNodesStatusCommands(nodes: Command) {
)
: null;
const filtered = nodes.filter((n) => {
if (connectedOnly && !n.connected) return false;
if (connectedOnly && !n.connected) {
return false;
}
if (sinceMs !== undefined) {
const paired = lastConnectedById?.get(n.nodeId);
const lastConnectedAtMs =
@@ -113,8 +135,12 @@ export function registerNodesStatusCommands(nodes: Command) {
: typeof n.connectedAtMs === "number"
? n.connectedAtMs
: undefined;
if (typeof lastConnectedAtMs !== "number") return false;
if (now - lastConnectedAtMs > sinceMs) return false;
if (typeof lastConnectedAtMs !== "number") {
return false;
}
if (now - lastConnectedAtMs > sinceMs) {
return false;
}
}
return true;
});
@@ -131,7 +157,9 @@ export function registerNodesStatusCommands(nodes: Command) {
defaultRuntime.log(
`Known: ${filtered.length}${filteredLabel} · Paired: ${pairedCount} · Connected: ${connectedCount}`,
);
if (filtered.length === 0) return;
if (filtered.length === 0) {
return;
}
const rows = filtered.map((n) => {
const name = n.displayName?.trim() ? n.displayName.trim() : n.nodeId;
@@ -261,7 +289,9 @@ export function registerNodesStatusCommands(nodes: Command) {
defaultRuntime.log(muted("- (none reported)"));
return;
}
for (const c of commands) defaultRuntime.log(`- ${c}`);
for (const c of commands) {
defaultRuntime.log(`- ${c}`);
}
});
}),
);
@@ -294,7 +324,9 @@ export function registerNodesStatusCommands(nodes: Command) {
const filteredPaired = paired.filter((node) => {
if (connectedOnly) {
const live = connectedById?.get(node.nodeId);
if (!live?.connected) return false;
if (!live?.connected) {
return false;
}
}
if (sinceMs !== undefined) {
const live = connectedById?.get(node.nodeId);
@@ -304,8 +336,12 @@ export function registerNodesStatusCommands(nodes: Command) {
: typeof live?.connectedAtMs === "number"
? live.connectedAtMs
: undefined;
if (typeof lastConnectedAtMs !== "number") return false;
if (now - lastConnectedAtMs > sinceMs) return false;
if (typeof lastConnectedAtMs !== "number") {
return false;
}
if (now - lastConnectedAtMs > sinceMs) {
return false;
}
}
return true;
});

View File

@@ -57,7 +57,9 @@ function normalizeNodeKey(value: string) {
export async function resolveNodeId(opts: NodesRpcOpts, query: string) {
const q = String(query ?? "").trim();
if (!q) throw new Error("node required");
if (!q) {
throw new Error("node required");
}
let nodes: NodeListNode[] = [];
try {
@@ -77,15 +79,25 @@ export async function resolveNodeId(opts: NodesRpcOpts, query: string) {
const qNorm = normalizeNodeKey(q);
const matches = nodes.filter((n) => {
if (n.nodeId === q) return true;
if (typeof n.remoteIp === "string" && n.remoteIp === q) return true;
if (n.nodeId === q) {
return true;
}
if (typeof n.remoteIp === "string" && n.remoteIp === q) {
return true;
}
const name = typeof n.displayName === "string" ? n.displayName : "";
if (name && normalizeNodeKey(name) === qNorm) return true;
if (q.length >= 6 && n.nodeId.startsWith(q)) return true;
if (name && normalizeNodeKey(name) === qNorm) {
return true;
}
if (q.length >= 6 && n.nodeId.startsWith(q)) {
return true;
}
return false;
});
if (matches.length === 1) return matches[0].nodeId;
if (matches.length === 1) {
return matches[0].nodeId;
}
if (matches.length === 0) {
const known = nodes
.map((n) => n.displayName || n.remoteIp || n.nodeId)

View File

@@ -1,14 +1,22 @@
import { parseTimeoutMs } from "./parse-timeout.js";
export function parseEnvPairs(pairs: unknown): Record<string, string> | undefined {
if (!Array.isArray(pairs) || pairs.length === 0) return undefined;
if (!Array.isArray(pairs) || pairs.length === 0) {
return undefined;
}
const env: Record<string, string> = {};
for (const pair of pairs) {
if (typeof pair !== "string") continue;
if (typeof pair !== "string") {
continue;
}
const idx = pair.indexOf("=");
if (idx <= 0) continue;
if (idx <= 0) {
continue;
}
const key = pair.slice(0, idx).trim();
if (!key) continue;
if (!key) {
continue;
}
env[key] = pair.slice(idx + 1);
}
return Object.keys(env).length > 0 ? env : undefined;

View File

@@ -9,9 +9,15 @@ const pairingIdLabels: Record<string, string> = {
discord: "discordUserId",
};
const normalizeChannelId = vi.fn((raw: string) => {
if (!raw) return null;
if (raw === "imsg") return "imessage";
if (["telegram", "discord", "imessage"].includes(raw)) return raw;
if (!raw) {
return null;
}
if (raw === "imsg") {
return "imessage";
}
if (["telegram", "discord", "imessage"].includes(raw)) {
return raw;
}
return null;
});
const getPairingAdapter = vi.fn((channel: string) => ({

View File

@@ -25,7 +25,9 @@ function parseChannel(raw: unknown, channels: PairingChannel[]): PairingChannel
)
.trim()
.toLowerCase();
if (!value) throw new Error("Channel required");
if (!value) {
throw new Error("Channel required");
}
const normalized = normalizeChannelId(value);
if (normalized) {
@@ -36,7 +38,9 @@ function parseChannel(raw: unknown, channels: PairingChannel[]): PairingChannel
}
// Allow extension channels: validate format but don't require registry
if (/^[a-z][a-z0-9_-]{0,63}$/.test(value)) return value as PairingChannel;
if (/^[a-z][a-z0-9_-]{0,63}$/.test(value)) {
return value as PairingChannel;
}
throw new Error(`Invalid channel: ${value}`);
}
@@ -136,7 +140,9 @@ export function registerPairingCli(program: Command) {
`${theme.success("Approved")} ${theme.muted(channel)} sender ${theme.command(approved.id)}.`,
);
if (!opts.notify) return;
if (!opts.notify) {
return;
}
await notifyApproved(channel, approved.id).catch((err) => {
defaultRuntime.log(theme.warn(`Failed to notify requester: ${String(err)}`));
});

View File

@@ -6,10 +6,14 @@ export function parseDurationMs(raw: string, opts?: DurationMsParseOptions): num
const trimmed = String(raw ?? "")
.trim()
.toLowerCase();
if (!trimmed) throw new Error("invalid duration (empty)");
if (!trimmed) {
throw new Error("invalid duration (empty)");
}
const m = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)?$/.exec(trimmed);
if (!m) throw new Error(`invalid duration: ${raw}`);
if (!m) {
throw new Error(`invalid duration: ${raw}`);
}
const value = Number(m[1]);
if (!Number.isFinite(value) || value < 0) {
@@ -28,6 +32,8 @@ export function parseDurationMs(raw: string, opts?: DurationMsParseOptions): num
? 3_600_000
: 86_400_000;
const ms = Math.round(value * multiplier);
if (!Number.isFinite(ms)) throw new Error(`invalid duration: ${raw}`);
if (!Number.isFinite(ms)) {
throw new Error(`invalid duration: ${raw}`);
}
return ms;
}

View File

@@ -1,5 +1,7 @@
export function parseTimeoutMs(raw: unknown): number | undefined {
if (raw === undefined || raw === null) return undefined;
if (raw === undefined || raw === null) {
return undefined;
}
let value = Number.NaN;
if (typeof raw === "number") {
value = raw;
@@ -7,7 +9,9 @@ export function parseTimeoutMs(raw: unknown): number | undefined {
value = Number(raw);
} else if (typeof raw === "string") {
const trimmed = raw.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
value = Number.parseInt(trimmed, 10);
}
return Number.isFinite(value) ? value : undefined;

View File

@@ -8,7 +8,9 @@ const log = createSubsystemLogger("plugins");
let pluginRegistryLoaded = false;
export function ensurePluginRegistryLoaded(): void {
if (pluginRegistryLoaded) return;
if (pluginRegistryLoaded) {
return;
}
const config = loadConfig();
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
const logger: PluginLogger = {

View File

@@ -58,11 +58,15 @@ function formatPluginLine(plugin: PluginRecord, verbose = false): string {
` source: ${theme.muted(shortenHomeInString(plugin.source))}`,
` origin: ${plugin.origin}`,
];
if (plugin.version) parts.push(` version: ${plugin.version}`);
if (plugin.version) {
parts.push(` version: ${plugin.version}`);
}
if (plugin.providerIds.length > 0) {
parts.push(` providers: ${plugin.providerIds.join(", ")}`);
}
if (plugin.error) parts.push(theme.error(` error: ${plugin.error}`));
if (plugin.error) {
parts.push(theme.error(` error: ${plugin.error}`));
}
return parts.join("\n");
}
@@ -85,7 +89,9 @@ function applySlotSelectionForPlugin(
}
function logSlotWarnings(warnings: string[]) {
if (warnings.length === 0) return;
if (warnings.length === 0) {
return;
}
for (const warning of warnings) {
defaultRuntime.log(theme.warn(warning));
}
@@ -200,12 +206,16 @@ export function registerPluginsCli(program: Command) {
if (plugin.name && plugin.name !== plugin.id) {
lines.push(theme.muted(`id: ${plugin.id}`));
}
if (plugin.description) lines.push(plugin.description);
if (plugin.description) {
lines.push(plugin.description);
}
lines.push("");
lines.push(`${theme.muted("Status:")} ${plugin.status}`);
lines.push(`${theme.muted("Source:")} ${shortenHomeInString(plugin.source)}`);
lines.push(`${theme.muted("Origin:")} ${plugin.origin}`);
if (plugin.version) lines.push(`${theme.muted("Version:")} ${plugin.version}`);
if (plugin.version) {
lines.push(`${theme.muted("Version:")} ${plugin.version}`);
}
if (plugin.toolNames.length > 0) {
lines.push(`${theme.muted("Tools:")} ${plugin.toolNames.join(", ")}`);
}
@@ -224,18 +234,27 @@ export function registerPluginsCli(program: Command) {
if (plugin.services.length > 0) {
lines.push(`${theme.muted("Services:")} ${plugin.services.join(", ")}`);
}
if (plugin.error) lines.push(`${theme.error("Error:")} ${plugin.error}`);
if (plugin.error) {
lines.push(`${theme.error("Error:")} ${plugin.error}`);
}
if (install) {
lines.push("");
lines.push(`${theme.muted("Install:")} ${install.source}`);
if (install.spec) lines.push(`${theme.muted("Spec:")} ${install.spec}`);
if (install.sourcePath)
if (install.spec) {
lines.push(`${theme.muted("Spec:")} ${install.spec}`);
}
if (install.sourcePath) {
lines.push(`${theme.muted("Source path:")} ${shortenHomePath(install.sourcePath)}`);
if (install.installPath)
}
if (install.installPath) {
lines.push(`${theme.muted("Install path:")} ${shortenHomePath(install.installPath)}`);
if (install.version) lines.push(`${theme.muted("Recorded version:")} ${install.version}`);
if (install.installedAt)
}
if (install.version) {
lines.push(`${theme.muted("Recorded version:")} ${install.version}`);
}
if (install.installedAt) {
lines.push(`${theme.muted("Installed at:")} ${install.installedAt}`);
}
}
defaultRuntime.log(lines.join("\n"));
});
@@ -514,7 +533,9 @@ export function registerPluginsCli(program: Command) {
}
}
if (diags.length > 0) {
if (lines.length > 0) lines.push("");
if (lines.length > 0) {
lines.push("");
}
lines.push(theme.warn("Diagnostics:"));
for (const diag of diags) {
const target = diag.pluginId ? `${diag.pluginId}: ` : "";

View File

@@ -19,13 +19,17 @@ export function parseLsofOutput(output: string): PortProcess[] {
let current: Partial<PortProcess> = {};
for (const line of lines) {
if (line.startsWith("p")) {
if (current.pid) results.push(current as PortProcess);
if (current.pid) {
results.push(current as PortProcess);
}
current = { pid: Number.parseInt(line.slice(1), 10) };
} else if (line.startsWith("c")) {
current.command = line.slice(1);
}
}
if (current.pid) results.push(current as PortProcess);
if (current.pid) {
results.push(current as PortProcess);
}
return results;
}
@@ -42,7 +46,9 @@ export function listPortListeners(port: number): PortProcess[] {
if (code === "ENOENT") {
throw new Error("lsof not found; required for --force", { cause: err });
}
if (status === 1) return []; // no listeners
if (status === 1) {
return [];
} // no listeners
throw err instanceof Error ? err : new Error(String(err));
}
}

View File

@@ -1,15 +1,23 @@
const PROFILE_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
export function isValidProfileName(value: string): boolean {
if (!value) return false;
if (!value) {
return false;
}
// Keep it path-safe + shell-friendly.
return PROFILE_NAME_RE.test(value);
}
export function normalizeProfileName(raw?: string | null): string | null {
const profile = raw?.trim();
if (!profile) return null;
if (profile.toLowerCase() === "default") return null;
if (!isValidProfileName(profile)) return null;
if (!profile) {
return null;
}
if (profile.toLowerCase() === "default") {
return null;
}
if (!isValidProfileName(profile)) {
return null;
}
return profile;
}

View File

@@ -12,21 +12,27 @@ describe("parseCliProfileArgs", () => {
"--dev",
"--allow-unconfigured",
]);
if (!res.ok) throw new Error(res.error);
if (!res.ok) {
throw new Error(res.error);
}
expect(res.profile).toBeNull();
expect(res.argv).toEqual(["node", "openclaw", "gateway", "--dev", "--allow-unconfigured"]);
});
it("still accepts global --dev before subcommand", () => {
const res = parseCliProfileArgs(["node", "openclaw", "--dev", "gateway"]);
if (!res.ok) throw new Error(res.error);
if (!res.ok) {
throw new Error(res.error);
}
expect(res.profile).toBe("dev");
expect(res.argv).toEqual(["node", "openclaw", "gateway"]);
});
it("parses --profile value and strips it", () => {
const res = parseCliProfileArgs(["node", "openclaw", "--profile", "work", "status"]);
if (!res.ok) throw new Error(res.error);
if (!res.ok) {
throw new Error(res.error);
}
expect(res.profile).toBe("work");
expect(res.argv).toEqual(["node", "openclaw", "status"]);
});

View File

@@ -24,7 +24,9 @@ function takeValue(
}
export function parseCliProfileArgs(argv: string[]): CliProfileParseResult {
if (argv.length < 2) return { ok: true, profile: null, argv };
if (argv.length < 2) {
return { ok: true, profile: null, argv };
}
const out: string[] = argv.slice(0, 2);
let profile: string | null = null;
@@ -34,7 +36,9 @@ export function parseCliProfileArgs(argv: string[]): CliProfileParseResult {
const args = argv.slice(2);
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (arg === undefined) continue;
if (arg === undefined) {
continue;
}
if (sawCommand) {
out.push(arg);
@@ -56,8 +60,12 @@ export function parseCliProfileArgs(argv: string[]): CliProfileParseResult {
}
const next = args[i + 1];
const { value, consumedNext } = takeValue(arg, next);
if (consumedNext) i += 1;
if (!value) return { ok: false, error: "--profile requires a value" };
if (consumedNext) {
i += 1;
}
if (!value) {
return { ok: false, error: "--profile requires a value" };
}
if (!isValidProfileName(value)) {
return {
ok: false,
@@ -93,13 +101,17 @@ export function applyCliProfileEnv(params: {
const env = params.env ?? (process.env as Record<string, string | undefined>);
const homedir = params.homedir ?? os.homedir;
const profile = params.profile.trim();
if (!profile) return;
if (!profile) {
return;
}
// Convenience only: fill defaults, never override explicit env values.
env.OPENCLAW_PROFILE = profile;
const stateDir = env.OPENCLAW_STATE_DIR?.trim() || resolveProfileStateDir(profile, homedir);
if (!env.OPENCLAW_STATE_DIR?.trim()) env.OPENCLAW_STATE_DIR = stateDir;
if (!env.OPENCLAW_STATE_DIR?.trim()) {
env.OPENCLAW_STATE_DIR = stateDir;
}
if (!env.OPENCLAW_CONFIG_PATH?.trim()) {
env.OPENCLAW_CONFIG_PATH = path.join(stateDir, "openclaw.json");

View File

@@ -84,8 +84,12 @@ describe("gateway --force helpers", () => {
(execFileSync as unknown as vi.Mock).mockImplementation(() => {
call += 1;
// 1st call: initial listeners to kill; 2nd call: still listed; 3rd call: gone.
if (call === 1) return ["p42", "cnode", ""].join("\n");
if (call === 2) return ["p42", "cnode", ""].join("\n");
if (call === 1) {
return ["p42", "cnode", ""].join("\n");
}
if (call === 2) {
return ["p42", "cnode", ""].join("\n");
}
return "";
});
@@ -116,7 +120,9 @@ describe("gateway --force helpers", () => {
(execFileSync as unknown as vi.Mock).mockImplementation(() => {
call += 1;
// 1st call: initial kill list; then keep showing until after SIGKILL.
if (call <= 6) return ["p42", "cnode", ""].join("\n");
if (call <= 6) {
return ["p42", "cnode", ""].join("\n");
}
return "";
});

View File

@@ -44,7 +44,9 @@ const routeHealth: RouteSpec = {
const json = hasFlag(argv, "--json");
const verbose = getVerboseFlag(argv, { includeDebug: true });
const timeoutMs = getPositiveIntFlagValue(argv, "--timeout");
if (timeoutMs === null) return false;
if (timeoutMs === null) {
return false;
}
await healthCommand({ json, timeoutMs, verbose }, defaultRuntime);
return true;
},
@@ -60,7 +62,9 @@ const routeStatus: RouteSpec = {
const usage = hasFlag(argv, "--usage");
const verbose = getVerboseFlag(argv, { includeDebug: true });
const timeoutMs = getPositiveIntFlagValue(argv, "--timeout");
if (timeoutMs === null) return false;
if (timeoutMs === null) {
return false;
}
await statusCommand({ json, deep, all, usage, timeoutMs, verbose }, defaultRuntime);
return true;
},
@@ -71,9 +75,13 @@ const routeSessions: RouteSpec = {
run: async (argv) => {
const json = hasFlag(argv, "--json");
const store = getFlagValue(argv, "--store");
if (store === null) return false;
if (store === null) {
return false;
}
const active = getFlagValue(argv, "--active");
if (active === null) return false;
if (active === null) {
return false;
}
await sessionsCommand({ json, store, active }, defaultRuntime);
return true;
},
@@ -93,7 +101,9 @@ const routeMemoryStatus: RouteSpec = {
match: (path) => path[0] === "memory" && path[1] === "status",
run: async (argv) => {
const agent = getFlagValue(argv, "--agent");
if (agent === null) return false;
if (agent === null) {
return false;
}
const json = hasFlag(argv, "--json");
const deep = hasFlag(argv, "--deep");
const index = hasFlag(argv, "--index");
@@ -166,9 +176,13 @@ export function registerProgramCommands(
export function findRoutedCommand(path: string[]): RouteSpec | null {
for (const entry of commandRegistry) {
if (!entry.routes) continue;
if (!entry.routes) {
continue;
}
for (const route of entry.routes) {
if (route.match(path)) return route;
if (route.match(path)) {
return route;
}
}
}
return null;

View File

@@ -52,7 +52,9 @@ export async function ensureConfigReady(params: {
: [];
const invalid = snapshot.exists && !snapshot.valid;
if (!invalid) return;
if (!invalid) {
return;
}
const rich = isRich();
const muted = (value: string) => colorize(rich, theme.muted, value);

View File

@@ -73,7 +73,9 @@ export function configureProgramHelp(program: Command, ctx: ProgramContext) {
}
program.addHelpText("beforeAll", () => {
if (hasEmittedCliBanner()) return "";
if (hasEmittedCliBanner()) {
return "";
}
const rich = isRich();
const line = formatCliBannerLine(ctx.programVersion, { richTty: rich });
return `\n${line}\n`;
@@ -84,7 +86,9 @@ export function configureProgramHelp(program: Command, ctx: ProgramContext) {
).join("\n");
program.addHelpText("afterAll", ({ command }) => {
if (command !== program) return "";
if (command !== program) {
return "";
}
const docs = formatDocsLink("/cli", "docs.openclaw.ai/cli");
return `\n${theme.heading("Examples:")}\n${fmtExamples}\n\n${theme.muted("Docs:")} ${docs}\n`;
});

View File

@@ -3,22 +3,30 @@ export function collectOption(value: string, previous: string[] = []): string[]
}
export function parsePositiveIntOrUndefined(value: unknown): number | undefined {
if (value === undefined || value === null || value === "") return undefined;
if (value === undefined || value === null || value === "") {
return undefined;
}
if (typeof value === "number") {
if (!Number.isFinite(value)) return undefined;
if (!Number.isFinite(value)) {
return undefined;
}
const parsed = Math.trunc(value);
return parsed > 0 ? parsed : undefined;
}
if (typeof value === "string") {
const parsed = Number.parseInt(value, 10);
if (Number.isNaN(parsed) || parsed <= 0) return undefined;
if (Number.isNaN(parsed) || parsed <= 0) {
return undefined;
}
return parsed;
}
return undefined;
}
export function resolveActionArgs(actionCommand?: import("commander").Command): string[] {
if (!actionCommand) return [];
if (!actionCommand) {
return [];
}
const args = (actionCommand as import("commander").Command & { args?: string[] }).args;
return Array.isArray(args) ? args : [];
}

View File

@@ -15,7 +15,9 @@ function setProcessTitleForCommand(actionCommand: Command) {
}
const name = current.name();
const cliName = resolveCliName();
if (!name || name === cliName) return;
if (!name || name === cliName) {
return;
}
process.title = `${cliName}-${name}`;
}
@@ -26,7 +28,9 @@ export function registerPreActionHooks(program: Command, programVersion: string)
program.hook("preAction", async (_thisCommand, actionCommand) => {
setProcessTitleForCommand(actionCommand);
const argv = process.argv;
if (hasHelpOrVersion(argv)) return;
if (hasHelpOrVersion(argv)) {
return;
}
const commandPath = getCommandPath(argv, 2);
const hideBanner =
isTruthyEnvValue(process.env.OPENCLAW_HIDE_BANNER) ||
@@ -41,7 +45,9 @@ export function registerPreActionHooks(program: Command, programVersion: string)
if (!verbose) {
process.env.NODE_NO_WARNINGS ??= "1";
}
if (commandPath[0] === "doctor" || commandPath[0] === "completion") return;
if (commandPath[0] === "doctor" || commandPath[0] === "completion") {
return;
}
await ensureConfigReady({ runtime: defaultRuntime, commandPath });
// Load plugins for commands that need channel access
if (PLUGIN_REQUIRED_COMMANDS.has(commandPath[0])) {

View File

@@ -17,14 +17,20 @@ function resolveInstallDaemonFlag(
command: unknown,
opts: { installDaemon?: boolean },
): boolean | undefined {
if (!command || typeof command !== "object") return undefined;
if (!command || typeof command !== "object") {
return undefined;
}
const getOptionValueSource =
"getOptionValueSource" in command ? command.getOptionValueSource : undefined;
if (typeof getOptionValueSource !== "function") return undefined;
if (typeof getOptionValueSource !== "function") {
return undefined;
}
// Commander doesn't support option conflicts natively; keep original behavior.
// If --skip-daemon is explicitly passed, it wins.
if (getOptionValueSource.call(command, "skipDaemon") === "cli") return false;
if (getOptionValueSource.call(command, "skipDaemon") === "cli") {
return false;
}
if (getOptionValueSource.call(command, "installDaemon") === "cli") {
return Boolean(opts.installDaemon);
}

View File

@@ -13,8 +13,12 @@ type SubCliEntry = {
};
const shouldRegisterPrimaryOnly = (argv: string[]) => {
if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_LAZY_SUBCOMMANDS)) return false;
if (hasHelpOrVersion(argv)) return false;
if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_LAZY_SUBCOMMANDS)) {
return false;
}
if (hasHelpOrVersion(argv)) {
return false;
}
return true;
};
@@ -251,9 +255,13 @@ function removeCommand(program: Command, command: Command) {
export async function registerSubCliByName(program: Command, name: string): Promise<boolean> {
const entry = entries.find((candidate) => candidate.name === name);
if (!entry) return false;
if (!entry) {
return false;
}
const existing = program.commands.find((cmd) => cmd.name() === entry.name);
if (existing) removeCommand(program, existing);
if (existing) {
removeCommand(program, existing);
}
await entry.register(program);
return true;
}

View File

@@ -41,13 +41,19 @@ const noopReporter: ProgressReporter = {
};
export function createCliProgress(options: ProgressOptions): ProgressReporter {
if (options.enabled === false) return noopReporter;
if (activeProgress > 0) return noopReporter;
if (options.enabled === false) {
return noopReporter;
}
if (activeProgress > 0) {
return noopReporter;
}
const stream = options.stream ?? process.stderr;
const isTty = stream.isTTY;
const allowLog = !isTty && options.fallback === "log";
if (!isTty && !allowLog) return noopReporter;
if (!isTty && !allowLog) {
return noopReporter;
}
const delayMs = typeof options.delayMs === "number" ? options.delayMs : DEFAULT_DELAY_MS;
const canOsc = isTty && supportsOscProgress(process.env, isTty);
@@ -78,7 +84,9 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter {
const spin = allowSpinner ? spinner() : null;
const renderLine = allowLine
? () => {
if (!started) return;
if (!started) {
return;
}
const suffix = indeterminate ? "" : ` ${percent}%`;
clearActiveProgressLine();
stream.write(`${theme.accent(label)}${suffix}`);
@@ -90,11 +98,15 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter {
let lastAt = 0;
const throttleMs = 250;
return () => {
if (!started) return;
if (!started) {
return;
}
const suffix = indeterminate ? "" : ` ${percent}%`;
const nextLine = `${label}${suffix}`;
const now = Date.now();
if (nextLine === lastLine && now - lastAt < throttleMs) return;
if (nextLine === lastLine && now - lastAt < throttleMs) {
return;
}
lastLine = nextLine;
lastAt = now;
stream.write(`${nextLine}\n`);
@@ -104,10 +116,15 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter {
let timer: NodeJS.Timeout | null = null;
const applyState = () => {
if (!started) return;
if (!started) {
return;
}
if (controller) {
if (indeterminate) controller.setIndeterminate(label);
else controller.setPercent(label, percent);
if (indeterminate) {
controller.setIndeterminate(label);
} else {
controller.setPercent(label, percent);
}
}
if (spin) {
spin.message(theme.accent(label));
@@ -121,7 +138,9 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter {
};
const start = () => {
if (started) return;
if (started) {
return;
}
started = true;
if (spin) {
spin.start(theme.accent(label));
@@ -147,7 +166,9 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter {
};
const tick = (delta = 1) => {
if (!total) return;
if (!total) {
return;
}
completed = Math.min(total, completed + delta);
const nextPercent = total > 0 ? Math.round((completed / total) * 100) : 0;
setPercent(nextPercent);
@@ -162,8 +183,12 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter {
activeProgress = Math.max(0, activeProgress - 1);
return;
}
if (controller) controller.clear();
if (spin) spin.stop();
if (controller) {
controller.clear();
}
if (spin) {
spin.stop();
}
clearActiveProgressLine();
if (isTty) {
unregisterActiveProgressLine(stream);
@@ -192,8 +217,12 @@ export async function withProgressTotals<T>(
): Promise<T> {
return await withProgress(options, async (progress) => {
const update = ({ completed, total, label }: ProgressTotalsUpdate) => {
if (label) progress.setLabel(label);
if (!Number.isFinite(total) || total <= 0) return;
if (label) {
progress.setLabel(label);
}
if (!Number.isFinite(total) || total <= 0) {
return;
}
progress.setPercent((completed / total) * 100);
};
return await work(update, progress);

View File

@@ -5,12 +5,18 @@ import { isVerbose, isYes } from "../globals.js";
export async function promptYesNo(question: string, defaultYes = false): Promise<boolean> {
// Simple Y/N prompt honoring global --yes and verbosity flags.
if (isVerbose() && isYes()) return true; // redundant guard when both flags set
if (isYes()) return true;
if (isVerbose() && isYes()) {
return true;
} // redundant guard when both flags set
if (isYes()) {
return true;
}
const rl = readline.createInterface({ input, output });
const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
rl.close();
if (!answer) return defaultYes;
if (!answer) {
return defaultYes;
}
return answer.startsWith("y");
}

View File

@@ -20,13 +20,21 @@ async function prepareRoutedCommand(params: {
}
export async function tryRouteCli(argv: string[]): Promise<boolean> {
if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_ROUTE_FIRST)) return false;
if (hasHelpOrVersion(argv)) return false;
if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_ROUTE_FIRST)) {
return false;
}
if (hasHelpOrVersion(argv)) {
return false;
}
const path = getCommandPath(argv, 2);
if (!path[0]) return false;
if (!path[0]) {
return false;
}
const route = findRoutedCommand(path);
if (!route) return false;
if (!route) {
return false;
}
await prepareRoutedCommand({ argv, commandPath: path, loadPlugins: route.loadPlugins });
return route.run(argv);
}

View File

@@ -16,7 +16,9 @@ import { tryRouteCli } from "./route.js";
export function rewriteUpdateFlagArgv(argv: string[]): string[] {
const index = argv.indexOf("--update");
if (index === -1) return argv;
if (index === -1) {
return argv;
}
const next = [...argv];
next.splice(index, 1, "update");
@@ -32,7 +34,9 @@ export async function runCli(argv: string[] = process.argv) {
// Enforce the minimum supported runtime before doing any work.
assertSupportedRuntime();
if (await tryRouteCli(normalizedArgv)) return;
if (await tryRouteCli(normalizedArgv)) {
return;
}
// Capture all console output into structured logs while keeping stdout/stderr behavior.
enableConsoleCapture();
@@ -69,7 +73,9 @@ export async function runCli(argv: string[] = process.argv) {
}
function stripWindowsNodeExec(argv: string[]): string[] {
if (process.platform !== "win32") return argv;
if (process.platform !== "win32") {
return argv;
}
const stripControlChars = (value: string): string => {
let out = "";
for (let i = 0; i < value.length; i += 1) {
@@ -90,9 +96,13 @@ function stripWindowsNodeExec(argv: string[]): string[] {
const execPathLower = execPath.toLowerCase();
const execBase = path.basename(execPath).toLowerCase();
const isExecPath = (value: string | undefined): boolean => {
if (!value) return false;
if (!value) {
return false;
}
const normalized = normalizeCandidate(value);
if (!normalized) return false;
if (!normalized) {
return false;
}
const lower = normalized.toLowerCase();
return (
lower === execPathLower ||
@@ -104,7 +114,9 @@ function stripWindowsNodeExec(argv: string[]): string[] {
);
};
const filtered = argv.filter((arg, index) => index === 0 || !isExecPath(arg));
if (filtered.length < 3) return filtered;
if (filtered.length < 3) {
return filtered;
}
const cleaned = [...filtered];
if (isExecPath(cleaned[1])) {
cleaned.splice(1, 1);

View File

@@ -89,21 +89,27 @@ export function registerSecurityCli(program: Command) {
for (const action of fixResult.actions) {
if (action.kind === "chmod") {
const mode = action.mode.toString(8).padStart(3, "0");
if (action.ok) lines.push(muted(` chmod ${mode} ${shortenHomePath(action.path)}`));
else if (action.skipped)
if (action.ok) {
lines.push(muted(` chmod ${mode} ${shortenHomePath(action.path)}`));
} else if (action.skipped) {
lines.push(
muted(` skip chmod ${mode} ${shortenHomePath(action.path)} (${action.skipped})`),
);
else if (action.error)
} else if (action.error) {
lines.push(
muted(` chmod ${mode} ${shortenHomePath(action.path)} failed: ${action.error}`),
);
}
continue;
}
const command = shortenHomeInString(action.command);
if (action.ok) lines.push(muted(` ${command}`));
else if (action.skipped) lines.push(muted(` skip ${command} (${action.skipped})`));
else if (action.error) lines.push(muted(` ${command} failed: ${action.error}`));
if (action.ok) {
lines.push(muted(` ${command}`));
} else if (action.skipped) {
lines.push(muted(` skip ${command} (${action.skipped})`));
} else if (action.error) {
lines.push(muted(` ${command} failed: ${action.error}`));
}
}
if (fixResult.errors.length > 0) {
for (const err of fixResult.errors) {
@@ -118,7 +124,9 @@ export function registerSecurityCli(program: Command) {
const render = (sev: "critical" | "warn" | "info") => {
const list = bySeverity(sev);
if (list.length === 0) return;
if (list.length === 0) {
return;
}
const label =
sev === "critical"
? rich
@@ -136,7 +144,9 @@ export function registerSecurityCli(program: Command) {
for (const f of list) {
lines.push(`${theme.muted(f.checkId)} ${f.title}`);
lines.push(` ${f.detail}`);
if (f.remediation?.trim()) lines.push(` ${muted(`Fix: ${f.remediation.trim()}`)}`);
if (f.remediation?.trim()) {
lines.push(` ${muted(`Fix: ${f.remediation.trim()}`)}`);
}
}
};

View File

@@ -218,7 +218,9 @@ describe("skills-cli", () => {
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(moduleDir, "..", "..");
const candidate = path.join(root, "skills");
if (fs.existsSync(candidate)) return candidate;
if (fs.existsSync(candidate)) {
return candidate;
}
return undefined;
}
@@ -251,7 +253,9 @@ describe("skills-cli", () => {
it("formats info for a real bundled skill (peekaboo)", () => {
const bundledDir = resolveBundledSkillsDir();
if (!bundledDir) return;
if (!bundledDir) {
return;
}
const report = buildWorkspaceSkillStatus("/tmp", {
managedSkillsDir: "/nonexistent",

View File

@@ -28,14 +28,22 @@ export type SkillsCheckOptions = {
};
function appendClawHubHint(output: string, json?: boolean): string {
if (json) return output;
if (json) {
return output;
}
return `${output}\n\nTip: use \`npx clawhub\` to search, install, and sync skills.`;
}
function formatSkillStatus(skill: SkillStatusEntry): string {
if (skill.eligible) return theme.success("✓ ready");
if (skill.disabled) return theme.warn("⏸ disabled");
if (skill.blockedByAllowlist) return theme.warn("🚫 blocked");
if (skill.eligible) {
return theme.success("✓ ready");
}
if (skill.disabled) {
return theme.warn("⏸ disabled");
}
if (skill.blockedByAllowlist) {
return theme.warn("🚫 blocked");
}
return theme.error("✗ missing");
}

View File

@@ -11,8 +11,12 @@ type SystemEventOpts = GatewayRpcOpts & { text?: string; mode?: string; json?: b
const normalizeWakeMode = (raw: unknown) => {
const mode = typeof raw === "string" ? raw.trim() : "";
if (!mode) return "next-heartbeat" as const;
if (mode === "now" || mode === "next-heartbeat") return mode;
if (!mode) {
return "next-heartbeat" as const;
}
if (mode === "now" || mode === "next-heartbeat") {
return mode;
}
throw new Error("--mode must be now or next-heartbeat");
};
@@ -36,11 +40,16 @@ export function registerSystemCli(program: Command) {
).action(async (opts: SystemEventOpts) => {
try {
const text = typeof opts.text === "string" ? opts.text.trim() : "";
if (!text) throw new Error("--text is required");
if (!text) {
throw new Error("--text is required");
}
const mode = normalizeWakeMode(opts.mode);
const result = await callGatewayFromCli("wake", opts, { mode, text }, { expectFinal: false });
if (opts.json) defaultRuntime.log(JSON.stringify(result, null, 2));
else defaultRuntime.log("ok");
if (opts.json) {
defaultRuntime.log(JSON.stringify(result, null, 2));
} else {
defaultRuntime.log("ok");
}
} catch (err) {
defaultRuntime.error(danger(String(err)));
defaultRuntime.exit(1);

View File

@@ -127,7 +127,9 @@ const onSpecificDates =
(date) => {
const parts = utcParts(date);
return dates.some(([year, month, day]) => {
if (parts.year !== year) return false;
if (parts.year !== year) {
return false;
}
const start = Date.UTC(year, month, day);
const current = Date.UTC(parts.year, parts.month, parts.day);
return current >= start && current < start + durationDays * DAY_MS;
@@ -146,7 +148,9 @@ const inYearWindow =
(date) => {
const parts = utcParts(date);
const window = windows.find((entry) => entry.year === parts.year);
if (!window) return false;
if (!window) {
return false;
}
const start = Date.UTC(window.year, window.month, window.day);
const current = Date.UTC(parts.year, parts.month, parts.day);
return current >= start && current < start + window.duration * DAY_MS;
@@ -154,7 +158,9 @@ const inYearWindow =
const isFourthThursdayOfNovember: HolidayRule = (date) => {
const parts = utcParts(date);
if (parts.month !== 10) return false; // November
if (parts.month !== 10) {
return false;
} // November
const firstDay = new Date(Date.UTC(parts.year, 10, 1)).getUTCDay();
const offsetToThursday = (4 - firstDay + 7) % 7; // 4 = Thursday
const fourthThursday = 1 + offsetToThursday + 21; // 1st + offset + 3 weeks
@@ -224,7 +230,9 @@ const HOLIDAY_RULES = new Map<string, HolidayRule>([
function isTaglineActive(tagline: string, date: Date): boolean {
const rule = HOLIDAY_RULES.get(tagline);
if (!rule) return true;
if (!rule) {
return true;
}
return rule(date);
}
@@ -235,7 +243,9 @@ export interface TaglineOptions {
}
export function activeTaglines(options: TaglineOptions = {}): string[] {
if (TAGLINES.length === 0) return [DEFAULT_TAGLINE];
if (TAGLINES.length === 0) {
return [DEFAULT_TAGLINE];
}
const today = options.now ? options.now() : new Date();
const filtered = TAGLINES.filter((tagline) => isTaglineActive(tagline, today));
return filtered.length > 0 ? filtered : TAGLINES;

View File

@@ -118,10 +118,16 @@ const OPENCLAW_REPO_URL = "https://github.com/openclaw/openclaw.git";
const DEFAULT_GIT_DIR = path.join(os.homedir(), ".openclaw");
function normalizeTag(value?: string | null): string | null {
if (!value) return null;
if (!value) {
return null;
}
const trimmed = value.trim();
if (!trimmed) return null;
if (trimmed.startsWith("openclaw@")) return trimmed.slice("openclaw@".length);
if (!trimmed) {
return null;
}
if (trimmed.startsWith("openclaw@")) {
return trimmed.slice("openclaw@".length);
}
if (trimmed.startsWith(`${DEFAULT_PACKAGE_NAME}@`)) {
return trimmed.slice(`${DEFAULT_PACKAGE_NAME}@`.length);
}
@@ -134,7 +140,9 @@ function pickUpdateQuip(): string {
function normalizeVersionTag(tag: string): string | null {
const trimmed = tag.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const cleaned = trimmed.startsWith("v") ? trimmed.slice(1) : trimmed;
return parseSemver(cleaned) ? cleaned : null;
}
@@ -151,7 +159,9 @@ async function readPackageVersion(root: string): Promise<string | null> {
async function resolveTargetVersion(tag: string, timeoutMs?: number): Promise<string | null> {
const direct = normalizeVersionTag(tag);
if (direct) return direct;
if (direct) {
return direct;
}
const res = await fetchNpmTagVersion({ tag, timeoutMs });
return res.version ?? null;
}
@@ -201,7 +211,9 @@ async function isEmptyDir(targetPath: string): Promise<boolean> {
function resolveGitInstallDir(): string {
const override = process.env.OPENCLAW_GIT_DIR?.trim();
if (override) return path.resolve(override);
if (override) {
return path.resolve(override);
}
return resolveDefaultGitDir();
}
@@ -211,7 +223,9 @@ function resolveDefaultGitDir(): string {
function resolveNodeRunner(): string {
const base = path.basename(process.execPath).toLowerCase();
if (base === "node" || base === "node.exe") return process.execPath;
if (base === "node" || base === "node.exe") {
return process.execPath;
}
return "node";
}
@@ -309,7 +323,9 @@ async function resolveGlobalManager(params: {
params.root,
params.timeoutMs,
);
if (detected) return detected;
if (detected) {
return detected;
}
}
const byPresence = await detectGlobalInstallManagerByPresence(runCommand, params.timeoutMs);
return byPresence ?? "npm";
@@ -459,7 +475,9 @@ function createUpdateProgress(enabled: boolean): ProgressController {
currentSpinner.start(theme.accent(getStepLabel(step)));
},
onStepComplete: (step) => {
if (!currentSpinner) return;
if (!currentSpinner) {
return;
}
const label = getStepLabel(step);
const duration = theme.muted(`(${formatDuration(step.durationMs)})`);
@@ -491,14 +509,20 @@ function createUpdateProgress(enabled: boolean): ProgressController {
}
function formatDuration(ms: number): string {
if (ms < 1000) return `${ms}ms`;
if (ms < 1000) {
return `${ms}ms`;
}
const seconds = (ms / 1000).toFixed(1);
return `${seconds}s`;
}
function formatStepStatus(exitCode: number | null): string {
if (exitCode === 0) return theme.success("\u2713");
if (exitCode === null) return theme.warn("?");
if (exitCode === 0) {
return theme.success("\u2713");
}
if (exitCode === null) {
return theme.warn("?");
}
return theme.error("\u2717");
}
@@ -875,7 +899,9 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
if (!opts.json) {
const summarizeList = (list: string[]) => {
if (list.length <= 6) return list.join(", ");
if (list.length <= 6) {
return list.join(", ");
}
return `${list.slice(0, 6).join(", ")} +${list.length - 6} more`;
};
@@ -907,13 +933,19 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
defaultRuntime.log(theme.muted("No plugin updates needed."));
} else {
const parts = [`${updated} updated`, `${unchanged} unchanged`];
if (failed > 0) parts.push(`${failed} failed`);
if (skipped > 0) parts.push(`${skipped} skipped`);
if (failed > 0) {
parts.push(`${failed} failed`);
}
if (skipped > 0) {
parts.push(`${skipped} skipped`);
}
defaultRuntime.log(theme.muted(`npm plugins: ${parts.join(", ")}.`));
}
for (const outcome of npmResult.outcomes) {
if (outcome.status !== "error") continue;
if (outcome.status !== "error") {
continue;
}
defaultRuntime.log(theme.error(outcome.message));
}
}

View File

@@ -108,7 +108,9 @@ export function registerWebhooksCli(program: Command) {
function parseGmailSetupOptions(raw: Record<string, unknown>): GmailSetupOptions {
const accountRaw = raw.account;
const account = typeof accountRaw === "string" ? accountRaw.trim() : "";
if (!account) throw new Error("--account is required");
if (!account) {
throw new Error("--account is required");
}
return {
account,
project: stringOption(raw.project),
@@ -154,19 +156,27 @@ function parseGmailRunOptions(raw: Record<string, unknown>): GmailRunOptions {
}
function stringOption(value: unknown): string | undefined {
if (typeof value !== "string") return undefined;
if (typeof value !== "string") {
return undefined;
}
const trimmed = value.trim();
return trimmed ? trimmed : undefined;
}
function numberOption(value: unknown): number | undefined {
if (value === undefined || value === null) return undefined;
if (value === undefined || value === null) {
return undefined;
}
const n = typeof value === "number" ? value : Number(value);
if (!Number.isFinite(n) || n <= 0) return undefined;
if (!Number.isFinite(n) || n <= 0) {
return undefined;
}
return Math.floor(n);
}
function booleanOption(value: unknown): boolean | undefined {
if (value === undefined || value === null) return undefined;
if (value === undefined || value === null) {
return undefined;
}
return Boolean(value);
}