UNPKG

@ankimcp/anki-mcp-server

Version:

Model Context Protocol server for Anki - enables AI assistants to interact with your Anki flashcards

107 lines • 5.09 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DUE_TREE_INVARIANT_NOTE = exports.cardStateCountsSchema = void 0; exports.cardStatesSchema = cardStatesSchema; exports.emptyCardStateCounts = emptyCardStateCounts; exports.deckScopeQuery = deckScopeQuery; exports.fetchCardStateCounts = fetchCardStateCounts; exports.dueTreeCountsShape = dueTreeCountsShape; exports.dueTreeCountsSchema = dueTreeCountsSchema; const zod_1 = require("zod"); exports.cardStateCountsSchema = zod_1.z.object({ new: zod_1.z .number() .describe("Cards never studied, excluding suspended and buried. True count: " + "not filtered by due date and not capped by the daily new-card limit."), learning: zod_1.z .number() .describe("Cards in learning or relearning (lapsed cards are counted here), " + "excluding suspended and buried."), review: zod_1.z .number() .describe("Cards in the review state that are not relearning, excluding " + "suspended and buried. Young and mature, due or not due."), suspended: zod_1.z .number() .describe("Suspended cards, whatever their underlying state."), buried: zod_1.z .number() .describe("Buried cards (manually or automatically), whatever their underlying state."), }); function cardStatesSchema(scope) { return exports.cardStateCountsSchema.describe(`True card-state counts for ${scope}, computed from Anki searches ` + "(is:new / is:learn / is:review / is:suspended / is:buried). " + "Unaffected by due dates and daily limits — use these to answer " + '"how many cards are in state X", and `counts` to answer ' + '"what will I study today". The five values are mutually exclusive ' + "and together cover every card in scope."); } const CARD_STATE_QUERIES = { new: "is:new -is:suspended -is:buried", learning: "is:learn -is:suspended -is:buried", review: "is:review -is:learn -is:suspended -is:buried", suspended: "is:suspended", buried: "is:buried", }; function emptyCardStateCounts() { return { new: 0, learning: 0, review: 0, suspended: 0, buried: 0 }; } const ANKI_SEARCH_SPECIALS = /[\\"*_]/g; function deckScopeQuery(deckName) { return `"deck:${deckName.replace(ANKI_SEARCH_SPECIALS, (char) => `\\${char}`)}"`; } async function fetchCardStateCounts(client, scope) { const prefix = scope ? `${scope} ` : ""; const counts = emptyCardStateCounts(); const entries = Object.entries(CARD_STATE_QUERIES); for (const [state, filter] of entries) { const cardIds = await client.invoke("findCards", { query: `${prefix}${filter}`, }); if (!Array.isArray(cardIds)) { throw new Error(`Invalid findCards response for state "${state}": expected array`); } counts[state] = cardIds.length; } return counts; } exports.DUE_TREE_INVARIANT_NOTE = "Normally total === new + learning + review + other; `other` is clamped at 0, " + "so in rare filtered-deck / inherited-limit cases the sum can exceed total."; function dueTreeCountsShape(totalDescription) { return { total: zod_1.z.number().describe(totalDescription), new: zod_1.z .number() .describe("New cards queued for study TODAY, capped by each deck's daily " + "new-card limit. This is not the total number of new cards — " + "see states.new."), learning: zod_1.z .number() .describe("Learning/relearning cards due now or today, the interday part " + "capped by the daily review limit."), review: zod_1.z .number() .describe("Review cards DUE TODAY, capped by each deck's daily review limit. " + "Not 'mature' cards and not all review cards — cards scheduled " + "for a future day are excluded. See states.review."), other: zod_1.z .number() .describe("Arithmetic remainder: total - new - learning - review. NOT a card " + "state. Dominated by review cards not due today plus new cards " + "beyond the daily new limit; suspended and buried cards also land " + "here. It can also reflect the mismatch between total (counted by " + "storage deck) and the scheduler buckets when a filtered deck has " + "borrowed cards from this deck. Use `states` for real per-state counts."), }; } function dueTreeCountsSchema(options) { return zod_1.z .object(dueTreeCountsShape(options.total)) .describe(`Today's study queue for ${options.scope}, exactly as shown in Anki's ` + "deck browser: due-today counts capped by the daily new/review limits " + "(parent limits included), always excluding suspended and buried cards. " + "These are NOT card totals — use `states`. " + (options.note ? `${options.note} ` : "") + exports.DUE_TREE_INVARIANT_NOTE); } //# sourceMappingURL=card-states.utils.js.map