Files
cortex/docs/milestones/08-import-export.md
omigamedev d484f61b29 Add development plan with 13 milestone specifications
- docs/plan.md: Master roadmap with phases and priorities
- docs/milestones/01-13: Detailed specs for each feature
- Updated CLAUDE.md with plan references and build commands

Milestones cover:
- Phase 1: Temporal versioning, auto-capture, context injection, codebase indexing
- Phase 2: Daily journal, content ingestion, graph visualization, import/export
- Phase 3: Multi-graph, smart retrieval, TUI dashboard, browser extension, shell completions
2026-02-03 09:36:08 +01:00

7.0 KiB

Milestone 8: Import/Export

Overview

Import from and export to popular formats: Obsidian vaults, Markdown folders, JSON-LD, and more. Never be locked into Cortex.

Motivation

  • Users have existing knowledge in other tools
  • Portability prevents lock-in
  • Enables backup and migration
  • Standard formats enable interoperability

Features

8.1 Obsidian Import

# Import Obsidian vault
cortex import obsidian ~/vaults/notes

# Import with tag mapping
cortex import obsidian ~/vaults/notes --map-tags

# Preview without importing
cortex import obsidian ~/vaults/notes --dry-run

Handles:

  • Wikilinks [[Page Name]] → edges
  • Tags #tag → node tags
  • YAML frontmatter → metadata
  • Folder structure → hierarchy

8.2 Markdown Folder Import

# Import markdown folder
cortex import markdown ./docs

# Respect folder hierarchy
cortex import markdown ./docs --hierarchy

# Set kind for all imported nodes
cortex import markdown ./docs --kind memory

8.3 Markdown Export

# Export to markdown folder
cortex export-md ./output

# Export with frontmatter
cortex export-md ./output --frontmatter

# Export specific nodes
cortex export-md ./output --kind component

8.4 JSON-LD Export

# Export as JSON-LD (linked data)
cortex export --format jsonld --output knowledge.jsonld

# Compatible with knowledge graph standards

8.5 Full Backup/Restore

# Backup entire database
cortex backup ./backup-2024-01-15.cortex

# Restore from backup
cortex restore ./backup-2024-01-15.cortex

# Auto-backup (cron-friendly)
cortex backup --auto --keep 7  # Keep last 7 backups

8.6 Notion Import (Future)

# Import from Notion export
cortex import notion ./notion-export

Implementation

Obsidian Importer

// src/core/import/obsidian.ts
export async function importObsidian(vaultPath: string, options: ImportOptions): Promise<ImportResult> {
  const files = await glob(`${vaultPath}/**/*.md`);
  const nodes: Map<string, Node> = new Map();
  const pendingLinks: Array<{ from: string; to: string; type: EdgeType }> = [];

  // First pass: create nodes
  for (const file of files) {
    const content = await fs.readFile(file, 'utf-8');
    const { frontmatter, body } = parseFrontmatter(content);

    const title = path.basename(file, '.md');
    const relativePath = path.relative(vaultPath, file);

    const node = await addNode({
      kind: frontmatter.kind || 'memory',
      title,
      content: body,
      tags: extractTags(content, frontmatter.tags),
      status: frontmatter.status,
      metadata: {
        ...frontmatter,
        importedFrom: 'obsidian',
        originalPath: relativePath,
      },
    });

    nodes.set(title, node);
  }

  // Second pass: create links
  for (const file of files) {
    const content = await fs.readFile(file, 'utf-8');
    const title = path.basename(file, '.md');
    const sourceNode = nodes.get(title);

    // Find wikilinks [[Target]]
    const wikilinks = content.matchAll(/\[\[([^\]]+)\]\]/g);
    for (const match of wikilinks) {
      const targetTitle = match[1].split('|')[0]; // Handle aliases [[Page|Alias]]
      const targetNode = nodes.get(targetTitle);

      if (sourceNode && targetNode) {
        addEdge(sourceNode.id, targetNode.id, 'relates_to');
      }
    }
  }

  // Third pass: folder hierarchy
  if (options.hierarchy) {
    await createFolderHierarchy(vaultPath, nodes);
  }

  return { imported: nodes.size };
}

Markdown Exporter

// src/core/export/markdown.ts
export async function exportMarkdown(outputDir: string, options: ExportOptions): Promise<void> {
  const nodes = await listNodes(options.filter);

  await fs.mkdir(outputDir, { recursive: true });

  for (const node of nodes) {
    const filename = sanitizeFilename(node.title) + '.md';
    const filepath = path.join(outputDir, filename);

    let content = '';

    // Add frontmatter
    if (options.frontmatter) {
      content += '---\n';
      content += `id: ${node.id}\n`;
      content += `kind: ${node.kind}\n`;
      content += `tags: [${node.tags.join(', ')}]\n`;
      if (node.status) content += `status: ${node.status}\n`;
      content += `created: ${new Date(node.createdAt).toISOString()}\n`;
      content += '---\n\n';
    }

    // Add title
    content += `# ${node.title}\n\n`;

    // Add content
    content += node.content || '';

    // Add linked nodes as wikilinks
    const connections = getConnections(node.id);
    if (connections.outgoing.length > 0) {
      content += '\n\n## Related\n\n';
      for (const conn of connections.outgoing) {
        content += `- [[${conn.node.title}]] (${conn.type})\n`;
      }
    }

    await fs.writeFile(filepath, content);
  }
}

JSON-LD Exporter

// src/core/export/jsonld.ts
export async function exportJsonLd(options: ExportOptions): Promise<object> {
  const nodes = await listNodes(options.filter);
  const edges = await listEdges();

  return {
    '@context': {
      '@vocab': 'https://schema.org/',
      'cortex': 'https://cortex.memory/',
      'relates_to': 'cortex:relatesTo',
      'contains': 'cortex:contains',
      'depends_on': 'cortex:dependsOn',
    },
    '@graph': nodes.map(node => ({
      '@id': `cortex:node/${node.id}`,
      '@type': kindToSchemaType(node.kind),
      'name': node.title,
      'description': node.content,
      'keywords': node.tags,
      'dateCreated': new Date(node.createdAt).toISOString(),
      ...edgesToRelations(node.id, edges),
    })),
  };
}

CLI Commands

Command Description
cortex import obsidian <path> Import Obsidian vault
cortex import markdown <path> Import markdown folder
cortex import notion <path> Import Notion export
cortex export-md <path> Export to markdown
cortex export --format jsonld Export as JSON-LD
cortex backup <path> Backup database
cortex restore <path> Restore from backup

MCP Tools

memory_import     // Import from file/path
memory_export     // Export to format
memory_backup     // Create backup

Testing

  • Obsidian wikilinks become edges
  • Tags extracted correctly
  • Frontmatter preserved
  • Folder hierarchy respected
  • Markdown export produces valid files
  • JSON-LD validates against schema
  • Backup/restore preserves all data

Acceptance Criteria

  • Import Obsidian vault with links intact
  • Export to Obsidian-compatible markdown
  • Round-trip preserves data
  • JSON-LD compatible with knowledge graph tools
  • Backup is single file, easily portable
  • Large vaults (10k+ files) import in <5 minutes

Estimated Effort

  • Obsidian importer: 6 hours
  • Markdown importer: 3 hours
  • Markdown exporter: 3 hours
  • JSON-LD exporter: 3 hours
  • Backup/restore: 2 hours
  • Testing: 4 hours
  • Total: ~21 hours

Dependencies

  • gray-matter for frontmatter parsing

References