Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 214 additions & 0 deletions .claude/commands/kindle-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# Kindle Highlights Sync

Sync your Kindle highlights to the vault with zero-config setup.

## Important Instructions

**Step 1: Check if this is first run:**

Check if `.kindle/config.json` exists:

- If it EXISTS: Skip to Step 2b
- If it DOES NOT exist: This is first run, go to Step 1b

**Step 1b (First run only): Ask for output folder:**

Ask the user:

```
This is your first time syncing Kindle highlights! Where would you like to save them?

1. 03_Resources/Kindle Highlights (default)
2. A custom path (e.g., 02_Areas/Reading)

Your choice:
```

Wait for their answer, then continue to Step 2a with the `--output` flag.

**Step 2a: Ask how many books to sync:**

Present this text directly:

```
How many books would you like to sync? This syncs your most recent books first.

A. 10 books - Quick test sync of your setup
B. 25 books - Broader sync
C. 50 books - Extensive sync
D. All books - Complete library (10-30 min for large libraries)
E. Custom number - You specify how many

Just reply with A, B, C, D, E, or a number.
```

Wait for their answer, then continue to Step 3.

**Step 2b: Ask how many books to sync:**

Present this text directly:

```
How many books would you like to sync?

A. 10 books (recommended) - Quick sync of your 10 most-recently highlighted books
B. 25 books - Broader sync
C. 50 books - Extensive sync
D. All books - Resyncs your complete library (10-30 min for large libraries -- only )
E. Custom number - You specify how many

Just reply with A, B, C, D, E, or a number.
```

**Step 3: Run the command immediately after their answer:**

Based on their response and whether it's first run:

**If first run (no config):**

- If they answer **A** or **10**: Run
`pnpm kindle:sync --limit 10 --output "[their-folder-choice]"`
- If they answer **B** or **25**: Run
`pnpm kindle:sync --limit 25 --output "[their-folder-choice]"`
- If they answer **C** or **50**: Run
`pnpm kindle:sync --limit 50 --output "[their-folder-choice]"`
- If they answer **D** or **all**: Run
`pnpm kindle:sync --all --output "[their-folder-choice]"`
- If they answer **E** or a number: Run
`pnpm kindle:sync --limit [number] --output "[their-folder-choice]"`

**If NOT first run (config exists):**

- If they answer **A** or **10**: Run `pnpm kindle:sync --limit 10`
- If they answer **B** or **25**: Run `pnpm kindle:sync --limit 25`
- If they answer **C** or **50**: Run `pnpm kindle:sync --limit 50`
- If they answer **D** or **all**: Run `pnpm kindle:sync --all`
- If they answer **E** or a number: Run `pnpm kindle:sync --limit [number]`

Note: If user just presses Enter for default folder, use
`03_Resources/Kindle Highlights` as the path.

## What This Does

Fetches your Kindle highlights from Amazon and creates beautifully formatted
Markdown notes in your vault.

**✨ First Run Magic**:

- First time? You'll choose where to save highlights (defaults to
`03_Resources/Kindle Highlights`)
- Not authenticated? Browser opens automatically for Amazon login
- Everything just works!

**Subsequent syncs:**

- Uses your saved folder location
- Uses cached authentication
- Much faster!

## What Gets Created

Each book creates a note with:

- **YAML frontmatter:** title, author, ASIN, tags, sync date
- **Metadata:** Total highlights, Kindle notebook link
- **All highlights:** Each with location and your notes
- **Kindle app links:** Click to open specific location in Kindle app
- **Notes section:** Space for your own thoughts

## Changing Your Settings

Want to change where highlights are saved or other settings? You have four
options:

### Option 1: View Current Settings

See what your current configuration is:

```bash
pnpm kindle:config
```

### Option 2: Edit Config File Directly (Recommended for Multiple Changes)

Open `.kindle/config.json` in your vault and edit:

```json
{
"outputFolder": "03_Resources/Kindle Highlights",
"templatePath": ".scripts/kindle/templates/kindle-note.md.hbs",
"overwrite": true,
"addTags": ["kindle", "highlights", "books"],
"lastSync": "2025-10-19T22:30:00.000Z"
}
```

**Available settings:**

- `outputFolder`: Where to save notes (default: `03_Resources/Kindle Highlights`)
- `templatePath`: Custom Handlebars template location
- `overwrite`: Whether to replace existing files on re-sync (default: `true`)
- `addTags`: Tags to add to all notes (default: `["kindle", "highlights", "books"]`)
- `lastSync`: Timestamp of last sync (auto-updated)

### Option 3: Reset to First-Run Prompt

Delete the config file to trigger the folder selection prompt again:

```bash
rm .kindle/config.json
# Next /kindle-sync will ask where to save highlights
```

### Option 4: One-Time Override

Use the `--output` flag to save to a different folder just once (without
changing config):

```bash
pnpm kindle:sync --limit 10 --output "01_Projects/Current Reading"
```

## Tips

### Expected Timing

- **10 books:** 1-3 minutes
- **50 books:** 5-10 minutes
- **100+ books (all):** 15-30 minutes

Rate limiting delays are required to avoid Amazon detection.

### Organization

