Skip to content

Commit 3952ae3

Browse files
committed
fix: update demo-mcp & usage
1 parent 560ddd8 commit 3952ae3

File tree

1 file changed

+133
-147
lines changed

1 file changed

+133
-147
lines changed

apps/demo-mcp/src/cli.ts

Lines changed: 133 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* - serve [--db <dbPath>] (stub)
1111
*
1212
* Defaults (if not provided):
13-
* --file ./export_rocket_scientist2_20250811.zip (relative to cwd)
13+
* --file (relative to cwd)
1414
* --db ./.data/reddit.db (relative to cwd)
1515
* --repo . (current working directory)
1616
*
@@ -22,15 +22,10 @@ import fs from 'node:fs/promises';
2222
import path from 'node:path';
2323
import { fileURLToPath } from 'node:url';
2424
import { createClient } from '@libsql/client';
25-
import type { Importer } from '@repo/vault-core';
26-
import {
27-
defaultConvention,
28-
LocalFileStore,
29-
markdownFormat,
30-
VaultService,
31-
} from '@repo/vault-core';
25+
import type { Adapter } from '@repo/vault-core';
26+
import { createVault, defaultConvention } from '@repo/vault-core';
27+
import { markdownFormat } from '@repo/vault-core/codecs';
3228
import { drizzle } from 'drizzle-orm/libsql';
33-
import { migrate } from 'drizzle-orm/libsql/migrator';
3429

