🇺🇸 English | 🇨🇳 中文 | 🇫🇷 Français | 🇪🇸 Español | 🇯🇵 日本語 | 🇰🇷 한국어 | 🇺🇦 Українська

One interface for your entire Cursor history.

Cursor conversations can be spread across workspaces, IDE databases, Agent transcripts, and newer CLI / ACP session stores. cursor-history discovers supported local sources and brings them together through one CLI and Node.js library.

Search conversation content across workspaces, inspect messages and available tool activity, and export to Markdown or JSON. Back up and restore Composer history, or migrate supported Composer sessions when projects move.

Already have months of Cursor history? No prior capture or indexing setup is required. Search runs locally, with no embeddings or API key.

Prefer an MCP server interface? Connect cursor-history-mcp to expose history as MCP tools. Agents can also call this project's CLI or Node.js API directly—choose the interface that fits your workflow. The packages release independently; check MCP storage compatibility and release information.

Quick Start

npm install -g cursor-history

cursor-history list --all
cursor-history search "authentication"
cursor-history show 1
cursor-history export 1
cursor-history backup

Requires Node.js 20.x or 22.x–26.x and existing local Cursor history. To try without a permanent install, run npx cursor-history list --all.

The numbers in show and export refer to the list from the same data source and workspace scope; use the session UUID for saved commands. The backup command archives Composer databases, not Store databases or transcripts.

Installation · Usage · Example output · Library API · Roadmap · Compatibility and safe upgrades

Why this exists

You may remember solving a problem with Cursor without remembering which project, session, or Cursor interface you used. A keyword search across the local history can bring that conversation back.

Cursor's Agent CLI provides agent ls, agent resume, and agent --resume=<id> to find or resume CLI sessions. cursor-history adds workflows for reading and managing supported local history across workspaces and storage formats.

What you want to do Where to start
Continue a conversation in Cursor Agent CLI Cursor's agent ls or agent --resume=<id>
Find a phrase in conversation content across workspaces cursor-history search "connection pool"
Read or export a discovered local session cursor-history show 1 or cursor-history export 1
Back up or restore Composer history cursor-history backup / cursor-history restore (usage)
Move supported Composer sessions to another workspace cursor-history migrate-session (usage)
Build on history in your own application The Node.js API

Works across Cursor storage generations and surfaces

Different Cursor versions and interfaces can leave different local representations on the same machine. cursor-history discovers these supported sources:

Source Local files How they are used
Legacy / Composer workspaceStorage/*/state.vscdb and globalStorage/state.vscdb under the Cursor user data directory Workspace records and global conversation data
Agent transcripts ~/.cursor/projects/**/agent-transcripts/**/*.jsonl Conversation text and tool calls available in the transcript
Store / CLI ~/.cursor/chats/**/store.db Per-session Store conversation data
ACP sessions ~/.cursor/acp-sessions/**/store.db Per-session Store data discovered under the ACP root

See Where Cursor Stores Data for platform paths, custom roots, and WSL configuration.

These representations do not always contain the same fields. A transcript may omit timestamps or tool results. When a usable Store database and transcript coexist within scope, the database supplies the Store conversation and the transcript remains provenance. Source details and timestamp provenance distinguish stored information from inferred values and partial views.

Reading support does not imply backup or migration support: current backup archives contain Composer data only, and Store-only or merged-source sessions cannot be migrated. Broader coverage is on the roadmap. See the Compatibility and Data-Integrity Contract for the full boundaries.

Three things you can do with your history

Find it

cursor-history list --all
cursor-history search "connection pool"
cursor-history show 1

Find the conversation where you already solved the problem, even if it belongs to another workspace. Existing supported local history is searchable without having installed this tool beforehand.

Preserve it

cursor-history export 1
cursor-history backup
cursor-history migrate-session 1 /path/to/new/workspace --dry-run

Export readable sessions to Markdown or JSON. Back up Composer history and preview migration of supported Composer sessions before moving them to another workspace.

Reuse it

Use the Node.js library directly, or connect the separate cursor-history-mcp server so an MCP-capable assistant can search your existing development history.

Months of decisions, fixes, prompts, and tool activity may already be on disk. Make that context easier to find the next time you need it.

