From 3e82cbd55bca40780b3f2a374b638672e8ef612f Mon Sep 17 00:00:00 2001 From: Benjamin Jesuiter Date: Mon, 2 Feb 2026 18:57:16 +0100 Subject: [PATCH] Memory: parse quoted qmd command --- src/memory/backend-config.test.ts | 14 +++++++ src/memory/backend-config.ts | 6 ++- src/utils/shell-argv.ts | 62 +++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/utils/shell-argv.ts diff --git a/src/memory/backend-config.test.ts b/src/memory/backend-config.test.ts index 4e9db881c..aa81ab276 100644 --- a/src/memory/backend-config.test.ts +++ b/src/memory/backend-config.test.ts @@ -30,6 +30,20 @@ describe("resolveMemoryBackendConfig", () => { expect(resolved.qmd?.update.intervalMs).toBeGreaterThan(0); }); + it("parses quoted qmd command paths", () => { + const cfg = { + agents: { defaults: { workspace: "/tmp/memory-test" } }, + memory: { + backend: "qmd", + qmd: { + command: '"/Applications/QMD Tools/qmd" --flag', + }, + }, + } as MoltbotConfig; + const resolved = resolveMemoryBackendConfig({ cfg, agentId: "main" }); + expect(resolved.qmd?.command).toBe("/Applications/QMD Tools/qmd"); + }); + it("resolves custom paths relative to workspace", () => { const cfg = { agents: { diff --git a/src/memory/backend-config.ts b/src/memory/backend-config.ts index 2510d16c3..100c6889a 100644 --- a/src/memory/backend-config.ts +++ b/src/memory/backend-config.ts @@ -11,6 +11,7 @@ import type { } from "../config/types.memory.js"; import type { SessionSendPolicyConfig } from "../config/types.base.js"; import { resolveUserPath } from "../utils.js"; +import { splitShellArgs } from "../utils/shell-argv.js"; export type ResolvedMemoryBackendConfig = { backend: MemoryBackend; @@ -232,8 +233,11 @@ export function resolveMemoryBackendConfig(params: { ...resolveCustomPaths(qmdCfg?.paths, workspaceDir, nameSet), ]; + const rawCommand = qmdCfg?.command?.trim() || "qmd"; + const parsedCommand = splitShellArgs(rawCommand); + const command = parsedCommand?.[0] || rawCommand.split(/\s+/)[0] || "qmd"; const resolved: ResolvedQmdConfig = { - command: (qmdCfg?.command?.trim() || "qmd").split(/\s+/)[0] || "qmd", + command, collections, includeDefaultMemory, sessions: resolveSessionConfig(qmdCfg?.sessions, workspaceDir), diff --git a/src/utils/shell-argv.ts b/src/utils/shell-argv.ts new file mode 100644 index 000000000..e52ca16bb --- /dev/null +++ b/src/utils/shell-argv.ts @@ -0,0 +1,62 @@ +export function splitShellArgs(raw: string): string[] | null { + const tokens: string[] = []; + let buf = ""; + let inSingle = false; + let inDouble = false; + let escaped = false; + + const pushToken = () => { + if (buf.length > 0) { + tokens.push(buf); + buf = ""; + } + }; + + for (let i = 0; i < raw.length; i += 1) { + const ch = raw[i]; + if (escaped) { + buf += ch; + escaped = false; + continue; + } + if (!inSingle && !inDouble && ch === "\\") { + escaped = true; + continue; + } + if (inSingle) { + if (ch === "'") { + inSingle = false; + } else { + buf += ch; + } + continue; + } + if (inDouble) { + if (ch === '"') { + inDouble = false; + } else { + buf += ch; + } + continue; + } + if (ch === "'") { + inSingle = true; + continue; + } + if (ch === '"') { + inDouble = true; + continue; + } + if (/\s/.test(ch)) { + pushToken(); + continue; + } + buf += ch; + } + + if (escaped || inSingle || inDouble) { + return null; + } + pushToken(); + return tokens; +}