fossel 1.0.1 → 1.0.8
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/LICENSE +21 -21
- package/README.md +108 -13
- package/dist/cli.js +1020 -0
- package/dist/index.js +469 -60
- package/package.json +5 -3
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1020 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/db/migrate.ts
|
|
13
|
+
function hasColumn(db, tableName, columnName) {
|
|
14
|
+
const columns = db.prepare(`PRAGMA table_info(${tableName})`).all();
|
|
15
|
+
return columns.some((column) => column.name === columnName);
|
|
16
|
+
}
|
|
17
|
+
function runMigrations(db) {
|
|
18
|
+
db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
name TEXT NOT NULL UNIQUE,
|
|
22
|
+
applied_at INTEGER NOT NULL
|
|
23
|
+
);
|
|
24
|
+
`);
|
|
25
|
+
const appliedRows = db.prepare("SELECT name FROM migrations").all();
|
|
26
|
+
const applied = new Set(appliedRows.map((row) => row.name));
|
|
27
|
+
const insertMigration = db.prepare(`
|
|
28
|
+
INSERT INTO migrations (name, applied_at)
|
|
29
|
+
VALUES (?, ?)
|
|
30
|
+
`);
|
|
31
|
+
for (const migration of migrations) {
|
|
32
|
+
if (applied.has(migration.name)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const applyTx = db.transaction(() => {
|
|
36
|
+
migration.apply(db);
|
|
37
|
+
insertMigration.run(migration.name, Math.floor(Date.now() / 1e3));
|
|
38
|
+
});
|
|
39
|
+
applyTx();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
var migrations;
|
|
43
|
+
var init_migrate = __esm({
|
|
44
|
+
"src/db/migrate.ts"() {
|
|
45
|
+
"use strict";
|
|
46
|
+
migrations = [
|
|
47
|
+
{
|
|
48
|
+
name: "001_init_memories_schema",
|
|
49
|
+
apply: (db) => {
|
|
50
|
+
db.exec(`
|
|
51
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
52
|
+
id TEXT PRIMARY KEY,
|
|
53
|
+
repo TEXT NOT NULL,
|
|
54
|
+
type TEXT NOT NULL CHECK (type IN ('convention', 'bug_fix', 'reviewer_pattern', 'decision', 'issue', 'general')),
|
|
55
|
+
note TEXT NOT NULL,
|
|
56
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
57
|
+
created_at INTEGER NOT NULL
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_memories_repo ON memories (repo);
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories (created_at DESC);
|
|
62
|
+
|
|
63
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
64
|
+
repo,
|
|
65
|
+
note,
|
|
66
|
+
content = 'memories',
|
|
67
|
+
content_rowid = 'rowid'
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
71
|
+
INSERT INTO memories_fts(rowid, repo, note) VALUES (new.rowid, new.repo, new.note);
|
|
72
|
+
END;
|
|
73
|
+
|
|
74
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
75
|
+
INSERT INTO memories_fts(memories_fts, rowid, repo, note) VALUES ('delete', old.rowid, old.repo, old.note);
|
|
76
|
+
END;
|
|
77
|
+
|
|
78
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
79
|
+
INSERT INTO memories_fts(memories_fts, rowid, repo, note) VALUES ('delete', old.rowid, old.repo, old.note);
|
|
80
|
+
INSERT INTO memories_fts(rowid, repo, note) VALUES (new.rowid, new.repo, new.note);
|
|
81
|
+
END;
|
|
82
|
+
`);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "002_add_memories_updated_at",
|
|
87
|
+
apply: (db) => {
|
|
88
|
+
if (!hasColumn(db, "memories", "updated_at")) {
|
|
89
|
+
db.exec(`
|
|
90
|
+
ALTER TABLE memories
|
|
91
|
+
ADD COLUMN updated_at INTEGER NOT NULL DEFAULT 0;
|
|
92
|
+
`);
|
|
93
|
+
db.exec(`
|
|
94
|
+
UPDATE memories
|
|
95
|
+
SET updated_at = created_at
|
|
96
|
+
WHERE updated_at = 0;
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "003_add_memories_pinned",
|
|
103
|
+
apply: (db) => {
|
|
104
|
+
if (!hasColumn(db, "memories", "pinned")) {
|
|
105
|
+
db.exec(`
|
|
106
|
+
ALTER TABLE memories
|
|
107
|
+
ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;
|
|
108
|
+
`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// src/db/client.ts
|
|
117
|
+
import Database from "better-sqlite3";
|
|
118
|
+
import { mkdirSync } from "fs";
|
|
119
|
+
import { dirname } from "path";
|
|
120
|
+
function initDb(dbPath) {
|
|
121
|
+
if (dbInstance) {
|
|
122
|
+
return dbInstance;
|
|
123
|
+
}
|
|
124
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
125
|
+
const db = new Database(dbPath);
|
|
126
|
+
db.pragma("journal_mode = WAL");
|
|
127
|
+
db.pragma("foreign_keys = ON");
|
|
128
|
+
runMigrations(db);
|
|
129
|
+
dbInstance = db;
|
|
130
|
+
return db;
|
|
131
|
+
}
|
|
132
|
+
function getDb() {
|
|
133
|
+
if (!dbInstance) {
|
|
134
|
+
throw new Error("Database has not been initialized. Call initDb() first.");
|
|
135
|
+
}
|
|
136
|
+
return dbInstance;
|
|
137
|
+
}
|
|
138
|
+
function closeDb() {
|
|
139
|
+
if (!dbInstance) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
dbInstance.close();
|
|
143
|
+
dbInstance = null;
|
|
144
|
+
}
|
|
145
|
+
var MEMORY_TYPES, dbInstance;
|
|
146
|
+
var init_client = __esm({
|
|
147
|
+
"src/db/client.ts"() {
|
|
148
|
+
"use strict";
|
|
149
|
+
init_migrate();
|
|
150
|
+
MEMORY_TYPES = [
|
|
151
|
+
"convention",
|
|
152
|
+
"bug_fix",
|
|
153
|
+
"reviewer_pattern",
|
|
154
|
+
"decision",
|
|
155
|
+
"issue",
|
|
156
|
+
"general"
|
|
157
|
+
];
|
|
158
|
+
dbInstance = null;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// src/tools/delete.ts
|
|
163
|
+
import { z } from "zod";
|
|
164
|
+
function registerDeleteMemoryTool(server) {
|
|
165
|
+
server.registerTool(
|
|
166
|
+
"delete_memory",
|
|
167
|
+
{
|
|
168
|
+
description: "Delete a memory from storage by id.",
|
|
169
|
+
inputSchema: deleteMemoryInputSchema
|
|
170
|
+
},
|
|
171
|
+
async ({ id }) => {
|
|
172
|
+
try {
|
|
173
|
+
const db = getDb();
|
|
174
|
+
const row = db.prepare("SELECT id FROM memories WHERE id = ?").get(id);
|
|
175
|
+
if (!row) {
|
|
176
|
+
return {
|
|
177
|
+
isError: true,
|
|
178
|
+
content: [
|
|
179
|
+
{
|
|
180
|
+
type: "text",
|
|
181
|
+
text: `Memory ${id} not found.`
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const deleteTx = db.transaction((memoryId) => {
|
|
187
|
+
db.prepare("DELETE FROM memories WHERE id = ?").run(memoryId);
|
|
188
|
+
});
|
|
189
|
+
deleteTx(id);
|
|
190
|
+
return {
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: "text",
|
|
194
|
+
text: `Deleted memory ${id}.`
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
};
|
|
198
|
+
} catch (error) {
|
|
199
|
+
const message = error instanceof Error ? error.message : "Unknown error while deleting memory.";
|
|
200
|
+
return {
|
|
201
|
+
isError: true,
|
|
202
|
+
content: [
|
|
203
|
+
{
|
|
204
|
+
type: "text",
|
|
205
|
+
text: `Failed to delete memory: ${message}`
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
var deleteMemoryInputSchema;
|
|
214
|
+
var init_delete = __esm({
|
|
215
|
+
"src/tools/delete.ts"() {
|
|
216
|
+
"use strict";
|
|
217
|
+
init_client();
|
|
218
|
+
deleteMemoryInputSchema = {
|
|
219
|
+
id: z.string().trim().min(1, "id is required")
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// src/tools/get-repo.ts
|
|
225
|
+
import { z as z2 } from "zod";
|
|
226
|
+
function parseTags(raw) {
|
|
227
|
+
try {
|
|
228
|
+
const parsed = JSON.parse(raw);
|
|
229
|
+
return Array.isArray(parsed) ? parsed.filter((value) => typeof value === "string") : [];
|
|
230
|
+
} catch {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function formatTypeHeading(type) {
|
|
235
|
+
return type.split("_").map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
|
|
236
|
+
}
|
|
237
|
+
function registerGetRepoContextTool(server) {
|
|
238
|
+
server.registerTool(
|
|
239
|
+
"get_repo_context",
|
|
240
|
+
{
|
|
241
|
+
description: "Get recent memories for a repository grouped by memory type.",
|
|
242
|
+
inputSchema: getRepoContextInputSchema
|
|
243
|
+
},
|
|
244
|
+
async ({ repo, limit }) => {
|
|
245
|
+
try {
|
|
246
|
+
const db = getDb();
|
|
247
|
+
const rows = db.prepare(
|
|
248
|
+
`
|
|
249
|
+
SELECT rowid AS row_id, id, repo, type, note, tags, created_at, updated_at, pinned
|
|
250
|
+
FROM memories
|
|
251
|
+
WHERE repo = ?
|
|
252
|
+
ORDER BY pinned DESC, updated_at DESC
|
|
253
|
+
LIMIT ?
|
|
254
|
+
`
|
|
255
|
+
).all(repo, limit);
|
|
256
|
+
if (rows.length === 0) {
|
|
257
|
+
return {
|
|
258
|
+
content: [
|
|
259
|
+
{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: `No memories found for ${repo}.`
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
267
|
+
for (const memory of rows) {
|
|
268
|
+
const tags = parseTags(memory.tags);
|
|
269
|
+
const tagSuffix = tags.length > 0 ? ` [tags: ${tags.join(", ")}]` : "";
|
|
270
|
+
const pinPrefix = memory.pinned ? "\u{1F4CC} Pinned " : "";
|
|
271
|
+
const item = `- (${memory.row_id} | legacy: ${memory.id}) ${pinPrefix}${memory.note}${tagSuffix}`;
|
|
272
|
+
const existing = grouped.get(memory.type) ?? [];
|
|
273
|
+
existing.push(item);
|
|
274
|
+
grouped.set(memory.type, existing);
|
|
275
|
+
}
|
|
276
|
+
const sections = [];
|
|
277
|
+
for (const type of MEMORY_TYPES) {
|
|
278
|
+
const entries = grouped.get(type);
|
|
279
|
+
if (!entries || entries.length === 0) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
sections.push(`${formatTypeHeading(type)}
|
|
283
|
+
${entries.join("\n")}`);
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
content: [
|
|
287
|
+
{
|
|
288
|
+
type: "text",
|
|
289
|
+
text: `Repository context for ${repo}
|
|
290
|
+
Total memories: ${rows.length}
|
|
291
|
+
|
|
292
|
+
${sections.join("\n\n")}`
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
};
|
|
296
|
+
} catch (error) {
|
|
297
|
+
const message = error instanceof Error ? error.message : "Unknown error while retrieving repository context.";
|
|
298
|
+
return {
|
|
299
|
+
isError: true,
|
|
300
|
+
content: [
|
|
301
|
+
{
|
|
302
|
+
type: "text",
|
|
303
|
+
text: `Failed to fetch repository context: ${message}`
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
var getRepoContextInputSchema;
|
|
312
|
+
var init_get_repo = __esm({
|
|
313
|
+
"src/tools/get-repo.ts"() {
|
|
314
|
+
"use strict";
|
|
315
|
+
init_client();
|
|
316
|
+
getRepoContextInputSchema = {
|
|
317
|
+
repo: z2.string().trim().min(1, "repo is required"),
|
|
318
|
+
limit: z2.number().int().positive().max(100).default(10)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// src/tools/pin.ts
|
|
324
|
+
import { z as z3 } from "zod";
|
|
325
|
+
function setPinnedState(memoryId, pinned) {
|
|
326
|
+
const db = getDb();
|
|
327
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
328
|
+
const updateResult = db.prepare(
|
|
329
|
+
`
|
|
330
|
+
UPDATE memories
|
|
331
|
+
SET pinned = ?, updated_at = ?
|
|
332
|
+
WHERE rowid = ?
|
|
333
|
+
`
|
|
334
|
+
).run(pinned, now, memoryId);
|
|
335
|
+
if (updateResult.changes === 0) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
return db.prepare(
|
|
339
|
+
`
|
|
340
|
+
SELECT rowid AS row_id, note, pinned
|
|
341
|
+
FROM memories
|
|
342
|
+
WHERE rowid = ?
|
|
343
|
+
`
|
|
344
|
+
).get(memoryId);
|
|
345
|
+
}
|
|
346
|
+
function registerPinMemoryTool(server) {
|
|
347
|
+
server.registerTool(
|
|
348
|
+
"pin_memory",
|
|
349
|
+
{
|
|
350
|
+
description: "Pin a memory to keep it at the top of repository context.",
|
|
351
|
+
inputSchema: pinInputSchema
|
|
352
|
+
},
|
|
353
|
+
async ({ id }) => {
|
|
354
|
+
try {
|
|
355
|
+
const memory = setPinnedState(id, 1);
|
|
356
|
+
if (!memory) {
|
|
357
|
+
return {
|
|
358
|
+
isError: true,
|
|
359
|
+
content: [
|
|
360
|
+
{
|
|
361
|
+
type: "text",
|
|
362
|
+
text: `Memory ${id} not found.`
|
|
363
|
+
}
|
|
364
|
+
]
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
content: [
|
|
369
|
+
{
|
|
370
|
+
type: "text",
|
|
371
|
+
text: `Pinned memory ${memory.row_id}: ${memory.note}`
|
|
372
|
+
}
|
|
373
|
+
]
|
|
374
|
+
};
|
|
375
|
+
} catch (error) {
|
|
376
|
+
const message = error instanceof Error ? error.message : "Unknown error while pinning memory.";
|
|
377
|
+
return {
|
|
378
|
+
isError: true,
|
|
379
|
+
content: [
|
|
380
|
+
{
|
|
381
|
+
type: "text",
|
|
382
|
+
text: `Failed to pin memory: ${message}`
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
function registerUnpinMemoryTool(server) {
|
|
391
|
+
server.registerTool(
|
|
392
|
+
"unpin_memory",
|
|
393
|
+
{
|
|
394
|
+
description: "Unpin a previously pinned memory.",
|
|
395
|
+
inputSchema: pinInputSchema
|
|
396
|
+
},
|
|
397
|
+
async ({ id }) => {
|
|
398
|
+
try {
|
|
399
|
+
const memory = setPinnedState(id, 0);
|
|
400
|
+
if (!memory) {
|
|
401
|
+
return {
|
|
402
|
+
isError: true,
|
|
403
|
+
content: [
|
|
404
|
+
{
|
|
405
|
+
type: "text",
|
|
406
|
+
text: `Memory ${id} not found.`
|
|
407
|
+
}
|
|
408
|
+
]
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
content: [
|
|
413
|
+
{
|
|
414
|
+
type: "text",
|
|
415
|
+
text: `Unpinned memory ${memory.row_id}.`
|
|
416
|
+
}
|
|
417
|
+
]
|
|
418
|
+
};
|
|
419
|
+
} catch (error) {
|
|
420
|
+
const message = error instanceof Error ? error.message : "Unknown error while unpinning memory.";
|
|
421
|
+
return {
|
|
422
|
+
isError: true,
|
|
423
|
+
content: [
|
|
424
|
+
{
|
|
425
|
+
type: "text",
|
|
426
|
+
text: `Failed to unpin memory: ${message}`
|
|
427
|
+
}
|
|
428
|
+
]
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
var pinInputSchema;
|
|
435
|
+
var init_pin = __esm({
|
|
436
|
+
"src/tools/pin.ts"() {
|
|
437
|
+
"use strict";
|
|
438
|
+
init_client();
|
|
439
|
+
pinInputSchema = {
|
|
440
|
+
id: z3.number().int().positive()
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// src/tools/search.ts
|
|
446
|
+
import { z as z4 } from "zod";
|
|
447
|
+
function normalizeFtsQuery(query) {
|
|
448
|
+
const terms = query.trim().split(/\s+/).map((term) => term.replaceAll('"', '""')).filter(Boolean);
|
|
449
|
+
if (terms.length === 0) {
|
|
450
|
+
throw new Error("query must contain searchable text");
|
|
451
|
+
}
|
|
452
|
+
return terms.map((term) => `"${term}"`).join(" AND ");
|
|
453
|
+
}
|
|
454
|
+
function parseTags2(raw) {
|
|
455
|
+
try {
|
|
456
|
+
const parsed = JSON.parse(raw);
|
|
457
|
+
return Array.isArray(parsed) ? parsed.filter((value) => typeof value === "string") : [];
|
|
458
|
+
} catch {
|
|
459
|
+
return [];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function registerSearchMemoryTool(server) {
|
|
463
|
+
server.registerTool(
|
|
464
|
+
"search_memory",
|
|
465
|
+
{
|
|
466
|
+
description: "Search memories using full-text search with optional repository filtering.",
|
|
467
|
+
inputSchema: searchMemoryInputSchema
|
|
468
|
+
},
|
|
469
|
+
async ({ query, repo, limit }) => {
|
|
470
|
+
try {
|
|
471
|
+
const db = getDb();
|
|
472
|
+
const ftsQuery = normalizeFtsQuery(query);
|
|
473
|
+
const rows = repo ? db.prepare(
|
|
474
|
+
`
|
|
475
|
+
SELECT m.rowid AS row_id, m.id, m.repo, m.type, m.note, m.tags, m.created_at, m.updated_at, m.pinned, bm25(memories_fts) AS rank
|
|
476
|
+
FROM memories_fts
|
|
477
|
+
JOIN memories AS m ON m.rowid = memories_fts.rowid
|
|
478
|
+
WHERE memories_fts MATCH ? AND m.repo = ?
|
|
479
|
+
ORDER BY rank
|
|
480
|
+
LIMIT ?
|
|
481
|
+
`
|
|
482
|
+
).all(ftsQuery, repo, limit) : db.prepare(
|
|
483
|
+
`
|
|
484
|
+
SELECT m.rowid AS row_id, m.id, m.repo, m.type, m.note, m.tags, m.created_at, m.updated_at, m.pinned, bm25(memories_fts) AS rank
|
|
485
|
+
FROM memories_fts
|
|
486
|
+
JOIN memories AS m ON m.rowid = memories_fts.rowid
|
|
487
|
+
WHERE memories_fts MATCH ?
|
|
488
|
+
ORDER BY rank
|
|
489
|
+
LIMIT ?
|
|
490
|
+
`
|
|
491
|
+
).all(ftsQuery, limit);
|
|
492
|
+
if (rows.length === 0) {
|
|
493
|
+
return {
|
|
494
|
+
content: [
|
|
495
|
+
{
|
|
496
|
+
type: "text",
|
|
497
|
+
text: repo ? `No memories matched "${query}" in ${repo}.` : `No memories matched "${query}".`
|
|
498
|
+
}
|
|
499
|
+
]
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
const formatted = rows.map((row, index) => {
|
|
503
|
+
const tags = parseTags2(row.tags);
|
|
504
|
+
const tagsText = tags.length > 0 ? ` | tags: ${tags.join(", ")}` : "";
|
|
505
|
+
const pinPrefix = row.pinned ? "\u{1F4CC} Pinned " : "";
|
|
506
|
+
return `${index + 1}. [${row.repo}] ${row.type} (${row.row_id} | legacy: ${row.id})
|
|
507
|
+
${pinPrefix}${row.note}${tagsText}`;
|
|
508
|
+
}).join("\n\n");
|
|
509
|
+
return {
|
|
510
|
+
content: [
|
|
511
|
+
{
|
|
512
|
+
type: "text",
|
|
513
|
+
text: `Search results for "${query}"${repo ? ` in ${repo}` : ""}:
|
|
514
|
+
|
|
515
|
+
${formatted}`
|
|
516
|
+
}
|
|
517
|
+
]
|
|
518
|
+
};
|
|
519
|
+
} catch (error) {
|
|
520
|
+
const message = error instanceof Error ? error.message : "Unknown error while searching memory.";
|
|
521
|
+
return {
|
|
522
|
+
isError: true,
|
|
523
|
+
content: [
|
|
524
|
+
{
|
|
525
|
+
type: "text",
|
|
526
|
+
text: `Failed to search memories: ${message}`
|
|
527
|
+
}
|
|
528
|
+
]
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
var searchMemoryInputSchema;
|
|
535
|
+
var init_search = __esm({
|
|
536
|
+
"src/tools/search.ts"() {
|
|
537
|
+
"use strict";
|
|
538
|
+
init_client();
|
|
539
|
+
searchMemoryInputSchema = {
|
|
540
|
+
query: z4.string().trim().min(1, "query is required"),
|
|
541
|
+
repo: z4.string().trim().min(1).optional(),
|
|
542
|
+
limit: z4.number().int().positive().max(50).default(5)
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// src/tools/store.ts
|
|
548
|
+
import { nanoid } from "nanoid";
|
|
549
|
+
import { z as z5 } from "zod";
|
|
550
|
+
function registerStoreContextTool(server) {
|
|
551
|
+
server.registerTool(
|
|
552
|
+
"store_context",
|
|
553
|
+
{
|
|
554
|
+
description: "Store repository-specific contributor context such as bug fixes, conventions, and decisions.",
|
|
555
|
+
inputSchema: storeContextInputSchema
|
|
556
|
+
},
|
|
557
|
+
async ({ repo, type, note, tags }) => {
|
|
558
|
+
try {
|
|
559
|
+
const db = getDb();
|
|
560
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
561
|
+
const id = nanoid();
|
|
562
|
+
const normalizedTags = Array.from(
|
|
563
|
+
new Set((tags ?? []).map((tag) => tag.trim()).filter(Boolean))
|
|
564
|
+
);
|
|
565
|
+
db.prepare(
|
|
566
|
+
`
|
|
567
|
+
INSERT INTO memories (id, repo, type, note, tags, created_at, updated_at)
|
|
568
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
569
|
+
`
|
|
570
|
+
).run(id, repo, type, note, JSON.stringify(normalizedTags), now, now);
|
|
571
|
+
const stored = db.prepare(
|
|
572
|
+
`
|
|
573
|
+
SELECT rowid AS row_id, id
|
|
574
|
+
FROM memories
|
|
575
|
+
WHERE id = ?
|
|
576
|
+
`
|
|
577
|
+
).get(id);
|
|
578
|
+
return {
|
|
579
|
+
content: [
|
|
580
|
+
{
|
|
581
|
+
type: "text",
|
|
582
|
+
text: `Stored memory ${id} (numeric id: ${stored?.row_id ?? "unknown"}) for ${repo} (${type}).`
|
|
583
|
+
}
|
|
584
|
+
]
|
|
585
|
+
};
|
|
586
|
+
} catch (error) {
|
|
587
|
+
const message = error instanceof Error ? error.message : "Unknown error while storing memory.";
|
|
588
|
+
return {
|
|
589
|
+
isError: true,
|
|
590
|
+
content: [
|
|
591
|
+
{
|
|
592
|
+
type: "text",
|
|
593
|
+
text: `Failed to store memory: ${message}`
|
|
594
|
+
}
|
|
595
|
+
]
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
var storeContextInputSchema;
|
|
602
|
+
var init_store = __esm({
|
|
603
|
+
"src/tools/store.ts"() {
|
|
604
|
+
"use strict";
|
|
605
|
+
init_client();
|
|
606
|
+
storeContextInputSchema = {
|
|
607
|
+
repo: z5.string().trim().min(1, "repo is required"),
|
|
608
|
+
type: z5.enum(MEMORY_TYPES),
|
|
609
|
+
note: z5.string().trim().min(1, "note is required"),
|
|
610
|
+
tags: z5.array(z5.string().trim().min(1)).optional()
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// src/tools/summarize.ts
|
|
616
|
+
import { z as z6 } from "zod";
|
|
617
|
+
function registerSummarizeRepoContextTool(server) {
|
|
618
|
+
server.registerTool(
|
|
619
|
+
"summarize_repo_context",
|
|
620
|
+
{
|
|
621
|
+
description: "Generate a structured markdown summary of all memories for a repository.",
|
|
622
|
+
inputSchema: summarizeRepoContextInputSchema
|
|
623
|
+
},
|
|
624
|
+
async ({ repo }) => {
|
|
625
|
+
try {
|
|
626
|
+
const db = getDb();
|
|
627
|
+
const rows = db.prepare(
|
|
628
|
+
`
|
|
629
|
+
SELECT rowid AS row_id, type, note, pinned
|
|
630
|
+
FROM memories
|
|
631
|
+
WHERE repo = ?
|
|
632
|
+
ORDER BY pinned DESC, updated_at DESC
|
|
633
|
+
`
|
|
634
|
+
).all(repo);
|
|
635
|
+
if (rows.length === 0) {
|
|
636
|
+
return {
|
|
637
|
+
content: [
|
|
638
|
+
{
|
|
639
|
+
type: "text",
|
|
640
|
+
text: `Fossel Context Summary: ${repo}
|
|
641
|
+
|
|
642
|
+
No memories found.`
|
|
643
|
+
}
|
|
644
|
+
]
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
const pinnedLines = rows.filter((row) => row.pinned === 1).map((row) => `- (${row.row_id}) ${row.note}`);
|
|
648
|
+
const sections = [`Fossel Context Summary: ${repo}`];
|
|
649
|
+
if (pinnedLines.length > 0) {
|
|
650
|
+
sections.push(`\u{1F4CC} Pinned
|
|
651
|
+
${pinnedLines.join("\n")}`);
|
|
652
|
+
}
|
|
653
|
+
for (const type of MEMORY_TYPES) {
|
|
654
|
+
const entries = rows.filter((row) => row.type === type).map((row) => `- (${row.row_id}) ${row.note}`);
|
|
655
|
+
if (entries.length === 0) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
sections.push(`${sectionTitleByType[type]}
|
|
659
|
+
${entries.join("\n")}`);
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
content: [
|
|
663
|
+
{
|
|
664
|
+
type: "text",
|
|
665
|
+
text: sections.join("\n\n")
|
|
666
|
+
}
|
|
667
|
+
]
|
|
668
|
+
};
|
|
669
|
+
} catch (error) {
|
|
670
|
+
const message = error instanceof Error ? error.message : "Unknown error while summarizing repository context.";
|
|
671
|
+
return {
|
|
672
|
+
isError: true,
|
|
673
|
+
content: [
|
|
674
|
+
{
|
|
675
|
+
type: "text",
|
|
676
|
+
text: `Failed to summarize repository context: ${message}`
|
|
677
|
+
}
|
|
678
|
+
]
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
var summarizeRepoContextInputSchema, sectionTitleByType;
|
|
685
|
+
var init_summarize = __esm({
|
|
686
|
+
"src/tools/summarize.ts"() {
|
|
687
|
+
"use strict";
|
|
688
|
+
init_client();
|
|
689
|
+
summarizeRepoContextInputSchema = {
|
|
690
|
+
repo: z6.string().trim().min(1, "repo is required")
|
|
691
|
+
};
|
|
692
|
+
sectionTitleByType = {
|
|
693
|
+
convention: "Conventions",
|
|
694
|
+
bug_fix: "Bug Fixes",
|
|
695
|
+
reviewer_pattern: "Reviewer Patterns",
|
|
696
|
+
decision: "Decisions",
|
|
697
|
+
issue: "Issues",
|
|
698
|
+
general: "General"
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// src/tools/update.ts
|
|
704
|
+
import { z as z7 } from "zod";
|
|
705
|
+
function parseTags3(raw) {
|
|
706
|
+
try {
|
|
707
|
+
const parsed = JSON.parse(raw);
|
|
708
|
+
return Array.isArray(parsed) ? parsed.filter((value) => typeof value === "string") : [];
|
|
709
|
+
} catch {
|
|
710
|
+
return [];
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function formatMemory(memory) {
|
|
714
|
+
const tags = parseTags3(memory.tags);
|
|
715
|
+
const tagsLine = tags.length > 0 ? tags.join(", ") : "(none)";
|
|
716
|
+
return [
|
|
717
|
+
`Memory ${memory.row_id} updated successfully.`,
|
|
718
|
+
`id: ${memory.row_id}`,
|
|
719
|
+
`legacy_id: ${memory.id}`,
|
|
720
|
+
`repo: ${memory.repo}`,
|
|
721
|
+
`memory_type: ${memory.type}`,
|
|
722
|
+
`content: ${memory.note}`,
|
|
723
|
+
`tags: ${tagsLine}`,
|
|
724
|
+
`pinned: ${memory.pinned === 1 ? "true" : "false"}`,
|
|
725
|
+
`created_at: ${memory.created_at}`,
|
|
726
|
+
`updated_at: ${memory.updated_at}`
|
|
727
|
+
].join("\n");
|
|
728
|
+
}
|
|
729
|
+
function registerUpdateMemoryTool(server) {
|
|
730
|
+
server.registerTool(
|
|
731
|
+
"update_memory",
|
|
732
|
+
{
|
|
733
|
+
description: "Update an existing memory by numeric id with partial fields.",
|
|
734
|
+
inputSchema: updateMemoryInputSchema
|
|
735
|
+
},
|
|
736
|
+
async ({ id, content, memory_type }) => {
|
|
737
|
+
try {
|
|
738
|
+
if (!content && !memory_type) {
|
|
739
|
+
return {
|
|
740
|
+
isError: true,
|
|
741
|
+
content: [
|
|
742
|
+
{
|
|
743
|
+
type: "text",
|
|
744
|
+
text: "Provide at least one field to update: content or memory_type."
|
|
745
|
+
}
|
|
746
|
+
]
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const db = getDb();
|
|
750
|
+
const existing = db.prepare(
|
|
751
|
+
`
|
|
752
|
+
SELECT rowid AS row_id, id, repo, type, note, tags, created_at, updated_at, pinned
|
|
753
|
+
FROM memories
|
|
754
|
+
WHERE rowid = ?
|
|
755
|
+
`
|
|
756
|
+
).get(id);
|
|
757
|
+
if (!existing) {
|
|
758
|
+
return {
|
|
759
|
+
isError: true,
|
|
760
|
+
content: [
|
|
761
|
+
{
|
|
762
|
+
type: "text",
|
|
763
|
+
text: `Memory ${id} not found.`
|
|
764
|
+
}
|
|
765
|
+
]
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
769
|
+
const nextType = memory_type ?? existing.type;
|
|
770
|
+
const nextNote = content ?? existing.note;
|
|
771
|
+
db.prepare(
|
|
772
|
+
`
|
|
773
|
+
UPDATE memories
|
|
774
|
+
SET type = ?, note = ?, updated_at = ?
|
|
775
|
+
WHERE rowid = ?
|
|
776
|
+
`
|
|
777
|
+
).run(nextType, nextNote, now, id);
|
|
778
|
+
const updated = db.prepare(
|
|
779
|
+
`
|
|
780
|
+
SELECT rowid AS row_id, id, repo, type, note, tags, created_at, updated_at, pinned
|
|
781
|
+
FROM memories
|
|
782
|
+
WHERE rowid = ?
|
|
783
|
+
`
|
|
784
|
+
).get(id);
|
|
785
|
+
if (!updated) {
|
|
786
|
+
return {
|
|
787
|
+
isError: true,
|
|
788
|
+
content: [
|
|
789
|
+
{
|
|
790
|
+
type: "text",
|
|
791
|
+
text: `Memory ${id} could not be loaded after update.`
|
|
792
|
+
}
|
|
793
|
+
]
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
return {
|
|
797
|
+
content: [
|
|
798
|
+
{
|
|
799
|
+
type: "text",
|
|
800
|
+
text: formatMemory(updated)
|
|
801
|
+
}
|
|
802
|
+
]
|
|
803
|
+
};
|
|
804
|
+
} catch (error) {
|
|
805
|
+
const message = error instanceof Error ? error.message : "Unknown error while updating memory.";
|
|
806
|
+
return {
|
|
807
|
+
isError: true,
|
|
808
|
+
content: [
|
|
809
|
+
{
|
|
810
|
+
type: "text",
|
|
811
|
+
text: `Failed to update memory: ${message}`
|
|
812
|
+
}
|
|
813
|
+
]
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
var updateMemoryInputSchema;
|
|
820
|
+
var init_update = __esm({
|
|
821
|
+
"src/tools/update.ts"() {
|
|
822
|
+
"use strict";
|
|
823
|
+
init_client();
|
|
824
|
+
updateMemoryInputSchema = {
|
|
825
|
+
id: z7.number().int().positive(),
|
|
826
|
+
content: z7.string().trim().min(1).optional(),
|
|
827
|
+
memory_type: z7.enum(MEMORY_TYPES).optional()
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
// src/index.ts
|
|
833
|
+
var index_exports = {};
|
|
834
|
+
__export(index_exports, {
|
|
835
|
+
resolveDbPath: () => resolveDbPath,
|
|
836
|
+
startServer: () => startServer
|
|
837
|
+
});
|
|
838
|
+
import { homedir } from "os";
|
|
839
|
+
import { join, resolve } from "path";
|
|
840
|
+
import { fileURLToPath } from "url";
|
|
841
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
842
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
843
|
+
function resolveDbPath() {
|
|
844
|
+
return process.env.FOSSEL_DB_PATH?.trim() || join(homedir(), ".fossel", "memory.db");
|
|
845
|
+
}
|
|
846
|
+
async function startServer() {
|
|
847
|
+
const dbPath = resolveDbPath();
|
|
848
|
+
initDb(dbPath);
|
|
849
|
+
const server = new McpServer({
|
|
850
|
+
name: "fossel",
|
|
851
|
+
version: "1.0.0"
|
|
852
|
+
});
|
|
853
|
+
registerStoreContextTool(server);
|
|
854
|
+
registerGetRepoContextTool(server);
|
|
855
|
+
registerSearchMemoryTool(server);
|
|
856
|
+
registerDeleteMemoryTool(server);
|
|
857
|
+
registerUpdateMemoryTool(server);
|
|
858
|
+
registerPinMemoryTool(server);
|
|
859
|
+
registerUnpinMemoryTool(server);
|
|
860
|
+
registerSummarizeRepoContextTool(server);
|
|
861
|
+
const transport = new StdioServerTransport();
|
|
862
|
+
await server.connect(transport);
|
|
863
|
+
}
|
|
864
|
+
var entryPath, currentPath;
|
|
865
|
+
var init_index = __esm({
|
|
866
|
+
"src/index.ts"() {
|
|
867
|
+
"use strict";
|
|
868
|
+
init_client();
|
|
869
|
+
init_delete();
|
|
870
|
+
init_get_repo();
|
|
871
|
+
init_pin();
|
|
872
|
+
init_search();
|
|
873
|
+
init_store();
|
|
874
|
+
init_summarize();
|
|
875
|
+
init_update();
|
|
876
|
+
entryPath = process.argv[1];
|
|
877
|
+
currentPath = fileURLToPath(import.meta.url);
|
|
878
|
+
if (entryPath && currentPath === resolve(entryPath)) {
|
|
879
|
+
startServer().catch((error) => {
|
|
880
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
881
|
+
console.error(`Fossel server failed to start: ${message}`);
|
|
882
|
+
process.exit(1);
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
// src/cli.ts
|
|
889
|
+
init_client();
|
|
890
|
+
import { homedir as homedir2 } from "os";
|
|
891
|
+
import { basename, join as join2 } from "path";
|
|
892
|
+
import { spawnSync } from "child_process";
|
|
893
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
894
|
+
var DEFAULT_DB_PATH = join2(homedir2(), ".fossel", "memory.db");
|
|
895
|
+
var INIT_MEMORY_TEXT = "Fossel is active for this repo. Use store_context to save context.";
|
|
896
|
+
function resolveDbPath2() {
|
|
897
|
+
return process.env.FOSSEL_DB_PATH?.trim() || DEFAULT_DB_PATH;
|
|
898
|
+
}
|
|
899
|
+
function detectRepoFromRemote(cwd) {
|
|
900
|
+
const result = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
901
|
+
cwd,
|
|
902
|
+
encoding: "utf8"
|
|
903
|
+
});
|
|
904
|
+
if (result.status !== 0) {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
const remote = result.stdout.trim();
|
|
908
|
+
if (!remote) {
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
const normalized = remote.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
912
|
+
const lastSegment = normalized.split("/").at(-1);
|
|
913
|
+
if (!lastSegment) {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
return lastSegment.replace(/\.git$/i, "");
|
|
917
|
+
}
|
|
918
|
+
function detectRepoName(cwd) {
|
|
919
|
+
return detectRepoFromRemote(cwd) ?? basename(cwd);
|
|
920
|
+
}
|
|
921
|
+
function ensureSampleMemory(repo) {
|
|
922
|
+
const db = getDb();
|
|
923
|
+
const existing = db.prepare(
|
|
924
|
+
`
|
|
925
|
+
SELECT rowid AS row_id
|
|
926
|
+
FROM memories
|
|
927
|
+
WHERE repo = ? AND type = 'convention' AND note = ?
|
|
928
|
+
LIMIT 1
|
|
929
|
+
`
|
|
930
|
+
).get(repo, INIT_MEMORY_TEXT);
|
|
931
|
+
if (existing) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
935
|
+
db.prepare(
|
|
936
|
+
`
|
|
937
|
+
INSERT INTO memories (id, repo, type, note, tags, created_at, updated_at, pinned)
|
|
938
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
939
|
+
`
|
|
940
|
+
).run(nanoid2(), repo, "convention", INIT_MEMORY_TEXT, "[]", now, now, 0);
|
|
941
|
+
}
|
|
942
|
+
function formatCursorConfig() {
|
|
943
|
+
return JSON.stringify(
|
|
944
|
+
{
|
|
945
|
+
mcpServers: {
|
|
946
|
+
fossel: {
|
|
947
|
+
command: "npx",
|
|
948
|
+
args: ["-y", "fossel"]
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
null,
|
|
953
|
+
2
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
function formatClaudeDesktopConfig() {
|
|
957
|
+
return JSON.stringify(
|
|
958
|
+
{
|
|
959
|
+
mcpServers: {
|
|
960
|
+
fossel: {
|
|
961
|
+
command: "npx",
|
|
962
|
+
args: ["-y", "fossel"]
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
},
|
|
966
|
+
null,
|
|
967
|
+
2
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
function printInitOutput(repo, dbPath) {
|
|
971
|
+
const db = getDb();
|
|
972
|
+
const countRow = db.prepare("SELECT COUNT(*) AS count FROM memories").get();
|
|
973
|
+
console.log("Fossel remembers project context locally for each repository.");
|
|
974
|
+
console.log("Store conventions, fixes, and decisions once; retrieve them when needed.");
|
|
975
|
+
console.log("Everything stays in your local SQLite database.\n");
|
|
976
|
+
console.log(`Detected repository: ${repo}
|
|
977
|
+
`);
|
|
978
|
+
console.log("Cursor MCP config (~/.cursor/mcp.json):");
|
|
979
|
+
console.log(formatCursorConfig());
|
|
980
|
+
console.log("");
|
|
981
|
+
console.log("Claude Desktop MCP config:");
|
|
982
|
+
console.log(formatClaudeDesktopConfig());
|
|
983
|
+
console.log("");
|
|
984
|
+
console.log(`DB Path: ${dbPath}`);
|
|
985
|
+
console.log(`Total memories: ${countRow.count}`);
|
|
986
|
+
console.log("");
|
|
987
|
+
console.log("Quick commands");
|
|
988
|
+
console.log("Command Description");
|
|
989
|
+
console.log("----------------------- --------------------------------------------");
|
|
990
|
+
console.log("npx -y fossel Start Fossel MCP server over stdio");
|
|
991
|
+
console.log("npx -y fossel init Initialize Fossel for current repository");
|
|
992
|
+
console.log("store_context Save context memory");
|
|
993
|
+
console.log("get_repo_context Retrieve recent repo memories");
|
|
994
|
+
console.log("summarize_repo_context Generate markdown context summary");
|
|
995
|
+
}
|
|
996
|
+
async function main() {
|
|
997
|
+
const command = process.argv[2];
|
|
998
|
+
if (!command) {
|
|
999
|
+
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_index(), index_exports));
|
|
1000
|
+
await startServer2();
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
if (command === "init") {
|
|
1004
|
+
const dbPath = resolveDbPath2();
|
|
1005
|
+
initDb(dbPath);
|
|
1006
|
+
const repo = detectRepoName(process.cwd());
|
|
1007
|
+
ensureSampleMemory(repo);
|
|
1008
|
+
printInitOutput(repo, dbPath);
|
|
1009
|
+
closeDb();
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
console.error(`Unknown command: ${command}`);
|
|
1013
|
+
console.error("Usage: fossel [init]");
|
|
1014
|
+
process.exit(1);
|
|
1015
|
+
}
|
|
1016
|
+
main().catch((error) => {
|
|
1017
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1018
|
+
console.error(`Fossel command failed: ${message}`);
|
|
1019
|
+
process.exit(1);
|
|
1020
|
+
});
|