Skip to content

Latest commit

 

History

History
458 lines (380 loc) · 29.3 KB

File metadata and controls

458 lines (380 loc) · 29.3 KB

cursor-history Development Guidelines

Overview

CLI tool and library to browse, search, and export Cursor AI chat history. Built with TypeScript, commander, pluggable SQLite drivers (better-sqlite3 or node:sqlite), and picocolors.

Dual Interface:

  • CLI: Command-line tool for interactive use (cursor-history list, cursor-history show 1 or show <composer-id>)
  • Library: Programmatic API for integration (import { listSessions } from 'cursor-history')

Quick Reference

# Build and run
npm run build && node dist/cli/index.js list

# Development
npm run dev          # Watch mode
npm test             # Run tests
npm run lint         # Lint code
npm run typecheck    # Type check

Architecture

Data Flow

  1. Workspace storage (workspaceStorage/*/state.vscdb)

    • Contains session metadata with correct workspace paths
    • User messages stored in ItemTable under composer.composerData
  2. Global storage (globalStorage/state.vscdb)

    • Contains full AI responses in cursorDiskKV table
    • Keys: composerData:<id> (metadata), bubbleId:<composerId>:<bubbleId> (messages)
  3. Bubble extraction priority (for assistant messages):

    • Mark errors early: toolFormerData.additionalData.status === 'error' but continue extraction
    • toolFormerData.result → check for diff blocks (write/edit operations)
    • toolFormerData.name + status: "completed" → standard tool calls with params
    • text field → natural language explanation (check for JSON diff if starts with {)
    • codeBlocks[].content → code/mermaid artifacts (COMBINED with text, wrapped in ```lang fences)
    • thinking.text → reasoning blocks (marked as [Thinking])
    • Last resort: Recursive walk through all fields to find longest string with markdown features (catches error messages)
    • If marked as error, prefix result with [Error] marker
    • Extractions include timestamps for display only when the source stores a message-level time

Project Structure

src/
├── cli/
│   ├── commands/          # list, show, search, export, migrate, migrate-session
│   ├── formatters/        # table.ts (terminal), json.ts
│   ├── errors.ts          # CLI-specific errors (CliError, SessionNotFoundError)
│   └── index.ts           # CLI entry, global options
├── core/
│   ├── database/          # Pluggable SQLite driver abstraction
│   │   ├── drivers/       # better-sqlite3.ts, node-sqlite.ts adapters
│   │   ├── types.ts       # Database, Statement, DatabaseDriver interfaces
│   │   ├── registry.ts    # DriverRegistry singleton (auto-select, manual set)
│   │   ├── errors.ts      # NoDriverAvailableError, DriverNotAvailableError
│   │   ├── debug.ts       # Debug logging utility
│   │   └── index.ts       # Public API (openDatabase, setDriver, getActiveDriver)
│   ├── storage.ts         # findWorkspaces, listSessions, getSession, extractBubbleText
│   ├── migrate.ts         # migrateSession, migrateWorkspace, copyBubbleDataInGlobalStorage
│   ├── backup.ts          # createBackup, restoreBackup, openBackupDatabase
│   ├── parser.ts          # parseChatData, exportToMarkdown, exportToJson
│   └── types.ts           # ChatSession, Message, Workspace, ToolCall, MigrationMode, etc.
└── lib/
    ├── index.ts           # Library entry point (listSessions, getSession, searchSessions, export*, migrate*, setDriver, getActiveDriver)
    ├── types.ts           # Public library types (Session, Message, SearchResult, MigrateSessionConfig, SqliteDriverName, etc.)
    ├── config.ts          # Configuration validation and merging (including sqliteDriver)
    ├── errors.ts          # Library errors (DatabaseLockedError, SessionNotFoundError, WorkspaceNotFoundError, etc.)
    ├── utils.ts           # Utility functions (getDefaultDataPath)
    └── platform.ts        # getCursorDataPath, expandPath, contractPath, normalizePath, pathsEqual

Architecture: Shared Core

Both CLI and Library share the same core logic:

┌─────────────────────────────────────────────────────────────┐
│                      src/core/                              │
│  storage.ts (DB queries)  +  parser.ts (data parsing)       │
│                    ↑               ↑                        │
└────────────────────┼───────────────┼────────────────────────┘
                     │               │
        ┌────────────┴───────────────┴────────────┐
        │                                         │
        ▼                                         ▼
┌───────────────────┐                 ┌───────────────────────┐
│   src/cli/        │                 │   src/lib/            │
│   commands/       │                 │   index.ts            │
│   (CLI interface) │                 │   (Library API)       │
└───────────────────┘                 └───────────────────────┘

Both share:

  • src/core/storage.ts - listSessions(), getSession(), searchSessions(), findWorkspaceForSession(), findWorkspaceByPath()
  • src/core/migrate.ts - migrateSession(), migrateWorkspace(), copyBubbleDataInGlobalStorage()
  • src/core/parser.ts - exportToJson(), exportToMarkdown()

The library adds:

  • Type conversions (core types → library types)
  • Config validation and merging
  • Custom error classes
  • Pagination wrapper (PaginatedResult<T>)
  • Zero-based indexing (core uses 1-based)

So when someone uses import { listSessions } from 'cursor-history', they're calling the same underlying database queries as cursor-history list CLI command.

Key Implementation Details

Storage Layer (src/core/storage.ts)

  • listSessions() - Uses workspace storage for listing (correct paths); when listing all sessions (no --workspace filter), deduplicates by session ID and attributes to first workspace in deterministic order (.code-workspace paths before folder paths)
  • getSession(identifier, ...) - Get session by 1-based index (number) or composer ID (string). Tries global storage first (full AI responses), falls back to workspace. Returns null when index or composer ID not found.
  • findWorkspaceForSession(sessionId) - Finds which workspace contains a session by ID
  • findWorkspaceByPath(path) - Finds workspace by its project path (folder or .code-workspace file path)
  • readWorkspaceJson(workspaceDir) - Reads workspace path from workspace.json: supports folder (single-folder workspace, file URI) and workspace (.code-workspace file URI); prefers workspace when both exist; same logic used when reading from backup zip
  • getComposerData(db) - Reads composer array, handles both allComposers and legacy formats
  • updateComposerData(db, composers) - Writes composer array, preserves original format
  • resolveSessionIdentifiers(input) - Converts index/ID/comma-separated to session ID array
  • extractBubbleText() - Extracts text from bubble with priority order (all based on DB fields, not pattern matching)
  • extractThinkingText() - Extracts from data.thinking.text DB field
  • formatToolCallWithResult() - Parses toolFormerData.result for diff blocks
  • formatToolCall() - Formats tool calls with parameters using getParam() helper
  • formatDiffBlock() - Formats diff chunks with ```diff markdown fencing
  • getParam() - Helper that tries multiple field name variations for tool parameters

Database Layer (src/core/database/)

Pluggable SQLite driver abstraction supporting both better-sqlite3 and Node.js built-in node:sqlite.

Components:

  • types.ts - Database, Statement, DatabaseDriver interfaces
  • registry.ts - DriverRegistry singleton with driver management
  • drivers/better-sqlite3.ts - Adapter for better-sqlite3 package
  • drivers/node-sqlite.ts - Adapter for Node.js 22.5+ built-in SQLite
  • errors.ts - NoDriverAvailableError, DriverNotAvailableError
  • debug.ts - Debug logging via DEBUG=cursor-history:*
  • index.ts - Public API exports

Driver Selection:

  • Auto-selects best available driver: node:sqlite (preferred, no native bindings) → better-sqlite3 (fallback)
  • Environment override: CURSOR_HISTORY_SQLITE_DRIVER=better-sqlite3|node:sqlite
  • Programmatic control: setDriver('better-sqlite3'), getActiveDriver()
  • Config option: LibraryConfig.sqliteDriver

Key Functions:

  • openDatabase(path) - Async, triggers driver auto-selection, returns read-only Database
  • openDatabaseReadWrite(path) - Async, returns read-write Database for migrations
  • setDriver(name) - Force specific driver
  • getActiveDriver() - Get currently active driver name
  • registry.openSync(path, options) - Sync open (requires driver pre-selected via ensureDriver())

Migration Layer (src/core/migrate.ts)

  • migrateSession(sessionId, options) - Core primitive: move/copy single session between workspaces
  • migrateSessions(options) - Batch migration with partial failure handling
  • migrateWorkspace(options) - Convenience wrapper: migrate all sessions from source workspace
  • copyBubbleDataInGlobalStorage(oldId, newId) - Deep copy bubble data for copy mode (prevents data loss when deleting copies)
  • generateSessionId() - Creates new UUID v4 for copied sessions

Migration modes:

  • move - Removes session from source, adds to destination (like mv)
  • copy - Duplicates session with new ID, both remain independent (like cp)

All special message detection is DB-field based:

  • Errors: toolFormerData.additionalData.status === 'error'
  • Tool calls: toolFormerData.name exists (any status: completed, cancelled, error)
  • Thinking: data.thinking.text
  • Code blocks: data.codeBlocks array

Tool call formatting:

  • Shows tool name, file paths, parameters regardless of completion status
  • Adds Status: ❌ cancelled/error line for failed operations
  • Supports write tool name and relativeWorkspacePath parameter

Bubble Types

  • type: 1 → user message
  • type: 2 → assistant message

Tool Call Format

Tool calls are stored in toolFormerData:

{
  name: "read_file" | "list_dir" | "run_terminal_command" | "write" | "edit_file" | "search_replace" | "grep" | ...
  params: '{"targetFile": "/path/to/file"}' | '{"relativeWorkspacePath": "..."}'
  rawArgs: '...' // alternative to params
  result: '{"contents": "..."}' | '{"diff": {"chunks": [{"diffString": "..."}]}, "resultForModel": "..."}'
  status: "completed"
}

Write/Edit operations store diff in both:

  • text field as JSON: {"diff":{"chunks":[{"diffString":"..."}],"editor":"EDITOR_AI"}}
  • toolFormerData.result as JSON with same structure

Tool parameter field name variations (handled by getParam()):

  • File paths: targetFile, path, file, filePath, relativeWorkspacePath
  • Search patterns: pattern, query, searchQuery, regex
  • Directories: targetDirectory, path, directory
  • Commands: command, cmd
  • Edit strings: oldString, old_string, search, searchString, newString, new_string, replace, replaceString
  • Content: content, fileContent, text

Display Formatters (src/cli/formatters/table.ts)

  • formatSessionDetail(session, workspacePath, options) - Shows full session with display options
    • No folding (P02): Every resolved message is rendered once in canonical array order. Consecutive duplicate folding (×N) was removed from the default show path so distinct structured tool calls, provenance, token usage, and duration are never hidden. --only filters by type and then renders each match without a second dedup pass.
    • options.short - Truncates user/assistant messages to 300 chars
    • options.fullThinking - Shows full thinking text (not truncated to 200 chars)
    • options.fullRead - Shows full file read content (not truncated to 100 chars)
    • options.fullError - Shows full error messages (not truncated to 300 chars)
  • formatTime() - Formats timestamps as HH:MM:SS (only shown when a message has a directly-stored time; absent otherwise — P01)
  • formatToolCallDisplay(content, fullRead) - Formats tool calls with optional full read content
  • formatThinkingDisplay(content, fullThinking) - Formats thinking blocks with optional full text
  • formatErrorDisplay(content, fullError) - Formats error messages with red text and ❌ emoji
  • Role labels with optional timestamps: You: HH:MM:SS, Assistant: HH:MM:SS, Tool: HH:MM:SS, Thinking: HH:MM:SS, Error: HH:MM:SS (the time segment is omitted when the message has no directly-stored timestamp)
  • Merged sessions (source 'merged') render a header line: ⊕ Merged from composer + store (backbone: <preferredSource>)

Display layer detects markers from storage layer:

  • isToolCall() - checks for [Tool: marker
  • isError() - checks for [Error] marker
  • isThinking() - checks for [Thinking] marker
  • All markers are set by storage layer based on DB fields

Library API (src/lib/)

Functions

Function Description
listSessions(config?) List sessions with pagination, returns PaginatedResult<Session>
getSession(identifier, config?) Get full session by zero-based index (number) or composer ID (string)
searchSessions(query, config?) Search across sessions, returns SearchResult[]
exportSessionToJson(identifier, config?) Export single session to JSON (index or composer ID)
exportSessionToMarkdown(identifier, config?) Export single session to Markdown (index or composer ID)
exportAllSessionsToJson(config?) Export all sessions to JSON array string
exportAllSessionsToMarkdown(config?) Export all sessions to Markdown string
migrateSession(config) Move/copy sessions to another workspace
migrateWorkspace(config) Move/copy all sessions between workspaces
getDefaultDataPath() Get platform-specific Cursor data path
setDriver(name) Set SQLite driver ('better-sqlite3' or 'node:sqlite')
getActiveDriver() Get currently active SQLite driver name

Configuration (LibraryConfig)

interface LibraryConfig {
  dataPath?: string;       // Custom Cursor data path
  workspace?: string;      // Filter by workspace path
  limit?: number;          // Pagination limit
  offset?: number;         // Pagination offset
  context?: number;        // Search context lines
  sqliteDriver?: 'better-sqlite3' | 'node:sqlite';  // Force specific SQLite driver
  messageFilter?: MessageType[];  // Filter messages by type (user, assistant, tool, thinking, error)
}

Error Handling

import { listSessions, isDatabaseLockedError, isDatabaseNotFoundError } from 'cursor-history';

try {
  const result = listSessions();
} catch (err) {
  if (isDatabaseLockedError(err)) {
    console.error('Close Cursor and retry');
  } else if (isDatabaseNotFoundError(err)) {
    console.error('Cursor not installed or no history');
  }
}

Key Differences from CLI

  • Zero-based indexing: Library uses getSession(0), CLI uses show 1
  • Structured data: Library returns typed objects, CLI formats for display
  • Stateless: Each function call opens/closes DB connection
  • No formatting: Library returns raw data, no colors or truncation

CLI Commands

Command Description
list List sessions (--all, --ids, --workspaces, -n)
show <index> Show session by index or composer ID (from list --ids) (-s/--short, -t/--think, -f/--fullread, -e/--error, -o/--only)
search <query> Search across sessions (-n, --context)
export [index] Export to md/json (--all, -o, -f, --force)
migrate-session <session> <dest> Move/copy session(s) to workspace (--copy, --dry-run, -f, --debug)
migrate <source> <dest> Move/copy all sessions between workspaces (--copy, --dry-run, -f, --debug)

Show Command Options

  • -s, --short - Truncate user and assistant messages to 300 characters
  • -t, --think - Show full AI thinking/reasoning text (default: 200 char preview)
  • -f, --fullread - Show full file read content (default: 100 char preview)
  • -e, --error - Show full error messages (default: 300 char preview)
  • -o, --only <types> - Filter by message types (comma-separated: user,assistant,tool,thinking,error)

Migration Command Options

  • --copy - Copy sessions instead of moving (keeps originals)
  • --dry-run - Preview migration without making changes
  • -f, --force - Proceed even if destination has existing sessions
  • --debug - Show detailed path transformation logs to stderr (useful for troubleshooting)

Global Options

  • --json - Output as JSON
  • --data-path <path> - Custom Cursor data path
  • --workspace <path> - Filter by workspace

Code Style

  • TypeScript strict mode
  • ESLint + Prettier
  • Prefer existing file edits over creating new files
  • Use picocolors for terminal output, not chalk
  • Handle errors with CliError and exit codes

Testing

npm test              # Run all tests
npm run test:watch    # Watch mode

Common Tasks

Adding a new command

  1. Create src/cli/commands/mycommand.ts
  2. Export registerMyCommand(program: Command)
  3. Import and register in src/cli/index.ts

Modifying bubble extraction

Edit extractBubbleText() in src/core/storage.ts. Priority matters:

  • For assistant: toolFormerData.result (diff check) → toolFormerData.name (tool call) → text (with diff check) → text + codeBlocks (combined) → thinking.text → codeBlocks alone
  • Combine text + codeBlocks, don't choose one
  • Wrap code blocks in markdown fences with language ID

Adding new output format

  1. Add formatter in src/cli/formatters/
  2. Export from src/cli/formatters/index.ts
  3. Use in command with --format option

Active Technologies

  • TypeScript 5.9+ (strict mode enabled)
  • Pluggable SQLite: better-sqlite3 (native bindings) or node:sqlite (Node.js 22.5+ built-in)
  • commander + picocolors for CLI (not used in library)
  • Dual ESM/CommonJS module support
  • SQLite databases (state.vscdb files) + zip archives for backups
  • TypeScript 5.9+ (strict mode enabled) + jszip (replacing adm-zip), commander, picocolors, better-sqlite3/node:sqlite (007-replace-adm-zip)
  • SQLite databases (state.vscdb), zip archives for backup (007-replace-adm-zip)
  • TypeScript 5.9+ (strict mode enabled) + commander (CLI), picocolors (formatting), better-sqlite3/node:sqlite (database) (008-message-type-filter)
  • SQLite databases (state.vscdb files) - read-only for this feature (008-message-type-filter)
  • TypeScript 5.0+ (strict mode enabled) + better-sqlite3 or node:sqlite (existing), picocolors (existing CLI formatting) (009-token-usage)
  • SQLite (read-only access to existing state.vscdb files) (009-token-usage)
  • TypeScript 5.9+ (strict mode enabled) + commander, picocolors, better-sqlite3 / node:sqlite (existing) (010-fix-timestamp-fallback)
  • SQLite databases (state.vscdb files) - read-only access (010-fix-timestamp-fallback)
  • TypeScript 5.9+ (strict mode enabled) + better-sqlite3 / node:sqlite (pluggable), commander, picocolors (012-fix-session-data-integrity)
  • SQLite databases (state.vscdb files) — read-only access (012-fix-session-data-integrity)
  • TypeScript 5.9+ (strict mode) + Node.js built-in only; no new deps (013-fix-tool-content-truncation)
  • N/A (read-only access to existing SQLite; no schema changes) (013-fix-tool-content-truncation)
  • TypeScript 5.9+ (strict mode enabled) + commander, picocolors, better-sqlite3/node:sqlite (existing — no new deps) (014-expose-bubble-id)
  • TypeScript 5.9.3 in strict mode; Node.js >=20.0.0; ES2022 and NodeNext ESM + Node standard library, Commander 14, JSZip 3.10, picocolors 1.1, better-sqlite3 v12 (no new runtime dependencies) (016-harden-session-integrity)
  • Cursor Composer SQLite databases, Cursor Store store.db, Store JSONL transcripts, ZIP backup archives, and local filesystem metadata (016-harden-session-integrity)

Recent Changes

  • 016-harden-session-integrity: Planned v0.16 identity preservation, scoped metadata-first reads, replica-safe addressing/migration, private snapshots, capability-aware SQLite selection, bounded read contexts, and release-blocking compatibility/package tests

  • 015-cursor-store-stack (P01+P02 incremental): Cross-stack merge + no display folding

    • P01: When the same session ID exists in both the Composer (vscdb) and Store (~/.cursor) stacks, the two representations are now field-merged instead of one being discarded. New src/core/store-stack/merge.ts performs a deterministic signature-based LCS alignment (role + normalized text + ordered tool signatures [name + normalized params]); matched messages are merged field by field, unmatched messages are preserved at their relative position, and the preferred source supplies the backbone order (never timestamp sorting). Conflict priority is platform-based via detectPreferredStackSource() in src/lib/platform.ts: WSL prefers Store, Windows/macOS/native Linux prefer Composer, and an explicit --data-path that is a Store root selects Store (detectPlatform() is unchanged; isWSL() is separate). listSessions marks collisions source:'merged' and merges summary scalars; getSession loads both stacks and merges, recomputing messageCount while true scalar conflicts such as lastUpdatedAt follow the same preferred-source rule. Provenance is additive: source:'merged', sources:['composer','store'], preferredSource, and per-message source:'composer'|'store'|'both'.
    • Per-message timestamps are now optional and only attached when directly stored (timestampSource: 'composer-created-at' | 'composer-timing' | 'store-turn-timing'). fillTimestampGaps() is no longer called — session createdAt/updatedAt and Store conversation-start times are NOT copied onto messages. Composer keeps native createdAt/timing; Store transcript/store.db messages carry no fabricated time. Display, JSON, Markdown export, and the library API conditionally emit timestamp/timestampSource/source.
    • P02: Removed consecutive-duplicate folding (×N) from the default show path. Every resolved message renders once in canonical order; --only tool renders each matching structured tool-call message (including empty-text ones) separately. No replacement folding flag was added.
  • 012-fix-session-data-integrity: Restored full session fidelity across storage fallbacks

    • Added shared bubble mapping in src/core/storage.ts so getSession() and getGlobalSession() preserve empty bubbles as [empty message], retain malformed rows as [corrupted message], and populate message.metadata.bubbleType
    • Populated structured message.toolCalls from toolFormerData, including default completed status handling and { _raw: ... } sentinels for invalid params
    • Added session.source to core and library types, threaded it through the library API, exposed it in CLI JSON, and surfaced a degraded warning in CLI detail output for workspace fallback sessions
    • Replaced silent global-load fallbacks with debugLogStorage() messages that distinguish missing global DBs, missing cursorDiskKV, empty composer bubble sets, query/open failures, and malformed bubble rows
  • Workspace file path resolution: Workspaces opened via a .code-workspace file are now discovered and matchable

    • readWorkspaceJson() and readWorkspaceJsonFromBackup() now support the workspace key (workspace file URI) in addition to folder (single-folder URI); prefer workspace when both exist;
    • Listing, --workspace filter, and findWorkspaceByPath() work when the workspace path is the .code-workspace file path
    • Session deduplication: When Cursor has two workspaceStorage entries (folder and .code-workspace), both may receive the same chats. When listing all sessions (no --workspace filter), the tool deduplicates by session ID so each chat appears once. Attribution is deterministic: workspaces are sorted by path with .code-workspace paths before others, so the session is attributed to the .code-workspace workspace when the same session exists in both. Deduplication is not applied when --workspace is used; each workspace's DB is listed as-is. Filtering or exporting by --workspace is unchanged (only that workspace's DB is read).
  • 010-fix-timestamp-fallback: Fixed incorrect timestamps on pre-2025-09 sessions (Issue #13)

    • Extended RawBubbleData.timingInfo with clientRpcSendTime and clientSettleTime fields
    • New extractTimestamp() function in src/core/storage.ts: priority chain createdAt > clientRpcSendTime > clientSettleTime > clientEndTime > null
    • New fillTimestampGaps() function in src/core/storage.ts: two-pass timestamp resolution (direct extraction + neighbor interpolation + session fallback)
    • Validates Unix ms timestamps with > 1_000_000_000_000 threshold
    • Updated getSession() and getGlobalSession() bubble mapping to use new functions
    • No public API changes; Message.timestamp type remains Date
  • 009-token-usage: Added token usage extraction and display

    • Extracts token counts from multiple sources with fallbacks: tokenCount (camelCase), usage (snake_case), contextWindowStatusAtCreation, promptDryRunInfo
    • Per-message display: badge appended after content [model input→output duration]
    • Session-level summary at bottom: context usage, total tokens
    • New types in src/core/types.ts: TokenUsage, SessionUsage, ContextWindowStatus
    • Extended Message interface with tokenUsage?, model?, durationMs? fields
    • Extended ChatSession interface with usage?: SessionUsage field
    • Extraction functions in src/core/storage.ts: extractTokenUsage(), extractModelInfo(), extractTimingInfo(), extractSessionUsage(), extractPromptDryRunInfo()
    • Formatting functions in src/cli/formatters/table.ts: formatTokenCount(), formatDuration(), formatUsageBadge(), formatSessionSummary()
    • Library API: TokenUsage, SessionUsage types exported; getSession() returns usage data
    • JSON output includes token usage on messages and session-level usage
  • 008-message-type-filter: Added message type filtering feature

    • --only <types> option for show command to filter by message type
    • Five filter types: user, assistant, tool, thinking, error
    • MessageType type and MESSAGE_TYPES constant in src/core/types.ts
    • Filter functions in src/cli/formatters/table.ts: getMessageType(), filterMessages(), validateMessageTypes()
    • Library API: LibraryConfig.messageFilter option for getSession()
    • InvalidFilterError for invalid filter type validation
    • JSON output includes filter, filteredMessageCount, and type fields when filtering
    • Informative message when filter results in zero matching messages
  • 006-pluggable-sqlite-driver: Added pluggable SQLite driver system

    • src/core/database/ - New database abstraction module
    • Supports better-sqlite3 (existing) and node:sqlite (Node.js 22.5+ built-in)
    • Auto-selects best available driver: node:sqlite first, then better-sqlite3
    • Environment override: CURSOR_HISTORY_SQLITE_DRIVER=better-sqlite3|node:sqlite
    • Library API: setDriver(), getActiveDriver(), LibraryConfig.sqliteDriver
    • Debug logging via DEBUG=cursor-history:*
    • Solves Node.js v24 ESM compatibility issues with native modules
    • Backup operations use pluggable driver system for reading backup databases
  • 005-fix-migration-paths: Fixed file path references in migrated sessions

    • File paths in bubble data are now updated during migration (move/copy)
    • Path fields updated: toolFormerData.params.{relativeWorkspacePath,targetFile,filePath,path}, codeBlocks[].uri.{path,_formatted,_fsPath}
    • External paths (outside source workspace) are silently preserved
    • Nested path detection prevents infinite replacement loops
    • New --debug flag shows detailed path transformation logs to stderr
    • Dry run now indicates "File paths will be updated to destination workspace"
    • New error: NestedPathError for detecting problematic path configurations
  • 003-migrate-workspace: Added session migration feature

    • src/core/migrate.ts - Core migration logic (move/copy sessions between workspaces)
    • src/cli/commands/migrate-session.ts - Single/multiple session migration command
    • src/cli/commands/migrate.ts - Workspace-level migration command
    • Copy mode creates fully independent copies (no shared bubble data)
    • Handles both allComposers format and legacy array format
    • Library API: migrateSession(), migrateWorkspace()
    • New errors: SessionNotFoundError, WorkspaceNotFoundError, SameWorkspaceError, etc.
  • 002-library-api: Added library API for programmatic access

    • src/lib/index.ts - Main entry point with all public functions
    • src/lib/types.ts - Public TypeScript types (Session, Message, etc.)
    • src/lib/errors.ts - Custom errors (DatabaseLockedError, etc.)
    • src/lib/config.ts - Configuration validation
    • CLI and library share src/core/ for database access
    • Zero-based indexing in library (vs 1-based in CLI)
    • Stateless design: each call opens/closes DB connection