From 559b5eab71dcd000c592033661af1bde7500dc43 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 25 Feb 2026 01:41:49 +0000 Subject: [PATCH] fix(cli): support --query in memory search command (#25904) --- CHANGELOG.md | 1 + src/cli/memory-cli.test.ts | 43 ++++++++++++++++++++++++++++++++++++++ src/cli/memory-cli.ts | 14 +++++++++++-- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f227da4c9e..a528cbfcb71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ Docs: https://docs.openclaw.ai - macOS/Menu bar: stop reusing the injector delegate for the "Usage cost (30 days)" submenu to prevent recursive submenu injection loops when opening cost history. (#25341) Thanks @yingchunbai. - Control UI/Chat images: route image-click opens through a shared safe-open helper (allowing only safe URL schemes) and open new tabs with opener isolation to block tabnabbing. (#18685, #25444, #25847) Thanks @Mariana-Codebase and @shakkernerd. - CLI/Doctor: correct stale recovery hints to use valid commands (`openclaw gateway status --deep` and `openclaw configure --section model`). (#24485) Thanks @chilu18. +- CLI/Memory search: accept `--query ` for `openclaw memory search` (while keeping positional query support), and emit a clear error when neither form is provided. (#25904, #25857) Thanks @niceysam and @stakeswky. - Security/Sandbox: canonicalize bind-mount source paths via existing-ancestor realpath so symlink-parent + non-existent-leaf paths cannot bypass allowed-source-roots or blocked-path checks. Thanks @tdjackey. - Doctor/Plugins: auto-enable now resolves third-party channel plugins by manifest plugin id (not channel id), preventing invalid `plugins.entries.` writes when ids differ. (#25275) Thanks @zerone0x. diff --git a/src/cli/memory-cli.test.ts b/src/cli/memory-cli.test.ts index 8a83bc5e906..3d6dfa7d2a2 100644 --- a/src/cli/memory-cli.test.ts +++ b/src/cli/memory-cli.test.ts @@ -382,6 +382,49 @@ describe("memory cli", () => { expect(close).toHaveBeenCalled(); }); + it("accepts --query for memory search", async () => { + const close = vi.fn(async () => {}); + const search = vi.fn(async () => []); + mockManager({ search, close }); + + const log = spyRuntimeLogs(); + await runMemoryCli(["search", "--query", "deployment notes"]); + + expect(search).toHaveBeenCalledWith("deployment notes", { + maxResults: undefined, + minScore: undefined, + }); + expect(log).toHaveBeenCalledWith("No matches."); + expect(close).toHaveBeenCalled(); + expect(process.exitCode).toBeUndefined(); + }); + + it("prefers --query when positional and flag are both provided", async () => { + const close = vi.fn(async () => {}); + const search = vi.fn(async () => []); + mockManager({ search, close }); + + spyRuntimeLogs(); + await runMemoryCli(["search", "positional", "--query", "flagged"]); + + expect(search).toHaveBeenCalledWith("flagged", { + maxResults: undefined, + minScore: undefined, + }); + expect(close).toHaveBeenCalled(); + }); + + it("fails when neither positional query nor --query is provided", async () => { + const error = spyRuntimeErrors(); + await runMemoryCli(["search"]); + + expect(error).toHaveBeenCalledWith( + "Missing search query. Provide a positional query or use --query .", + ); + expect(getMemorySearchManager).not.toHaveBeenCalled(); + expect(process.exitCode).toBe(1); + }); + it("prints search results as json when requested", async () => { const close = vi.fn(async () => {}); const search = vi.fn(async () => [ diff --git a/src/cli/memory-cli.ts b/src/cli/memory-cli.ts index 6449653f8ac..f530d5b510e 100644 --- a/src/cli/memory-cli.ts +++ b/src/cli/memory-cli.ts @@ -702,19 +702,29 @@ export function registerMemoryCli(program: Command) { memory .command("search") .description("Search memory files") - .argument("", "Search query") + .argument("[query]", "Search query") + .option("--query ", "Search query (alternative to positional argument)") .option("--agent ", "Agent id (default: default agent)") .option("--max-results ", "Max results", (value: string) => Number(value)) .option("--min-score ", "Minimum score", (value: string) => Number(value)) .option("--json", "Print JSON") .action( async ( - query: string, + queryArg: string | undefined, opts: MemoryCommandOptions & { + query?: string; maxResults?: number; minScore?: number; }, ) => { + const query = opts.query ?? queryArg; + if (!query) { + defaultRuntime.error( + "Missing search query. Provide a positional query or use --query .", + ); + process.exitCode = 1; + return; + } const cfg = loadConfig(); const agentId = resolveAgent(cfg, opts.agent); await withMemoryManagerForAgent({