Features

  • Dual interface - Use as CLI tool or import as a library in your Node.js projects
  • List sessions - View all chat sessions across workspaces
  • View conversations - Inspect the content available in each source, including:
    • AI responses with natural language explanations
    • Full diff display for file edits and writes with syntax highlighting
    • Detailed tool calls showing all parameters (file paths, search patterns, commands, etc.)
    • AI reasoning and thinking blocks
    • Message timestamps with explicit stored/inferred provenance
  • Search - Search conversation content across workspaces by keyword with highlighted matches
  • Export - Save sessions as Markdown or JSON files
  • Migrate - Move or copy supported Composer sessions between workspaces (e.g., when renaming projects)
  • Backup & Restore - Back up Composer databases and restore them when needed
  • Cross-platform - Works on macOS, Windows, and Linux

Installation

# Install globally
npm install -g cursor-history

# Use the CLI
cursor-history list

If you prefer pnpm:

# Install globally
pnpm add -g cursor-history

# Use the CLI
cursor-history list

From Source

# Clone and build
git clone https://github.com/S2thend/cursor_chat_history.git
cd cursor_chat_history
npm install
npm run build

# Run directly
node dist/cli/index.js list

# Or link globally
npm link
cursor-history list

Equivalent pnpm workflow:

# Clone and build
git clone https://github.com/S2thend/cursor_chat_history.git
cd cursor_chat_history
pnpm install
pnpm build

# Run directly
node dist/cli/index.js list
node dist/cli/index.js show 1 --json

Requirements

  • Node.js 20.x or 22.x–26.x (Node 21 is not supported; Node.js 22.5+ is recommended for built-in SQLite support)
  • Existing local history from Cursor IDE or Agent CLI in a supported format

SQLite Driver Configuration

cursor-history supports two SQLite drivers for maximum compatibility:

Driver Description Node.js capability boundary
node:sqlite Built-in module; selected only when it provides every API required by the operation Import/read support starts in 22.5; online backup starts in 22.16.0 and 23.8.0
better-sqlite3 Native binding and automatic fallback when capable Supported project majors: 20 and 22–26

Automatic Driver Selection

Driver selection is per operation and probes capabilities, not only whether a module imports. In automatic mode cursor-history:

  1. prefers node:sqlite when it implements all APIs required by that operation; then
  2. falls back to an installed, capable better-sqlite3.

If neither provider is capable, the operation returns an actionable typed error. Store snapshot capability failures are fatal and are not silently converted into an empty/partial session or a transcript fallback.

Manual Driver Selection

You can force a specific driver using the environment variable:

# Force better-sqlite3
CURSOR_HISTORY_SQLITE_DRIVER=better-sqlite3 cursor-history list

# Force node:sqlite (the runtime must support every API needed by this operation)
CURSOR_HISTORY_SQLITE_DRIVER=node:sqlite cursor-history list

A forced driver never falls back. If it lacks a required capability, cursor-history reports the driver, operation, missing capability, available alternative, and remedy.

Debug Driver Selection

To see which driver is being used:

DEBUG=cursor-history:* cursor-history list

Library API Driver Control

When using cursor-history as a library, you can control the driver programmatically:

import { setDriver, getActiveDriver, listSessions } from 'cursor-history';

// Force a specific driver before any operations
setDriver('better-sqlite3');

// Check which driver is active
const driver = getActiveDriver();
console.log(`Using driver: ${driver}`);

// Or configure via LibraryConfig
const result = await listSessions({
  sqliteDriver: 'node:sqlite'  // Force node:sqlite for this call
});

Usage

List Sessions

# List recent sessions (default: 20)
cursor-history list

# List all sessions
cursor-history list --all

# List with composer IDs (for external tools)
cursor-history list --ids

# Limit results
cursor-history list -n 10

# List workspaces only
cursor-history list --workspaces

list --workspaces is intentionally unscoped discovery and cannot be combined with --workspace. Choose a path from its output, then use that same --workspace value for scoped session commands.

View a Session

# Show session by index number
cursor-history show 1

# Show with truncated messages (for quick overview)
cursor-history show 1 --short

# Show full AI thinking/reasoning text
cursor-history show 1 --think

# Show full tool call details (commands, content, results)
cursor-history show 1 --tool

# Show full error messages (not truncated to 300 chars)
cursor-history show 1 --error

