code icon Code

Load Due Cards

Load decks and calculate which cards are due for review today

Source Code

import { readFileSync, writeFileSync, existsSync } from "fs";

const [decksPath, dueCardsPath, deckFilter] = process.argv.slice(2);

try {
  // Check if decks file exists
  if (!existsSync(decksPath)) {
    console.log(
      JSON.stringify({
        success: true,
        totalDue: 0,
        decks: [],
        message: "No flashcard decks found. Create some flashcards first!",
      })
    );
    writeFileSync(
      dueCardsPath,
      JSON.stringify({ summary: { totalDue: 0, decks: [] }, cards: [] }, null, 2)
    );
    process.exit(0);
  }

  const decksData = JSON.parse(readFileSync(decksPath, "utf-8"));
  const today = new Date().toISOString().split("T")[0];

  const dueCards = [];
  const deckSummaries = [];

  for (const [slug, deck] of Object.entries(decksData.decks)) {
    // Skip if filtering and this isn't the target deck
    if (deckFilter && deckFilter !== slug) continue;

    const deckDueCards = deck.cards.filter((card) => {
      // Card is due if nextReview is today or earlier
      return card.nextReview <= today;
    });

    if (deckDueCards.length > 0) {
      deckSummaries.push({
        slug: slug,
        name: deck.name,
        dueCount: deckDueCards.length,
        totalCards: deck.cards.length,
      });

      // Add cards with deck context
      for (const card of deckDueCards) {
        dueCards.push({
          id: card.id,
          deckSlug: slug,
          deckName: deck.name,
          front: card.front,
          back: card.back,
          interval: card.interval,
          correctCount: card.correctCount,
          incorrectCount: card.incorrectCount,
        });
      }
    }
  }

  // Write due cards to session file
  const dueCardsOutput = {
    summary: {
      totalDue: dueCards.length,
      decks: deckSummaries,
      generatedAt: new Date().toISOString(),
    },
    cards: dueCards,
  };

  writeFileSync(dueCardsPath, JSON.stringify(dueCardsOutput, null, 2));

  // Log summary for agent
  if (dueCards.length === 0) {
    // Find next due card
    let nextDue = null;
    let nextDueDeck = null;

    for (const [slug, deck] of Object.entries(decksData.decks)) {
      for (const card of deck.cards) {
        if (!nextDue || card.nextReview < nextDue) {
          nextDue = card.nextReview;
          nextDueDeck = deck.name;
        }
      }
    }

    console.log(
      JSON.stringify({
        success: true,
        totalDue: 0,
        decks: [],
        message: nextDue
          ? `No cards due for review today. Next card due on ${nextDue} from "${nextDueDeck}".`
          : "No cards due for review.",
      })
    );
  } else {
    const deckList = deckSummaries
      .map((d) => `${d.dueCount} from "${d.name}"`)
      .join(", ");

    console.log(
      JSON.stringify({
        success: true,
        totalDue: dueCards.length,
        decks: deckSummaries,
        dueCardsPath: dueCardsPath,
        message: `${dueCards.length} cards due for review: ${deckList}`,
      })
    );
  }
} catch (error) {
  console.log(
    JSON.stringify({
      success: false,
      error: `Failed to load decks: ${error.message}`,
    })
  );
  process.exit(1);
}