v0.1.0 · Open Source · MIT · Node.js ≥ 18

Transfer & Sync
Your Notes Everywhere

NoteShift is a TypeScript-first CLI tool and programmatic library for moving notes between Notion, Google Keep, Memos, AFFiNE, and local files — without vendor lock-in.

terminal
# Install globally
npm install -g noteshift

# Configure adapters interactively
noteshift config init

# Transfer notes from Notion to Memos
noteshift transfer --from my-notion --to my-memos

# Backup to local Markdown files
noteshift backup --from my-notion --output ./backup

Powerful Features

Everything you need to manage your notes across platforms

Cross-Platform Transfer

Move notes between any two supported platforms with a single command. Preserves tags, pinned state, and rich metadata.

Backup & Restore

Snapshot all your notes to Markdown or JSON files with full metadata. Restore back to any platform at any time.

Scheduled Backups

Run automated backups via the built-in node-cron daemon or generate OS-native schedulers (crontab, launchd, schtasks).

Conflict Resolution

Handles duplicate notes with interactive diff-based prompts or automatic strategies: overwrite, skip, rename, or merge.

Dry-Run Mode

Preview every planned create / update / skip action in a formatted table before committing a single write.

Programmatic API

Use all adapters and the transfer engine directly in your own Node.js or TypeScript projects via the noteshift package.

Retry & Rate Limiting

Exponential-backoff retry on transient API errors (5 retries, 500ms–60s). Built-in rate limiting per platform to avoid quota violations.

Interactive TUI

Run noteshift without arguments to launch a full interactive terminal UI — guided menus for every operation.

Supported Platforms

Connect any source to any destination

Notion

Read Write

Official Notion API. Supports pages and database entries. Requires an integration token.

Google Keep

Read Write*

Enterprise mode (Google Workspace) for full read/write. Personal mode reads from Google Takeout exports.

Memos

Read Write

Self-hosted Memos instance via its REST API. Supports memo content, tags, pinned state, and visibility.

AFFiNE

Read Write

Connects to self-hosted or cloud AFFiNE workspaces via GraphQL API and WebSocket protocol.

Local Files

Read Write

Markdown (.md) and JSON files on your filesystem. Reads YAML frontmatter for metadata.

Transfer Compatibility Matrix

Source ↓ / Dest → Notion Google Keep Memos AFFiNE Local
Notion Enterprise
Google Keep
Memos Enterprise
AFFiNE Enterprise
Local Enterprise

✓ = Full support  ·  Enterprise = Google Workspace accounts only  ·  — = Same platform

Quick Start

Up and running in under five minutes

1

Install Node.js

NoteShift requires Node.js ≥ 18. Check your version:

node --version
2

Install NoteShift

Install globally via npm:

npm install -g noteshift

Or as a project dependency:

npm install noteshift
3

Configure & Go

Run the interactive setup wizard to connect your platforms:

noteshift config init

Then test your connections and start transferring:

noteshift status
noteshift transfer --from my-notion --to my-memos --dry-run

Documentation

Complete guides, references, and API docs — optimized for GitHub Pages

Getting Started

NoteShift is a Node.js CLI tool and library. All you need is Node.js ≥ 18 and an npm account (optional, for publishing).

Installation

npm install -g noteshift

First-time Setup

Run the interactive wizard to configure your adapters:

noteshift config init

The wizard will:

  1. Ask for your default conflict resolution strategy
  2. Walk you through adding one or more adapters
  3. Test each connection before saving

Test Connections

# Test all adapters
noteshift status

# Test a specific adapter
noteshift status --adapter my-notion

A green next to each adapter means the connection is working.

Your First Transfer

# Always preview first
noteshift transfer --from my-notion --to my-memos --dry-run

# Then run for real
noteshift transfer --from my-notion --to my-memos

Interactive Mode

Run noteshift with no arguments to open the full interactive TUI:

noteshift

Transfer Notes

