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';
2222import path from 'node:path' ;
2323import { fileURLToPath } from 'node:url' ;
2424import { 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' ;
3228import { drizzle } from 'drizzle-orm/libsql' ;
33- import { migrate } from 'drizzle-orm/libsql/migrator' ;
3429
3530// -------------------------------------------------------------
3631type CLIArgs = {
@@ -78,17 +73,17 @@ function getBinPath(): string {
7873function 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// -------------------------------------------------------------
179233async 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// -------------------------------------------------------------
225266async 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