# Filter by message type (user, assistant, tool, thinking, error)
cursor-history show 1 --only user
cursor-history show 1 --only user,assistant
cursor-history show 1 --only tool,error

# Combine options
cursor-history show 1 --short --think --tool --error
cursor-history show 1 --only user,assistant --short

# Output as JSON
cursor-history show 1 --json
# Search for keyword
cursor-history search "react hooks"

# Limit results
cursor-history search "api" -n 5

# Adjust context around matches
cursor-history search "error" --context 100

Export

# Export single session to Markdown
cursor-history export 1

# Export to specific file
cursor-history export 1 -o ./my-chat.md

# Export as JSON
cursor-history export 1 --format json

# Export all sessions to directory
cursor-history export --all -o ./exports/

# Overwrite existing files
cursor-history export 1 --force

Migrate Sessions

Migration supports eligible Composer sessions. Store-only, merged-source, and ambiguous sessions are rejected; use --dry-run to preview a migration.

# Move a single session to another workspace
cursor-history migrate-session 1 /path/to/new/project

# Move multiple sessions (comma-separated indices or IDs)
cursor-history migrate-session 1,3,5 /path/to/project

# Copy instead of move (keeps original)
cursor-history migrate-session --copy 1 /path/to/project

# Preview what would happen without making changes
cursor-history migrate-session --dry-run 1 /path/to/project

# Move all sessions from one workspace to another
cursor-history migrate /old/project /new/project

# Copy all sessions (backup)
cursor-history migrate --copy /project /backup/project

# Force merge with existing sessions at destination
cursor-history migrate --force /old/project /existing/project

Backup & Restore

Backup archives contain Composer state.vscdb data. They do not include Store databases, Agent transcripts, or ACP session stores. Export readable sessions from those sources to Markdown or JSON when you need a portable copy; exports are not restorable backup archives.

# Create a backup of Composer chat history
cursor-history backup

# Create backup to specific file
cursor-history backup -o ~/my-backup.zip

# Overwrite existing backup
cursor-history backup --force

# List available backups
cursor-history list-backups

# List backups in a specific directory
cursor-history list-backups -d /path/to/backups

# Restore from a backup
cursor-history restore ~/cursor-history-backups/backup.zip

# Restore to a custom location
cursor-history restore backup.zip --target /custom/cursor/data

# Force overwrite existing data
cursor-history restore backup.zip --force

# View sessions from a backup without restoring
cursor-history list --backup ~/backup.zip
cursor-history show 1 --backup ~/backup.zip
cursor-history search "query" --backup ~/backup.zip
cursor-history export 1 --backup ~/backup.zip

Global Options

# Output as JSON (works with all commands)
cursor-history --json list

# Use custom Cursor data path
cursor-history --data-path ~/.cursor-alt list

# Filter by workspace
cursor-history --workspace /path/to/project list

Example Output

List Sessions

cursor-history list

cursor-history - Chat History Browser

Sessions (showing 3 of 42):

  #1  12/26 09:15 AM  cursor_chat_history
      15 messages · Updated 2 min ago
      "Help me fix the migration path issue..."

  #2  12/25 03:22 PM  my-react-app
      8 messages · Updated 18 hours ago
      "Add authentication to the app..."

  #3  12/24 11:30 AM  api-server
      23 messages · Updated 2 days ago
      "Create REST endpoints for users..."

Show Session Details

cursor-history show 1

Session #1 · cursor_chat_history
15 messages · Created 12/26 09:15 AM

────────────────────────────────────────

You: 09:15:23 AM

Help me fix the migration path issue in the codebase

────────────────────────────────────────

Assistant: 09:15:45 AM

I'll help you fix the migration path issue. Let me first examine
the relevant files.

────────────────────────────────────────

