coding-friend-cli 1.16.0 → 1.17.1
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/README.md +12 -0
- package/dist/{chunk-D4EWPGBL.js → chunk-C5LYVVEI.js} +1 -1
- package/dist/{chunk-X5WEODUD.js → chunk-CYQU33FY.js} +1 -0
- package/dist/{chunk-QNLL3ZDF.js → chunk-G6CEEMAR.js} +3 -3
- package/dist/{chunk-4DB4XTSL.js → chunk-KTX4MGMR.js} +15 -1
- package/dist/{chunk-KJUGTLPQ.js → chunk-YO6JKGR3.js} +38 -2
- package/dist/{config-AIZJJ5D2.js → config-LZFXXOI4.js} +276 -14
- package/dist/{dev-WJ5QQ35B.js → dev-R3IYWZ3M.js} +2 -2
- package/dist/{disable-JDVOQNZG.js → disable-R6K5YJN4.js} +2 -2
- package/dist/{enable-JBJ4Q2S7.js → enable-HF4PYVJN.js} +2 -2
- package/dist/{host-NA7LZ4HX.js → host-SYZH3FVC.js} +4 -4
- package/dist/index.js +78 -18
- package/dist/{init-FZ3GG53E.js → init-MF7ISADJ.js} +102 -6
- package/dist/{install-I3GOS56Q.js → install-Q4PWEU43.js} +4 -4
- package/dist/{mcp-DLS3J6QJ.js → mcp-TBEDYELW.js} +4 -4
- package/dist/memory-RGLM35HC.js +647 -0
- package/dist/postinstall.js +1 -1
- package/dist/{session-E3CZJJZQ.js → session-H4XW2WXH.js} +1 -1
- package/dist/{statusline-6HQCDWBD.js → statusline-6Y2EBAFQ.js} +1 -1
- package/dist/{uninstall-JN5YIKKM.js → uninstall-3PSUDGI4.js} +3 -3
- package/dist/{update-OWS4IJTG.js → update-WL6SFGGO.js} +4 -4
- package/lib/cf-memory/CHANGELOG.md +25 -0
- package/lib/cf-memory/README.md +284 -0
- package/lib/cf-memory/package-lock.json +2790 -0
- package/lib/cf-memory/package.json +31 -0
- package/lib/cf-memory/scripts/migrate-frontmatter.ts +134 -0
- package/lib/cf-memory/src/__tests__/daemon-e2e.test.ts +223 -0
- package/lib/cf-memory/src/__tests__/daemon.test.ts +407 -0
- package/lib/cf-memory/src/__tests__/dedup.test.ts +103 -0
- package/lib/cf-memory/src/__tests__/embeddings.test.ts +292 -0
- package/lib/cf-memory/src/__tests__/lazy-install.test.ts +210 -0
- package/lib/cf-memory/src/__tests__/markdown-backend.test.ts +410 -0
- package/lib/cf-memory/src/__tests__/migration.test.ts +255 -0
- package/lib/cf-memory/src/__tests__/migrations.test.ts +288 -0
- package/lib/cf-memory/src/__tests__/minisearch-backend.test.ts +262 -0
- package/lib/cf-memory/src/__tests__/ollama.test.ts +48 -0
- package/lib/cf-memory/src/__tests__/schema.test.ts +128 -0
- package/lib/cf-memory/src/__tests__/search.test.ts +115 -0
- package/lib/cf-memory/src/__tests__/temporal-decay.test.ts +54 -0
- package/lib/cf-memory/src/__tests__/tier.test.ts +293 -0
- package/lib/cf-memory/src/__tests__/tools.test.ts +83 -0
- package/lib/cf-memory/src/backends/markdown.ts +318 -0
- package/lib/cf-memory/src/backends/minisearch.ts +203 -0
- package/lib/cf-memory/src/backends/sqlite/embeddings.ts +286 -0
- package/lib/cf-memory/src/backends/sqlite/index.ts +549 -0
- package/lib/cf-memory/src/backends/sqlite/migrations.ts +188 -0
- package/lib/cf-memory/src/backends/sqlite/schema.ts +120 -0
- package/lib/cf-memory/src/backends/sqlite/search.ts +296 -0
- package/lib/cf-memory/src/bin/cf-memory.ts +2 -0
- package/lib/cf-memory/src/daemon/entry.ts +99 -0
- package/lib/cf-memory/src/daemon/process.ts +271 -0
- package/lib/cf-memory/src/daemon/server.ts +166 -0
- package/lib/cf-memory/src/daemon/watcher.ts +90 -0
- package/lib/cf-memory/src/index.ts +53 -0
- package/lib/cf-memory/src/lib/backend.ts +23 -0
- package/lib/cf-memory/src/lib/daemon-client.ts +163 -0
- package/lib/cf-memory/src/lib/dedup.ts +80 -0
- package/lib/cf-memory/src/lib/lazy-install.ts +274 -0
- package/lib/cf-memory/src/lib/ollama.ts +76 -0
- package/lib/cf-memory/src/lib/temporal-decay.ts +19 -0
- package/lib/cf-memory/src/lib/tier.ts +107 -0
- package/lib/cf-memory/src/lib/types.ts +109 -0
- package/lib/cf-memory/src/resources/index.ts +62 -0
- package/lib/cf-memory/src/server.ts +20 -0
- package/lib/cf-memory/src/tools/delete.ts +38 -0
- package/lib/cf-memory/src/tools/list.ts +38 -0
- package/lib/cf-memory/src/tools/retrieve.ts +52 -0
- package/lib/cf-memory/src/tools/search.ts +47 -0
- package/lib/cf-memory/src/tools/store.ts +70 -0
- package/lib/cf-memory/src/tools/update.ts +62 -0
- package/lib/cf-memory/tsconfig.json +15 -0
- package/lib/cf-memory/vitest.config.ts +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coding-friend-cf-memory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cf-memory": "./dist/bin/cf-memory.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"dev:watch": "tsc --watch --preserveWatchOutput",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@hono/node-server": "^1.19.11",
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
20
|
+
"gray-matter": "^4.0.3",
|
|
21
|
+
"hono": "^4.12.7",
|
|
22
|
+
"minisearch": "^7.2.0",
|
|
23
|
+
"zod": "^3.25.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.19.15",
|
|
27
|
+
"tsx": "^4.0.0",
|
|
28
|
+
"typescript": "^5.7.0",
|
|
29
|
+
"vitest": "^4.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-time migration script: adds `type`, `importance`, `source` fields
|
|
3
|
+
* to existing docs/memory/ markdown files.
|
|
4
|
+
*
|
|
5
|
+
* Uses string manipulation instead of gray-matter stringify to preserve
|
|
6
|
+
* the original date format (YYYY-MM-DD) and tag format ([a, b, c]).
|
|
7
|
+
*
|
|
8
|
+
* Usage: npx tsx scripts/migrate-frontmatter.ts [docsDir]
|
|
9
|
+
* Default docsDir: ../../../../docs/memory (relative to this script)
|
|
10
|
+
*/
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
|
|
14
|
+
const CATEGORY_TO_TYPE: Record<string, string> = {
|
|
15
|
+
features: "fact",
|
|
16
|
+
conventions: "preference",
|
|
17
|
+
decisions: "context",
|
|
18
|
+
bugs: "episode",
|
|
19
|
+
infrastructure: "procedure",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function migrateFile(filePath: string, type: string): boolean {
|
|
23
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
24
|
+
|
|
25
|
+
// Check if the file has frontmatter
|
|
26
|
+
if (!raw.startsWith("---")) return false;
|
|
27
|
+
|
|
28
|
+
const endIdx = raw.indexOf("---", 3);
|
|
29
|
+
if (endIdx === -1) return false;
|
|
30
|
+
|
|
31
|
+
const frontmatter = raw.slice(3, endIdx);
|
|
32
|
+
const body = raw.slice(endIdx + 3);
|
|
33
|
+
|
|
34
|
+
const hasType = /^type:/m.test(frontmatter);
|
|
35
|
+
const hasImportance = /^importance:/m.test(frontmatter);
|
|
36
|
+
const hasSource = /^source:/m.test(frontmatter);
|
|
37
|
+
|
|
38
|
+
// Nothing to add
|
|
39
|
+
if (hasType && hasImportance && hasSource) return false;
|
|
40
|
+
|
|
41
|
+
// Build new fields to insert before the closing ---
|
|
42
|
+
const newFields: string[] = [];
|
|
43
|
+
|
|
44
|
+
// Insert type after tags line
|
|
45
|
+
let updatedFrontmatter = frontmatter;
|
|
46
|
+
|
|
47
|
+
if (!hasType) {
|
|
48
|
+
// Insert type after description line
|
|
49
|
+
updatedFrontmatter = updatedFrontmatter.replace(
|
|
50
|
+
/^(description:.*\n)/m,
|
|
51
|
+
`$1type: ${type}\n`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!hasImportance) {
|
|
56
|
+
// Insert importance before created line
|
|
57
|
+
updatedFrontmatter = updatedFrontmatter.replace(
|
|
58
|
+
/^(created:)/m,
|
|
59
|
+
`importance: 3\n$1`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!hasSource) {
|
|
64
|
+
// Insert source after updated line
|
|
65
|
+
updatedFrontmatter = updatedFrontmatter.replace(
|
|
66
|
+
/^(updated:.*\n)/m,
|
|
67
|
+
`$1source: conversation\n`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const output = `---${updatedFrontmatter}---${body}`;
|
|
72
|
+
fs.writeFileSync(filePath, output, "utf-8");
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function migrate(docsDir: string): void {
|
|
77
|
+
if (!fs.existsSync(docsDir)) {
|
|
78
|
+
console.error(`Directory not found: ${docsDir}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const entries = fs.readdirSync(docsDir, { withFileTypes: true });
|
|
83
|
+
let updated = 0;
|
|
84
|
+
let skipped = 0;
|
|
85
|
+
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
88
|
+
const category = entry.name;
|
|
89
|
+
const catDir = path.join(docsDir, category);
|
|
90
|
+
const files = fs
|
|
91
|
+
.readdirSync(catDir)
|
|
92
|
+
.filter((f) => f.endsWith(".md") && f !== "README.md");
|
|
93
|
+
|
|
94
|
+
const type = CATEGORY_TO_TYPE[category] ?? "fact";
|
|
95
|
+
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
const filePath = path.join(catDir, file);
|
|
98
|
+
if (migrateFile(filePath, type)) {
|
|
99
|
+
console.log(` Updated: ${category}/${file}`);
|
|
100
|
+
updated++;
|
|
101
|
+
} else {
|
|
102
|
+
console.log(` Skipped: ${category}/${file} (already migrated)`);
|
|
103
|
+
skipped++;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Handle root-level markdown files
|
|
109
|
+
if (
|
|
110
|
+
entry.isFile() &&
|
|
111
|
+
entry.name.endsWith(".md") &&
|
|
112
|
+
entry.name !== "README.md"
|
|
113
|
+
) {
|
|
114
|
+
const filePath = path.join(docsDir, entry.name);
|
|
115
|
+
if (migrateFile(filePath, "fact")) {
|
|
116
|
+
console.log(` Updated: ${entry.name} (root)`);
|
|
117
|
+
updated++;
|
|
118
|
+
} else {
|
|
119
|
+
console.log(` Skipped: ${entry.name} (already migrated)`);
|
|
120
|
+
skipped++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(`\nDone. Updated: ${updated}, Skipped: ${skipped}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// CLI entry
|
|
129
|
+
const docsDir =
|
|
130
|
+
process.argv[2] ??
|
|
131
|
+
path.resolve(import.meta.dirname, "../../../../docs/memory");
|
|
132
|
+
|
|
133
|
+
console.log(`Migrating frontmatter in: ${docsDir}\n`);
|
|
134
|
+
migrate(docsDir);
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdirSync, rmSync, existsSync, readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import http from "node:http";
|
|
6
|
+
import matter from "gray-matter";
|
|
7
|
+
import { MiniSearchBackend } from "../backends/minisearch.js";
|
|
8
|
+
import { startDaemonServer, type DaemonPaths } from "../daemon/process.js";
|
|
9
|
+
import { DaemonClient } from "../lib/daemon-client.js";
|
|
10
|
+
|
|
11
|
+
let testDir: string;
|
|
12
|
+
let docsDir: string;
|
|
13
|
+
let testPaths: DaemonPaths;
|
|
14
|
+
let handle: ReturnType<typeof startDaemonServer> | null = null;
|
|
15
|
+
let client: DaemonClient;
|
|
16
|
+
let counter = 0;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
testDir = join(tmpdir(), `cf-memory-e2e-${Date.now()}-${++counter}`);
|
|
20
|
+
docsDir = join(testDir, "docs");
|
|
21
|
+
mkdirSync(docsDir, { recursive: true });
|
|
22
|
+
|
|
23
|
+
const daemonDir = join(testDir, "daemon");
|
|
24
|
+
mkdirSync(daemonDir, { recursive: true });
|
|
25
|
+
|
|
26
|
+
testPaths = {
|
|
27
|
+
socketPath: join(daemonDir, "daemon.sock"),
|
|
28
|
+
pidFile: join(daemonDir, "daemon.pid"),
|
|
29
|
+
logFile: join(daemonDir, "daemon.log"),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const backend = new MiniSearchBackend(docsDir);
|
|
33
|
+
handle = startDaemonServer(backend, {
|
|
34
|
+
paths: testPaths,
|
|
35
|
+
idleTimeoutMs: 0,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
client = new DaemonClient(testPaths.socketPath);
|
|
39
|
+
|
|
40
|
+
// Wait for server to be listening
|
|
41
|
+
await new Promise<void>((resolve) => {
|
|
42
|
+
handle!.server.once("listening", resolve);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(async () => {
|
|
47
|
+
if (handle) {
|
|
48
|
+
await new Promise<void>((resolve) => {
|
|
49
|
+
handle!.server.close(() => resolve());
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
rmSync(testPaths.socketPath, { force: true });
|
|
53
|
+
rmSync(testPaths.pidFile, { force: true });
|
|
54
|
+
} catch {
|
|
55
|
+
// ignore
|
|
56
|
+
}
|
|
57
|
+
handle = null;
|
|
58
|
+
}
|
|
59
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("Daemon E2E: DaemonClient → Daemon → MiniSearch → Markdown", () => {
|
|
63
|
+
it("full pipeline: store → search → retrieve → update → delete", async () => {
|
|
64
|
+
// Store
|
|
65
|
+
const memory = await client.store({
|
|
66
|
+
title: "E2E Auth Pattern",
|
|
67
|
+
description: "JWT auth for API endpoints",
|
|
68
|
+
type: "fact",
|
|
69
|
+
tags: ["auth", "jwt"],
|
|
70
|
+
content: "# Auth\n\nUses RS256 signed JWT tokens.",
|
|
71
|
+
});
|
|
72
|
+
expect(memory.id).toBe("features/e2e-auth-pattern");
|
|
73
|
+
|
|
74
|
+
// Verify file on disk
|
|
75
|
+
const filePath = join(docsDir, "features", "e2e-auth-pattern.md");
|
|
76
|
+
expect(existsSync(filePath)).toBe(true);
|
|
77
|
+
const raw = matter(readFileSync(filePath, "utf-8"));
|
|
78
|
+
expect(raw.data.title).toBe("E2E Auth Pattern");
|
|
79
|
+
expect(raw.data.type).toBe("fact");
|
|
80
|
+
|
|
81
|
+
// Search via daemon
|
|
82
|
+
const searchResults = await client.search({ query: "auth" });
|
|
83
|
+
expect(searchResults.length).toBeGreaterThan(0);
|
|
84
|
+
|
|
85
|
+
// Retrieve
|
|
86
|
+
const retrieved = await client.retrieve("features/e2e-auth-pattern");
|
|
87
|
+
expect(retrieved).not.toBeNull();
|
|
88
|
+
expect(retrieved!.frontmatter.title).toBe("E2E Auth Pattern");
|
|
89
|
+
expect(retrieved!.content).toContain("RS256");
|
|
90
|
+
|
|
91
|
+
// Update
|
|
92
|
+
const updated = await client.update({
|
|
93
|
+
id: "features/e2e-auth-pattern",
|
|
94
|
+
title: "Updated E2E Auth Pattern",
|
|
95
|
+
tags: ["security"],
|
|
96
|
+
});
|
|
97
|
+
expect(updated).not.toBeNull();
|
|
98
|
+
expect(updated!.frontmatter.title).toBe("Updated E2E Auth Pattern");
|
|
99
|
+
expect(updated!.frontmatter.tags).toContain("security");
|
|
100
|
+
expect(updated!.frontmatter.tags).toContain("auth");
|
|
101
|
+
|
|
102
|
+
// Verify update on disk
|
|
103
|
+
const updatedRaw = matter(readFileSync(filePath, "utf-8"));
|
|
104
|
+
expect(updatedRaw.data.title).toBe("Updated E2E Auth Pattern");
|
|
105
|
+
|
|
106
|
+
// Delete
|
|
107
|
+
const deleted = await client.delete("features/e2e-auth-pattern");
|
|
108
|
+
expect(deleted).toBe(true);
|
|
109
|
+
|
|
110
|
+
// Verify deleted from disk
|
|
111
|
+
expect(existsSync(filePath)).toBe(false);
|
|
112
|
+
|
|
113
|
+
// Verify search no longer finds it
|
|
114
|
+
const afterDelete = await client.search({ query: "e2e auth" });
|
|
115
|
+
expect(afterDelete.length).toBe(0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("list returns all stored memories", async () => {
|
|
119
|
+
await client.store({
|
|
120
|
+
title: "Memory One",
|
|
121
|
+
description: "First memory",
|
|
122
|
+
type: "fact",
|
|
123
|
+
tags: ["one"],
|
|
124
|
+
content: "Content one.",
|
|
125
|
+
});
|
|
126
|
+
await client.store({
|
|
127
|
+
title: "Memory Two",
|
|
128
|
+
description: "Second memory",
|
|
129
|
+
type: "episode",
|
|
130
|
+
tags: ["two"],
|
|
131
|
+
content: "Content two.",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const list = await client.list({});
|
|
135
|
+
expect(list.length).toBe(2);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("stats reflects stored memories", async () => {
|
|
139
|
+
await client.store({
|
|
140
|
+
title: "A Fact",
|
|
141
|
+
description: "Fact desc",
|
|
142
|
+
type: "fact",
|
|
143
|
+
tags: [],
|
|
144
|
+
content: "Fact content.",
|
|
145
|
+
});
|
|
146
|
+
await client.store({
|
|
147
|
+
title: "A Bug",
|
|
148
|
+
description: "Bug desc",
|
|
149
|
+
type: "episode",
|
|
150
|
+
tags: [],
|
|
151
|
+
content: "Bug content.",
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const stats = await client.stats();
|
|
155
|
+
expect(stats.total).toBe(2);
|
|
156
|
+
expect(stats.byType.fact).toBe(1);
|
|
157
|
+
expect(stats.byType.episode).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("rebuild rebuilds the search index", async () => {
|
|
161
|
+
await client.store({
|
|
162
|
+
title: "Before Rebuild",
|
|
163
|
+
description: "Test rebuild",
|
|
164
|
+
type: "fact",
|
|
165
|
+
tags: ["rebuild"],
|
|
166
|
+
content: "Before rebuild content.",
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await client.rebuild();
|
|
170
|
+
|
|
171
|
+
// Search still works after rebuild
|
|
172
|
+
const results = await client.search({ query: "rebuild" });
|
|
173
|
+
expect(results.length).toBeGreaterThan(0);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("ping returns true when daemon is running", async () => {
|
|
177
|
+
const alive = await client.ping();
|
|
178
|
+
expect(alive).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("retrieve returns null for non-existent memory", async () => {
|
|
182
|
+
const result = await client.retrieve("features/nonexistent");
|
|
183
|
+
expect(result).toBeNull();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("delete returns false for non-existent memory", async () => {
|
|
187
|
+
const result = await client.delete("features/nonexistent");
|
|
188
|
+
expect(result).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("update returns null for non-existent memory", async () => {
|
|
192
|
+
const result = await client.update({
|
|
193
|
+
id: "features/nonexistent",
|
|
194
|
+
title: "Test",
|
|
195
|
+
});
|
|
196
|
+
expect(result).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("DaemonClient fallback", () => {
|
|
201
|
+
it("ping returns false when daemon not reachable", async () => {
|
|
202
|
+
const deadClient = new DaemonClient(
|
|
203
|
+
"/tmp/nonexistent-socket-" + Date.now(),
|
|
204
|
+
);
|
|
205
|
+
const alive = await deadClient.ping();
|
|
206
|
+
expect(alive).toBe(false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("store throws when daemon not reachable", async () => {
|
|
210
|
+
const deadClient = new DaemonClient(
|
|
211
|
+
"/tmp/nonexistent-socket-" + Date.now(),
|
|
212
|
+
);
|
|
213
|
+
await expect(
|
|
214
|
+
deadClient.store({
|
|
215
|
+
title: "Test",
|
|
216
|
+
description: "Test",
|
|
217
|
+
type: "fact",
|
|
218
|
+
tags: [],
|
|
219
|
+
content: "Test",
|
|
220
|
+
}),
|
|
221
|
+
).rejects.toThrow();
|
|
222
|
+
});
|
|
223
|
+
});
|