openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
177 lines (176 loc) • 8.19 kB
JavaScript
import { T as normalizeLowercaseStringOrEmpty } from "./runtime-doctor-migrations-BhouWAUC.js";
import "./number-coercion-CWp8Ho-Y.js";
import { i as resolveUserPath, n as resolveHomeRelativePath, r as resolveRequiredHomeDir } from "./account-core-bZmYRKLc.js";
import path from "node:path";
import fs from "node:fs";
import { canUseRootFileOpen, openRootFileSync } from "@openclaw/fs-safe/advanced";
import "@openclaw/fs-safe/errors";
import os from "node:os";
//#region src/cli/profile-utils.ts
const PROFILE_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
function isValidProfileName(value) {
return PROFILE_NAME_RE.test(value);
}
/** Resolve the canonical home-scoped state root for a validated CLI profile. */
function resolveProfileStateDir(profile, env, homedir) {
const trimmed = profile.trim();
if (!isValidProfileName(trimmed)) throw new Error(`Invalid profile name: ${JSON.stringify(profile)}`);
const suffix = normalizeLowercaseStringOrEmpty(trimmed) === "default" ? "" : `-${trimmed}`;
return path.join(resolveRequiredHomeDir(env, homedir), `.openclaw${suffix}`);
}
//#endregion
//#region src/infra/test-runtime-env.ts
/** Detects Vitest/test execution from the env shape used by local and worker processes. */
function isVitestRuntimeEnv(env = process.env) {
const vitest = env.VITEST;
return vitest === "true" || vitest === "1" || env.VITEST_POOL_ID !== void 0 || env.VITEST_WORKER_ID !== void 0 || env.NODE_ENV === "test";
}
/** Enables the shared fast-test shortcuts only inside a detected test runtime. */
function isFastTestRuntimeEnv(env = process.env) {
return env.OPENCLAW_TEST_FAST === "1" && (isVitestRuntimeEnv(env) || env !== process.env && isVitestRuntimeEnv(process.env));
}
//#endregion
//#region src/config/state-dir.ts
function resolveLegacyStateDirs(homedir = resolveRequiredHomeDir) {
return [path.join(homedir(), ".clawdbot")];
}
function resolveNewStateDir(homedir = resolveRequiredHomeDir) {
return path.join(homedir(), ".openclaw");
}
/**
* State directory for mutable data (sessions, logs, caches).
* Can be overridden via OPENCLAW_STATE_DIR.
* Default: ~/.openclaw
*/
function resolveStateDir(env = process.env, homedir = () => resolveRequiredHomeDir(env, os.homedir)) {
const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir);
const override = env.OPENCLAW_STATE_DIR?.trim();
if (override) return resolveHomeRelativePath(override, {
env,
homedir: effectiveHomedir
});
return resolveStateDirFromHome(env, effectiveHomedir);
}
/** Select a default state directory from the caller's already resolved home. */
function resolveStateDirFromHome(env, effectiveHomedir) {
const newDir = resolveNewStateDir(effectiveHomedir);
if (isFastTestRuntimeEnv(env) || fs.existsSync(newDir)) return newDir;
return resolveLegacyStateDirs(effectiveHomedir).find((dir) => fs.existsSync(dir)) ?? newDir;
}
//#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";
}
resolveIsNixMode();
const CONFIG_FILENAME = "openclaw.json";
const LEGACY_CONFIG_FILENAMES = ["clawdbot.json"];
function configPathsInStateDir(stateDir) {
return [CONFIG_FILENAME, ...LEGACY_CONFIG_FILENAMES].map((name) => path.join(stateDir, name));
}
function findExistingConfigPath(candidates) {
return candidates.find((candidate) => fs.existsSync(candidate));
}
/** Build a homedir thunk that respects OPENCLAW_HOME for the given env. */
function envHomedir(env) {
return () => resolveRequiredHomeDir(env, os.homedir);
}
/**
* Optional allowlist of directories that `$include` directives may resolve
* outside the config directory. Set via `OPENCLAW_INCLUDE_ROOTS` as a
* platform-delimited path list (`:` on POSIX, `;` on Windows).
*
* Each entry is tilde-expanded and resolved to an absolute path. Entries that
* cannot be resolved or that are not absolute after expansion are dropped.
*
* Returns an empty array when the var is unset or contains no usable entries,
* preserving the historical behavior where `$include` is confined to the
* directory containing `openclaw.json`.
*/
function resolveIncludeRoots(env = process.env, homedir = envHomedir(env)) {
const raw = env.OPENCLAW_INCLUDE_ROOTS?.trim();
if (!raw) return [];
const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir);
const seen = /* @__PURE__ */ new Set();
const roots = [];
for (const entry of raw.split(path.delimiter)) {
const trimmed = entry.trim();
if (!trimmed) continue;
const resolved = path.resolve(resolveHomeRelativePath(trimmed, {
env,
homedir: effectiveHomedir
}));
if (seen.has(resolved)) continue;
seen.add(resolved);
roots.push(resolved);
}
return roots;
}
resolveStateDir();
/**
* Config file path (JSON or JSON5).
* Can be overridden via OPENCLAW_CONFIG_PATH.
* Default: ~/.openclaw/openclaw.json (or $OPENCLAW_STATE_DIR/openclaw.json)
*/
function resolveCanonicalConfigPath(env = process.env, stateDir) {
const override = env.OPENCLAW_CONFIG_PATH?.trim();
if (override) return resolveUserPath(override, env, envHomedir(env));
return path.join(stateDir ?? resolveStateDir(env, envHomedir(env)), 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 override = env.OPENCLAW_CONFIG_PATH?.trim();
if (override) return resolveUserPath(override, env, homedir);
if (isFastTestRuntimeEnv(env)) return resolveCanonicalConfigPath(env, resolveStateDir(env, homedir));
return findExistingConfigPath(resolveDefaultConfigCandidates(env, homedir)) ?? resolveCanonicalConfigPath(env, resolveStateDir(env, homedir));
}
/**
* Active config path (prefers existing config files).
*/
function resolveConfigPath(env = process.env, stateDir, homedir = envHomedir(env)) {
const override = env.OPENCLAW_CONFIG_PATH?.trim();
if (override) return resolveUserPath(override, env, homedir);
const selectedStateDir = stateDir ?? resolveStateDir(env, envHomedir(env));
if (isFastTestRuntimeEnv(env)) return path.join(selectedStateDir, CONFIG_FILENAME);
const stateOverride = env.OPENCLAW_STATE_DIR?.trim();
const existing = findExistingConfigPath(configPathsInStateDir(selectedStateDir));
if (existing) return existing;
if (stateOverride) return path.join(selectedStateDir, CONFIG_FILENAME);
const defaultStateDir = resolveStateDir(env, homedir);
if (path.resolve(selectedStateDir) === path.resolve(defaultStateDir)) return resolveConfigPathCandidate(env, homedir);
return path.join(selectedStateDir, CONFIG_FILENAME);
}
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();
if (explicit) return [resolveUserPath(explicit, env, effectiveHomedir)];
const openclawStateDir = env.OPENCLAW_STATE_DIR?.trim();
return [
...openclawStateDir ? [resolveUserPath(openclawStateDir, env, effectiveHomedir)] : [],
resolveNewStateDir(effectiveHomedir),
...resolveLegacyStateDirs(effectiveHomedir)
].flatMap(configPathsInStateDir);
}
/** Resolves the legacy credentials directory retained for Doctor and backup ownership. */
function resolveOAuthDir(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) {
const override = env.OPENCLAW_OAUTH_DIR?.trim();
if (override) return resolveUserPath(override, env, envHomedir(env));
return path.join(stateDir, "credentials");
}
//#endregion
export { resolveOAuthDir as a, resolveIncludeRoots as i, openRootFileSync as n, resolveStateDir as o, resolveConfigPath as r, resolveProfileStateDir as s, canUseRootFileOpen as t };