projectionlab-mcp 1.0.1 → 1.2.0

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 (122) hide show
  1. package/HOW-IT-WORKS.md +2 -2
  2. package/MANUAL-SETUP.md +64 -0
  3. package/README.md +34 -271
  4. package/REFERENCE.md +221 -0
  5. package/SETUP-FOR-AGENTS.md +37 -0
  6. package/dist/bootstrap.js +20 -3
  7. package/dist/bootstrap.js.map +1 -1
  8. package/dist/bridge.d.ts +2 -0
  9. package/dist/clonePlan.d.ts +9 -0
  10. package/dist/clonePlan.js +15 -14
  11. package/dist/clonePlan.js.map +1 -1
  12. package/dist/config.d.ts +4 -0
  13. package/dist/config.js +41 -6
  14. package/dist/config.js.map +1 -1
  15. package/dist/currentFinances.d.ts +12 -1
  16. package/dist/currentFinances.js +60 -0
  17. package/dist/currentFinances.js.map +1 -1
  18. package/dist/dataModel.d.ts +58 -0
  19. package/dist/dataModel.js +1429 -0
  20. package/dist/dataModel.js.map +1 -0
  21. package/dist/deletePlan.d.ts +17 -0
  22. package/dist/deletePlan.js +72 -0
  23. package/dist/deletePlan.js.map +1 -0
  24. package/dist/diff.d.ts +288 -0
  25. package/dist/diff.js +744 -0
  26. package/dist/diff.js.map +1 -0
  27. package/dist/errors.d.ts +6 -0
  28. package/dist/errors.js +14 -1
  29. package/dist/errors.js.map +1 -1
  30. package/dist/extensionBridge.d.ts +16 -2
  31. package/dist/extensionBridge.js +104 -23
  32. package/dist/extensionBridge.js.map +1 -1
  33. package/dist/index.js +27 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/instructions.d.ts +1 -1
  36. package/dist/instructions.js +3 -2
  37. package/dist/instructions.js.map +1 -1
  38. package/dist/largeOutput.js +25 -0
  39. package/dist/largeOutput.js.map +1 -1
  40. package/dist/logger.js +11 -4
  41. package/dist/logger.js.map +1 -1
  42. package/dist/pathPatch.d.ts +5 -2
  43. package/dist/pathPatch.js +16 -2
  44. package/dist/pathPatch.js.map +1 -1
  45. package/dist/planSettings.d.ts +1 -1
  46. package/dist/planSettings.js +3 -3
  47. package/dist/planSettings.js.map +1 -1
  48. package/dist/plans.d.ts +73 -10
  49. package/dist/plans.js +206 -42
  50. package/dist/plans.js.map +1 -1
  51. package/dist/prompts/index.js +7 -4
  52. package/dist/prompts/index.js.map +1 -1
  53. package/dist/realEstate.d.ts +22 -0
  54. package/dist/realEstate.js +118 -0
  55. package/dist/realEstate.js.map +1 -0
  56. package/dist/redact.d.ts +26 -0
  57. package/dist/redact.js +93 -1
  58. package/dist/redact.js.map +1 -1
  59. package/dist/resources/dataModel.d.ts +3 -0
  60. package/dist/resources/dataModel.js +66 -0
  61. package/dist/resources/dataModel.js.map +1 -0
  62. package/dist/resources/knowledge.js +189 -47
  63. package/dist/resources/knowledge.js.map +1 -1
  64. package/dist/resources/planData.js +3 -1
  65. package/dist/resources/planData.js.map +1 -1
  66. package/dist/resources/summary.js +4 -1
  67. package/dist/resources/summary.js.map +1 -1
  68. package/dist/snapshot.d.ts +21 -0
  69. package/dist/snapshot.js +116 -12
  70. package/dist/snapshot.js.map +1 -1
  71. package/dist/summary.js +19 -6
  72. package/dist/summary.js.map +1 -1
  73. package/dist/tools/clonePlan.js +17 -8
  74. package/dist/tools/clonePlan.js.map +1 -1
  75. package/dist/tools/connection.js +29 -4
  76. package/dist/tools/connection.js.map +1 -1
  77. package/dist/tools/deletePlan.d.ts +4 -0
  78. package/dist/tools/deletePlan.js +103 -0
  79. package/dist/tools/deletePlan.js.map +1 -0
  80. package/dist/tools/diff.d.ts +4 -0
  81. package/dist/tools/diff.js +417 -0
  82. package/dist/tools/diff.js.map +1 -0
  83. package/dist/tools/getCurrentFinances.js +27 -4
  84. package/dist/tools/getCurrentFinances.js.map +1 -1
  85. package/dist/tools/getPlan.js +4 -4
  86. package/dist/tools/getPlan.js.map +1 -1
  87. package/dist/tools/getPlanSection.js +7 -6
  88. package/dist/tools/getPlanSection.js.map +1 -1
  89. package/dist/tools/getPlanSettings.js +10 -6
  90. package/dist/tools/getPlanSettings.js.map +1 -1
  91. package/dist/tools/restore.js +18 -1
  92. package/dist/tools/restore.js.map +1 -1
  93. package/dist/tools/schema.js +28 -4
  94. package/dist/tools/schema.js.map +1 -1
  95. package/dist/tools/snapshots.d.ts +1 -1
  96. package/dist/tools/snapshots.js +53 -6
  97. package/dist/tools/snapshots.js.map +1 -1
  98. package/dist/tools/summary.js +3 -2
  99. package/dist/tools/summary.js.map +1 -1
  100. package/dist/tools/updateAccount.js +9 -7
  101. package/dist/tools/updateAccount.js.map +1 -1
  102. package/dist/tools/updatePlanEvent.js +71 -33
  103. package/dist/tools/updatePlanEvent.js.map +1 -1
  104. package/dist/tools/updatePlanSection.js +17 -32
  105. package/dist/tools/updatePlanSection.js.map +1 -1
  106. package/dist/tools/updatePlanSettings.js +11 -7
  107. package/dist/tools/updatePlanSettings.js.map +1 -1
  108. package/dist/types/projectionlab.d.ts +3 -1
  109. package/dist/types/wire.d.ts +27 -2
  110. package/dist/types/wire.js +6 -2
  111. package/dist/types/wire.js.map +1 -1
  112. package/dist/updateCheck.d.ts +22 -0
  113. package/dist/updateCheck.js +58 -0
  114. package/dist/updateCheck.js.map +1 -0
  115. package/dist/versionCompare.d.ts +5 -0
  116. package/dist/versionCompare.js +33 -0
  117. package/dist/versionCompare.js.map +1 -0
  118. package/dist/versionNotices.d.ts +9 -0
  119. package/dist/versionNotices.js +42 -0
  120. package/dist/versionNotices.js.map +1 -0
  121. package/package.json +6 -3
  122. package/skills/projectionlab/SKILL.md +329 -29