Tool: 09:15:46 AM
🔧 Read File
   File: src/core/migrate.ts
   Content: export function migrateSession(sessionId: string...
   Status: ✓ completed

────────────────────────────────────────

Tool: 09:16:02 AM
🔧 Edit File
   File: src/core/migrate.ts

   ```diff
   + function transformPath(path: string): string {
   +   return path.replace(sourcePrefix, destPrefix);
   + }
   ```

   Status: ✓ completed

────────────────────────────────────────

Thinking: 09:16:02 AM
💭 Now I need to update the function to call transformPath
   for each file reference in the bubble data...

────────────────────────────────────────

Assistant: 09:16:30 AM

I've added the path transformation logic. The migration will now
update all file paths when moving sessions between workspaces.

─────────────���──────────────────────────

Error: 09:17:01 AM
 Build failed: Cannot find module './utils'

────────────────────────────────────────

What You Can View

When browsing your chat history, you'll see:

  • Complete conversations - All messages exchanged with Cursor AI
  • Every message rendered - Each resolved message is shown once in order; consecutive duplicates are not folded, so distinct tool calls, provenance, and token data are never hidden
  • Timestamps - Composer sessions retain their historical timestamp recovery and interpolation; Store messages show a time only when Cursor provides a directly mapped turn timestamp
  • Resolved cross-stack sessions - When one byte-exact UUID exists in Composer and Store, compatible Composer identities are preserved while permitted sources produce one provenance-rich resolved view. Workspace scope is applied before payload reads: known off-scope sources remain unopened and make the result partial; permitted sources follow the documented backbone/alignment policy rather than an unconditional field union.
  • AI tool actions - Detailed view of what Cursor AI did:
    • File edits/writes - Full diff display with syntax highlighting showing exactly what changed
    • File reads - File paths and content previews (use --tool for complete content)
    • Search operations - Patterns, paths, and search queries used
    • Terminal commands - Complete command text
    • Directory listings - Paths explored
    • Tool errors - Failed/cancelled operations shown with ❌ status indicator and parameters
    • User decisions - Shows if you accepted (✓), rejected (✗), or pending (⏳) on tool operations
    • Errors - Error messages with ❌ emoji highlighting (extracted from toolFormerData.additionalData.status)
  • AI reasoning - See the AI's thinking process behind decisions (use --think for full text)
  • Code artifacts - Mermaid diagrams, code blocks, with syntax highlighting
  • Natural language explanations - AI explanations combined with code for full context

Display Options

  • Default view - Full messages with truncated thinking (200 chars), file reads (100 chars), and errors (300 chars)
  • --short mode - Truncates user and assistant messages to 300 chars for quick scanning
  • --think flag - Shows complete AI reasoning/thinking text (not truncated)
  • --tool flag - Shows full tool call details, including commands, content, and results
  • --error flag - Shows full error messages instead of 300-char preview
  • --only <types> flag - Filter messages by type: user, assistant, tool, thinking, error (comma-separated)

A natural-language assistant response that also contains structured tool calls matches both the assistant and tool filters. Tool-only records match only tool.

Where Cursor Stores Data

Platform Composer stack Store stack
macOS ~/Library/Application Support/Cursor/User/ ~/.cursor/
Windows %APPDATA%/Cursor/User/ %USERPROFILE%\.cursor\
Linux / WSL ~/.config/Cursor/User/ ~/.cursor/

The tool automatically finds and reads both stacks. Per-session store.db is the primary Store message source. After a capable snapshot/read setup succeeds, the transcript may be a fallback when the database is absent, contains no usable messages, or has a source-data corruption/read failure. Driver capability and snapshot-infrastructure failures are fatal and never become a transcript fallback. A usable database remains the sole Store backbone; a coexisting transcript is retained only as superseded provenance.

Use --data-path <path> or CURSOR_DATA_PATH to point at a custom Cursor data tree. Use CURSOR_STORE_ROOT to configure the Store root independently. A Store root itself, or its chats, projects, or acp-sessions child, is accepted and normalized to the same root.

Inside WSL, Windows-side Store data is normally mounted at /mnt/c/Users/<windows-user>/.cursor. For example: CURSOR_STORE_ROOT=/mnt/c/Users/<windows-user>/.cursor cursor-history list --all. Use the WSL-side ~/.cursor path instead when the sessions were created by a Cursor agent running inside that WSL distribution.

When running the CLI inside WSL against a Windows-mounted project, do not reuse Windows-installed native node_modules. Native packages are platform-specific; install dependencies with Linux Node.js in a separate WSL dependency tree before running Linux-side tests or builds. cursor-history never installs or removes dependencies automatically.

Library API

In addition to the CLI, you can use cursor-history as a library in your Node.js projects:

import {
  listSessions,
  getSession,
  searchSessions,
  exportSessionToMarkdown
} from 'cursor-history';

// List all sessions with pagination
const result = await listSessions({ limit: 10 });
console.log(`Found ${result.pagination.total} sessions`);

for (const session of result.data) {
  console.log(`${session.id}: ${session.messageCount} messages`);
}

// Get a specific session (zero-based index)
const session = await getSession(0);
console.log(session.messages);

// Search across all sessions
const results = await searchSessions('authentication', { context: 2 });
for (const match of results) {
  // Complete message-array index, UTF-16 offset in complete content, and complete source line.
  console.log(match.messageIndex, match.offset, match.match);
}

// Export to Markdown
const markdown = await exportSessionToMarkdown(0);

These search-coordinate semantics are corrected in 0.18.0. If you persisted values returned by v0.16/v0.17, recompute them after upgrading; they are not message identities. Library JSON exports include an additive zero-based session index, consistent with the read API.

Migration API

import { migrateSession, migrateWorkspace } from 'cursor-history';

// Move a session to another workspace
const moveResults = await migrateSession({
  sessions: 3,  // index or ID
  destination: '/path/to/new/project'
});
console.log(moveResults);

// Copy multiple sessions (keeps originals)
const copyResults = await migrateSession({
  sessions: [1, 3, 5],
  destination: '/path/to/project',
  mode: 'copy'
});
console.log(copyResults);

// Migrate all sessions between workspaces
const workspaceResult = await migrateWorkspace({
  source: '/old/project',
  destination: '/new/project'
});
console.log(`Migrated ${workspaceResult.successCount} sessions`);

Backup API

import {
  createBackup,
  restoreBackup,
  validateBackup,
  listBackups,
  getDefaultBackupDir,
  listSessions
} from 'cursor-history';

// Create a backup
const result = await createBackup({
  outputPath: '~/my-backup.zip',
  force: true,
  onProgress: (progress) => {
    console.log(`${progress.phase}: ${progress.filesCompleted}/${progress.totalFiles}`);
  }
});
console.log(`Backup created: ${result.backupPath}`);
console.log(`Sessions: ${result.manifest.stats.sessionCount}`);

// Validate a backup
const validation = await validateBackup('~/backup.zip');
if (validation.status === 'valid') {
  console.log('Backup is valid');
} else if (validation.status === 'warnings') {
  console.log('Backup has warnings:', validation.corruptedFiles);
}

// Restore from backup
const restoreResult = await restoreBackup({
  backupPath: '~/backup.zip',
  force: true
});
console.log(`Restored ${restoreResult.filesRestored} files`);
// Check restoreResult.warnings: corrupt entries are skipped, never restored.

// List available backups
const backups = await listBackups();  // Scans ~/cursor-history-backups/
for (const backup of backups) {
  console.log(`${backup.filename}: ${backup.manifest?.stats.sessionCount} sessions`);
}

// Read sessions from backup without restoring
const sessions = await listSessions({ backupPath: '~/backup.zip' });

Available Functions

Function Description
listSessions(config?) List sessions with pagination
getSession(index, config?) Get full session by index
searchSessions(query, config?) Search across sessions
exportSessionToJson(index, config?) Export session to JSON
exportSessionToMarkdown(index, config?) Export session to Markdown
exportAllSessionsToJson(config?) Export all sessions to JSON
exportAllSessionsToMarkdown(config?) Export all sessions to Markdown
migrateSession(config) Move/copy sessions to another workspace
migrateWorkspace(config) Move/copy all sessions between workspaces
createBackup(config?) Back up Composer chat history
restoreBackup(config) Restore chat history from backup
validateBackup(path) Validate backup integrity
listBackups(directory?) List available backup files
getDefaultBackupDir() Get default backup directory path
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 Options

import type { MessageType } from 'cursor-history';

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
  backupPath?: string;     // Read from backup file instead of live data
  sqliteDriver?: 'better-sqlite3' | 'node:sqlite';  // Force specific SQLite driver
  messageFilter?: MessageType[];  // Filter messages by type (user, assistant, tool, thinking, error)
}

