@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 CHANGED
@@ -39,7 +39,8 @@
39
39
 
40
40
  ## What you can ask
41
41
 
42
- With this server, you can ask your assistant:
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.1";
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
- const settings = options.settings ? checkSettings(options.settings) : settingsOf(old);
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 unreadable = false;
63669
+ let broken = false;
63660
63670
  try {
63661
63671
  oldSettings = settingsOf(old);
63662
63672
  } catch (e) {
63663
- if (asked === void 0) throw new Error(`${e.message} in ${target}: give new settings, or delete the file`, { cause: e });
63673
+ if (asked === void 0) throw unreadable(e, target);
63664
63674
  oldSettings = {};
63665
- unreadable = true;
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) || unreadable)) {
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 and the activities of each board."
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 and the activities of each board").optional()
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.1",
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. First ask two questions. If your client has a tool for questions with choices, use it; without
6
- one, ask in chat as numbered lists, with the default first:
7
- - **Title**: options "Keep the title" (default: the title of the page, "{{title}}" for a new
8
- page) and "Type my own".
9
- - **Headline** (the line under the title): options "Keep the headline" (default: the headline of
10
- the page, "{{headline}}" for a new page) and "Type my own".
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
- 2. Call `writeDashboard` with `weeks` = {{weeks}} and `layout` = "{{layout}}". The server gets the
13
- data, writes the page from its template, and returns the path of the page and a digest of the
14
- data. An existing page stays as a `.bak` file.
15
- 3. Write at most 5 coach notes from the digest. Each note names a specific number of the digest, for
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
- 4. Call `writeDashboard` again with the same `weeks` and the `notes`, without `layout`. The page keeps
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
- 5. If the result has `missingProfileLanguage`, say first that my Garmin profile uses that language,
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.