diff --git a/.claude/commands/kindle-sync.md b/.claude/commands/kindle-sync.md new file mode 100644 index 00000000..8e944c69 --- /dev/null +++ b/.claude/commands/kindle-sync.md @@ -0,0 +1,216 @@ +# 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: ` - .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 diff --git a/.gitignore b/.gitignore index e3ed9d7a..0a8dcc31 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,7 @@ pnpm-lock.yaml .worktrees/ # Never commit real API keys -*_API_KEY \ No newline at end of file +*_API_KEY + +# Kindle Auth Directory +.kindle/auth/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index c4580023..f9f06588 100644 --- a/.prettierignore +++ b/.prettierignore @@ -25,5 +25,8 @@ build/ # Ignore git .git/ +# Ignore Handlebars templates (use Handlebars-specific syntax) +.scripts/kindle/templates/*.hbs + # Ignore markdown files -*.md \ No newline at end of file +*.md diff --git a/.scripts/kindle/auth.ts b/.scripts/kindle/auth.ts new file mode 100644 index 00000000..d003ecab --- /dev/null +++ b/.scripts/kindle/auth.ts @@ -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) +}) diff --git a/.scripts/kindle/config.ts b/.scripts/kindle/config.ts new file mode 100644 index 00000000..6c1e9d16 --- /dev/null +++ b/.scripts/kindle/config.ts @@ -0,0 +1,146 @@ +#!/usr/bin/env node +import fs from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import yargs from 'yargs' +import { hideBin } from 'yargs/helpers' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const CONFIG_DIR = path.resolve(__dirname, '../../.kindle') +const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json') + +export interface KindleConfig { + addTags: string[] + lastSync: null | string + outputFolder: string + overwrite: boolean + templatePath: string +} + +/** + * Default configuration + */ +export const DEFAULT_CONFIG: KindleConfig = { + addTags: ['kindle', 'highlights', 'books'], + lastSync: null, + outputFolder: '03_Resources/Kindle Highlights', + overwrite: true, + templatePath: '.scripts/kindle/templates/kindle-note.md.hbs', +} + +/** + * Load configuration from file, or create with defaults if it doesn't exist + */ +export async function loadConfig(): Promise<KindleConfig> { + try { + const content = await fs.readFile(CONFIG_PATH, 'utf-8') + const parsed: unknown = JSON.parse(content) + // Safe to cast as we merge with defaults, ensuring all required fields are present + const config = ( + parsed && typeof parsed === 'object' ? parsed : {} + ) as Partial<KindleConfig> + // Merge with defaults to ensure all fields are present + return { ...DEFAULT_CONFIG, ...config } + } catch (_error: unknown) { + // Config doesn't exist, create it with defaults + console.log('πŸ“ Creating default configuration...') + await saveConfig(DEFAULT_CONFIG) + return DEFAULT_CONFIG + } +} + +/** + * Save configuration to file + */ +export async function saveConfig(config: KindleConfig): Promise<void> { + await fs.mkdir(CONFIG_DIR, { recursive: true }) + await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8') +} + +/** + * Update specific config fields + */ +export async function updateConfig( + updates: Partial<KindleConfig>, +): Promise<KindleConfig> { + const config = await loadConfig() + const updatedConfig = { ...config, ...updates } + await saveConfig(updatedConfig) + return updatedConfig +} + +/** + * Show current configuration + */ +async function showConfig(): Promise<void> { + const currentConfig = await loadConfig() + console.log('πŸ“‹ Current Kindle Sync Configuration:\n') + console.log(JSON.stringify(currentConfig, null, 2)) + console.log(`\nπŸ“ Config file: ${CONFIG_PATH}`) + console.log('πŸ“ Edit this file directly to change settings\n') +} + +/** + * CLI interface + */ +async function main(): Promise<void> { + const argv = await yargs(hideBin(process.argv)) + .command('show', 'Show current configuration') + .command('reset', 'Reset to default configuration') + .option('interactive', { + alias: 'i', + description: 'Run interactive configuration', + type: 'boolean', + }) + .option('set', { + alias: 's', + description: 'Set a config value (format: key=value)', + type: 'string', + }) + .help() + .alias('h', 'help') + .parseAsync() + + if (argv.interactive) { + await showConfig() + } else if (argv.set) { + const parts = argv.set.split('=') + const key = parts[0] + const value = parts.slice(1).join('=') + if (!key || !value) { + console.error('❌ Invalid format. Use: --set key=value') + process.exit(1) + } + + // Parse value (try JSON parse for objects/arrays, otherwise string) + let parsedValue: unknown + try { + parsedValue = JSON.parse(value) + } catch { + parsedValue = value + } + + await updateConfig({ [key]: parsedValue } as Partial<KindleConfig>) + console.log(`βœ… Updated ${key} = ${JSON.stringify(parsedValue)}`) + console.log(`\nπŸ“ Config saved to: ${CONFIG_PATH}`) + } else if (argv._[0] === 'reset') { + await saveConfig(DEFAULT_CONFIG) + console.log('βœ… Configuration reset to defaults') + console.log(JSON.stringify(DEFAULT_CONFIG, null, 2)) + } else { + // Default: show config + const config = await loadConfig() + console.log('πŸ“‹ Current Kindle Sync Configuration:\n') + console.log(JSON.stringify(config, null, 2)) + console.log(`\nπŸ“ Config file: ${CONFIG_PATH}`) + } +} + +// Run CLI if this file is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch((error: unknown) => { + const message = error instanceof Error ? error.message : String(error) + console.error('❌ Error:', message) + process.exit(1) + }) +} diff --git a/.scripts/kindle/lib/config.ts b/.scripts/kindle/lib/config.ts new file mode 100644 index 00000000..594b1cc2 --- /dev/null +++ b/.scripts/kindle/lib/config.ts @@ -0,0 +1,65 @@ +/** + * Configuration for Kindle Notebook scraper + * Centralizes all selectors and scraping parameters + */ + +export interface BookSelectors { + container: string // Sidebar container with list of books + links: string // Individual book items in sidebar (not actual links, but clickable divs) +} + +export interface HighlightSelectors { + item: string // Container for all highlights + location: string // Location is in the highlight header + note: string // Note text + text: string // Highlight text +} + +export interface MetadataSelectors { + author: string // Author in sidebar + title: string // Title in sidebar + truncationWarning: string // Copyright truncation warning +} + +export interface ScraperConfig { + bookSelectors: BookSelectors + highlightSelectors: HighlightSelectors + metadataSelectors: MetadataSelectors + scraping: ScrapingConfig +} + +export interface ScrapingConfig { + rateLimitBaseDelay: number + rateLimitJitter: number +} + +/** + * Default configuration for Kindle Notebook scraper + * These selectors were last verified: 2025-10-18 + * + * NOTE: Amazon's Kindle Notebook page structure may change. + * If scraping fails, inspect the page DOM and update these selectors. + */ +export const DEFAULT_SCRAPER_CONFIG: ScraperConfig = { + bookSelectors: { + container: '#kp-notebook-library', // Sidebar container with list of books + links: '.kp-notebook-library-each-book', // Individual book items in sidebar (not actual links, but clickable divs) + }, + highlightSelectors: { + item: '#kp-notebook-annotations > .a-row.a-spacing-base', // Container for all highlights + location: '#annotationHighlightHeader', // Location is in the highlight header + note: '.kp-notebook-note #note', // Note text + text: '.kp-notebook-highlight #highlight', // Highlight text + }, + metadataSelectors: { + author: '.kp-notebook-library-each-book p.kp-notebook-searchable', // Author in sidebar + title: '.kp-notebook-library-each-book h2.kp-notebook-searchable', // Title in sidebar + truncationWarning: '.copyright-export-limit', // Copyright truncation warning + }, + scraping: { + rateLimitBaseDelay: 500, + rateLimitJitter: 400, + }, +} + +export const NOTEBOOK_URL = 'https://read.amazon.com/notebook' diff --git a/.scripts/kindle/lib/errors.ts b/.scripts/kindle/lib/errors.ts new file mode 100644 index 00000000..b67233bc --- /dev/null +++ b/.scripts/kindle/lib/errors.ts @@ -0,0 +1,41 @@ +/** + * Custom error types for Kindle sync functionality + */ + +/** + * Base error class for all Kindle export errors + */ +export class KindleExportError extends Error { + constructor(message: string) { + super(message) + this.name = this.constructor.name + Error.captureStackTrace(this, this.constructor) + } +} + +/** + * Error thrown when authentication fails or is missing + */ +export class AuthenticationError extends KindleExportError {} + +/** + * Error thrown when data validation fails + */ +export class ValidationError extends KindleExportError { + public errors: string[] + + constructor(message: string, errors: string[]) { + super(message) + this.errors = errors + } +} + +/** + * Format error for user-friendly display + */ +export function formatError(error: unknown): string { + if (error instanceof Error) { + return `${error.name}: ${error.message}` + } + return String(error) +} diff --git a/.scripts/kindle/lib/renderer.ts b/.scripts/kindle/lib/renderer.ts new file mode 100644 index 00000000..9d08091e --- /dev/null +++ b/.scripts/kindle/lib/renderer.ts @@ -0,0 +1,158 @@ +import Handlebars from 'handlebars' +import { decode } from 'html-entities' +import fs from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import { createBookFilename, formatDate } from './utils.js' +import { + type BookExport, + type BookExports, + parseBookExports, +} from './validation.js' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +/** + * Create a Kindle app link for a specific location + */ +function createKindleLink( + bookId: string, + location: string | undefined, +): string { + if (!location) return '' + // Remove commas from location number + const cleanLocation = location.replace(/,/g, '') + return `kindle://book?action=open&asin=${bookId}&location=${cleanLocation}` +} + +/** + * Register Handlebars helpers (runs once at module initialization) + */ +// Helper to decode HTML entities +Handlebars.registerHelper('decode', function (text?: string) { + return new Handlebars.SafeString(decode(text ?? '')) +}) + +// Helper to create Kindle app links +Handlebars.registerHelper( + 'kindleLink', + function (bookId: string, location?: string) { + return createKindleLink(bookId, location) + }, +) + +// Helper to format dates +Handlebars.registerHelper('formatDate', function (date: Date | string) { + return formatDate(date) +}) + +export interface ExportOptions { + overwrite?: boolean + template?: Handlebars.TemplateDelegate<BookExport> + templatePath?: string +} + +export interface ExportResult { + files: { book: BookExport; path: string }[] + skipped: number + written: number +} + +/** + * Load and compile Handlebars template + */ +export async function loadTemplate( + templatePath?: string, +): Promise<Handlebars.TemplateDelegate<BookExport>> { + // Default template location + const defaultTemplate = path.resolve( + __dirname, + '../templates/kindle-note.md.hbs', + ) + const resolvedPath = templatePath || defaultTemplate + + const templateSrc = await fs.readFile(resolvedPath, 'utf-8') + return Handlebars.compile(templateSrc) +} + +/** + * Render a single book to Markdown + */ +export function renderBook( + book: BookExport, + template: Handlebars.TemplateDelegate<BookExport>, +): string { + return template(book) +} + +/** + * Export books to Markdown format using Handlebars template + */ +export async function exportBooksToMarkdown( + books: BookExports, + outputDir: string, + options: ExportOptions = {}, +): Promise<ExportResult> { + const { + overwrite = false, + template: precompiledTemplate, + templatePath, + } = options + + // Use provided template or load from path + const template = precompiledTemplate ?? (await loadTemplate(templatePath)) + + // Ensure output directory exists + await fs.mkdir(outputDir, { recursive: true }) + + let written = 0 + let skipped = 0 + const files: { book: BookExport; path: string }[] = [] + + for (const book of books) { + // Create filename + const filename = createBookFilename(book.title, book.author) + const destPath = path.join(outputDir, filename) + + // Check if file exists and handle overwrite logic + if (!overwrite) { + try { + await fs.access(destPath) + skipped++ + continue // File exists and we don't want to overwrite + } catch { + // File doesn't exist, proceed + } + } + + const markdown = renderBook(book, template) + await fs.writeFile(destPath, markdown, 'utf-8') + written++ + files.push({ book, path: destPath }) + } + + return { files, skipped, written } +} + +/** + * Load books from JSON file + */ +export async function loadBooksFromJSON( + jsonPath: string, +): Promise<BookExports> { + const rawJson = await fs.readFile(jsonPath, 'utf-8') + const parsedJson: unknown = JSON.parse(rawJson) + return parseBookExports(parsedJson) +} + +/** + * Save books to JSON file + */ +export async function saveBooksToJSON( + books: BookExports, + jsonPath: string, +): Promise<void> { + await fs.mkdir(path.dirname(jsonPath), { recursive: true }) + await fs.writeFile(jsonPath, JSON.stringify(books, null, 2), 'utf-8') +} diff --git a/.scripts/kindle/lib/scraper.ts b/.scripts/kindle/lib/scraper.ts new file mode 100644 index 00000000..a28f4909 --- /dev/null +++ b/.scripts/kindle/lib/scraper.ts @@ -0,0 +1,319 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { + type BrowserContext, + chromium, + type ElementHandle, + type Page, +} from 'playwright' + +import type { BookExport, Highlight } from './validation.js' + +import { + DEFAULT_SCRAPER_CONFIG, + NOTEBOOK_URL, + type ScraperConfig, +} from './config.js' +import { AuthenticationError } from './errors.js' +import { generateHighlightId } from './utils.js' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const AUTH_DIR = path.resolve(__dirname, '../../../.kindle/auth') + +export interface BookInfo { + asin: string + author?: string + title: string +} + +export interface ScrapeOptions { + config?: ScraperConfig + headless?: boolean + limit?: number +} + +export interface ScrapeResult { + books: BookExport[] + errors: { asin: string; error: string; title: string }[] +} + +/** + * Extract text content from an element + */ +async function extractText( + item: ElementHandle, + selector: string, +): Promise<string | undefined> { + const el = await item.$(selector) + if (!el) return undefined + const textContent = await el.textContent() + return textContent?.trim() +} + +/** + * Launch a persistent browser context using saved authentication + */ +export async function getContext(headless = false): Promise<BrowserContext> { + return chromium.launchPersistentContext(AUTH_DIR, { + headless, + viewport: { height: 900, width: 1000 }, + // Keep browser in consistent position for both auth and scraping + args: headless ? [] : ['--window-position=50,50'], + }) +} + +/** + * Verify authentication by checking if we're redirected to signin + */ +export async function ensureAuth(page: Page): Promise<void> { + console.log(`πŸ” Navigating to: ${NOTEBOOK_URL}`) + await page.goto(NOTEBOOK_URL, { waitUntil: 'networkidle' }) + console.log(`πŸ“ Current URL: ${page.url()}`) + + if (/signin|ap\/signin/i.test(page.url())) { + throw new AuthenticationError( + 'Not authenticated. Authentication is required to access Kindle highlights.', + ) + } + + console.log('βœ… Authentication verified') +} + +/** + * Discover all books from the sidebar + */ +export async function discoverBooks( + page: Page, + config: ScraperConfig = DEFAULT_SCRAPER_CONFIG, +): Promise<BookInfo[]> { + console.log('πŸ” Discovering books from sidebar...') + + // Wait for notebook container to load + await page + .waitForSelector(config.bookSelectors.container, { + timeout: 10000, + }) + .catch(() => { + console.warn( + '⚠️ Could not find notebook container, proceeding anyway...', + ) + }) + + // Extract all book items from sidebar + const bookSelector = config.bookSelectors.links + const books = await page.$$eval( + bookSelector, + (elements, selectors) => { + return elements.map((el) => { + // ASIN is the ID of the book div + const asin = el.id || '' + + // Extract title + const titleEl = el.querySelector(selectors.title) + const title = (titleEl?.textContent ?? '').trim() || 'Unknown' + + // Extract author + const authorEl = el.querySelector(selectors.author) + let author = (authorEl?.textContent ?? '').trim() || '' + if (author) { + // Remove "By: " prefix if present + author = author.replace(/^By:\s*/i, '') + } + + // Build result object, only including author if it exists + const result: { asin: string; author?: string; title: string } = { + asin, + title, + } + if (author) { + result.author = author + } + + return result + }) + }, + { + author: config.metadataSelectors.author, + title: config.metadataSelectors.title, + }, + ) + + console.log(`πŸ“š Found ${books.length} book(s)`) + return books.filter((b) => b.asin) // Only return books with valid ASINs +} + +/** + * Click a book in the sidebar and wait for highlights to load + */ +export async function clickBook(page: Page, asin: string): Promise<void> { + // Find the book div by ASIN + // The data-action is on a span, and the anchor is inside it + const bookDiv = page.locator(`#${asin}`) + const clickableLink = bookDiv.locator( + 'span[data-action="get-annotations-for-asin"] a', + ) + + await clickableLink.click() + + // Wait for highlights to load + await page + .waitForSelector('#kp-notebook-annotations', { timeout: 5000 }) + .catch(() => { + console.warn('⚠️ Annotations container not found, continuing anyway...') + }) + + // Additional wait for content to populate + await page.waitForTimeout(500) +} + +/** + * Scrape a single book's highlights from the current page (after clicking it) + */ +export async function scrapeBook( + page: Page, + bookInfo: BookInfo, + config: ScraperConfig = DEFAULT_SCRAPER_CONFIG, +): Promise<BookExport> { + const { asin, author, title } = bookInfo + + // Click the book to load its highlights + await clickBook(page, asin) + + // Check for copyright truncation warning + const truncated = + (await page.locator(config.metadataSelectors.truncationWarning).count()) > 0 + + // Extract highlights from the annotations container + const annotationsContainer = '#kp-notebook-annotations' + const highlightItems = await page.$$( + `${annotationsContainer} > .a-row.a-spacing-base`, + ) + + const highlights = await Promise.all( + highlightItems.map(async (item) => { + // Extract highlight text + const text = + (await extractText(item, '.kp-notebook-highlight #highlight')) || '' + + // Extract note text (if present) + const note = await extractText(item, '.kp-notebook-note #note') + + // Extract location and/or page from metadata + const locationText = await extractText(item, '#annotationHighlightHeader') + const location = locationText?.match(/Location:\s*(\S+)/i)?.[1] + const page = locationText?.match(/Page:\s*(\S+)/i)?.[1] + + return { + location, + note, + page, + text, + } + }), + ) + + // Filter out empty highlights and generate stable IDs + const validHighlights: Highlight[] = highlights + .filter((h) => h.text.length > 0) + .map((h) => ({ + ...h, + id: generateHighlightId(h.text, h.location, h.page), + })) + + const notebookUrl = `${NOTEBOOK_URL}?asin=${asin}` + + console.log(`βœ… Scraped: ${title} (${validHighlights.length} highlights)`) + + return { + author: author || undefined, + bookId: asin, + extractedAt: new Date().toISOString(), + highlights: validHighlights, + meta: { + totalHighlights: validHighlights.length, + truncatedByCopyright: truncated || undefined, + }, + notebookUrl, + source: 'read.amazon.com', + title, + } +} + +/** + * Scrape all books from Kindle Notebook + */ +export async function scrapeAllBooks( + options: ScrapeOptions = {}, +): Promise<ScrapeResult> { + const { config = DEFAULT_SCRAPER_CONFIG, headless = false, limit } = options + + console.log('πŸ“– Starting Kindle Notebook scraper\n') + + // Launch browser and authenticate + // Using headed mode by default because Amazon detects headless browsers + const ctx = await getContext(headless) + const page = await ctx.newPage() + + try { + await ensureAuth(page) + + // Discover all books from the sidebar + const allBooks = await discoverBooks(page, config) + + if (allBooks.length === 0) { + console.warn( + '⚠️ No books found. Check selectors or notebook page structure.', + ) + await ctx.close() + return { books: [], errors: [] } + } + + // Apply limit if specified + const books = limit ? allBooks.slice(0, limit) : allBooks + + if (limit && allBooks.length > limit) { + console.log( + `πŸ“š Found ${allBooks.length} books, scraping first ${limit}\n`, + ) + } + + // Scrape each book + const results: BookExport[] = [] + const errors: { asin: string; error: string; title: string }[] = [] + + for (let i = 0; i < books.length; i++) { + const bookInfo = books[i] + console.log( + `\nπŸ“„ Processing book ${i + 1}/${books.length}: ${bookInfo.title}`, + ) + + try { + const book = await scrapeBook(page, bookInfo, config) + results.push(book) + + // Rate limiting: random delay between books + if (i < books.length - 1) { + const delay = + config.scraping.rateLimitBaseDelay + + Math.random() * config.scraping.rateLimitJitter + await page.waitForTimeout(delay) + } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error) + console.error(`❌ Failed to scrape book: ${errorMsg}`) + errors.push({ + asin: bookInfo.asin, + error: errorMsg, + title: bookInfo.title, + }) + } + } + + await ctx.close() + + return { books: results, errors } + } catch (error) { + await ctx.close() + throw error + } +} diff --git a/.scripts/kindle/lib/utils.ts b/.scripts/kindle/lib/utils.ts new file mode 100644 index 00000000..fa7cc693 --- /dev/null +++ b/.scripts/kindle/lib/utils.ts @@ -0,0 +1,75 @@ +import { createHash } from 'node:crypto' + +/** + * Sanitize a string for use in filenames by replacing invalid characters + */ +export function fileSafe(input: string): string { + return input.replace(/[\\/:*?"<>|]/g, '_').trim() +} + +/** + * Generate a stable hash-based ID for a highlight + * Uses text + location/page to ensure stability across exports + */ +export function generateHighlightId( + text: string, + location: string | undefined, + page?: string, +): string { + const positionMarker = location || page + const content = positionMarker ? `${text}::${positionMarker}` : text + return createHash('sha256').update(content).digest('hex').slice(0, 16) +} + +/** + * Format date for YAML frontmatter + */ +export function formatDate(date: Date | string): string { + if (typeof date === 'string') { + date = new Date(date) + } + return date.toISOString().split('T')[0] +} + +/** + * Extract author's last name from full author name + * For multiple authors, extracts the last name of the first author: + * - 3+ authors (with comma): "Juan Rulfo, Gabriel GarcΓ­a MΓ‘rquez, and Douglas J. Weatherford" + * - 2 authors (with "and"): "Juan Rulfo and Gabriel GarcΓ­a MΓ‘rquez" + * - Single author: "Juan Rulfo" + */ +function extractLastName(author: string): string { + if (!author) return 'Unknown' + + let firstAuthor = author.trim() + + // If there's a comma, take everything before the first comma (handles 3+ authors) + if (firstAuthor.includes(',')) { + firstAuthor = firstAuthor.split(',')[0].trim() + } + // Otherwise, if there's "and", take everything before it (handles 2 authors) + else if (/\band\b/i.test(firstAuthor)) { + firstAuthor = firstAuthor.split(/\band\b/i)[0].trim() + } + + // Extract the last name (last word) from the first author + const parts = firstAuthor.split(/\s+/) + return parts[parts.length - 1] || 'Unknown' +} + +/** + * Create a safe filename from book title and author + */ +export function createBookFilename( + title: string, + author: string | undefined, +): string { + const safeTitle = fileSafe(title) + + if (author) { + const lastName = extractLastName(author) + const safeLastName = fileSafe(lastName) + return `${safeLastName} - ${safeTitle}.md` + } + return `${safeTitle}.md` +} diff --git a/.scripts/kindle/lib/validation.ts b/.scripts/kindle/lib/validation.ts new file mode 100644 index 00000000..d4326a30 --- /dev/null +++ b/.scripts/kindle/lib/validation.ts @@ -0,0 +1,79 @@ +import { z } from 'zod' + +import { ValidationError } from './errors.js' + +/** + * Zod schema for a single highlight + */ +export const highlightSchema = z.object({ + id: z.string().min(1), + location: z.string().optional(), + note: z.string().optional(), + page: z.string().optional(), + text: z.string().min(1), +}) + +export type Highlight = z.infer<typeof highlightSchema> + +/** + * Zod schema for book export metadata + */ +export const bookMetaSchema = z + .object({ + totalHighlights: z.number().int().nonnegative().optional(), + truncatedByCopyright: z.boolean().optional(), + }) + .optional() + +export type BookMeta = z.infer<typeof bookMetaSchema> + +/** + * Zod schema for a complete book export + */ +export const bookExportSchema = z.object({ + author: z.string().optional(), + bookId: z.string().min(1), + extractedAt: z.string().datetime(), + highlights: z.array(highlightSchema), + meta: bookMetaSchema, + notebookUrl: z.string().url().optional(), + source: z.literal('read.amazon.com'), + title: z.string().min(1), +}) + +export type BookExport = z.infer<typeof bookExportSchema> + +/** + * Zod schema for an array of book exports + */ +export const bookExportsSchema = z.array(bookExportSchema) + +export type BookExports = z.infer<typeof bookExportsSchema> + +/** + * Format Zod error issues into readable error messages + */ +function formatZodError(error: z.ZodError): string[] { + return error.issues.map((issue) => { + const path = issue.path.join('.') + return `${path}: ${issue.message}` + }) +} + +/** + * Parse and validate an array of book exports + * @throws {ValidationError} if validation fails + */ +export function parseBookExports(data: unknown): BookExports { + try { + return bookExportsSchema.parse(data) + } catch (error) { + if (error instanceof z.ZodError) { + throw new ValidationError( + 'Failed to validate book exports', + formatZodError(error), + ) + } + throw error + } +} diff --git a/.scripts/kindle/sync.ts b/.scripts/kindle/sync.ts new file mode 100644 index 00000000..a2cc13f8 --- /dev/null +++ b/.scripts/kindle/sync.ts @@ -0,0 +1,416 @@ +#!/usr/bin/env node +import { spawn } from 'node:child_process' +import fs from 'node:fs/promises' +import path from 'node:path' +import readline from 'node:readline' +import { fileURLToPath } from 'node:url' +import yargs from 'yargs' +import { hideBin } from 'yargs/helpers' + +import type { BookExports } from './lib/validation.js' + +import { loadConfig, updateConfig } from './config.js' +import { AuthenticationError, formatError } from './lib/errors.js' +import { + exportBooksToMarkdown, + loadTemplate, + saveBooksToJSON, +} from './lib/renderer.js' +import { scrapeAllBooks } from './lib/scraper.js' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const CACHE_PATH = path.resolve(__dirname, '../../.kindle/cache/all_books.json') +const AUTH_DIR = path.resolve(__dirname, '../../.kindle/auth') +const AUTH_SCRIPT = path.resolve(__dirname, './auth.ts') +const CONFIG_PATH = path.resolve(__dirname, '../../.kindle/config.json') + +export interface SyncOptions { + all?: boolean + books?: string + force?: boolean + headless?: boolean + last?: number + limit?: number + outputFolder?: string +} + +interface GetOrScrapeOptions { + all: boolean + force: boolean + headless: boolean + last?: number + limit?: number + specificBooks?: string +} + +/** + * Check if authentication exists + */ +async function checkAuthExists(): Promise<boolean> { + try { + const stats = await fs.stat(AUTH_DIR) + if (!stats.isDirectory()) return false + + // Check if auth directory has files + const files = await fs.readdir(AUTH_DIR) + return files.length > 0 + } catch { + return false + } +} + +/** + * Check if this is the first run (no config file exists) + */ +async function isFirstRun(): Promise<boolean> { + try { + await fs.access(CONFIG_PATH) + return false + } catch { + return true + } +} + +/** + * Prompt user for output folder on first run + */ +async function promptForOutputFolder(): Promise<string> { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }) + + return new Promise((resolve) => { + console.log('\nπŸ“ Kindle Sync Setup\n') + console.log('Where would you like to save your Kindle highlights?') + console.log( + '(Press Enter to use default: 03_Resources/Kindle Highlights)\n', + ) + + rl.question('Output folder: ', (answer) => { + rl.close() + const folder = answer.trim() || '03_Resources/Kindle Highlights' + resolve(folder) + }) + }) +} + +/** + * Run authentication script + */ +async function runAuth(): Promise<void> { + console.log('πŸ” Authentication required. Starting auth flow...\n') + + return new Promise((resolve, reject) => { + const authProcess = spawn('tsx', [AUTH_SCRIPT], { + cwd: path.dirname(AUTH_SCRIPT), + stdio: 'inherit', // Pass through stdin/stdout/stderr + }) + + authProcess.on('close', (code) => { + if (code === 0) { + console.log('\nβœ… Authentication complete! Continuing with sync...\n') + resolve() + } else { + reject(new Error(`Authentication failed with exit code ${code}`)) + } + }) + + authProcess.on('error', (error) => { + reject(new Error(`Failed to start auth process: ${error.message}`)) + }) + }) +} + +/** + * Handle first-run setup: prompt for output folder and save to config + */ +async function ensureFirstRunSetup( + outputFolder?: string, +): Promise<string | undefined> { + const firstRun = await isFirstRun() + + if (firstRun && !outputFolder) { + const userFolder = await promptForOutputFolder() + console.log(`\nβœ… Will save highlights to: ${userFolder}\n`) + await updateConfig({ outputFolder: userFolder }) + return userFolder + } else if (firstRun && outputFolder) { + console.log(`πŸ“ First run: saving output folder to config...\n`) + await updateConfig({ outputFolder }) + return outputFolder + } + + return outputFolder +} + +/** + * Get books either from cache or by scraping, with auth retry logic + */ +async function getOrScrapeBooks( + options: GetOrScrapeOptions, +): Promise<BookExports> { + const { all, force, headless, last, limit, specificBooks } = options + + // Determine scraping strategy + const scrapeLimit = all ? undefined : last ? undefined : limit + const useCache = !force && !all && !specificBooks && !limit + + // Try to use cache first + if (useCache) { + try { + console.log('πŸ” Checking for cached data...') + const { loadBooksFromJSON } = await import('./lib/renderer.js') + const books = await loadBooksFromJSON(CACHE_PATH) + console.log(`βœ… Loaded ${books.length} books from cache\n`) + return books + } catch (_error) { + console.log('πŸ“₯ No cache found, will scrape fresh data\n') + } + } + + // Scrape fresh data + console.log('🌐 Scraping Kindle Notebook...\n') + + if (last) { + console.log(`πŸ“‹ Will scrape all books, then select last ${last}...\n`) + } else if (limit) { + console.log(`πŸ“‹ Will scrape first ${limit} books...\n`) + } + + try { + return await attemptScrape(scrapeLimit, headless, last) + } catch (error) { + // If authentication error, try to re-auth and retry once + if (error instanceof AuthenticationError) { + console.error('\n❌ Authentication expired or invalid.\n') + await runAuth() + console.log('\nπŸ”„ Retrying scrape...\n') + + try { + return await attemptScrape(scrapeLimit, headless, last) + } catch (retryError) { + console.error('\n❌ Scraping failed after re-authentication:') + console.error(formatError(retryError)) + throw retryError + } + } else { + console.error('\n❌ Scraping failed:') + console.error(formatError(error)) + throw error + } + } +} + +/** + * Attempt to scrape books with proper error handling and caching + */ +async function attemptScrape( + scrapeLimit: number | undefined, + headless: boolean, + last: number | undefined, +): Promise<BookExports> { + const scrapeOptions: { headless: boolean; limit?: number } = { headless } + if (scrapeLimit !== undefined) { + scrapeOptions.limit = scrapeLimit + } + const result = await scrapeAllBooks(scrapeOptions) + let books = result.books + + // If using --last flag, get the last N books instead of first N + if (last) { + if (books.length > last) { + console.log( + `\nπŸ“š Selecting last ${last} books from ${books.length} total...\n`, + ) + books = books.slice(-last) + } else { + console.log( + `\nπŸ“š Found ${books.length} books (less than ${last} requested)\n`, + ) + } + } + + if (result.errors.length > 0) { + console.warn(`\n⚠️ Failed to scrape ${result.errors.length} book(s):`) + result.errors.forEach(({ error, title }) => { + console.warn(` - ${title}: ${error}`) + }) + } + + // Save to cache + console.log('\nπŸ’Ύ Saving to cache...') + await saveBooksToJSON(books, CACHE_PATH) + console.log(` βœ… Cached ${books.length} books`) + + return books +} + +/** + * Main sync function + */ +async function sync(options: SyncOptions): Promise<void> { + const { + all = false, + books: specificBooks, + force = false, + outputFolder: initialOutputFolder, + } = options + + console.log('πŸ“š Kindle Highlights Sync\n') + + // Handle first-run setup + const outputFolder = await ensureFirstRunSetup(initialOutputFolder) + + // Check authentication before proceeding + const hasAuth = await checkAuthExists() + if (!hasAuth) { + console.log('⚠️ No authentication found.\n') + await runAuth() + } + + // Load configuration + const config = await loadConfig() + const resolvedOutputFolder = outputFolder || config.outputFolder + + // Resolve output folder relative to vault root + const vaultRoot = path.resolve(__dirname, '../../') + const outputDir = path.resolve(vaultRoot, resolvedOutputFolder) + + console.log(`πŸ“ Output folder: ${resolvedOutputFolder}`) + console.log(`πŸ“„ Template: ${config.templatePath}\n`) + + // Get books from cache or by scraping + const scrapeOptions: GetOrScrapeOptions = { + all, + force, + headless: options.headless ?? false, + } + if (options.last !== undefined) { + scrapeOptions.last = options.last + } + if (options.limit !== undefined) { + scrapeOptions.limit = options.limit + } + if (specificBooks !== undefined) { + scrapeOptions.specificBooks = specificBooks + } + const books = await getOrScrapeBooks(scrapeOptions) + + if (books.length === 0) { + console.log('⚠️ No books to sync') + return + } + + // Filter for specific books if requested + let booksToExport = books + if (specificBooks) { + const bookTitles = specificBooks + .split(',') + .map((t) => t.trim().toLowerCase()) + booksToExport = books.filter((b) => + bookTitles.some((title) => b.title.toLowerCase().includes(title)), + ) + console.log( + `\nπŸ” Filtered to ${booksToExport.length} book(s) matching: ${specificBooks}`, + ) + } + + // Load template + console.log('\nπŸ“ Rendering Markdown...') + const templatePath = path.resolve(vaultRoot, config.templatePath) + const template = await loadTemplate(templatePath) + + // Export to Markdown + const result = await exportBooksToMarkdown(booksToExport, outputDir, { + overwrite: config.overwrite, + template, + }) + + console.log('\nβœ… Sync complete!') + console.log(` πŸ“ Written: ${result.written} file(s)`) + if (result.skipped > 0) { + console.log(` ⏭️ Skipped: ${result.skipped} file(s) (already exist)`) + } + console.log(` πŸ“ Location: ${outputDir}`) + + // Update last sync time + await updateConfig({ lastSync: new Date().toISOString() }) +} + +/** + * CLI interface + */ +async function main(): Promise<void> { + const argv = await yargs(hideBin(process.argv)) + .option('all', { + alias: 'a', + default: false, + description: 'Sync all books (re-scrape everything)', + type: 'boolean', + }) + .option('limit', { + alias: 'l', + description: 'Limit number of books to sync - takes FIRST N books', + type: 'number', + }) + .option('last', { + description: 'Sync last N books instead of first N', + type: 'number', + }) + .option('force', { + alias: 'f', + default: false, + description: 'Force re-scrape even if cache exists', + type: 'boolean', + }) + .option('books', { + alias: 'b', + description: + 'Comma-separated list of book titles to sync (partial match)', + type: 'string', + }) + .option('output', { + alias: 'o', + description: 'Override output folder from config', + type: 'string', + }) + .option('headless', { + default: false, + description: + 'Run browser in headless mode (not recommended - may cause auth issues)', + type: 'boolean', + }) + .example('$0', 'Sync using cached data') + .example('$0 --all', 'Re-scrape all books') + .example('$0 --limit 5', 'Scrape first 5 books only') + .example('$0 --last 5', 'Scrape last 5 books only') + .example('$0 --books "Atomic Habits,Deep Work"', 'Sync specific books') + .help() + .alias('h', 'help') + .parseAsync() + + const syncOptions: SyncOptions = { + all: argv.all, + force: argv.force, + headless: argv.headless, + } + if (argv.books !== undefined) { + syncOptions.books = argv.books + } + if (argv.last !== undefined) { + syncOptions.last = argv.last + } + if (argv.limit !== undefined) { + syncOptions.limit = argv.limit + } + if (argv.output !== undefined) { + syncOptions.outputFolder = argv.output + } + await sync(syncOptions) +} + +main().catch((error: unknown) => { + console.error('\n❌ Sync failed:', formatError(error)) + process.exit(1) +}) diff --git a/.scripts/kindle/templates/kindle-note.md.hbs b/.scripts/kindle/templates/kindle-note.md.hbs new file mode 100644 index 00000000..fac46e59 --- /dev/null +++ b/.scripts/kindle/templates/kindle-note.md.hbs @@ -0,0 +1,26 @@ +--- +title: "{{title}}" +author: "{{#if author}}{{author}}{{else}}Unknown{{/if}}" +asin: {{bookId}} +highlights: {{highlights.length}} +lastSynced: {{extractedAt}} +--- + +# {{title}} + +{{#if meta.truncatedByCopyright}} + +> ⚠️ Some highlights were truncated due to publisher export limits. +{{/if}} + +## Highlights + +{{#each highlights}} +{{text}}{{#if location}} - loc: [{{location}}]({{kindleLink ../bookId location}}){{else}}{{#if page}} - page: {{page}}{{/if}}{{/if}} + +{{#if note}} +***Note:*** {{{decode note}}} +{{/if}} + +--- +{{/each}} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 45726055..db3c20ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to ## [Unreleased] +### Added + +- New `kindle-sync` command for syncing a user's kindle highlights and notes to + their vault. ## [0.14.2] - 2026-01-13 ### Changed diff --git a/README.md b/README.md index 132db7a3..07003fb3 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,11 @@ Pre-configured AI assistants ready to use: - `install-claudesidian-command` - Install shell command to launch vault from anywhere +**Kindle Integration:** + +- `kindle-sync` - Sync your Kindle highlights (interactive: choose 10, 25, 50, + or all books) + Run with: `/[command-name]` in Claude Code ### Staying Updated with `/upgrade` @@ -253,6 +258,60 @@ npm run firecrawl:batch -- urls.txt "03_Resources/Research" 4. Copy the key (format: `fc-xxxxx...`) 5. Set it in your environment: `export FIRECRAWL_API_KEY="fc-your-key-here"` +## Kindle Highlights Sync + +Automatically sync your Kindle highlights to your vault with zero configuration. +On first run, you'll choose where to save highlights (defaults to +`03_Resources/Kindle Highlights`), then the integration handles everything else: + +- **Interactive sync**: Choose how many books to sync (10, 25, 50, or all) +- **Smart authentication**: Browser opens automatically when needed +- **Formatted notes**: Each book becomes a Markdown file with YAML frontmatter, + tags, and Kindle app deep links +- **Auto-retry**: If authentication expires mid-sync, automatically + re-authenticates + +**Quick Start:** + +```bash +# In Claude Code, run: +/kindle-sync + +# You'll be prompted to choose: +# - Default (10): Quick sync of 10 most recent books +# - Custom number: Any amount (e.g., 25, 50) +# - All: Complete library (takes 10-30 minutes for large libraries) + +# First time only: +# 1. Choose output folder (or press Enter for default) +# 2. Browser opens for Amazon login +# 3. Syncs your selected books +``` + +**What you get:** + +Each book creates a note with: + +- YAML frontmatter (title, author, ASIN, tags, sync date) +- All highlights with locations and your notes +- Kindle app deep links to open specific passages in Kindle app +- Formatted with your custom template (`.scripts/kindle/templates/`) + +**Changing settings:** + +All settings are saved to `.kindle/config.json`. To change where highlights are +saved or other options: + +1. **Edit directly**: Open `.kindle/config.json` and change `outputFolder` or + other settings +2. **Reset setup**: Delete `.kindle/config.json` to trigger the first-run folder + prompt again +3. **One-time override**: Run with `--output` flag to save to a different folder + just once + +See `/kindle-sync` command documentation for full details on all configuration +options. + ## Helper Scripts Run these with `pnpm`: diff --git a/package-lock.json b/package-lock.json index 0a4021f7..c696c3fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,37 +1,483 @@ { "name": "claudesidian", - "version": "0.9.2", + "version": "0.13.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claudesidian", - "version": "0.9.2", + "version": "0.13.1", "license": "MIT", "dependencies": { "@google/generative-ai": "^0.21.0", - "@modelcontextprotocol/sdk": "^1.0.0" + "@modelcontextprotocol/sdk": "^1.0.0", + "handlebars": "^4.7.8", + "html-entities": "^2.6.0", + "playwright": "^1.48.0", + "yargs": "^17.7.2", + "zod": "^3.23.8" }, "devDependencies": { "@eslint/compat": "^1.3.0", - "@next/eslint-plugin-next": "^15.3.4", + "@types/node": "^24.9.1", + "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.35.0", "@typescript-eslint/parser": "^8.35.0", "eslint": "^9.29.0", "eslint-plugin-check-file": "^3.3.0", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-perfectionist": "^4.15.0", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^5.2.0", "globals": "^16.2.0", "prettier": "^3.6.0", "prettier-plugin-packagejson": "^2.5.15", "prettier-plugin-sort-json": "^4.1.1", - "prettier-plugin-tailwindcss": "^0.6.13", + "tsx": "^4.20.6", + "typescript": "^5.9.3", "typescript-eslint": "^8.35.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -346,16 +792,6 @@ "node": ">=18" } }, - "node_modules/@next/eslint-plugin-next": { - "version": "15.3.4", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.4.tgz", - "integrity": "sha512-lBxYdj7TI8phbJcLSAqDt57nIcobEign5NYIKCiy0hXQhrUbTqLqOaSDi568U6vFg4hJfBdZYsG4iP/uKhCqgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "3.3.1" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -435,18 +871,45 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", + "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", - "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", + "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/type-utils": "8.35.0", - "@typescript-eslint/utils": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/type-utils": "8.46.2", + "@typescript-eslint/utils": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -460,22 +923,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.35.0", + "@typescript-eslint/parser": "^8.46.2", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz", - "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", + "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/typescript-estree": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4" }, "engines": { @@ -487,18 +950,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", - "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", + "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.35.0", - "@typescript-eslint/types": "^8.35.0", + "@typescript-eslint/tsconfig-utils": "^8.46.2", + "@typescript-eslint/types": "^8.46.2", "debug": "^4.3.4" }, "engines": { @@ -509,18 +972,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", - "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", + "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0" + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -531,9 +994,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", - "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", + "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", "dev": true, "license": "MIT", "engines": { @@ -544,18 +1007,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz", - "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", + "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.35.0", - "@typescript-eslint/utils": "8.35.0", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/utils": "8.46.2", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -568,13 +1032,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", - "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", + "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", "dev": true, "license": "MIT", "engines": { @@ -586,16 +1050,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", - "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", + "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.35.0", - "@typescript-eslint/tsconfig-utils": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", + "@typescript-eslint/project-service": "8.46.2", + "@typescript-eslint/tsconfig-utils": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -611,7 +1075,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { @@ -645,16 +1109,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", - "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", + "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/typescript-estree": "8.35.0" + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -665,17 +1129,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", - "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", + "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/types": "8.46.2", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -751,11 +1215,19 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -774,16 +1246,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -824,27 +1286,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array.prototype.findlastindex": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", @@ -905,23 +1346,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", @@ -944,13 +1368,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ast-types-flow": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", - "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", - "dev": true, - "license": "MIT" - }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -977,26 +1394,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axe-core": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", - "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", - "dev": true, - "license": "MPL-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1131,11 +1528,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1148,7 +1558,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/concat-map": { @@ -1224,13 +1633,6 @@ "node": ">= 8" } }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -1410,13 +1812,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1513,34 +1908,6 @@ "node": ">= 0.4" } }, - "node_modules/es-iterator-helpers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", - "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.6", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.4", - "safe-array-concat": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -1600,6 +1967,57 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1838,60 +2256,6 @@ "semver": "bin/semver.js" } }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", - "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "aria-query": "^5.3.2", - "array-includes": "^3.1.8", - "array.prototype.flatmap": "^1.3.2", - "ast-types-flow": "^0.0.8", - "axe-core": "^4.10.0", - "axobject-query": "^4.1.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "hasown": "^2.0.2", - "jsx-ast-utils": "^3.3.5", - "language-tags": "^1.0.9", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "safe-regex-test": "^1.0.3", - "string.prototype.includes": "^2.0.1" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint-plugin-perfectionist": { "version": "4.15.0", "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-4.15.0.tgz", @@ -1910,104 +2274,6 @@ "eslint": ">=8.45.0" } }, - "node_modules/eslint-plugin-react": { - "version": "7.37.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", - "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.9", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -2255,46 +2521,16 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT" }, @@ -2423,6 +2659,20 @@ "node": ">= 0.8" } }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2463,6 +2713,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2518,6 +2777,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/git-hooks-list": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-4.1.1.tgz", @@ -2590,6 +2862,27 @@ "dev": true, "license": "MIT" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -2682,6 +2975,22 @@ "node": ">= 0.4" } }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2947,6 +3256,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", @@ -3209,31 +3527,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3280,22 +3573,6 @@ "json5": "lib/cli.js" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3306,26 +3583,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/language-subtag-registry": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", - "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/language-tags": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", - "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", - "dev": true, - "license": "MIT", - "dependencies": { - "language-subtag-registry": "^0.3.20" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3363,19 +3620,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3471,7 +3715,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3509,6 +3752,12 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3561,22 +3810,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/object.fromentries": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", @@ -3799,6 +4032,36 @@ "node": ">=16.20.0" } }, + "node_modules/playwright": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.56.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -3867,97 +4130,6 @@ "prettier": "^3.0.0" } }, - "node_modules/prettier-plugin-tailwindcss": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.13.tgz", - "integrity": "sha512-uQ0asli1+ic8xrrSmIOaElDu0FacR4x69GynTh2oZjFY10JUt6EEumTQl5tB4fMeD6I1naKd+4rXQQ7esT2i1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.21.3" - }, - "peerDependencies": { - "@ianvs/prettier-plugin-sort-imports": "*", - "@prettier/plugin-pug": "*", - "@shopify/prettier-plugin-liquid": "*", - "@trivago/prettier-plugin-sort-imports": "*", - "@zackad/prettier-plugin-twig": "*", - "prettier": "^3.0", - "prettier-plugin-astro": "*", - "prettier-plugin-css-order": "*", - "prettier-plugin-import-sort": "*", - "prettier-plugin-jsdoc": "*", - "prettier-plugin-marko": "*", - "prettier-plugin-multiline-arrays": "*", - "prettier-plugin-organize-attributes": "*", - "prettier-plugin-organize-imports": "*", - "prettier-plugin-sort-imports": "*", - "prettier-plugin-style-order": "*", - "prettier-plugin-svelte": "*" - }, - "peerDependenciesMeta": { - "@ianvs/prettier-plugin-sort-imports": { - "optional": true - }, - "@prettier/plugin-pug": { - "optional": true - }, - "@shopify/prettier-plugin-liquid": { - "optional": true - }, - "@trivago/prettier-plugin-sort-imports": { - "optional": true - }, - "@zackad/prettier-plugin-twig": { - "optional": true - }, - "prettier-plugin-astro": { - "optional": true - }, - "prettier-plugin-css-order": { - "optional": true - }, - "prettier-plugin-import-sort": { - "optional": true - }, - "prettier-plugin-jsdoc": { - "optional": true - }, - "prettier-plugin-marko": { - "optional": true - }, - "prettier-plugin-multiline-arrays": { - "optional": true - }, - "prettier-plugin-organize-attributes": { - "optional": true - }, - "prettier-plugin-organize-imports": { - "optional": true - }, - "prettier-plugin-sort-imports": { - "optional": true - }, - "prettier-plugin-style-order": { - "optional": true - }, - "prettier-plugin-svelte": { - "optional": true - } - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4056,13 +4228,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "license": "MIT" - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -4107,6 +4272,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -4138,6 +4312,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -4494,6 +4678,15 @@ "sort-package-json": "cli.js" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -4517,59 +4710,25 @@ "node": ">= 0.4" } }, - "node_modules/string.prototype.includes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", - "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", - "dev": true, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/string.prototype.repeat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/string.prototype.trim": { "version": "1.2.10", @@ -4630,13 +4789,25 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", - "engines": { + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { "node": ">=4" } }, @@ -4791,6 +4962,41 @@ "strip-bom": "^3.0.0" } }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4897,12 +5103,11 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4934,6 +5139,419 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", + "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/type-utils": "8.35.0", + "@typescript-eslint/utils": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.35.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz", + "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/utils": "8.35.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", + "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.35.0", + "@typescript-eslint/tsconfig-utils": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", + "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.35.0", + "@typescript-eslint/types": "^8.35.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", + "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz", + "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", + "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.35.0", + "@typescript-eslint/tsconfig-utils": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", + "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.35.0", + "@typescript-eslint/types": "^8.35.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", + "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", + "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", + "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", + "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", + "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.35.0", + "@typescript-eslint/tsconfig-utils": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", + "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.35.0", + "@typescript-eslint/types": "^8.35.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", + "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", + "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.35.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/typescript-eslint/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/typescript-eslint/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -4953,6 +5571,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -5094,12 +5719,71 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 3a24a7ec..10668416 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,12 @@ "attachments:create-organized": "mkdir -p 05_Attachments/Organized", "transcript:extract": ".scripts/transcript-extract.sh", "vault:stats": ".scripts/vault-stats.sh", - "check-updates": "REMOTE=$(curl -s https://raw.githubusercontent.com/heyitsnoah/claudesidian/main/package.json | grep version | head -1 | sed 's/.*: \"\\(.*\\)\".*/\\1/') && LOCAL=$(grep version package.json | head -1 | sed 's/.*: \"\\(.*\\)\".*/\\1/') && if [ \"$LOCAL\" != \"$REMOTE\" ]; then echo -e \"πŸ“¦ Update available! Latest: $REMOTE (you have: $LOCAL)\\n\\n⬇\\n/upgrade\\n⬆\\n\\n## What will this do\\n\\nβœ… Update to the latest version of Claudesidian\\nβœ… Get new features and improvements\\nβœ… Preserve your vault content and settings\\n\\n\"; fi" + "check-updates": "REMOTE=$(curl -s https://raw.githubusercontent.com/heyitsnoah/claudesidian/main/package.json | grep version | head -1 | sed 's/.*: \"\\(.*\\)\".*/\\1/') && LOCAL=$(grep version package.json | head -1 | sed 's/.*: \"\\(.*\\)\".*/\\1/') && if [ \"$LOCAL\" != \"$REMOTE\" ]; then echo -e \"πŸ“¦ Update available! Latest: $REMOTE (you have: $LOCAL)\\n\\n⬇\\n/upgrade\\n⬆\\n\\n## What will this do\\n\\nβœ… Update to the latest version of Claudesidian\\nβœ… Get new features and improvements\\nβœ… Preserve your vault content and settings\\n\\n\"; fi", + "kindle:auth": "tsx .scripts/kindle/auth.ts", + "kindle:sync": "tsx .scripts/kindle/sync.ts", + "kindle:sync-all": "tsx .scripts/kindle/sync.ts --all", + "kindle:config": "tsx .scripts/kindle/config.ts --interactive", + "playwright:install": "pnpm exec playwright install chromium" }, "keywords": [ "obsidian", @@ -37,9 +42,16 @@ "dependencies": { "@google/generative-ai": "^0.21.0", "@modelcontextprotocol/sdk": "^1.25.2" + "handlebars": "^4.7.8", + "html-entities": "^2.6.0", + "playwright": "^1.48.0", + "yargs": "^17.7.2", + "zod": "^3.23.8" }, "devDependencies": { "@eslint/compat": "^1.4.1", + "@types/node": "^24.9.1", + "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.53.0", "@typescript-eslint/parser": "^8.53.0", "eslint": "^9.39.2", @@ -50,6 +62,8 @@ "prettier": "^3.7.4", "prettier-plugin-packagejson": "^2.5.21", "prettier-plugin-sort-json": "^4.2.0", + "tsx": "^4.20.6", + "typescript": "^5.9.3", "typescript-eslint": "^8.53.0" }, "packageManager": "pnpm@9.15.4" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..53e972ed --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "exactOptionalPropertyTypes": true, + "incremental": true, + "isolatedModules": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "noImplicitReturns": true, // preferred over https://typescript-eslint.io/rules/consistent-return/ + "noUnusedLocals": false, // prefer using https://typescript-eslint.io/rules/no-unused-vars/ + "noUnusedParameters": false, // prefer using https://typescript-eslint.io/rules/no-unused-vars/ + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2020", + "verbatimModuleSyntax": false // prefer using https://typescript-eslint.io/rules/consistent-type-imports + }, + "exclude": ["node_modules", "dist", ".user-config", ".kindle"], + "include": [".scripts/**/*", ".claude/**/*"] +}