Error Handling

import {
  listSessions,
  createBackup,
  restoreBackup,
  isDatabaseLockedError,
  isDatabaseNotFoundError,
  isSessionNotFoundError,
  isWorkspaceNotFoundError,
  isBackupError,
  isBackupPublishedPermissionError,
  isRestoreRollbackError,
  isRestoreError,
  isInvalidBackupError,
  validateMessageTypes
} from 'cursor-history';

try {
  const result = await listSessions();
} catch (err) {
  if (isDatabaseLockedError(err)) {
    console.error('Database locked - close Cursor and retry');
  } else if (isDatabaseNotFoundError(err)) {
    console.error('Cursor data not found');
  } else if (isSessionNotFoundError(err)) {
    console.error('Session not found');
  } else if (isWorkspaceNotFoundError(err)) {
    console.error('Workspace not found - open project in Cursor first');
  }
}

try {
  await createBackup({ outputPath: '/private/backups/cursor.zip' });
} catch (err) {
  if (isBackupPublishedPermissionError(err)) {
    if (err.details.pathIdentityVerified) {
      console.error('Verified published backup needs a mode correction:', err.details.outputPath);
    } else {
      // The commit point was crossed, but this pathname is untrusted. Do not chmod it from here.
      console.error('Published backup path requires identity recovery:', err.details.outputPath);
    }
  }
}