The transfer command moves notes from one adapter to another. Both --from and --to must be adapter names you configured.

Basic Transfer

noteshift transfer --from my-notion --to my-memos

Filtering

# Filter by tags
noteshift transfer --from my-notion --to my-memos --tags work,important

# Only notes updated after a date
noteshift transfer --from my-notion --to my-memos --updated-after 2026-01-01

# Limit number of notes
noteshift transfer --from my-notion --to my-memos --limit 50

# Include archived notes (excluded by default)
noteshift transfer --from my-notion --to my-memos --include-archived

Dry Run

Preview all planned operations without writing anything:

noteshift transfer --from my-notion --to my-memos --dry-run

Conflict Resolution

When a note with the same title already exists on the destination:

StrategyBehaviour
askShow side-by-side diff and prompt for each conflict
overwriteAlways replace the destination note
skipKeep destination note unchanged
renameCreate note with unique title (e.g. Note (2))
mergeAppend incoming content below a --- divider
noteshift transfer --from my-notion --to my-memos --conflict overwrite

All Transfer Options

FlagRequiredDescription
-f, --from <name>Source adapter
-t, --to <name>Destination adapter
--tags <t1,t2>Comma-separated tag filter
--updated-after <ISO>Only notes updated after timestamp
--limit <n>Maximum notes to transfer
--include-archivedInclude archived notes
--conflict <strategy>ask | overwrite | skip | rename | merge
--batch-size <n>Notes per batch (default: 10)
--dry-runPreview without writing

Backup & Restore

NoteShift can snapshot your notes to local files and restore them later to any platform.

Backup to Markdown

noteshift backup --from my-notion --output ./backup --format markdown

Backup to JSON

noteshift backup --from my-memos --output ./backup.json --format json

Dry-Run Backup

noteshift backup --from my-notion --output ./backup --dry-run

Backup Options

FlagRequiredDescription
-f, --from <name>Source adapter
-o, --output <path>Output directory or file
--format <fmt>markdown | json (default: markdown)
--tags <t1,t2>Tag filter
--limit <n>Maximum notes
--dry-runPreview without writing

Restore

Restore notes from a previous backup to any destination adapter:

# Restore from directory
noteshift restore --to my-memos --input ./backup

# With conflict strategy
noteshift restore --to my-memos --input ./backup --conflict skip

# Dry run first
noteshift restore --to my-memos --input ./backup --dry-run

Backup File Format (Markdown)

Each note is saved as a .md file with YAML frontmatter:

---
id: abc123
title: My Note
tags: [work, project]
pinned: false
archived: false
createdAt: 2026-01-15T09:00:00Z
updatedAt: 2026-01-20T14:30:00Z
source: notion
---

# My Note

Note content here...

Configuration

NoteShift stores config at a platform-specific path. Run noteshift config path to see the exact location.

OSDefault Config Path
Linux / macOS~/.config/noteshift/config.json
Windows%APPDATA%\noteshift\Config\config.json

Notion Adapter

{
  "adapters": {
    "my-notion": {
      "platform": "notion",
      "token": "secret_... or ntn_...",
      "parentId": "database-or-page-id"
    }
  }
}

Google Keep — Enterprise

{
  "adapters": {
    "keep-work": {
      "platform": "google-keep",
      "mode": "enterprise",
      "serviceAccountKeyFile": "/path/to/key.json",
      "userId": "user@company.com"
    }
  }
}

Google Keep — Personal (Takeout)

{
  "adapters": {
    "keep-personal": {
      "platform": "google-keep",
      "mode": "personal",
      "takeoutDirectory": "/path/to/Takeout/Keep"
    }
  }
}

Memos Adapter

{
  "adapters": {
    "my-memos": {
      "platform": "memos",
      "baseUrl": "http://localhost:5230",
      "token": "your-api-token"
    }
  }
}

AFFiNE Adapter