3530
// -------------------------------------------------------------
3631
type CLIArgs = {
@@ -78,17 +73,17 @@ function getBinPath(): string {
7873
function printHelp(): void {
7974
const bin = getBinPath();
8075
console.log(
81-
`Usage:\n bun run ${bin} <command> [options]\n\nCommands:\n import <adapter> Import a Reddit export ZIP into the database\n export-fs <adapter> Export DB rows to Markdown files under vault/<adapter>/...\n import-fs <adapter> Import Markdown files from vault/<adapter>/... into the DB\n serve Start stub server (not implemented)\n\nOptions:\n --file <zip> Path to Reddit export ZIP (import only)\n --db <path> Path to SQLite DB file (default: ./.data/reddit.db or DATABASE_URL)\n --repo <dir> Repo root for plaintext I/O (default: .)\n -h, --help Show this help\n\nNotes:\n - Files are Markdown only, written under vault/<adapter>/<table>/<pk...>.md\n - DATABASE_URL, if set, overrides --db entirely.\n`,
76+
`Usage:\n bun run ${bin} <command> [options]\n\nCommands:\n import <adapter> Import a Reddit export ZIP into the database\n export-fs <adapter> Export DB rows to Markdown files under vault/<adapter>/...\n import-fs <adapter> Import Markdown files from vault/<adapter>/... into the DB\n\nOptions:\n --file <zip> Path to Reddit export ZIP (import only)\n --db <path> Path to SQLite DB file (default: ./.data/reddit.db or DATABASE_URL)\n --repo <dir> Repo root for plaintext I/O (default: .)\n -h, --help Show this help\n\nNotes:\n - Files are Markdown only, written under vault/<adapter>/<table>/<pk...>.md\n - DATABASE_URL, if set, overrides --db entirely.\n`,
8277
);
8378
}
8479

85-
function resolveZipPath(p?: string): string {
80+
function resolveZipPath(p: string): string {
8681
const candidate = p ?? './export_rocket_scientist2_20250811.zip';
8782
return path.resolve(process.cwd(), candidate);
8883
}
8984

90-
function resolveDbFile(p?: string): string {
91-
const candidate = p ?? './.data/reddit.db';
85+
function resolveDbFile(p: string): string {
86+
const candidate = p;
9287
return path.resolve(process.cwd(), candidate);
9388
}
9489

@@ -116,195 +111,189 @@ function toDbUrl(dbFileAbs: string): string {
116111
}
117112

118113
// -------------------------------------------------------------
119-
// Import command
114+
// Helpers to work with new core API
120115
// -------------------------------------------------------------
121-
async function cmdImport(args: CLIArgs, adapterID: string) {
122-
const zipPath = resolveZipPath(args.file);
123-
const dbFile = resolveDbFile(args.db);
124-
const dbUrl = toDbUrl(dbFile);
125-
126-
// Prepare DB and run migrations
127-
await ensureDirExists(dbFile);
128-
const client = createClient({ url: dbUrl });
129-
const rawDb = drizzle(client);
130-
// Cast libsql drizzle DB to the generic BaseSQLiteDatabase shape expected by Vault
131-
const db = rawDb;
132-
133-
// Read input once (adapters may ignore if not applicable)
134-
const data = await fs.readFile(zipPath);
135-
const blob = new Blob([new Uint8Array(data)], { type: 'application/zip' });
136-
137-
// Build adapter instances, ensuring migrations path is absolute per adapter package
138-
let importer: Importer | undefined;
139-
140-
// This is just patch code, don't look too closely!
141-
const keys = await fs.readdir(
142-
path.resolve(repoRoot, 'packages/vault-core/src/adapters'),
116+
async function findAdapter(adapterID: string): Promise<Adapter> {
117+
const adaptersDir = path.resolve(
118+
repoRoot,
119+
'packages/vault-core/src/adapters',
143120
);
121+
const keys = await fs.readdir(adaptersDir);
144122
for (const key of keys) {
145123
const modulePath = import.meta.resolve(
146124
`../../../packages/vault-core/src/adapters/${key}`,
147125
);
148126
const mod = (await import(modulePath)) as Record<string, unknown>;
149127
for (const func of Object.values(mod)) {
150128
if (typeof func !== 'function') continue;
151-
const a = func();
129+
try {
130+
const a = func();
131+
if (a && typeof a === 'object' && 'id' in a && a.id === adapterID) {
132+
return a as Adapter;
133+
}
134+
} catch {
135+
// ignore factory functions that require params or throw
136+
}
137+
}
138+
}
139+
throw new Error(`Could not find adapter for key ${adapterID}`);
140+
}
141+
142+
async function writeFilesToRepo(
143+
repoDir: string,
144+
files: Map<string, File>,
145+
): Promise<number> {
146+
let count = 0;
147+
for (const [relPath, file] of files) {
148+
const absPath = path.resolve(repoDir, relPath);
149+
await ensureDirExists(absPath);
150+
const text = await file.text();
151+
await fs.writeFile(absPath, text, 'utf8');
152+
count++;
153+
}
154+
return count;
155+
}
152156

153-
// TODO
154-
if (a && typeof a === 'object' && 'id' in a && a.id === adapterID) {
155-
importer = a as Importer;
157+
async function collectFilesFromRepo(
158+
repoDir: string,
159+
): Promise<Map<string, File>> {
160+
const root = path.resolve(repoDir, 'vault');
161+
const out = new Map<string, File>();
162+
163+
async function walk(dir: string) {
164+
let entries: Array<import('node:fs').Dirent>;
165+
try {
166+
// @ts-ignore - withFileTypes is supported in Node/Bun
167+
entries = await fs.readdir(dir, { withFileTypes: true });
168+
} catch {
169+
return;
170+
}
171+
for (const entry of entries) {
172+
const full = path.join(dir, entry.name);
173+
if (entry.isDirectory()) {
174+
await walk(full);
175+
} else if (entry.isFile()) {
176+
const relFromRepo = path
177+
.relative(repoDir, full)
178+
.split(path.sep)
179+
.join('/');
180+
const text = await fs.readFile(full, 'utf8');
181+
const f = new File([text], entry.name, { type: 'text/plain' });
182+
out.set(relFromRepo, f);
156183
}
157184
}
158185
}
159186

160-
if (!importer) throw new Error(`Could not find adapter for key ${adapterID}`);
187+
await walk(root);
188+
return out;
189+
}
190+
191+
// -------------------------------------------------------------
192+
// Import command (ZIP ingest via adapter ingestor)
193+
// -------------------------------------------------------------
194+
async function cmdImport(args: CLIArgs, adapterID: string) {
195+
const { file, db } = args;
196+
if (!file) throw new Error('--file is required for import command');
197+
if (!db) throw new Error('--db is required for import command');
198+
199+
const zipPath = resolveZipPath(file);
200+
const dbFile = resolveDbFile(db);
201+
const dbUrl = toDbUrl(dbFile);
161202

162-
// Initialize VaultService (runs migrations implicitly)
163-
const service = await VaultService.create({
164-
importers: [importer],
165-
database: db,
166-
migrateFunc: migrate,
203+
// Prepare DB
204+
await ensureDirExists(dbFile);
205+
const client = createClient({ url: dbUrl });
206+
const rawDb = drizzle(client);
207+
208+
// Read ZIP and wrap in File for bun runtime
209+
const data = await fs.readFile(zipPath);
210+
const blob = new Blob([new Uint8Array(data)], { type: 'application/zip' });
211+
const zipFile = new File([blob], path.basename(zipPath), {
212+
type: 'application/zip',
213+
});
214+
215+
// Resolve adapter and create vault
216+
const adapter = await findAdapter(adapterID);
217+
const vault = createVault({
218+
adapters: [adapter],
219+
database: rawDb,
167220
});
168221

169-
const res = await service.importBlob(blob, adapterID);
170-
const counts = countRecords(res.parsed);
171-
console.log(`\n=== Adapter: ${res.importer} ===`);
172-
printCounts(counts);
173-
console.log(`\nImport complete. DB path: ${dbFile}`);
222+
// Ingest data through adapter's ingestor
223+
await vault.ingestData({ adapter, file: zipFile });
224+
225+
console.log(
226+
`\nIngest complete for adapter '${adapterID}'. DB path: ${dbFile}`,
227+
);
174228
}
175229

176230
// -------------------------------------------------------------
177231
// Export DB -> Files (Markdown only)
178232
// -------------------------------------------------------------
179233
async function cmdExportFs(args: CLIArgs, adapterID: string) {
180-
const dbFile = resolveDbFile(args.db);
234+
const { db } = args;
235+
if (!db) throw new Error('--db is required for export-fs command');
236+
237+
const dbFile = resolveDbFile(db);
181238
const dbUrl = toDbUrl(dbFile);
182239
const repoDir = resolveRepoDir(args.repo);
183240

184241
await ensureDirExists(dbFile);
185242
const client = createClient({ url: dbUrl });
186243
const rawDb = drizzle(client);
187-
const db = rawDb;
188244

189-
let importer: Importer | undefined;
190-
const keys = await fs.readdir(
191-
path.resolve(repoRoot, 'packages/vault-core/src/adapters'),
192-
);
193-
for (const key of keys) {
194-
const modulePath = import.meta.resolve(
195-
`../../../packages/vault-core/src/adapters/${key}`,
196-
);
197-
const mod = (await import(modulePath)) as Record<string, unknown>;
198-
for (const func of Object.values(mod)) {
199-
if (typeof func !== 'function') continue;
200-
const a = func();
201-
if (a && typeof a === 'object' && 'id' in a && a.id === adapterID) {
202-
importer = a as Importer;
203-
}
204-
}
205-
}
206-
if (!importer) throw new Error(`Could not find adapter for key ${adapterID}`);
245+
// Resolve adapter and create vault
246+
const adapter = await findAdapter(adapterID);
247+
const vault = createVault({
248+
adapters: [adapter],
249+
database: rawDb,
250+
});
207251

208-
const service = await VaultService.create({
209-
importers: [importer],
210-
database: db,
211-
migrateFunc: migrate,
252+
// Export files as Map<string, File> using markdown codec and default conventions
253+
const files = await vault.exportData({
254+
adapterIDs: [adapterID],
212255
codec: markdownFormat,
213256
conventions: defaultConvention(),
214257
});
215258

216-
const store = new LocalFileStore(repoDir);
217-
const result = await service.export(adapterID, store);
218-
const n = Object.keys(result.files).length;
259+
const n = await writeFilesToRepo(repoDir, files);
219260
console.log(`Exported ${n} files to ${repoDir}/vault/${adapterID}`);
220261
}
221262

222263
// -------------------------------------------------------------
223264
// Import Files -> DB (Markdown only)
224265
// -------------------------------------------------------------
225266
async function cmdImportFs(args: CLIArgs, adapterID: string) {
226-
const dbFile = resolveDbFile(args.db);
267+
const { db } = args;
268+
if (!db) throw new Error('--db is required for import-fs command');
269+
270+
const dbFile = resolveDbFile(db);
227271
const dbUrl = toDbUrl(dbFile);
228272
const repoDir = resolveRepoDir(args.repo);
229273

230274
await ensureDirExists(dbFile);
231275
const client = createClient({ url: dbUrl });
232276
const rawDb = drizzle(client);
233-
const db = rawDb;
234277

235-
let importer: Importer | undefined;
236-
const keys = await fs.readdir(
237-
path.resolve(repoRoot, 'packages/vault-core/src/adapters'),
238-
);
239-
for (const key of keys) {
240-
const modulePath = import.meta.resolve(
241-
`../../../packages/vault-core/src/adapters/${key}`,
242-
);
243-
const mod = (await import(modulePath)) as Record<string, unknown>;
244-
for (const func of Object.values(mod)) {
245-
if (typeof func !== 'function') continue;
246-
const a = func();
247-
if (a && typeof a === 'object' && 'id' in a && a.id === adapterID) {
248-
importer = a as Importer;
249-
}
250-
}
251-
}
252-
if (!importer) throw new Error(`Could not find adapter for key ${adapterID}`);
278+
// Resolve adapter and create vault
279+
const adapter = await findAdapter(adapterID);
280+
const vault = createVault({
281+
adapters: [adapter],
282+
database: rawDb,
283+
});
253284

254-
const service = await VaultService.create({
255-
importers: [importer],
256-
database: db,
257-
migrateFunc: migrate,
285+
// Read files under repoDir/vault and import via markdown codec
286+
const files = await collectFilesFromRepo(repoDir);
287+
await vault.importData({
288+
files,
258289
codec: markdownFormat,
259-
conventions: defaultConvention(),
260290
});
261291

262-
const store = new LocalFileStore(repoDir);
263-
await service.import(adapterID, store);
264292
console.log(
265293
`Imported files from ${repoDir}/vault/${adapterID} into DB ${dbFile}`,
266294
);
267295
}
268296

269-
function printCounts(parsedOrCounts: Record<string, unknown>) {
270-
const entries: [string, number][] = Object.entries(parsedOrCounts).map(
271-
([k, v]) => [
272-
k,
273-
typeof v === 'number' ? v : Array.isArray(v) ? v.length : 0,
274-
],
275-
);
276-
const maxKey = Math.max(...entries.map(([k]) => k.length), 10);
277-
for (const [k, n] of entries.sort((a, b) => a[0].localeCompare(b[0]))) {
278-
console.log(`${k.padEnd(maxKey, ' ')} : ${n}`);
279-
}
280-
}
281-
282-
function countRecords(parsed: unknown): Record<string, number> {
283-
const out: Record<string, number> = {};
284-
if (parsed && typeof parsed === 'object') {
285-
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
286-
out[k] = Array.isArray(v) ? v.length : 0;
287-
}
288-
}
289-
return out;
290-
}
291-
292-
// -------------------------------------------------------------
293-
// Serve command (stub)
294-
// -------------------------------------------------------------
295-
async function cmdServe(args: CLIArgs) {
296-
const dbFile = resolveDbFile(args.db);
297-
const dbUrl = toDbUrl(dbFile);
298-
299-
console.log('Serve is not implemented in this minimal demo.');
300-
console.log(
301-
'Intended behavior: start an MCP server sourced by the adapter and DB.',
302-
);
303-
console.log(`DB path: ${dbFile}`);
304-
console.log(`DB URL: ${dbUrl}`);
305-
console.log('Exiting.');
306-
}
307-
308297
// -------------------------------------------------------------
309298
// Entrypoint
310299
// -------------------------------------------------------------
@@ -339,9 +328,6 @@ async function main() {
339328
await cmdImportFs(args, adapter);
340329
}
341330
break;
342-
case 'serve':
343-
await cmdServe(args);
344-
break;
345331
default:
346332
console.error(`Unknown command: ${command}`);
347333
printHelp();

0 commit comments

Comments
 (0)