// Validate untyped filter values before passing them to a read operation
const invalidTypes = validateMessageTypes(['invalid']);
if (invalidTypes.length > 0) {
  console.error('Invalid filter types:', invalidTypes);
}

// Backup-specific errors
try {
  await createBackup();
} catch (err) {
  if (isBackupError(err)) {
    console.error('Backup failed:', err.message);
  } else if (isInvalidBackupError(err)) {
    console.error('Invalid backup file');
  } else if (isRestoreError(err)) {
    console.error('Restore failed:', err.message);
  }
}

try {
  await restoreBackup({ backupPath: '/private/backups/cursor.zip', force: true });
} catch (err) {
  if (isRestoreRollbackError(err)) {
    // These are manifest-relative paths, never private physical locators.
    console.error('Manual recovery required for:', err.details.residualFiles);
  }
}

Compatibility and safe upgrades

The authoritative identity, scoped-index, workspace-I/O, source-fidelity, timestamp, input-limit, backup-permission, and upgrade rules are in the shipped Compatibility and Data-Integrity Contract. Library consumers that persist cursor-history output should read that contract before changing versions.

Warning for v0.17 incremental-library consumers

v0.17 introduced transitional Store/merged behavior that can change positional message keys, replacement signals, and timestamp-watermark assumptions. If your application incrementally stores library output—such as a vibe-history archive—keep cursor-history v0.16 pinned until you can validate the 0.18.0 corrective path. Back up the downstream archive before upgrading.

The confirmed no-consumer-change upgrade path is deliberately narrower: an archive populated from v0.16 Composer-only data can become a complete Composer-backed merged view while retaining every old session, Composer-message, and existing ordinal-derived tool key byte-for-byte. A changed complete view still reports source: "global", so the unchanged consumer performs its existing whole-session atomic replacement; a second identical sync performs no session/content mutations. The unchanged consumer still executes one existing sync_metadata schema-version upsert statement per synchronization; a fresh target may initialize that metadata row, while later same-version upserts are value-preserving bookkeeping outside the session/content mutation count. Store-only turns may be interleaved without renumbering old Composer identities. Do not use a maximum timestamp as the incremental boundary, and never replace complete archived data with source: "workspace-fallback".

Complete affected v0.17 Store/merged data instead has a documented one-time whole-session replacement path. Unstable v0.17 Store positional/cross-format synthetic IDs are not preserved. A degraded v0.17 result must be pinned, retried from complete sources, or migrated manually.

