earnrate 0.2.16 → 0.2.17

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.
Files changed (52) hide show
  1. package/README.md +14 -8
  2. package/data/CHANGELOG.md +14 -0
  3. package/data/VERSION +1 -1
  4. package/data/cards/amex-business-platinum.yaml +15 -1
  5. package/data/cards/barclays-jetblue-plus.yaml +19 -1
  6. package/data/cards/chase-freedom-flex.yaml +7 -7
  7. package/data/cards/chase-ihg-one-rewards-business.yaml +19 -3
  8. package/data/cards/chase-ihg-one-rewards-premier-select.yaml +5 -1
  9. package/data/cards/chase-ihg-one-rewards-premier.yaml +5 -1
  10. package/data/cards/chase-ihg-one-rewards.yaml +13 -2
  11. package/data/cards/chase-ink-business-cash.yaml +5 -1
  12. package/data/cards/chase-ink-business-unlimited.yaml +5 -1
  13. package/data/cards/chase-prime-visa.yaml +3 -3
  14. package/data/cards/chase-sapphire-reserve.yaml +12 -3
  15. package/data/cards/chase-united-business.yaml +5 -1
  16. package/data/cards/chase-united-club-business.yaml +5 -1
  17. package/data/cards/chase-united-club.yaml +5 -1
  18. package/data/cards/chase-united-explorer.yaml +5 -1
  19. package/data/cards/chase-united-quest.yaml +5 -1
  20. package/data/cards/citi-strata-premier.yaml +14 -1
  21. package/data/cards/wells-fargo-autograph-journey.yaml +16 -1
  22. package/data/merchants/overrides.yaml +3 -3
  23. package/dist/cli/args.js +13 -7
  24. package/dist/cli/run.js +4 -0
  25. package/dist/cli/wallet.js +18 -8
  26. package/dist/data/integrity.js +13 -0
  27. package/dist/data/wallet-edit.js +5 -0
  28. package/dist/engine/checklist.js +25 -5
  29. package/dist/engine/credits.js +56 -7
  30. package/dist/engine/evaluate.js +21 -1
  31. package/dist/engine/explore.js +27 -9
  32. package/dist/engine/memberships.js +21 -9
  33. package/dist/engine/milestones.js +11 -3
  34. package/dist/engine/offers.js +9 -4
  35. package/dist/engine/recommend.js +21 -2
  36. package/dist/engine/rule-text.js +20 -7
  37. package/dist/engine/setup.js +7 -6
  38. package/dist/engine/simulate.js +43 -10
  39. package/dist/engine/text.js +46 -5
  40. package/dist/engine/wallet-change.js +18 -7
  41. package/dist/engine/wallet-summary.js +25 -10
  42. package/dist/format/card-text.js +2 -10
  43. package/dist/format/explore.js +12 -8
  44. package/dist/format/offers.js +1 -1
  45. package/dist/format/wallet.js +2 -0
  46. package/dist/mcp/inputs.js +9 -1
  47. package/dist/mcp/schemas.js +23 -0
  48. package/dist/mcp/tools/account.js +1 -1
  49. package/dist/mcp/tools/wallet.js +5 -1
  50. package/dist/schema/card.js +11 -0
  51. package/dist/schema/wallet.js +6 -2
  52. package/package.json +1 -1
