@jayjiang/byoao 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import fs from "fs-extra";
2
2
  import path from "node:path";
3
+ import { detectVaultContext } from "../vault/vault-detect.js";
3
4
  /**
4
5
  * Read AGENT.md from the current working directory.
5
6
  * Returns the content if found, null otherwise.
@@ -22,13 +23,30 @@ export function readAgentMdFromCwd() {
22
23
  return null;
23
24
  }
24
25
  /**
25
- * Hook: inject AGENT.md into system prompt.
26
+ * Hook: inject AGENT.md and vault retrieval context into system prompt.
26
27
  * Mutating pattern — modifies output.system in place.
27
28
  */
28
29
  export async function systemTransformHook(_input, output) {
30
+ // 1. Inject AGENT.md content
29
31
  const agentMd = readAgentMdFromCwd();
30
32
  if (agentMd) {
31
33
  output.system.push(`\n---\n## BYOAO Vault Context (from AGENT.md)\n\n${agentMd}`);
32
34
  }
35
+ // 2. Inject vault retrieval tool guidance
36
+ const vaultPath = detectVaultContext(process.cwd());
37
+ if (vaultPath) {
38
+ output.system.push([
39
+ "\n---",
40
+ "## BYOAO Vault Retrieval Context",
41
+ "",
42
+ `You are in a BYOAO Obsidian vault at: ${vaultPath}`,
43
+ "For vault knowledge queries (notes, tags, links, properties, graph health), prefer the BYOAO vault tools:",
44
+ "- byoao_search_vault — text search across vault notes",
45
+ "- byoao_note_read — read a specific note by name",
46
+ "- byoao_graph_health — find orphans, unresolved links, dead ends",
47
+ "",
48
+ "Use grep/rg only for source code searches or when BYOAO tools return runtime_unavailable.",
49
+ ].join("\n"));
50
+ }
33
51
  }
34
52
  //# sourceMappingURL=system-transform.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"system-transform.js","sourceRoot":"","sources":["../../src/hooks/system-transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC;KAC3C,CAAC;IAEF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,OAAO,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAA8C,EAC9C,MAA4B;IAE5B,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACrC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,CAAC,MAAM,CAAC,IAAI,CAChB,oDAAoD,OAAO,EAAE,CAC9D,CAAC;IACJ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"system-transform.js","sourceRoot":"","sources":["../../src/hooks/system-transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC;KAC3C,CAAC;IAEF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,OAAO,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAA8C,EAC9C,MAA4B;IAE5B,6BAA6B;IAC7B,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACrC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,CAAC,MAAM,CAAC,IAAI,CAChB,oDAAoD,OAAO,EAAE,CAC9D,CAAC;IACJ,CAAC;IAED,0CAA0C;IAC1C,MAAM,SAAS,GAAG,kBAAkB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACpD,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,CAAC,MAAM,CAAC,IAAI,CAChB;YACE,OAAO;YACP,kCAAkC;YAClC,EAAE;YACF,yCAAyC,SAAS,EAAE;YACpD,2GAA2G;YAC3G,uDAAuD;YACvD,kDAAkD;YAClD,kEAAkE;YAClE,EAAE;YACF,2FAA2F;SAC5F,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACJ,CAAC;AACH,CAAC"}
package/dist/index.js CHANGED
@@ -8,6 +8,9 @@ import { byoao_add_glossary_term } from "./tools/add-glossary-term.js";
8
8
  import { byoao_vault_status } from "./tools/vault-status.js";
9
9
  import { byoao_vault_doctor } from "./tools/vault-doctor.js";
10
10
  import { byoao_switch_provider } from "./tools/switch-provider.js";
11
+ import { byoao_search_vault } from "./tools/search-vault.js";
12
+ import { byoao_note_read } from "./tools/note-read.js";
13
+ import { byoao_graph_health } from "./tools/graph-health.js";
11
14
  import { systemTransformHook } from "./hooks/system-transform.js";
12
15
  import { getIdleSuggestion } from "./hooks/idle-suggestions.js";