@@ -1,30 +1,61 @@
1
1
  const KNOWLEDGE = `# ProjectionLab & FIRE Reference
2
2
 
3
3
  Curated concepts for analyzing retirement plans through this MCP server.
4
- Educational only - not financial advice. Always verify current-year tax figures
5
- via web search; they change annually.
4
+
5
+ ProjectionLab v5 is in early access; this reference and the tool descriptions
6
+ describe the current release, 4.6.0
7
+ (https://projectionlab.com/help/projectionlab-v5-early-access-guide).
8
+
9
+ ## How to use this reference
10
+
11
+ - **Explain, do not recommend.** Say what a term means, what ProjectionLab does
12
+ with it, and what the user can look at in the app to decide. Do not tell the
13
+ user what to do with their money and do not predict a personal outcome. This
14
+ is information and education, not personalized financial advice; the decision
15
+ belongs to the user and a licensed professional.
16
+ - **Concepts only; ProjectionLab owns the yearly figures.** The app applies the
17
+ current tax brackets, standard deduction, IRMAA tiers, RMD ages, contribution
18
+ limits and ACA subsidies, and the user sees those numbers in the app (Tax
19
+ Analytics). Never state a yearly figure from memory: they change every year.
20
+ Ask the user to read the current one in ProjectionLab.
6
21
 
7
22
  ## ProjectionLab data model (quick map)
8
23
 
9
24
  - **plans[]**: each plan is a milestone-driven simulation.
10
25
  - **milestones[]**: lifecycle events (retirement, Medicare, death) keyed by
11
26
  date/year/milestone-relative criteria. Other events reference milestone IDs.
27
+ Retirement and Life Expectancy always exist in the app and its delete
28
+ control is disabled for them, so never remove either one
29
+ (https://projectionlab.com/help/manage-milestones).
12
30
  - **income.events[]**: salary, Social Security, pension, inheritance; start/end
13
31
  reference milestone IDs.
14
32
  - **expenses.events[]**: living, travel, healthcare, LTC; carry a
15
33
  \`spendingType\` (essential vs discretionary).
16
34
  - **accounts.events[]**: investment accounts with \`rothConversions\`
17
- schedules, withdrawal rules, and bond/equity allocation glidepaths.
35
+ schedules, withdrawal rules, and bond/equity allocation glidepaths. A custom
36
+ growth or dividend rate on one account covers its stock portion only; the
37
+ bonds there follow the plan-level bond settings unless that account's bond
38
+ allocation is 0%
39
+ (https://projectionlab.com/help/growth-dividend-settings-chance-of-success).
18
40
  - **assets.events[]**: real estate, vehicles with appreciation/depreciation.
19
41
  - **variables**: tax settings, inflation, returns, drawdown order, IRMAA,
20
42
  estate config.
21
- - **withdrawalStrategy**: safe-withdrawal-rate settings, spend mode.
22
- - **montecarlo**: simulation config (review only; cannot be run remotely).
23
- - **priorities.events[]**: savings contribution ordering.
43
+ - **withdrawalStrategy**: a separate mode, not the plan's normal drawdown
44
+ policy. From the year or milestone it starts, its rule replaces most other
45
+ plan events, such as expenses and asset purchases, so most plans leave it
46
+ off (https://projectionlab.com/help/withdrawal-strategy-mode).
47
+ - **montecarlo**: Chance of Success settings (review only; cannot be run
48
+ remotely). Chance of Success runs many trials over historical market periods
49
+ by default, with randomized (Monte Carlo) sampling as an option
50
+ (https://projectionlab.com/help/plan-vs-chance-of-success).
51
+ - **priorities.events[]**: savings contribution ordering. An "Always Fund"
52
+ flow is paid even when income falls short: ProjectionLab sells from other
53
+ accounts to cover it (https://projectionlab.com/help/mandatory-goals).
24
54
  - **today / currentFinances**: actual account balances; \`accountId\` links these
25
55
  to plan \`accounts.events[]\`.
26
- - Monte Carlo and projection *math* run only in the ProjectionLab UI. This
27
- server reads/writes the plan definition; it does not compute projections.
56
+ - Chance of Success trials and projection *math* run only in the ProjectionLab
57
+ UI. This server reads/writes the plan definition; it does not compute
58
+ projections.
28
59
 
29
60
  ## Plan-change outcome metrics
30
61
 
@@ -35,57 +66,168 @@ the requested input changed; they do not measure its financial impact. The
35
66
  Plugin API currently does not expose a supported calculation/result method, so
36
67
  recalculate both outcomes in the UI after applying a change.
37
68
 
69
+ ## Account tax vocabulary
70
+
71
+ Use three words for every account, and use them consistently:
72
+
73
+ - **taxable**: funded with after-tax money; interest, dividends and gains are
74
+ taxed in the year they are received.
75
+ - **tax-deferred**: traditional 401(k) and IRA. Contributions lower taxable
76
+ income now; withdrawals are taxed as ordinary income later.
77
+ - **tax-free**: Roth accounts, and an HSA spent on healthcare. Qualified
78
+ withdrawals are not taxed.
79
+
80
+ Do not use "pre-tax" or "post-tax" as account names; they blur the line between
81
+ tax-deferred and taxable.
82
+
38
83
  ## Core FIRE / decumulation concepts
39
84
 
40
- - **Safe Withdrawal Rate (SWR)**: the classic "4% rule" (Trinity study) is a
41
- starting heuristic for a 30-yr horizon; longer horizons or rich valuations
42
- argue for ~3.0–3.5%. Withdrawal rate falls as guaranteed income (Social
43
- Security, pension) switches on.
44
- - **Sequence-of-returns risk**: poor returns in the *first* years of drawdown
45
- do far more damage than the same returns later. Mitigants: a cash/bond bridge,
46
- and a **rising-equity glidepath** ("bond tent" - most bonds at retirement,
47
- equity rising after).
48
- - **Asset location**: hold bonds in tax-deferred (traditional), high-growth
49
- equity in Roth/HSA (tax-free), and tax-efficient equity/cash in taxable.
50
- Improves after-tax returns without changing the overall allocation.
51
- - **Roth conversion ladder**: convert traditional → Roth in lower-income years
52
- (typically the pre-Social-Security / pre-RMD window) to fill a target bracket,
53
- reduce future RMDs, and shrink the taxable estate. Pay the tax from *taxable*,
54
- not from withholding, so every dollar converted reaches the Roth.
55
-
56
- ## Tax landmarks that gate conversions (verify current-year values!)
57
-
58
- - **Federal brackets**: common conversion targets are the top of the 22% or 24%
59
- bracket.
60
- - **ACA premium subsidies**: a MAGI cliff (~400% FPL) can make marginal rates on
61
- conversions effectively >40% for pre-Medicare years *if* on marketplace
62
- coverage. Irrelevant if covered by retiree/employer/ministry plans.
63
- - **IRMAA**: Medicare Part B/D surcharges triggered by MAGI on a **2-year
64
- lookback**; step-function, so crossing a tier by \$1 costs a full tier.
65
- - **NIIT**: 3.8% on net investment income above a MAGI threshold.
66
- - **0% LTCG bracket**: long-term gains taxed at 0% below a taxable-income
67
- threshold; enables gain harvesting in low-income years.
68
- - **RMDs**: begin at the SECURE 2.0 age (73, rising to 75); large pre-tax
69
- balances force taxable withdrawals that can trigger IRMAA/higher brackets -
70
- the main reason to convert earlier.
85
+ - **Safe Withdrawal Rate (SWR)**: the share of the portfolio taken out in the
86
+ first year of drawdown. The classic "4% rule" (Trinity study) is a starting
87
+ heuristic for a 30-year horizon, not a guarantee; a longer horizon argues for
88
+ a lower start. The portfolio withdrawal a plan needs falls as guaranteed
89
+ income (Social Security, pension) switches on.
90
+ - **Sequence-of-returns risk**: poor returns in the first years of drawdown do
91
+ far more damage than the same returns later. Common mitigations: a cash/bond
92
+ bridge, and a **rising-equity glidepath** ("bond tent" - most bonds at
93
+ retirement, equity rising after).
94
+ - **Asset location**: which account holds which asset. A common convention
95
+ places bonds in tax-deferred accounts, high-growth equity in tax-free (Roth,
96
+ HSA) accounts, and tax-efficient equity and cash in taxable. Location changes
97
+ after-tax return without changing the overall allocation.
98
+ - **Roth conversion**: moving money from a tax-deferred account into a tax-free
99
+ one. The converted amount is taxed as ordinary income in the year of the
100
+ conversion. A "Roth conversion ladder" converts in low-income years (often
101
+ the window after work ends and before Social Security or RMDs start) to fill
102
+ a low bracket, lower later RMDs, and shrink the taxable estate. Paying that
103
+ year's tax from a taxable account rather than from withholding keeps more
104
+ money inside the tax-free account.
105
+
106
+ ## Tax concepts that gate conversions
107
+
108
+ Only the shape of each rule is here. ProjectionLab computes with the
109
+ current-year figures, and the user reads them in Tax Analytics. MAGI means
110
+ modified adjusted gross income; each program (ACA, IRMAA, NIIT) defines it
111
+ slightly differently.
112
+
113
+ - **Federal income tax brackets**: ordinary income is taxed in bands. Income
114
+ that fills an unused low band is cheap; income pushed into the next band
115
+ costs more. What a conversion costs depends on where it lands.
116
+ - **ACA premium subsidies**: marketplace health coverage is income-tested. In
117
+ some income ranges extra income can cut a subsidy faster than tax does, so
118
+ the effective marginal rate in those pre-Medicare years is higher than the
119
+ bracket alone suggests. Not a factor with employer, retiree or other
120
+ non-marketplace coverage.
121
+ - **IRMAA**: an extra charge on Medicare Part B and Part D premiums. It is set
122
+ by the person's MAGI from two years earlier and comes in tiers, so income
123
+ just over a tier boundary pays the whole higher charge. A conversion in one
124
+ year can raise premiums two years later. If the user is already on Medicare
125
+ when the plan starts, ProjectionLab estimates that lookback income in the
126
+ first two plan years, and the actual rule applies from year 3
127
+ (https://projectionlab.com/help/modeling-irmaa-surcharges).
128
+ - **NIIT (net investment income tax)**: a surtax on investment income (gains,
129
+ dividends, interest, rent) once MAGI is above an income threshold. It raises
130
+ the cost of a year that mixes a conversion with investment income.
131
+ - **Long-term capital gains rate**: while taxable income stays under a
132
+ threshold, long-term gains are taxed at 0%. A low-income year can realize
133
+ some gains at no tax cost before that room is gone.
134
+ - **RMDs (Required Minimum Distributions)**: once a person reaches the RMD age,
135
+ tax-deferred accounts require a withdrawal every year, whether or not the
136
+ cash is needed. A large tax-deferred balance can push those forced
137
+ withdrawals into higher brackets and IRMAA tiers, which is the usual reason
138
+ people convert while their income is still low.
71
139
 
72
140
  ## Analysis checklist
73
141
 
74
142
  1. Build a lifecycle timeline (phase, date range, milestone trigger).
75
- 2. Cash flow by phase; flag pure-drawdown years and the pre-SS bridge.
143
+ 2. Cash flow by phase; flag pure-drawdown years and the gap before Social
144
+ Security starts.
76
145
  3. Withdrawal rate now and after guaranteed income begins.
77
- 4. Sequence risk: model a -30% shock in year 1–2.
78
- 5. Roth conversion efficiency: bracket fill vs ACA/IRMAA/NIIT ceilings; compare
79
- lifetime taxes AND net legacy.
80
- 6. Longevity: does the plan survive +5–10 years past life expectancy?
81
- 7. Inflation: nominal vs TIPS in the bond sleeve; healthcare/LTC are inflation-
82
- linked and often decades out.
146
+ 4. Sequence risk: what a poor-return start does to the plan. Only a run in the
147
+ ProjectionLab UI can show it.
148
+ 5. Roth conversion efficiency: bracket headroom versus the ACA, IRMAA and NIIT
149
+ ceilings; compare lifetime taxes and net legacy, not just the tax now.
150
+ 6. Longevity: does the plan last well past life expectancy?
151
+ 7. Inflation: healthcare and long-term care costs rise with inflation and are
152
+ often decades out; check how the plan assumes them.
153
+
154
+ ## ProjectionLab help articles
155
+
156
+ ProjectionLab's own pages, linked for the user and summarized here for a model
157
+ with no web access. The bracketed tag is the plan section or area this reference
158
+ uses for it. The summaries are ours, not ProjectionLab's wording. Glossary of
159
+ financial terms: https://projectionlab.com/financial-terms
160
+
161
+ - **Withdrawal Strategy mode** [withdrawalStrategy]
162
+ https://projectionlab.com/help/withdrawal-strategy-mode - a comparison mode
163
+ for classic rules (initial percentage, variable percentage withdrawal,
164
+ Guyton-Klinger) that starts at a chosen year or milestone. While it is on,
165
+ its rule replaces most other plan events, so most plans leave it off.
166
+ - **Flows, the cash flow priorities** [priorities]
167
+ https://projectionlab.com/help/cash-flow-priorities - Flows (the
168
+ \`priorities\` section in the data) send income to savings goals,
169
+ contributions, extra debt payments and transfers, earliest-added first.
170
+ - **Mandatory goals ("Always Fund")** [priorities]
171
+ https://projectionlab.com/help/mandatory-goals - an Always Fund flow is paid
172
+ ahead of the other flows, selling from other accounts when income falls short.
173
+ - **Milestones** [milestones]
174
+ https://projectionlab.com/help/manage-milestones - lifecycle markers
175
+ triggered by age, date, balance, or a combination of criteria; other events
176
+ anchor their start and end to them. Retirement and Life Expectancy cannot be
177
+ deleted.
178
+ - **Drawdown order** [variables; the chart is app only]
179
+ https://projectionlab.com/help/drawdown-order-visualization - the order
180
+ accounts are liquidated in to cover a yearly shortfall. The graph and its red
181
+ early-withdrawal penalty line are a UI view of that order.
182
+ - **Roth conversions** [accounts; the optimizer is app only]
183
+ https://projectionlab.com/help/roth-conversion - Optimize searches conversion
184
+ strategies against brackets, IRMAA and legacy goals with a beam search, then
185
+ writes the chosen schedule into the plan's accounts. This server can read and
186
+ edit such a schedule; it cannot run the search.
187
+ - **Bond allocation and location** [accounts, variables]
188
+ https://projectionlab.com/help/bond-allocation-and-overrides - the bond
189
+ percentage is set plan-wide and can be overridden per account, and the other
190
+ accounts adjust to keep the plan-wide target, with a warning when it cannot
191
+ balance. Bond location chooses which accounts hold bonds first.
192
+ - **Custom growth and dividend rates** [accounts]
193
+ https://projectionlab.com/help/growth-dividend-settings-chance-of-success - an
194
+ account's custom growth or dividend rate covers its stock portion; the bond
195
+ portion follows the plan's bond settings unless the account's bond allocation
196
+ is overridden to 0%. In Chance of Success such a rate is either held fixed or
197
+ sampled like the rest of the simulation.
198
+ - **IRMAA surcharges** [variables; the optimizer is app only]
199
+ https://projectionlab.com/help/modeling-irmaa-surcharges - tiered Medicare
200
+ Part B and Part D surcharges set by MAGI two years earlier, where crossing a
201
+ tier by any amount costs the whole surcharge. When the user is already on
202
+ Medicare at plan start, ProjectionLab estimates the lookback income for the
203
+ first two plan years.
204
+ - **Plan vs Chance of Success** [montecarlo; results are app only]
205
+ https://projectionlab.com/help/plan-vs-chance-of-success - the Plan tab is one
206
+ fixed path at fixed growth; Chance of Success runs many trials over historical
207
+ or randomized market data. All other tabs follow the Plan tab, not the trial
208
+ outcomes.
209
+ - **Simulation engine** [app only]
210
+ https://projectionlab.com/help/simulation-engine - the app simulates a full
211
+ year at a time (income, withholding, expenses, debt, dividends, prior-year
212
+ taxes, RMDs) and turns a surplus into savings and a shortfall into
213
+ withdrawals.
214
+ - **Flexible spending** [expenses; the feature is app only]
215
+ https://projectionlab.com/help/flex-spending - an optional rule that raises
216
+ or cuts spending with portfolio performance against earlier all-time highs,
217
+ scoped either to all spending or to discretionary expenses only.
218
+ - **Plugins and external accounts** [Current Finances; app only]
219
+ https://projectionlab.com/help/plugins - ProjectionLab does not link to banks;
220
+ plugins (Lunch Money, YNAB, Monarch, or your own code) write balances into
221
+ Current Finances through the Plugin API, which has to be enabled in account
222
+ settings. This server uses that same API.
83
223
  `;
