Migrating from Reflect notes to Bear Notes (while implementing Forever ✱ Notes)

Recently, I've been unsatisfied with Reflect notes primarily because the way my brain works doesn't seem to align well with Reflect notes. Systems like Reflect, Roam, Obsidian, etc, all work based on the concept that ideas are scattered and you create links between them.

Perhaps for some jobs and/or people, that is true. However, for me, I spend a lot of time curating my notes, pruning them, and removing old/unnecessary notes hastily scribbled typed out. My notes are much more akin to a digital garden than a sea of thoughts and ideas.

For that reason, I've been wanting to migrate to a new notes platform. However, I've tried most of the big players: Apple Notes, Obsidian, Reflect, Bear, Evernote, NotePlan, etc. The last time I did my survey (2022), Reflect was the clear winner for me.

But, since Bear launched its 2.0 editor in 2023, after I migrated my whole system to Reflect, I hadn't used any of Bear's new features. This update added many features I wanted, such as tables and backlinks. (Reflect has backlinks, but not tables.) Another point of friction that I felt was that Reflect's core business principles didn't quite align with the things I cared about. The focus on AI-related features was cool, but AI doesn't really have a good place in my digital garden... again: curation. I could certainly see how having AI wrangle your untended mass of thoughts is a much more enticing idea when you don't spend a lot of time curating. However, I believe that "Editing is Thinking", thus jotting down a quick note isn't much value beyond helping you remember that thought. Refining that thought into actions, more ideas, etc., is much more valuable to me.

So, to that end, I've opted to migrate back to Bear notes.

But wait—there's more!

Around the same time I had this idea, I also found a cool project called Forever ✱ Notes, which immediately made me go, "Duh". It just made sense to me. The framework is primarily demoed via Apple Notes, but it can definitely work with Bear. The worst part is setting everything up. So I wrote a couple of quick scripts to help migrate all my markdown files over to Bear.

This script generated all of my Month notes, which is quite a chore because otherwise you have to individually link 366 different notes across 12 months. 😵

#!/usr/bin/env ruby

# Forever Notes Monthly Generator for Bear
# ======================================
#
# This script generates or updates monthly navigation notes in Bear Notes using
# a year-agnostic "forever" system. Each month note contains:
#
# 1. Navigation header with Home, Quarter, and prev/next month links
# 2. Daily note links organized in weekly rows (7 days per row)
#
# 📝 UPDATE MODES
# - Default: PREPEND navigation to existing content (safe)
# - --overwrite: REPLACE all content (destructive, requires confirmation)
# - --dry-run: Preview changes without making modifications
#
# Features:
# - Year-agnostic: Only one "Jun" note, not "Jun 2024", "Jun 2025", etc.
# - Consistent day counts: Feb always has 29 days for simplicity
# - Quarter mapping: Q1 (Jan-Mar), Q2 (Apr-Jun), Q3 (Jul-Sep), Q4 (Oct-Dec)
# - Weekly layout: Days displayed in rows of 7 for calendar-like viewing
# - Safe updates: Uses Bear's add-text API to update existing notes
#
# Usage:
#   ruby generate_forever_notes_months_bear.rb                # Update all 12 months
#   ruby generate_forever_notes_months_bear.rb Jun           # Update just June
#   ruby generate_forever_notes_months_bear.rb Jun --dry-run # Preview June content
#
# Dependencies: Bear Notes app with x-callback-url support
# Author: Generated for daily-notes workflow system

require 'uri'
require 'cgi'

