@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 +3 -3
- package/src/detect.test.ts +4 -0
- package/src/detect.ts +2 -0
- package/src/export.test.ts +4 -0
- package/src/export.ts +5 -0
- package/src/filename.test.ts +1 -0
- package/src/filename.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cueloop/integration-obsidian",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
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.
|
|
28
|
-
"@cueloop/schema": "0.1.0-alpha.
|
|
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"
|
package/src/detect.test.ts
CHANGED
|
@@ -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));
|
package/src/export.test.ts
CHANGED
|
@@ -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
|
}
|
package/src/filename.test.ts
CHANGED
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
|
}
|