@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
JavaScript
;
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