@indaco/garmin-personal-mcp 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/index.js +20 -9
- package/package.json +1 -1
- package/prompts/training-dashboard.md +23 -12
package/README.md
CHANGED
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
|
|
40
40
|
## What you can ask
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Ask your assistant anything about your Garmin data, in your own words and in any language. The
|
|
43
|
+
assistant chooses the tools and can combine their data. Some examples:
|
|
43
44
|
|
|
44
45
|
- Activities: "List my last 5 activities, with pace and heart rate."
|
|
45
46
|
- Activities: "How much time did I spend in each HR zone on my runs this month?"
|
|
@@ -211,7 +212,9 @@ In Claude Code, the arguments are in order and separated by spaces: first `weeks
|
|
|
211
212
|
You can also give only the layout: `/garmin-personal:training-dashboard freeform`.
|
|
212
213
|
|
|
213
214
|
With `preset` and `blank`, the assistant first asks for the title and the headline (the line under
|
|
214
|
-
the title) of the page. Keep them or type your own. Updates and the automatic update keep them.
|
|
215
|
+
the title) of the page. Keep them or type your own. Updates and the automatic update keep them. When
|
|
216
|
+
the page has other settings, for example only one sport, the assistant tells you these settings and
|
|
217
|
+
asks you to keep them, to use the defaults or to type your own choice.
|
|
215
218
|
|
|
216
219
|
The assistant gives you the path of the page and the command to open it. The page has a board for
|
|
217
220
|
all your training, a Recovery board and a board for each sport, with 11 themes, in English and the
|
package/dist/index.js
CHANGED
|
@@ -24096,7 +24096,7 @@ function toError(value) {
|
|
|
24096
24096
|
}
|
|
24097
24097
|
// package.json
|
|
24098
24098
|
var name = "@indaco/garmin-personal-mcp";
|
|
24099
|
-
var version = "0.3.
|
|
24099
|
+
var version = "0.3.2";
|
|
24100
24100
|
// src/log.ts
|
|
24101
24101
|
/** Past this size the log moves to `<file>.1`, replacing the one before, and a new log starts. */
|
|
24102
24102
|
const MAX_BYTES = 1e6;
|
|
@@ -63517,6 +63517,10 @@ function settingsOf(page) {
|
|
|
63517
63517
|
const s = page === void 0 ? void 0 : readBlock(page, "settings");
|
|
63518
63518
|
return s && typeof s === "object" ? s : {};
|
|
63519
63519
|
}
|
|
63520
|
+
/** The settings block of the page cannot be read: only new settings, or a new file, help. */
|
|
63521
|
+
function unreadable(e, target) {
|
|
63522
|
+
return new Error(`${e.message} in ${target}: give new settings, or delete the file`, { cause: e });
|
|
63523
|
+
}
|
|
63520
63524
|
/** The number of items for each key. */
|
|
63521
63525
|
function countBy(items, key) {
|
|
63522
63526
|
const out = {};
|
|
@@ -63636,12 +63640,18 @@ var DashboardWriter = class {
|
|
|
63636
63640
|
const run = this.#queue.then(async () => {
|
|
63637
63641
|
const target = this.#target(options.path);
|
|
63638
63642
|
const old = await readExisting(target);
|
|
63639
|
-
|
|
63643
|
+
let settings;
|
|
63644
|
+
try {
|
|
63645
|
+
settings = options.settings ? checkSettings(options.settings) : settingsOf(old);
|
|
63646
|
+
} catch (e) {
|
|
63647
|
+
throw options.settings ? e : unreadable(e, target);
|
|
63648
|
+
}
|
|
63640
63649
|
const data = JSON.parse(await this.#data(options.weeks ?? weeksOf(old)));
|
|
63641
63650
|
return {
|
|
63642
63651
|
path: target,
|
|
63643
63652
|
boards: countBy(listOf(data.activities), groupOf),
|
|
63644
|
-
digest: digest(filterData(data, settings))
|
|
63653
|
+
digest: digest(filterData(data, settings)),
|
|
63654
|
+
settings
|
|
63645
63655
|
};
|
|
63646
63656
|
});
|
|
63647
63657
|
this.#queue = run.catch(() => {});
|
|
@@ -63656,13 +63666,13 @@ var DashboardWriter = class {
|
|
|
63656
63666
|
const old = await readExisting(target);
|
|
63657
63667
|
if (onlyExisting && old === void 0) throw new Error(`${target} does not exist: make it first with the training-dashboard prompt`);
|
|
63658
63668
|
let oldSettings;
|
|
63659
|
-
let
|
|
63669
|
+
let broken = false;
|
|
63660
63670
|
try {
|
|
63661
63671
|
oldSettings = settingsOf(old);
|
|
63662
63672
|
} catch (e) {
|
|
63663
|
-
if (asked === void 0) throw
|
|
63673
|
+
if (asked === void 0) throw unreadable(e, target);
|
|
63664
63674
|
oldSettings = {};
|
|
63665
|
-
|
|
63675
|
+
broken = true;
|
|
63666
63676
|
}
|
|
63667
63677
|
const own = asked ?? oldSettings;
|
|
63668
63678
|
const template = await this.template();
|
|
@@ -63678,7 +63688,7 @@ var DashboardWriter = class {
|
|
|
63678
63688
|
boards: {}
|
|
63679
63689
|
});
|
|
63680
63690
|
kind = old === void 0 ? "created" : "replaced";
|
|
63681
|
-
} else if (templateVersion(old) > 0 && (templateVersion(old) < templateVersion(template) || !same(own, oldSettings) ||
|
|
63691
|
+
} else if (templateVersion(old) > 0 && (templateVersion(old) < templateVersion(template) || !same(own, oldSettings) || broken)) {
|
|
63682
63692
|
next = writeBlock(template, "layout", readBlock(old, "layout") ?? null);
|
|
63683
63693
|
next = writeBlock(next, "widgets", readBlock(old, "widgets") ?? []);
|
|
63684
63694
|
next = writeBlock(next, "i18n", readBlock(old, "i18n") ?? {});
|
|
@@ -64020,7 +64030,8 @@ function registerDashboardWriter(server, writer) {
|
|
|
64020
64030
|
"profile when the page has no texts in it (it shows English); editDashboard `languages` adds it.",
|
|
64021
64031
|
"`missingWidgetTexts` lists the custom widgets that miss texts in a language of the page, with",
|
|
64022
64032
|
"those languages; editDashboard `widgetTexts` adds them.",
|
|
64023
|
-
"`preview: true` writes nothing: it returns the digest
|
|
64033
|
+
"`preview: true` writes nothing: it returns the digest, the activities of each board and the",
|
|
64034
|
+
"`settings` of the page."
|
|
64024
64035
|
].join("\n"),
|
|
64025
64036
|
inputSchema: object({
|
|
64026
64037
|
weeks: number$1().int().describe("Weeks to cover, default 12, max 52").optional(),
|
|
@@ -64038,7 +64049,7 @@ function registerDashboardWriter(server, writer) {
|
|
|
64038
64049
|
themes: array(string()).describe(`Themes to keep: ${THEMES.map((t) => t.id).join(", ")}`).optional(),
|
|
64039
64050
|
theme: string().describe("Default theme").optional()
|
|
64040
64051
|
}).describe("The page settings; replaces the page's own").optional(),
|
|
64041
|
-
preview: boolean().describe("Write nothing: return the digest
|
|
64052
|
+
preview: boolean().describe("Write nothing: return the digest, the activities of each board and the page settings").optional()
|
|
64042
64053
|
})
|
|
64043
64054
|
}, async ({ preview, ...a }) => ({ content: [{
|
|
64044
64055
|
type: "text",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indaco/garmin-personal-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "MCP server for the official Garmin Connect Personal API",
|
|
5
5
|
"homepage": "https://github.com/indaco/garmin-personal-mcp#readme",
|
|
6
6
|
"bugs": "https://github.com/indaco/garmin-personal-mcp/issues",
|
|
@@ -2,23 +2,34 @@ You are a data-minded endurance coach. Using the tools of the `garmin-personal`
|
|
|
2
2
|
personal **training + recovery dashboard** for the last **{{weeks}} weeks** (today is {{today}}).
|
|
3
3
|
Answer in my language.
|
|
4
4
|
|
|
5
|
-
1.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **
|
|
10
|
-
|
|
5
|
+
1. Call `writeDashboard` with `preview` = true and `weeks` = {{weeks}}. It writes nothing. Its
|
|
6
|
+
`settings` are the settings of the page, or none when there is no page.
|
|
7
|
+
2. Then ask the questions below. If your client has a tool for questions with choices, use it;
|
|
8
|
+
without one, ask in chat as numbered lists, with the default first:
|
|
9
|
+
- **Settings**: only when `settings` has more than `title` and `headline`. Tell me those settings
|
|
10
|
+
in words, for example "only strength training", "without sleep data" or "Italian only". If
|
|
11
|
+
`settings` has `exclude`, name each item it leaves out and say that the defaults put them back
|
|
12
|
+
into the page, also on each automatic update: this matters for a page that I share.
|
|
13
|
+
Options "Keep these settings" (default) and "Use the defaults: all sports, all data, English and
|
|
14
|
+
the language of my Garmin profile, all themes, opens in midnight". Say that I can type my own
|
|
15
|
+
choice, for example "all sports, keep the rest".
|
|
16
|
+
- **Title**: options "Keep the title" (default: the `title` of `settings`, else "{{title}}") and
|
|
17
|
+
"Type my own".
|
|
18
|
+
- **Headline** (the line under the title): options "Keep the headline" (default: the `headline`
|
|
19
|
+
of `settings`, else "{{headline}}") and "Type my own".
|
|
11
20
|
When I choose "Type my own" and give no text, ask for the text in chat.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
21
|
+
3. Call `writeDashboard` with `weeks` = {{weeks}} and `layout` = "{{layout}}". If I chose the
|
|
22
|
+
defaults, also give `settings` = only the `title` and `headline` of `settings`, the ones that it
|
|
23
|
+
has. If I typed my own choice, give `settings` = `settings` changed as I said. The server gets
|
|
24
|
+
the data, writes the page from its template, and returns the path of the page and a digest of
|
|
25
|
+
the data. An existing page stays as a `.bak` file.
|
|
26
|
+
4. Write at most 5 coach notes from the digest. Each note names a specific number of the digest, for
|
|
16
27
|
example "load ratio 1.18 while HRV was under its weekly average on 3 of the last 7 nights".
|
|
17
28
|
Training suggestions only, no medical advice. Plain text, in my language.
|
|
18
|
-
|
|
29
|
+
5. Call `writeDashboard` again with the same `weeks` and the `notes`, without `layout`. The page keeps
|
|
19
30
|
its layout. If I typed my own title or headline, also give `settings` = the `settings` of the
|
|
20
31
|
last result with `title` and `headline` set to my texts: the other settings of the page stay.
|
|
21
|
-
|
|
32
|
+
6. If the result has `missingProfileLanguage`, say first that my Garmin profile uses that language,
|
|
22
33
|
that the page has no texts in it yet, so the page shows English, and that you can add it.
|
|
23
34
|
Make that language the default of the question below instead of "No".
|
|
24
35
|
Tell me the languages of the page (`pageLanguages` of the result) and ask whether to add one.
|