fix(vault): pass vault= as top-level obsidian cli option

The vault=<name> argument was appended after the subcommand
(obsidian bases vault=Work), but the Obsidian CLI only honors it as a
top-level option before the subcommand. Placed after, the subcommand
consumed/ignored it and the command bound to whichever vault had window
focus, so vault://<name>?op=... silently queried the wrong vault.

Prepend the vault arg in #runCli and #vaultInfo so it precedes the
subcommand (obsidian vault=Work bases), matching the CLI's documented
top-level-option semantics.

Fixes #7771
This commit is contained in:
roboomp
2026-08-06 01:36:01 +00:00
parent 1e492d6ff9
commit f5f7b09dd7
3 changed files with 51 additions and 27 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed `vault://<name>?op=...` commands targeting the focused/most-recently-active vault instead of the named one. The `vault=<name>` argument was appended after the Obsidian CLI subcommand (`obsidian bases vault=Work`), but the CLI only honors it as a top-level option before the subcommand (`obsidian vault=Work bases`); it is now prepended so the named vault is queried (and opened) regardless of window focus ([#7771](https://github.com/can1357/oh-my-pi/issues/7771)).
## [17.2.9] - 2026-08-05
### Breaking Changes
@@ -787,7 +787,7 @@ export class VaultProtocolHandler implements ProtocolHandler {
const cacheKey = parsed.ref.active ? "_" : (parsed.ref.vault ?? "_");
let cliInfo = cachedVaultInfo.get(cacheKey);
if (cliInfo === undefined) {
const result = await this.#spawn(["vault", "info", ...this.#vaultCliArg(parsed.ref)], context);
const result = await this.#spawn([...this.#vaultCliArg(parsed.ref), "vault", "info"], context);
assertCliSuccess("vault info", result);
cliInfo = result.stdout.trim();
cachedVaultInfo.set(cacheKey, cliInfo);
@@ -926,7 +926,7 @@ export class VaultProtocolHandler implements ProtocolHandler {
context?: ResolveContext,
): Promise<InternalResource> {
const invocation = buildObsidianCliInvocation(parsed);
const args = [...invocation.args, ...this.#vaultCliArg(parsed.ref)];
const args = [...this.#vaultCliArg(parsed.ref), ...invocation.args];
const result = await this.#spawn(args, context);
assertCliSuccess(invocation.opLabel, result);
return {
@@ -183,6 +183,26 @@ describe("VaultProtocolHandler", () => {
expect(spawnSpy.mock.calls[0][1]).toEqual(["vault", "info", "path"]);
});
});
it("targets a named vault by prepending vault= before the vault info subcommand", async () => {
await withTempDir(async tempDir => {
const root = path.join(tempDir, "work-vault");
await fs.mkdir(root, { recursive: true });
await Bun.write(path.join(root, "note.md"), "note");
VaultProtocolHandler.setVaultDirectoryForTests({ Work: root });
const spawnSpy = vi.spyOn(vaultProtocol, "spawnObsidian").mockResolvedValue({
stdout: `name\tWork\npath\t${root}\n`,
stderr: "",
exitCode: 0,
});
const handler = testHandler(vaultProtocol.spawnObsidian);
await handler.resolve(resourceUrl("vault://Work"));
expect(spawnSpy).toHaveBeenCalledTimes(1);
expect(spawnSpy.mock.calls[0][1]).toEqual(["vault=Work", "vault", "info"]);
});
});
it("writes files through the protocol hook and resolves cached vault paths for edit plumbing", async () => {
await withTempDir(async tempDir => {
const root = path.join(tempDir, "vault");
@@ -290,31 +310,31 @@ describe("VaultProtocolHandler", () => {
}
expect(calls).toEqual({
outline: ["outline", "path=Note.md", "format=md", "vault=Work"],
backlinks: ["backlinks", "path=Note.md", "counts", "format=tsv", "vault=Work"],
links: ["links", "path=Note.md", "vault=Work"],
fileTags: ["tags", "path=Note.md", "counts", "format=json", "vault=Work"],
fileProperties: ["properties", "path=Note.md", "format=yaml", "vault=Work"],
fileTasks: ["tasks", "path=Note.md", "verbose", "format=json", "vault=Work"],
wordcount: ["wordcount", "path=Note.md", "vault=Work"],
history: ["history", "path=Note.md", "vault=Work"],
base: ["base:query", "path=Note.md", "view=Main", "format=md", "vault=Work"],
search: ["search:context", "query=plan", "path=Folder", "limit=5", "case", "format=json", "vault=Work"],
daily: ["daily:read", "vault=Work"],
dailyPath: ["daily:path", "vault=Work"],
vaultTags: ["tags", "counts", "format=json", "vault=Work"],
tag: ["tag", "name=#todo", "verbose", "vault=Work"],
vaultTasks: ["tasks", "todo", "verbose", "format=json", "vault=Work"],
orphans: ["orphans", "vault=Work"],
unresolved: ["unresolved", "counts", "verbose", "format=json", "vault=Work"],
deadends: ["deadends", "vault=Work"],
bases: ["bases", "vault=Work"],
bookmarks: ["bookmarks", "verbose", "format=json", "vault=Work"],
recents: ["recents", "vault=Work"],
templates: ["templates", "vault=Work"],
aliases: ["aliases", "verbose", "format=json", "vault=Work"],
vaultProperties: ["properties", "counts", "format=yaml", "vault=Work"],
property: ["property:read", "name=status", "path=Note.md", "vault=Work"],
outline: ["vault=Work", "outline", "path=Note.md", "format=md"],
backlinks: ["vault=Work", "backlinks", "path=Note.md", "counts", "format=tsv"],
links: ["vault=Work", "links", "path=Note.md"],
fileTags: ["vault=Work", "tags", "path=Note.md", "counts", "format=json"],
fileProperties: ["vault=Work", "properties", "path=Note.md", "format=yaml"],
fileTasks: ["vault=Work", "tasks", "path=Note.md", "verbose", "format=json"],
wordcount: ["vault=Work", "wordcount", "path=Note.md"],
history: ["vault=Work", "history", "path=Note.md"],
base: ["vault=Work", "base:query", "path=Note.md", "view=Main", "format=md"],
search: ["vault=Work", "search:context", "query=plan", "path=Folder", "limit=5", "case", "format=json"],
daily: ["vault=Work", "daily:read"],
dailyPath: ["vault=Work", "daily:path"],
vaultTags: ["vault=Work", "tags", "counts", "format=json"],
tag: ["vault=Work", "tag", "name=#todo", "verbose"],
vaultTasks: ["vault=Work", "tasks", "todo", "verbose", "format=json"],
orphans: ["vault=Work", "orphans"],
unresolved: ["vault=Work", "unresolved", "counts", "verbose", "format=json"],
deadends: ["vault=Work", "deadends"],
bases: ["vault=Work", "bases"],
bookmarks: ["vault=Work", "bookmarks", "verbose", "format=json"],
recents: ["vault=Work", "recents"],
templates: ["vault=Work", "templates"],
aliases: ["vault=Work", "aliases", "verbose", "format=json"],
vaultProperties: ["vault=Work", "properties", "counts", "format=yaml"],
property: ["vault=Work", "property:read", "name=status", "path=Note.md"],
});
});