{
  "adapters": {
    "my-affine": {
      "platform": "affine",
      "baseUrl": "http://localhost:3010",
      "token": "your-token",
      "workspaceId": "workspace-id"
    }
  }
}

Local Adapter

{
  "adapters": {
    "local-backup": {
      "platform": "local",
      "directory": "/home/user/notes",
      "format": "markdown"
    }
  }
}

CLI Reference

Complete reference for all NoteShift commands and flags.

Global Flags

FlagDescription
-v, --versionPrint version and exit
--verboseEnable debug-level output
--silentSuppress all output except errors

config

noteshift config init                   # Interactive setup wizard
noteshift config show                   # Print config as JSON
noteshift config path                   # Print config file path
noteshift config adapter add <name>    # Add or update an adapter
noteshift config adapter remove <name> # Remove an adapter
noteshift config adapter list           # List all adapters
noteshift config reset                  # Delete all configuration

status

noteshift status                         # Test all adapters
noteshift status --adapter my-notion    # Test one adapter

transfer

noteshift transfer
  -f, --from <name>          Source adapter (required)
  -t, --to <name>            Destination adapter (required)
  --tags <t1,t2>             Tag filter
  --updated-after <ISO>      Date filter
  --limit <n>                Max notes
  --include-archived          Include archived notes
  --conflict <strategy>      ask|overwrite|skip|rename|merge
  --batch-size <n>           Batch size (default: 10)
  --dry-run                   Preview without writing

backup

noteshift backup
  -f, --from <name>          Source adapter (required)
  -o, --output <path>        Output directory or file (required)
  --format <fmt>             markdown|json (default: markdown)
  --tags <t1,t2>             Tag filter
  --limit <n>                Max notes
  --dry-run                   Preview without writing

restore

noteshift restore
  -t, --to <name>            Destination adapter (required)
  -i, --input <path>         Backup directory or file (required)
  --conflict <strategy>      ask|overwrite|skip|rename|merge
  --dry-run                   Preview without writing

schedule

noteshift schedule list                 # List all schedules
noteshift schedule add                  # Add a new schedule
noteshift schedule disable <id>        # Disable a schedule
noteshift schedule enable <id>         # Enable a schedule
noteshift schedule remove <id>         # Remove a schedule

daemon

noteshift daemon start                  # Start the background daemon
noteshift daemon stop                   # Stop the daemon
noteshift daemon status                 # Check daemon status

os-schedule

noteshift os-schedule generate          # Generate OS-native schedule config
# Supports: Linux crontab, macOS launchd, Windows schtasks

Scheduling

NoteShift provides two ways to automate backups: a built-in Node.js daemon and OS-native scheduler generators.

Built-in Daemon

The daemon runs inside Node.js using node-cron. Ideal for personal machines.

# Add a daily backup at 2 AM
noteshift schedule add \
  --cron "0 2 * * *" \
  --task backup \
  --from my-notion \
  --output ./backup

# Start the daemon
noteshift daemon start

Cron Expression Reference

┌───── minute  (0-59)
│ ┌─── hour    (0-23)
│ │ ┌─ day     (1-31)
│ │ │ ┌ month  (1-12)
│ │ │ │ ┌ weekday (0-6, Sun=0)
* * * * *
ExpressionMeaning
0 2 * * *Every day at 2:00 AM
0 */6 * * *Every 6 hours
30 23 * * 5Every Friday at 11:30 PM
0 9 1 * *First day of every month at 9 AM
*/15 * * * *Every 15 minutes

OS-Native Schedulers

Generate a native schedule config for your OS:

# Linux — outputs a crontab entry
noteshift os-schedule generate --type crontab

# macOS — outputs a launchd .plist file
noteshift os-schedule generate --type launchd

# Windows — outputs an schtasks XML file
noteshift os-schedule generate --type schtasks

Programmatic API

All adapters and the transfer engine are exported from the noteshift package. Install as a library:

npm install noteshift

NotionAdapter

import { NotionAdapter } from 'noteshift';

