From bb0332bfbf0c11e97c058ff31335163f3519658c Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 9 May 2026 08:00:16 +0800 Subject: [PATCH] fix(cli): guide lookup misses --- src/cli/mcp-cli.ts | 20 +++++++++++++++----- src/commands/agents.commands.bind.ts | 17 +++++++++++++---- src/commands/agents.commands.delete.ts | 9 +++++++-- src/commands/export-trajectory.ts | 13 ++++++++++--- src/commands/flows.ts | 15 +++++++++++---- src/commands/tasks.ts | 19 +++++++++++++------ 6 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/cli/mcp-cli.ts b/src/cli/mcp-cli.ts index 2b44835b3ed..fee3de54082 100644 --- a/src/cli/mcp-cli.ts +++ b/src/cli/mcp-cli.ts @@ -5,12 +5,14 @@ import { setConfiguredMcpServer, unsetConfiguredMcpServer, } from "../config/mcp-config.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { serveOpenClawChannelMcp } from "../mcp/channel-server.js"; import { defaultRuntime } from "../runtime.js"; import { normalizeLowercaseStringOrEmpty, normalizeStringifiedOptionalString, } from "../shared/string-coerce.js"; +import { formatCliCommand } from "./command-format.js"; import { resolveGatewayAuthOptions } from "./gateway-secret-options.js"; import { applyParentDefaultHelpAction } from "./program/parent-default-help.js"; @@ -52,7 +54,7 @@ export function registerMcpCli(program: Command) { claudeChannelMode !== "on" && claudeChannelMode !== "off" ) { - throw new Error("Invalid --claude-channel-mode value. Use auto, on, or off."); + throw new Error('Invalid --claude-channel-mode value. Use "auto", "on", or "off".'); } await serveOpenClawChannelMcp({ gatewayUrl: opts.url as string | undefined, @@ -62,7 +64,9 @@ export function registerMcpCli(program: Command) { verbose: Boolean(opts.verbose), }); } catch (err) { - defaultRuntime.error(String(err)); + defaultRuntime.error( + `MCP server failed to start: ${formatErrorMessage(err)}. Run ${formatCliCommand("openclaw mcp list")} to inspect configured servers.`, + ); defaultRuntime.exit(1); } }); @@ -82,7 +86,9 @@ export function registerMcpCli(program: Command) { } const names = Object.keys(loaded.mcpServers).toSorted(); if (names.length === 0) { - defaultRuntime.log(`No MCP servers configured in ${loaded.path}.`); + defaultRuntime.log( + `No MCP servers configured in ${loaded.path}. Add one with ${formatCliCommand('openclaw mcp set \'{"command":"uvx","args":["context7-mcp"]}\'')}.`, + ); return; } defaultRuntime.log(`MCP servers (${loaded.path}):`); @@ -103,7 +109,9 @@ export function registerMcpCli(program: Command) { } const value = name ? loaded.mcpServers[name] : loaded.mcpServers; if (name && !value) { - fail(`No MCP server named "${name}" in ${loaded.path}.`); + fail( + `No MCP server named "${name}" in ${loaded.path}. Run ${formatCliCommand("openclaw mcp list")} to see configured servers.`, + ); } if (opts.json) { printJson(value ?? {}); @@ -144,7 +152,9 @@ export function registerMcpCli(program: Command) { fail(result.error); } if (!result.removed) { - fail(`No MCP server named "${name}" in ${result.path}.`); + fail( + `No MCP server named "${name}" in ${result.path}. Run ${formatCliCommand("openclaw mcp list")} to see configured servers.`, + ); } defaultRuntime.log(`Removed MCP server "${name}" from ${result.path}.`); }); diff --git a/src/commands/agents.commands.bind.ts b/src/commands/agents.commands.bind.ts index cc7aaa139a9..b73ab64d9de 100644 --- a/src/commands/agents.commands.bind.ts +++ b/src/commands/agents.commands.bind.ts @@ -1,4 +1,5 @@ import { listAgentEntries, resolveDefaultAgentId } from "../agents/agent-scope.js"; +import { formatCliCommand } from "../cli/command-format.js"; import { isRouteBinding, listRouteBindings } from "../config/bindings.js"; import { replaceConfigFile } from "../config/config.js"; import { logConfigUpdated } from "../config/logging.js"; @@ -80,12 +81,16 @@ function resolveTargetAgentIdOrExit(params: { fallbackToDefault: true, }); if (!agentId) { - params.runtime.error("Unable to resolve agent id."); + params.runtime.error( + `Unable to resolve agent id. Run ${formatCliCommand("openclaw agents list")} to choose one.`, + ); params.runtime.exit(1); return null; } if (!hasAgent(params.cfg, agentId)) { - params.runtime.error(`Agent "${agentId}" not found.`); + params.runtime.error( + `Agent "${agentId}" not found. Run ${formatCliCommand("openclaw agents list")} to see configured agents.`, + ); params.runtime.exit(1); return null; } @@ -178,12 +183,16 @@ export async function agentsBindingsCommand( const filterAgentId = resolveAgentId(cfg, opts.agent?.trim()); if (opts.agent && !filterAgentId) { - runtime.error("Agent id is required."); + runtime.error( + `Agent id is required. Run ${formatCliCommand("openclaw agents list")} to choose one.`, + ); runtime.exit(1); return; } if (filterAgentId && !hasAgent(cfg, filterAgentId)) { - runtime.error(`Agent "${filterAgentId}" not found.`); + runtime.error( + `Agent "${filterAgentId}" not found. Run ${formatCliCommand("openclaw agents list")} to see configured agents.`, + ); runtime.exit(1); return; } diff --git a/src/commands/agents.commands.delete.ts b/src/commands/agents.commands.delete.ts index 7f73e7cab13..f0414bf392d 100644 --- a/src/commands/agents.commands.delete.ts +++ b/src/commands/agents.commands.delete.ts @@ -1,5 +1,6 @@ import { findOverlappingWorkspaceAgentIds } from "../agents/agent-delete-safety.js"; import { resolveAgentDir, resolveAgentWorkspaceDir } from "../agents/agent-scope.js"; +import { formatCliCommand } from "../cli/command-format.js"; import { replaceConfigFile } from "../config/config.js"; import { logConfigUpdated } from "../config/logging.js"; import { @@ -64,7 +65,9 @@ export async function agentsDeleteCommand( const input = opts.id?.trim(); if (!input) { - runtime.error("Agent id is required."); + runtime.error( + `Agent id is required. Run ${formatCliCommand("openclaw agents list")} to choose one.`, + ); runtime.exit(1); return; } @@ -80,7 +83,9 @@ export async function agentsDeleteCommand( } if (findAgentEntryIndex(listAgentEntries(cfg), agentId) < 0) { - runtime.error(`Agent "${agentId}" not found.`); + runtime.error( + `Agent "${agentId}" not found. Run ${formatCliCommand("openclaw agents list")} to see configured agents.`, + ); runtime.exit(1); return; } diff --git a/src/commands/export-trajectory.ts b/src/commands/export-trajectory.ts index 43ad2497c2a..f175602c948 100644 --- a/src/commands/export-trajectory.ts +++ b/src/commands/export-trajectory.ts @@ -1,4 +1,5 @@ import path from "node:path"; +import { formatCliCommand } from "../cli/command-format.js"; import { resolveDefaultSessionStorePath, resolveSessionFilePath, @@ -86,7 +87,9 @@ export async function exportTrajectoryCommand( } const sessionKey = resolvedOpts.sessionKey?.trim(); if (!sessionKey) { - runtime.error("--session-key is required"); + runtime.error( + `--session-key is required. Run ${formatCliCommand("openclaw sessions list")} to choose a session.`, + ); runtime.exit(1); return; } @@ -97,7 +100,9 @@ export async function exportTrajectoryCommand( const store = loadSessionStore(storePath, { skipCache: true }); const entry = store[sessionKey] as SessionEntry | undefined; if (!entry?.sessionId) { - runtime.error(`Session not found: ${sessionKey}`); + runtime.error( + `Session not found: ${sessionKey}. Run ${formatCliCommand("openclaw sessions list")} to see available sessions.`, + ); runtime.exit(1); return; } @@ -115,7 +120,9 @@ export async function exportTrajectoryCommand( return; } if (!(await pathExists(sessionFile))) { - runtime.error("Session file not found."); + runtime.error( + `Session file not found for ${sessionKey}. Run ${formatCliCommand("openclaw doctor")} to inspect session storage.`, + ); runtime.exit(1); return; } diff --git a/src/commands/flows.ts b/src/commands/flows.ts index d64d0bad47a..143813adcb6 100644 --- a/src/commands/flows.ts +++ b/src/commands/flows.ts @@ -1,3 +1,4 @@ +import { formatCliCommand } from "../cli/command-format.js"; import { getRuntimeConfig } from "../config/config.js"; import { info } from "../globals.js"; import type { RuntimeEnv } from "../runtime.js"; @@ -19,6 +20,10 @@ const MODE_PAD = 14; const REV_PAD = 6; const CTRL_PAD = 20; +function formatFlowLookupMiss(lookup: string): string { + return `TaskFlow not found: ${lookup}. Run ${formatCliCommand("openclaw tasks flow list")} to see recent flow ids.`; +} + function truncate(value: string, maxChars: number) { if (value.length <= maxChars) { return value; @@ -173,7 +178,9 @@ export async function flowsListCommand( runtime.log(info(`Status filter: ${statusFilter}`)); } if (flows.length === 0) { - runtime.log("No TaskFlows found."); + runtime.log( + `No TaskFlows found. Run ${formatCliCommand("openclaw tasks list")} to inspect standalone background tasks.`, + ); return; } const rich = isRich(); @@ -188,7 +195,7 @@ export async function flowsShowCommand( ) { const flow = resolveTaskFlowForLookupToken(opts.lookup); if (!flow) { - runtime.error(`TaskFlow not found: ${opts.lookup}`); + runtime.error(formatFlowLookupMiss(opts.lookup)); runtime.exit(1); return; } @@ -245,7 +252,7 @@ export async function flowsShowCommand( export async function flowsCancelCommand(opts: { lookup: string }, runtime: RuntimeEnv) { const flow = resolveTaskFlowForLookupToken(opts.lookup); if (!flow) { - runtime.error(`Flow not found: ${opts.lookup}`); + runtime.error(formatFlowLookupMiss(opts.lookup)); runtime.exit(1); return; } @@ -254,7 +261,7 @@ export async function flowsCancelCommand(opts: { lookup: string }, runtime: Runt flowId: flow.flowId, }); if (!result.found) { - runtime.error(result.reason ?? `Flow not found: ${opts.lookup}`); + runtime.error(result.reason ?? formatFlowLookupMiss(opts.lookup)); runtime.exit(1); return; } diff --git a/src/commands/tasks.ts b/src/commands/tasks.ts index c306d13311c..0afe94d7abd 100644 --- a/src/commands/tasks.ts +++ b/src/commands/tasks.ts @@ -1,3 +1,4 @@ +import { formatCliCommand } from "../cli/command-format.js"; import { getRuntimeConfig } from "../config/config.js"; import { resolveCronStorePath } from "../cron/store.js"; import type { RuntimeEnv } from "../runtime.js"; @@ -46,6 +47,10 @@ const RUN_PAD = 10; const info = theme.info; +function formatTaskLookupMiss(lookup: string): string { + return `Task not found: ${lookup}. Run ${formatCliCommand("openclaw tasks list")} to see recent task ids.`; +} + async function loadTaskCancelConfig() { return getRuntimeConfig(); } @@ -313,7 +318,9 @@ export async function tasksListCommand( runtime.log(info(`Status filter: ${statusFilter}`)); } if (tasks.length === 0) { - runtime.log("No background tasks found."); + runtime.log( + `No background tasks found. Run ${formatCliCommand("openclaw tasks audit")} to check for stale task state.`, + ); return; } const rich = isRich(); @@ -328,7 +335,7 @@ export async function tasksShowCommand( ) { const task = reconcileTaskLookupToken(opts.lookup); if (!task) { - runtime.error(`Task not found: ${opts.lookup}`); + runtime.error(formatTaskLookupMiss(opts.lookup)); runtime.exit(1); return; } @@ -374,7 +381,7 @@ export async function tasksNotifyCommand( ) { const task = reconcileTaskLookupToken(opts.lookup); if (!task) { - runtime.error(`Task not found: ${opts.lookup}`); + runtime.error(formatTaskLookupMiss(opts.lookup)); runtime.exit(1); return; } @@ -383,7 +390,7 @@ export async function tasksNotifyCommand( notifyPolicy: opts.notify, }); if (!updated) { - runtime.error(`Task not found: ${opts.lookup}`); + runtime.error(formatTaskLookupMiss(opts.lookup)); runtime.exit(1); return; } @@ -393,7 +400,7 @@ export async function tasksNotifyCommand( export async function tasksCancelCommand(opts: { lookup: string }, runtime: RuntimeEnv) { const task = reconcileTaskLookupToken(opts.lookup); if (!task) { - runtime.error(`Task not found: ${opts.lookup}`); + runtime.error(formatTaskLookupMiss(opts.lookup)); runtime.exit(1); return; } @@ -402,7 +409,7 @@ export async function tasksCancelCommand(opts: { lookup: string }, runtime: Runt taskId: task.taskId, }); if (!result.found) { - runtime.error(result.reason ?? `Task not found: ${opts.lookup}`); + runtime.error(result.reason ?? formatTaskLookupMiss(opts.lookup)); runtime.exit(1); return; }