Problem
The export format (Markdown) is hardcoded into FileWriter. There is clear user appetite for alternative formats (structured JSON, CSV, custom archive formats) but no extension point — any addition requires touching core code.
Proposed Solution
A file-based exporter plugin system with env-var activation. Drop a .ts file into src/exporters/, add its name to ENABLED_EXPORTERS — that's the entire integration surface.
Interface
// src/exporters/exporter.interface.ts
export interface ConversationExporter {
/** Must match exactly what you put in ENABLED_EXPORTERS */
name: string
fileExtension: string
/** Where to write output files. Return config.exportDir as the safe default. */
outputDir(config: Config): string
/** Serialize the conversation. Return a string (UTF-8). */
export(conversation: ExtractedConversation): string
}
Registration & Discovery
At startup, FileWriter scans src/exporters/*.ts and auto-registers any file that default-exports a ConversationExporter. No manual imports needed.
Activation via env var
# .env.example
# Comma-separated list of exporter names to enable.
# Names must match the `name` field in each exporter file.
# Default: markdown
ENABLED_EXPORTERS=markdown
- Default is
markdown only — fully backwards compatible, no surprise output directories
- If
ENABLED_EXPORTERS is unset, falls back to markdown
- Multiple exporters:
ENABLED_EXPORTERS=markdown,csv
File Structure
src/exporters/
exporter.interface.ts ← the interface definition
markdown.exporter.ts ← built-in default (current Markdown logic moved here)
custom.exporter.ts.example ← copy-paste starting point (see below)
Example File (custom.exporter.ts.example)
Fully commented, immediately runnable CSV example:
// Copy this file to src/exporters/csv.exporter.ts
// Then add "csv" to ENABLED_EXPORTERS in your .env
//
// The `name` field here MUST match what you put in ENABLED_EXPORTERS.
import type { ConversationExporter } from './exporter.interface.js'
import type { ExtractedConversation } from '../scraper/conversation-extractor.js'
import type { Config } from '../utils/config.js'
const exporter: ConversationExporter = {
name: 'csv',
fileExtension: '.csv',
// outputDir: where your files will be written.
// Return config.exportDir to share the default exports folder,
// or return a custom path for a separate output directory.
outputDir(config: Config): string {
return config.exportDir
},
// export: receives the fully extracted conversation, returns a string.
// The string will be written to: outputDir / spaceName / title (id).csv
export(conversation: ExtractedConversation): string {
const header = 'role,content'
const rows = conversation.messages.map(
(m) => `${m.role},"${m.content.replace(/"/g, '""')}"`
)
return [header, ...rows].join('\n')
},
}
export default exporter
FileWriter changes
- Iterates registered + active exporters, calls each
export(), writes to outputDir / spaceName / filename
- Integrity check runs per exporter (file exists, non-empty)
- Hardcoded Markdown path logic removed from
FileWriter core
Documentation
Add ## Exporters section to README covering:
- Where to drop the file (
src/exporters/)
- What each interface method does
- That
custom.exporter.ts.example exists and how to activate it
- That
outputDir returning config.exportDir is the safe default
- That the
name field must exactly match ENABLED_EXPORTERS — this is the only "magic" in the system
Problem
The export format (Markdown) is hardcoded into
FileWriter. There is clear user appetite for alternative formats (structured JSON, CSV, custom archive formats) but no extension point — any addition requires touching core code.Proposed Solution
A file-based exporter plugin system with env-var activation. Drop a
.tsfile intosrc/exporters/, add its name toENABLED_EXPORTERS— that's the entire integration surface.Interface
Registration & Discovery
At startup,
FileWriterscanssrc/exporters/*.tsand auto-registers any file that default-exports aConversationExporter. No manual imports needed.Activation via env var
markdownonly — fully backwards compatible, no surprise output directoriesENABLED_EXPORTERSis unset, falls back tomarkdownENABLED_EXPORTERS=markdown,csvFile Structure
Example File (
custom.exporter.ts.example)Fully commented, immediately runnable CSV example:
FileWriter changes
export(), writes tooutputDir / spaceName / filenameFileWritercoreDocumentation
Add
## Exporterssection to README covering:src/exporters/)custom.exporter.ts.exampleexists and how to activate itoutputDirreturningconfig.exportDiris the safe defaultnamefield must exactly matchENABLED_EXPORTERS— this is the only "magic" in the system