- Notes are automatically named: `<Author Last Name> - <Title>.md`
- All include `#kindle`, `#highlights`, `#books` tags by default
- Easy to find with Obsidian search or graph view

## Troubleshooting

### "Not authenticated" error

Shouldn't happen anymore! The script auto-detects and runs authentication. But
if you see this:

- Just run `/kindle-sync` again
- It will automatically handle authentication

### No books found

- Check if your Kindle library is visible at read.amazon.com/notebook
- Verify you have highlights (not just books in library)

### Preventing file overwrites

By default, kindle-sync will overwrite existing files on re-sync. To preserve
your edits:

- Set `overwrite: false` in `.kindle/config.json`
- Or rename files you've edited so they won't match the sync pattern

### Scraping seems slow

- Normal! Amazon requires delays between requests
- Use smaller number of books for faster syncs
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ pnpm-lock.yaml
.tmp/

# Never commit real API keys
*_API_KEY
*_API_KEY

# Kindle Auth Directory
.kindle/auth/
5 changes: 4 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ build/
.backup/

# Ignore git
.git/
.git/

# Ignore Handlebars templates (use Handlebars-specific syntax)
.scripts/kindle/templates/*.hbs
136 changes: 136 additions & 0 deletions .scripts/kindle/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/usr/bin/env node
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { type BrowserContext, chromium, type Page } from 'playwright'

import { NOTEBOOK_URL } from './lib/config.js'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const AUTH_DIR = path.resolve(__dirname, '../../.kindle/auth')

/**
* Check if authentication is successful by verifying page state
*/
async function checkAuthenticationSuccess(page: Page): Promise<boolean> {
try {
// If library loads, we're authenticated
const count = await page.locator('#kp-notebook-library').count()
return count > 0
} catch (_error) {
return false
}
}

/**
* Poll the page until authentication is successful or timeout
*/
async function waitForAuthenticationSuccess(
page: Page,
timeoutMs = 300000,
): Promise<boolean> {
const startTime = Date.now()
const pollInterval = 2000 // Check every 2 seconds

console.log('⏳ Waiting for authentication to complete...')
console.log(
' (Polling page state automatically - this may take a few minutes)\n',
)

while (Date.now() - startTime < timeoutMs) {
const isAuthenticated = await checkAuthenticationSuccess(page)

if (isAuthenticated) {
return true
}

// Wait before next check
await page.waitForTimeout(pollInterval)
}

throw new Error('Authentication timeout - please try again')
}

/**
* Interactive authentication - opens browser for user to log in
*/
async function main(): Promise<void> {
console.log('🔐 Kindle Authentication\n')
console.log('📁 Session will be saved to:', AUTH_DIR)
console.log('⏱️ Timeout: 5 minutes\n')

const ctx: BrowserContext = await chromium.launchPersistentContext(AUTH_DIR, {
headless: false,
viewport: { height: 900, width: 1000 },
// Position on right side of screen (works for most screen sizes)
args: ['--window-position=50,50'],
})

const page = await ctx.newPage()

try {
console.log('🌐 Opening Amazon Kindle Notebook...\n')
await page.goto(NOTEBOOK_URL, { waitUntil: 'load' })

console.log('📖 INSTRUCTIONS:')
console.log(' 1. Log in to Amazon in the browser window')
console.log(' 2. Wait for your Kindle library/highlights to load')
console.log(' 3. Authentication will be saved automatically!\n')
console.log(" 💡 The script will detect when you're logged in.\n")

// Wait for successful authentication (auto-detected)
await waitForAuthenticationSuccess(page)

console.log('\n✅ Authentication detected! Saving session...\n')

// Wait for network to settle
await page.waitForLoadState('networkidle').catch(() => {
console.log(' (Network still active, continuing anyway)')
})

// Get final state
const finalUrl = page.url()
console.log(` 📍 Final URL: ${finalUrl}`)

// Clean up redirect parameters if needed
if (finalUrl.includes('openid') && !finalUrl.includes('signin')) {
console.log(' 🔄 Cleaning up redirect parameters...')
await page.goto(NOTEBOOK_URL, { waitUntil: 'networkidle' })
}

// Verify cookies
const cookies = await ctx.cookies()
const hasXMain = cookies.some((c) => c.name === 'x-main')
const hasSessionToken = cookies.some((c) => c.name === 'session-token')

console.log(
` 🍪 Auth cookies: x-main=${hasXMain}, session-token=${hasSessionToken}`,
)

if (!hasSessionToken) {
console.log(
'\n⚠️ Warning: Session token not found. Authentication may be incomplete.',
)
console.log(' Try running /kindle-sync again if sync fails.')
}

await ctx.close()

console.log('\n✅ Authentication saved successfully!\n')
console.log('Next steps:')
console.log(' • Run /kindle-sync to sync your highlights')
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
console.error('\n❌ Authentication failed:', errorMessage)
console.log('\nTroubleshooting:')
console.log(' • Make sure you fully logged in to Amazon')
console.log(' • Verify your book library is visible in the browser')
console.log(' • Try running /kindle-sync again\n')
await ctx.close()
process.exit(1)
}
}

main().catch((error: unknown) => {
console.error('❌ Authentication failed:', error)
process.exit(1)
})
Loading