const adapter = new NotionAdapter({
  platform: 'notion',
  token: 'secret_...',
  parentId: 'database-or-page-id', // optional
});

const notes = await adapter.listNotes();
await adapter.createNote({ title: 'Hello', content: '# Hello' });

MemosAdapter

import { MemosAdapter } from 'noteshift';

const adapter = new MemosAdapter({
  platform: 'memos',
  baseUrl: 'http://localhost:5230',
  token: 'your-api-token',
});

const notes = await adapter.listNotes();
await adapter.createNote({ title: 'Hello', content: 'World' });

GoogleKeepAdapter

import { GoogleKeepAdapter } from 'noteshift';

// Enterprise (service account)
const adapter = new GoogleKeepAdapter({
  platform: 'google-keep',
  mode: 'enterprise',
  serviceAccountKeyFile: '/path/to/key.json',
  userId: 'user@company.com',
});

// Personal (read-only Takeout)
const reader = new GoogleKeepAdapter({
  platform: 'google-keep',
  mode: 'personal',
  takeoutDirectory: '/path/to/Takeout/Keep',
});

LocalAdapter

import { LocalAdapter } from 'noteshift';

const adapter = new LocalAdapter({
  platform: 'local',
  directory: '/home/user/notes',
  format: 'markdown', // or 'json'
});

AffineAdapter

import { AffineAdapter } from 'noteshift';

const adapter = new AffineAdapter({
  platform: 'affine',
  baseUrl: 'http://localhost:3010',
  token: 'your-token',
  workspaceId: 'workspace-id',
});

TransferEngine

import { TransferEngine, NotionAdapter, MemosAdapter } from 'noteshift';

const source = new NotionAdapter({ platform: 'notion', token: '...' });
const dest   = new MemosAdapter({ platform: 'memos', baseUrl: '...', token: '...' });

const engine = new TransferEngine({ conflictStrategy: 'skip' });

const result = await engine.transfer(source, dest, {
  dryRun: false,
  tags: ['work'],
  limit: 100,
});

console.log(`Created: ${result.created}, Updated: ${result.updated}, Skipped: ${result.skipped}`);

NoteAdapter Interface

interface NoteAdapter {
  connect(): Promise<void>;
  disconnect(): Promise<void>;
  listNotes(filter?: NoteFilter): Promise<Note[]>;
  getNote(id: string): Promise<Note>;
  createNote(note: CreateNoteInput): Promise<Note>;
  updateNote(id: string, note: UpdateNoteInput): Promise<Note>;
  deleteNote(id: string): Promise<void>;
  testConnection(): Promise<boolean>;
}

License & Credits

License

NoteShift is open source software licensed under the MIT License.

MIT License

Copyright © 2025–2026 SkyLostTR (@Keeftraum). All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software.

See the LICENSE file in the repository for the complete terms.

Author

SkyLostTR avatar
SkyLost_TR (@Keeftraum)

Creator and maintainer of NoteShift, OST2GO, and other open-source tools.

Dependencies

NoteShift is built on these excellent open-source packages:

  • commander — Command-line framework
  • chalk — Terminal string styling
  • inquirer — Interactive CLI prompts
  • @notionhq/client — Official Notion SDK
  • node-cron — Built-in task scheduler
  • ws — WebSocket client for AFFiNE
  • gray-matter — YAML frontmatter parser
  • diff — Conflict resolution diffs
  • conf — Config file management

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/my-feature)
  3. Write tests for your changes
  4. Submit a pull request
git clone https://github.com/SkyLostTR/NoteShift.git
cd NoteShift
npm install
npm run build
npm test

Wiki Pages

Browse the full NoteShift wiki directly from this GitHub Pages site.

Adapter-specific guides:

Support NoteShift

NoteShift is a free, open-source project. If you find it useful, consider supporting its development!

Every contribution helps: sponsoring, starring the repo, reporting bugs, or improving docs and adapters.