Identity, addressing, and source meaning

  • Session.id remains the native Cursor UUID. Physical source instances and locators are separate and are never encoded into the public ID.
  • CLI/core indices are one-based, public-library read indices are zero-based, and public-library migration selectors are one-based. All are ephemeral within the exact data source, workspace, catalog snapshot, and invocation that produced them; persist the native UUID instead.
  • Migration resolves both numbers and UUIDs through the complete scoped logical catalog. Ambiguous rows retain their displayed positions and return the same typed ambiguity by either selector; they are never skipped, shifted, treated as not found, or mutated.
  • For unchanged Composer input, sessions tied on createdAt retain v0.16's stable discovery order. Composer-backed merged or ambiguous rows keep that tie position; new-only rows follow the legacy tie group in deterministic UUID order.
  • Structured numeric output declares indexScope: "global" | "workspace"; workspace rows also carry the full indexWorkspacePath.
  • Workspace matching uses normalized exact matching first, then one unambiguous complete-component suffix. Ambiguity fails before conversation payload is read.
  • A workspace is a payload-I/O boundary by default. --include-cross-workspace-sources or includeCrossWorkspaceSources: true can load complementary sources only for UUIDs already selected in scope; omitted contributors make the default view explicitly partial.
  • Legacy source reports fidelity: global is complete/replacement-safe and workspace-fallback is partial/unsafe to overwrite complete data. resolvedSource, sources, and resolution report actual Composer/Store provenance additively.
  • Every resolved message includes deterministic timestamp provenance. Human output marks inferred times as approximate; JSON/library consumers receive timestampSource. A legacy timestamp of unprovable origin is retained as unknown, not presented as directly stored.
  • When a usable Store database and transcript coexist inside the permitted scope, this is a supported normal case: the database is the sole Store conversation backbone and the transcript is retained as superseded provenance rather than merged heuristically. A known representation outside the workspace I/O boundary is not opened and makes the scoped view explicitly partial.

Round-trip a CLI index only inside the same workspace scope:

cursor-history --json --workspace /work/a list --all
cursor-history --json --workspace /work/a show 1
cursor-history --json --workspace /work/a search needle-a
cursor-history --workspace /work/a migrate-session 1 /work/destination --dry-run

Use a stable UUID for reusable library addressing (read indices are zero-based):

import { getSession, listSessions } from 'cursor-history';

const workspace = '/work/a';
const page = await listSessions({ workspace, limit: 20 });
const first = page.data[0];

if (first) {
  const session = await getSession(first.id, { workspace });
  console.log(session.id, session.source, session.resolvedSource);
}

Fatal JSON migration note: some v0.17 command-owned failures wrote JSON to stdout. The corrective release writes every fatal JSON object to stderr and leaves stdout empty; successful output remains on stdout. Existing error fields/types/values and exit-category meanings are preserved for the same fixture, with only documented safe additive fields allowed. Scripts that parsed fatal JSON from stdout must read stderr after a nonzero exit.

Public-library search correction in 0.18.0: existing messageIndex now identifies the matched message in the complete returned session.messages array, offset is a zero-based UTF-16 code-unit position in that message's complete original content, and match/context values are complete original source lines. v0.16/v0.17 returned placeholder or snippet-relative values; consumers that persisted those coordinates must recompute them after upgrade. Session, message, and tool identities do not change under this correction. Public-library JSON exports also gain an additive zero-based index; v0.16/v0.17 exports omitted that property.

Backup permissions

Temporary plaintext snapshot workspaces are owner-only (0700 directories and 0600 files on POSIX) and cleaned on success and failure. New final archives default to 0600; force-overwrite preserves an existing mode. backup --shared explicitly requests 0666 & ~currentUmask without broadening temporary files, changing the process umask, or modifying parent permissions. Windows uses its system per-user temporary directory, inherited ACLs, exclusive paths, and the same cleanup contract; this release does not claim independently verified cross-user ACL isolation on Windows. New manifests record the actual running package version as diagnostic producer metadata; it never changes session/message identity, replica equivalence, deduplication, or incremental sync. New backups keep the enclosing manifest.version at 1.0.0 and add an optional canonical metadata-only Composer workspace/UUID inventory with its own independently validated schemaVersion: 1; existing v1 readers may ignore this additive field. This lets --workspace select an archived workspace without extracting other workspace databases. A scoped backup read never extracts the shared global database; it returns the selected workspace view as explicitly partial. Legacy backups with one workspace remain scoped-readable, while legacy multi-workspace backups without this inventory fail closed with BACKUP_WORKSPACE_SCOPE_METADATA_REQUIRED before database extraction.

Session-ID lookup is byte-exact and case-sensitive, including for canonical UUID syntax, matching v0.16 behavior. Persist and reuse the exact Session.id spelling returned by Cursor. A differently cased value is a distinct ID: it is not an alias for lookup, grouping, Composer/Store association, or migration.