84
224
  export function registerKnowledgeResource(server) {
85
225
  server.registerResource("knowledge", "projectionlab://knowledge", {
86
226
  title: "ProjectionLab & FIRE Knowledge",
87
227
  description: "Curated reference: ProjectionLab data model, FIRE/decumulation concepts, " +
88
- "tax landmarks for Roth conversions, and an analysis checklist. Static; educational only.",
228
+ "retirement tax concepts without yearly figures, an analysis checklist, and an " +
229
+ "index of ProjectionLab help articles. Static; information and education, not " +
230
+ "personalized financial advice.",
89
231
  mimeType: "text/markdown",
90
232
  }, async (uri) => ({
91
233
  contents: [
@@ -1 +1 @@
1
- {"version":3,"file":"knowledge.js","sourceRoot":"","sources":["../../src/resources/knowledge.ts"],"names":[],"mappings":"AAEA,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFjB,CAAC;AAEF,MAAM,UAAU,yBAAyB,CAAC,MAAiB;IACzD,MAAM,CAAC,gBAAgB,CACrB,WAAW,EACX,2BAA2B,EAC3B;QACE,KAAK,EAAE,gCAAgC;QACvC,WAAW,EACT,2EAA2E;YAC3E,0FAA0F;QAC5F,QAAQ,EAAE,eAAe;KAC1B,EACD,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACd,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,GAAG,CAAC,IAAI;gBACb,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,eAAe;aAC1B;SACF;KACF,CAAC,CACH,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"knowledge.js","sourceRoot":"","sources":["../../src/resources/knowledge.ts"],"names":[],"mappings":"AAEA,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8NjB,CAAC;AAEF,MAAM,UAAU,yBAAyB,CAAC,MAAiB;IACzD,MAAM,CAAC,gBAAgB,CACrB,WAAW,EACX,2BAA2B,EAC3B;QACE,KAAK,EAAE,gCAAgC;QACvC,WAAW,EACT,2EAA2E;YAC3E,gFAAgF;YAChF,+EAA+E;YAC/E,gCAAgC;QAClC,QAAQ,EAAE,eAAe;KAC1B,EACD,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACd,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,GAAG,CAAC,IAAI;gBACb,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,eAAe;aAC1B;SACF;KACF,CAAC,CACH,CAAC;AACJ,CAAC"}
@@ -27,7 +27,9 @@ export function registerPlanDataResource(server, bridge) {
27
27
  uri: uri.href,
28
28
  text: JSON.stringify({
29
29
  error: "Failed to export data",
30
- details: err instanceof Error ? err.message : String(err),
30
+ // The message can echo request data, so scrub the Plugin API
31
+ // key from it the way errorResult does.
32
+ details: redactSecrets(err instanceof Error ? err.message : String(err)),
31
33
  }),
32
34
  mimeType: "application/json",
33
35
  },
@@ -1 +1 @@
1
- {"version":3,"file":"planData.js","sourceRoot":"","sources":["../../src/resources/planData.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGpC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,UAAU,wBAAwB,CACtC,MAAiB,EACjB,MAAc;IAEd,MAAM,CAAC,gBAAgB,CACrB,WAAW,EACX,2BAA2B,EAC3B;QACE,KAAK,EAAE,yBAAyB;QAChC,WAAW,EACT,wGAAwG;QAC1G,QAAQ,EAAE,kBAAkB;KAC7B,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACxE,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG,EAAE,GAAG,CAAC,IAAI;wBACb,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;wBAClB,QAAQ,EAAE,kBAAkB;qBAC7B;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,KAAK,CAAC,oBAAoB,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7C,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG,EAAE,GAAG,CAAC,IAAI;wBACb,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,KAAK,EAAE,uBAAuB;4BAC9B,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;yBAC1D,CAAC;wBACF,QAAQ,EAAE,kBAAkB;qBAC7B;iBACF;aACF,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"planData.js","sourceRoot":"","sources":["../../src/resources/planData.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGpC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,UAAU,wBAAwB,CACtC,MAAiB,EACjB,MAAc;IAEd,MAAM,CAAC,gBAAgB,CACrB,WAAW,EACX,2BAA2B,EAC3B;QACE,KAAK,EAAE,yBAAyB;QAChC,WAAW,EACT,wGAAwG;QAC1G,QAAQ,EAAE,kBAAkB;KAC7B,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACxE,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG,EAAE,GAAG,CAAC,IAAI;wBACb,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;wBAClB,QAAQ,EAAE,kBAAkB;qBAC7B;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,KAAK,CAAC,oBAAoB,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7C,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG,EAAE,GAAG,CAAC,IAAI;wBACb,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,KAAK,EAAE,uBAAuB;4BAC9B,6DAA6D;4BAC7D,wCAAwC;4BACxC,OAAO,EAAE,aAAa,CACpB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CACjD;yBACF,CAAC;wBACF,QAAQ,EAAE,kBAAkB;qBAC7B;iBACF;aACF,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { toJson } from "../json.js";
2
2
  import { log } from "../logger.js";
3
+ import { redactSecrets } from "../redact.js";
3
4
  import { buildSummary } from "../summary.js";
4
5
  export function registerSummaryResource(server, bridge) {
5
6
  server.registerResource("summary", "projectionlab://summary", {
@@ -28,7 +29,9 @@ export function registerSummaryResource(server, bridge) {
28
29
  uri: uri.href,
29
30
  text: JSON.stringify({
30
31
  error: "Failed to build summary",
31
- details: err instanceof Error ? err.message : String(err),
32
+ // The message can echo request data, so scrub the Plugin API
33
+ // key from it the way errorResult does.
34
+ details: redactSecrets(err instanceof Error ? err.message : String(err)),
32
35
  }),
33
36
  mimeType: "application/json",
34
37
  },
@@ -1 +1 @@
1
- {"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/resources/summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGpC,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,UAAU,uBAAuB,CACrC,MAAiB,EACjB,MAAc;IAEd,MAAM,CAAC,gBAAgB,CACrB,SAAS,EACT,yBAAyB,EACzB;QACE,KAAK,EAAE,0BAA0B;QACjC,WAAW,EACT,4HAA4H;QAC9H,QAAQ,EAAE,kBAAkB;KAC7B,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;YACvC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YACnC,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG,EAAE,GAAG,CAAC,IAAI;wBACb,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC;wBACrB,QAAQ,EAAE,kBAAkB;qBAC7B;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,KAAK,CAAC,kBAAkB,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3C,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG,EAAE,GAAG,CAAC,IAAI;wBACb,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,KAAK,EAAE,yBAAyB;4BAChC,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;yBAC1D,CAAC;wBACF,QAAQ,EAAE,kBAAkB;qBAC7B;iBACF;aACF,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/resources/summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGpC,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,UAAU,uBAAuB,CACrC,MAAiB,EACjB,MAAc;IAEd,MAAM,CAAC,gBAAgB,CACrB,SAAS,EACT,yBAAyB,EACzB;QACE,KAAK,EAAE,0BAA0B;QACjC,WAAW,EACT,4HAA4H;QAC9H,QAAQ,EAAE,kBAAkB;KAC7B,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;YACvC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YACnC,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG,EAAE,GAAG,CAAC,IAAI;wBACb,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC;wBACrB,QAAQ,EAAE,kBAAkB;qBAC7B;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,KAAK,CAAC,kBAAkB,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3C,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG,EAAE,GAAG,CAAC,IAAI;wBACb,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,KAAK,EAAE,yBAAyB;4BAChC,6DAA6D;4BAC7D,wCAAwC;4BACxC,OAAO,EAAE,aAAa,CACpB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CACjD;yBACF,CAAC;wBACF,QAAQ,EAAE,kBAAkB;qBAC7B;iBACF;aACF,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -1,4 +1,10 @@
1
1
  import type { Snapshot } from "./types/projectionlab.js";
2
+ /** The three mutually exclusive ways a caller can name one snapshot. */
3
+ export interface SnapshotSelectorArgs {
4
+ snapshotId?: string | null;
5
+ nBack?: number | null;
6
+ beforeTime?: string | null;
7
+ }
2
8
  export declare class SnapshotManager {
3
9
  private dir;
4
10
  private initialized;
@@ -8,3 +14,18 @@ export declare class SnapshotManager {
8
14
  list(): Promise<Omit<Snapshot, "data">[]>;
9
15
  get(snapshotId: string): Promise<Snapshot>;
10
16
  }
17
+ /**
18
+ * Resolve a snapshot selector to one snapshot. Shared by
19
+ * projectionlab_rollback and projectionlab_diff so both pick the same
20
+ * snapshot for the same selector.
21
+ *
22
+ * nBack and beforeTime rank snapshots newest first, the order
23
+ * projectionlab_list_snapshots shows, and count every saved snapshot including
24
+ * the rollback_pre_* snapshot a confirmed rollback itself writes. nBack: 1 is
25
+ * therefore always the top of that list, which is the state saved just before
26
+ * a confirmed rollback, so a rollback is undone by rolling back once more.
27
+ *
28
+ * `subject` names the selector in the errors, for example "rollback snapshot"
29
+ * or "from snapshot".
30
+ */
31
+ export declare function resolveSnapshot(snapshots: SnapshotManager, args: SnapshotSelectorArgs, subject?: string): Promise<Snapshot>;
package/dist/snapshot.js CHANGED
@@ -1,13 +1,56 @@
1
- import { readdir, readFile, unlink } from "node:fs/promises";
1
+ import { readdir, readFile, stat, unlink } from "node:fs/promises";
2
2
  import { join, resolve, sep } from "node:path";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { config } from "./config.js";
5
5
  import { ProjectionLabError } from "./errors.js";
6
- import { redactSecrets } from "./redact.js";
6
+ import { redactApiKeyToPlaceholder, restoreApiKeyFromPlaceholder } from "./redact.js";
7
7
  import { secureMkdir, secureWriteFile } from "./secureFs.js";
8
8
  import { log } from "./logger.js";
9
9
  const RETENTION_DAYS = 30;
10
10
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11
+ const SELECTOR_KEYS = ["snapshotId", "nBack", "beforeTime"];
12
+ const SELECTOR_HELP = "Supply exactly one of snapshotId (an exact ID from projectionlab_list_snapshots), " +
13
+ "nBack (how many snapshots back, 1 being the most recent) or " +
14
+ "beforeTime (an ISO 8601 time with an explicit Z or ±HH:MM offset)";
15
+ // Snapshot timestamps are UTC instants. Date.parse reads an offset-less
16
+ // date-time in the host's zone and accepts loose text such as "12345" or
17
+ // "November 8 2026", so beforeTime has to carry its own offset to name one
18
+ // unambiguous instant. A bare date is refused for the same reason: it states
19
+ // no time of day.
20
+ const ISO_TIME_WITH_OFFSET = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:\d{2})$/i;
21
+ /** The instant a beforeTime names, or NaN when it is not a usable one. */
22
+ function parseBeforeTime(value) {
23
+ const parts = ISO_TIME_WITH_OFFSET.exec(value);
24
+ if (!parts)
25
+ return NaN;
26
+ const ms = Date.parse(value);
27
+ if (Number.isNaN(ms))
28
+ return NaN;
29
+ const offset = parts[8].toUpperCase();
30
+ const offsetMinutes = offset === "Z"
31
+ ? 0
32
+ : (offset[0] === "-" ? -1 : 1) *
33
+ (Number(offset.slice(1, 3)) * 60 + Number(offset.slice(4, 6)));
34
+ // Date.parse rolls 31 February over instead of rejecting it, so the wall
35
+ // clock the caller wrote has to survive the round trip.
36
+ const wall = new Date(ms + offsetMinutes * 60_000);
37
+ const matches = wall.getUTCFullYear() === Number(parts[1]) &&
38
+ wall.getUTCMonth() === Number(parts[2]) - 1 &&
39
+ wall.getUTCDate() === Number(parts[3]) &&
40
+ wall.getUTCHours() === Number(parts[4]) &&
41
+ wall.getUTCMinutes() === Number(parts[5]);
42
+ return matches ? ms : NaN;
43
+ }
44
+ function pickSelector(args, subject) {
45
+ const supplied = SELECTOR_KEYS.filter((key) => args[key] !== undefined && args[key] !== null);
46
+ if (supplied.length !== 1) {
47
+ throw new ProjectionLabError("INVALID_INPUT", supplied.length === 0
48
+ ? `No ${subject} selector supplied`
49
+ : `Supply exactly one ${subject} selector, received ${supplied.join(" and ")}`, SELECTOR_HELP);
50
+ }
51
+ // Exactly one key is present and is neither undefined nor null.
52
+ return { kind: supplied[0], value: args[supplied[0]] };
53
+ }
11
54
  export class SnapshotManager {
12
55
  dir = config.snapshotDir;
13
56
  initialized = false;
@@ -24,11 +67,12 @@ export class SnapshotManager {
24
67
  if (!file.endsWith(".json"))
25
68
  continue;
26
69
  try {
27
- const raw = await readFile(join(this.dir, file), "utf-8");
28
- const snap = JSON.parse(raw);
29
- if (new Date(snap.timestamp).getTime() < cutoff) {
30
- await unlink(join(this.dir, file));
31
- log.info("snapshot", `Pruned expired snapshot ${snap.id}`);
70
+ // mtime is the write time; snapshot files are never rewritten.
71
+ const filePath = join(this.dir, file);
72
+ const { mtimeMs } = await stat(filePath);
73
+ if (mtimeMs < cutoff) {
74
+ await unlink(filePath);
75
+ log.info("snapshot", `Pruned expired snapshot ${file}`);
32
76
  }
33
77
  }
34
78
  catch {
@@ -43,9 +87,11 @@ export class SnapshotManager {
43
87
  timestamp: new Date().toISOString(),
44
88
  operation,
45
89
  sections,
46
- // Value-only redaction: scrubs the literal API key if it ever appears in
47
- // the state without altering any financial data, so rollback stays exact.
48
- data: redactSecrets(data),
90
+ // The Plugin API key is stored as SNAPSHOT_KEY_PLACEHOLDER, so the file
91
+ // on disk never holds it; SnapshotManager.get puts the current
92
+ // config.apiKey back on every read, which keeps a rollback exact while
93
+ // the key itself stays out of the snapshot files.
94
+ data: redactApiKeyToPlaceholder(data),
49
95
  };
50
96
  const filePath = join(this.dir, `${snapshot.id}.json`);
51
97
  await secureWriteFile(filePath, JSON.stringify(snapshot, null, 2));
@@ -75,7 +121,10 @@ export class SnapshotManager {
75
121
  log.warn("snapshot", `Failed to read snapshot file: ${file}`);
76
122
  }
77
123
  }
78
- return snapshots.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
124
+ // Newest first, with equal millisecond timestamps ordered by id so a
125
+ // recency-based rollback selector picks the same snapshot every time.
126
+ return snapshots.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() ||
127
+ (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
79
128
  }
80
129
  async get(snapshotId) {
81
130
  await this.ensureDir();
@@ -92,11 +141,66 @@ export class SnapshotManager {
92
141
  }
93
142
  try {
94
143
  const raw = await readFile(filePath, "utf-8");
95
- return JSON.parse(raw);
144
+ const snapshot = JSON.parse(raw);
145
+ // Undo the on-disk key placeholder (see create) so every reader sees
146
+ // the real key wherever the exported state carried it.
147
+ snapshot.data = restoreApiKeyFromPlaceholder(snapshot.data);
148
+ return snapshot;
96
149
  }
97
150
  catch {
98
151
  throw new ProjectionLabError("SNAPSHOT_NOT_FOUND", `Snapshot ${snapshotId} not found`, "Use projectionlab_list_snapshots to see available snapshots");
99
152
  }
100
153
  }
101
154
  }
155
+ /**
156
+ * Resolve a snapshot selector to one snapshot. Shared by
157
+ * projectionlab_rollback and projectionlab_diff so both pick the same
158
+ * snapshot for the same selector.
159
+ *
160
+ * nBack and beforeTime rank snapshots newest first, the order
161
+ * projectionlab_list_snapshots shows, and count every saved snapshot including
162
+ * the rollback_pre_* snapshot a confirmed rollback itself writes. nBack: 1 is
163
+ * therefore always the top of that list, which is the state saved just before
164
+ * a confirmed rollback, so a rollback is undone by rolling back once more.
165
+ *
166
+ * `subject` names the selector in the errors, for example "rollback snapshot"
167
+ * or "from snapshot".
168
+ */
169
+ export async function resolveSnapshot(snapshots, args, subject = "rollback snapshot") {
170
+ const selector = pickSelector(args, subject);
171
+ switch (selector.kind) {
172
+ case "snapshotId":
173
+ return snapshots.get(selector.value);
174
+ case "nBack": {
175
+ const nBack = selector.value;
176
+ if (!Number.isInteger(nBack) || nBack < 1) {
177
+ throw new ProjectionLabError("INVALID_INPUT", `nBack must be a positive integer with 1 as the most recent snapshot, received ${String(nBack)}`, SELECTOR_HELP);
178
+ }
179
+ const list = await snapshots.list();
180
+ if (nBack > list.length) {
181
+ const existence = list.length === 0
182
+ ? "no snapshots exist"
183
+ : `only ${list.length} snapshot${list.length === 1 ? "" : "s"} exist${list.length === 1 ? "s" : ""}`;
184
+ throw new ProjectionLabError("SNAPSHOT_NOT_FOUND", `nBack ${nBack} is out of range; ${existence}`, "Use projectionlab_list_snapshots to see them, or a smaller nBack. " +
185
+ "Snapshots are saved automatically before confirmed writes");
186
+ }
187
+ return snapshots.get(list[nBack - 1].id);
188
+ }
189
+ case "beforeTime": {
190
+ const cutoff = parseBeforeTime(selector.value);
191
+ if (Number.isNaN(cutoff)) {
192
+ throw new ProjectionLabError("INVALID_INPUT", `beforeTime "${selector.value}" is not an ISO 8601 time with an explicit Z or ±HH:MM offset`, "Use a time such as 2026-11-08T15:30:00Z or 2026-11-08T15:30:00+02:00. " +
193
+ "projectionlab_list_snapshots shows snapshot times in that form");
194
+ }
195
+ const list = await snapshots.list();
196
+ const match = list.find((entry) => Date.parse(entry.timestamp) < cutoff);
197
+ if (!match) {
198
+ throw new ProjectionLabError("SNAPSHOT_NOT_FOUND", list.length === 0
199
+ ? "No snapshots exist, so nothing predates that beforeTime"
200
+ : `No snapshot predates ${selector.value}; the oldest is ${list[list.length - 1].timestamp}`, "Use projectionlab_list_snapshots to see the saved snapshot times, or a later beforeTime");
201
+ }
202
+ return snapshots.get(match.id);
203
+ }
204
+ }
205
+ }
102
206
  //# sourceMappingURL=snapshot.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"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"}
package/dist/summary.js CHANGED
@@ -23,15 +23,28 @@ export function buildSummary(data) {
23
23
  for (const item of items) {
24
24
  if (item && typeof item === "object") {
25
25
  const obj = item;
26
- const balance = Number(obj.balance ?? obj.value ?? 0);
27
26
  group.count++;
28
- group.total += balance;
29
- // Debts reduce net worth
30
- if (section === "debts") {
31
- totalNetWorth -= Math.abs(balance);
27
+ if (section === "assets") {
28
+ // In today.assets, amount is market value and balance is the loan balance.
29
+ const marketValue = Number(obj.amount ?? obj.value ?? 0);
30
+ group.total += marketValue;
31
+ totalNetWorth += marketValue;
32
+ // An outstanding loan balance on an asset is subtracted from net worth
33
+ // as a liability. Paid-in-full assets carry balance: 0.
34
+ const loanBalance = Number(obj.balance ?? 0);
35
+ if (loanBalance > 0) {
36
+ totalNetWorth -= loanBalance;
37
+ }
32
38
  }
33
39
  else {
34
- totalNetWorth += balance;
40
+ const balance = Number(obj.balance ?? obj.value ?? 0);
41
+ group.total += balance;
42
+ if (section === "debts") {
43
+ totalNetWorth -= Math.abs(balance);
44
+ }
45
+ else {
46
+ totalNetWorth += balance;
47
+ }
35
48
  }
36
49
  }
37
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"summary.js","sourceRoot":"","sources":["../src/summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAO7C;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAkB;IAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAA4C,CAAC;IACnE,MAAM,cAAc,GAAqD,EAAE,CAAC;IAC5E,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,IAAI,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC7C,MAAM,QAAQ,GAAG;YACf,oBAAoB;YACpB,iBAAiB;YACjB,OAAO;YACP,QAAQ;SACT,CAAC;QAEF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAI,QAAoC,CAAC,OAAO,CAAC,CAAC;YAC7D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,SAAS;YAEpC,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;YACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;oBACrC,MAAM,GAAG,GAAG,IAA+B,CAAC;oBAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;oBACtD,KAAK,CAAC,KAAK,EAAE,CAAC;oBACd,KAAK,CAAC,KAAK,IAAI,OAAO,CAAC;oBACvB,yBAAyB;oBACzB,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;wBACxB,aAAa,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBACrC,CAAC;yBAAM,CAAC;wBACN,aAAa,IAAI,OAAO,CAAC;oBAC3B,CAAC;gBACH,CAAC;YACH,CAAC;YACD,IAAI,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;gBACpB,cAAc,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAG;QAClB,eAAe;QACf,QAAQ;QACR,gBAAgB;QAChB,UAAU;QACV,YAAY;QACZ,gBAAgB;QAChB,eAAe;KAChB,CAAC;IAEF,MAAM,KAAK,GAAkB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QACpD,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACnB,MAAM,GAAG,GAAG,CAA4B,CAAC;YACzC,MAAM,OAAO,GAAgB;gBAC3B,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;gBACxB,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,SAAS,CAAC;gBACnC,GAAG,eAAe,CAAC,GAAG,CAAC;aACxB,CAAC;YACF,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;gBAC9B,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;oBAC3B,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC;QACJ,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;QACL,KAAK,EAAE,iBAAiB;QACxB,0BAA0B,EAAE,KAAK;QACjC,aAAa;QACb,cAAc;QACd,SAAS,EAAE,KAAK,CAAC,MAAM;QACvB,KAAK;KACN,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"summary.js","sourceRoot":"","sources":["../src/summary.ts"],"names":[],"mappings":"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"}