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.
- package/HOW-IT-WORKS.md +2 -2
- package/MANUAL-SETUP.md +64 -0
- package/README.md +34 -271
- package/REFERENCE.md +221 -0
- package/SETUP-FOR-AGENTS.md +37 -0
- package/dist/bootstrap.js +20 -3
- package/dist/bootstrap.js.map +1 -1
- package/dist/bridge.d.ts +2 -0
- package/dist/clonePlan.d.ts +9 -0
- package/dist/clonePlan.js +15 -14
- package/dist/clonePlan.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +41 -6
- package/dist/config.js.map +1 -1
- package/dist/currentFinances.d.ts +12 -1
- package/dist/currentFinances.js +60 -0
- package/dist/currentFinances.js.map +1 -1
- package/dist/dataModel.d.ts +58 -0
- package/dist/dataModel.js +1429 -0
- package/dist/dataModel.js.map +1 -0
- package/dist/deletePlan.d.ts +17 -0
- package/dist/deletePlan.js +72 -0
- package/dist/deletePlan.js.map +1 -0
- package/dist/diff.d.ts +288 -0
- package/dist/diff.js +744 -0
- package/dist/diff.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +14 -1
- package/dist/errors.js.map +1 -1
- package/dist/extensionBridge.d.ts +16 -2
- package/dist/extensionBridge.js +104 -23
- package/dist/extensionBridge.js.map +1 -1
- package/dist/index.js +27 -1
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +3 -2
- package/dist/instructions.js.map +1 -1
- package/dist/largeOutput.js +25 -0
- package/dist/largeOutput.js.map +1 -1
- package/dist/logger.js +11 -4
- package/dist/logger.js.map +1 -1
- package/dist/pathPatch.d.ts +5 -2
- package/dist/pathPatch.js +16 -2
- package/dist/pathPatch.js.map +1 -1
- package/dist/planSettings.d.ts +1 -1
- package/dist/planSettings.js +3 -3
- package/dist/planSettings.js.map +1 -1
- package/dist/plans.d.ts +73 -10
- package/dist/plans.js +206 -42
- package/dist/plans.js.map +1 -1
- package/dist/prompts/index.js +7 -4
- package/dist/prompts/index.js.map +1 -1
- package/dist/realEstate.d.ts +22 -0
- package/dist/realEstate.js +118 -0
- package/dist/realEstate.js.map +1 -0
- package/dist/redact.d.ts +26 -0
- package/dist/redact.js +93 -1
- package/dist/redact.js.map +1 -1
- package/dist/resources/dataModel.d.ts +3 -0
- package/dist/resources/dataModel.js +66 -0
- package/dist/resources/dataModel.js.map +1 -0
- package/dist/resources/knowledge.js +189 -47
- package/dist/resources/knowledge.js.map +1 -1
- package/dist/resources/planData.js +3 -1
- package/dist/resources/planData.js.map +1 -1
- package/dist/resources/summary.js +4 -1
- package/dist/resources/summary.js.map +1 -1
- package/dist/snapshot.d.ts +21 -0
- package/dist/snapshot.js +116 -12
- package/dist/snapshot.js.map +1 -1
- package/dist/summary.js +19 -6
- package/dist/summary.js.map +1 -1
- package/dist/tools/clonePlan.js +17 -8
- package/dist/tools/clonePlan.js.map +1 -1
- package/dist/tools/connection.js +29 -4
- package/dist/tools/connection.js.map +1 -1
- package/dist/tools/deletePlan.d.ts +4 -0
- package/dist/tools/deletePlan.js +103 -0
- package/dist/tools/deletePlan.js.map +1 -0
- package/dist/tools/diff.d.ts +4 -0
- package/dist/tools/diff.js +417 -0
- package/dist/tools/diff.js.map +1 -0
- package/dist/tools/getCurrentFinances.js +27 -4
- package/dist/tools/getCurrentFinances.js.map +1 -1
- package/dist/tools/getPlan.js +4 -4
- package/dist/tools/getPlan.js.map +1 -1
- package/dist/tools/getPlanSection.js +7 -6
- package/dist/tools/getPlanSection.js.map +1 -1
- package/dist/tools/getPlanSettings.js +10 -6
- package/dist/tools/getPlanSettings.js.map +1 -1
- package/dist/tools/restore.js +18 -1
- package/dist/tools/restore.js.map +1 -1
- package/dist/tools/schema.js +28 -4
- package/dist/tools/schema.js.map +1 -1
- package/dist/tools/snapshots.d.ts +1 -1
- package/dist/tools/snapshots.js +53 -6
- package/dist/tools/snapshots.js.map +1 -1
- package/dist/tools/summary.js +3 -2
- package/dist/tools/summary.js.map +1 -1
- package/dist/tools/updateAccount.js +9 -7
- package/dist/tools/updateAccount.js.map +1 -1
- package/dist/tools/updatePlanEvent.js +71 -33
- package/dist/tools/updatePlanEvent.js.map +1 -1
- package/dist/tools/updatePlanSection.js +17 -32
- package/dist/tools/updatePlanSection.js.map +1 -1
- package/dist/tools/updatePlanSettings.js +11 -7
- package/dist/tools/updatePlanSettings.js.map +1 -1
- package/dist/types/projectionlab.d.ts +3 -1
- package/dist/types/wire.d.ts +27 -2
- package/dist/types/wire.js +6 -2
- package/dist/types/wire.js.map +1 -1
- package/dist/updateCheck.d.ts +22 -0
- package/dist/updateCheck.js +58 -0
- package/dist/updateCheck.js.map +1 -0
- package/dist/versionCompare.d.ts +5 -0
- package/dist/versionCompare.js +33 -0
- package/dist/versionCompare.js.map +1 -0
- package/dist/versionNotices.d.ts +9 -0
- package/dist/versionNotices.js +42 -0
- package/dist/versionNotices.js.map +1 -0
- package/package.json +6 -3
- package/skills/projectionlab/SKILL.md +329 -29
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveSnapshot, } from "../snapshot.js";
|
|
3
|
+
import { ProjectionLabError, errorResult } from "../errors.js";
|
|
4
|
+
import { toJson } from "../json.js";
|
|
5
|
+
import { textOrFile } from "../largeOutput.js";
|
|
6
|
+
import { redactSecrets } from "../redact.js";
|
|
7
|
+
import { FIELD_CHANGE_CAP, createDiffBudget, diffPlanPair, diffSectionData, planKey, } from "../diff.js";
|
|
8
|
+
import { PLAN_SECTIONS, resolvePlanIndex, planNotFoundError, } from "../plans.js";
|
|
9
|
+
/** The data areas a snapshot can hold and this tool knows how to compare. */
|
|
10
|
+
const COMPARED_SECTIONS = [
|
|
11
|
+
"plans",
|
|
12
|
+
"currentFinances",
|
|
13
|
+
"progress",
|
|
14
|
+
"settings",
|
|
15
|
+
];
|
|
16
|
+
/** A snapshot stores Current Finances under its own key; the export uses `today`. */
|
|
17
|
+
function getSection(data, section) {
|
|
18
|
+
return section === "currentFinances" ? data.today : data[section];
|
|
19
|
+
}
|
|
20
|
+
function describeSnapshot(snapshot) {
|
|
21
|
+
return {
|
|
22
|
+
source: "snapshot",
|
|
23
|
+
snapshotId: snapshot.id,
|
|
24
|
+
timestamp: snapshot.timestamp,
|
|
25
|
+
operation: snapshot.operation,
|
|
26
|
+
sections: snapshot.sections,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function plansOf(...values) {
|
|
30
|
+
return values.flatMap((value) => (Array.isArray(value) ? value : []));
|
|
31
|
+
}
|
|
32
|
+
const selectorSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
snapshotId: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Exact ID of a saved snapshot, from projectionlab_list_snapshots. Supply exactly one of snapshotId, nBack or beforeTime."),
|
|
38
|
+
nBack: z
|
|
39
|
+
.number()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("How many snapshots back, a positive integer with 1 as the most recent, counted in the same newest-first order as projectionlab_list_snapshots. Supply exactly one of snapshotId, nBack or beforeTime."),
|
|
42
|
+
beforeTime: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("ISO 8601 time carrying an explicit Z or ±HH:MM offset, such as 2026-11-08T15:30:00Z; picks the latest snapshot taken strictly before it. Supply exactly one of snapshotId, nBack or beforeTime."),
|
|
46
|
+
})
|
|
47
|
+
.strict();
|
|
48
|
+
/**
|
|
49
|
+
* The tool compares one data set at two points in time (`time`) or two
|
|
50
|
+
* different plans side by side (`plans`). Which one is asked for decides which
|
|
51
|
+
* arguments are required, and the two modes share no argument, so a mixed call
|
|
52
|
+
* is refused with what to drop rather than guessed at.
|
|
53
|
+
*/
|
|
54
|
+
function requireMode(args) {
|
|
55
|
+
if (args.planA === undefined && args.planB === undefined) {
|
|
56
|
+
if (args.from === undefined) {
|
|
57
|
+
throw new ProjectionLabError("INVALID_INPUT", "No from snapshot selector supplied", "Supply from with exactly one of snapshotId, nBack or beforeTime to compare a snapshot against another snapshot or the live data, or supply planA and planB together to compare two plans");
|
|
58
|
+
}
|
|
59
|
+
return { mode: "time" };
|
|
60
|
+
}
|
|
61
|
+
if (args.planA === undefined || args.planB === undefined) {
|
|
62
|
+
const missing = args.planA === undefined ? "planA" : "planB";
|
|
63
|
+
throw new ProjectionLabError("INVALID_INPUT", `planA and planB compare two plans together, and ${missing} is missing`, "Name both plans, or drop planA and planB to compare a snapshot against another snapshot or the live data");
|
|
64
|
+
}
|
|
65
|
+
const { planA, planB } = args;
|
|
66
|
+
for (const name of ["to", "planIdentifier"]) {
|
|
67
|
+
if (args[name] !== undefined) {
|
|
68
|
+
throw new ProjectionLabError("INVALID_INPUT", `planA and planB compare two plans, and ${name} belongs to the points-in-time comparison`, `Drop ${name}. planA and planB read both plans from the live app, or from one snapshot with from. Compare points in time with from and to alone`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { mode: "plans", planA, planB };
|
|
72
|
+
}
|
|
73
|
+
/** A missing plan names the side it was asked for, so the fix is unambiguous. */
|
|
74
|
+
function planSideNotFoundError(plans, identifier, side) {
|
|
75
|
+
const notFound = planNotFoundError(plans, identifier);
|
|
76
|
+
const position = side === "planA" ? "first" : "second";
|
|
77
|
+
return new ProjectionLabError("INVALID_INPUT", `${notFound.message} for ${side}`, `${side}'s values are reported ${position} in each "A → B" pair. ${notFound.suggestion}`);
|
|
78
|
+
}
|
|
79
|
+
/** How a plan is named to the user: its name, falling back to its id. */
|
|
80
|
+
function planLabel(plan) {
|
|
81
|
+
const obj = (plan ?? {});
|
|
82
|
+
return String(obj.name ?? obj.id ?? "unnamed");
|
|
83
|
+
}
|
|
84
|
+
export function registerDiffTool(server, bridge, snapshots) {
|
|
85
|
+
/** The plans a plan-to-plan comparison reads, and where they came from. */
|
|
86
|
+
async function readPlansForPair(from) {
|
|
87
|
+
if (from) {
|
|
88
|
+
const snapshot = await resolveSnapshot(snapshots, from, "from snapshot");
|
|
89
|
+
const stored = snapshot.data.plans;
|
|
90
|
+
if (!Array.isArray(stored)) {
|
|
91
|
+
throw new ProjectionLabError("INVALID_INPUT", `The snapshot "${snapshot.id}" holds no plans data`, "Drop from to compare the plans in ProjectionLab now, or pick a snapshot whose sections include plans; projectionlab_list_snapshots shows what each snapshot holds");
|
|
92
|
+
}
|
|
93
|
+
return { plans: stored, readFrom: describeSnapshot(snapshot) };
|
|
94
|
+
}
|
|
95
|
+
const live = await bridge.exportData({ fresh: true });
|
|
96
|
+
const plans = Array.isArray(live.plans) ? live.plans : [];
|
|
97
|
+
if (plans.length === 0) {
|
|
98
|
+
throw new ProjectionLabError("INVALID_INPUT", "No plans found in ProjectionLab", "Create the plans in ProjectionLab first, or name two saved plans with from");
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
plans,
|
|
102
|
+
readFrom: {
|
|
103
|
+
source: "live",
|
|
104
|
+
readAt: new Date().toISOString(),
|
|
105
|
+
note: "both plans read from ProjectionLab when this diff ran",
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** Compare two different plans field by field, planA's values reported first. */
|
|
110
|
+
async function comparePlans(args) {
|
|
111
|
+
const budget = createDiffBudget({
|
|
112
|
+
pathsOnly: args.pathsOnly === true,
|
|
113
|
+
});
|
|
114
|
+
const { plans, readFrom } = await readPlansForPair(args.from);
|
|
115
|
+
const indexA = resolvePlanIndex(plans, args.planA);
|
|
116
|
+
if (indexA === -1) {
|
|
117
|
+
throw planSideNotFoundError(plans, args.planA, "planA");
|
|
118
|
+
}
|
|
119
|
+
const indexB = resolvePlanIndex(plans, args.planB);
|
|
120
|
+
if (indexB === -1) {
|
|
121
|
+
throw planSideNotFoundError(plans, args.planB, "planB");
|
|
122
|
+
}
|
|
123
|
+
if (indexA === indexB) {
|
|
124
|
+
throw new ProjectionLabError("INVALID_INPUT", `planA and planB both name the same plan ("${planLabel(plans[indexA])}")`, "Name two different plans; projectionlab_describe_schema lists them by name and ID");
|
|
125
|
+
}
|
|
126
|
+
const pair = diffPlanPair(plans[indexA], plans[indexB], budget, args.section);
|
|
127
|
+
const result = {
|
|
128
|
+
mode: "plans",
|
|
129
|
+
planA: pair.a,
|
|
130
|
+
planB: pair.b,
|
|
131
|
+
readFrom,
|
|
132
|
+
...(args.section ? { section: args.section } : {}),
|
|
133
|
+
...(args.pathsOnly ? { pathsOnly: true } : {}),
|
|
134
|
+
changed: pair.changed,
|
|
135
|
+
...(pair.changed
|
|
136
|
+
? {}
|
|
137
|
+
: {
|
|
138
|
+
message: "No differences in the fields compared. The plan's own id and name, each event's own id and random key, and the computed fields (lastUpdated, simKey, meta) are excluded, and a reference to another event of the same plan is compared by the event it names, so a plan rebuilt by hand reads as unchanged.",
|
|
139
|
+
}),
|
|
140
|
+
planSettings: pair.settings,
|
|
141
|
+
...(pair.sections ? { sections: pair.sections } : {}),
|
|
142
|
+
...(pair.changedSections
|
|
143
|
+
? { changedSections: pair.changedSections }
|
|
144
|
+
: {}),
|
|
145
|
+
fieldChangeCap: budget.fieldChangeCap,
|
|
146
|
+
...(budget.omittedFieldChanges > 0
|
|
147
|
+
? { omittedFieldChanges: budget.omittedFieldChanges }
|
|
148
|
+
: {}),
|
|
149
|
+
...(budget.omittedEntries > 0
|
|
150
|
+
? { omittedEntries: budget.omittedEntries }
|
|
151
|
+
: {}),
|
|
152
|
+
};
|
|
153
|
+
const json = toJson(redactSecrets(result, { byName: true }));
|
|
154
|
+
// A spilled result keeps the facts that name the comparison; the
|
|
155
|
+
// sections themselves stay in the file it points at.
|
|
156
|
+
const { sections: _sections, ...summary } = result;
|
|
157
|
+
return {
|
|
158
|
+
content: [
|
|
159
|
+
{ type: "text", text: await textOrFile(json, "diff", summary) },
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
server.registerTool("projectionlab_diff", {
|
|
164
|
+
title: "Diff Snapshots Over Time, or Two Plans Against Each Other",
|
|
165
|
+
description: "Reports field-level differences, read-only: it never writes and never " +
|
|
166
|
+
"snapshots, so it works with PL_READ_ONLY=true. Two modes, told apart by the " +
|
|
167
|
+
"arguments you supply. " +
|
|
168
|
+
"Points in time: from is required and names a saved snapshot with exactly " +
|
|
169
|
+
"one of snapshotId, nBack or beforeTime, resolved the way " +
|
|
170
|
+
"projectionlab_rollback resolves them. " +
|
|
171
|
+
"to is optional: another snapshot selector, or omitted to compare against the data in " +
|
|
172
|
+
"ProjectionLab right now. " +
|
|
173
|
+
"Only the data sections the from snapshot holds are compared, and when to is a snapshot, " +
|
|
174
|
+
"only the sections both hold; comparedSections says which. " +
|
|
175
|
+
"For plans it reports plans added, removed and changed, and within a changed plan the " +
|
|
176
|
+
"events added, removed and modified per section, each modification as " +
|
|
177
|
+
"\"path\": \"before → after\" dot paths you can read back with projectionlab_get_plan_section " +
|
|
178
|
+
"or patch with projectionlab_update_plan_event. " +
|
|
179
|
+
"Computed plan fields (lastUpdated, simKey, meta) are left out, so a plan ProjectionLab " +
|
|
180
|
+
"merely re-saved reads as unchanged. " +
|
|
181
|
+
"For Current Finances it reports rows added, removed and modified by id within each " +
|
|
182
|
+
"collection. " +
|
|
183
|
+
"Use it to confirm a write landed as intended, to explain to the user what changed, or to " +
|
|
184
|
+
"see what projectionlab_rollback would undo before rolling back. " +
|
|
185
|
+
"Narrow the output with planIdentifier (plan name or ID) and section. " +
|
|
186
|
+
"Two plans: planA and planB name two different plans and compare one against the " +
|
|
187
|
+
"other, so a plan rebuilt by hand in the ProjectionLab UI can be checked against " +
|
|
188
|
+
"the original. Both plans are read from the live app, or from one snapshot when " +
|
|
189
|
+
"from is also given; planA and planB cannot be combined with to or planIdentifier. " +
|
|
190
|
+
"Events match by name plus occurrence. The fields that say which plan an object is " +
|
|
191
|
+
"(the plan's id and name, each event's own id) and the computed fields are left " +
|
|
192
|
+
"out, as is each event's random key, which ProjectionLab assigns when the event is " +
|
|
193
|
+
"created, so re-entering the same event does not read as a difference. A reference " +
|
|
194
|
+
"from one event to another event of the same plan, such as a " +
|
|
195
|
+
"milestone id in a start or end condition, is compared by the event it names, so " +
|
|
196
|
+
"the same milestone still compares equal under the unrelated ids two plans store. " +
|
|
197
|
+
"Each section reports onlyInA, onlyInB, different (each with \"path\": \"planA " +
|
|
198
|
+
"value → planB value\") and sameCount; planSettings reports the plan's own fields " +
|
|
199
|
+
"outside the sections. Use it to answer \"does the plan I rebuilt match the " +
|
|
200
|
+
"original?\" " +
|
|
201
|
+
"In either mode pathsOnly: true reports the paths that differ with no values, for " +
|
|
202
|
+
"a comparison whose values are private. " +
|
|
203
|
+
"Any value whose path names a credential (apiKey, token, password, ...) is reported as " +
|
|
204
|
+
"[REDACTED] rather than shown, and a value longer than 200 characters is truncated with its " +
|
|
205
|
+
"original length. Output is capped at " +
|
|
206
|
+
`${FIELD_CHANGE_CAP} field changes, with omittedFieldChanges and per-entry ` +
|
|
207
|
+
"omittedChanges counting the rest; a difference past the cap is still counted as changed.",
|
|
208
|
+
inputSchema: {
|
|
209
|
+
from: selectorSchema
|
|
210
|
+
.optional()
|
|
211
|
+
.describe("The baseline side, whose values are reported as before: one saved snapshot, selected like projectionlab_rollback does. Required to compare points in time. With planA and planB it is optional and names the one snapshot both plans are read from."),
|
|
212
|
+
to: selectorSchema
|
|
213
|
+
.optional()
|
|
214
|
+
.describe("The side whose values are reported as after: another saved snapshot, or omit it to compare against the live data read from ProjectionLab now. Points-in-time mode only; never combine it with planA or planB."),
|
|
215
|
+
planIdentifier: z
|
|
216
|
+
.string()
|
|
217
|
+
.trim()
|
|
218
|
+
.min(1)
|
|
219
|
+
.optional()
|
|
220
|
+
.describe("Report one plan only: plan name (case-insensitive; a partial match must identify exactly one plan) or exact plan ID. Points-in-time mode only; name the plans with planA and planB instead."),
|
|
221
|
+
planA: z
|
|
222
|
+
.string()
|
|
223
|
+
.trim()
|
|
224
|
+
.min(1)
|
|
225
|
+
.optional()
|
|
226
|
+
.describe("Compare two different plans instead of two points in time: the plan whose values are reported first, by plan name (case-insensitive; a partial match must identify exactly one plan) or exact plan ID. Supply planA and planB together, never with to or planIdentifier."),
|
|
227
|
+
planB: z
|
|
228
|
+
.string()
|
|
229
|
+
.trim()
|
|
230
|
+
.min(1)
|
|
231
|
+
.optional()
|
|
232
|
+
.describe("The second plan of a plan-to-plan comparison, whose values are reported second in each \"A → B\" pair. Supply it together with planA."),
|
|
233
|
+
section: z
|
|
234
|
+
.enum(PLAN_SECTIONS)
|
|
235
|
+
.optional()
|
|
236
|
+
.describe("Compare one plan section only, such as income or expenses. Plan settings outside the sections are still compared."),
|
|
237
|
+
pathsOnly: z
|
|
238
|
+
.boolean()
|
|
239
|
+
.optional()
|
|
240
|
+
.describe("Report the paths that differ with no values, for a comparison whose values are private. Works in both modes."),
|
|
241
|
+
},
|
|
242
|
+
annotations: {
|
|
243
|
+
readOnlyHint: true,
|
|
244
|
+
destructiveHint: false,
|
|
245
|
+
openWorldHint: false,
|
|
246
|
+
},
|
|
247
|
+
}, async (args) => {
|
|
248
|
+
try {
|
|
249
|
+
const request = requireMode(args);
|
|
250
|
+
if (request.mode === "plans") {
|
|
251
|
+
return await comparePlans({
|
|
252
|
+
planA: request.planA,
|
|
253
|
+
planB: request.planB,
|
|
254
|
+
from: args.from,
|
|
255
|
+
section: args.section,
|
|
256
|
+
pathsOnly: args.pathsOnly,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
// The points-in-time mode: one data set read at two moments.
|
|
260
|
+
const { from = {}, to, planIdentifier, section } = args;
|
|
261
|
+
const fromSnapshot = await resolveSnapshot(snapshots, from, "from snapshot");
|
|
262
|
+
const toSnapshot = to
|
|
263
|
+
? await resolveSnapshot(snapshots, to, "to snapshot")
|
|
264
|
+
: null;
|
|
265
|
+
// Against the live data every section the from snapshot holds is
|
|
266
|
+
// available; against another snapshot, only the sections both hold.
|
|
267
|
+
const comparedSections = fromSnapshot.sections.filter((name) => COMPARED_SECTIONS.includes(name) &&
|
|
268
|
+
(toSnapshot === null || toSnapshot.sections.includes(name)));
|
|
269
|
+
const notCompared = [];
|
|
270
|
+
const mentioned = new Set([
|
|
271
|
+
...fromSnapshot.sections,
|
|
272
|
+
...(toSnapshot ? toSnapshot.sections : []),
|
|
273
|
+
]);
|
|
274
|
+
for (const name of [...mentioned].sort()) {
|
|
275
|
+
if (comparedSections.includes(name))
|
|
276
|
+
continue;
|
|
277
|
+
if (!COMPARED_SECTIONS.includes(name)) {
|
|
278
|
+
notCompared.push({
|
|
279
|
+
section: name,
|
|
280
|
+
reason: "this tool does not compare that section",
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
else if (!fromSnapshot.sections.includes(name)) {
|
|
284
|
+
notCompared.push({
|
|
285
|
+
section: name,
|
|
286
|
+
reason: "the from snapshot does not hold it",
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
notCompared.push({
|
|
291
|
+
section: name,
|
|
292
|
+
reason: "the to snapshot does not hold it",
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (comparedSections.length === 0) {
|
|
297
|
+
throw new ProjectionLabError("INVALID_INPUT", toSnapshot
|
|
298
|
+
? `The two snapshots share no comparable data section; from holds [${fromSnapshot.sections.join(", ")}] and to holds [${toSnapshot.sections.join(", ")}]`
|
|
299
|
+
: `The from snapshot holds no comparable data section: [${fromSnapshot.sections.join(", ")}]`, "Compare a snapshot whose sections overlap, or diff against the live data by omitting to. Snapshots record the sections they saved; projectionlab_list_snapshots shows them");
|
|
300
|
+
}
|
|
301
|
+
// `to` is either a snapshot or the live export; one reader covers both.
|
|
302
|
+
const live = toSnapshot ? null : await bridge.exportData({ fresh: true });
|
|
303
|
+
const newerValue = (name) => toSnapshot ? toSnapshot.data[name] : getSection(live ?? {}, name);
|
|
304
|
+
// A section one side carries no data for is not a difference, it is
|
|
305
|
+
// nothing to compare: rollback skips such a section rather than
|
|
306
|
+
// restoring nothing, so diffing it would report every field as
|
|
307
|
+
// "(absent)" and describe a change no rollback would make.
|
|
308
|
+
const compared = [];
|
|
309
|
+
for (const name of comparedSections) {
|
|
310
|
+
const before = fromSnapshot.data[name];
|
|
311
|
+
const after = newerValue(name);
|
|
312
|
+
if (before === undefined) {
|
|
313
|
+
notCompared.push({
|
|
314
|
+
section: name,
|
|
315
|
+
reason: "the from snapshot holds no data for it",
|
|
316
|
+
});
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (after === undefined) {
|
|
320
|
+
notCompared.push({
|
|
321
|
+
section: name,
|
|
322
|
+
reason: toSnapshot
|
|
323
|
+
? "the to snapshot holds no data for it"
|
|
324
|
+
: "the live export returned no such section",
|
|
325
|
+
});
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
compared.push({ section: name, before, after });
|
|
329
|
+
}
|
|
330
|
+
if (compared.length === 0) {
|
|
331
|
+
throw new ProjectionLabError("INVALID_INPUT", `Both sides list [${comparedSections.join(", ")}] but no section carries data on both sides`, "Confirm what the snapshot saved with projectionlab_list_snapshots and read the current data with projectionlab_export_data");
|
|
332
|
+
}
|
|
333
|
+
const wantsPlans = compared.some((entry) => entry.section === "plans");
|
|
334
|
+
if (section && !wantsPlans) {
|
|
335
|
+
throw new ProjectionLabError("INVALID_INPUT", `section "${section}" narrows the plans comparison, but no plans data is being compared`, `Drop section, or pick a snapshot whose sections include plans; projectionlab_list_snapshots shows what each snapshot holds`);
|
|
336
|
+
}
|
|
337
|
+
const newerPlans = wantsPlans ? plansOf(newerValue("plans")) : [];
|
|
338
|
+
let planOnlyKey;
|
|
339
|
+
if (planIdentifier) {
|
|
340
|
+
if (!wantsPlans) {
|
|
341
|
+
throw new ProjectionLabError("INVALID_INPUT", `planIdentifier "${planIdentifier}" narrows the plans comparison, but no plans data is being compared`, "Drop planIdentifier, or pick a snapshot whose sections include plans; projectionlab_list_snapshots shows what each snapshot holds");
|
|
342
|
+
}
|
|
343
|
+
const olderPlans = plansOf(fromSnapshot.data.plans);
|
|
344
|
+
const beforeIndex = resolvePlanIndex(olderPlans, planIdentifier);
|
|
345
|
+
const afterIndex = resolvePlanIndex(newerPlans, planIdentifier);
|
|
346
|
+
const match = beforeIndex !== -1
|
|
347
|
+
? olderPlans[beforeIndex]
|
|
348
|
+
: afterIndex !== -1
|
|
349
|
+
? newerPlans[afterIndex]
|
|
350
|
+
: undefined;
|
|
351
|
+
if (match === undefined) {
|
|
352
|
+
const byKey = new Map(plansOf(fromSnapshot.data.plans, newerPlans).map((plan) => [
|
|
353
|
+
planKey(plan),
|
|
354
|
+
plan,
|
|
355
|
+
]));
|
|
356
|
+
throw planNotFoundError([...byKey.values()], planIdentifier);
|
|
357
|
+
}
|
|
358
|
+
planOnlyKey = planKey(match);
|
|
359
|
+
}
|
|
360
|
+
const budget = createDiffBudget({
|
|
361
|
+
pathsOnly: args.pathsOnly === true,
|
|
362
|
+
});
|
|
363
|
+
const sections = {};
|
|
364
|
+
let changed = false;
|
|
365
|
+
for (const entry of compared) {
|
|
366
|
+
const payload = diffSectionData(entry.section, entry.before, entry.after, budget, {
|
|
367
|
+
planSection: entry.section === "plans" ? section : undefined,
|
|
368
|
+
planOnlyKey: entry.section === "plans" ? planOnlyKey : undefined,
|
|
369
|
+
});
|
|
370
|
+
sections[entry.section] = payload;
|
|
371
|
+
if (payload.changed)
|
|
372
|
+
changed = true;
|
|
373
|
+
}
|
|
374
|
+
const result = {
|
|
375
|
+
from: describeSnapshot(fromSnapshot),
|
|
376
|
+
to: toSnapshot
|
|
377
|
+
? describeSnapshot(toSnapshot)
|
|
378
|
+
: {
|
|
379
|
+
source: "live",
|
|
380
|
+
readAt: new Date().toISOString(),
|
|
381
|
+
note: "read from ProjectionLab when this diff ran",
|
|
382
|
+
},
|
|
383
|
+
comparedSections: compared.map((entry) => entry.section),
|
|
384
|
+
...(notCompared.length > 0 ? { notCompared } : {}),
|
|
385
|
+
...(planIdentifier ? { planIdentifier } : {}),
|
|
386
|
+
...(section ? { section } : {}),
|
|
387
|
+
changed,
|
|
388
|
+
...(changed
|
|
389
|
+
? {}
|
|
390
|
+
: {
|
|
391
|
+
message: "No differences in the compared sections. Computed plan fields (lastUpdated, simKey, meta) are excluded, so a plan ProjectionLab only re-saved reads as unchanged.",
|
|
392
|
+
}),
|
|
393
|
+
sections,
|
|
394
|
+
fieldChangeCap: budget.fieldChangeCap,
|
|
395
|
+
...(budget.omittedFieldChanges > 0
|
|
396
|
+
? { omittedFieldChanges: budget.omittedFieldChanges }
|
|
397
|
+
: {}),
|
|
398
|
+
...(budget.omittedEntries > 0
|
|
399
|
+
? { omittedEntries: budget.omittedEntries }
|
|
400
|
+
: {}),
|
|
401
|
+
};
|
|
402
|
+
const json = toJson(redactSecrets(result, { byName: true }));
|
|
403
|
+
// A spilled result keeps the facts that name the comparison; the
|
|
404
|
+
// sections themselves stay in the file it points at.
|
|
405
|
+
const { sections: _sections, ...summary } = result;
|
|
406
|
+
return {
|
|
407
|
+
content: [
|
|
408
|
+
{ type: "text", text: await textOrFile(json, "diff", summary) },
|
|
409
|
+
],
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
return errorResult(err);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/tools/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EACL,eAAe,GAGhB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,OAAO,GACR,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,iBAAiB,GAGlB,MAAM,aAAa,CAAC;AAGrB,6EAA6E;AAC7E,MAAM,iBAAiB,GAAsB;IAC3C,OAAO;IACP,iBAAiB;IACjB,UAAU;IACV,UAAU;CACX,CAAC;AAEF,qFAAqF;AACrF,SAAS,UAAU,CAAC,IAA6B,EAAE,OAAe;IAChE,OAAO,OAAO,KAAK,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAkB;IAC1C,OAAO;QACL,MAAM,EAAE,UAAmB;QAC3B,UAAU,EAAE,QAAQ,CAAC,EAAE;QACvB,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;KAC5B,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,GAAG,MAAiB;IACnC,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,cAAc,GAAG,CAAC;KACrB,MAAM,CAAC;IACN,UAAU,EAAE,CAAC;SACV,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,yHAAyH,CAC1H;IACH,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,uMAAuM,CACxM;IACH,UAAU,EAAE,CAAC;SACV,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,iMAAiM,CAClM;CACJ,CAAC;KACD,MAAM,EAAE,CAAC;AAaZ;;;;;GAKG;AACH,SAAS,WAAW,CAClB,IAAc;IAEd,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACzD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,oCAAoC,EACpC,0LAA0L,CAC3L,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC7D,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,mDAAmD,OAAO,aAAa,EACvE,0GAA0G,CAC3G,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAU,EAAE,CAAC;QACrD,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,0CAA0C,IAAI,2CAA2C,EACzF,QAAQ,IAAI,oIAAoI,CACjJ,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACzC,CAAC;AAED,iFAAiF;AACjF,SAAS,qBAAqB,CAC5B,KAAgB,EAChB,UAAkB,EAClB,IAAuB;IAEvB,MAAM,QAAQ,GAAG,iBAAiB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;IACvD,OAAO,IAAI,kBAAkB,CAC3B,eAAe,EACf,GAAG,QAAQ,CAAC,OAAO,QAAQ,IAAI,EAAE,EACjC,GAAG,IAAI,0BAA0B,QAAQ,0BAA0B,QAAQ,CAAC,UAAU,EAAE,CACzF,CAAC;AACJ,CAAC;AAWD,yEAAyE;AACzE,SAAS,SAAS,CAAC,IAAa;IAC9B,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAY,CAAC;IACpC,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,SAAS,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,MAAiB,EACjB,MAAc,EACd,SAA0B;IAE1B,2EAA2E;IAC3E,KAAK,UAAU,gBAAgB,CAAC,IAA2B;QAIzD,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,SAAS,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;YACzE,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;YACnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,iBAAiB,QAAQ,CAAC,EAAE,uBAAuB,EACnD,mKAAmK,CACpK,CAAC;YACJ,CAAC;YACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjE,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,iCAAiC,EACjC,4EAA4E,CAC7E,CAAC;QACJ,CAAC;QACD,OAAO;YACL,KAAK;YACL,QAAQ,EAAE;gBACR,MAAM,EAAE,MAAe;gBACvB,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAChC,IAAI,EAAE,uDAAuD;aAC9D;SACF,CAAC;IACJ,CAAC;IAED,iFAAiF;IACjF,KAAK,UAAU,YAAY,CAAC,IAAiB;QAC3C,MAAM,MAAM,GAAG,gBAAgB,CAAC;YAC9B,SAAS,EAAE,IAAI,CAAC,SAAS,KAAK,IAAI;SACnC,CAAC,CAAC;QACH,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACnD,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;YAClB,MAAM,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACnD,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;YAClB,MAAM,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC1D,CAAC;QACD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,6CAA6C,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EACzE,mFAAmF,CACpF,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,YAAY,CACvB,KAAK,CAAC,MAAM,CAAY,EACxB,KAAK,CAAC,MAAM,CAAY,EACxB,MAAM,EACN,IAAI,CAAC,OAAO,CACb,CAAC;QACF,MAAM,MAAM,GAAG;YACb,IAAI,EAAE,OAAgB;YACtB,KAAK,EAAE,IAAI,CAAC,CAAC;YACb,KAAK,EAAE,IAAI,CAAC,CAAC;YACb,QAAQ;YACR,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClD,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,GAAG,CAAC,IAAI,CAAC,OAAO;gBACd,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,OAAO,EACL,6SAA6S;iBAChT,CAAC;YACN,YAAY,EAAE,IAAI,CAAC,QAAQ;YAC3B,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,GAAG,CAAC,IAAI,CAAC,eAAe;gBACtB,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,eAAe,EAAE;gBAC3C,CAAC,CAAC,EAAE,CAAC;YACP,cAAc,EAAE,MAAM,CAAC,cAAc;YACrC,GAAG,CAAC,MAAM,CAAC,mBAAmB,GAAG,CAAC;gBAChC,CAAC,CAAC,EAAE,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,EAAE;gBACrD,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,MAAM,CAAC,cAAc,GAAG,CAAC;gBAC3B,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE;gBAC3C,CAAC,CAAC,EAAE,CAAC;SACR,CAAC;QAEF,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7D,iEAAiE;QACjE,qDAAqD;QACrD,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,EAAE,GAAG,MAAM,CAAC;QACnD,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;aACzE;SACF,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,2DAA2D;QAClE,WAAW,EACT,wEAAwE;YACxE,8EAA8E;YAC9E,wBAAwB;YACxB,2EAA2E;YAC3E,2DAA2D;YAC3D,wCAAwC;YACxC,uFAAuF;YACvF,2BAA2B;YAC3B,0FAA0F;YAC1F,4DAA4D;YAC5D,uFAAuF;YACvF,uEAAuE;YACvE,+FAA+F;YAC/F,iDAAiD;YACjD,yFAAyF;YACzF,sCAAsC;YACtC,qFAAqF;YACrF,cAAc;YACd,2FAA2F;YAC3F,kEAAkE;YAClE,uEAAuE;YACvE,kFAAkF;YAClF,kFAAkF;YAClF,iFAAiF;YACjF,oFAAoF;YACpF,oFAAoF;YACpF,iFAAiF;YACjF,oFAAoF;YACpF,oFAAoF;YACpF,8DAA8D;YAC9D,kFAAkF;YAClF,mFAAmF;YACnF,gFAAgF;YAChF,mFAAmF;YACnF,6EAA6E;YAC7E,cAAc;YACd,mFAAmF;YACnF,yCAAyC;YACzC,wFAAwF;YACxF,6FAA6F;YAC7F,uCAAuC;YACvC,GAAG,gBAAgB,yDAAyD;YAC5E,0FAA0F;QAC5F,WAAW,EAAE;YACX,IAAI,EAAE,cAAc;iBACjB,QAAQ,EAAE;iBACV,QAAQ,CACP,qPAAqP,CACtP;YACH,EAAE,EAAE,cAAc;iBACf,QAAQ,EAAE;iBACV,QAAQ,CACP,+MAA+M,CAChN;YACH,cAAc,EAAE,CAAC;iBACd,MAAM,EAAE;iBACR,IAAI,EAAE;iBACN,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,EAAE;iBACV,QAAQ,CACP,6LAA6L,CAC9L;YACH,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,IAAI,EAAE;iBACN,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,EAAE;iBACV,QAAQ,CACP,0QAA0Q,CAC3Q;YACH,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,IAAI,EAAE;iBACN,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,EAAE;iBACV,QAAQ,CACP,uIAAuI,CACxI;YACH,OAAO,EAAE,CAAC;iBACP,IAAI,CAAC,aAAa,CAAC;iBACnB,QAAQ,EAAE;iBACV,QAAQ,CACP,mHAAmH,CACpH;YACH,SAAS,EAAE,CAAC;iBACT,OAAO,EAAE;iBACT,QAAQ,EAAE;iBACV,QAAQ,CACP,8GAA8G,CAC/G;SACJ;QACD,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;YAClC,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC7B,OAAO,MAAM,YAAY,CAAC;oBACxB,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,SAAS,EAAE,IAAI,CAAC,SAAS;iBAC1B,CAAC,CAAC;YACL,CAAC;YACD,6DAA6D;YAC7D,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;YACxD,MAAM,YAAY,GAAG,MAAM,eAAe,CACxC,SAAS,EACT,IAAI,EACJ,eAAe,CAChB,CAAC;YACF,MAAM,UAAU,GAAG,EAAE;gBACnB,CAAC,CAAC,MAAM,eAAe,CAAC,SAAS,EAAE,EAAE,EAAE,aAAa,CAAC;gBACrD,CAAC,CAAC,IAAI,CAAC;YAET,iEAAiE;YACjE,oEAAoE;YACpE,MAAM,gBAAgB,GAAG,YAAY,CAAC,QAAQ,CAAC,MAAM,CACnD,CAAC,IAAI,EAAE,EAAE,CACP,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAChC,CAAC,UAAU,KAAK,IAAI,IAAI,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAC9D,CAAC;YAEF,MAAM,WAAW,GAA0C,EAAE,CAAC;YAC9D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;gBACxB,GAAG,YAAY,CAAC,QAAQ;gBACxB,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;aAC3C,CAAC,CAAC;YACH,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;gBACzC,IAAI,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAC9C,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBACtC,WAAW,CAAC,IAAI,CAAC;wBACf,OAAO,EAAE,IAAI;wBACb,MAAM,EAAE,yCAAyC;qBAClD,CAAC,CAAC;gBACL,CAAC;qBAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBACjD,WAAW,CAAC,IAAI,CAAC;wBACf,OAAO,EAAE,IAAI;wBACb,MAAM,EAAE,oCAAoC;qBAC7C,CAAC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACN,WAAW,CAAC,IAAI,CAAC;wBACf,OAAO,EAAE,IAAI;wBACb,MAAM,EAAE,kCAAkC;qBAC3C,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAED,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,UAAU;oBACR,CAAC,CAAC,mEAAmE,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;oBACzJ,CAAC,CAAC,wDAAwD,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAC/F,4KAA4K,CAC7K,CAAC;YACJ,CAAC;YAED,wEAAwE;YACxE,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1E,MAAM,UAAU,GAAG,CAAC,IAAY,EAAW,EAAE,CAC3C,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;YAEpE,oEAAoE;YACpE,gEAAgE;YAChE,+DAA+D;YAC/D,2DAA2D;YAC3D,MAAM,QAAQ,GAIR,EAAE,CAAC;YACT,KAAK,MAAM,IAAI,IAAI,gBAAgB,EAAE,CAAC;gBACpC,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACvC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;gBAC/B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;oBACzB,WAAW,CAAC,IAAI,CAAC;wBACf,OAAO,EAAE,IAAI;wBACb,MAAM,EAAE,wCAAwC;qBACjD,CAAC,CAAC;oBACH,SAAS;gBACX,CAAC;gBACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;oBACxB,WAAW,CAAC,IAAI,CAAC;wBACf,OAAO,EAAE,IAAI;wBACb,MAAM,EAAE,UAAU;4BAChB,CAAC,CAAC,sCAAsC;4BACxC,CAAC,CAAC,0CAA0C;qBAC/C,CAAC,CAAC;oBACH,SAAS;gBACX,CAAC;gBACD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;YAClD,CAAC;YAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,oBAAoB,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,6CAA6C,EAC5F,4HAA4H,CAC7H,CAAC;YACJ,CAAC;YAED,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;YACvE,IAAI,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;gBAC3B,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,YAAY,OAAO,qEAAqE,EACxF,4HAA4H,CAC7H,CAAC;YACJ,CAAC;YAED,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAElE,IAAI,WAA+B,CAAC;YACpC,IAAI,cAAc,EAAE,CAAC;gBACnB,IAAI,CAAC,UAAU,EAAE,CAAC;oBAChB,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,mBAAmB,cAAc,qEAAqE,EACtG,mIAAmI,CACpI,CAAC;gBACJ,CAAC;gBACD,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACpD,MAAM,WAAW,GAAG,gBAAgB,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;gBACjE,MAAM,UAAU,GAAG,gBAAgB,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;gBAChE,MAAM,KAAK,GACT,WAAW,KAAK,CAAC,CAAC;oBAChB,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC;oBACzB,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC;wBACjB,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;wBACxB,CAAC,CAAC,SAAS,CAAC;gBAClB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;oBACxB,MAAM,KAAK,GAAG,IAAI,GAAG,CACnB,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;wBACzD,OAAO,CAAC,IAAI,CAAC;wBACb,IAAI;qBACL,CAAC,CACH,CAAC;oBACF,MAAM,iBAAiB,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC;gBAC/D,CAAC;gBACD,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;YAC/B,CAAC;YAED,MAAM,MAAM,GAAG,gBAAgB,CAAC;gBAC9B,SAAS,EAAE,IAAI,CAAC,SAAS,KAAK,IAAI;aACnC,CAAC,CAAC;YACH,MAAM,QAAQ,GAA4B,EAAE,CAAC;YAC7C,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;gBAC7B,MAAM,OAAO,GAAG,eAAe,CAC7B,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,KAAK,EACX,MAAM,EACN;oBACE,WAAW,EACT,KAAK,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;oBACjD,WAAW,EACT,KAAK,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;iBACtD,CACF,CAAC;gBACF,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;gBAClC,IAAI,OAAO,CAAC,OAAO;oBAAE,OAAO,GAAG,IAAI,CAAC;YACtC,CAAC;YAED,MAAM,MAAM,GAAG;gBACb,IAAI,EAAE,gBAAgB,CAAC,YAAY,CAAC;gBACpC,EAAE,EAAE,UAAU;oBACZ,CAAC,CAAC,gBAAgB,CAAC,UAAU,CAAC;oBAC9B,CAAC,CAAC;wBACE,MAAM,EAAE,MAAe;wBACvB,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;wBAChC,IAAI,EAAE,4CAA4C;qBACnD;gBACL,gBAAgB,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC;gBACxD,GAAG,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClD,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7C,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/B,OAAO;gBACP,GAAG,CAAC,OAAO;oBACT,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC;wBACE,OAAO,EACL,mKAAmK;qBACtK,CAAC;gBACN,QAAQ;gBACR,cAAc,EAAE,MAAM,CAAC,cAAc;gBACrC,GAAG,CAAC,MAAM,CAAC,mBAAmB,GAAG,CAAC;oBAChC,CAAC,CAAC,EAAE,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,EAAE;oBACrD,CAAC,CAAC,EAAE,CAAC;gBACP,GAAG,CAAC,MAAM,CAAC,cAAc,GAAG,CAAC;oBAC3B,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE;oBAC3C,CAAC,CAAC,EAAE,CAAC;aACR,CAAC;YAEF,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAC7D,iEAAiE;YACjE,qDAAqD;YACrD,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,EAAE,GAAG,MAAM,CAAC;YACnD,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;iBAChE;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { annotateAddressableValues } from "../dataModel.js";
|
|
2
3
|
import { currentFinanceAccountNotFoundError, findCurrentFinanceEntry, requireCurrentFinances, } from "../currentFinances.js";
|
|
3
4
|
import { ProjectionLabError, errorResult } from "../errors.js";
|
|
4
5
|
import { toJson } from "../json.js";
|
|
5
6
|
import { textOrFile } from "../largeOutput.js";
|
|
6
7
|
import { listAddressableValues } from "../pathPatch.js";
|
|
7
8
|
import { redactSecrets } from "../redact.js";
|
|
9
|
+
import { buildRealEstateBlock, withRealEstateBlocks } from "../realEstate.js";
|
|
8
10
|
export function registerGetCurrentFinancesTool(server, bridge) {
|
|
9
11
|
server.registerTool("projectionlab_get_current_finances", {
|
|
10
12
|
title: "Get Current Finances",
|
|
11
13
|
description: "Returns just the Current Finances section (account balances, debts, real estate) " +
|
|
12
14
|
"without plan data. Narrow to one existing account by exact ID, and set listPaths=true " +
|
|
13
|
-
"to discover typed dot paths for projectionlab_update_account pathChanges."
|
|
15
|
+
"to discover typed dot paths for projectionlab_update_account pathChanges. " +
|
|
16
|
+
"Real-estate assets (assets entries with type \"real-estate\") carry a realEstate block " +
|
|
17
|
+
"that labels the fields ProjectionLab stores confusingly: marketValue is the stored " +
|
|
18
|
+
"amount, loanBalance is the remaining loan balance (never the property's value; an " +
|
|
19
|
+
"absent balance reads as 0), and netEquity is marketValue minus loanBalance, an " +
|
|
20
|
+
"arithmetic on stored fields and not ProjectionLab's own net-worth figure, which can " +
|
|
21
|
+
"depend on excludeLoanFromLNW. The block also carries paymentMethod, classification and " +
|
|
22
|
+
"excludeLoanFromLNW where present, loan terms (apr, interestType, monthlyPayment, " +
|
|
23
|
+
"compounding) when paymentMethod is \"financed\", and carrying costs (propertyTax, " +
|
|
24
|
+
"insurance, maintenance, monthlyHOA, appreciation) where present. Values come from the " +
|
|
25
|
+
"Current Finances record only, never from plan-modeled asset events. Only the amount " +
|
|
26
|
+
"and balance labels are verified; the other labels name stored fields whose UI meaning " +
|
|
27
|
+
"is not yet confirmed. Raw fields are kept unchanged alongside the block.",
|
|
14
28
|
inputSchema: {
|
|
15
29
|
accountId: z
|
|
16
30
|
.string()
|
|
@@ -33,24 +47,33 @@ export function registerGetCurrentFinancesTool(server, bridge) {
|
|
|
33
47
|
if (listPaths && accountId === undefined) {
|
|
34
48
|
throw new ProjectionLabError("INVALID_INPUT", "listPaths requires accountId", "Supply the exact account ID returned by projectionlab_get_current_finances");
|
|
35
49
|
}
|
|
36
|
-
let output
|
|
50
|
+
let output;
|
|
37
51
|
let suffix = "all";
|
|
38
52
|
if (accountId !== undefined) {
|
|
39
53
|
const entry = findCurrentFinanceEntry(today, accountId);
|
|
40
54
|
if (!entry) {
|
|
41
55
|
throw currentFinanceAccountNotFoundError(today, accountId);
|
|
42
56
|
}
|
|
57
|
+
const realEstate = !listPaths && entry.collection === "assets"
|
|
58
|
+
? buildRealEstateBlock(entry.account)
|
|
59
|
+
: null;
|
|
43
60
|
output = {
|
|
44
61
|
accountId,
|
|
45
62
|
accountName: entry.account.name,
|
|
46
63
|
collection: entry.collection,
|
|
47
64
|
...(listPaths ? { listPaths: true } : {}),
|
|
48
65
|
data: listPaths
|
|
49
|
-
? listAddressableValues(entry.account)
|
|
50
|
-
:
|
|
66
|
+
? annotateAddressableValues(entry.collection, listAddressableValues(entry.account))
|
|
67
|
+
: {
|
|
68
|
+
...entry.account,
|
|
69
|
+
...(realEstate ? { realEstate } : {}),
|
|
70
|
+
},
|
|
51
71
|
};
|
|
52
72
|
suffix = accountId;
|
|
53
73
|
}
|
|
74
|
+
else {
|
|
75
|
+
output = withRealEstateBlocks(today);
|
|
76
|
+
}
|
|
54
77
|
return {
|
|
55
78
|
content: [
|
|
56
79
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getCurrentFinances.js","sourceRoot":"","sources":["../../src/tools/getCurrentFinances.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EACL,kCAAkC,EAClC,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"getCurrentFinances.js","sourceRoot":"","sources":["../../src/tools/getCurrentFinances.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/getPlan.js
CHANGED
|
@@ -3,20 +3,20 @@ import { errorResult } from "../errors.js";
|
|
|
3
3
|
import { toJson } from "../json.js";
|
|
4
4
|
import { textOrFile } from "../largeOutput.js";
|
|
5
5
|
import { redactSecrets } from "../redact.js";
|
|
6
|
-
import {
|
|
6
|
+
import { resolvePlan, getEvents, planNotFoundError, requirePlans, } from "../plans.js";
|
|
7
7
|
export function registerGetPlanTool(server, bridge) {
|
|
8
8
|
server.registerTool("projectionlab_get_plan", {
|
|
9
9
|
title: "Get Plan",
|
|
10
10
|
description: "Returns a single plan by name or ID. Much smaller than export_data. " +
|
|
11
11
|
"Use this for deep analysis of a specific plan; prefer " +
|
|
12
12
|
"projectionlab_get_plan_section when only one aspect is needed. " +
|
|
13
|
-
"Accepts a plan name (case-insensitive partial match) or exact plan ID.",
|
|
13
|
+
"Accepts a plan name (case-insensitive; a partial match must identify exactly one plan) or exact plan ID.",
|
|
14
14
|
inputSchema: {
|
|
15
15
|
planIdentifier: z
|
|
16
16
|
.string()
|
|
17
17
|
.trim()
|
|
18
18
|
.min(1)
|
|
19
|
-
.describe("Plan name (case-insensitive
|
|
19
|
+
.describe("Plan name (case-insensitive; a partial match must identify exactly one plan) or exact plan ID"),
|
|
20
20
|
},
|
|
21
21
|
annotations: {
|
|
22
22
|
readOnlyHint: true,
|
|
@@ -27,7 +27,7 @@ export function registerGetPlanTool(server, bridge) {
|
|
|
27
27
|
try {
|
|
28
28
|
const data = await bridge.exportData();
|
|
29
29
|
const plans = requirePlans(data);
|
|
30
|
-
const plan =
|
|
30
|
+
const plan = resolvePlan(plans, planIdentifier);
|
|
31
31
|
if (!plan)
|
|
32
32
|
throw planNotFoundError(plans, planIdentifier);
|
|
33
33
|
const header = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getPlan.js","sourceRoot":"","sources":["../../src/tools/getPlan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"getPlan.js","sourceRoot":"","sources":["../../src/tools/getPlan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EACL,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,YAAY,GACb,MAAM,aAAa,CAAC;AAErB,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,MAAc;IACnE,MAAM,CAAC,YAAY,CACjB,wBAAwB,EACxB;QACE,KAAK,EAAE,UAAU;QACjB,WAAW,EACT,sEAAsE;YACtE,wDAAwD;YACxD,iEAAiE;YACjE,0GAA0G;QAC5G,WAAW,EAAE;YACX,cAAc,EAAE,CAAC;iBACd,MAAM,EAAE;iBACR,IAAI,EAAE;iBACN,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CACP,+FAA+F,CAChG;SACJ;QACD,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE;QAC3B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YACjC,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;YAChD,IAAI,CAAC,IAAI;gBAAE,MAAM,iBAAiB,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;YAE1D,MAAM,MAAM,GAAG;gBACb,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,EAAE,EAAE,IAAI,CAAC,EAAE;gBACX,cAAc,EAAE,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM;gBACjD,gBAAgB,EAAE,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM;gBAC/C,iBAAiB,EAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM;gBAClD,iBAAiB,EAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM;gBAClD,eAAe,EAAE,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM;gBAC9C,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE;aACnC,CAAC;YAEF,MAAM,MAAM,GAAG,iBAAiB,MAAM,CAAC,MAAM,CAAC,wBAAwB,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YAEtH,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,MAAM,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;qBAChE;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { annotateAddressableValues } from "../dataModel.js";
|
|
2
3
|
import { ProjectionLabError, errorResult } from "../errors.js";
|
|
3
4
|
import { toJson } from "../json.js";
|
|
4
5
|
import { textOrFile } from "../largeOutput.js";
|
|
5
6
|
import { listAddressableValues } from "../pathPatch.js";
|
|
6
7
|
import { redactSecrets } from "../redact.js";
|
|
7
|
-
import { PLAN_SECTIONS, eventLabel,
|
|
8
|
+
import { PLAN_SECTIONS, eventLabel, resolveEventIndex, resolvePlan, getEvents, planNotFoundError, requirePlans, } from "../plans.js";
|
|
8
9
|
/** Resolve a dot-path like "events[3].rothConversions" or "events.3.name". */
|
|
9
10
|
function resolvePath(root, path) {
|
|
10
11
|
const parts = path
|
|
@@ -40,7 +41,7 @@ export function registerGetPlanSectionTool(server, bridge) {
|
|
|
40
41
|
.string()
|
|
41
42
|
.trim()
|
|
42
43
|
.min(1)
|
|
43
|
-
.describe("Plan name (case-insensitive
|
|
44
|
+
.describe("Plan name (case-insensitive; a partial match must identify exactly one plan) or exact plan ID"),
|
|
44
45
|
section: z
|
|
45
46
|
.enum(PLAN_SECTIONS)
|
|
46
47
|
.describe("Which section of the plan to return"),
|
|
@@ -49,7 +50,7 @@ export function registerGetPlanSectionTool(server, bridge) {
|
|
|
49
50
|
.trim()
|
|
50
51
|
.min(1)
|
|
51
52
|
.optional()
|
|
52
|
-
.describe("Return only the event whose name (case-insensitive
|
|
53
|
+
.describe("Return only the event whose name (case-insensitive; a partial match must identify exactly one event) or ID matches. " +
|
|
53
54
|
"Only valid for event-based sections (income, expenses, accounts, assets, milestones, priorities)."),
|
|
54
55
|
path: z
|
|
55
56
|
.string()
|
|
@@ -70,7 +71,7 @@ export function registerGetPlanSectionTool(server, bridge) {
|
|
|
70
71
|
try {
|
|
71
72
|
const data = await bridge.exportData();
|
|
72
73
|
const plans = requirePlans(data);
|
|
73
|
-
const plan =
|
|
74
|
+
const plan = resolvePlan(plans, planIdentifier);
|
|
74
75
|
if (!plan)
|
|
75
76
|
throw planNotFoundError(plans, planIdentifier);
|
|
76
77
|
let sectionData = plan[section];
|
|
@@ -79,7 +80,7 @@ export function registerGetPlanSectionTool(server, bridge) {
|
|
|
79
80
|
}
|
|
80
81
|
if (eventIdentifier !== undefined) {
|
|
81
82
|
const events = getEvents(sectionData);
|
|
82
|
-
const index =
|
|
83
|
+
const index = resolveEventIndex(events, eventIdentifier);
|
|
83
84
|
const match = index === -1 ? undefined : events[index];
|
|
84
85
|
if (!match) {
|
|
85
86
|
throw new ProjectionLabError("INVALID_INPUT", `No event matching "${eventIdentifier}" in section "${section}" of plan "${plan.name}"`, `Available events: ${events.map(eventLabel).sort().join(", ") || "(none)"}`);
|
|
@@ -94,7 +95,7 @@ export function registerGetPlanSectionTool(server, bridge) {
|
|
|
94
95
|
sectionData = resolved;
|
|
95
96
|
}
|
|
96
97
|
if (listPaths) {
|
|
97
|
-
sectionData = listAddressableValues(sectionData);
|
|
98
|
+
sectionData = annotateAddressableValues(section, listAddressableValues(sectionData));
|
|
98
99
|
}
|
|
99
100
|
const output = {
|
|
100
101
|
planName: plan.name,
|