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 1orshow <composer-id>) - Library: Programmatic API for integration (
import { listSessions } from 'cursor-history')
# 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-
Workspace storage (
workspaceStorage/*/state.vscdb)- Contains session metadata with correct workspace paths
- User messages stored in
ItemTableundercomposer.composerData
-
Global storage (
globalStorage/state.vscdb)- Contains full AI responses in
cursorDiskKVtable - Keys:
composerData:<id>(metadata),bubbleId:<composerId>:<bubbleId>(messages)
- Contains full AI responses in
-
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 paramstextfield → 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
- Mark errors early:
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
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.
listSessions()- Uses workspace storage for listing (correct paths); when listing all sessions (no--workspacefilter), 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 IDfindWorkspaceByPath(path)- Finds workspace by its project path (folder or .code-workspace file path)readWorkspaceJson(workspaceDir)- Reads workspace path fromworkspace.json: supportsfolder(single-folder workspace, file URI) andworkspace(.code-workspace file URI); prefersworkspacewhen both exist; same logic used when reading from backup zipgetComposerData(db)- Reads composer array, handles bothallComposersand legacy formatsupdateComposerData(db, composers)- Writes composer array, preserves original formatresolveSessionIdentifiers(input)- Converts index/ID/comma-separated to session ID arrayextractBubbleText()- Extracts text from bubble with priority order (all based on DB fields, not pattern matching)extractThinkingText()- Extracts fromdata.thinking.textDB fieldformatToolCallWithResult()- ParsestoolFormerData.resultfor diff blocksformatToolCall()- Formats tool calls with parameters usinggetParam()helperformatDiffBlock()- Formats diff chunks with ```diff markdown fencinggetParam()- Helper that tries multiple field name variations for tool parameters
Pluggable SQLite driver abstraction supporting both better-sqlite3 and Node.js built-in node:sqlite.
Components:
types.ts-Database,Statement,DatabaseDriverinterfacesregistry.ts-DriverRegistrysingleton with driver managementdrivers/better-sqlite3.ts- Adapter for better-sqlite3 packagedrivers/node-sqlite.ts- Adapter for Node.js 22.5+ built-in SQLiteerrors.ts-NoDriverAvailableError,DriverNotAvailableErrordebug.ts- Debug logging viaDEBUG=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 DatabaseopenDatabaseReadWrite(path)- Async, returns read-write Database for migrationssetDriver(name)- Force specific drivergetActiveDriver()- Get currently active driver nameregistry.openSync(path, options)- Sync open (requires driver pre-selected viaensureDriver())
migrateSession(sessionId, options)- Core primitive: move/copy single session between workspacesmigrateSessions(options)- Batch migration with partial failure handlingmigrateWorkspace(options)- Convenience wrapper: migrate all sessions from source workspacecopyBubbleDataInGlobalStorage(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 (likemv)copy- Duplicates session with new ID, both remain independent (likecp)
All special message detection is DB-field based:
- Errors:
toolFormerData.additionalData.status === 'error' - Tool calls:
toolFormerData.nameexists (any status: completed, cancelled, error) - Thinking:
data.thinking.text - Code blocks:
data.codeBlocksarray
Tool call formatting:
- Shows tool name, file paths, parameters regardless of completion status
- Adds
Status: ❌ cancelled/errorline for failed operations - Supports
writetool name andrelativeWorkspacePathparameter
type: 1→ user messagetype: 2→ assistant message
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:
textfield as JSON:{"diff":{"chunks":[{"diffString":"..."}],"editor":"EDITOR_AI"}}toolFormerData.resultas 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
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
showpath so distinct structured tool calls, provenance, token usage, and duration are never hidden.--onlyfilters by type and then renders each match without a second dedup pass. options.short- Truncates user/assistant messages to 300 charsoptions.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)
- No folding (P02): Every resolved message is rendered once in canonical array order. Consecutive duplicate folding (×N) was removed from the default
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 contentformatThinkingDisplay(content, fullThinking)- Formats thinking blocks with optional full textformatErrorDisplay(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:markerisError()- checks for[Error]markerisThinking()- checks for[Thinking]marker- All markers are set by storage layer based on DB fields
| 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 |
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)
}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');
}
}- Zero-based indexing: Library uses
getSession(0), CLI usesshow 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
| 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) |
-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)
--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)
--json- Output as JSON--data-path <path>- Custom Cursor data path--workspace <path>- Filter by workspace
- TypeScript strict mode
- ESLint + Prettier
- Prefer existing file edits over creating new files
- Use picocolors for terminal output, not chalk
- Handle errors with
CliErrorand exit codes
npm test # Run all tests
npm run test:watch # Watch mode- Create
src/cli/commands/mycommand.ts - Export
registerMyCommand(program: Command) - Import and register in
src/cli/index.ts
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
- Add formatter in
src/cli/formatters/ - Export from
src/cli/formatters/index.ts - Use in command with
--formatoption
- 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.vscdbfiles) (009-token-usage) - TypeScript 5.9+ (strict mode enabled) + commander, picocolors, better-sqlite3 / node:sqlite (existing) (010-fix-timestamp-fallback)
- SQLite databases (
state.vscdbfiles) - 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)
-
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.tsperforms 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 viadetectPreferredStackSource()insrc/lib/platform.ts: WSL prefers Store, Windows/macOS/native Linux prefer Composer, and an explicit--data-paththat is a Store root selects Store (detectPlatform()is unchanged;isWSL()is separate).listSessionsmarks collisionssource:'merged'and merges summary scalars;getSessionloads both stacks and merges, recomputingmessageCountwhile true scalar conflicts such aslastUpdatedAtfollow the same preferred-source rule. Provenance is additive:source:'merged',sources:['composer','store'],preferredSource, and per-messagesource:'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 nativecreatedAt/timing; Store transcript/store.db messages carry no fabricated time. Display, JSON, Markdown export, and the library API conditionally emittimestamp/timestampSource/source. - P02: Removed consecutive-duplicate folding (×N) from the default
showpath. Every resolved message renders once in canonical order;--only toolrenders each matching structured tool-call message (including empty-text ones) separately. No replacement folding flag was added.
- 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
-
012-fix-session-data-integrity: Restored full session fidelity across storage fallbacks
- Added shared bubble mapping in
src/core/storage.tssogetSession()andgetGlobalSession()preserve empty bubbles as[empty message], retain malformed rows as[corrupted message], and populatemessage.metadata.bubbleType - Populated structured
message.toolCallsfromtoolFormerData, including defaultcompletedstatus handling and{ _raw: ... }sentinels for invalid params - Added
session.sourceto 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, missingcursorDiskKV, empty composer bubble sets, query/open failures, and malformed bubble rows
- Added shared bubble mapping in
-
Workspace file path resolution: Workspaces opened via a .code-workspace file are now discovered and matchable
readWorkspaceJson()andreadWorkspaceJsonFromBackup()now support theworkspacekey (workspace file URI) in addition tofolder(single-folder URI); preferworkspacewhen both exist;- Listing,
--workspacefilter, andfindWorkspaceByPath()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
--workspacefilter), 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--workspaceis used; each workspace's DB is listed as-is. Filtering or exporting by--workspaceis unchanged (only that workspace's DB is read).
-
010-fix-timestamp-fallback: Fixed incorrect timestamps on pre-2025-09 sessions (Issue #13)
- Extended
RawBubbleData.timingInfowithclientRpcSendTimeandclientSettleTimefields - New
extractTimestamp()function insrc/core/storage.ts: priority chaincreatedAt>clientRpcSendTime>clientSettleTime>clientEndTime>null - New
fillTimestampGaps()function insrc/core/storage.ts: two-pass timestamp resolution (direct extraction + neighbor interpolation + session fallback) - Validates Unix ms timestamps with
> 1_000_000_000_000threshold - Updated
getSession()andgetGlobalSession()bubble mapping to use new functions - No public API changes;
Message.timestamptype remainsDate
- Extended
-
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
Messageinterface withtokenUsage?,model?,durationMs?fields - Extended
ChatSessioninterface withusage?: SessionUsagefield - 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,SessionUsagetypes exported;getSession()returns usage data - JSON output includes token usage on messages and session-level usage
- Extracts token counts from multiple sources with fallbacks:
-
008-message-type-filter: Added message type filtering feature
--only <types>option forshowcommand to filter by message type- Five filter types:
user,assistant,tool,thinking,error MessageTypetype andMESSAGE_TYPESconstant insrc/core/types.ts- Filter functions in
src/cli/formatters/table.ts:getMessageType(),filterMessages(),validateMessageTypes() - Library API:
LibraryConfig.messageFilteroption forgetSession() InvalidFilterErrorfor invalid filter type validation- JSON output includes
filter,filteredMessageCount, andtypefields 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) andnode:sqlite(Node.js 22.5+ built-in) - Auto-selects best available driver:
node:sqlitefirst, thenbetter-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
--debugflag shows detailed path transformation logs to stderr - Dry run now indicates "File paths will be updated to destination workspace"
- New error:
NestedPathErrorfor 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 commandsrc/cli/commands/migrate.ts- Workspace-level migration command- Copy mode creates fully independent copies (no shared bubble data)
- Handles both
allComposersformat 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 functionssrc/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