earnrate 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -12
- package/data/CHANGELOG.md +291 -0
- package/data/VERSION +1 -1
- package/data/cards/amex-blue-business-plus.yaml +1 -0
- package/data/cards/amex-blue-cash-everyday.yaml +29 -2
- package/data/cards/amex-blue-cash-preferred.yaml +26 -0
- package/data/cards/amex-business-gold.yaml +169 -0
- package/data/cards/amex-business-platinum.yaml +184 -0
- package/data/cards/amex-delta-skymiles-blue.yaml +91 -0
- package/data/cards/amex-delta-skymiles-gold.yaml +29 -1
- package/data/cards/amex-delta-skymiles-platinum.yaml +35 -1
- package/data/cards/amex-delta-skymiles-reserve.yaml +35 -1
- package/data/cards/amex-gold.yaml +53 -0
- package/data/cards/amex-hilton-honors-aspire.yaml +25 -1
- package/data/cards/amex-hilton-honors-business.yaml +106 -0
- package/data/cards/amex-hilton-honors-surpass.yaml +16 -1
- package/data/cards/amex-hilton-honors.yaml +2 -0
- package/data/cards/amex-marriott-bonvoy-bevy.yaml +2 -0
- package/data/cards/amex-marriott-bonvoy-brilliant.yaml +17 -1
- package/data/cards/amex-marriott-bonvoy-business.yaml +116 -0
- package/data/cards/amex-platinum.yaml +111 -0
- package/data/cards/apple-card.yaml +76 -0
- package/data/cards/bank-of-america-atmos-rewards-ascent.yaml +1 -0
- package/data/cards/bank-of-america-atmos-rewards-business.yaml +2 -0
- package/data/cards/bank-of-america-atmos-rewards-summit.yaml +1 -0
- package/data/cards/bank-of-america-customized-cash-rewards.yaml +34 -1
- package/data/cards/bank-of-america-premium-rewards.yaml +134 -0
- package/data/cards/bank-of-america-travel-rewards.yaml +123 -0
- package/data/cards/bank-of-america-unlimited-cash-rewards.yaml +83 -0
- package/data/cards/barclays-jetblue-plus.yaml +86 -0
- package/data/cards/bilt-blue.yaml +60 -0
- package/data/cards/bilt-obsidian.yaml +13 -2
- package/data/cards/bilt-palladium.yaml +86 -0
- package/data/cards/caltech-efcu-platinum-mastercard.yaml +50 -0
- package/data/cards/capital-one-kohls-card.yaml +1 -0
- package/data/cards/capital-one-kohls-rewards-visa.yaml +1 -0
- package/data/cards/capital-one-quicksilver.yaml +2 -0
- package/data/cards/capital-one-rei-co-op-mastercard.yaml +62 -0
- package/data/cards/capital-one-savor.yaml +2 -0
- package/data/cards/capital-one-spark-cash-plus.yaml +80 -0
- package/data/cards/capital-one-venture-x-business.yaml +124 -0
- package/data/cards/capital-one-venture-x.yaml +32 -2
- package/data/cards/capital-one-venture.yaml +2 -0
- package/data/cards/chase-aeroplan.yaml +16 -0
- package/data/cards/chase-freedom-flex.yaml +1 -0
- package/data/cards/chase-freedom-rise.yaml +29 -0
- package/data/cards/chase-freedom-unlimited.yaml +1 -0
- package/data/cards/chase-ihg-one-rewards-business.yaml +28 -1
- package/data/cards/chase-ihg-one-rewards-premier-select.yaml +18 -0
- package/data/cards/chase-ihg-one-rewards-premier.yaml +16 -0
- package/data/cards/chase-ihg-one-rewards.yaml +1 -0
- package/data/cards/chase-ink-business-cash.yaml +14 -0
- package/data/cards/chase-ink-business-preferred.yaml +1 -0
- package/data/cards/chase-ink-business-unlimited.yaml +80 -0
- package/data/cards/chase-marriott-bonvoy-bold.yaml +1 -0
- package/data/cards/chase-marriott-bonvoy-boundless.yaml +2 -0
- package/data/cards/chase-prime-visa.yaml +1 -0
- package/data/cards/chase-sapphire-preferred.yaml +12 -0
- package/data/cards/chase-sapphire-reserve.yaml +45 -4
- package/data/cards/chase-southwest-performance-business.yaml +2 -0
- package/data/cards/chase-southwest-plus.yaml +2 -0
- package/data/cards/chase-southwest-premier-business.yaml +2 -0
- package/data/cards/chase-southwest-premier.yaml +2 -0
- package/data/cards/chase-southwest-priority.yaml +2 -0
- package/data/cards/chase-united-business.yaml +33 -0
- package/data/cards/chase-united-club-business.yaml +41 -0
- package/data/cards/chase-united-club.yaml +41 -0
- package/data/cards/chase-united-explorer.yaml +33 -0
- package/data/cards/chase-united-gateway.yaml +1 -0
- package/data/cards/chase-united-quest.yaml +41 -0
- package/data/cards/citi-aadvantage-business.yaml +2 -0
- package/data/cards/citi-aadvantage-executive.yaml +28 -1
- package/data/cards/citi-aadvantage-globe.yaml +2 -0
- package/data/cards/citi-aadvantage-mileup.yaml +2 -0
- package/data/cards/citi-aadvantage-platinum-select.yaml +2 -0
- package/data/cards/citi-costco-anywhere-visa-business.yaml +2 -0
- package/data/cards/citi-costco-anywhere-visa.yaml +2 -0
- package/data/cards/citi-custom-cash.yaml +1 -0
- package/data/cards/citi-double-cash.yaml +1 -0
- package/data/cards/citi-my-best-buy-visa.yaml +1 -0
- package/data/cards/citi-strata-elite.yaml +21 -1
- package/data/cards/citi-strata-premier.yaml +1 -0
- package/data/cards/citi-strata.yaml +164 -0
- package/data/cards/discover-it-cash-back.yaml +22 -3
- package/data/cards/fidelity-rewards-visa-signature.yaml +60 -0
- package/data/cards/navy-federal-cash-rewards-plus.yaml +61 -0
- package/data/cards/navy-federal-cash-rewards.yaml +60 -0
- package/data/cards/navy-federal-more-rewards-amex.yaml +92 -0
- package/data/cards/paypal-cashback-mastercard.yaml +52 -0
- package/data/cards/penfed-power-cash-rewards.yaml +47 -0
- package/data/cards/sams-club-mastercard.yaml +68 -0
- package/data/cards/sofi-unlimited-2.yaml +1 -0
- package/data/cards/td-bank-nordstrom-credit-card.yaml +1 -0
- package/data/cards/td-bank-nordstrom-visa.yaml +1 -0
- package/data/cards/td-bank-target-circle-card.yaml +1 -0
- package/data/cards/usbank-altitude-connect.yaml +156 -0
- package/data/cards/usbank-altitude-go.yaml +124 -0
- package/data/cards/usbank-cash-plus.yaml +1 -0
- package/data/cards/usbank-smartly.yaml +107 -0
- package/data/cards/venmo-credit-card.yaml +151 -0
- package/data/cards/verizon-visa-card.yaml +122 -0
- package/data/cards/wells-fargo-active-cash.yaml +1 -0
- package/data/cards/wells-fargo-autograph-journey.yaml +73 -0
- package/data/cards/wells-fargo-autograph.yaml +1 -0
- package/data/categories.yaml +3 -1
- package/data/currencies.yaml +48 -0
- package/data/issuers.yaml +44 -1
- package/data/memberships.yaml +45 -0
- package/data/merchants/overrides.yaml +947 -1
- package/data/merchants/seed.yaml +1916 -62
- package/dist/cli/args.js +18 -0
- package/dist/cli/explore.js +10 -1
- package/dist/cli/import.js +2 -2
- package/dist/cli/run.js +4 -1
- package/dist/cli/wallet.js +37 -3
- package/dist/data/file-store.js +5 -2
- package/dist/data/integrity.js +59 -0
- package/dist/data/store.js +4 -1
- package/dist/data/wallet-edit.js +28 -7
- package/dist/engine/cheat-sheet.js +17 -5
- package/dist/engine/checklist.js +38 -6
- package/dist/engine/credits.js +65 -0
- package/dist/engine/evaluate.js +57 -18
- package/dist/engine/explore.js +70 -7
- package/dist/engine/intro.js +36 -0
- package/dist/engine/offers.js +2 -1
- package/dist/engine/periods.js +10 -3
- package/dist/engine/recommend.js +101 -20
- package/dist/engine/record.js +43 -1
- package/dist/engine/rotation.js +3 -0
- package/dist/engine/rule-text.js +28 -3
- package/dist/engine/setup.js +20 -3
- package/dist/engine/simulate.js +10 -1
- package/dist/engine/text.js +107 -0
- package/dist/engine/wallet-change.js +57 -4
- package/dist/engine/wallet-summary.js +54 -6
- package/dist/format/card-art.js +21 -0
- package/dist/format/card-text.js +4 -3
- package/dist/format/cards.js +1 -0
- package/dist/format/cheat-sheet.js +2 -0
- package/dist/format/explore.js +20 -4
- package/dist/format/recommendation.js +6 -2
- package/dist/format/wallet-yaml.js +1 -0
- package/dist/format/wallet.js +15 -5
- package/dist/import/analyze.js +8 -2
- package/dist/mcp/inputs.js +13 -0
- package/dist/mcp/prompts.js +9 -1
- package/dist/mcp/schemas.js +87 -1
- package/dist/mcp/tools/account.js +3 -1
- package/dist/mcp/tools/answers.js +9 -4
- package/dist/mcp/tools/wallet.js +20 -8
- package/dist/merchants/resolve.js +8 -0
- package/dist/schema/card.js +60 -3
- package/dist/schema/common.js +1 -1
- package/dist/schema/issuer.js +4 -0
- package/dist/schema/merchant.js +3 -0
- package/dist/schema/wallet.js +11 -0
- package/package.json +12 -3
package/dist/mcp/schemas.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { CapPeriod, MilestoneSchema } from "../schema/card.js";
|
|
2
|
+
import { CapPeriod, CreditPeriod, MilestoneSchema } from "../schema/card.js";
|
|
3
3
|
const MilestonePeriod = MilestoneSchema.shape.period;
|
|
4
4
|
import { CardNetwork, Network } from "../schema/common.js";
|
|
5
5
|
// Mirrors the engine's result types. The SDK validates every structured
|
|
@@ -43,6 +43,18 @@ const RankedCardSchema = z.object({
|
|
|
43
43
|
milestoneBonus: z
|
|
44
44
|
.object({ points: z.number(), dollars: z.number() })
|
|
45
45
|
.nullable(),
|
|
46
|
+
credit: z
|
|
47
|
+
.object({
|
|
48
|
+
id: z.string(),
|
|
49
|
+
name: z.string(),
|
|
50
|
+
amount: z.number(),
|
|
51
|
+
period: CreditPeriod,
|
|
52
|
+
enrolled: z.boolean(),
|
|
53
|
+
left: z.number().nullable(),
|
|
54
|
+
until: z.string().nullable(),
|
|
55
|
+
dollars: z.number().nullable(),
|
|
56
|
+
})
|
|
57
|
+
.nullable(),
|
|
46
58
|
capped: z.boolean(),
|
|
47
59
|
reasoning: z.array(z.string()),
|
|
48
60
|
warnings: z.array(z.string()),
|
|
@@ -100,8 +112,12 @@ export const WalletSummarySchema = z.object({
|
|
|
100
112
|
'bonus',
|
|
101
113
|
'milestone',
|
|
102
114
|
'rotation',
|
|
115
|
+
'intro',
|
|
116
|
+
'credit',
|
|
117
|
+
'enroll',
|
|
103
118
|
]),
|
|
104
119
|
cardId: z.string(),
|
|
120
|
+
ref: z.string().optional(),
|
|
105
121
|
text: z.string(),
|
|
106
122
|
due: z.string().nullable(),
|
|
107
123
|
}))
|
|
@@ -156,6 +172,20 @@ export const WalletSummarySchema = z.object({
|
|
|
156
172
|
rewards: z.array(z.string()),
|
|
157
173
|
text: z.string(),
|
|
158
174
|
})),
|
|
175
|
+
credits: z.array(z.object({
|
|
176
|
+
id: z.string(),
|
|
177
|
+
name: z.string(),
|
|
178
|
+
amount: z.number(),
|
|
179
|
+
period: CreditPeriod,
|
|
180
|
+
enroll: z.boolean(),
|
|
181
|
+
enrolled: z.boolean(),
|
|
182
|
+
currentPeriod: z.string().nullable(),
|
|
183
|
+
until: z.string().nullable(),
|
|
184
|
+
used: z.number(),
|
|
185
|
+
left: z.number().nullable(),
|
|
186
|
+
tracked: z.boolean(),
|
|
187
|
+
text: z.string(),
|
|
188
|
+
})),
|
|
159
189
|
chosen: z
|
|
160
190
|
.object({
|
|
161
191
|
period: z.string(),
|
|
@@ -169,6 +199,14 @@ export const WalletSummarySchema = z.object({
|
|
|
169
199
|
activated: z.array(z.string()),
|
|
170
200
|
toActivate: z.array(z.string()),
|
|
171
201
|
anniversary: z.string().nullable(),
|
|
202
|
+
opened: z.string().nullable(),
|
|
203
|
+
intro: z
|
|
204
|
+
.object({
|
|
205
|
+
state: z.enum(['active', 'ended', 'no-date']),
|
|
206
|
+
until: z.string().nullable(),
|
|
207
|
+
days: z.number(),
|
|
208
|
+
})
|
|
209
|
+
.nullable(),
|
|
172
210
|
})),
|
|
173
211
|
});
|
|
174
212
|
export const RecordResultSchema = z.object({
|
|
@@ -179,6 +217,12 @@ export const RecordResultSchema = z.object({
|
|
|
179
217
|
capKey: z.string(),
|
|
180
218
|
ruleId: z.string(),
|
|
181
219
|
period: z.string(),
|
|
220
|
+
periodKind: z.enum([
|
|
221
|
+
'month',
|
|
222
|
+
'quarter',
|
|
223
|
+
'calendar_year',
|
|
224
|
+
'cardmember_year',
|
|
225
|
+
]),
|
|
182
226
|
capAmount: z.number(),
|
|
183
227
|
before: z.number(),
|
|
184
228
|
after: z.number(),
|
|
@@ -197,10 +241,23 @@ export const RecordResultSchema = z.object({
|
|
|
197
241
|
cardId: z.string(),
|
|
198
242
|
milestoneId: z.string(),
|
|
199
243
|
period: z.string(),
|
|
244
|
+
periodKind: z.enum(['calendar_year', 'cardmember_year']),
|
|
200
245
|
spend: z.number(),
|
|
201
246
|
before: z.number(),
|
|
202
247
|
after: z.number(),
|
|
203
248
|
})),
|
|
249
|
+
credit: z
|
|
250
|
+
.object({
|
|
251
|
+
cardId: z.string(),
|
|
252
|
+
creditId: z.string(),
|
|
253
|
+
name: z.string(),
|
|
254
|
+
period: z.string(),
|
|
255
|
+
periodKind: CreditPeriod,
|
|
256
|
+
amount: z.number(),
|
|
257
|
+
before: z.number(),
|
|
258
|
+
after: z.number(),
|
|
259
|
+
})
|
|
260
|
+
.nullable(),
|
|
204
261
|
message: z.string(),
|
|
205
262
|
});
|
|
206
263
|
export const OfferComparisonSchema = z.object({
|
|
@@ -270,6 +327,15 @@ export const CardSearchSchema = z.object({
|
|
|
270
327
|
rewards: z.array(z.string()),
|
|
271
328
|
text: z.string(),
|
|
272
329
|
})),
|
|
330
|
+
introRates: z.array(z.object({ id: z.string(), days: z.number(), text: z.string() })),
|
|
331
|
+
credits: z.array(z.object({
|
|
332
|
+
id: z.string(),
|
|
333
|
+
name: z.string(),
|
|
334
|
+
amount: z.number(),
|
|
335
|
+
period: CreditPeriod,
|
|
336
|
+
enroll: z.boolean(),
|
|
337
|
+
text: z.string(),
|
|
338
|
+
})),
|
|
273
339
|
})),
|
|
274
340
|
});
|
|
275
341
|
export const WalletChangeSchema = z.object({
|
|
@@ -298,6 +364,25 @@ const ExcludedCardSchema = z.object({
|
|
|
298
364
|
const Picks = z.record(z.string(), z.array(z.string()));
|
|
299
365
|
export const ExplorationSchema = z.object({
|
|
300
366
|
merchant: MerchantResolutionSchema,
|
|
367
|
+
// Cards with a statement credit at this store, shown before the ranking.
|
|
368
|
+
credits: z.array(z.object({
|
|
369
|
+
cardId: z.string(),
|
|
370
|
+
cardName: z.string(),
|
|
371
|
+
issuer: z.string(),
|
|
372
|
+
network: CardNetwork,
|
|
373
|
+
annualFee: z.number().nullable(),
|
|
374
|
+
firstYearFee: z.number().nullable(),
|
|
375
|
+
creditId: z.string(),
|
|
376
|
+
name: z.string(),
|
|
377
|
+
amount: z.number(),
|
|
378
|
+
period: CreditPeriod,
|
|
379
|
+
enroll: z.boolean(),
|
|
380
|
+
note: z.string().nullable(),
|
|
381
|
+
back: z.string(),
|
|
382
|
+
earn: z.string(),
|
|
383
|
+
text: z.string(),
|
|
384
|
+
evidence: z.object({ quote: z.string(), url: z.string() }).nullable(),
|
|
385
|
+
})),
|
|
301
386
|
ranked: z.array(RankedCardSchema.extend({
|
|
302
387
|
issuer: z.string(),
|
|
303
388
|
network: CardNetwork,
|
|
@@ -386,6 +471,7 @@ export const CheatSheetSchema = z.object({
|
|
|
386
471
|
note: z.string().nullable(),
|
|
387
472
|
})),
|
|
388
473
|
notes: z.array(z.string()),
|
|
474
|
+
assumptions: z.array(z.string()),
|
|
389
475
|
});
|
|
390
476
|
export const PurchaseAnswerSchema = RecommendationSchema.extend({
|
|
391
477
|
// Every card earnrate knows, ranked: set when no cards were given.
|
|
@@ -136,7 +136,9 @@ export function registerAccountTools(server, catalog, store, ctx) {
|
|
|
136
136
|
annotations: {
|
|
137
137
|
title: 'Switch wallets',
|
|
138
138
|
readOnlyHint: false,
|
|
139
|
-
|
|
139
|
+
// It signs out this app's other connections to the wallet, which only
|
|
140
|
+
// the recovery code undoes.
|
|
141
|
+
destructiveHint: true,
|
|
140
142
|
idempotentHint: true,
|
|
141
143
|
openWorldHint: false,
|
|
142
144
|
},
|
|
@@ -15,7 +15,7 @@ import { formatOffers } from "../../format/offers.js";
|
|
|
15
15
|
import { formatRecommendation } from "../../format/recommendation.js";
|
|
16
16
|
import { resolvePlace, withCallerCategory } from "../../merchants/resolve.js";
|
|
17
17
|
import { ALL_CARDS_NOTE, fail } from "../context.js";
|
|
18
|
-
import { ChannelInput, CountryInput, DateInput, Merchant, settings, } from "../inputs.js";
|
|
18
|
+
import { ChannelInput, CountryInput, PaymentInput, DateInput, Merchant, settings, } from "../inputs.js";
|
|
19
19
|
import { CardSearchSchema, CheatSheetSchema, ExplorationSchema, MerchantResolutionSchema, OfferComparisonSchema, PurchaseAnswerSchema, SpendingSimulationSchema, } from "../schemas.js";
|
|
20
20
|
const SOURCES = {
|
|
21
21
|
override: "from earnrate's merchant list",
|
|
@@ -34,7 +34,7 @@ export function registerAnswerTools(server, deps, ctx, inputs) {
|
|
|
34
34
|
const { CategoryInput, CatalogCategory, NamedCards, NamedMemberships, OfferFilters, CatalogFilters, } = inputs;
|
|
35
35
|
server.registerTool('best_card_for_purchase', {
|
|
36
36
|
title: 'Best card for a purchase',
|
|
37
|
-
description: "Which credit card earns the most rewards for a purchase, and why. With no cards given and no saved wallet, it ranks every card earnrate knows (in `allCards`): a complete answer for anyone. With cards named in the conversation (`cards`) or a saved wallet, it picks the best of the user's own: `decision.cardId` is the pick and `decision.checkoutLabel` is how the card appears at checkout, with every usable card ranked with reasons, cards the merchant won't take, assumptions, warnings, and when a different point valuation would change the pick. For a business earnrate may not know, like a local restaurant, pass `category` when you can tell what kind of business it is. When the user is paying outside the U.S. or in another currency, pass `country`: abroad, each card is ranked after its foreign transaction fee. Never needs or returns card numbers.",
|
|
37
|
+
description: "Which credit card earns the most rewards for a purchase, and why. With no cards given and no saved wallet, it ranks every card earnrate knows (in `allCards`): a complete answer for anyone. With cards named in the conversation (`cards`) or a saved wallet, it picks the best of the user's own: `decision.cardId` is the pick and `decision.checkoutLabel` is how the card appears at checkout, with every usable card ranked with reasons, cards the merchant won't take, assumptions, warnings, and when a different point valuation would change the pick. For a business earnrate may not know, like a local restaurant, pass `category` when you can tell what kind of business it is. When the user is paying outside the U.S. or in another currency, pass `country`: abroad, each card is ranked after its foreign transaction fee. Pass `payment` only when the user says how they're paying, like Apple Pay or PayPal checkout. A merchant statement credit (a card's `credit`, like Amex Platinum's lululemon credit) counts toward the pick only once it's enrolled in the wallet and has dollars left this period, and only when the amount is given; say its dollars apart from the % back. With no cards, `allCards.credits` lists cards with a credit at the store; present those first. Never needs or returns card numbers.",
|
|
38
38
|
inputSchema: z.object({
|
|
39
39
|
merchant: Merchant,
|
|
40
40
|
amount: z
|
|
@@ -45,6 +45,7 @@ export function registerAnswerTools(server, deps, ctx, inputs) {
|
|
|
45
45
|
channel: ChannelInput,
|
|
46
46
|
date: DateInput,
|
|
47
47
|
country: CountryInput,
|
|
48
|
+
payment: PaymentInput,
|
|
48
49
|
category: CategoryInput,
|
|
49
50
|
cards: NamedCards,
|
|
50
51
|
memberships: NamedMemberships,
|
|
@@ -57,7 +58,7 @@ export function registerAnswerTools(server, deps, ctx, inputs) {
|
|
|
57
58
|
destructiveHint: false,
|
|
58
59
|
openWorldHint: deps.llm !== null,
|
|
59
60
|
},
|
|
60
|
-
}, async ({ merchant, amount, channel, date, country, category, cards, memberships, valuation, }) => {
|
|
61
|
+
}, async ({ merchant, amount, channel, date, country, payment, category, cards, memberships, valuation, }) => {
|
|
61
62
|
const held = await ctx.cardsFor(cards, memberships);
|
|
62
63
|
if (typeof held === 'string')
|
|
63
64
|
return fail(held);
|
|
@@ -70,6 +71,7 @@ export function registerAnswerTools(server, deps, ctx, inputs) {
|
|
|
70
71
|
channel: channel ?? null,
|
|
71
72
|
date: date ?? deps.today(),
|
|
72
73
|
country: (country ?? 'US').toUpperCase(),
|
|
74
|
+
payment: payment ?? null,
|
|
73
75
|
};
|
|
74
76
|
const resolved = await ctx.resolve(merchant, purchase.channel, category ?? null);
|
|
75
77
|
const retry = resolved.merchant.source === 'none'
|
|
@@ -83,6 +85,7 @@ export function registerAnswerTools(server, deps, ctx, inputs) {
|
|
|
83
85
|
channel: purchase.channel,
|
|
84
86
|
date: purchase.date,
|
|
85
87
|
country: purchase.country,
|
|
88
|
+
payment: purchase.payment,
|
|
86
89
|
}, settings({ memberships, valuation }, await ctx.saved()), resolved.merchant);
|
|
87
90
|
const allCards = { ...explored, ranked: explored.ranked.slice(0, TOP) };
|
|
88
91
|
const empty = recommend(catalog, EMPTY_WALLET, purchase, resolved.merchant);
|
|
@@ -205,7 +208,7 @@ export function registerAnswerTools(server, deps, ctx, inputs) {
|
|
|
205
208
|
});
|
|
206
209
|
server.registerTool('best_cards_for', {
|
|
207
210
|
title: 'Best cards for a purchase',
|
|
208
|
-
description: 'Ranks every card earnrate knows that takes new applicants, not only the user\'s, for one purchase or category, each set up at its best (the right category pick, activated, cap unused), with its annual fee and what earning the rate takes. Use it for questions like "what\'s the best card for groceries?" or "which card should I get for Amazon?". It needs no wallet. For which of the user\'s own cards to use, call best_card_for_purchase instead. Mention the annual fee and any conditions alongside each card.',
|
|
211
|
+
description: 'Ranks every card earnrate knows that takes new applicants, not only the user\'s, for one purchase or category, each set up at its best (the right category pick, activated, cap unused), with its annual fee and what earning the rate takes. Use it for questions like "what\'s the best card for groceries?" or "which card should I get for Amazon?". It needs no wallet. For which of the user\'s own cards to use, call best_card_for_purchase instead. Mention the annual fee and any conditions alongside each card. For a store, cards with a statement credit there come first in `credits` ("$75 back each quarter, then 1x Amex Membership Rewards"): present them first, in dollars and never as a percentage, saying when the cardholder must enroll, then the ranking by rate.',
|
|
209
212
|
inputSchema: z.object({
|
|
210
213
|
merchant: Merchant.optional(),
|
|
211
214
|
category: CatalogCategory.optional(),
|
|
@@ -216,6 +219,7 @@ export function registerAnswerTools(server, deps, ctx, inputs) {
|
|
|
216
219
|
.describe('Purchase amount in US dollars, if known.'),
|
|
217
220
|
channel: ChannelInput,
|
|
218
221
|
country: CountryInput,
|
|
222
|
+
payment: PaymentInput,
|
|
219
223
|
...CatalogFilters,
|
|
220
224
|
}),
|
|
221
225
|
outputSchema: ExplorationSchema,
|
|
@@ -236,6 +240,7 @@ export function registerAnswerTools(server, deps, ctx, inputs) {
|
|
|
236
240
|
channel,
|
|
237
241
|
date: deps.today(),
|
|
238
242
|
country: (args.country ?? 'US').toUpperCase(),
|
|
243
|
+
payment: args.payment ?? null,
|
|
239
244
|
}, settings(args, await ctx.saved()), args.merchant === undefined
|
|
240
245
|
? undefined
|
|
241
246
|
: withCallerCategory(resolvePlace(catalog, args.merchant, channel), args.category ?? null));
|
package/dist/mcp/tools/wallet.js
CHANGED
|
@@ -39,6 +39,10 @@ function changes(catalog, args) {
|
|
|
39
39
|
out.push(`checkout name "${args.checkoutLabel}"`);
|
|
40
40
|
if (args.anniversary !== undefined)
|
|
41
41
|
out.push(`anniversary ${args.anniversary}`);
|
|
42
|
+
if (args.opened === null)
|
|
43
|
+
out.push('opening date cleared');
|
|
44
|
+
else if (args.opened !== undefined)
|
|
45
|
+
out.push(`opened ${args.opened}`);
|
|
42
46
|
if (args.activated !== undefined)
|
|
43
47
|
out.push(args.activated.length === 0
|
|
44
48
|
? 'nothing activated'
|
|
@@ -46,7 +50,13 @@ function changes(catalog, args) {
|
|
|
46
50
|
for (const [key, spent] of Object.entries(args.capSpent ?? {}))
|
|
47
51
|
out.push(`${money(spent)} already spent on ${key} this period`);
|
|
48
52
|
for (const [id, spent] of Object.entries(args.milestoneSpent ?? {}))
|
|
49
|
-
out.push(`${money(spent)} spent toward ${id} this year`);
|
|
53
|
+
out.push(`${money(spent)} spent toward ${id} this ${card?.milestones.find((m) => m.id === id)?.period === 'cardmember_year' ? 'cardmember year' : 'year'}`);
|
|
54
|
+
if (args.enrolled !== undefined)
|
|
55
|
+
out.push(args.enrolled.length === 0
|
|
56
|
+
? 'no credits enrolled'
|
|
57
|
+
: `enrolled in: ${args.enrolled.join(', ')}`);
|
|
58
|
+
for (const [id, used] of Object.entries(args.creditUsed ?? {}))
|
|
59
|
+
out.push(`${money(used)} of the ${id} credit used this period`);
|
|
50
60
|
if (args.bonus === null)
|
|
51
61
|
out.push('welcome bonus no longer tracked');
|
|
52
62
|
else if (args.bonus !== undefined)
|
|
@@ -58,7 +68,7 @@ export function registerWalletTools(server, deps, ctx, inputs) {
|
|
|
58
68
|
const { catalog, store } = deps;
|
|
59
69
|
server.registerTool('list_wallet', {
|
|
60
70
|
title: 'List wallet',
|
|
61
|
-
description: `Starts with a to-do list for the user's cards right now (bonuses to activate and their deadlines, categories to pick or confirm, caps nearly used up, welcome bonuses to finish and the weekly pace, yearly spend bonuses at least 75% of the way); use it when the user asks what to do with their cards this quarter or what they're forgetting. Then lists the user's cards: checkout label, network, rewards currency and its value, spending caps with how much is left this period, chosen bonus categories (and whether they're current), welcome-bonus progress, progress toward yearly spend bonuses, which stores a store card works at, when each card's data was last verified, the point values the user saved, and (for the hosted wallet) when it was started. ${ctx.walletUse}`,
|
|
71
|
+
description: `Starts with a to-do list for the user's cards right now (bonuses to activate and their deadlines, categories to pick or confirm, caps nearly used up, welcome bonuses to finish and the weekly pace, yearly spend bonuses at least 75% of the way, merchant credits to enroll in or use before they reset); use it when the user asks what to do with their cards this quarter or what they're forgetting. Then lists the user's cards: checkout label, network, rewards currency and its value, spending caps with how much is left this period, chosen bonus categories (and whether they're current), welcome-bonus progress, progress toward yearly spend bonuses, merchant statement credits (enrolled or not, and what's left this period), which stores a store card works at, when each card's data was last verified, the point values the user saved, and (for the hosted wallet) when it was started. ${ctx.walletUse}`,
|
|
62
72
|
outputSchema: WalletSummarySchema,
|
|
63
73
|
annotations: {
|
|
64
74
|
title: 'List wallet',
|
|
@@ -92,7 +102,7 @@ export function registerWalletTools(server, deps, ctx, inputs) {
|
|
|
92
102
|
});
|
|
93
103
|
server.registerTool('record_purchase', {
|
|
94
104
|
title: 'Record a purchase',
|
|
95
|
-
description: `Records a purchase the user has already made, so spending caps, welcome-bonus progress
|
|
105
|
+
description: `Records a purchase the user has already made, so spending caps, welcome-bonus progress, yearly spend milestones and merchant credits used stay accurate for future recommendations. It updates the user's saved wallet. Only call it after the purchase has happened, and only once per purchase: calling it twice counts the spend twice. Purchases with no cap, no open welcome bonus, no spend milestone and no enrolled merchant credit are acknowledged and nothing is written. For a merchant earnrate doesn't recognize it asks for category first, so caps aren't missed. ${ctx.walletUse}`,
|
|
96
106
|
inputSchema: z.object({
|
|
97
107
|
cardId: z
|
|
98
108
|
.string()
|
|
@@ -131,21 +141,23 @@ export function registerWalletTools(server, deps, ctx, inputs) {
|
|
|
131
141
|
const result = await store.record(purchase, resolved.merchant);
|
|
132
142
|
if (!result.ok)
|
|
133
143
|
return fail(`Couldn't record it: ${result.error}`);
|
|
134
|
-
const { cap: booking, bonus, milestones } = result;
|
|
135
|
-
const output = booking || bonus || milestones.length > 0
|
|
144
|
+
const { cap: booking, bonus, milestones, credit } = result;
|
|
145
|
+
const output = booking || bonus || milestones.length > 0 || credit
|
|
136
146
|
? {
|
|
137
147
|
recorded: true,
|
|
138
148
|
booking,
|
|
139
149
|
bonus,
|
|
140
150
|
milestones,
|
|
141
|
-
|
|
151
|
+
credit,
|
|
152
|
+
message: `${formatBooking(amount, cardId, booking, bonus, milestones, credit)} Saved in ${ctx.walletName}.`,
|
|
142
153
|
}
|
|
143
154
|
: {
|
|
144
155
|
recorded: false,
|
|
145
156
|
booking: null,
|
|
146
157
|
bonus: null,
|
|
147
158
|
milestones: [],
|
|
148
|
-
|
|
159
|
+
credit: null,
|
|
160
|
+
message: `Nothing to track: ${cardId} has no spending cap, open welcome bonus, spend milestone or enrolled merchant credit for this purchase, so the wallet wasn't changed.`,
|
|
149
161
|
};
|
|
150
162
|
return {
|
|
151
163
|
content: [
|
|
@@ -179,7 +191,7 @@ export function registerWalletTools(server, deps, ctx, inputs) {
|
|
|
179
191
|
});
|
|
180
192
|
server.registerTool('update_card', {
|
|
181
193
|
title: 'Update a card',
|
|
182
|
-
description: `Changes settings on a card already in the wallet: its category picks for this period, checkout label, anniversary, activated categories, cap spending so far (capSpent), spending this year toward its spend milestones (milestoneSpent) or a welcome bonus to track. Picks replace the saved ones in each choice group you set (other groups are kept) for the current period. Use it when the user picks new categories, such as each quarter on U.S. Bank Cash+. ${ctx.walletUse}`,
|
|
194
|
+
description: `Changes settings on a card already in the wallet: its category picks for this period, checkout label, anniversary, opening date (optional; for first-year rates), activated categories, cap spending so far (capSpent), spending this year toward its spend milestones (milestoneSpent), merchant credits enrolled in (enrolled) and used this period (creditUsed), or a welcome bonus to track. Picks replace the saved ones in each choice group you set (other groups are kept) for the current period. Use it when the user picks new categories, such as each quarter on U.S. Bank Cash+. ${ctx.walletUse}`,
|
|
183
195
|
inputSchema: CardChange,
|
|
184
196
|
outputSchema: WalletChangeSchema,
|
|
185
197
|
annotations: {
|
|
@@ -121,6 +121,14 @@ export function findMerchant(catalog, id) {
|
|
|
121
121
|
return (catalog.merchants.overrides.find((m) => m.id === id) ??
|
|
122
122
|
catalog.merchants.seed.find((m) => m.id === id));
|
|
123
123
|
}
|
|
124
|
+
// The ids a card rule can name to match this merchant: its own and, for a
|
|
125
|
+
// brand, its parent's.
|
|
126
|
+
export function merchantFamily(catalog, id) {
|
|
127
|
+
if (id === null)
|
|
128
|
+
return [];
|
|
129
|
+
const parent = findMerchant(catalog, id)?.brand_of;
|
|
130
|
+
return parent === undefined ? [id] : [id, parent];
|
|
131
|
+
}
|
|
124
132
|
export function resolveMerchantById(catalog, id, channel) {
|
|
125
133
|
const override = catalog.merchants.overrides.find((m) => m.id === id);
|
|
126
134
|
const found = findMerchant(catalog, id);
|
package/dist/schema/card.js
CHANGED
|
@@ -33,6 +33,16 @@ export const SharedCapSchema = CapSchema.extend({
|
|
|
33
33
|
// aren't in the data yet, not that there's no bonus.
|
|
34
34
|
rotating: z.literal(true).optional(),
|
|
35
35
|
});
|
|
36
|
+
// How a purchase is paid, for rates that depend on it: Apple Card's 2% with
|
|
37
|
+
// Apple Pay, PayPal Cashback's 3% through PayPal checkout. `card` is the card
|
|
38
|
+
// itself or its number, not a phone wallet or PayPal. Unknown unless the
|
|
39
|
+
// person says, so a rule that requires one never applies by assumption.
|
|
40
|
+
export const PaymentMethod = z.enum([
|
|
41
|
+
'card',
|
|
42
|
+
'apple-pay',
|
|
43
|
+
'google-pay',
|
|
44
|
+
'paypal',
|
|
45
|
+
]);
|
|
36
46
|
const hasAny = (m) => Object.values(m).some((v) => v === true || (v !== undefined && v.length > 0));
|
|
37
47
|
// Kinds combine with AND, values within a kind with OR: `{ merchants:
|
|
38
48
|
// [lyft], categories: [transit] }` is a Lyft purchase that codes as transit.
|
|
@@ -52,8 +62,9 @@ export const ExcludeSchema = z
|
|
|
52
62
|
categories: z.array(CategoryId).optional(),
|
|
53
63
|
merchants: z.array(KebabId).optional(),
|
|
54
64
|
mccs: z.array(Mcc).optional(),
|
|
65
|
+
payment: z.array(PaymentMethod).optional(),
|
|
55
66
|
})
|
|
56
|
-
.refine(hasAny, 'exclude needs categories, merchants or
|
|
67
|
+
.refine(hasAny, 'exclude needs categories, merchants, mccs or payment');
|
|
57
68
|
export const RuleSchema = z
|
|
58
69
|
.strictObject({
|
|
59
70
|
id: KebabId,
|
|
@@ -71,6 +82,7 @@ export const RuleSchema = z
|
|
|
71
82
|
.strictObject({
|
|
72
83
|
membership: KebabId.optional(),
|
|
73
84
|
activation: z.boolean().optional(),
|
|
85
|
+
payment: PaymentMethod.optional(),
|
|
74
86
|
})
|
|
75
87
|
.optional(),
|
|
76
88
|
valid_from: IsoDate.optional(),
|
|
@@ -145,10 +157,52 @@ export const ChoiceSchema = z.strictObject({
|
|
|
145
157
|
})),
|
|
146
158
|
])),
|
|
147
159
|
});
|
|
160
|
+
// A rate for a stretch after the account opens, counted from the opening
|
|
161
|
+
// day: Discover's Cashback Match doubles every rate for the first 365 days,
|
|
162
|
+
// Customized Cash adds 3% to its choice category. With `rules`, it changes
|
|
163
|
+
// only those rules' rates, inside their caps; past a cap they earn what they
|
|
164
|
+
// always do. Without, it changes every rate on the card, past caps included.
|
|
165
|
+
export const IntroRateSchema = z
|
|
166
|
+
.strictObject({
|
|
167
|
+
id: KebabId,
|
|
168
|
+
days: z.number().int().positive(),
|
|
169
|
+
rules: z.array(KebabId).min(1).optional(),
|
|
170
|
+
multiply: z.number().positive().optional(),
|
|
171
|
+
add: z.number().positive().optional(), // currency units per dollar
|
|
172
|
+
note: z.string().optional(),
|
|
173
|
+
})
|
|
174
|
+
.refine((i) => (i.multiply === undefined) !== (i.add === undefined), 'an intro rate needs multiply or add, not both');
|
|
175
|
+
// Merchant statement credits: dollars back for spending at named merchants
|
|
176
|
+
// (or a category) up to `amount` each period, like Amex Platinum's $75 a
|
|
177
|
+
// quarter at lululemon. They're separate from earn rates: the purchase still
|
|
178
|
+
// earns the card's rate on its whole amount, and the credit comes on top.
|
|
179
|
+
// `half_year` runs January to June and July to December.
|
|
180
|
+
export const CreditPeriod = z.enum([...CapPeriod.options, 'half_year']);
|
|
181
|
+
export const CreditSchema = z.strictObject({
|
|
182
|
+
id: KebabId,
|
|
183
|
+
match: z
|
|
184
|
+
.strictObject({
|
|
185
|
+
merchants: z.array(KebabId).min(1).optional(),
|
|
186
|
+
categories: z.array(CategoryId).min(1).optional(),
|
|
187
|
+
})
|
|
188
|
+
.refine((m) => (m.merchants === undefined) !== (m.categories === undefined), 'a credit matches merchants or categories, not both'),
|
|
189
|
+
amount: z.number().positive(), // dollars of credit per period
|
|
190
|
+
period: CreditPeriod,
|
|
191
|
+
// The cardholder must enroll with the issuer before it pays.
|
|
192
|
+
enroll: z.literal(true).optional(),
|
|
193
|
+
note: z.string().optional(),
|
|
194
|
+
});
|
|
195
|
+
const HexColor = z.string().regex(/^#[0-9a-f]{6}$/);
|
|
148
196
|
export const CardSchema = z
|
|
149
197
|
.strictObject({
|
|
150
198
|
id: KebabId,
|
|
151
199
|
name: z.string().min(1),
|
|
200
|
+
// A name that fits one line of the Wallet pass's strip (24 characters),
|
|
201
|
+
// for cards whose name is longer: "AAdvantage Platinum".
|
|
202
|
+
short_name: z.string().min(1).max(24).optional(),
|
|
203
|
+
// The card face's main color, roughly, for earnrate's drawing of the
|
|
204
|
+
// card. Never the issuer's card art.
|
|
205
|
+
color: HexColor.optional(),
|
|
152
206
|
issuer: z.string().min(1),
|
|
153
207
|
network: CardNetwork,
|
|
154
208
|
// Merchant ids a store card works at. Required for, and only for, `store`.
|
|
@@ -183,8 +237,11 @@ export const CardSchema = z
|
|
|
183
237
|
choices: z.array(ChoiceSchema).default([]),
|
|
184
238
|
rules: z.array(RuleSchema).default([]),
|
|
185
239
|
milestones: z.array(MilestoneSchema).default([]),
|
|
186
|
-
|
|
187
|
-
|
|
240
|
+
intro_rates: z.array(IntroRateSchema).default([]),
|
|
241
|
+
credits: z.array(CreditSchema).default([]),
|
|
242
|
+
// A short quote from an issuer page for each rule (or `base_rate`, or a
|
|
243
|
+
// credit's id). The weekly source check fails a rule whose quote
|
|
244
|
+
// disappears from its page.
|
|
188
245
|
evidence: z
|
|
189
246
|
.array(z.strictObject({
|
|
190
247
|
rule: z.string().min(1),
|
package/dist/schema/common.js
CHANGED
|
@@ -19,7 +19,7 @@ export const IsoDate = z.iso.date();
|
|
|
19
19
|
// YAML reads `period: 2026` as a number, so accept both and normalize.
|
|
20
20
|
export const PeriodKey = z.preprocess((v) => (typeof v === 'number' ? String(v) : v), z
|
|
21
21
|
.string()
|
|
22
|
-
.regex(/^\d{4}(-Q[1-4]|-(0[1-9]|1[0-2]))?$/, 'expected a period like 2026, 2026-Q4 or 2026-10'));
|
|
22
|
+
.regex(/^\d{4}(-Q[1-4]|-H[12]|-(0[1-9]|1[0-2]))?$/, 'expected a period like 2026, 2026-Q4, 2026-H2 or 2026-10'));
|
|
23
23
|
export const ValueRange = z
|
|
24
24
|
.tuple([z.number().positive(), z.number().positive()])
|
|
25
25
|
.refine(([low, high]) => low <= high, 'range must be [low, high]');
|
package/dist/schema/issuer.js
CHANGED
|
@@ -4,5 +4,9 @@ export const IssuerSchema = z.strictObject({
|
|
|
4
4
|
id: KebabId,
|
|
5
5
|
name: z.string().min(1), // as shown: "American Express"
|
|
6
6
|
search: z.array(z.string().min(1)).default([]), // other words people use
|
|
7
|
+
color: z
|
|
8
|
+
.string()
|
|
9
|
+
.regex(/^#[0-9a-f]{6}$/)
|
|
10
|
+
.optional(), // for a card without its own
|
|
7
11
|
});
|
|
8
12
|
export const IssuersFileSchema = z.array(IssuerSchema).min(1);
|
package/dist/schema/merchant.js
CHANGED
|
@@ -13,6 +13,9 @@ export const MerchantSchema = z.strictObject({
|
|
|
13
13
|
aliases: z.array(z.string().min(1)).default([]),
|
|
14
14
|
domains: z.array(z.string().min(1)).default([]),
|
|
15
15
|
category: CategoryId,
|
|
16
|
+
// A brand of another merchant (Hampton Inn of Hilton): rules that name the
|
|
17
|
+
// parent's id also match the brand. One level only.
|
|
18
|
+
brand_of: KebabId.optional(),
|
|
16
19
|
likely_mcc: Mcc.optional(),
|
|
17
20
|
accepts: Accepts.optional(),
|
|
18
21
|
in_store: ChannelOverride.optional(),
|
package/dist/schema/wallet.js
CHANGED
|
@@ -16,6 +16,10 @@ const CapUsage = z.strictObject({
|
|
|
16
16
|
period: PeriodKey,
|
|
17
17
|
spent: z.number().nonnegative(), // dollars
|
|
18
18
|
});
|
|
19
|
+
const CreditUsage = z.strictObject({
|
|
20
|
+
period: PeriodKey,
|
|
21
|
+
used: z.number().nonnegative(), // dollars of credit
|
|
22
|
+
});
|
|
19
23
|
export const WalletCardSchema = z.strictObject({
|
|
20
24
|
id: KebabId,
|
|
21
25
|
checkout_label: z
|
|
@@ -28,6 +32,9 @@ export const WalletCardSchema = z.strictObject({
|
|
|
28
32
|
.string()
|
|
29
33
|
.regex(/^\d{2}-\d{2}$/)
|
|
30
34
|
.optional(),
|
|
35
|
+
// The day the account opened, for rates that apply only for a stretch
|
|
36
|
+
// after it (intro_rates). bonus.opened stands in when this isn't set.
|
|
37
|
+
opened: IsoDate.optional(),
|
|
31
38
|
// Keyed by shared cap id, or by rule id for an inline cap.
|
|
32
39
|
cap_usage: z.record(KebabId, CapUsage).default({}),
|
|
33
40
|
chosen: z
|
|
@@ -39,6 +46,10 @@ export const WalletCardSchema = z.strictObject({
|
|
|
39
46
|
activated: z.array(KebabId).default([]),
|
|
40
47
|
// Keyed by milestone id: every purchase on the card this period.
|
|
41
48
|
milestone_usage: z.record(KebabId, CapUsage).optional(),
|
|
49
|
+
// Merchant credits (the card's `credits`) the cardholder has enrolled in,
|
|
50
|
+
// by credit id, and the dollars of each credit already received this period.
|
|
51
|
+
enrolled: z.array(KebabId).optional(),
|
|
52
|
+
credit_usage: z.record(KebabId, CreditUsage).optional(),
|
|
42
53
|
// A welcome bonus in progress. Terms default to the card's published
|
|
43
54
|
// offer; set spend, by, points or dollars when yours was different.
|
|
44
55
|
bonus: z
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "earnrate",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "Which credit card earns the most at a merchant, from your cards or every card, and why",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"credit cards",
|
|
@@ -42,19 +42,23 @@
|
|
|
42
42
|
"prepack": "yarn build",
|
|
43
43
|
"smoke:pack": "node scripts/smoke-pack.ts",
|
|
44
44
|
"mcpb:pack": "node scripts/build-mcpb.ts",
|
|
45
|
+
"chatgpt:pack": "node scripts/pack-chatgpt.ts",
|
|
45
46
|
"smoke:mcpb": "node scripts/smoke-mcpb.ts",
|
|
46
47
|
"remote:build": "node scripts/build-remote.ts",
|
|
47
48
|
"remote:check": "yarn remote:build && WRANGLER_SEND_METRICS=false wrangler deploy --config remote/wrangler.jsonc --dry-run --outdir dist",
|
|
48
49
|
"remote:deploy": "yarn remote:build && WRANGLER_SEND_METRICS=false wrangler deploy --config remote/wrangler.jsonc",
|
|
50
|
+
"pass:build": "node scripts/build-pass.ts",
|
|
51
|
+
"pass:check": "yarn pass:build && WRANGLER_SEND_METRICS=false wrangler deploy --config pass/wrangler.jsonc --dry-run --outdir dist",
|
|
52
|
+
"pass:deploy": "yarn pass:build && WRANGLER_SEND_METRICS=false wrangler deploy --config pass/wrangler.jsonc",
|
|
49
53
|
"monitor:check": "WRANGLER_SEND_METRICS=false wrangler deploy --config monitor/wrangler.jsonc --dry-run --outdir dist",
|
|
50
54
|
"monitor:deploy": "WRANGLER_SEND_METRICS=false wrangler deploy --config monitor/wrangler.jsonc",
|
|
51
55
|
"site:build": "vite build site && node scripts/build-pages.ts",
|
|
52
56
|
"site:dev": "vite site",
|
|
53
|
-
"site:deploy": "yarn site:build && WRANGLER_SEND_METRICS=false wrangler deploy",
|
|
57
|
+
"site:deploy": "yarn site:build && WRANGLER_SEND_METRICS=false wrangler deploy && node scripts/indexnow.ts",
|
|
54
58
|
"test": "vitest run",
|
|
55
59
|
"test:watch": "vitest",
|
|
56
60
|
"coverage": "vitest run --coverage --coverage.include=src/**",
|
|
57
|
-
"typecheck": "tsc && tsc -p site && tsc -p remote && tsc -p monitor",
|
|
61
|
+
"typecheck": "tsc && tsc -p site && tsc -p remote && tsc -p monitor && tsc -p pass",
|
|
58
62
|
"lint": "eslint",
|
|
59
63
|
"format": "prettier --write .",
|
|
60
64
|
"format:check": "prettier --check .",
|
|
@@ -71,11 +75,16 @@
|
|
|
71
75
|
"@cloudflare/workers-oauth-provider": "1.2.4",
|
|
72
76
|
"@cloudflare/workers-types": "5.20261031.1",
|
|
73
77
|
"@eslint/js": "^10.0.1",
|
|
78
|
+
"@expo-google-fonts/inter": "0.2.3",
|
|
74
79
|
"@modelcontextprotocol/client": "2.3.1",
|
|
80
|
+
"@resvg/resvg-wasm": "2.6.2",
|
|
75
81
|
"@types/node": "^24",
|
|
82
|
+
"@types/node-forge": "1.3.11",
|
|
76
83
|
"@vitest/coverage-v8": "5.0.5",
|
|
77
84
|
"eslint": "^10.13.0",
|
|
78
85
|
"eslint-config-prettier": "^10.1.8",
|
|
86
|
+
"fflate": "0.8.2",
|
|
87
|
+
"node-forge": "1.3.1",
|
|
79
88
|
"playwright": "1.64.1",
|
|
80
89
|
"preact": "^10.29.8",
|
|
81
90
|
"prettier": "^3.9.10",
|