Rename/link to the requested backup path is the publication commit point. If a later permission read, adjustment, or identity check fails, the command exits nonzero with BACKUP_PUBLISHED_PERMISSION_FAILED. details.published: true means the commit point was crossed; trust the reported pathname and inspect/correct its mode only when details.pathIdentityVerified: true. When it is false, the pathname may have been replaced or become unverifiable: do not chmod it based on the error, do not assume rollback, and do not blindly retry with --force. On POSIX the permission step follows no links: it verifies the published regular file has the same lossless device/inode identity as private staging, changes mode only through that open descriptor, and rechecks the final path. A replacement race fails without chmodding the replacement. If non-force publication commits but its private sibling cannot be removed safely, BACKUP_PUBLISHED_CLEANUP_FAILED reports output-path identity plus verified and unverified residue paths. Never blindly delete, chmod, or force-retry an unverified path; a concurrent replacement is left untouched.

Restore rejects empty inventories, unmanifested file payloads, invalid manifest type/path pairs, duplicate destinations, and observed links beneath the canonical selected Cursor user root. It stages only size/checksum-valid entries and preflights all destinations; --force does not bypass those checks. Integrity-mismatched entries are reported and skipped. New destinations use an atomic no-clobber publication, while forced replacements publish a new owner-private same-directory inode instead of writing through an existing hard link. Portable Node path APIs cannot atomically compare and then replace or unlink a destination, so a failure after any publication never attempts automatic rollback. It leaves every published destination untouched and throws typed RESTORE_ROLLBACK_INCOMPLETE details containing all safe manifest-relative residual entries plus any verified or unverified private temporary residue paths. Stop Cursor and recover those entries from a known-good backup; never blindly delete an unverified path. Use an owner-controlled destination tree: Node 20 has no portable directory-relative no-follow creation API, so restore does not claim atomic defense against a hostile process swapping an ancestor between the final validation and directory-entry publication.

Roadmap

Extend preservation and migration to the newer sources already supported for reading. These are planned directions; no release version or date is committed yet.

  • Store / ACP backup and restore — Include Store databases, Agent transcripts, and associated metadata in restorable archives, preserving source relationships and validating round trips.
  • Store-only session migration — Move or copy Store-only sessions between workspaces, with workspace bindings and path references updated and checked against Cursor's session discovery.
  • Merged-source session migration — Move or copy sessions represented in both Composer and Store while preserving native identities and keeping the contributing sources consistent.

Until these land, backup and restore cover Composer data, and migration supports eligible Composer sessions. Markdown and JSON exports remain available for readable Store / ACP sessions, but are not restorable backup archives.

Share use cases and reproducible storage examples through GitHub Issues to help prioritize this work.

Development

Building from Source

npm install
npm run build

With pnpm:

pnpm install
pnpm build

Running Tests

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

With pnpm:

pnpm test             # Run all tests
pnpm test:watch       # Watch mode
pnpm typecheck

Releasing to npm

Releases use npm trusted publishing through GitHub Actions. No NPM_TOKEN repository secret is used. Before the first release:

  1. Configure the npm package's trusted publisher for this exact GitHub repository, enter npm-publish.yml as the workflow filename (the file lives at .github/workflows/npm-publish.yml), set the environment to npm-release-verification, and allow the npm publish action.
  2. Create the GitHub environment npm-release-verification, require designated maintainer reviewers, and prevent unreviewed bypass according to the repository's protection policy.

For each release:

  1. Update and validate all versioned package metadata and release notes, complete the documented release gates, and freeze one clean revision.
  2. Confirm the version tag does not already exist, then push only that tag (for example, git push origin v0.18.0). Do not push or move a release tag before the revision is frozen.
  3. The workflow validates sources and every supported runtime, packs exactly once, and binds the candidate to its revision and SHA-256. After those gates pass, the actual publish job pauses at the protected npm-release-verification environment before it can request its OIDC token.
  4. Download that checksum-addressed candidate and complete the private exact-artifact checks in docs/release-verification.md. Approve the environment only after those checks pass.
  5. Approval publishes those same preserved bytes with npm provenance; the workflow does not rebuild or repack them.

Any failed source, runtime, artifact, or private verification blocks publication. Never silently force-move a release tag; remediate an unpublished failed candidate explicitly, and use a new version if any bytes have already been published.