@jayjiang/byoao 0.3.3 → 0.5.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.
- package/dist/cli/cli-program.js +118 -0
- package/dist/cli/cli-program.js.map +1 -1
- package/dist/hooks/system-transform.js +19 -1
- package/dist/hooks/system-transform.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/graph-health.js +25 -0
- package/dist/tools/graph-health.js.map +1 -0
- package/dist/tools/note-read.js +19 -0
- package/dist/tools/note-read.js.map +1 -0
- package/dist/tools/search-vault.js +22 -0
- package/dist/tools/search-vault.js.map +1 -0
- package/dist/tools/vault-upgrade.js +77 -0
- package/dist/tools/vault-upgrade.js.map +1 -0
- package/dist/vault/__tests__/create.test.js +12 -0
- package/dist/vault/__tests__/create.test.js.map +1 -1
- package/dist/vault/__tests__/graph-health.test.js +102 -0
- package/dist/vault/__tests__/graph-health.test.js.map +1 -0
- package/dist/vault/__tests__/manifest.test.js +76 -0
- package/dist/vault/__tests__/manifest.test.js.map +1 -0
- package/dist/vault/__tests__/note-read.test.js +71 -0
- package/dist/vault/__tests__/note-read.test.js.map +1 -0
- package/dist/vault/__tests__/search-vault.test.js +93 -0
- package/dist/vault/__tests__/search-vault.test.js.map +1 -0
- package/dist/vault/__tests__/upgrade.test.js +181 -0
- package/dist/vault/__tests__/upgrade.test.js.map +1 -0
- package/dist/vault/__tests__/vault-detect.test.js +54 -0
- package/dist/vault/__tests__/vault-detect.test.js.map +1 -0
- package/dist/vault/create.js +20 -3
- package/dist/vault/create.js.map +1 -1
- package/dist/vault/graph-health.js +83 -0
- package/dist/vault/graph-health.js.map +1 -0
- package/dist/vault/manifest.js +68 -0
- package/dist/vault/manifest.js.map +1 -0
- package/dist/vault/note-read.js +70 -0
- package/dist/vault/note-read.js.map +1 -0
- package/dist/vault/retrieval-types.js +5 -0
- package/dist/vault/retrieval-types.js.map +1 -0
- package/dist/vault/search-vault.js +87 -0
- package/dist/vault/search-vault.js.map +1 -0
- package/dist/vault/upgrade.js +266 -0
- package/dist/vault/upgrade.js.map +1 -0
- package/dist/vault/vault-detect.js +29 -0
- package/dist/vault/vault-detect.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
let tmpDir;
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "byoao-manifest-"));
|
|
8
|
+
});
|
|
9
|
+
afterEach(async () => {
|
|
10
|
+
await fs.remove(tmpDir);
|
|
11
|
+
});
|
|
12
|
+
describe("writeManifest", () => {
|
|
13
|
+
it("creates .byoao/manifest.json with correct structure", async () => {
|
|
14
|
+
const { writeManifest } = await import("../manifest.js");
|
|
15
|
+
const installedFiles = {
|
|
16
|
+
skills: [".opencode/skills/byoao-conventions.md"],
|
|
17
|
+
commands: [".opencode/commands/vault-doctor.md"],
|
|
18
|
+
obsidianConfig: [".obsidian/core-plugins.json"],
|
|
19
|
+
templates: ["Knowledge/templates/Daily Note.md"],
|
|
20
|
+
};
|
|
21
|
+
await writeManifest(tmpDir, "pm-tpm", installedFiles);
|
|
22
|
+
const manifestPath = path.join(tmpDir, ".byoao", "manifest.json");
|
|
23
|
+
expect(await fs.pathExists(manifestPath)).toBe(true);
|
|
24
|
+
const manifest = await fs.readJson(manifestPath);
|
|
25
|
+
expect(manifest.version).toBeDefined();
|
|
26
|
+
expect(manifest.preset).toBe("pm-tpm");
|
|
27
|
+
expect(manifest.createdAt).toBeDefined();
|
|
28
|
+
expect(manifest.updatedAt).toBeDefined();
|
|
29
|
+
expect(manifest.infrastructure.skills).toEqual([".opencode/skills/byoao-conventions.md"]);
|
|
30
|
+
expect(manifest.infrastructure.commands).toEqual([".opencode/commands/vault-doctor.md"]);
|
|
31
|
+
expect(manifest.infrastructure.obsidianConfig).toEqual([".obsidian/core-plugins.json"]);
|
|
32
|
+
expect(manifest.infrastructure.templates).toEqual(["Knowledge/templates/Daily Note.md"]);
|
|
33
|
+
});
|
|
34
|
+
it("creates .byoao/ directory if it does not exist", async () => {
|
|
35
|
+
const { writeManifest } = await import("../manifest.js");
|
|
36
|
+
await writeManifest(tmpDir, "pm-tpm", {
|
|
37
|
+
skills: [], commands: [], obsidianConfig: [], templates: [],
|
|
38
|
+
});
|
|
39
|
+
expect(await fs.pathExists(path.join(tmpDir, ".byoao"))).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe("readManifest", () => {
|
|
43
|
+
it("returns parsed manifest when valid", async () => {
|
|
44
|
+
const { readManifest, writeManifest } = await import("../manifest.js");
|
|
45
|
+
await writeManifest(tmpDir, "pm-tpm", {
|
|
46
|
+
skills: [".opencode/skills/test.md"],
|
|
47
|
+
commands: [],
|
|
48
|
+
obsidianConfig: [],
|
|
49
|
+
templates: [],
|
|
50
|
+
});
|
|
51
|
+
const result = await readManifest(tmpDir);
|
|
52
|
+
expect(result).not.toBeNull();
|
|
53
|
+
expect(result.preset).toBe("pm-tpm");
|
|
54
|
+
expect(result.infrastructure.skills).toEqual([".opencode/skills/test.md"]);
|
|
55
|
+
});
|
|
56
|
+
it("returns null when manifest does not exist", async () => {
|
|
57
|
+
const { readManifest } = await import("../manifest.js");
|
|
58
|
+
const result = await readManifest(tmpDir);
|
|
59
|
+
expect(result).toBeNull();
|
|
60
|
+
});
|
|
61
|
+
it("returns null when manifest has invalid JSON", async () => {
|
|
62
|
+
const { readManifest } = await import("../manifest.js");
|
|
63
|
+
await fs.ensureDir(path.join(tmpDir, ".byoao"));
|
|
64
|
+
await fs.writeFile(path.join(tmpDir, ".byoao", "manifest.json"), "{ broken json");
|
|
65
|
+
const result = await readManifest(tmpDir);
|
|
66
|
+
expect(result).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
it("returns null when manifest fails schema validation", async () => {
|
|
69
|
+
const { readManifest } = await import("../manifest.js");
|
|
70
|
+
await fs.ensureDir(path.join(tmpDir, ".byoao"));
|
|
71
|
+
await fs.writeJson(path.join(tmpDir, ".byoao", "manifest.json"), { version: 123, wrong: "schema" });
|
|
72
|
+
const result = await readManifest(tmpDir);
|
|
73
|
+
expect(result).toBeNull();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
//# sourceMappingURL=manifest.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.test.js","sourceRoot":"","sources":["../../../src/vault/__tests__/manifest.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,IAAI,MAAc,CAAC;AAEnB,UAAU,CAAC,KAAK,IAAI,EAAE;IACpB,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;AACvE,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,MAAM,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAEzD,MAAM,cAAc,GAAG;YACrB,MAAM,EAAE,CAAC,uCAAuC,CAAC;YACjD,QAAQ,EAAE,CAAC,oCAAoC,CAAC;YAChD,cAAc,EAAE,CAAC,6BAA6B,CAAC;YAC/C,SAAS,EAAE,CAAC,mCAAmC,CAAC;SACjD,CAAC;QAEF,MAAM,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;QAEtD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;QAClE,MAAM,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAErD,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QACvC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC;QAC1F,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,oCAAoC,CAAC,CAAC,CAAC;QACzF,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC;QACxF,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,mCAAmC,CAAC,CAAC,CAAC;IAC3F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAEzD,MAAM,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE;YACpC,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE;SAC5D,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAEvE,MAAM,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE;YACpC,MAAM,EAAE,CAAC,0BAA0B,CAAC;YACpC,QAAQ,EAAE,EAAE;YACZ,cAAc,EAAE,EAAE;YAClB,SAAS,EAAE,EAAE;SACd,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,MAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,CAAC,MAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAExD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAExD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;QAChD,MAAM,EAAE,CAAC,SAAS,CAChB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,eAAe,CAAC,EAC5C,eAAe,CAChB,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAExD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;QAChD,MAAM,EAAE,CAAC,SAAS,CAChB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,eAAe,CAAC,EAC5C,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,CAClC,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -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,181 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
let tmpDir;
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "byoao-upgrade-"));
|
|
8
|
+
});
|
|
9
|
+
afterEach(async () => {
|
|
10
|
+
await fs.remove(tmpDir);
|
|
11
|
+
});
|
|
12
|
+
describe("scanInstalledAssets", () => {
|
|
13
|
+
it("finds skills, commands, obsidian config, and templates", async () => {
|
|
14
|
+
const { scanInstalledAssets } = await import("../upgrade.js");
|
|
15
|
+
// Set up vault with infrastructure files
|
|
16
|
+
await fs.ensureDir(path.join(tmpDir, ".opencode", "skills"));
|
|
17
|
+
await fs.writeFile(path.join(tmpDir, ".opencode", "skills", "test.md"), "# Skill");
|
|
18
|
+
await fs.ensureDir(path.join(tmpDir, ".opencode", "commands"));
|
|
19
|
+
await fs.writeFile(path.join(tmpDir, ".opencode", "commands", "cmd.md"), "# Cmd");
|
|
20
|
+
await fs.ensureDir(path.join(tmpDir, ".obsidian"));
|
|
21
|
+
await fs.writeFile(path.join(tmpDir, ".obsidian", "core-plugins.json"), "[]");
|
|
22
|
+
await fs.ensureDir(path.join(tmpDir, "Knowledge", "templates"));
|
|
23
|
+
await fs.writeFile(path.join(tmpDir, "Knowledge", "templates", "Daily Note.md"), "# DN");
|
|
24
|
+
const result = await scanInstalledAssets(tmpDir);
|
|
25
|
+
expect(result.skills).toContain(".opencode/skills/test.md");
|
|
26
|
+
expect(result.commands).toContain(".opencode/commands/cmd.md");
|
|
27
|
+
expect(result.obsidianConfig).toContain(".obsidian/core-plugins.json");
|
|
28
|
+
expect(result.templates).toContain("Knowledge/templates/Daily Note.md");
|
|
29
|
+
});
|
|
30
|
+
it("returns empty arrays when no infrastructure exists", async () => {
|
|
31
|
+
const { scanInstalledAssets } = await import("../upgrade.js");
|
|
32
|
+
const result = await scanInstalledAssets(tmpDir);
|
|
33
|
+
expect(result.skills).toEqual([]);
|
|
34
|
+
expect(result.commands).toEqual([]);
|
|
35
|
+
expect(result.obsidianConfig).toEqual([]);
|
|
36
|
+
expect(result.templates).toEqual([]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("bootstrapManifest", () => {
|
|
40
|
+
it("creates manifest with version 0.0.0", async () => {
|
|
41
|
+
const { bootstrapManifest } = await import("../upgrade.js");
|
|
42
|
+
const { readManifest } = await import("../manifest.js");
|
|
43
|
+
await fs.ensureDir(path.join(tmpDir, ".obsidian"));
|
|
44
|
+
await fs.writeFile(path.join(tmpDir, "AGENT.md"), "# Agent");
|
|
45
|
+
await fs.ensureDir(path.join(tmpDir, ".opencode", "skills"));
|
|
46
|
+
await fs.writeFile(path.join(tmpDir, ".opencode", "skills", "s.md"), "skill");
|
|
47
|
+
await bootstrapManifest(tmpDir);
|
|
48
|
+
const manifest = await readManifest(tmpDir);
|
|
49
|
+
expect(manifest).not.toBeNull();
|
|
50
|
+
expect(manifest.version).toBe("0.0.0");
|
|
51
|
+
expect(manifest.preset).toBe("pm-tpm");
|
|
52
|
+
expect(manifest.infrastructure.skills).toContain(".opencode/skills/s.md");
|
|
53
|
+
});
|
|
54
|
+
it("accepts preset override", async () => {
|
|
55
|
+
const { bootstrapManifest } = await import("../upgrade.js");
|
|
56
|
+
const { readManifest } = await import("../manifest.js");
|
|
57
|
+
await bootstrapManifest(tmpDir, "custom-preset");
|
|
58
|
+
const manifest = await readManifest(tmpDir);
|
|
59
|
+
expect(manifest.preset).toBe("custom-preset");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe("buildUpgradePlan", () => {
|
|
63
|
+
it("marks missing files as add and existing files as update", async () => {
|
|
64
|
+
const { buildUpgradePlan } = await import("../upgrade.js");
|
|
65
|
+
// Vault has one skill on disk
|
|
66
|
+
await fs.ensureDir(path.join(tmpDir, ".opencode", "skills"));
|
|
67
|
+
await fs.writeFile(path.join(tmpDir, ".opencode", "skills", "existing.md"), "old content");
|
|
68
|
+
const manifest = {
|
|
69
|
+
version: "0.3.0",
|
|
70
|
+
preset: "pm-tpm",
|
|
71
|
+
createdAt: "2026-03-20",
|
|
72
|
+
updatedAt: "2026-03-20",
|
|
73
|
+
infrastructure: {
|
|
74
|
+
skills: [".opencode/skills/existing.md"],
|
|
75
|
+
commands: [],
|
|
76
|
+
obsidianConfig: [],
|
|
77
|
+
templates: [],
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
// Package ships two skills: existing.md and new.md
|
|
81
|
+
const packageAssets = {
|
|
82
|
+
skills: [
|
|
83
|
+
{ relativePath: ".opencode/skills/existing.md", sourcePath: "/fake/existing.md" },
|
|
84
|
+
{ relativePath: ".opencode/skills/new.md", sourcePath: "/fake/new.md" },
|
|
85
|
+
],
|
|
86
|
+
commands: [],
|
|
87
|
+
obsidianConfig: [],
|
|
88
|
+
templates: [],
|
|
89
|
+
};
|
|
90
|
+
const plan = buildUpgradePlan(tmpDir, manifest, packageAssets);
|
|
91
|
+
const addItem = plan.items.find((i) => i.file === ".opencode/skills/new.md");
|
|
92
|
+
const updateItem = plan.items.find((i) => i.file === ".opencode/skills/existing.md");
|
|
93
|
+
expect(addItem).toBeDefined();
|
|
94
|
+
expect(addItem.action).toBe("add");
|
|
95
|
+
expect(updateItem).toBeDefined();
|
|
96
|
+
expect(updateItem.action).toBe("update");
|
|
97
|
+
});
|
|
98
|
+
it("marks files in manifest but not in package as deprecated", async () => {
|
|
99
|
+
const { buildUpgradePlan } = await import("../upgrade.js");
|
|
100
|
+
const manifest = {
|
|
101
|
+
version: "0.3.0",
|
|
102
|
+
preset: "pm-tpm",
|
|
103
|
+
createdAt: "2026-03-20",
|
|
104
|
+
updatedAt: "2026-03-20",
|
|
105
|
+
infrastructure: {
|
|
106
|
+
skills: [".opencode/skills/removed.md"],
|
|
107
|
+
commands: [],
|
|
108
|
+
obsidianConfig: [],
|
|
109
|
+
templates: [],
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
const packageAssets = {
|
|
113
|
+
skills: [],
|
|
114
|
+
commands: [],
|
|
115
|
+
obsidianConfig: [],
|
|
116
|
+
templates: [],
|
|
117
|
+
};
|
|
118
|
+
const plan = buildUpgradePlan(tmpDir, manifest, packageAssets);
|
|
119
|
+
const deprecated = plan.items.find((i) => i.file === ".opencode/skills/removed.md");
|
|
120
|
+
expect(deprecated).toBeDefined();
|
|
121
|
+
expect(deprecated.action).toBe("deprecated");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe("upgradeVault", () => {
|
|
125
|
+
it("returns up-to-date result when versions match", async () => {
|
|
126
|
+
vi.resetModules();
|
|
127
|
+
const { writeManifest, getPackageVersion } = await import("../manifest.js");
|
|
128
|
+
const version = getPackageVersion();
|
|
129
|
+
await fs.ensureDir(path.join(tmpDir, ".obsidian"));
|
|
130
|
+
await fs.writeFile(path.join(tmpDir, "AGENT.md"), "# Agent");
|
|
131
|
+
await writeManifest(tmpDir, "pm-tpm", {
|
|
132
|
+
skills: [], commands: [], obsidianConfig: [], templates: [],
|
|
133
|
+
});
|
|
134
|
+
const { upgradeVault } = await import("../upgrade.js");
|
|
135
|
+
const result = await upgradeVault(tmpDir);
|
|
136
|
+
expect(result.fromVersion).toBe(version);
|
|
137
|
+
expect(result.toVersion).toBe(version);
|
|
138
|
+
expect(result.added).toEqual([]);
|
|
139
|
+
expect(result.updated).toEqual([]);
|
|
140
|
+
});
|
|
141
|
+
it("proceeds when force is true even if versions match", async () => {
|
|
142
|
+
vi.resetModules();
|
|
143
|
+
const { writeManifest } = await import("../manifest.js");
|
|
144
|
+
await fs.ensureDir(path.join(tmpDir, ".obsidian"));
|
|
145
|
+
await fs.writeFile(path.join(tmpDir, "AGENT.md"), "# Agent");
|
|
146
|
+
await writeManifest(tmpDir, "pm-tpm", {
|
|
147
|
+
skills: [], commands: [], obsidianConfig: [], templates: [],
|
|
148
|
+
});
|
|
149
|
+
const { upgradeVault } = await import("../upgrade.js");
|
|
150
|
+
const result = await upgradeVault(tmpDir, { force: true });
|
|
151
|
+
expect(result.dryRun).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
it("returns dry-run result without modifying files", async () => {
|
|
154
|
+
vi.resetModules();
|
|
155
|
+
await fs.ensureDir(path.join(tmpDir, ".obsidian"));
|
|
156
|
+
await fs.writeFile(path.join(tmpDir, "AGENT.md"), "# Agent");
|
|
157
|
+
const { bootstrapManifest } = await import("../upgrade.js");
|
|
158
|
+
await bootstrapManifest(tmpDir);
|
|
159
|
+
vi.resetModules();
|
|
160
|
+
const { upgradeVault } = await import("../upgrade.js");
|
|
161
|
+
const result = await upgradeVault(tmpDir, { dryRun: true });
|
|
162
|
+
expect(result.dryRun).toBe(true);
|
|
163
|
+
const { readManifest } = await import("../manifest.js");
|
|
164
|
+
const manifest = await readManifest(tmpDir);
|
|
165
|
+
expect(manifest.version).toBe("0.0.0");
|
|
166
|
+
});
|
|
167
|
+
it("bootstraps manifest when none exists", async () => {
|
|
168
|
+
vi.resetModules();
|
|
169
|
+
await fs.ensureDir(path.join(tmpDir, ".obsidian"));
|
|
170
|
+
await fs.writeFile(path.join(tmpDir, "AGENT.md"), "# Agent");
|
|
171
|
+
const { upgradeVault } = await import("../upgrade.js");
|
|
172
|
+
const result = await upgradeVault(tmpDir, { force: true });
|
|
173
|
+
expect(result.fromVersion).toBe("0.0.0");
|
|
174
|
+
});
|
|
175
|
+
it("throws when vault path is not a BYOAO vault", async () => {
|
|
176
|
+
vi.resetModules();
|
|
177
|
+
const { upgradeVault } = await import("../upgrade.js");
|
|
178
|
+
await expect(upgradeVault(tmpDir)).rejects.toThrow(/No BYOAO vault detected/);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
//# sourceMappingURL=upgrade.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.test.js","sourceRoot":"","sources":["../../../src/vault/__tests__/upgrade.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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"}
|
package/dist/vault/create.js
CHANGED
|
@@ -5,6 +5,7 @@ import { loadPreset, getCommonDir } from "./preset.js";
|
|
|
5
5
|
import { configureMcp } from "./mcp.js";
|
|
6
6
|
import { configureObsidianPlugins } from "./obsidian-plugins.js";
|
|
7
7
|
import { configureProvider } from "./provider.js";
|
|
8
|
+
import { writeManifest } from "./manifest.js";
|
|
8
9
|
function countWikilinks(content) {
|
|
9
10
|
const stripped = content
|
|
10
11
|
.replace(/```[\s\S]*?```/g, "")
|
|
@@ -30,6 +31,12 @@ export async function createVault(config) {
|
|
|
30
31
|
const commonDir = getCommonDir();
|
|
31
32
|
const presetDir = path.join(presetsDir, presetName);
|
|
32
33
|
let filesCreated = 0;
|
|
34
|
+
const installedFiles = {
|
|
35
|
+
skills: [],
|
|
36
|
+
commands: [],
|
|
37
|
+
obsidianConfig: [],
|
|
38
|
+
templates: [],
|
|
39
|
+
};
|
|
33
40
|
// Merge directories: common + preset-specific
|
|
34
41
|
const allDirectories = [...COMMON_DIRECTORIES, ...presetConfig.directories];
|
|
35
42
|
// 1. Create directories
|
|
@@ -42,6 +49,10 @@ export async function createVault(config) {
|
|
|
42
49
|
if (await fs.pathExists(obsidianSrc)) {
|
|
43
50
|
await fs.copy(obsidianSrc, path.join(vaultPath, ".obsidian"), { overwrite: false });
|
|
44
51
|
filesCreated += (await fs.readdir(obsidianSrc)).length;
|
|
52
|
+
const obsidianFiles = await fs.readdir(obsidianSrc);
|
|
53
|
+
for (const f of obsidianFiles) {
|
|
54
|
+
installedFiles.obsidianConfig.push(`.obsidian/${f}`);
|
|
55
|
+
}
|
|
45
56
|
}
|
|
46
57
|
// 3. Copy note templates: common first, then preset overlay
|
|
47
58
|
const templateDest = path.join(vaultPath, "Knowledge/templates");
|
|
@@ -54,6 +65,7 @@ export async function createVault(config) {
|
|
|
54
65
|
await fs.copy(path.join(commonTemplatesDir, file), path.join(templateDest, file), { overwrite: false });
|
|
55
66
|
allTemplateNames.push(file.replace(/\.md$/, ""));
|
|
56
67
|
filesCreated++;
|
|
68
|
+
installedFiles.templates.push(`Knowledge/templates/${file}`);
|
|
57
69
|
}
|
|
58
70
|
}
|
|
59
71
|
// Preset templates
|
|
@@ -64,6 +76,7 @@ export async function createVault(config) {
|
|
|
64
76
|
await fs.copy(path.join(presetTemplatesDir, file), path.join(templateDest, file), { overwrite: false });
|
|
65
77
|
allTemplateNames.push(file.replace(/\.md$/, ""));
|
|
66
78
|
filesCreated++;
|
|
79
|
+
installedFiles.templates.push(`Knowledge/templates/${file}`);
|
|
67
80
|
}
|
|
68
81
|
}
|
|
69
82
|
// 4. Generate Glossary.md
|
|
@@ -250,7 +263,7 @@ tags: [team]
|
|
|
250
263
|
}
|
|
251
264
|
}
|
|
252
265
|
// 11. Configure vault as OpenCode project (plugin + skills + commands)
|
|
253
|
-
await configureOpenCodeProject(vaultPath);
|
|
266
|
+
await configureOpenCodeProject(vaultPath, installedFiles);
|
|
254
267
|
// 12. Configure MCP servers in global OpenCode config
|
|
255
268
|
const mcpResult = await configureMcp(presetConfig);
|
|
256
269
|
// 13. Install Obsidian community plugins from preset
|
|
@@ -260,7 +273,9 @@ tags: [team]
|
|
|
260
273
|
if (config.provider && config.provider !== "skip") {
|
|
261
274
|
providerResult = await configureProvider(config.provider, config.provider === "gemini" ? config.gcpProjectId : undefined);
|
|
262
275
|
}
|
|
263
|
-
// 15.
|
|
276
|
+
// 15. Write BYOAO manifest
|
|
277
|
+
await writeManifest(vaultPath, presetName, installedFiles);
|
|
278
|
+
// 16. Count wikilinks from all generated markdown files
|
|
264
279
|
let wikilinksCreated = 0;
|
|
265
280
|
const entries = await fs.readdir(vaultPath, { recursive: true });
|
|
266
281
|
for (const entry of entries) {
|
|
@@ -286,7 +301,7 @@ tags: [team]
|
|
|
286
301
|
* and copies skills + commands so BYOAO tools work when
|
|
287
302
|
* OpenCode is launched from the vault (including via Agent Client).
|
|
288
303
|
*/
|
|
289
|
-
async function configureOpenCodeProject(vaultPath) {
|
|
304
|
+
async function configureOpenCodeProject(vaultPath, installedFiles) {
|
|
290
305
|
// 1. Write .opencode.json with BYOAO plugin
|
|
291
306
|
const configPath = path.join(vaultPath, ".opencode.json");
|
|
292
307
|
let config = {};
|
|
@@ -314,6 +329,7 @@ async function configureOpenCodeProject(vaultPath) {
|
|
|
314
329
|
for (const file of files) {
|
|
315
330
|
if (file.endsWith(".md")) {
|
|
316
331
|
await fs.copy(path.join(obsidianSkillsSrc, file), path.join(skillsDest, file), { overwrite: true });
|
|
332
|
+
installedFiles.skills.push(`.opencode/skills/${file}`);
|
|
317
333
|
}
|
|
318
334
|
}
|
|
319
335
|
}
|
|
@@ -326,6 +342,7 @@ async function configureOpenCodeProject(vaultPath) {
|
|
|
326
342
|
for (const file of files) {
|
|
327
343
|
if (file.endsWith(".md")) {
|
|
328
344
|
await fs.copy(path.join(byoaoSkillsSrc, file), path.join(commandsDest, file), { overwrite: true });
|
|
345
|
+
installedFiles.commands.push(`.opencode/commands/${file}`);
|
|
329
346
|
}
|
|
330
347
|
}
|
|
331
348
|
}
|