@gguf/claw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,502 lines (1,466 loc) • 510 kB
JavaScript
import { createRequire } from "node:module";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { Logger } from "tslog";
import JSON5 from "json5";
import chalk, { Chalk } from "chalk";
import fs$1 from "node:fs/promises";
import { execFile, execFileSync, spawn } from "node:child_process";
import { isDeepStrictEqual, promisify } from "node:util";
import crypto, { X509Certificate, randomUUID } from "node:crypto";
import { spinner } from "@clack/prompts";
import { fileURLToPath } from "node:url";
import dotenv from "dotenv";
import { getOAuthProviders } from "@mariozechner/pi-ai";
import "@aws-sdk/client-bedrock";
import AjvPkg from "ajv";
import { z } from "zod";
import "@mariozechner/pi-coding-agent";
import { WebSocket } from "ws";
import { Buffer as Buffer$1 } from "node:buffer";
import net from "node:net";
import { Type } from "@sinclair/typebox";
import { Writable } from "node:stream";
import { createOscProgressController, supportsOscProgress } from "osc-progress";
//#region src/infra/home-dir.ts
function normalize(value) {
const trimmed = value?.trim();
return trimmed ? trimmed : void 0;
}
function resolveEffectiveHomeDir(env = process.env, homedir = os.homedir) {
const raw = resolveRawHomeDir(env, homedir);
return raw ? path.resolve(raw) : void 0;
}
function resolveRawHomeDir(env, homedir) {
const explicitHome = normalize(env.OPENCLAW_HOME);
if (explicitHome) {
if (explicitHome === "~" || explicitHome.startsWith("~/") || explicitHome.startsWith("~\\")) {
const fallbackHome = normalize(env.HOME) ?? normalize(env.USERPROFILE) ?? normalizeSafe(homedir);
if (fallbackHome) return explicitHome.replace(/^~(?=$|[\\/])/, fallbackHome);
return;
}
return explicitHome;
}
const envHome = normalize(env.HOME);
if (envHome) return envHome;
const userProfile = normalize(env.USERPROFILE);
if (userProfile) return userProfile;
return normalizeSafe(homedir);
}
function normalizeSafe(homedir) {
try {
return normalize(homedir());
} catch {
return;
}
}
function resolveRequiredHomeDir(env = process.env, homedir = os.homedir) {
return resolveEffectiveHomeDir(env, homedir) ?? path.resolve(process.cwd());
}
function expandHomePrefix(input, opts) {
if (!input.startsWith("~")) return input;
const home = normalize(opts?.home) ?? resolveEffectiveHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir);
if (!home) return input;
return input.replace(/^~(?=$|[\\/])/, home);
}
//#endregion
//#region src/config/paths.ts
/**
* Nix mode detection: When OPENCLAW_NIX_MODE=1, the gateway is running under Nix.
* In this mode:
* - No auto-install flows should be attempted
* - Missing dependencies should produce actionable Nix-specific error messages
* - Config is managed externally (read-only from Nix perspective)
*/
function resolveIsNixMode(env = process.env) {
return env.OPENCLAW_NIX_MODE === "1";
}
const isNixMode = resolveIsNixMode();
const LEGACY_STATE_DIRNAMES = [
".clawdbot",
".moldbot",
".moltbot"
];
const NEW_STATE_DIRNAME = ".openclaw";
const CONFIG_FILENAME = "openclaw.json";
const LEGACY_CONFIG_FILENAMES = [
"clawdbot.json",
"moldbot.json",
"moltbot.json"
];
function resolveDefaultHomeDir() {
return resolveRequiredHomeDir(process.env, os.homedir);
}
/** Build a homedir thunk that respects OPENCLAW_HOME for the given env. */
function envHomedir(env) {
return () => resolveRequiredHomeDir(env, os.homedir);
}
function legacyStateDirs(homedir = resolveDefaultHomeDir) {
return LEGACY_STATE_DIRNAMES.map((dir) => path.join(homedir(), dir));
}
function newStateDir(homedir = resolveDefaultHomeDir) {
return path.join(homedir(), NEW_STATE_DIRNAME);
}
/**
* State directory for mutable data (sessions, logs, caches).
* Can be overridden via OPENCLAW_STATE_DIR.
* Default: ~/.openclaw
*/
function resolveStateDir(env = process.env, homedir = envHomedir(env)) {
const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir);
const override = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
if (override) return resolveUserPath$1(override, env, effectiveHomedir);
const newDir = newStateDir(effectiveHomedir);
const legacyDirs = legacyStateDirs(effectiveHomedir);
if (fs.existsSync(newDir)) return newDir;
const existingLegacy = legacyDirs.find((dir) => {
try {
return fs.existsSync(dir);
} catch {
return false;
}
});
if (existingLegacy) return existingLegacy;
return newDir;
}
function resolveUserPath$1(input, env = process.env, homedir = envHomedir(env)) {
const trimmed = input.trim();
if (!trimmed) return trimmed;
if (trimmed.startsWith("~")) {
const expanded = expandHomePrefix(trimmed, {
home: resolveRequiredHomeDir(env, homedir),
env,
homedir
});
return path.resolve(expanded);
}
return path.resolve(trimmed);
}
const STATE_DIR = resolveStateDir();
/**
* Config file path (JSON5).
* Can be overridden via OPENCLAW_CONFIG_PATH.
* Default: ~/.openclaw/openclaw.json (or $OPENCLAW_STATE_DIR/openclaw.json)
*/
function resolveCanonicalConfigPath(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) {
const override = env.OPENCLAW_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
if (override) return resolveUserPath$1(override, env, envHomedir(env));
return path.join(stateDir, CONFIG_FILENAME);
}
/**
* Resolve the active config path by preferring existing config candidates
* before falling back to the canonical path.
*/
function resolveConfigPathCandidate(env = process.env, homedir = envHomedir(env)) {
const existing = resolveDefaultConfigCandidates(env, homedir).find((candidate) => {
try {
return fs.existsSync(candidate);
} catch {
return false;
}
});
if (existing) return existing;
return resolveCanonicalConfigPath(env, resolveStateDir(env, homedir));
}
/**
* Active config path (prefers existing config files).
*/
function resolveConfigPath(env = process.env, stateDir = resolveStateDir(env, envHomedir(env)), homedir = envHomedir(env)) {
const override = env.OPENCLAW_CONFIG_PATH?.trim();
if (override) return resolveUserPath$1(override, env, homedir);
const stateOverride = env.OPENCLAW_STATE_DIR?.trim();
const existing = [path.join(stateDir, CONFIG_FILENAME), ...LEGACY_CONFIG_FILENAMES.map((name) => path.join(stateDir, name))].find((candidate) => {
try {
return fs.existsSync(candidate);
} catch {
return false;
}
});
if (existing) return existing;
if (stateOverride) return path.join(stateDir, CONFIG_FILENAME);
const defaultStateDir = resolveStateDir(env, homedir);
if (path.resolve(stateDir) === path.resolve(defaultStateDir)) return resolveConfigPathCandidate(env, homedir);
return path.join(stateDir, CONFIG_FILENAME);
}
const CONFIG_PATH = resolveConfigPathCandidate();
/**
* Resolve default config path candidates across default locations.
* Order: explicit config path → state-dir-derived paths → new default.
*/
function resolveDefaultConfigCandidates(env = process.env, homedir = envHomedir(env)) {
const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir);
const explicit = env.OPENCLAW_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
if (explicit) return [resolveUserPath$1(explicit, env, effectiveHomedir)];
const candidates = [];
const openclawStateDir = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
if (openclawStateDir) {
const resolved = resolveUserPath$1(openclawStateDir, env, effectiveHomedir);
candidates.push(path.join(resolved, CONFIG_FILENAME));
candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => path.join(resolved, name)));
}
const defaultDirs = [newStateDir(effectiveHomedir), ...legacyStateDirs(effectiveHomedir)];
for (const dir of defaultDirs) {
candidates.push(path.join(dir, CONFIG_FILENAME));
candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => path.join(dir, name)));
}
return candidates;
}
const DEFAULT_GATEWAY_PORT = 18789;
/**
* OAuth credentials storage directory.
*
* Precedence:
* - `OPENCLAW_OAUTH_DIR` (explicit override)
* - `$*_STATE_DIR/credentials` (canonical server/default)
*/
function resolveOAuthDir(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) {
const override = env.OPENCLAW_OAUTH_DIR?.trim();
if (override) return resolveUserPath$1(override, env, envHomedir(env));
return path.join(stateDir, "credentials");
}
function resolveGatewayPort(cfg, env = process.env) {
const envRaw = env.OPENCLAW_GATEWAY_PORT?.trim() || env.CLAWDBOT_GATEWAY_PORT?.trim();
if (envRaw) {
const parsed = Number.parseInt(envRaw, 10);
if (Number.isFinite(parsed) && parsed > 0) return parsed;
}
const configPort = cfg?.gateway?.port;
if (typeof configPort === "number" && Number.isFinite(configPort)) {
if (configPort > 0) return configPort;
}
return DEFAULT_GATEWAY_PORT;
}
//#endregion
//#region src/infra/tmp-openclaw-dir.ts
const POSIX_OPENCLAW_TMP_DIR = "/tmp/openclaw";
function isNodeErrorWithCode(err, code) {
return typeof err === "object" && err !== null && "code" in err && err.code === code;
}
function resolvePreferredOpenClawTmpDir(options = {}) {
const accessSync = options.accessSync ?? fs.accessSync;
const lstatSync = options.lstatSync ?? fs.lstatSync;
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
const getuid = options.getuid ?? (() => {
try {
return typeof process.getuid === "function" ? process.getuid() : void 0;
} catch {
return;
}
});
const tmpdir = options.tmpdir ?? os.tmpdir;
const uid = getuid();
const isSecureDirForUser = (st) => {
if (uid === void 0) return true;
if (typeof st.uid === "number" && st.uid !== uid) return false;
if (typeof st.mode === "number" && (st.mode & 18) !== 0) return false;
return true;
};
const fallback = () => {
const base = tmpdir();
const suffix = uid === void 0 ? "openclaw" : `openclaw-${uid}`;
return path.join(base, suffix);
};
try {
const preferred = lstatSync(POSIX_OPENCLAW_TMP_DIR);
if (!preferred.isDirectory() || preferred.isSymbolicLink()) return fallback();
accessSync(POSIX_OPENCLAW_TMP_DIR, fs.constants.W_OK | fs.constants.X_OK);
if (!isSecureDirForUser(preferred)) return fallback();
return POSIX_OPENCLAW_TMP_DIR;
} catch (err) {
if (!isNodeErrorWithCode(err, "ENOENT")) return fallback();
}
try {
accessSync("/tmp", fs.constants.W_OK | fs.constants.X_OK);
mkdirSync(POSIX_OPENCLAW_TMP_DIR, {
recursive: true,
mode: 448
});
try {
const preferred = lstatSync(POSIX_OPENCLAW_TMP_DIR);
if (!preferred.isDirectory() || preferred.isSymbolicLink()) return fallback();
if (!isSecureDirForUser(preferred)) return fallback();
} catch {
return fallback();
}
return POSIX_OPENCLAW_TMP_DIR;
} catch {
return fallback();
}
}
//#endregion
//#region src/logging/config.ts
function readLoggingConfig() {
const configPath = resolveConfigPath();
try {
if (!fs.existsSync(configPath)) return;
const raw = fs.readFileSync(configPath, "utf-8");
const logging = JSON5.parse(raw)?.logging;
if (!logging || typeof logging !== "object" || Array.isArray(logging)) return;
return logging;
} catch {
return;
}
}
//#endregion
//#region src/logging/levels.ts
const ALLOWED_LOG_LEVELS = [
"silent",
"fatal",
"error",
"warn",
"info",
"debug",
"trace"
];
function normalizeLogLevel(level, fallback = "info") {
const candidate = (level ?? fallback).trim();
return ALLOWED_LOG_LEVELS.includes(candidate) ? candidate : fallback;
}
function levelToMinLevel(level) {
return {
fatal: 0,
error: 1,
warn: 2,
info: 3,
debug: 4,
trace: 5,
silent: Number.POSITIVE_INFINITY
}[level];
}
//#endregion
//#region src/logging/state.ts
const loggingState = {
cachedLogger: null,
cachedSettings: null,
cachedConsoleSettings: null,
overrideSettings: null,
consolePatched: false,
forceConsoleToStderr: false,
consoleTimestampPrefix: false,
consoleSubsystemFilter: null,
resolvingConsoleSettings: false,
streamErrorHandlersInstalled: false,
rawConsole: null
};
//#endregion
//#region src/logging/logger.ts
const DEFAULT_LOG_DIR = resolvePreferredOpenClawTmpDir();
const DEFAULT_LOG_FILE = path.join(DEFAULT_LOG_DIR, "openclaw.log");
const LOG_PREFIX = "openclaw";
const LOG_SUFFIX = ".log";
const MAX_LOG_AGE_MS = 1440 * 60 * 1e3;
function resolveNodeRequire$2() {
const getBuiltinModule = process.getBuiltinModule;
if (typeof getBuiltinModule !== "function") return null;
try {
const moduleNamespace = getBuiltinModule("module");
return typeof moduleNamespace.createRequire === "function" ? moduleNamespace.createRequire : null;
} catch {
return null;
}
}
const requireConfig$2 = resolveNodeRequire$2()?.(import.meta.url) ?? null;
const externalTransports = /* @__PURE__ */ new Set();
function attachExternalTransport(logger, transport) {
logger.attachTransport((logObj) => {
if (!externalTransports.has(transport)) return;
try {
transport(logObj);
} catch {}
});
}
function resolveSettings() {
let cfg = loggingState.overrideSettings ?? readLoggingConfig();
if (!cfg) try {
cfg = (requireConfig$2?.("../config/config.js"))?.loadConfig?.().logging;
} catch {
cfg = void 0;
}
const defaultLevel = process.env.VITEST === "true" && process.env.OPENCLAW_TEST_FILE_LOG !== "1" ? "silent" : "info";
return {
level: normalizeLogLevel(cfg?.level, defaultLevel),
file: cfg?.file ?? defaultRollingPathForToday()
};
}
function settingsChanged(a, b) {
if (!a) return true;
return a.level !== b.level || a.file !== b.file;
}
function isFileLogLevelEnabled(level) {
const settings = loggingState.cachedSettings ?? resolveSettings();
if (!loggingState.cachedSettings) loggingState.cachedSettings = settings;
if (settings.level === "silent") return false;
return levelToMinLevel(level) <= levelToMinLevel(settings.level);
}
function buildLogger(settings) {
fs.mkdirSync(path.dirname(settings.file), { recursive: true });
if (isRollingPath(settings.file)) pruneOldRollingLogs(path.dirname(settings.file));
const logger = new Logger({
name: "openclaw",
minLevel: levelToMinLevel(settings.level),
type: "hidden"
});
logger.attachTransport((logObj) => {
try {
const time = logObj.date?.toISOString?.() ?? (/* @__PURE__ */ new Date()).toISOString();
const line = JSON.stringify({
...logObj,
time
});
fs.appendFileSync(settings.file, `${line}\n`, { encoding: "utf8" });
} catch {}
});
for (const transport of externalTransports) attachExternalTransport(logger, transport);
return logger;
}
function getLogger() {
const settings = resolveSettings();
const cachedLogger = loggingState.cachedLogger;
const cachedSettings = loggingState.cachedSettings;
if (!cachedLogger || settingsChanged(cachedSettings, settings)) {
loggingState.cachedLogger = buildLogger(settings);
loggingState.cachedSettings = settings;
}
return loggingState.cachedLogger;
}
function getChildLogger(bindings, opts) {
const base = getLogger();
const minLevel = opts?.level ? levelToMinLevel(opts.level) : void 0;
const name = bindings ? JSON.stringify(bindings) : void 0;
return base.getSubLogger({
name,
minLevel,
prefix: bindings ? [name ?? ""] : []
});
}
function getResolvedLoggerSettings() {
return resolveSettings();
}
function formatLocalDate(date) {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
}
function defaultRollingPathForToday() {
const today = formatLocalDate(/* @__PURE__ */ new Date());
return path.join(DEFAULT_LOG_DIR, `${LOG_PREFIX}-${today}${LOG_SUFFIX}`);
}
function isRollingPath(file) {
const base = path.basename(file);
return base.startsWith(`${LOG_PREFIX}-`) && base.endsWith(LOG_SUFFIX) && base.length === `${LOG_PREFIX}-YYYY-MM-DD${LOG_SUFFIX}`.length;
}
function pruneOldRollingLogs(dir) {
try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
const cutoff = Date.now() - MAX_LOG_AGE_MS;
for (const entry of entries) {
if (!entry.isFile()) continue;
if (!entry.name.startsWith(`${LOG_PREFIX}-`) || !entry.name.endsWith(LOG_SUFFIX)) continue;
const fullPath = path.join(dir, entry.name);
try {
if (fs.statSync(fullPath).mtimeMs < cutoff) fs.rmSync(fullPath, { force: true });
} catch {}
}
} catch {}
}
//#endregion
//#region src/terminal/palette.ts
const LOBSTER_PALETTE = {
accent: "#FF5A2D",
accentBright: "#FF7A3D",
accentDim: "#D14A22",
info: "#FF8A5B",
success: "#2FBF71",
warn: "#FFB020",
error: "#E23D2D",
muted: "#8B7F77"
};
//#endregion
//#region src/terminal/theme.ts
const hasForceColor = typeof process.env.FORCE_COLOR === "string" && process.env.FORCE_COLOR.trim().length > 0 && process.env.FORCE_COLOR.trim() !== "0";
const baseChalk = process.env.NO_COLOR && !hasForceColor ? new Chalk({ level: 0 }) : chalk;
const hex = (value) => baseChalk.hex(value);
const theme = {
accent: hex(LOBSTER_PALETTE.accent),
accentBright: hex(LOBSTER_PALETTE.accentBright),
accentDim: hex(LOBSTER_PALETTE.accentDim),
info: hex(LOBSTER_PALETTE.info),
success: hex(LOBSTER_PALETTE.success),
warn: hex(LOBSTER_PALETTE.warn),
error: hex(LOBSTER_PALETTE.error),
muted: hex(LOBSTER_PALETTE.muted),
heading: baseChalk.bold.hex(LOBSTER_PALETTE.accent),
command: hex(LOBSTER_PALETTE.accentBright),
option: hex(LOBSTER_PALETTE.warn)
};
const isRich = () => Boolean(baseChalk.level > 0);
const colorize = (rich, color, value) => rich ? color(value) : value;
//#endregion
//#region src/globals.ts
let globalVerbose = false;
function isVerbose() {
return globalVerbose;
}
function logVerboseConsole(message) {
if (!globalVerbose) return;
console.log(theme.muted(message));
}
const success = theme.success;
const warn = theme.warn;
const info = theme.info;
const danger = theme.error;
//#endregion
//#region src/infra/plain-object.ts
/**
* Strict plain-object guard (excludes arrays and host objects).
*/
function isPlainObject$1(value) {
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]";
}
//#endregion
//#region src/utils.ts
async function ensureDir$1(dir) {
await fs.promises.mkdir(dir, { recursive: true });
}
/**
* Type guard for Record<string, unknown> (less strict than isPlainObject).
* Accepts any non-null object that isn't an array.
*/
function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function resolveUserPath(input) {
const trimmed = input.trim();
if (!trimmed) return trimmed;
if (trimmed.startsWith("~")) {
const expanded = expandHomePrefix(trimmed, {
home: resolveRequiredHomeDir(process.env, os.homedir),
env: process.env,
homedir: os.homedir
});
return path.resolve(expanded);
}
return path.resolve(trimmed);
}
function resolveConfigDir(env = process.env, homedir = os.homedir) {
const override = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
if (override) return resolveUserPath(override);
const newDir = path.join(resolveRequiredHomeDir(env, homedir), ".openclaw");
try {
if (fs.existsSync(newDir)) return newDir;
} catch {}
return newDir;
}
function resolveHomeDir$2() {
return resolveEffectiveHomeDir(process.env, os.homedir);
}
function resolveHomeDisplayPrefix() {
const home = resolveHomeDir$2();
if (!home) return;
if (process.env.OPENCLAW_HOME?.trim()) return {
home,
prefix: "$OPENCLAW_HOME"
};
return {
home,
prefix: "~"
};
}
function shortenHomePath(input) {
if (!input) return input;
const display = resolveHomeDisplayPrefix();
if (!display) return input;
const { home, prefix } = display;
if (input === home) return prefix;
if (input.startsWith(`${home}/`) || input.startsWith(`${home}\\`)) return `${prefix}${input.slice(home.length)}`;
return input;
}
function shortenHomeInString(input) {
if (!input) return input;
const display = resolveHomeDisplayPrefix();
if (!display) return input;
return input.split(display.home).join(display.prefix);
}
function formatTerminalLink(label, url, opts) {
const esc = "\x1B";
const safeLabel = label.replaceAll(esc, "");
const safeUrl = url.replaceAll(esc, "");
if (!(opts?.force === true ? true : opts?.force === false ? false : Boolean(process.stdout.isTTY))) return opts?.fallback ?? `${safeLabel} (${safeUrl})`;
return `\u001b]8;;${safeUrl}\u0007${safeLabel}\u001b]8;;\u0007`;
}
const CONFIG_DIR = resolveConfigDir();
//#endregion
//#region src/terminal/links.ts
const DOCS_ROOT = "https://docs.openclaw.ai";
function formatDocsLink(path, label, opts) {
const trimmed = path.trim();
const url = trimmed.startsWith("http") ? trimmed : `${DOCS_ROOT}${trimmed.startsWith("/") ? trimmed : `/${trimmed}`}`;
return formatTerminalLink(label ?? url, url, {
fallback: opts?.fallback ?? url,
force: opts?.force
});
}
//#endregion
//#region src/cli/command-options.ts
function getOptionSource(command, name) {
if (typeof command.getOptionValueSource !== "function") return;
return command.getOptionValueSource(name);
}
const MAX_INHERIT_DEPTH = 2;
function inheritOptionFromParent(command, name) {
if (!command) return;
const childSource = getOptionSource(command, name);
if (childSource && childSource !== "default") return;
let depth = 0;
let ancestor = command.parent;
while (ancestor && depth < MAX_INHERIT_DEPTH) {
const source = getOptionSource(ancestor, name);
if (source && source !== "default") return ancestor.opts()[name];
depth += 1;
ancestor = ancestor.parent;
}
}
//#endregion
//#region src/cli/cli-name.ts
const DEFAULT_CLI_NAME = "openclaw";
const KNOWN_CLI_NAMES = new Set([DEFAULT_CLI_NAME]);
const CLI_PREFIX_RE$1 = /^(?:((?:pnpm|npm|bunx|npx)\s+))?(openclaw)\b/;
function resolveCliName(argv = process.argv) {
const argv1 = argv[1];
if (!argv1) return DEFAULT_CLI_NAME;
const base = path.basename(argv1).trim();
if (KNOWN_CLI_NAMES.has(base)) return base;
return DEFAULT_CLI_NAME;
}
function replaceCliName(command, cliName = resolveCliName()) {
if (!command.trim()) return command;
if (!CLI_PREFIX_RE$1.test(command)) return command;
return command.replace(CLI_PREFIX_RE$1, (_match, runner) => {
return `${runner ?? ""}${cliName}`;
});
}
//#endregion
//#region src/cli/profile-utils.ts
const PROFILE_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
function isValidProfileName(value) {
if (!value) return false;
return PROFILE_NAME_RE.test(value);
}
function normalizeProfileName(raw) {
const profile = raw?.trim();
if (!profile) return null;
if (profile.toLowerCase() === "default") return null;
if (!isValidProfileName(profile)) return null;
return profile;
}
//#endregion
//#region src/cli/command-format.ts
const CLI_PREFIX_RE = /^(?:pnpm|npm|bunx|npx)\s+openclaw\b|^openclaw\b/;
const PROFILE_FLAG_RE = /(?:^|\s)--profile(?:\s|=|$)/;
const DEV_FLAG_RE = /(?:^|\s)--dev(?:\s|$)/;
function formatCliCommand(command, env = process.env) {
const normalizedCommand = replaceCliName(command, resolveCliName());
const profile = normalizeProfileName(env.OPENCLAW_PROFILE);
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;
return normalizedCommand.replace(CLI_PREFIX_RE, (match) => `${match} --profile ${profile}`);
}
//#endregion
//#region src/config/env-vars.ts
function collectConfigEnvVars(cfg) {
const envConfig = cfg?.env;
if (!envConfig) return {};
const entries = {};
if (envConfig.vars) for (const [key, value] of Object.entries(envConfig.vars)) {
if (!value) continue;
entries[key] = value;
}
for (const [key, value] of Object.entries(envConfig)) {
if (key === "shellEnv" || key === "vars") continue;
if (typeof value !== "string" || !value.trim()) continue;
entries[key] = value;
}
return entries;
}
function applyConfigEnvVars(cfg, env = process.env) {
const entries = collectConfigEnvVars(cfg);
for (const [key, value] of Object.entries(entries)) {
if (env[key]?.trim()) continue;
env[key] = value;
}
}
//#endregion
//#region src/daemon/constants.ts
const GATEWAY_LAUNCH_AGENT_LABEL = "ai.openclaw.gateway";
const GATEWAY_SYSTEMD_SERVICE_NAME = "openclaw-gateway";
const GATEWAY_WINDOWS_TASK_NAME = "OpenClaw Gateway";
const GATEWAY_SERVICE_MARKER = "openclaw";
const GATEWAY_SERVICE_KIND = "gateway";
function normalizeGatewayProfile(profile) {
const trimmed = profile?.trim();
if (!trimmed || trimmed.toLowerCase() === "default") return null;
return trimmed;
}
function resolveGatewayProfileSuffix(profile) {
const normalized = normalizeGatewayProfile(profile);
return normalized ? `-${normalized}` : "";
}
function resolveGatewayLaunchAgentLabel(profile) {
const normalized = normalizeGatewayProfile(profile);
if (!normalized) return GATEWAY_LAUNCH_AGENT_LABEL;
return `ai.openclaw.${normalized}`;
}
function resolveLegacyGatewayLaunchAgentLabels(profile) {
return [];
}
function resolveGatewaySystemdServiceName(profile) {
const suffix = resolveGatewayProfileSuffix(profile);
if (!suffix) return GATEWAY_SYSTEMD_SERVICE_NAME;
return `openclaw-gateway${suffix}`;
}
function resolveGatewayWindowsTaskName(profile) {
const normalized = normalizeGatewayProfile(profile);
if (!normalized) return GATEWAY_WINDOWS_TASK_NAME;
return `OpenClaw Gateway (${normalized})`;
}
function formatGatewayServiceDescription(params) {
const profile = normalizeGatewayProfile(params?.profile);
const version = params?.version?.trim();
const parts = [];
if (profile) parts.push(`profile: ${profile}`);
if (version) parts.push(`v${version}`);
if (parts.length === 0) return "OpenClaw Gateway";
return `OpenClaw Gateway (${parts.join(", ")})`;
}
function resolveGatewayServiceDescription(params) {
return params.description ?? formatGatewayServiceDescription({
profile: params.env.OPENCLAW_PROFILE,
version: params.environment?.OPENCLAW_SERVICE_VERSION ?? params.env.OPENCLAW_SERVICE_VERSION
});
}
//#endregion
//#region src/daemon/program-args.ts
function isNodeRuntime$1(execPath) {
const base = path.basename(execPath).toLowerCase();
return base === "node" || base === "node.exe";
}
function isBunRuntime$1(execPath) {
const base = path.basename(execPath).toLowerCase();
return base === "bun" || base === "bun.exe";
}
async function resolveCliEntrypointPathForService() {
const argv1 = process.argv[1];
if (!argv1) throw new Error("Unable to resolve CLI entrypoint path");
const normalized = path.resolve(argv1);
const resolvedPath = await resolveRealpathSafe(normalized);
if (/[/\\]dist[/\\].+\.(cjs|js|mjs)$/.test(resolvedPath)) {
await fs$1.access(resolvedPath);
if (/[/\\]dist[/\\].+\.(cjs|js|mjs)$/.test(normalized) && normalized !== resolvedPath) try {
await fs$1.access(normalized);
return normalized;
} catch {}
return resolvedPath;
}
const distCandidates = buildDistCandidates(resolvedPath, normalized);
for (const candidate of distCandidates) try {
await fs$1.access(candidate);
return candidate;
} catch {}
throw new Error(`Cannot find built CLI at ${distCandidates.join(" or ")}. Run "pnpm build" first, or use dev mode.`);
}
async function resolveRealpathSafe(inputPath) {
try {
return await fs$1.realpath(inputPath);
} catch {
return inputPath;
}
}
function buildDistCandidates(...inputs) {
const candidates = [];
const seen = /* @__PURE__ */ new Set();
for (const inputPath of inputs) {
if (!inputPath) continue;
const baseDir = path.dirname(inputPath);
appendDistCandidates(candidates, seen, path.resolve(baseDir, ".."));
appendDistCandidates(candidates, seen, baseDir);
appendNodeModulesBinCandidates(candidates, seen, inputPath);
}
return candidates;
}
function appendDistCandidates(candidates, seen, baseDir) {
const distDir = path.resolve(baseDir, "dist");
const distEntries = [
path.join(distDir, "index.js"),
path.join(distDir, "index.mjs"),
path.join(distDir, "entry.js"),
path.join(distDir, "entry.mjs")
];
for (const entry of distEntries) {
if (seen.has(entry)) continue;
seen.add(entry);
candidates.push(entry);
}
}
function appendNodeModulesBinCandidates(candidates, seen, inputPath) {
const parts = inputPath.split(path.sep);
const binIndex = parts.lastIndexOf(".bin");
if (binIndex <= 0) return;
if (parts[binIndex - 1] !== "node_modules") return;
const binName = path.basename(inputPath);
const nodeModulesDir = parts.slice(0, binIndex).join(path.sep);
appendDistCandidates(candidates, seen, path.join(nodeModulesDir, binName));
}
function resolveRepoRootForDev() {
const argv1 = process.argv[1];
if (!argv1) throw new Error("Unable to resolve repo root");
const parts = path.resolve(argv1).split(path.sep);
const srcIndex = parts.lastIndexOf("src");
if (srcIndex === -1) throw new Error("Dev mode requires running from repo (src/index.ts)");
return parts.slice(0, srcIndex).join(path.sep);
}
async function resolveBunPath() {
return await resolveBinaryPath("bun");
}
async function resolveNodePath() {
return await resolveBinaryPath("node");
}
async function resolveBinaryPath(binary) {
const { execFileSync } = await import("node:child_process");
const cmd = process.platform === "win32" ? "where" : "which";
try {
const resolved = execFileSync(cmd, [binary], { encoding: "utf8" }).trim().split(/\r?\n/)[0]?.trim();
if (!resolved) throw new Error("empty");
await fs$1.access(resolved);
return resolved;
} catch {
if (binary === "bun") throw new Error("Bun not found in PATH. Install bun: https://bun.sh");
throw new Error("Node not found in PATH. Install Node 22+.");
}
}
async function resolveCliProgramArguments(params) {
const execPath = process.execPath;
const runtime = params.runtime ?? "auto";
if (runtime === "node") return { programArguments: [
params.nodePath ?? (isNodeRuntime$1(execPath) ? execPath : await resolveNodePath()),
await resolveCliEntrypointPathForService(),
...params.args
] };
if (runtime === "bun") {
if (params.dev) {
const repoRoot = resolveRepoRootForDev();
const devCliPath = path.join(repoRoot, "src", "index.ts");
await fs$1.access(devCliPath);
return {
programArguments: [
isBunRuntime$1(execPath) ? execPath : await resolveBunPath(),
devCliPath,
...params.args
],
workingDirectory: repoRoot
};
}
return { programArguments: [
isBunRuntime$1(execPath) ? execPath : await resolveBunPath(),
await resolveCliEntrypointPathForService(),
...params.args
] };
}
if (!params.dev) try {
return { programArguments: [
execPath,
await resolveCliEntrypointPathForService(),
...params.args
] };
} catch (error) {
if (!isNodeRuntime$1(execPath)) return { programArguments: [execPath, ...params.args] };
throw error;
}
const repoRoot = resolveRepoRootForDev();
const devCliPath = path.join(repoRoot, "src", "index.ts");
await fs$1.access(devCliPath);
if (isBunRuntime$1(execPath)) return {
programArguments: [
execPath,
devCliPath,
...params.args
],
workingDirectory: repoRoot
};
return {
programArguments: [
await resolveBunPath(),
devCliPath,
...params.args
],
workingDirectory: repoRoot
};
}
async function resolveGatewayProgramArguments(params) {
return resolveCliProgramArguments({
args: [
"gateway",
"--port",
String(params.port)
],
dev: params.dev,
runtime: params.runtime,
nodePath: params.nodePath
});
}
//#endregion
//#region src/terminal/progress-line.ts
let activeStream = null;
function registerActiveProgressLine(stream) {
if (!stream.isTTY) return;
activeStream = stream;
}
function clearActiveProgressLine() {
if (!activeStream?.isTTY) return;
activeStream.write("\r\x1B[2K");
}
function unregisterActiveProgressLine(stream) {
if (!activeStream) return;
if (stream && activeStream !== stream) return;
activeStream = null;
}
//#endregion
//#region src/terminal/restore.ts
const RESET_SEQUENCE = "\x1B[0m\x1B[?25h\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?2004l";
function reportRestoreFailure(scope, err, reason) {
const suffix = reason ? ` (${reason})` : "";
const message = `[terminal] restore ${scope} failed${suffix}: ${String(err)}`;
try {
process.stderr.write(`${message}\n`);
} catch (writeErr) {
console.error(`[terminal] restore reporting failed${suffix}: ${String(writeErr)}`);
}
}
function restoreTerminalState(reason, options = {}) {
const resumeStdin = options.resumeStdinIfPaused ?? options.resumeStdin ?? false;
try {
clearActiveProgressLine();
} catch (err) {
reportRestoreFailure("progress line", err, reason);
}
const stdin = process.stdin;
if (stdin.isTTY && typeof stdin.setRawMode === "function") {
try {
stdin.setRawMode(false);
} catch (err) {
reportRestoreFailure("raw mode", err, reason);
}
if (resumeStdin && typeof stdin.isPaused === "function" && stdin.isPaused()) try {
stdin.resume();
} catch (err) {
reportRestoreFailure("stdin resume", err, reason);
}
}
if (process.stdout.isTTY) try {
process.stdout.write(RESET_SEQUENCE);
} catch (err) {
reportRestoreFailure("stdout reset", err, reason);
}
}
//#endregion
//#region src/runtime.ts
function shouldEmitRuntimeLog(env = process.env) {
if (env.VITEST !== "true") return true;
if (env.OPENCLAW_TEST_RUNTIME_LOG === "1") return true;
return typeof console.log.mock === "object";
}
function createRuntimeIo() {
return {
log: (...args) => {
if (!shouldEmitRuntimeLog()) return;
clearActiveProgressLine();
console.log(...args);
},
error: (...args) => {
clearActiveProgressLine();
console.error(...args);
}
};
}
const defaultRuntime = {
...createRuntimeIo(),
exit: (code) => {
restoreTerminalState("runtime exit", { resumeStdinIfPaused: false });
process.exit(code);
throw new Error("unreachable");
}
};
//#endregion
//#region src/infra/runtime-guard.ts
const MIN_NODE = {
major: 22,
minor: 12,
patch: 0
};
const SEMVER_RE = /(\d+)\.(\d+)\.(\d+)/;
function parseSemver(version) {
if (!version) return null;
const match = version.match(SEMVER_RE);
if (!match) return null;
const [, major, minor, patch] = match;
return {
major: Number.parseInt(major, 10),
minor: Number.parseInt(minor, 10),
patch: Number.parseInt(patch, 10)
};
}
function isAtLeast(version, minimum) {
if (!version) return false;
if (version.major !== minimum.major) return version.major > minimum.major;
if (version.minor !== minimum.minor) return version.minor > minimum.minor;
return version.patch >= minimum.patch;
}
function isSupportedNodeVersion(version) {
return isAtLeast(parseSemver(version), MIN_NODE);
}
//#endregion
//#region src/daemon/runtime-paths.ts
const VERSION_MANAGER_MARKERS = [
"/.nvm/",
"/.fnm/",
"/.volta/",
"/.asdf/",
"/.n/",
"/.nodenv/",
"/.nodebrew/",
"/nvs/"
];
function getPathModule$1(platform) {
return platform === "win32" ? path.win32 : path.posix;
}
function isNodeExecPath(execPath, platform) {
const base = getPathModule$1(platform).basename(execPath).toLowerCase();
return base === "node" || base === "node.exe";
}
function normalizeForCompare(input, platform) {
const normalized = getPathModule$1(platform).normalize(input).replaceAll("\\", "/");
if (platform === "win32") return normalized.toLowerCase();
return normalized;
}
function buildSystemNodeCandidates(env, platform) {
if (platform === "darwin") return [
"/opt/homebrew/bin/node",
"/usr/local/bin/node",
"/usr/bin/node"
];
if (platform === "linux") return ["/usr/local/bin/node", "/usr/bin/node"];
if (platform === "win32") {
const pathModule = getPathModule$1(platform);
const programFiles = env.ProgramFiles ?? "C:\\Program Files";
const programFilesX86 = env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
return [pathModule.join(programFiles, "nodejs", "node.exe"), pathModule.join(programFilesX86, "nodejs", "node.exe")];
}
return [];
}
const execFileAsync$2 = promisify(execFile);
async function resolveNodeVersion(nodePath, execFileImpl) {
try {
const { stdout } = await execFileImpl(nodePath, ["-p", "process.versions.node"], { encoding: "utf8" });
const value = stdout.trim();
return value ? value : null;
} catch {
return null;
}
}
function isVersionManagedNodePath(nodePath, platform = process.platform) {
const normalized = normalizeForCompare(nodePath, platform);
return VERSION_MANAGER_MARKERS.some((marker) => normalized.includes(marker));
}
function isSystemNodePath(nodePath, env = process.env, platform = process.platform) {
const normalized = normalizeForCompare(nodePath, platform);
return buildSystemNodeCandidates(env, platform).some((candidate) => {
return normalized === normalizeForCompare(candidate, platform);
});
}
async function resolveSystemNodePath(env = process.env, platform = process.platform) {
const candidates = buildSystemNodeCandidates(env, platform);
for (const candidate of candidates) try {
await fs$1.access(candidate);
return candidate;
} catch {}
return null;
}
async function resolveSystemNodeInfo(params) {
const systemNode = await resolveSystemNodePath(params.env ?? process.env, params.platform ?? process.platform);
if (!systemNode) return null;
const version = await resolveNodeVersion(systemNode, params.execFile ?? execFileAsync$2);
return {
path: systemNode,
version,
supported: isSupportedNodeVersion(version)
};
}
function renderSystemNodeWarning(systemNode, selectedNodePath) {
if (!systemNode || systemNode.supported) return null;
const versionLabel = systemNode.version ?? "unknown";
const selectedLabel = selectedNodePath ? ` Using ${selectedNodePath} for the daemon.` : "";
return `System Node ${versionLabel} at ${systemNode.path} is below the required Node 22+.${selectedLabel} Install Node 22+ from nodejs.org or Homebrew.`;
}
async function resolvePreferredNodePath(params) {
if (params.runtime !== "node") return;
const platform = params.platform ?? process.platform;
const currentExecPath = params.execPath ?? process.execPath;
if (currentExecPath && isNodeExecPath(currentExecPath, platform)) {
if (isSupportedNodeVersion(await resolveNodeVersion(currentExecPath, params.execFile ?? execFileAsync$2))) return currentExecPath;
}
const systemNode = await resolveSystemNodeInfo(params);
if (!systemNode?.supported) return;
return systemNode.path;
}
//#endregion
//#region src/version.ts
const CORE_PACKAGE_NAME = "openclaw";
const PACKAGE_JSON_CANDIDATES = [
"../package.json",
"../../package.json",
"../../../package.json",
"./package.json"
];
const BUILD_INFO_CANDIDATES = [
"../build-info.json",
"../../build-info.json",
"./build-info.json"
];
function readVersionFromJsonCandidates(moduleUrl, candidates, opts = {}) {
try {
const require = createRequire(moduleUrl);
for (const candidate of candidates) try {
const parsed = require(candidate);
const version = parsed.version?.trim();
if (!version) continue;
if (opts.requirePackageName && parsed.name !== CORE_PACKAGE_NAME) continue;
return version;
} catch {}
return null;
} catch {
return null;
}
}
function readVersionFromPackageJsonForModuleUrl(moduleUrl) {
return readVersionFromJsonCandidates(moduleUrl, PACKAGE_JSON_CANDIDATES, { requirePackageName: true });
}
function readVersionFromBuildInfoForModuleUrl(moduleUrl) {
return readVersionFromJsonCandidates(moduleUrl, BUILD_INFO_CANDIDATES);
}
function resolveVersionFromModuleUrl(moduleUrl) {
return readVersionFromPackageJsonForModuleUrl(moduleUrl) || readVersionFromBuildInfoForModuleUrl(moduleUrl);
}
const VERSION = typeof __OPENCLAW_VERSION__ === "string" && __OPENCLAW_VERSION__ || process.env.OPENCLAW_BUNDLED_VERSION || resolveVersionFromModuleUrl(import.meta.url) || "0.0.0";
//#endregion
//#region src/daemon/service-env.ts
function addNonEmptyDir(dirs, dir) {
if (dir) dirs.push(dir);
}
function appendSubdir(base, subdir) {
if (!base) return;
return base.endsWith(`/${subdir}`) ? base : path.posix.join(base, subdir);
}
function addCommonUserBinDirs(dirs, home) {
dirs.push(`${home}/.local/bin`);
dirs.push(`${home}/.npm-global/bin`);
dirs.push(`${home}/bin`);
dirs.push(`${home}/.volta/bin`);
dirs.push(`${home}/.asdf/shims`);
dirs.push(`${home}/.bun/bin`);
}
function resolveSystemPathDirs(platform) {
if (platform === "darwin") return [
"/opt/homebrew/bin",
"/usr/local/bin",
"/usr/bin",
"/bin"
];
if (platform === "linux") return [
"/usr/local/bin",
"/usr/bin",
"/bin"
];
return [];
}
/**
* Resolve common user bin directories for macOS.
* These are paths where npm global installs and node version managers typically place binaries.
*
* Key differences from Linux:
* - fnm: macOS uses ~/Library/Application Support/fnm (not ~/.local/share/fnm)
* - pnpm: macOS uses ~/Library/pnpm (not ~/.local/share/pnpm)
*/
function resolveDarwinUserBinDirs(home, env) {
if (!home) return [];
const dirs = [];
addNonEmptyDir(dirs, env?.PNPM_HOME);
addNonEmptyDir(dirs, appendSubdir(env?.NPM_CONFIG_PREFIX, "bin"));
addNonEmptyDir(dirs, appendSubdir(env?.BUN_INSTALL, "bin"));
addNonEmptyDir(dirs, appendSubdir(env?.VOLTA_HOME, "bin"));
addNonEmptyDir(dirs, appendSubdir(env?.ASDF_DATA_DIR, "shims"));
addNonEmptyDir(dirs, env?.NVM_DIR);
addNonEmptyDir(dirs, appendSubdir(env?.FNM_DIR, "aliases/default/bin"));
addCommonUserBinDirs(dirs, home);
dirs.push(`${home}/Library/Application Support/fnm/aliases/default/bin`);
dirs.push(`${home}/.fnm/aliases/default/bin`);
dirs.push(`${home}/Library/pnpm`);
dirs.push(`${home}/.local/share/pnpm`);
return dirs;
}
/**
* Resolve common user bin directories for Linux.
* These are paths where npm global installs and node version managers typically place binaries.
*/
function resolveLinuxUserBinDirs(home, env) {
if (!home) return [];
const dirs = [];
addNonEmptyDir(dirs, env?.PNPM_HOME);
addNonEmptyDir(dirs, appendSubdir(env?.NPM_CONFIG_PREFIX, "bin"));
addNonEmptyDir(dirs, appendSubdir(env?.BUN_INSTALL, "bin"));
addNonEmptyDir(dirs, appendSubdir(env?.VOLTA_HOME, "bin"));
addNonEmptyDir(dirs, appendSubdir(env?.ASDF_DATA_DIR, "shims"));
addNonEmptyDir(dirs, appendSubdir(env?.NVM_DIR, "current/bin"));
addNonEmptyDir(dirs, appendSubdir(env?.FNM_DIR, "current/bin"));
addCommonUserBinDirs(dirs, home);
dirs.push(`${home}/.nvm/current/bin`);
dirs.push(`${home}/.fnm/current/bin`);
dirs.push(`${home}/.local/share/pnpm`);
return dirs;
}
function getMinimalServicePathParts(options = {}) {
const platform = options.platform ?? process.platform;
if (platform === "win32") return [];
const parts = [];
const extraDirs = options.extraDirs ?? [];
const systemDirs = resolveSystemPathDirs(platform);
const userDirs = platform === "linux" ? resolveLinuxUserBinDirs(options.home, options.env) : platform === "darwin" ? resolveDarwinUserBinDirs(options.home, options.env) : [];
const add = (dir) => {
if (!dir) return;
if (!parts.includes(dir)) parts.push(dir);
};
for (const dir of extraDirs) add(dir);
for (const dir of userDirs) add(dir);
for (const dir of systemDirs) add(dir);
return parts;
}
function getMinimalServicePathPartsFromEnv(options = {}) {
const env = options.env ?? process.env;
return getMinimalServicePathParts({
...options,
home: options.home ?? env.HOME,
env
});
}
function buildMinimalServicePath(options = {}) {
const env = options.env ?? process.env;
if ((options.platform ?? process.platform) === "win32") return env.PATH ?? "";
return getMinimalServicePathPartsFromEnv({
...options,
env
}).join(path.posix.delimiter);
}
function buildServiceEnvironment(params) {
const { env, port, token, launchdLabel } = params;
const profile = env.OPENCLAW_PROFILE;
const resolvedLaunchdLabel = launchdLabel || (process.platform === "darwin" ? resolveGatewayLaunchAgentLabel(profile) : void 0);
const systemdUnit = `${resolveGatewaySystemdServiceName(profile)}.service`;
const stateDir = env.OPENCLAW_STATE_DIR;
const configPath = env.OPENCLAW_CONFIG_PATH;
const tmpDir = env.TMPDIR?.trim() || os.tmpdir();
return {
HOME: env.HOME,
TMPDIR: tmpDir,
PATH: buildMinimalServicePath({ env }),
OPENCLAW_PROFILE: profile,
OPENCLAW_STATE_DIR: stateDir,
OPENCLAW_CONFIG_PATH: configPath,
OPENCLAW_GATEWAY_PORT: String(port),
OPENCLAW_GATEWAY_TOKEN: token,
OPENCLAW_LAUNCHD_LABEL: resolvedLaunchdLabel,
OPENCLAW_SYSTEMD_UNIT: systemdUnit,
OPENCLAW_SERVICE_MARKER: GATEWAY_SERVICE_MARKER,
OPENCLAW_SERVICE_KIND: GATEWAY_SERVICE_KIND,
OPENCLAW_SERVICE_VERSION: VERSION
};
}
//#endregion
//#region src/commands/daemon-install-runtime-warning.ts
async function emitNodeRuntimeWarning(params) {
if (params.runtime !== "node") return;
const warning = renderSystemNodeWarning(await resolveSystemNodeInfo({ env: params.env }), params.nodeProgram);
if (warning) params.warn?.(warning, params.title);
}
//#endregion
//#region src/commands/daemon-install-helpers.ts
function resolveGatewayDevMode(argv = process.argv) {
const normalizedEntry = argv[1]?.replaceAll("\\", "/");
return Boolean(normalizedEntry?.includes("/src/") && normalizedEntry.endsWith(".ts"));
}
async function buildGatewayInstallPlan(params) {
const devMode = params.devMode ?? resolveGatewayDevMode();
const nodePath = params.nodePath ?? await resolvePreferredNodePath({
env: params.env,
runtime: params.runtime
});
const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({
port: params.port,
dev: devMode,
runtime: params.runtime,
nodePath
});
await emitNodeRuntimeWarning({
env: params.env,
runtime: params.runtime,
nodeProgram: programArguments[0],
warn: params.warn,
title: "Gateway runtime"
});
const serviceEnvironment = buildServiceEnvironment({
env: params.env,
port: params.port,
token: params.token,
launchdLabel: process.platform === "darwin" ? resolveGatewayLaunchAgentLabel(params.env.OPENCLAW_PROFILE) : void 0
});
const environment = { ...collectConfigEnvVars(params.config) };
Object.assign(environment, serviceEnvironment);
return {
programArguments,
workingDirectory,
environment
};
}
//#endregion
//#region src/commands/daemon-runtime.ts
const DEFAULT_GATEWAY_DAEMON_RUNTIME = "node";
function isGatewayDaemonRuntime(value) {
return value === "node" || value === "bun";
}
//#endregion
//#region src/plugins/registry.ts
function createEmptyPluginRegistry() {
return {
plugins: [],
tools: [],
hooks: [],
typedHooks: [],
channels: [],
providers: [],
gatewayHandlers: {},
httpHandlers: [],
httpRoutes: [],
cliRegistrars: [],
services: [],
commands: [],
diagnostics: []
};
}
//#endregion
//#region src/plugins/runtime.ts
const REGISTRY_STATE = Symbol.for("openclaw.pluginRegistryState");
const state = (() => {
const globalState = globalThis;
if (!globalState[REGISTRY_STATE]) globalState[REGISTRY_STATE] = {
registry: createEmptyPluginRegistry(),
key: null
};
return globalState[REGISTRY_STATE];
})();
//#endregion
//#region src/channels/registry.ts
const CHAT_CHANNEL_ORDER = [
"telegram",
"whatsapp",
"discord",
"irc",
"googlechat",
"slack",
"signal",
"imessage"
];
const CHANNEL_IDS = [...CHAT_CHANNEL_ORDER];
const CHAT_CHANNEL_ALIASES = {
imsg: "imessage",
"internet-relay-chat": "irc",
"google-chat": "googlechat",
gchat: "googlechat"
};
const normalizeChannelKey = (raw) => {
return raw?.trim().toLowerCase() || void 0;
};
function normalizeChatChannelId(raw) {
const normalized = normalizeChannelKey(raw);
if (!normalized) return null;
const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized;
return CHAT_CHANNEL_ORDER.includes(resolved) ? resolved : null;
}
//#endregion
//#region src/terminal/ansi.ts
const ANSI_SGR_PATTERN = "\\x1b\\[[0-9;]*m";
const OSC8_PATTERN = "\\x1b\\]8;;.*?\\x1b\\\\|\\x1b\\]8;;\\x1b\\\\";
const ANSI_REGEX = new RegExp(ANSI_SGR_PATTERN, "g");
const OSC8_REGEX = new RegExp(OSC8_PATTERN, "g");
//#endregion
//#region src/logging/console.ts
function resolveNodeRequire$1() {
const getBuiltinModule = process.getBuiltinModule;
if (typeof getBuiltinModule !== "function") return null;
try {
const moduleNamespace = getBuiltinModule("module");
return typeof moduleNamespace.createRequire === "function" ? moduleNamespace.createRequire : null;
} catch {
return null;
}
}
const requireConfig$1 = resolveNodeRequire$1()?.(import.meta.url) ?? null;
const loadConfigFallbackDefault = () => {
try {
return (requireConfig$1?.("../config/config.js"))?.loadConfig?.().logging;
} catch {
return;
}
};
let loadConfigFallback = loadConfigFallbackDefault;
function normalizeConsoleLevel(level) {
if (isVerbose()) return "debug";
if (!level && process.env.VITEST === "true" && process.env.OPENCLAW_TEST_CONSOLE !== "1") return "silent";
return normalizeLogLevel(level, "info");
}
function normalizeConsoleStyle(style) {
if (style === "compact" || style === "json" || style === "pretty") return style;
if (!process.stdout.isTTY) return "compact";
return "pretty";
}
function resolveConsoleSettings() {
let cfg = loggingState.overrideSettings ?? readLoggingConfig();
if (!cfg) if (loggingState.resolvingConsoleSettings) cfg = void 0;
else {
loggingState.resolvingConsoleSettings = true;
try {
cfg = loadConfigFallback();
} finally {
loggingState.resolvingConsoleSettings = false;
}
}
return {
level: normalizeConsoleLevel(cfg?.consoleLevel),
style: normalizeConsoleStyle(cfg?.consoleStyle)
};
}
function consoleSettingsChanged(a, b) {
if (!a) return true;
return a.level !== b.level || a.style !== b.style;
}
function getConsoleSettings() {
const settings = resolveConsoleSettings();
const cached = loggingState.cachedConsoleSettings;
if (!cached || consoleSettingsChanged(cached, settings)) loggingState.cachedConsoleSettings = settings;
return loggingState.cachedConsoleSettings;
}
function shouldLogSubsystemToConsole(subsystem) {
const filter = loggingState.consoleSubsystemFilter;
if (!filter || filter.length === 0) return true;
return filter.some((prefix) => subsystem === prefix || subsystem.startsWith(`${prefix}/`));
}
//#endregion
//#region src/logging/subsystem.ts
function shouldLogToConsole(level, settings) {
if (settings.level === "silent")