class BearMonthGenerator
  MONTHS = [
    { name: 'Jan', days: 31, quarter: 'Q1' },
    { name: 'Feb', days: 29, quarter: 'Q1' },
    { name: 'Mar', days: 31, quarter: 'Q1' },
    { name: 'Apr', days: 30, quarter: 'Q2' },
    { name: 'May', days: 31, quarter: 'Q2' },
    { name: 'Jun', days: 30, quarter: 'Q2' },
    { name: 'Jul', days: 31, quarter: 'Q3' },
    { name: 'Aug', days: 31, quarter: 'Q3' },
    { name: 'Sep', days: 30, quarter: 'Q3' },
    { name: 'Oct', days: 31, quarter: 'Q4' },
    { name: 'Nov', days: 30, quarter: 'Q4' },
    { name: 'Dec', days: 31, quarter: 'Q4' }
  ]

  def initialize
    @dry_run = ARGV.include?('--dry-run')
    @overwrite = ARGV.include?('--overwrite')
    @single_month = ARGV.find { |arg| !arg.start_with?('--') }
    @mode = @overwrite ? 'replace' : 'prepend'
  end

  def generate_all_months
    if @single_month
      month_index = MONTHS.find_index { |m| m[:name].downcase == @single_month.downcase }
      if month_index
        generate_month(MONTHS[month_index], month_index)
      else
        puts "❌ Invalid month: #{@single_month}"
        puts "Valid months: #{MONTHS.map { |m| m[:name] }.join(', ')}"
        exit 1
      end
    else
      MONTHS.each_with_index do |month, index|
        generate_month(month, index)
      end
    end
  end

  private

  def generate_month(month, index)
    prev_month = MONTHS[(index - 1) % 12][:name]
    next_month = MONTHS[(index + 1) % 12][:name]

    content = build_month_content(month, prev_month, next_month)

    if @dry_run
      puts "=== #{month[:name]} ==="
      puts content
      puts "\n"
    else
      create_bear_note(month[:name], content)
    end
  end

  def build_month_content(month, prev_month, next_month)
    header = "[[✱ Home]] | [[#{month[:quarter]}]] | ← [[#{prev_month}]] • [[#{next_month}]] →"

    daily_links = (1..month[:days]).map do |day|
      "[[#{day.to_s.rjust(2, '0')} #{month[:name]}]]"
    end

    # Group days into rows of 7
    rows = daily_links.each_slice(7).map { |week| week.join(' • ') }

    "#{header}\n\n#{rows.join("\n")}"
  end


  def create_bear_note(title, content)
    encoded_title = URI.encode_www_form_component(title).gsub('+', '%20')
    encoded_content = URI.encode_www_form_component(content).gsub('+', '%20')

    url = "bear://x-callback-url/add-text?title=#{encoded_title}&text=#{encoded_content}&mode=#{@mode}"

    action = @mode == 'replace' ? 'Overwriting' : 'Prepending to'
    puts "#{action} note: #{title}"
    system("open '#{url}'")

    # Small delay to avoid overwhelming Bear
    sleep(0.5)
  end
end

if __FILE__ == $0
  puts "Forever Notes Monthly Generator for Bear"
  puts "========================================"

  if ARGV.include?('--help') || ARGV.include?('-h')
    puts "Usage: ruby #{$0} [month] [--dry-run] [--overwrite]"
    puts "  month: Generate a single month (e.g., 'Jun', 'December')"
    puts "  --dry-run: Preview the content without creating notes"
    puts "  --overwrite: Replace all content (default: prepend to existing)"
    puts ""
    puts "Examples:"
    puts "  ruby #{$0} Jun --dry-run      # Preview June note"
    puts "  ruby #{$0} Jun               # Prepend navigation to June note"
    puts "  ruby #{$0} Jun --overwrite   # Replace June note content"
    puts "  ruby #{$0}                   # Prepend navigation to all months"
    exit
  end

  generator = BearMonthGenerator.new

  if ARGV.include?('--dry-run')
    puts "DRY RUN - No notes will be created\n\n"
  elsif ARGV.include?('--overwrite')
    puts ""
    puts "⚠️  WARNING: --overwrite flag detected!"
    puts "This will COMPLETELY OVERWRITE existing content in your notes!"
    puts ""
    puts "Please type: 'I understand it will overwrite my monthly notes' to continue:"
    print "> "
    
    confirmation = STDIN.gets.chomp
    unless confirmation == "I understand it will overwrite my monthly notes"
      puts "❌ Confirmation not received. Exiting safely."
      puts "💡 Remove --overwrite flag to prepend instead."
      exit 1
    end
    puts ""
  else
    puts "✅ Using PREPEND mode - navigation will be added to the top of existing content.\n"
  end

  generator.generate_all_months

  unless ARGV.include?('--dry-run')
    puts "\n✅ All month notes updated in Bear!"
  end
end

Use at your own risk and all that jazz, but I did run it for my notes, and it worked just fine as of Jun 2025.