@@ -179,6 +179,8 @@ export const WalletSummarySchema = z.object({
179
179
  period: CreditPeriod,
180
180
  enroll: z.boolean(),
181
181
  enrolled: z.boolean(),
182
+ skipped: z.boolean(), // the user doesn't use it: not counted anywhere
183
+ confirmed: z.boolean(), // said in use: counts against the annual fee
182
184
  currentPeriod: z.string().nullable(),
183
185
  until: z.string().nullable(),
184
186
  used: z.number(),
@@ -186,6 +188,8 @@ export const WalletSummarySchema = z.object({
186
188
  tracked: z.boolean(),
187
189
  text: z.string(),
188
190
  })),
191
+ usedCredits: z.number(), // a year, counted against the annual fee
192
+ usedCreditsText: z.string().nullable(),
189
193
  chosen: z
190
194
  .object({
191
195
  period: z.string(),
@@ -282,6 +286,10 @@ export const OfferComparisonSchema = z.object({
282
286
  ineligible: z.string().nullable(),
283
287
  bonusValue: z.number(),
284
288
  firstYearValue: z.number(),
289
+ // Statement credits, a year at face value, and the first-year value
290
+ // if all were used. Not ranked on.
291
+ credits: z.number(),
292
+ firstYearValueIfCreditsUsed: z.number(),
285
293
  held: z.boolean(),
286
294
  lastVerified: z.string(),
287
295
  verifiedBy: z.string(),
@@ -381,6 +389,7 @@ export const ExplorationSchema = z.object({
381
389
  enroll: z.boolean(),
382
390
  note: z.string().nullable(),
383
391
  back: z.string(),
392
+ terms: z.string(),
384
393
  earn: z.string(),
385
394
  text: z.string(),
386
395
  evidence: z.object({ quote: z.string(), url: z.string() }).nullable(),
@@ -431,7 +440,19 @@ const SimulationSchema = z.object({
431
440
  cardIds: z.array(z.string()),
432
441
  })),
433
442
  membershipFees: z.number(),
443
+ // Credits the user uses on cards they hold, a year: counted in net.
444
+ heldCredits: z.number(),
434
445
  net: z.number(),
446
+ // Credits on cards not held, a year at face value: not in net.
447
+ otherCredits: z.number(),
448
+ netIfCreditsUsed: z.number(),
449
+ credits: z.array(z.object({
450
+ cardId: z.string(),
451
+ yearly: z.number(),
452
+ creditIds: z.array(z.string()),
453
+ names: z.array(z.string()),
454
+ held: z.boolean(),
455
+ })),
435
456
  lines: z.array(z.object({
436
457
  label: z.string(),
437
458
  spend: z.number(),
@@ -459,6 +480,8 @@ export const SpendingSimulationSchema = z.object({
459
480
  annualFee: z.number(),
460
481
  membershipFee: z.number(),
461
482
  gain: z.number(),
483
+ credits: z.number(), // its statement credits, a year: not in gain
484
+ gainIfCreditsUsed: z.number(),
462
485
  })),
463
486
  });
464
487
  export const CheatSheetSchema = z.object({
@@ -57,7 +57,7 @@ export function registerAccountTools(server, catalog, store, ctx) {
57
57
  const importWallet = importHandler(catalog, store, ctx);
58
58
  server.registerTool('export_wallet', {
59
59
  title: 'Export the wallet',
60
- description: 'The saved wallet as a wallet.yaml file: card ids, checkout labels, picks, activations, opening dates and anniversaries, memberships, point values, cap usage, welcome-bonus progress, yearly spending toward spend bonuses, and statement credits enrolled in and used. earnrate.dev imports it, and the earnrate command line reads it saved as ~/.earnrate/wallet.yaml. Never card numbers.',
60
+ description: 'The saved wallet as a wallet.yaml file: card ids, checkout labels, picks, activations, opening dates and anniversaries, memberships, point values, cap usage, welcome-bonus progress, yearly spending toward spend bonuses, and statement credits enrolled in, used and not used. earnrate.dev imports it, and the earnrate command line reads it saved as ~/.earnrate/wallet.yaml. Never card numbers.',
61
61
  inputSchema: z.object({}),
62
62
  outputSchema: z.object({ yaml: z.string() }),
63
63
  annotations: {
@@ -57,6 +57,10 @@ function changes(catalog, args) {
57
57
  : `enrolled in: ${args.enrolled.join(', ')}`);
58
58
  for (const [id, used] of Object.entries(args.creditUsed ?? {}))
59
59
  out.push(`${money(used)} of the ${id} credit used this period`);
60
+ if (args.skippedCredits !== undefined)
61
+ out.push(args.skippedCredits.length === 0
62
+ ? 'every credit counted'
63
+ : `credits not used: ${args.skippedCredits.join(', ')}`);
60
64
  if (args.bonus === null)
61
65
  out.push('welcome bonus no longer tracked');
62
66
  else if (args.bonus !== undefined)
@@ -291,7 +295,7 @@ export function registerWalletTools(server, deps, ctx, inputs) {
291
295
  }, (args) => tools.add_card.run(args));
292
296
  server.registerTool('update_card', {
293
297
  title: 'Update a card',
294
- 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 bonuses (milestoneSpent), statement 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}`,
298
+ 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 bonuses (milestoneSpent), statement credits enrolled in (enrolled), used this period (creditUsed) or not used at all (skippedCredits), 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}`,
295
299
  inputSchema: tools.update_card.input,
296
300
  outputSchema: WalletChangeSchema,
297
301
  annotations: {
@@ -23,6 +23,9 @@ export const MilestoneSchema = z
23
23
  period: z.enum(['calendar_year', 'cardmember_year']),
24
24
  points: z.number().positive().optional(), // in the card's currency
25
25
  rewards: z.array(z.string().min(1)).min(1).optional(),
26
+ // The day the issuer starts it, for one announced ahead: spending counts
27
+ // from then.
28
+ valid_from: IsoDate.optional(),
26
29
  note: z.string().optional(),
27
30
  })
28
31
  .refine((m) => m.points !== undefined || m.rewards !== undefined, 'a milestone needs points, rewards or both');
@@ -76,6 +79,9 @@ export const RuleSchema = z
76
79
  geography: z
77
80
  .enum(['us_only', 'foreign_only', 'worldwide'])
78
81
  .default('worldwide'),
82
+ // Only a purchase of at least this many dollars. Unknown without an
83
+ // amount, so then it never applies by assumption.
84
+ min_amount: z.number().positive().optional(),
79
85
  // Inline cap, or the id of a shared cap in the card's `caps`.
80
86
  cap: z.union([CapSchema, KebabId]).optional(),
81
87
  requires: z
@@ -186,6 +192,11 @@ export const CreditSchema = z.strictObject({
186
192
  period: CreditPeriod,
187
193
  // The cardholder must enroll with the issuer before it pays.
188
194
  enroll: z.literal(true).optional(),
195
+ // It pays only on a purchase of at least this many dollars.
196
+ min_amount: z.number().positive().optional(),
197
+ // The issuer's last day for it. Past it the credit counts nowhere, and a
198
+ // year's worth counts only the periods that start by then.
199
+ valid_to: IsoDate.optional(),
189
200
  note: z.string().optional(),
190
201
  });
191
202
  const HexColor = z.string().regex(/^#[0-9a-f]{6}$/);
@@ -46,10 +46,14 @@ export const WalletCardSchema = z.strictObject({
46
46
  activated: z.array(KebabId).default([]),
47
47
  // Keyed by milestone id: every purchase on the card this period.
48
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.
49
+ // Merchant credits (the card's `credits`) the cardholder has enrolled in
50
+ // or, for ones that need no enrolling, says they use, by credit id; and
51
+ // the dollars of each credit already received this period.
51
52
  enrolled: z.array(KebabId).optional(),
52
53
  credit_usage: z.record(KebabId, CreditUsage).optional(),
54
+ // Credits the cardholder doesn't use: left out of the pick, the to-do
55
+ // list and the fee math, even if enrolled.
56
+ skipped_credits: z.array(KebabId).optional(),
53
57
  // A welcome bonus in progress. Terms default to the card's published
54
58
  // offer; set spend, by, points or dollars when yours was different.
55
59
  bonus: z
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "earnrate",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
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",