@cueloop/integration-obsidian 0.1.0-alpha.59 → 0.1.0-alpha.60

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueloop/integration-obsidian",
3
- "version": "0.1.0-alpha.59",
3
+ "version": "0.1.0-alpha.60",
4
4
  "description": "Export reviewed cueloop plans into an Obsidian vault",
5
5
  "homepage": "https://github.com/mmurakaru/cueloop#readme",
6
6
  "bugs": {
@@ -24,8 +24,8 @@
24
24
  "access": "public"
25
25
  },
26
26
  "dependencies": {
27
- "@cueloop/extension-api": "0.1.0-alpha.59",
28
- "@cueloop/schema": "0.1.0-alpha.59"
27
+ "@cueloop/extension-api": "0.1.0-alpha.60",
28
+ "@cueloop/schema": "0.1.0-alpha.60"
29
29
  },
30
30
  "engines": {
31
31
  "bun": ">=1.3.0"
@@ -17,9 +17,11 @@ describe("detectVaults", () => {
17
17
  test("returns registered vault paths that exist on disk", () => {
18
18
  // Arrange
19
19
  const alive = join(dir, "notes");
20
+
20
21
  mkdirSync(alive);
21
22
  const gone = join(dir, "deleted-vault");
22
23
  const configPath = join(dir, "obsidian.json");
24
+
23
25
  writeFileSync(
24
26
  configPath,
25
27
  JSON.stringify({
@@ -37,9 +39,11 @@ describe("detectVaults", () => {
37
39
  test("missing or malformed config yields no vaults", () => {
38
40
  expect(detectVaults(join(dir, "nope.json"))).toEqual([]);
39
41
  const broken = join(dir, "broken.json");
42
+
40
43
  writeFileSync(broken, "not json");
41
44
  expect(detectVaults(broken)).toEqual([]);
42
45
  const empty = join(dir, "empty.json");
46
+
43
47
  writeFileSync(empty, "{}");
44
48
  expect(detectVaults(empty)).toEqual([]);
45
49
  });
package/src/detect.ts CHANGED
@@ -20,6 +20,7 @@ export function obsidianConfigPath(platform: NodeJS.Platform = process.platform)
20
20
  "obsidian.json",
21
21
  );
22
22
  }
23
+
23
24
  return join(
24
25
  process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"),
25
26
  "obsidian",
@@ -33,6 +34,7 @@ export function detectVaults(configPath: string = obsidianConfigPath()): string[
33
34
  const raw = JSON.parse(readFileSync(configPath, "utf8")) as {
34
35
  vaults?: Record<string, { path?: string }>;
35
36
  };
37
+
36
38
  return Object.values(raw.vaults ?? {})
37
39
  .map((vault) => vault.path)
38
40
  .filter((path): path is string => typeof path === "string" && existsSync(path));
@@ -60,6 +60,7 @@ describe("exportSession", () => {
60
60
  expect(result.success).toBe(true);
61
61
  expect(result.path).toBe(join(vault, "cueloop", "2026-08-07 - Migration Plan.md"));
62
62
  const written = readFileSync(result.path!, "utf8");
63
+
63
64
  expect(written).toBe(`${frontmatter(session, NOW)}\n\n# Migration Plan\n\nMove the store.\n`);
64
65
  expect(written).toContain("created: 2026-08-07T12:34:56.000Z");
65
66
  expect(written).toContain("source: cueloop");
@@ -123,8 +124,10 @@ describe("exportSession", () => {
123
124
  test("falls back to the first auto-detected vault", () => {
124
125
  // Arrange
125
126
  const detected = join(vault, "detected-vault");
127
+
126
128
  mkdirSync(detected);
127
129
  const obsidianJson = join(vault, "obsidian.json");
130
+
128
131
  writeFileSync(obsidianJson, JSON.stringify({ vaults: { v1: { path: detected } } }));
129
132
 
130
133
  // Act
@@ -151,6 +154,7 @@ describe("obsidian extension", () => {
151
154
  // Assert
152
155
  expect(record.errors).toEqual([]);
153
156
  const exporter = record.exporters.get("obsidian");
157
+
154
158
  expect(exporter).toBeDefined();
155
159
 
156
160
  // Act
package/src/export.ts CHANGED
@@ -34,6 +34,7 @@ export const OBSIDIAN_DEFAULTS: ObsidianConfig = {
34
34
  /** approve exports only approvals; resolve exports any verdict; manual never auto-exports. */
35
35
  export function shouldExport(exportOn: ExportOn, verdict: VerdictKind): boolean {
36
36
  if (exportOn === "approve") return verdict === "approve";
37
+
37
38
  return exportOn === "resolve";
38
39
  }
39
40
 
@@ -49,15 +50,19 @@ export function exportSession(
49
50
  now: Date = new Date(),
50
51
  ): ExportResult {
51
52
  const vault = config.vault ?? detectVaults(config.obsidianConfigPath)[0];
53
+
52
54
  if (!vault) return { success: false, error: "no Obsidian vault configured or detected" };
53
55
  if (!existsSync(vault)) return { success: false, error: `vault not found: ${vault}` };
54
56
 
55
57
  const content = session.workingCopy ?? session.artifact.content;
56
58
  const title = titleFrom(content, session.artifact.meta.title);
57
59
  const dir = join(vault, config.folder);
60
+
58
61
  mkdirSync(dir, { recursive: true });
59
62
  const base = formatFilename(config.filenameFormat, title, now, config.separator);
60
63
  const path = uniquePath(dir, base);
64
+
61
65
  writeFileSync(path, `${frontmatter(session, now)}\n\n${content}`);
66
+
62
67
  return { success: true, path };
63
68
  }
@@ -53,6 +53,7 @@ describe("formatFilename", () => {
53
53
 
54
54
  describe("uniquePath", () => {
55
55
  let dir: string;
56
+
56
57
  beforeEach(() => {
57
58
  dir = mkdtempSync(join(tmpdir(), "cueloop-obsidian-name-"));
58
59
  });
package/src/filename.ts CHANGED
@@ -18,12 +18,14 @@ const FORBIDDEN = /[<>:"/\\|?*(){}[\]#~`]/g;
18
18
  export function sanitizeTitle(raw: string): string {
19
19
  const clean = raw.replace(FORBIDDEN, "").replace(/\s+/g, " ").trim();
20
20
  const capped = clean.slice(0, 50).trim();
21
+
21
22
  return capped || "untitled";
22
23
  }
23
24
 
24
25
  /** Title = first H1 of the content, else the fallback, sanitized. */
25
26
  export function titleFrom(content: string, fallback?: string): string {
26
27
  const h1 = content.match(/^#[ \t]+(.+)$/m)?.[1];
28
+
27
29
  return sanitizeTitle(h1 ?? fallback ?? "untitled");
28
30
  }
29
31
 
@@ -43,14 +45,17 @@ export function formatFilename(
43
45
  .replaceAll("{MM}", pad(date.getMonth() + 1))
44
46
  .replaceAll("{DD}", pad(date.getDate()))
45
47
  .replaceAll("{title}", title);
48
+
46
49
  return name.replace(/\s+/g, SEPARATOR_CHAR[separator]);
47
50
  }
48
51
 
49
52
  /** First free path for base.md in dir: base.md, base 2.md, base 3.md, ... */
50
53
  export function uniquePath(dir: string, base: string): string {
51
54
  let candidate = join(dir, `${base}.md`);
55
+
52
56
  for (let n = 2; existsSync(candidate); n++) {
53
57
  candidate = join(dir, `${base} ${n}.md`);
54
58
  }
59
+
55
60
  return candidate;
56
61
  }