13
16
  const BYOAOPlugin = async (ctx) => {
@@ -22,6 +25,9 @@ const BYOAOPlugin = async (ctx) => {
22
25
  byoao_vault_status,
23
26
  byoao_vault_doctor,
24
27
  byoao_switch_provider,
28
+ byoao_search_vault,
29
+ byoao_note_read,
30
+ byoao_graph_health,
25
31
  },
26
32
  "experimental.chat.system.transform": systemTransformHook,
27
33
  event: async ({ event }) => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,sDAAsD;AACtD,kEAAkE;AAGlE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhE,MAAM,WAAW,GAAW,KAAK,EAAE,GAAG,EAAE,EAAE;IACxC,kEAAkE;IAClE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IAEvB,OAAO;QACL,IAAI,EAAE;YACJ,gBAAgB;YAChB,gBAAgB;YAChB,iBAAiB;YACjB,uBAAuB;YACvB,kBAAkB;YAClB,kBAAkB;YAClB,qBAAqB;SACtB;QACD,oCAAoC,EAAE,mBAAmB;QACzD,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;YACzB,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;gBAClC,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;gBACvC,IAAI,UAAU,EAAE,CAAC;oBACf,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;wBACnB,IAAI,EAAE;4BACJ,KAAK,EAAE,OAAO;4BACd,OAAO,EAAE,UAAU;4BACnB,OAAO,EAAE,MAAM;4BACf,QAAQ,EAAE,IAAI;yBACf;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,sDAAsD;AACtD,kEAAkE;AAGlE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhE,MAAM,WAAW,GAAW,KAAK,EAAE,GAAG,EAAE,EAAE;IACxC,kEAAkE;IAClE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IAEvB,OAAO;QACL,IAAI,EAAE;YACJ,gBAAgB;YAChB,gBAAgB;YAChB,iBAAiB;YACjB,uBAAuB;YACvB,kBAAkB;YAClB,kBAAkB;YAClB,qBAAqB;YACrB,kBAAkB;YAClB,eAAe;YACf,kBAAkB;SACnB;QACD,oCAAoC,EAAE,mBAAmB;QACzD,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;YACzB,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;gBAClC,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;gBACvC,IAAI,UAAU,EAAE,CAAC;oBACf,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;wBACnB,IAAI,EAAE;4BACJ,KAAK,EAAE,OAAO;4BACd,OAAO,EAAE,UAAU;4BACnB,OAAO,EAAE,MAAM;4BACf,QAAQ,EAAE,IAAI;yBACf;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { tool } from "@opencode-ai/plugin/tool";
2
+ import { getGraphHealth } from "../vault/graph-health.js";
3
+ export const byoao_graph_health = tool({
4
+ description: "Diagnose Obsidian vault graph health — find orphan notes (no links in or out), unresolved links (broken wikilinks), and dead-end notes. Uses Obsidian CLI for vault-aware analysis.",
5
+ args: {
6
+ vaultPath: tool.schema.string().describe("Absolute path to the Obsidian vault"),
7
+ check: tool.schema
8
+ .enum(["all", "orphans", "unresolved", "deadends"])
9
+ .optional()
10
+ .describe("Which check to run: 'all' (default), 'orphans', 'unresolved', or 'deadends'"),
11
+ limit: tool.schema
12
+ .number()
13
+ .optional()
14
+ .describe("Maximum number of results to return (default: 20)"),
15
+ },
16
+ async execute(args) {
17
+ const result = await getGraphHealth({
18
+ vaultPath: args.vaultPath,
19
+ check: args.check,
20
+ limit: args.limit,
21
+ });
22
+ return JSON.stringify(result, null, 2);
23
+ },
24
+ });
25
+ //# sourceMappingURL=graph-health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-health.js","sourceRoot":"","sources":["../../src/tools/graph-health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;IACrC,WAAW,EACT,qLAAqL;IACvL,IAAI,EAAE;QACJ,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAC/E,KAAK,EAAE,IAAI,CAAC,MAAM;aACf,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;aAClD,QAAQ,EAAE;aACV,QAAQ,CACP,6EAA6E,CAC9E;QACH,KAAK,EAAE,IAAI,CAAC,MAAM;aACf,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,mDAAmD,CAAC;KACjE;IACD,KAAK,CAAC,OAAO,CAAC,IAAI;QAChB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;YAClC,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAkE;YAC9E,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { tool } from "@opencode-ai/plugin/tool";
2
+ import { readNote } from "../vault/note-read.js";
3
+ export const byoao_note_read = tool({
4
+ description: "Read a specific note from an Obsidian vault by name. Uses Obsidian CLI to resolve and read the note. Preferred over cat/Read for Obsidian vault notes.",
5
+ args: {
6
+ vaultPath: tool.schema.string().describe("Absolute path to the Obsidian vault"),
7
+ file: tool.schema
8
+ .string()
9
+ .describe("Note name to read (without .md extension, e.g. 'Refund Automation')"),
10
+ },
11
+ async execute(args) {
12
+ const result = await readNote({
13
+ vaultPath: args.vaultPath,
14
+ file: args.file,
15
+ });
16
+ return JSON.stringify(result, null, 2);
17
+ },
18
+ });
19
+ //# sourceMappingURL=note-read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"note-read.js","sourceRoot":"","sources":["../../src/tools/note-read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;IAClC,WAAW,EACT,wJAAwJ;IAC1J,IAAI,EAAE;QACJ,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAC/E,IAAI,EAAE,IAAI,CAAC,MAAM;aACd,MAAM,EAAE;aACR,QAAQ,CAAC,qEAAqE,CAAC;KACnF;IACD,KAAK,CAAC,OAAO,CAAC,IAAI;QAChB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC;YAC5B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { tool } from "@opencode-ai/plugin/tool";
2
+ import { searchVault } from "../vault/search-vault.js";
3
+ export const byoao_search_vault = tool({
4
+ description: "Search an Obsidian vault for notes matching a text query. Uses Obsidian CLI search:context for vault-aware results. Preferred over grep/rg for Obsidian vault knowledge queries about notes, tags, and content.",
5
+ args: {
6
+ vaultPath: tool.schema.string().describe("Absolute path to the Obsidian vault"),
7
+ query: tool.schema.string().describe("Text query to search for in vault notes"),
8
+ limit: tool.schema
9
+ .number()
10
+ .optional()
11
+ .describe("Maximum number of results to return (default: 20)"),
12
+ },
13
+ async execute(args) {
14
+ const result = await searchVault({
15
+ vaultPath: args.vaultPath,
16
+ query: args.query,
17
+ limit: args.limit,
18
+ });
19
+ return JSON.stringify(result, null, 2);
20
+ },
21
+ });
22
+ //# sourceMappingURL=search-vault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-vault.js","sourceRoot":"","sources":["../../src/tools/search-vault.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;IACrC,WAAW,EACT,iNAAiN;IACnN,IAAI,EAAE;QACJ,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAC/E,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;QAC/E,KAAK,EAAE,IAAI,CAAC,MAAM;aACf,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,mDAAmD,CAAC;KACjE;IACD,KAAK,CAAC,OAAO,CAAC,IAAI;QAChB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC;YAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ vi.mock("../obsidian-cli.js", () => ({
3
+ isObsidianCliAvailable: vi.fn(),
4
+ execObsidianCmd: vi.fn(),
5
+ }));
6
+ import { getGraphHealth } from "../graph-health.js";
7
+ import { isObsidianCliAvailable, execObsidianCmd } from "../obsidian-cli.js";
8
+ const mockIsAvailable = vi.mocked(isObsidianCliAvailable);
9
+ const mockExecCmd = vi.mocked(execObsidianCmd);
10
+ beforeEach(() => {
11
+ vi.resetAllMocks();
12
+ });
13
+ describe("getGraphHealth", () => {
14
+ it("returns runtime_unavailable when CLI is not available", async () => {
15
+ mockIsAvailable.mockReturnValue(false);
16
+ const result = await getGraphHealth({ vaultPath: "/vault" });
17
+ expect(result.status).toBe("runtime_unavailable");
18
+ expect(result.mode).toBe("graph-health");
19
+ expect(result.diagnostics).toContain("Obsidian CLI not available");
20
+ });
21
+ it("runs all three checks by default", async () => {
22
+ mockIsAvailable.mockReturnValue(true);
23
+ mockExecCmd.mockReturnValue({ success: true, output: "" });
24
+ await getGraphHealth({ vaultPath: "/vault" });
25
+ expect(mockExecCmd).toHaveBeenCalledTimes(3);
26
+ expect(mockExecCmd).toHaveBeenCalledWith(["orphans", "--vault", "/vault"]);
27
+ expect(mockExecCmd).toHaveBeenCalledWith(["unresolved", "--vault", "/vault"]);
28
+ expect(mockExecCmd).toHaveBeenCalledWith(["deadends", "--vault", "/vault"]);
29
+ });
30
+ it("runs only the specified check", async () => {
31
+ mockIsAvailable.mockReturnValue(true);
32
+ mockExecCmd.mockReturnValue({ success: true, output: "" });
33
+ await getGraphHealth({ vaultPath: "/vault", check: "orphans" });
34
+ expect(mockExecCmd).toHaveBeenCalledTimes(1);
35
+ expect(mockExecCmd).toHaveBeenCalledWith(["orphans", "--vault", "/vault"]);
36
+ });
37
+ it("returns no_results when vault is healthy", async () => {
38
+ mockIsAvailable.mockReturnValue(true);
39
+ mockExecCmd.mockReturnValue({ success: true, output: "" });
40
+ const result = await getGraphHealth({ vaultPath: "/vault" });
41
+ expect(result.status).toBe("no_results");
42
+ expect(result.results).toHaveLength(0);
43
+ expect(result.summary).toContain("No issues");
44
+ });
45
+ it("parses orphan results into structured items", async () => {
46
+ mockIsAvailable.mockReturnValue(true);
47
+ mockExecCmd.mockImplementation((args) => {
48
+ if (args[0] === "orphans") {
49
+ return {
50
+ success: true,
51
+ output: "Projects/Abandoned.md\nInbox/Random.md",
52
+ };
53
+ }
54
+ return { success: true, output: "" };
55
+ });
56
+ const result = await getGraphHealth({ vaultPath: "/vault" });
57
+ expect(result.status).toBe("ok");
58
+ const orphans = result.results.filter((r) => r.metadata?.check === "orphans");
59
+ expect(orphans).toHaveLength(2);
60
+ expect(orphans[0].title).toBe("Abandoned");
61
+ expect(orphans[0].path).toBe("Projects/Abandoned.md");
62
+ });
63
+ it("parses unresolved links into structured items", async () => {
64
+ mockIsAvailable.mockReturnValue(true);
65
+ mockExecCmd.mockImplementation((args) => {
66
+ if (args[0] === "unresolved") {
67
+ return { success: true, output: "Missing Note\nAnother Missing" };
68
+ }
69
+ return { success: true, output: "" };
70
+ });
71
+ const result = await getGraphHealth({ vaultPath: "/vault" });
72
+ const unresolved = result.results.filter((r) => r.metadata?.check === "unresolved");
73
+ expect(unresolved).toHaveLength(2);
74
+ expect(unresolved[0].title).toBe("Missing Note");
75
+ });
76
+ it("continues when one check fails but others succeed", async () => {
77
+ mockIsAvailable.mockReturnValue(true);
78
+ mockExecCmd.mockImplementation((args) => {
79
+ if (args[0] === "orphans") {
80
+ return { success: false, output: "", error: "timeout" };
81
+ }
82
+ return { success: true, output: "Inbox/note.md" };
83
+ });
84
+ const result = await getGraphHealth({ vaultPath: "/vault" });
85
+ // Should still return results from successful checks
86
+ expect(result.results.length).toBeGreaterThan(0);
87
+ expect(result.diagnostics).toContain("orphans check failed: timeout");
88
+ });
89
+ it("respects the limit parameter", async () => {
90
+ mockIsAvailable.mockReturnValue(true);
91
+ const lines = Array.from({ length: 30 }, (_, i) => `Notes/note${i}.md`);
92
+ mockExecCmd.mockReturnValue({ success: true, output: lines.join("\n") });
93
+ const result = await getGraphHealth({
94
+ vaultPath: "/vault",
95
+ check: "orphans",
96
+ limit: 5,
97
+ });
98
+ expect(result.results).toHaveLength(5);
99
+ expect(result.truncated).toBe(true);
100
+ });
101
+ });
102
+ //# sourceMappingURL=graph-health.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-health.test.js","sourceRoot":"","sources":["../../../src/vault/__tests__/graph-health.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ vi.mock("../obsidian-cli.js", () => ({
3
+ isObsidianCliAvailable: vi.fn(),
4
+ execObsidianCmd: vi.fn(),
5
+ }));
6
+ import { readNote } from "../note-read.js";
7
+ import { isObsidianCliAvailable, execObsidianCmd } from "../obsidian-cli.js";
8
+ const mockIsAvailable = vi.mocked(isObsidianCliAvailable);
9
+ const mockExecCmd = vi.mocked(execObsidianCmd);
10
+ beforeEach(() => {
11
+ vi.resetAllMocks();
12
+ });
13
+ describe("readNote", () => {
14
+ it("returns runtime_unavailable when CLI is not available", async () => {
15
+ mockIsAvailable.mockReturnValue(false);
16
+ const result = await readNote({ vaultPath: "/vault", file: "MyNote" });
17
+ expect(result.status).toBe("runtime_unavailable");
18
+ expect(result.mode).toBe("read");
19
+ expect(result.diagnostics).toContain("Obsidian CLI not available");
20
+ });
21
+ it("returns runtime_unavailable when CLI command fails", async () => {
22
+ mockIsAvailable.mockReturnValue(true);
23
+ mockExecCmd.mockReturnValue({
24
+ success: false,
25
+ output: "",
26
+ error: "Note not found",
27
+ });
28
+ const result = await readNote({ vaultPath: "/vault", file: "Missing" });
29
+ expect(result.status).toBe("runtime_unavailable");
30
+ expect(result.diagnostics[0]).toContain("Note not found");
31
+ });
32
+ it("returns note content as a single result item", async () => {
33
+ mockIsAvailable.mockReturnValue(true);
34
+ mockExecCmd.mockReturnValue({
35
+ success: true,
36
+ output: "---\ntype: project\ntags: [active]\n---\n\n# Refund Automation\n\nProject details here.",
37
+ });
38
+ const result = await readNote({ vaultPath: "/vault", file: "Refund Automation" });
39
+ expect(result.status).toBe("ok");
40
+ expect(result.results).toHaveLength(1);
41
+ expect(result.results[0].title).toBe("Refund Automation");
42
+ expect(result.results[0].file).toBe("Refund Automation");
43
+ expect(result.results[0].snippet).toContain("Project details here");
44
+ });
45
+ it("returns no_results when CLI returns empty output", async () => {
46
+ mockIsAvailable.mockReturnValue(true);
47
+ mockExecCmd.mockReturnValue({ success: true, output: "" });
48
+ const result = await readNote({ vaultPath: "/vault", file: "Empty" });
49
+ expect(result.status).toBe("no_results");
50
+ expect(result.results).toHaveLength(0);
51
+ });
52
+ it("passes vault path and file to CLI correctly", async () => {
53
+ mockIsAvailable.mockReturnValue(true);
54
+ mockExecCmd.mockReturnValue({ success: true, output: "content" });
55
+ await readNote({ vaultPath: "/my/vault", file: "My Note" });
56
+ expect(mockExecCmd).toHaveBeenCalledWith([
57
+ "read",
58
+ "--vault",
59
+ "/my/vault",
60
+ "My Note",
61
+ ]);
62
+ });
63
+ it("truncates snippet for very long notes", async () => {
64
+ mockIsAvailable.mockReturnValue(true);
65
+ const longContent = "a".repeat(500);
66
+ mockExecCmd.mockReturnValue({ success: true, output: longContent });
67
+ const result = await readNote({ vaultPath: "/vault", file: "Long" });
68
+ expect(result.results[0].snippet.length).toBeLessThanOrEqual(240);
69
+ });
70
+ });
71
+ //# sourceMappingURL=note-read.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"note-read.test.js","sourceRoot":"","sources":["../../../src/vault/__tests__/note-read.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ // Mock obsidian-cli before importing the module under test
3
+ vi.mock("../obsidian-cli.js", () => ({
4
+ isObsidianCliAvailable: vi.fn(),
5
+ execObsidianCmd: vi.fn(),
6
+ }));
7
+ import { searchVault } from "../search-vault.js";
8
+ import { isObsidianCliAvailable, execObsidianCmd } from "../obsidian-cli.js";
9
+ const mockIsAvailable = vi.mocked(isObsidianCliAvailable);
10
+ const mockExecCmd = vi.mocked(execObsidianCmd);
11
+ beforeEach(() => {
12
+ vi.resetAllMocks();
13
+ });
14
+ describe("searchVault", () => {
15
+ it("returns runtime_unavailable when CLI is not available", async () => {
16
+ mockIsAvailable.mockReturnValue(false);
17
+ const result = await searchVault({ vaultPath: "/vault", query: "test" });
18
+ expect(result.status).toBe("runtime_unavailable");
19
+ expect(result.mode).toBe("search:context");
20
+ expect(result.fallback).toBe("none");
21
+ expect(result.diagnostics).toContain("Obsidian CLI not available");
22
+ });
23
+ it("returns runtime_unavailable when CLI command fails", async () => {
24
+ mockIsAvailable.mockReturnValue(true);
25
+ mockExecCmd.mockReturnValue({
26
+ success: false,
27
+ output: "",
28
+ error: "Vault not found",
29
+ });
30
+ const result = await searchVault({ vaultPath: "/vault", query: "test" });
31
+ expect(result.status).toBe("runtime_unavailable");
32
+ expect(result.diagnostics[0]).toContain("Vault not found");
33
+ });
34
+ it("returns no_results when CLI returns empty output", async () => {
35
+ mockIsAvailable.mockReturnValue(true);
36
+ mockExecCmd.mockReturnValue({ success: true, output: "" });
37
+ const result = await searchVault({ vaultPath: "/vault", query: "test" });
38
+ expect(result.status).toBe("no_results");
39
+ expect(result.results).toHaveLength(0);
40
+ });
41
+ it("parses CLI output into structured results", async () => {
42
+ mockIsAvailable.mockReturnValue(true);
43
+ mockExecCmd.mockReturnValue({
44
+ success: true,
45
+ output: [
46
+ "Projects/Refund Automation.md:Owner handoff for refund automation workflow",
47
+ "Daily/2026-03-20.md:Discussed refund automation timeline with team",
48
+ ].join("\n"),
49
+ });
50
+ const result = await searchVault({ vaultPath: "/vault", query: "refund" });
51
+ expect(result.status).toBe("ok");
52
+ expect(result.results).toHaveLength(2);
53
+ expect(result.results[0].title).toBe("Refund Automation");
54
+ expect(result.results[0].path).toBe("Projects/Refund Automation.md");
55
+ expect(result.results[0].file).toBe("Refund Automation");
56
+ expect(result.results[0].snippet).toContain("refund automation");
57
+ });
58
+ it("respects the limit parameter", async () => {
59
+ mockIsAvailable.mockReturnValue(true);
60
+ const lines = Array.from({ length: 30 }, (_, i) => `Notes/note${i}.md:content line ${i}`);
61
+ mockExecCmd.mockReturnValue({ success: true, output: lines.join("\n") });
62
+ const result = await searchVault({
63
+ vaultPath: "/vault",
64
+ query: "content",
65
+ limit: 5,
66
+ });
67
+ expect(result.results).toHaveLength(5);
68
+ expect(result.truncated).toBe(true);
69
+ expect(result.totalMatches).toBe(30);
70
+ });
71
+ it("truncates snippets to MAX_SNIPPET_LENGTH", async () => {
72
+ mockIsAvailable.mockReturnValue(true);
73
+ const longLine = "a".repeat(500);
74
+ mockExecCmd.mockReturnValue({
75
+ success: true,
76
+ output: `Notes/long.md:${longLine}`,
77
+ });
78
+ const result = await searchVault({ vaultPath: "/vault", query: "a" });
79
+ expect(result.results[0].snippet.length).toBeLessThanOrEqual(240);
80
+ });
81
+ it("passes vault path and query to CLI correctly", async () => {
82
+ mockIsAvailable.mockReturnValue(true);
83
+ mockExecCmd.mockReturnValue({ success: true, output: "" });
84
+ await searchVault({ vaultPath: "/my/vault", query: "hello world" });
85
+ expect(mockExecCmd).toHaveBeenCalledWith([
86
+ "search:context",
87
+ "--vault",
88
+ "/my/vault",
89
+ "hello world",
90
+ ]);
91
+ });
92
+ });
93
+ //# sourceMappingURL=search-vault.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-vault.test.js","sourceRoot":"","sources":["../../../src/vault/__tests__/search-vault.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import fs from "fs-extra";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { detectVaultContext } from "../vault-detect.js";
6
+ let tmpDir;
7
+ beforeEach(async () => {
8
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "byoao-detect-"));
9
+ });
10
+ afterEach(async () => {
11
+ await fs.remove(tmpDir);
12
+ });
13
+ describe("detectVaultContext", () => {
14
+ it("returns vault path when .obsidian/ and AGENT.md exist", async () => {
15
+ await fs.ensureDir(path.join(tmpDir, ".obsidian"));
16
+ await fs.writeFile(path.join(tmpDir, "AGENT.md"), "# Agent");
17
+ const result = detectVaultContext(tmpDir);
18
+ expect(result).toBe(tmpDir);
19
+ });
20
+ it("returns vault path when .obsidian/ and Knowledge/Glossary.md exist", async () => {
21
+ await fs.ensureDir(path.join(tmpDir, ".obsidian"));
22
+ await fs.ensureDir(path.join(tmpDir, "Knowledge"));
23
+ await fs.writeFile(path.join(tmpDir, "Knowledge/Glossary.md"), "# Glossary");
24
+ const result = detectVaultContext(tmpDir);
25
+ expect(result).toBe(tmpDir);
26
+ });
27
+ it("returns null when .obsidian/ is missing", async () => {
28
+ await fs.writeFile(path.join(tmpDir, "AGENT.md"), "# Agent");
29
+ const result = detectVaultContext(tmpDir);
30
+ expect(result).toBeNull();
31
+ });
32
+ it("returns null when .obsidian/ exists but no AGENT.md or Glossary", async () => {
33
+ await fs.ensureDir(path.join(tmpDir, ".obsidian"));
34
+ const result = detectVaultContext(tmpDir);
35
+ expect(result).toBeNull();
36
+ });
37
+ it("checks parent directory when target is a subdirectory", async () => {
38
+ await fs.ensureDir(path.join(tmpDir, ".obsidian"));
39
+ await fs.writeFile(path.join(tmpDir, "AGENT.md"), "# Agent");
40
+ const subDir = path.join(tmpDir, "Projects");
41
+ await fs.ensureDir(subDir);
42
+ const result = detectVaultContext(subDir);
43
+ expect(result).toBe(tmpDir);
44
+ });
45
+ it("returns null for a completely empty directory", async () => {
46
+ const result = detectVaultContext(tmpDir);
47
+ expect(result).toBeNull();
48
+ });
49
+ it("returns null for non-existent path", async () => {
50
+ const result = detectVaultContext("/nonexistent/path/12345");
51
+ expect(result).toBeNull();
52
+ });
53
+ });
54
+ //# sourceMappingURL=vault-detect.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault-detect.test.js","sourceRoot":"","sources":["../../../src/vault/__tests__/vault-detect.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import path from "node:path";
2
+ import { isObsidianCliAvailable, execObsidianCmd } from "./obsidian-cli.js";
3
+ import { DEFAULT_RESULT_LIMIT } from "./retrieval-types.js";
4
+ const ALL_CHECKS = ["orphans", "unresolved", "deadends"];
5
+ /** Parse CLI output lines into result items for a given check type. */
6
+ function parseCheckOutput(check, output) {
7
+ const lines = output.split("\n").filter((l) => l.trim().length > 0);
8
+ return lines.map((line) => {
9
+ const trimmed = line.trim();
10
+ // For orphans/deadends: output is file paths
11
+ // For unresolved: output is link target names
12
+ const isPath = trimmed.includes("/") || trimmed.endsWith(".md");
13
+ const title = isPath ? path.basename(trimmed, ".md") : trimmed;
14
+ return {
15
+ title,
16
+ path: isPath ? trimmed : "",
17
+ file: title,
18
+ metadata: { check },
19
+ };
20
+ });
21
+ }
22
+ /**
23
+ * Run graph health diagnostics using Obsidian CLI.
24
+ * Returns runtime_unavailable if CLI is not available — no internal fallback.
25
+ */
26
+ export async function getGraphHealth(input) {
27
+ const { vaultPath, check = "all", limit = DEFAULT_RESULT_LIMIT } = input;
28
+ const mode = "graph-health";
29
+ const base = {
30
+ mode,
31
+ vault: vaultPath,
32
+ fallback: "none",
33
+ };
34
+ // 1. Check CLI availability
35
+ if (!isObsidianCliAvailable()) {
36
+ return {
37
+ ...base,
38
+ status: "runtime_unavailable",
39
+ summary: "Obsidian CLI not available",
40
+ results: [],
41
+ truncated: false,
42
+ diagnostics: ["Obsidian CLI not available"],
43
+ };
44
+ }
45
+ // 2. Determine which checks to run
46
+ const checks = check === "all" ? ALL_CHECKS : [check];
47
+ // 3. Run each check and collect results
48
+ const allItems = [];
49
+ const diagnostics = [];
50
+ for (const checkType of checks) {
51
+ const cliResult = execObsidianCmd([checkType, "--vault", vaultPath]);
52
+ if (!cliResult.success) {
53
+ diagnostics.push(`${checkType} check failed: ${cliResult.error ?? "unknown error"}`);
54
+ continue;
55
+ }
56
+ if (cliResult.output.trim().length > 0) {
57
+ allItems.push(...parseCheckOutput(checkType, cliResult.output));
58
+ }
59
+ }
60
+ // 4. Build response
61
+ if (allItems.length === 0 && diagnostics.length === 0) {
62
+ return {
63
+ ...base,
64
+ status: "no_results",
65
+ summary: "No issues found — vault graph is healthy",
66
+ results: [],
67
+ truncated: false,
68
+ diagnostics,
69
+ };
70
+ }
71
+ const truncated = allItems.length > limit;
72
+ const results = allItems.slice(0, limit);
73
+ return {
74
+ ...base,
75
+ status: allItems.length > 0 ? "ok" : "no_results",
76
+ summary: `Found ${allItems.length} graph issues across ${checks.join(", ")} checks`,
77
+ results,
78
+ truncated,
79
+ totalMatches: allItems.length,
80
+ diagnostics,
81
+ };
82
+ }
83
+ //# sourceMappingURL=graph-health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-health.js","sourceRoot":"","sources":["../../src/vault/graph-health.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import { isObsidianCliAvailable, execObsidianCmd } from "./obsidian-cli.js";
2
+ import { MAX_SNIPPET_LENGTH } from "./retrieval-types.js";
3
+ /**
4
+ * Read a note from the vault using Obsidian CLI.
5
+ * Returns runtime_unavailable if CLI is not available — no internal fallback.
6
+ */
7
+ export async function readNote(input) {
8
+ const { vaultPath, file } = input;
9
+ const mode = "read";
10
+ const base = {
11
+ mode,
12
+ vault: vaultPath,
13
+ fallback: "none",
14
+ };
15
+ // 1. Check CLI availability
16
+ if (!isObsidianCliAvailable()) {
17
+ return {
18
+ ...base,
19
+ status: "runtime_unavailable",
20
+ summary: "Obsidian CLI not available",
21
+ results: [],
22
+ truncated: false,
23
+ diagnostics: ["Obsidian CLI not available"],
24
+ };
25
+ }
26
+ // 2. Execute read command
27
+ const cliResult = execObsidianCmd(["read", "--vault", vaultPath, file]);
28
+ if (!cliResult.success) {
29
+ return {
30
+ ...base,
31
+ status: "runtime_unavailable",
32
+ summary: `Failed to read note "${file}"`,
33
+ results: [],
34
+ truncated: false,
35
+ diagnostics: [cliResult.error ?? "Unknown CLI error"],
36
+ };
37
+ }
38
+ // 3. Handle empty output
39
+ const content = cliResult.output.trim();
40
+ if (content.length === 0) {
41
+ return {
42
+ ...base,
43
+ status: "no_results",
44
+ summary: `Note "${file}" is empty or not found`,
45
+ results: [],
46
+ truncated: false,
47
+ diagnostics: [],
48
+ };
49
+ }
50
+ // 4. Build result — full content as snippet (truncated for the summary item)
51
+ const snippet = content.length > MAX_SNIPPET_LENGTH
52
+ ? content.substring(0, MAX_SNIPPET_LENGTH)
53
+ : content;
54
+ return {
55
+ ...base,
56
+ status: "ok",
57
+ summary: `Read note "${file}" (${content.length} chars)`,
58
+ results: [
59
+ {
60
+ title: file,
61
+ path: "",
62
+ file,
63
+ snippet,
64
+ },
65
+ ],
66
+ truncated: false,
67
+ diagnostics: [],
68
+ };
69
+ }
70
+ //# sourceMappingURL=note-read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"note-read.js","sourceRoot":"","sources":["../../src/vault/note-read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAO1D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,KAAoB;IACjD,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC;IACpB,MAAM,IAAI,GAAyD;QACjE,IAAI;QACJ,KAAK,EAAE,SAAS;QAChB,QAAQ,EAAE,MAAM;KACjB,CAAC;IAEF,4BAA4B;IAC5B,IAAI,CAAC,sBAAsB,EAAE,EAAE,CAAC;QAC9B,OAAO;YACL,GAAG,IAAI;YACP,MAAM,EAAE,qBAAqB;YAC7B,OAAO,EAAE,4BAA4B;YACrC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,CAAC,4BAA4B,CAAC;SAC5C,CAAC;IACJ,CAAC;IAED,0BAA0B;IAC1B,MAAM,SAAS,GAAG,eAAe,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;IAExE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QACvB,OAAO;YACL,GAAG,IAAI;YACP,MAAM,EAAE,qBAAqB;YAC7B,OAAO,EAAE,wBAAwB,IAAI,GAAG;YACxC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,CAAC,SAAS,CAAC,KAAK,IAAI,mBAAmB,CAAC;SACtD,CAAC;IACJ,CAAC;IAED,yBAAyB;IACzB,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACxC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,GAAG,IAAI;YACP,MAAM,EAAE,YAAY;YACpB,OAAO,EAAE,SAAS,IAAI,yBAAyB;YAC/C,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,EAAE;SAChB,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,MAAM,OAAO,GACX,OAAO,CAAC,MAAM,GAAG,kBAAkB;QACjC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,kBAAkB,CAAC;QAC1C,CAAC,CAAC,OAAO,CAAC;IAEd,OAAO;QACL,GAAG,IAAI;QACP,MAAM,EAAE,IAAI;QACZ,OAAO,EAAE,cAAc,IAAI,MAAM,OAAO,CAAC,MAAM,SAAS;QACxD,OAAO,EAAE;YACP;gBACE,KAAK,EAAE,IAAI;gBACX,IAAI,EAAE,EAAE;gBACR,IAAI;gBACJ,OAAO;aACR;SACF;QACD,SAAS,EAAE,KAAK;QAChB,WAAW,EAAE,EAAE;KAChB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Default maximum number of results to return. */
2
+ export const DEFAULT_RESULT_LIMIT = 20;
3
+ /** Default maximum snippet length per result (characters). */
4
+ export const MAX_SNIPPET_LENGTH = 240;
5
+ //# sourceMappingURL=retrieval-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retrieval-types.js","sourceRoot":"","sources":["../../src/vault/retrieval-types.ts"],"names":[],"mappings":"AA4CA,mDAAmD;AACnD,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC,8DAA8D;AAC9D,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC"}
@@ -0,0 +1,87 @@
1
+ import path from "node:path";
2
+ import { isObsidianCliAvailable, execObsidianCmd } from "./obsidian-cli.js";
3
+ import { DEFAULT_RESULT_LIMIT, MAX_SNIPPET_LENGTH } from "./retrieval-types.js";
4
+ /**
5
+ * Search vault notes using Obsidian CLI search:context.
6
+ * Returns runtime_unavailable if CLI is not available — no internal fallback.
7
+ */
8
+ export async function searchVault(input) {
9
+ const { vaultPath, query, limit = DEFAULT_RESULT_LIMIT } = input;
10
+ const mode = "search:context";
11
+ const base = {
12
+ mode,
13
+ vault: vaultPath,
14
+ fallback: "none",
15
+ };
16
+ // 1. Check CLI availability
17
+ if (!isObsidianCliAvailable()) {
18
+ return {
19
+ ...base,
20
+ status: "runtime_unavailable",
21
+ summary: "Obsidian CLI not available",
22
+ results: [],
23
+ truncated: false,
24
+ diagnostics: ["Obsidian CLI not available"],
25
+ };
26
+ }
27
+ // 2. Execute search:context
28
+ const cliResult = execObsidianCmd([
29
+ "search:context",
30
+ "--vault",
31
+ vaultPath,
32
+ query,
33
+ ]);
34
+ if (!cliResult.success) {
35
+ return {
36
+ ...base,
37
+ status: "runtime_unavailable",
38
+ summary: "Obsidian CLI command failed",
39
+ results: [],
40
+ truncated: false,
41
+ diagnostics: [cliResult.error ?? "Unknown CLI error"],
42
+ };
43
+ }
44
+ // 3. Parse output lines (format: path/to/note.md:context line)
45
+ const lines = cliResult.output.split("\n").filter((l) => l.trim().length > 0);
46
+ if (lines.length === 0) {
47
+ return {
48
+ ...base,
49
+ status: "no_results",
50
+ summary: `No matches for "${query}"`,
51
+ results: [],
52
+ truncated: false,
53
+ diagnostics: [],
54
+ };
55
+ }
56
+ // 4. Build result items
57
+ const allItems = [];
58
+ for (const line of lines) {
59
+ const colonIdx = line.indexOf(":");
60
+ if (colonIdx === -1)
61
+ continue;
62
+ const filePath = line.substring(0, colonIdx);
63
+ const snippet = line.substring(colonIdx + 1).trim();
64
+ const title = path.basename(filePath, ".md");
65
+ allItems.push({
66
+ title,
67
+ path: filePath,
68
+ file: title,
69
+ snippet: snippet.length > MAX_SNIPPET_LENGTH
70
+ ? snippet.substring(0, MAX_SNIPPET_LENGTH)
71
+ : snippet,
72
+ });
73
+ }
74
+ // 5. Apply limit
75
+ const truncated = allItems.length > limit;
76
+ const results = allItems.slice(0, limit);
77
+ return {
78
+ ...base,
79
+ status: "ok",
80
+ summary: `${allItems.length} matching notes for "${query}"`,
81
+ results,
82
+ truncated,
83
+ totalMatches: allItems.length,
84
+ diagnostics: [],
85
+ };
86
+ }
87
+ //# sourceMappingURL=search-vault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-vault.js","sourceRoot":"","sources":["../../src/vault/search-vault.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+ /**
4
+ * Detect whether a directory is (or is inside) a BYOAO vault.
5
+ * Returns the vault root path if detected, null otherwise.
6
+ *
7
+ * A directory is a BYOAO vault if:
8
+ * - .obsidian/ exists AND AGENT.md exists, OR
9
+ * - .obsidian/ exists AND Knowledge/Glossary.md exists
10
+ *
11
+ * Checks the given directory and its immediate parent.
12
+ */
13
+ export function detectVaultContext(dir) {
14
+ const candidates = [dir, path.dirname(dir)];
15
+ for (const candidate of candidates) {
16
+ if (!fs.existsSync(candidate))
17
+ continue;
18
+ const hasObsidian = fs.existsSync(path.join(candidate, ".obsidian"));
19
+ if (!hasObsidian)
20
+ continue;
21
+ const hasAgentMd = fs.existsSync(path.join(candidate, "AGENT.md"));
22
+ const hasGlossary = fs.existsSync(path.join(candidate, "Knowledge", "Glossary.md"));
23
+ if (hasAgentMd || hasGlossary) {
24
+ return candidate;
25
+ }
26
+ }
27
+ return null;
28
+ }
29
+ //# sourceMappingURL=vault-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault-detect.js","sourceRoot":"","sources":["../../src/vault/vault-detect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAE5C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,SAAS;QAExC,MAAM,WAAW,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;QACrE,IAAI,CAAC,WAAW;YAAE,SAAS;QAE3B,MAAM,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;QACnE,MAAM,WAAW,GAAG,EAAE,CAAC,UAAU,CAC/B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,EAAE,aAAa,CAAC,CACjD,CAAC;QAEF,IAAI,UAAU,IAAI,WAAW,EAAE,CAAC;YAC9B,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jayjiang/byoao",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "Build Your Own AI OS — Obsidian + AI Agent",
5
5
  "type": "module",
6
6
  "engines": {