diff --git a/docs/providers/xiaomi.md b/docs/providers/xiaomi.md index 008c421058b..1e270c14728 100644 --- a/docs/providers/xiaomi.md +++ b/docs/providers/xiaomi.md @@ -1,14 +1,14 @@ --- -summary: "Use Xiaomi MiMo (mimo-v2-flash) with Moltbot" +summary: "Use Xiaomi MiMo (mimo-v2-flash) with OpenClaw" read_when: - - You want Xiaomi MiMo models in Moltbot + - You want Xiaomi MiMo models in OpenClaw - You need XIAOMI_API_KEY setup --- # Xiaomi MiMo Xiaomi MiMo is the API platform for **MiMo** models. It provides REST APIs compatible with OpenAI and Anthropic formats and uses API keys for authentication. Create your API key in -the [Xiaomi MiMo console](https://platform.xiaomimimo.com/#/console/api-keys). Moltbot uses +the [Xiaomi MiMo console](https://platform.xiaomimimo.com/#/console/api-keys). OpenClaw uses the `xiaomi` provider with a Xiaomi MiMo API key. ## Model overview @@ -20,9 +20,9 @@ the `xiaomi` provider with a Xiaomi MiMo API key. ## CLI setup ```bash -moltbot onboard --auth-choice xiaomi-api-key +openclaw onboard --auth-choice xiaomi-api-key # or non-interactive -moltbot onboard --auth-choice xiaomi-api-key --xiaomi-api-key "$XIAOMI_API_KEY" +openclaw onboard --auth-choice xiaomi-api-key --xiaomi-api-key "$XIAOMI_API_KEY" ``` ## Config snippet diff --git a/package.json b/package.json index 1186b041365..9f21078dfd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openclaw", - "version": "2026.1.29-beta.1", + "version": "2026.1.29-beta.2", "description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent", "type": "module", "main": "dist/index.js", diff --git a/src/cli/banner.ts b/src/cli/banner.ts index 1174de6740f..79ed3ee9017 100644 --- a/src/cli/banner.ts +++ b/src/cli/banner.ts @@ -64,10 +64,11 @@ export function formatCliBannerLine(version: string, options: BannerOptions = {} const LOBSTER_ASCII = [ "▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄", - "█████░█████░█████░█░░░█░█████░█░░░░░░███░░█░░░█", - "█░░░█░█░░░█░███░░░██░░█░█░░░░░█░░░░░█░░░█░█░█░█", - "█████░████░░█████░█░░██░█████░█████░█████░██░██", - "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀", + "█████░█████░█████░█░░░█░█████░█░░░░░█████░█░░░█", + "█░░░█░█░░░█░█░░░░░██░░█░█░░░░░█░░░░░█░░░█░█░░░█", + "█░░░█░█████░████░░█░█░█░█░░░░░█░░░░░█████░█░█░█", + "█░░░█░█░░░░░█░░░░░█░░██░█░░░░░█░░░░░█░░░█░██░██", + "█████░█░░░░░█████░█░░░█░█████░█████░█░░░█░█░░░█", " 🦞 OPENCLAW 🦞 ", " ", ]; diff --git a/src/commands/doctor-gateway-services.ts b/src/commands/doctor-gateway-services.ts index 4311c740028..7ca23a13022 100644 --- a/src/commands/doctor-gateway-services.ts +++ b/src/commands/doctor-gateway-services.ts @@ -1,4 +1,8 @@ +import { execFile } from "node:child_process"; +import fs from "node:fs/promises"; +import os from "node:os"; import path from "node:path"; +import { promisify } from "node:util"; import type { OpenClawConfig } from "../config/config.js"; import { resolveGatewayPort, resolveIsNixMode } from "../config/paths.js"; @@ -16,6 +20,8 @@ import { buildGatewayInstallPlan } from "./daemon-install-helpers.js"; import { DEFAULT_GATEWAY_DAEMON_RUNTIME, type GatewayDaemonRuntime } from "./daemon-runtime.js"; import type { DoctorOptions, DoctorPrompter } from "./doctor-prompter.js"; +const execFileAsync = promisify(execFile); + function detectGatewayRuntime(programArguments: string[] | undefined): GatewayDaemonRuntime { const first = programArguments?.[0]; if (first) { @@ -37,6 +43,42 @@ function normalizeExecutablePath(value: string): string { return path.resolve(value); } +function extractDetailPath(detail: string, prefix: string): string | null { + if (!detail.startsWith(prefix)) return null; + const value = detail.slice(prefix.length).trim(); + return value.length > 0 ? value : null; +} + +async function cleanupLegacyLaunchdService(params: { + label: string; + plistPath: string; +}): Promise { + const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501"; + await execFileAsync("launchctl", ["bootout", domain, params.plistPath]).catch(() => undefined); + await execFileAsync("launchctl", ["unload", params.plistPath]).catch(() => undefined); + + const trashDir = path.join(os.homedir(), ".Trash"); + try { + await fs.mkdir(trashDir, { recursive: true }); + } catch { + // ignore + } + + try { + await fs.access(params.plistPath); + } catch { + return null; + } + + const dest = path.join(trashDir, `${params.label}-${Date.now()}.plist`); + try { + await fs.rename(params.plistPath, dest); + return dest; + } catch { + return null; + } +} + export async function maybeRepairGatewayServiceConfig( cfg: OpenClawConfig, mode: "local" | "remote", @@ -150,7 +192,11 @@ export async function maybeRepairGatewayServiceConfig( } } -export async function maybeScanExtraGatewayServices(options: DoctorOptions) { +export async function maybeScanExtraGatewayServices( + options: DoctorOptions, + runtime: RuntimeEnv, + prompter: DoctorPrompter, +) { const extraServices = await findExtraGatewayServices(process.env, { deep: options.deep, }); @@ -161,6 +207,47 @@ export async function maybeScanExtraGatewayServices(options: DoctorOptions) { "Other gateway-like services detected", ); + const legacyServices = extraServices.filter((svc) => svc.legacy === true); + if (legacyServices.length > 0) { + const shouldRemove = await prompter.confirmSkipInNonInteractive({ + message: "Remove legacy gateway services (clawdbot/moltbot) now?", + initialValue: true, + }); + if (shouldRemove) { + const removed: string[] = []; + const failed: string[] = []; + for (const svc of legacyServices) { + if (svc.platform !== "darwin") { + failed.push(`${svc.label} (${svc.platform})`); + continue; + } + if (svc.scope !== "user") { + failed.push(`${svc.label} (${svc.scope})`); + continue; + } + const plistPath = extractDetailPath(svc.detail, "plist:"); + if (!plistPath) { + failed.push(`${svc.label} (missing plist path)`); + continue; + } + const dest = await cleanupLegacyLaunchdService({ + label: svc.label, + plistPath, + }); + removed.push(dest ? `${svc.label} -> ${dest}` : svc.label); + } + if (removed.length > 0) { + note(removed.map((line) => `- ${line}`).join("\n"), "Legacy gateway removed"); + } + if (failed.length > 0) { + note(failed.map((line) => `- ${line}`).join("\n"), "Legacy gateway cleanup skipped"); + } + if (removed.length > 0) { + runtime.log("Legacy gateway services removed. Installing OpenClaw gateway next."); + } + } + } + const cleanupHints = renderGatewayServiceCleanupHints(); if (cleanupHints.length > 0) { note(cleanupHints.map((hint) => `- ${hint}`).join("\n"), "Cleanup hints"); diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index 40d86bc7fb4..f89e84c4177 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -185,7 +185,7 @@ export async function doctorCommand( cfg = await maybeRepairSandboxImages(cfg, runtime, prompter); noteSandboxScopeWarnings(cfg); - await maybeScanExtraGatewayServices(options); + await maybeScanExtraGatewayServices(options, runtime, prompter); await maybeRepairGatewayServiceConfig(cfg, resolveMode(cfg), runtime, prompter); await noteMacLaunchAgentOverrides(); await noteMacLaunchctlGatewayEnvOverrides(cfg); diff --git a/src/commands/onboard-helpers.ts b/src/commands/onboard-helpers.ts index 9c23073d17e..0efa9909028 100644 --- a/src/commands/onboard-helpers.ts +++ b/src/commands/onboard-helpers.ts @@ -65,9 +65,11 @@ export function randomToken(): string { export function printWizardHeader(runtime: RuntimeEnv) { const header = [ "▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄", - "█████░█████░█████░█░░░█░█████░█░░░░░░███░░█░░░█", - "█░░░█░█░░░█░███░░░██░░█░█░░░░░█░░░░░█░░░█░█░█░█", - "█████░████░░█████░█░░██░█████░█████░█████░██░██", + "█████░█████░█████░█░░░█░█████░█░░░░░█████░█░░░█", + "█░░░█░█░░░█░█░░░░░██░░█░█░░░░░█░░░░░█░░░█░█░░░█", + "█░░░█░█████░████░░█░█░█░█░░░░░█░░░░░█████░█░█░█", + "█░░░█░█░░░░░█░░░░░█░░██░█░░░░░█░░░░░█░░░█░██░██", + "█████░█░░░░░█████░█░░░█░█████░█████░█░░░█░█░░░█", "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀", " 🦞 FRESH DAILY 🦞 ", " ", diff --git a/src/daemon/inspect.ts b/src/daemon/inspect.ts index 84f22bacf23..1018635ee85 100644 --- a/src/daemon/inspect.ts +++ b/src/daemon/inspect.ts @@ -16,13 +16,15 @@ export type ExtraGatewayService = { label: string; detail: string; scope: "user" | "system"; + marker?: "openclaw" | "clawdbot" | "moltbot"; + legacy?: boolean; }; export type FindExtraGatewayServicesOptions = { deep?: boolean; }; -const EXTRA_MARKERS = ["openclaw"]; +const EXTRA_MARKERS = ["openclaw", "clawdbot", "moltbot"] as const; const execFileAsync = promisify(execFile); export function renderGatewayServiceCleanupHints( @@ -56,9 +58,14 @@ function resolveHomeDir(env: Record): string { return home; } -function containsMarker(content: string): boolean { +type Marker = (typeof EXTRA_MARKERS)[number]; + +function detectMarker(content: string): Marker | null { const lower = content.toLowerCase(); - return EXTRA_MARKERS.some((marker) => lower.includes(marker)); + for (const marker of EXTRA_MARKERS) { + if (lower.includes(marker)) return marker; + } + return null; } function hasGatewayServiceMarker(content: string): boolean { @@ -111,6 +118,11 @@ function isIgnoredSystemdName(name: string): boolean { return name === resolveGatewaySystemdServiceName(); } +function isLegacyLabel(label: string): boolean { + const lower = label.toLowerCase(); + return lower.includes("clawdbot") || lower.includes("moltbot"); +} + async function scanLaunchdDir(params: { dir: string; scope: "user" | "system"; @@ -134,15 +146,18 @@ async function scanLaunchdDir(params: { } catch { continue; } - if (!containsMarker(contents)) continue; + const marker = detectMarker(contents); + if (!marker) continue; const label = tryExtractPlistLabel(contents) ?? labelFromName; if (isIgnoredLaunchdLabel(label)) continue; - if (isOpenClawGatewayLaunchdService(label, contents)) continue; + if (marker === "openclaw" && isOpenClawGatewayLaunchdService(label, contents)) continue; results.push({ platform: "darwin", label, detail: `plist: ${fullPath}`, scope: params.scope, + marker, + legacy: marker !== "openclaw" || isLegacyLabel(label), }); } @@ -172,13 +187,16 @@ async function scanSystemdDir(params: { } catch { continue; } - if (!containsMarker(contents)) continue; - if (isOpenClawGatewaySystemdService(name, contents)) continue; + const marker = detectMarker(contents); + if (!marker) continue; + if (marker === "openclaw" && isOpenClawGatewaySystemdService(name, contents)) continue; results.push({ platform: "linux", label: entry, detail: `unit: ${fullPath}`, scope: params.scope, + marker, + legacy: marker !== "openclaw", }); } @@ -336,15 +354,21 @@ export async function findExtraGatewayServices( if (isOpenClawGatewayTaskName(name)) continue; const lowerName = name.toLowerCase(); const lowerCommand = task.taskToRun?.toLowerCase() ?? ""; - const matches = EXTRA_MARKERS.some( - (marker) => lowerName.includes(marker) || lowerCommand.includes(marker), - ); - if (!matches) continue; + let marker: Marker | null = null; + for (const candidate of EXTRA_MARKERS) { + if (lowerName.includes(candidate) || lowerCommand.includes(candidate)) { + marker = candidate; + break; + } + } + if (!marker) continue; push({ platform: "win32", label: name, detail: task.taskToRun ? `task: ${name}, run: ${task.taskToRun}` : name, scope: "system", + marker, + legacy: marker !== "openclaw", }); } return results; diff --git a/ui/index.html b/ui/index.html index d42804f47b2..dc03f49115c 100644 --- a/ui/index.html +++ b/ui/index.html @@ -5,7 +5,9 @@ OpenClaw Control - + + + diff --git a/ui/public/apple-touch-icon.png b/ui/public/apple-touch-icon.png new file mode 100644 index 00000000000..71781843f85 Binary files /dev/null and b/ui/public/apple-touch-icon.png differ diff --git a/ui/public/favicon-32.png b/ui/public/favicon-32.png new file mode 100644 index 00000000000..563c79b0e6b Binary files /dev/null and b/ui/public/favicon-32.png differ diff --git a/ui/public/favicon.svg b/ui/public/favicon.svg new file mode 100644 index 00000000000..bcbc1e10cb4 --- /dev/null +++ b/ui/public/favicon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + +