trainbud 0.5.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/.env.example +11 -0
- package/CHANGELOG.md +675 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +182 -0
- package/README.md +322 -0
- package/dist/appDb.d.ts +83 -0
- package/dist/appDb.d.ts.map +1 -0
- package/dist/appDb.js +239 -0
- package/dist/appDb.js.map +1 -0
- package/dist/check.d.ts +12 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +230 -0
- package/dist/check.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +457 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +61 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +241 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard.d.ts +8 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +1004 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/dashboardCharts.d.ts +99 -0
- package/dist/dashboardCharts.d.ts.map +1 -0
- package/dist/dashboardCharts.js +317 -0
- package/dist/dashboardCharts.js.map +1 -0
- package/dist/dashboardData.d.ts +89 -0
- package/dist/dashboardData.d.ts.map +1 -0
- package/dist/dashboardData.js +228 -0
- package/dist/dashboardData.js.map +1 -0
- package/dist/detect/baseline.d.ts +17 -0
- package/dist/detect/baseline.d.ts.map +1 -0
- package/dist/detect/baseline.js +62 -0
- package/dist/detect/baseline.js.map +1 -0
- package/dist/detect/countdown.d.ts +30 -0
- package/dist/detect/countdown.d.ts.map +1 -0
- package/dist/detect/countdown.js +79 -0
- package/dist/detect/countdown.js.map +1 -0
- package/dist/detect/detectors.d.ts +20 -0
- package/dist/detect/detectors.d.ts.map +1 -0
- package/dist/detect/detectors.js +291 -0
- package/dist/detect/detectors.js.map +1 -0
- package/dist/detect/findings.d.ts +29 -0
- package/dist/detect/findings.d.ts.map +1 -0
- package/dist/detect/findings.js +2 -0
- package/dist/detect/findings.js.map +1 -0
- package/dist/detect/forecast.d.ts +33 -0
- package/dist/detect/forecast.d.ts.map +1 -0
- package/dist/detect/forecast.js +122 -0
- package/dist/detect/forecast.js.map +1 -0
- package/dist/detect/index.d.ts +50 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/detect/index.js +114 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/detect/sleepQuality.d.ts +21 -0
- package/dist/detect/sleepQuality.d.ts.map +1 -0
- package/dist/detect/sleepQuality.js +164 -0
- package/dist/detect/sleepQuality.js.map +1 -0
- package/dist/detect/trimp.d.ts +22 -0
- package/dist/detect/trimp.d.ts.map +1 -0
- package/dist/detect/trimp.js +77 -0
- package/dist/detect/trimp.js.map +1 -0
- package/dist/detect/week.d.ts +31 -0
- package/dist/detect/week.d.ts.map +1 -0
- package/dist/detect/week.js +184 -0
- package/dist/detect/week.js.map +1 -0
- package/dist/garmin/auth.d.ts +10 -0
- package/dist/garmin/auth.d.ts.map +1 -0
- package/dist/garmin/auth.js +81 -0
- package/dist/garmin/auth.js.map +1 -0
- package/dist/garmin/cache.d.ts +42 -0
- package/dist/garmin/cache.d.ts.map +1 -0
- package/dist/garmin/cache.js +141 -0
- package/dist/garmin/cache.js.map +1 -0
- package/dist/garmin/client.d.ts +22 -0
- package/dist/garmin/client.d.ts.map +1 -0
- package/dist/garmin/client.js +237 -0
- package/dist/garmin/client.js.map +1 -0
- package/dist/garmin/daily.d.ts +24 -0
- package/dist/garmin/daily.d.ts.map +1 -0
- package/dist/garmin/daily.js +96 -0
- package/dist/garmin/daily.js.map +1 -0
- package/dist/garmin/garminApiTypes.d.ts +70 -0
- package/dist/garmin/garminApiTypes.d.ts.map +1 -0
- package/dist/garmin/garminApiTypes.js +3 -0
- package/dist/garmin/garminApiTypes.js.map +1 -0
- package/dist/garmin/garminConnect.d.ts +17 -0
- package/dist/garmin/garminConnect.d.ts.map +1 -0
- package/dist/garmin/garminConnect.js +5 -0
- package/dist/garmin/garminConnect.js.map +1 -0
- package/dist/garmin/partial.d.ts +32 -0
- package/dist/garmin/partial.d.ts.map +1 -0
- package/dist/garmin/partial.js +38 -0
- package/dist/garmin/partial.js.map +1 -0
- package/dist/garmin/rawApi.d.ts +59 -0
- package/dist/garmin/rawApi.d.ts.map +1 -0
- package/dist/garmin/rawApi.js +113 -0
- package/dist/garmin/rawApi.js.map +1 -0
- package/dist/garmin/types.d.ts +85 -0
- package/dist/garmin/types.d.ts.map +1 -0
- package/dist/garmin/types.js +11 -0
- package/dist/garmin/types.js.map +1 -0
- package/dist/history/capture.d.ts +12 -0
- package/dist/history/capture.d.ts.map +1 -0
- package/dist/history/capture.js +63 -0
- package/dist/history/capture.js.map +1 -0
- package/dist/history/context.d.ts +51 -0
- package/dist/history/context.d.ts.map +1 -0
- package/dist/history/context.js +122 -0
- package/dist/history/context.js.map +1 -0
- package/dist/history/fallback.d.ts +72 -0
- package/dist/history/fallback.d.ts.map +1 -0
- package/dist/history/fallback.js +185 -0
- package/dist/history/fallback.js.map +1 -0
- package/dist/history/ingest.d.ts +44 -0
- package/dist/history/ingest.d.ts.map +1 -0
- package/dist/history/ingest.js +347 -0
- package/dist/history/ingest.js.map +1 -0
- package/dist/history/scheduler.d.ts +20 -0
- package/dist/history/scheduler.d.ts.map +1 -0
- package/dist/history/scheduler.js +54 -0
- package/dist/history/scheduler.js.map +1 -0
- package/dist/history/schema.d.ts +41 -0
- package/dist/history/schema.d.ts.map +1 -0
- package/dist/history/schema.js +91 -0
- package/dist/history/schema.js.map +1 -0
- package/dist/history/store.d.ts +112 -0
- package/dist/history/store.d.ts.map +1 -0
- package/dist/history/store.js +282 -0
- package/dist/history/store.js.map +1 -0
- package/dist/httpServer.d.ts +55 -0
- package/dist/httpServer.d.ts.map +1 -0
- package/dist/httpServer.js +1027 -0
- package/dist/httpServer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/mcpConfig.d.ts +56 -0
- package/dist/mcpConfig.d.ts.map +1 -0
- package/dist/mcpConfig.js +106 -0
- package/dist/mcpConfig.js.map +1 -0
- package/dist/pairApi.d.ts +14 -0
- package/dist/pairApi.d.ts.map +1 -0
- package/dist/pairApi.js +38 -0
- package/dist/pairApi.js.map +1 -0
- package/dist/paths.d.ts +45 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +105 -0
- package/dist/paths.js.map +1 -0
- package/dist/profile.d.ts +123 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +327 -0
- package/dist/profile.js.map +1 -0
- package/dist/promptApi.d.ts +43 -0
- package/dist/promptApi.d.ts.map +1 -0
- package/dist/promptApi.js +328 -0
- package/dist/promptApi.js.map +1 -0
- package/dist/promptSuggestions.d.ts +16 -0
- package/dist/promptSuggestions.d.ts.map +1 -0
- package/dist/promptSuggestions.js +141 -0
- package/dist/promptSuggestions.js.map +1 -0
- package/dist/selfTest.d.ts +53 -0
- package/dist/selfTest.d.ts.map +1 -0
- package/dist/selfTest.js +225 -0
- package/dist/selfTest.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +67 -0
- package/dist/server.js.map +1 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +210 -0
- package/dist/setup.js.map +1 -0
- package/dist/toolErrors.d.ts +2 -0
- package/dist/toolErrors.d.ts.map +1 -0
- package/dist/toolErrors.js +18 -0
- package/dist/toolErrors.js.map +1 -0
- package/dist/tools/activities.d.ts +18 -0
- package/dist/tools/activities.d.ts.map +1 -0
- package/dist/tools/activities.js +182 -0
- package/dist/tools/activities.js.map +1 -0
- package/dist/tools/bodyComposition.d.ts +10 -0
- package/dist/tools/bodyComposition.d.ts.map +1 -0
- package/dist/tools/bodyComposition.js +139 -0
- package/dist/tools/bodyComposition.js.map +1 -0
- package/dist/tools/context.d.ts +8 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +148 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/findings.d.ts +7 -0
- package/dist/tools/findings.d.ts.map +1 -0
- package/dist/tools/findings.js +51 -0
- package/dist/tools/findings.js.map +1 -0
- package/dist/tools/heartRate.d.ts +10 -0
- package/dist/tools/heartRate.d.ts.map +1 -0
- package/dist/tools/heartRate.js +105 -0
- package/dist/tools/heartRate.js.map +1 -0
- package/dist/tools/index.d.ts +66 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +101 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/payloads.d.ts +148 -0
- package/dist/tools/payloads.d.ts.map +1 -0
- package/dist/tools/payloads.js +2 -0
- package/dist/tools/payloads.js.map +1 -0
- package/dist/tools/recovery.d.ts +31 -0
- package/dist/tools/recovery.d.ts.map +1 -0
- package/dist/tools/recovery.js +350 -0
- package/dist/tools/recovery.js.map +1 -0
- package/dist/tools/sleep.d.ts +10 -0
- package/dist/tools/sleep.d.ts.map +1 -0
- package/dist/tools/sleep.js +124 -0
- package/dist/tools/sleep.js.map +1 -0
- package/dist/tools/stress.d.ts +11 -0
- package/dist/tools/stress.d.ts.map +1 -0
- package/dist/tools/stress.js +108 -0
- package/dist/tools/stress.js.map +1 -0
- package/dist/tools/trainingInsights.d.ts +15 -0
- package/dist/tools/trainingInsights.d.ts.map +1 -0
- package/dist/tools/trainingInsights.js +79 -0
- package/dist/tools/trainingInsights.js.map +1 -0
- package/dist/tools/types.d.ts +12 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/vo2Max.d.ts +11 -0
- package/dist/tools/vo2Max.d.ts.map +1 -0
- package/dist/tools/vo2Max.js +110 -0
- package/dist/tools/vo2Max.js.map +1 -0
- package/dist/tools/week.d.ts +12 -0
- package/dist/tools/week.d.ts.map +1 -0
- package/dist/tools/week.js +74 -0
- package/dist/tools/week.js.map +1 -0
- package/dist/usage.d.ts +104 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +247 -0
- package/dist/usage.js.map +1 -0
- package/dist/utils/batch.d.ts +2 -0
- package/dist/utils/batch.d.ts.map +1 -0
- package/dist/utils/batch.js +18 -0
- package/dist/utils/batch.js.map +1 -0
- package/dist/utils/helpers.d.ts +32 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +188 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/jsonFile.d.ts +19 -0
- package/dist/utils/jsonFile.d.ts.map +1 -0
- package/dist/utils/jsonFile.js +24 -0
- package/dist/utils/jsonFile.js.map +1 -0
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +97 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/secretFile.d.ts +15 -0
- package/dist/utils/secretFile.d.ts.map +1 -0
- package/dist/utils/secretFile.js +47 -0
- package/dist/utils/secretFile.js.map +1 -0
- package/dist/utils/stdio.d.ts +10 -0
- package/dist/utils/stdio.d.ts.map +1 -0
- package/dist/utils/stdio.js +70 -0
- package/dist/utils/stdio.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/dist/watchApi.d.ts +264 -0
- package/dist/watchApi.d.ts.map +1 -0
- package/dist/watchApi.js +333 -0
- package/dist/watchApi.js.map +1 -0
- package/package.json +77 -0
package/dist/profile.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getSetting, setSetting } from "./appDb.js";
|
|
3
|
+
// The Ask menu's own limits. Imported rather than restated: a second copy of
|
|
4
|
+
// "five" and "32" here would drift from the file that decides what the watch
|
|
5
|
+
// draws, and the drift would look like a working setting.
|
|
6
|
+
import { PROMPT_MAX_LENGTH, PROMPT_SLOTS } from "./promptSuggestions.js";
|
|
7
|
+
// SECTION: Profile — everything TrainBud knows about you that Garmin does not
|
|
8
|
+
//
|
|
9
|
+
// Garmin measures you. It has no idea what you are training for, which units
|
|
10
|
+
// you think in, which screens you care about, or what "a bad night" means for
|
|
11
|
+
// your body rather than for the population. All of that lives here, in one
|
|
12
|
+
// row of app.db, and every surface reads it from this module.
|
|
13
|
+
//
|
|
14
|
+
// Two rules hold this together.
|
|
15
|
+
//
|
|
16
|
+
// A corrupt or partial row must never take the server down. `getProfile` is on
|
|
17
|
+
// the path of every watch fetch, every dashboard paint and several CLI
|
|
18
|
+
// commands; a JSON parse error in a settings row is not a reason for any of
|
|
19
|
+
// them to fail. Anything unreadable falls back to its default, field by field,
|
|
20
|
+
// and the readable neighbours survive.
|
|
21
|
+
//
|
|
22
|
+
// Thresholds are resolved HERE and nowhere else. The watch used to carry its
|
|
23
|
+
// own copies of these bands in Monkey C -- `recoveryColor`, `sleepColor`,
|
|
24
|
+
// `stressColor`, `heartRateColor` -- which meant the number on the wrist and
|
|
25
|
+
// the number in the browser could disagree about whether the same score was
|
|
26
|
+
// good, and personalising the bands would have made that certain. The server
|
|
27
|
+
// sends a resolved state; the watch maps state to colour and knows nothing
|
|
28
|
+
// about where the line falls.
|
|
29
|
+
/** The cards the carousel can show, in shipping order. */
|
|
30
|
+
export const CARD_IDS = [
|
|
31
|
+
"today",
|
|
32
|
+
"ask",
|
|
33
|
+
"insight",
|
|
34
|
+
"week",
|
|
35
|
+
"overview",
|
|
36
|
+
"recovery",
|
|
37
|
+
"sleep",
|
|
38
|
+
"activity",
|
|
39
|
+
"stress",
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Metrics that can be graded, and which direction is better.
|
|
43
|
+
*
|
|
44
|
+
* `restingHrDelta` is deliberately a delta rather than a rate. 58 bpm is
|
|
45
|
+
* unremarkable for one person and a warning for another; the distance from
|
|
46
|
+
* that person's own median is the only version of this number that means
|
|
47
|
+
* anything, and the detectors already compute the median.
|
|
48
|
+
*/
|
|
49
|
+
export const GRADED_METRICS = {
|
|
50
|
+
recovery: "higher",
|
|
51
|
+
sleepHours: "higher",
|
|
52
|
+
stress: "lower",
|
|
53
|
+
restingHrDelta: "lower",
|
|
54
|
+
};
|
|
55
|
+
const bandSchema = z.object({
|
|
56
|
+
good: z.number(),
|
|
57
|
+
caution: z.number(),
|
|
58
|
+
});
|
|
59
|
+
const thresholdsSchema = z.object({
|
|
60
|
+
recovery: bandSchema,
|
|
61
|
+
sleepHours: bandSchema,
|
|
62
|
+
stress: bandSchema,
|
|
63
|
+
restingHrDelta: bandSchema,
|
|
64
|
+
});
|
|
65
|
+
const profileSchema = z.object({
|
|
66
|
+
displayName: z.string().max(60).nullable(),
|
|
67
|
+
units: z.enum(["metric", "imperial"]),
|
|
68
|
+
timezone: z.string().max(64).nullable(),
|
|
69
|
+
primarySport: z
|
|
70
|
+
.enum(["running", "cycling", "swimming", "strength", "mixed"])
|
|
71
|
+
.nullable(),
|
|
72
|
+
weeklyGoal: z.object({
|
|
73
|
+
sessions: z.number().int().min(0).max(30).nullable(),
|
|
74
|
+
minutes: z.number().int().min(0).max(10_000).nullable(),
|
|
75
|
+
}),
|
|
76
|
+
thresholds: thresholdsSchema,
|
|
77
|
+
cards: z.object({
|
|
78
|
+
order: z.array(z.string()),
|
|
79
|
+
hidden: z.array(z.string()),
|
|
80
|
+
}),
|
|
81
|
+
ai: z.object({
|
|
82
|
+
tone: z.enum(["direct", "supportive", "technical"]),
|
|
83
|
+
length: z.enum(["short", "normal", "detailed"]),
|
|
84
|
+
// Constrained to models this build can price. An unpriced model would
|
|
85
|
+
// record its tokens with an unknown cost and quietly make the monthly cap
|
|
86
|
+
// unenforceable, so the choice is limited to what `usage.ts` has rates for.
|
|
87
|
+
model: z.enum(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-5"]),
|
|
88
|
+
// The Ask menu the user writes for themselves. Bounded by what the menu
|
|
89
|
+
// can show rather than by a round number: five slots, and a width the
|
|
90
|
+
// watch can draw without clipping. It shipped in 0.5.0 as `max(120)` and
|
|
91
|
+
// `max(8)` with nothing reading it, which stored questions that could not
|
|
92
|
+
// have appeared and questions that would have been cut off mid-word.
|
|
93
|
+
customPrompts: z
|
|
94
|
+
.array(z.string().trim().min(1).max(PROMPT_MAX_LENGTH))
|
|
95
|
+
.max(PROMPT_SLOTS),
|
|
96
|
+
}),
|
|
97
|
+
budget: z.object({
|
|
98
|
+
monthlyUsd: z.number().min(0).max(10_000).nullable(),
|
|
99
|
+
}),
|
|
100
|
+
analytics: z.object({
|
|
101
|
+
enabled: z.boolean(),
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
/**
|
|
105
|
+
* Bands chosen to match what the watch already drew, so nobody's colours move
|
|
106
|
+
* on upgrade. They are a starting point, not a claim about physiology -- the
|
|
107
|
+
* whole reason they are editable is that the population defaults are wrong for
|
|
108
|
+
* most individuals.
|
|
109
|
+
*/
|
|
110
|
+
export const DEFAULT_PROFILE = {
|
|
111
|
+
displayName: null,
|
|
112
|
+
units: "metric",
|
|
113
|
+
timezone: null,
|
|
114
|
+
primarySport: null,
|
|
115
|
+
weeklyGoal: { sessions: null, minutes: null },
|
|
116
|
+
thresholds: {
|
|
117
|
+
recovery: { good: 70, caution: 50 },
|
|
118
|
+
sleepHours: { good: 7.5, caution: 6.5 },
|
|
119
|
+
stress: { good: 25, caution: 50 },
|
|
120
|
+
restingHrDelta: { good: 2, caution: 5 },
|
|
121
|
+
},
|
|
122
|
+
cards: { order: [...CARD_IDS], hidden: [] },
|
|
123
|
+
// Haiku by default: these answers are two or three sentences read on a
|
|
124
|
+
// watch, and the user pays for every one of them out of their own key.
|
|
125
|
+
ai: { tone: "direct", length: "short", model: "claude-haiku-4-5", customPrompts: [] },
|
|
126
|
+
budget: { monthlyUsd: null },
|
|
127
|
+
analytics: { enabled: true },
|
|
128
|
+
};
|
|
129
|
+
const PROFILE_KEY = "profile";
|
|
130
|
+
let cached = null;
|
|
131
|
+
/** Tests reach into the cache; nothing else should. */
|
|
132
|
+
export function __resetProfileCacheForTests() {
|
|
133
|
+
cached = null;
|
|
134
|
+
}
|
|
135
|
+
function isRecord(value) {
|
|
136
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Merge a stored value over a default, field by field, keeping whatever is
|
|
140
|
+
* readable.
|
|
141
|
+
*
|
|
142
|
+
* A whole-object `safeParse` is the wrong tool for reading storage: one bad
|
|
143
|
+
* enum in one nested object discards the twelve fields around it that were
|
|
144
|
+
* perfectly good. Writes are validated strictly (see `updateProfile`); reads
|
|
145
|
+
* salvage.
|
|
146
|
+
*/
|
|
147
|
+
function salvage(stored, fallback, schema) {
|
|
148
|
+
const direct = schema.safeParse(stored);
|
|
149
|
+
if (direct.success) {
|
|
150
|
+
return direct.data;
|
|
151
|
+
}
|
|
152
|
+
if (!isRecord(stored) || !isRecord(fallback)) {
|
|
153
|
+
return fallback;
|
|
154
|
+
}
|
|
155
|
+
const merged = { ...fallback };
|
|
156
|
+
for (const [key, value] of Object.entries(stored)) {
|
|
157
|
+
if (!(key in fallback)) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const fallbackValue = fallback[key];
|
|
161
|
+
const candidate = { ...merged, [key]: value };
|
|
162
|
+
if (schema.safeParse(candidate).success) {
|
|
163
|
+
merged[key] = value;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
// The field itself is bad, but it may be an object with good parts.
|
|
167
|
+
if (isRecord(value) && isRecord(fallbackValue)) {
|
|
168
|
+
const nested = { ...fallbackValue };
|
|
169
|
+
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
170
|
+
if (!(nestedKey in fallbackValue)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const nestedCandidate = { ...merged, [key]: { ...nested, [nestedKey]: nestedValue } };
|
|
174
|
+
if (schema.safeParse(nestedCandidate).success) {
|
|
175
|
+
nested[nestedKey] = nestedValue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
merged[key] = nested;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const result = schema.safeParse(merged);
|
|
182
|
+
return result.success ? result.data : fallback;
|
|
183
|
+
}
|
|
184
|
+
export function getProfile() {
|
|
185
|
+
if (cached) {
|
|
186
|
+
return cached;
|
|
187
|
+
}
|
|
188
|
+
const raw = getSetting(PROFILE_KEY);
|
|
189
|
+
if (raw === null) {
|
|
190
|
+
cached = DEFAULT_PROFILE;
|
|
191
|
+
return cached;
|
|
192
|
+
}
|
|
193
|
+
let parsed;
|
|
194
|
+
try {
|
|
195
|
+
parsed = JSON.parse(raw);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// A row that is not JSON is wreckage, not configuration. Defaults are a
|
|
199
|
+
// working server; a throw here would take down every watch fetch.
|
|
200
|
+
cached = DEFAULT_PROFILE;
|
|
201
|
+
return cached;
|
|
202
|
+
}
|
|
203
|
+
cached = normalizeCards(salvage(parsed, DEFAULT_PROFILE, profileSchema));
|
|
204
|
+
return cached;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Put the card list back into a state the carousel can render.
|
|
208
|
+
*
|
|
209
|
+
* Unknown ids are dropped and missing ids are appended, which is what stops an
|
|
210
|
+
* older client from deleting a card it has never heard of simply by leaving it
|
|
211
|
+
* out of the order it sends.
|
|
212
|
+
*/
|
|
213
|
+
function normalizeCards(profile) {
|
|
214
|
+
const known = new Set(CARD_IDS);
|
|
215
|
+
const seen = new Set();
|
|
216
|
+
const order = [];
|
|
217
|
+
for (const id of profile.cards.order) {
|
|
218
|
+
if (known.has(id) && !seen.has(id)) {
|
|
219
|
+
order.push(id);
|
|
220
|
+
seen.add(id);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
for (const id of CARD_IDS) {
|
|
224
|
+
if (!seen.has(id)) {
|
|
225
|
+
order.push(id);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const hidden = profile.cards.hidden.filter((id) => known.has(id));
|
|
229
|
+
return { ...profile, cards: { order, hidden } };
|
|
230
|
+
}
|
|
231
|
+
function deepMerge(base, patch) {
|
|
232
|
+
if (!isRecord(base) || !isRecord(patch)) {
|
|
233
|
+
return patch ?? base;
|
|
234
|
+
}
|
|
235
|
+
const out = { ...base };
|
|
236
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
237
|
+
if (value === undefined) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const current = out[key];
|
|
241
|
+
if (isRecord(value) && isRecord(current)) {
|
|
242
|
+
out[key] = deepMerge(current, value);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
out[key] = value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
export class ProfileValidationError extends Error {
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Apply a partial change.
|
|
254
|
+
*
|
|
255
|
+
* Unlike reads, writes are strict: a caller sending a unit we do not know is a
|
|
256
|
+
* bug in that caller, and storing it would mean every later read has to
|
|
257
|
+
* salvage around it. The error names the field so the dashboard can say which
|
|
258
|
+
* input was refused.
|
|
259
|
+
*/
|
|
260
|
+
export function updateProfile(patch) {
|
|
261
|
+
const merged = deepMerge(getProfile(), patch);
|
|
262
|
+
const parsed = profileSchema.safeParse(merged);
|
|
263
|
+
if (!parsed.success) {
|
|
264
|
+
const issue = parsed.error.issues[0];
|
|
265
|
+
const path = issue?.path.join(".") || "profile";
|
|
266
|
+
throw new ProfileValidationError(`${path}: ${issue?.message ?? "invalid value"}`);
|
|
267
|
+
}
|
|
268
|
+
const next = parsed.data;
|
|
269
|
+
for (const id of next.cards.order) {
|
|
270
|
+
if (!CARD_IDS.includes(id)) {
|
|
271
|
+
throw new ProfileValidationError(`cards.order: unknown card "${id}"`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
for (const id of next.cards.hidden) {
|
|
275
|
+
if (!CARD_IDS.includes(id)) {
|
|
276
|
+
throw new ProfileValidationError(`cards.hidden: unknown card "${id}"`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (next.cards.hidden.length >= CARD_IDS.length) {
|
|
280
|
+
throw new ProfileValidationError("cards.hidden: at least one card must stay visible");
|
|
281
|
+
}
|
|
282
|
+
for (const [metric, direction] of Object.entries(GRADED_METRICS)) {
|
|
283
|
+
const band = next.thresholds[metric];
|
|
284
|
+
const ordered = direction === "higher" ? band.good > band.caution : band.good < band.caution;
|
|
285
|
+
if (!ordered) {
|
|
286
|
+
throw new ProfileValidationError(direction === "higher"
|
|
287
|
+
? `thresholds.${metric}: good must be above caution`
|
|
288
|
+
: `thresholds.${metric}: good must be below caution`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const normalized = normalizeCards(next);
|
|
292
|
+
setSetting(PROFILE_KEY, JSON.stringify(normalized));
|
|
293
|
+
cached = normalized;
|
|
294
|
+
return normalized;
|
|
295
|
+
}
|
|
296
|
+
/** Card ids in the user's order, with hidden ones removed. */
|
|
297
|
+
export function visibleCards(profile = getProfile()) {
|
|
298
|
+
const hidden = new Set(profile.cards.hidden);
|
|
299
|
+
return profile.cards.order.filter((id) => !hidden.has(id));
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Grade one value against this user's bands.
|
|
303
|
+
*
|
|
304
|
+
* A missing value returns `unknown`, never `hard`. An unworn watch is not a
|
|
305
|
+
* recovery score of zero, and colouring an absence red is how a UI invents a
|
|
306
|
+
* measurement it was never given.
|
|
307
|
+
*/
|
|
308
|
+
export function stateFor(metric, value, profile = getProfile()) {
|
|
309
|
+
if (value === null || value === undefined || !Number.isFinite(value)) {
|
|
310
|
+
return "unknown";
|
|
311
|
+
}
|
|
312
|
+
const band = profile.thresholds[metric];
|
|
313
|
+
const higherIsBetter = GRADED_METRICS[metric] === "higher";
|
|
314
|
+
if (higherIsBetter) {
|
|
315
|
+
if (value >= band.good)
|
|
316
|
+
return "good";
|
|
317
|
+
if (value >= band.caution)
|
|
318
|
+
return "caution";
|
|
319
|
+
return "hard";
|
|
320
|
+
}
|
|
321
|
+
if (value <= band.good)
|
|
322
|
+
return "good";
|
|
323
|
+
if (value <= band.caution)
|
|
324
|
+
return "caution";
|
|
325
|
+
return "hard";
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { buildWatchSummary } from "./watchApi.js";
|
|
2
|
+
import { type DetectionResult } from "./detect/index.js";
|
|
3
|
+
import { type ContextEntry } from "./history/context.js";
|
|
4
|
+
/** True when AI features are usable, from either source. */
|
|
5
|
+
export declare function isAiConfigured(): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* What the model is told before it writes a sentence.
|
|
8
|
+
*
|
|
9
|
+
* This used to be four current numbers, which is why the daily insight read
|
|
10
|
+
* like a horoscope: with nothing but today's figures there was nothing to say
|
|
11
|
+
* that the numbers did not already say themselves. Findings refer to things
|
|
12
|
+
* that happened -- a run of days, a deficit against a personal median -- and
|
|
13
|
+
* context says who it is happening to.
|
|
14
|
+
*
|
|
15
|
+
* Cold start is stated explicitly. Handed an empty findings list with no
|
|
16
|
+
* explanation, a model will confidently reassure the user out of no data at all.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatFindingsContext(result: DetectionResult, context: ContextEntry[]): string;
|
|
19
|
+
export declare function formatHealthContext(summary: Awaited<ReturnType<typeof buildWatchSummary>>): string;
|
|
20
|
+
export interface SubmitPromptResult {
|
|
21
|
+
job_id: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function submitPrompt(prompt: string): SubmitPromptResult;
|
|
24
|
+
export interface PromptJobStatus {
|
|
25
|
+
status: "pending" | "running" | "done" | "error";
|
|
26
|
+
result?: string;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function getPromptStatus(id: string): PromptJobStatus | null;
|
|
30
|
+
/**
|
|
31
|
+
* The daily insight used to be generated inline on every /api/watch request, so
|
|
32
|
+
* a cold fetch blocked the watch for a full Claude round trip and spent API
|
|
33
|
+
* credit on every sync. It is now generated once per local day and cached in the
|
|
34
|
+
* settings table.
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateDailyInsight(summary: Awaited<ReturnType<typeof buildWatchSummary>>, options?: {
|
|
37
|
+
force?: boolean;
|
|
38
|
+
}): Promise<string | null>;
|
|
39
|
+
/** Returns today's cached insight without contacting the API. */
|
|
40
|
+
export declare function getCachedDailyInsight(): string | null;
|
|
41
|
+
/** Drops today's cached insight so the next request regenerates it. */
|
|
42
|
+
export declare function clearDailyInsight(): void;
|
|
43
|
+
//# sourceMappingURL=promptApi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promptApi.d.ts","sourceRoot":"","sources":["../src/promptApi.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAA0C,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACjG,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAuBxE,4DAA4D;AAC5D,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,YAAY,EAAE,GACtB,MAAM,CA0CR;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,GAAG,MAAM,CA6ElG;AA2GD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAU/D;AAwBD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAQlE;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,EACtD,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAChC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmCxB;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,IAAI,CAErD;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|