ts-watches 0.1.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/LICENSE.md +21 -0
- package/README.md +358 -0
- package/dist/analysis/index.d.ts +5 -0
- package/dist/analysis/personal-records.d.ts +58 -0
- package/dist/analysis/race-predictor.d.ts +53 -0
- package/dist/analysis/running-dynamics.d.ts +66 -0
- package/dist/analysis/training-load.d.ts +40 -0
- package/dist/analysis/zones.d.ts +69 -0
- package/dist/bin/cli.js +52 -0
- package/dist/chunk-gsgt8r0z.js +2 -0
- package/dist/cloud/garmin-activity-api.d.ts +129 -0
- package/dist/cloud/garmin-connect.d.ts +67 -0
- package/dist/cloud/index.d.ts +4 -0
- package/dist/cloud/strava.d.ts +88 -0
- package/dist/cloud/trainingpeaks.d.ts +48 -0
- package/dist/config.d.ts +4 -0
- package/dist/drivers/apple.d.ts +17 -0
- package/dist/drivers/coros.d.ts +13 -0
- package/dist/drivers/garmin.d.ts +12 -0
- package/dist/drivers/index.d.ts +6 -0
- package/dist/drivers/polar.d.ts +14 -0
- package/dist/drivers/suunto.d.ts +13 -0
- package/dist/drivers/wahoo.d.ts +13 -0
- package/dist/export/csv.d.ts +15 -0
- package/dist/export/geojson.d.ts +23 -0
- package/dist/export/gpx.d.ts +10 -0
- package/dist/export/index.d.ts +4 -0
- package/dist/export/tcx.d.ts +10 -0
- package/dist/fit/constants.d.ts +606 -0
- package/dist/fit/decoder.d.ts +16 -0
- package/dist/fit/index.d.ts +3 -0
- package/dist/fit/parser.d.ts +21 -0
- package/dist/index.d.ts +40 -0
- package/dist/realtime/ant-plus.d.ts +201 -0
- package/dist/realtime/bluetooth.d.ts +153 -0
- package/dist/realtime/index.d.ts +3 -0
- package/dist/realtime/live-tracking.d.ts +70 -0
- package/dist/src/index.js +324 -0
- package/dist/types.d.ts +307 -0
- package/dist/workouts/builder.d.ts +184 -0
- package/dist/workouts/course.d.ts +74 -0
- package/dist/workouts/index.d.ts +3 -0
- package/dist/workouts/training-plan.d.ts +77 -0
- package/package.json +92 -0
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import{u as E}from"../chunk-gsgt8r0z.js";import{existsSync as q6}from"node:fs";import h from"node:fs/promises";import p from"node:os";import k from"node:path";import P from"node:process";import{EventEmitter as j6}from"node:events";import L from"node:process";import n from"node:process";import g from"node:process";import b from"node:process";import B from"node:process";import r from"node:tty";import r6,{stdin as bz,stdout as Az}from"node:process";import s,{stdin as t6,stdout as o6}from"node:process";import e6 from"node:readline";class o{configPath;config=null;events=[];retryCount=0;maxRetries=3;retryDelayMs=1000;constructor(){let z=p.homedir(),J=k.join(z,".config","clapp");this.configPath=k.join(J,"telemetry.json")}async isEnabled(){if(P.env.DO_NOT_TRACK==="1"||P.env.DO_NOT_TRACK==="true")return!1;if(P.env.NO_TELEMETRY==="1"||P.env.NO_TELEMETRY==="true")return!1;return(await this.loadConfig()).enabled}async enable(){let z=await this.loadConfig();if(z.enabled=!0,!z.userId)z.userId=this.generateUserId();await this.saveConfig(z)}async disable(){let z=await this.loadConfig();z.enabled=!1,await this.saveConfig(z)}async track(z,J){if(!await this.isEnabled())return;let $={event:z,...J,timestamp:Date.now(),platform:p.platform(),nodeVersion:P.version};if(this.events.push($),this.events.length>=10)await this.send()}async trackCommand(z,J){await this.track("command",{command:z,duration:J})}async trackError(z,J){await this.track("error",{error:z,command:J})}async send(){if(!await this.isEnabled()||this.events.length===0)return!0;try{this.events=[],this.retryCount=0;let J=await this.loadConfig();return J.lastSent=Date.now(),await this.saveConfig(J),!0}catch{if(this.retryCount<this.maxRetries){this.retryCount++;let J=this.retryDelayMs*2**(this.retryCount-1);return await this.sleep(J),this.send()}return this.events=[],this.retryCount=0,!1}}async flush(){if(this.events.length===0)return!0;return this.send()}sleep(z){return new Promise((J)=>setTimeout(J,z))}async status(){let z=await this.loadConfig();return{enabled:z.enabled,doNotTrack:P.env.DO_NOT_TRACK==="1"||P.env.DO_NOT_TRACK==="true",eventsQueued:this.events.length,lastSent:z.lastSent}}async loadConfig(){if(this.config)return this.config;try{if(q6(this.configPath)){let z=await h.readFile(this.configPath,"utf-8");return this.config=JSON.parse(z),this.config}}catch{}return this.config={enabled:!1},this.config}async saveConfig(z){this.config=z;try{let J=k.dirname(this.configPath);await h.mkdir(J,{recursive:!0}),await h.writeFile(this.configPath,JSON.stringify(z,null,2),"utf-8")}catch{}}generateUserId(){let z=Math.random().toString(36).substring(2,15),J=Date.now().toString(36);return`${z}-${J}`}}var wz=new o;function X6(z,J={}){let Z={_:[]},$=J.alias||{},Q=new Set(J.boolean||[]),q={};for(let j of Object.keys($))for(let Y of $[j])q[Y]=j;for(let j of Q)if($[j])for(let Y of $[j])Q.add(Y);function X(j,Y){let U=q[j]||j;if(Z[U]=Y,$[U])for(let F of $[U])Z[F]=Y;if(q[j]&&$[q[j]])for(let F of $[q[j]])Z[F]=Y;Z[j]=Y}for(let j=0;j<z.length;j++){let Y=z[j];if(Y==="--"){Z._.push(...z.slice(j+1));break}if(Y.startsWith("--")){let U=Y.indexOf("=");if(U!==-1){let F=Y.slice(2,U),G=Y.slice(U+1);X(F,G)}else{let F=Y.slice(2);if(F.startsWith("no-")){let H=F.slice(3);X(H,!1);continue}let G=q[F]||F;if(Q.has(G)||Q.has(F))X(F,!0);else{let H=z[j+1];if(H!==void 0&&!H.startsWith("-"))X(F,H),j++;else X(F,!0)}}}else if(Y.startsWith("-")&&Y.length>1){let U=Y.slice(1);for(let F=0;F<U.length;F++){let G=U[F],H=q[G]||G;if(F===U.length-1&&!Q.has(H)&&!Q.has(G)){let K=z[j+1];if(K!==void 0&&!K.startsWith("-"))X(G,K),j++;else X(G,!0)}else X(G,!0)}}else Z._.push(Y)}return Z}function v(z){return z.replace(/[<[].+/,"").trim()}function U6(z){let J=/<([^>]+)>/g,Z=/\[([^\]]+)\]/g,$=[],Q=(j)=>{let Y=!1,U=j[1];if(U.startsWith("..."))U=U.slice(3),Y=!0;return{required:j[0].startsWith("<"),value:U,variadic:Y}},q;while(q=J.exec(z))$.push(Q(q));let X;while(X=Z.exec(z))$.push(Q(X));return $}function F6(z){let J={alias:{},boolean:[]};for(let[Z,$]of z.entries()){if($.names.length>1)J.alias[$.names[0]]=$.names.slice(1);if($.isBoolean)if($.negated){if(!z.some((q,X)=>{return X!==Z&&q.names.some((j)=>$.names.includes(j))&&typeof q.required==="boolean"}))J.boolean.push($.names[0])}else J.boolean.push($.names[0])}return J}function i(z){return z.reduce((J,Z)=>J.length>=Z.length?J:Z,"")}function S(z,J){return z.length>=J?z:`${z}${" ".repeat(J-z.length)}`}function G6(z){return z.replace(/([a-z])-([a-z])/g,(J,Z,$)=>{return Z+$.toUpperCase()})}function H6(z,J,Z){let $=0,Q=J.length,q=z,X;for(;$<Q;++$)X=q[J[$]],q=q[J[$]]=$===Q-1?Z:X!=null?X:!!~J[$+1].indexOf(".")||!(+J[$+1]>-1)?{}:[]}function K6(z,J){for(let Z of Object.keys(J)){let $=J[Z];if($.shouldTransform){if(z[Z]=Array.prototype.concat.call([],z[Z]),typeof $.transformFunction==="function")z[Z]=z[Z].map($.transformFunction)}}}function V6(z){let J=/([^\\/]+)$/.exec(z);return J?J[1]:""}function e(z){return z.split(".").map((J,Z)=>{return Z===0?G6(J):J}).join(".")}class I extends Error{exitCode=2;isUsageError=!0;constructor(z){super(z);if(this.name=this.constructor.name,typeof Error.captureStackTrace==="function")Error.captureStackTrace(this,this.constructor);else this.stack=Error(z).stack}format(z=!1){if(z&&this.stack)return`${this.message}
|
|
3
|
+
|
|
4
|
+
Stack trace:
|
|
5
|
+
${this.stack}`;return this.message}}function W6(){let{env:z}=n,{TERM:J,TERM_PROGRAM:Z}=z;if(n.platform!=="win32")return J!=="linux";return Boolean(z.WT_SESSION)||Boolean(z.TERMINUS_SUBLIME)||z.ConEmuTask==="{cmd::Cmder}"||Z==="Terminus-Sublime"||Z==="vscode"||J==="xterm-256color"||J==="alacritty"||J==="rxvt-unicode"||J==="rxvt-unicode-256color"||z.TERMINAL_EMULATOR==="JetBrains-JediTerm"}var C="\x1B",w=`${C}[`;var D={to(z,J){if(!J)return`${w}${z+1}G`;return`${w}${J+1};${z+1}H`},move(z,J){let Z="";if(z<0)Z+=`${w}${-z}D`;else if(z>0)Z+=`${w}${z}C`;if(J<0)Z+=`${w}${-J}A`;else if(J>0)Z+=`${w}${J}B`;return Z},up:(z=1)=>`${w}${z}A`,down:(z=1)=>`${w}${z}B`,forward:(z=1)=>`${w}${z}C`,backward:(z=1)=>`${w}${z}D`,nextLine:(z=1)=>`${w}E`.repeat(z),prevLine:(z=1)=>`${w}F`.repeat(z),left:`${w}G`,hide:`${w}?25l`,show:`${w}?25h`,save:`${C}7`,restore:`${C}8`};var y={screen:`${w}2J`,up:(z=1)=>`${w}1J`.repeat(z),down:(z=1)=>`${w}J`.repeat(z),line:`${w}2K`,lineEnd:`${w}K`,lineStart:`${w}1K`,lines(z){let J="";for(let Z=0;Z<z;Z++)J+=this.line+(Z<z-1?D.up():"");if(z)J+=D.left;return J}},Nz={screen:`${C}c`};function w6(z,J){let Z=[];for(let $=0;$<=J.length;$++)Z[$]=[$];for(let $=0;$<=z.length;$++)Z[0][$]=$;for(let $=1;$<=J.length;$++)for(let Q=1;Q<=z.length;Q++)if(J.charAt($-1)===z.charAt(Q-1))Z[$][Q]=Z[$-1][Q-1];else Z[$][Q]=Math.min(Z[$-1][Q-1]+1,Z[$][Q-1]+1,Z[$-1][Q]+1);return Z[J.length][z.length]}function z6(z,J,Z=2,$=3){return J.map((Q)=>({cmd:Q,distance:w6(z,Q)})).filter(({distance:Q})=>Q<=Z).sort((Q,q)=>Q.distance-q.distance).slice(0,$).map(({cmd:Q})=>Q)}class J6{rawName;description;name;names;isBoolean;required;config;negated;constructor(z,J,Z){if(this.rawName=z,this.description=J,this.config=Object.assign({},Z),z=z.replace(/\.\*/g,""),this.negated=!1,this.names=v(z).split(",").map(($)=>{let Q=$.trim().replace(/^-{1,2}/,"");if(Q.startsWith("no-"))this.negated=!0,Q=Q.replace(/^no-/,"");return e(Q)}).sort(($,Q)=>$.length>Q.length?1:-1),this.name=this.names[this.names.length-1],this.negated&&this.config.default==null)this.config.default=!0;if(z.includes("<"))this.required=!0;else if(z.includes("["))this.required=!1;else this.isBoolean=!0}}var Oz=g.argv,D6=`${g.platform}-${g.arch} bun-v${typeof Bun<"u"?Bun.version:"unknown"}`,l=b.argv,L6=`${b.platform}-${b.arch} node-${b.version}`;class _{rawName;description;config;cli;options;aliasNames;name;namespace;args;commandAction;usageText;versionNumber;examples;helpCallback;globalCommand;beforeHooks;afterHooks;middleware;constructor(z,J,Z,$){this.rawName=z,this.description=J,this.config=Z,this.cli=$,this.options=[],this.aliasNames=[],this.name=v(z);let Q=v(z),q=Q.indexOf(":");if(q>0)this.namespace=Q.substring(0,q),this.name=Q.substring(q+1);if(this.args=U6(z),this.examples=[],this.beforeHooks=[],this.afterHooks=[],this.middleware=[],!Z)this.config={}}usage(z){return this.usageText=z,this}allowUnknownOptions(){return this.config.allowUnknownOptions=!0,this}ignoreOptionDefaultValue(){return this.config.ignoreOptionDefaultValue=!0,this}version(z,J="-v, --version"){return this.versionNumber=z,this.option(J,"Display version number"),this}example(z){return this.examples.push(z),this}option(z,J,Z){let $=new J6(z,J,Z);return this.options.push($),this}alias(z){return this.aliasNames.push(z),this}action(z){return this.commandAction=z,this}before(z){return this.beforeHooks.push(z),this}after(z){return this.afterHooks.push(z),this}use(z){return this.middleware.push(z),this}isMatched(z){if(this.aliasNames.includes(z))return!0;if(this.namespace)return`${this.namespace}:${this.name}`===z;return this.name===z}get isDefaultCommand(){return this.name===""||this.aliasNames.includes("!")}get isGlobalCommand(){return this instanceof f}get displayName(){return this.namespace?`${this.namespace}:${this.name}`:this.name}hasOption(z){return z=z.split(".")[0],!!this.options.find((J)=>{return J.names.includes(z)})}outputHelp(){let{name:z,commands:J}=this.cli,{versionNumber:Z,options:$,helpCallback:Q}=this.cli.globalCommand,q=[{body:`${z}${Z?`/${Z}`:""}`}];if(q.push({title:"Usage",body:` $ ${z} ${this.usageText||this.rawName}`}),(this.isGlobalCommand||this.isDefaultCommand)&&J.length>0){let Y=i(J.map((K)=>K.rawName)),U=new Map,F=[];for(let K of J)if(K.namespace){if(!U.has(K.namespace))U.set(K.namespace,[]);U.get(K.namespace).push(K)}else F.push(K);let G="";if(F.length>0)G+=F.map((K)=>{return` ${S(K.rawName,Y.length)} ${K.description}`}).join(`
|
|
6
|
+
`);let H=Array.from(U.keys()).sort();for(let K of H){let Y6=U.get(K);if(G.length>0)G+=`
|
|
7
|
+
|
|
8
|
+
`;G+=` ${K}:
|
|
9
|
+
`,G+=Y6.map((c)=>{return` ${S(c.rawName,Y.length-2)} ${c.description}`}).join(`
|
|
10
|
+
`)}q.push({title:"Commands",body:G}),q.push({title:"For more info, run any command with the `--help` flag",body:J.map((K)=>` $ ${z}${K.displayName===""?"":` ${K.displayName}`} --help`).join(`
|
|
11
|
+
`)})}let j=this.isGlobalCommand?$:[...this.options,...$||[]];if(!this.isGlobalCommand&&!this.isDefaultCommand)j=j.filter((Y)=>Y.name!=="version");if(j.length>0){let Y=i(j.map((U)=>U.rawName));q.push({title:"Options",body:j.map((U)=>{return` ${S(U.rawName,Y.length)} ${U.description} ${U.config.default===void 0?"":`(default: ${U.config.default})`}`}).join(`
|
|
12
|
+
`)})}if(this.examples.length>0)q.push({title:"Examples",body:this.examples.map((Y)=>{if(typeof Y==="function")return Y(z);return Y}).join(`
|
|
13
|
+
`)});if(Q)q=Q(q)||q;console.log(q.map((Y)=>{return Y.title?`${Y.title}:
|
|
14
|
+
${Y.body}`:Y.body}).join(`
|
|
15
|
+
|
|
16
|
+
`))}outputVersion(){let{name:z}=this.cli,{versionNumber:J}=this.cli.globalCommand;if(J)console.log(`${z}/${J} ${typeof Bun<"u"?D6:L6}`)}checkRequiredArgs(){let z=this.args.filter((J)=>J.required).length;if(this.cli.args.length<z){let Z=this.args.filter((Q)=>Q.required).slice(this.cli.args.length),$=Z.map((Q)=>`<${Q.value}>`).join(" ");throw new I(`Missing required argument${Z.length>1?"s":""}: ${$}
|
|
17
|
+
|
|
18
|
+
Run \`${this.cli.name} ${this.rawName} --help\` for usage information.`)}}checkUnknownOptions(){let{options:z,globalCommand:J}=this.cli;if(!this.config.allowUnknownOptions){for(let Z of Object.keys(z))if(Z!=="--"&&!this.hasOption(Z)&&!J.hasOption(Z)){let Q=[...J.options,...this.options].flatMap((Y)=>Y.names),q=Z.length>1?`--${Z}`:`-${Z}`,X=z6(Z,Q),j=`Unknown option \`${q}\``;if(X.length>0)j+=`
|
|
19
|
+
|
|
20
|
+
Did you mean one of these?`,X.forEach((Y)=>{let U=Y.length>1?`--${Y}`:`-${Y}`;j+=`
|
|
21
|
+
• ${U}`});throw j+=`
|
|
22
|
+
|
|
23
|
+
Run \`${this.cli.name} ${this.rawName} --help\` to see available options.`,new I(j)}}}checkOptionValue(){let{options:z,globalCommand:J}=this.cli,Z=[...J.options,...this.options];for(let $ of Z){let Q=z[$.name.split(".")[0]];if($.required){let q=Z.some((X)=>X.negated&&X.names.includes($.name));if(Q===!0||Q===!1&&!q)throw new I(`Option \`${$.rawName}\` requires a value.
|
|
24
|
+
|
|
25
|
+
Example: ${this.cli.name} ${this.rawName} ${$.rawName} <value>`)}}}}class f extends _{constructor(z){super("@@global@@","",{},z)}}var M6=_,M={red:["\x1B[31m","\x1B[39m"],green:["\x1B[32m","\x1B[39m"],blue:["\x1B[34m","\x1B[39m"],yellow:["\x1B[33m","\x1B[39m"],cyan:["\x1B[36m","\x1B[39m"],magenta:["\x1B[35m","\x1B[39m"],white:["\x1B[37m","\x1B[39m"],gray:["\x1B[90m","\x1B[39m"],bgRed:["\x1B[41m","\x1B[49m"],bgGreen:["\x1B[42m","\x1B[49m"],bgBlue:["\x1B[44m","\x1B[49m"],bgYellow:["\x1B[43m","\x1B[49m"],bgCyan:["\x1B[46m","\x1B[49m"],bgMagenta:["\x1B[45m","\x1B[49m"],bold:["\x1B[1m","\x1B[22m"],italic:["\x1B[3m","\x1B[23m"],underline:["\x1B[4m","\x1B[24m"],dim:["\x1B[2m","\x1B[22m"],inverse:["\x1B[7m","\x1B[27m"],hidden:["\x1B[8m","\x1B[28m"],strikethrough:["\x1B[9m","\x1B[29m"]};var O={primary:"blue",secondary:"cyan",success:"green",warning:"yellow",error:"red",info:"magenta",muted:"gray"};function N6(){return!0}function P6(){let z={};z.supportsColor=N6();function J($,Q=[]){let q=$===""?[]:[...Q,$],X=function(Y){if(!z.supportsColor)return Y;let U="",F="";for(let G of q)if(G in O&&O[G]in M){let H=O[G];U+=M[H][0],F=M[H][1]+F}else if(G in M)U+=M[G][0],F=M[G][1]+F;return U+Y+F},j=[...Object.keys(M),...Object.keys(O)];for(let Y of j)if(!(Y in X))Object.defineProperty(X,Y,{get(){return J(Y,q)}});return X}let Z=[...Object.keys(M),...Object.keys(O)];for(let $ of Z)if(!($ in z))Object.defineProperty(z,$,{get(){return J($)}});return z}var T=P6();class m extends j6{name;commands;globalCommand;matchedCommand;matchedCommandName;rawArgs;args;options;showHelpOnExit;showVersionOnExit;enableDidYouMean=!0;signalHandlersSet=!1;isVerbose=!1;isQuiet=!1;isDebug=!1;isNoInteraction=!1;environment;isDryRun=!1;isForce=!1;useEmoji=!0;theme;isNoCache=!1;constructor(z=""){super();this.name=z,this.commands=[],this.rawArgs=[],this.args=[],this.options={},this.globalCommand=new f(this),this.globalCommand.usage("<command> [options]")}handleSignals(z){if(this.signalHandlersSet)return this;let J=async(Z)=>{if(console.log(`
|
|
26
|
+
|
|
27
|
+
Received ${Z}, cleaning up...`),z)try{await z()}catch($){console.error("Error during cleanup:",$)}L.exit(0)};return L.on("SIGINT",()=>J("SIGINT")),L.on("SIGTERM",()=>J("SIGTERM")),this.signalHandlersSet=!0,this}didYouMean(z=!0){return this.enableDidYouMean=z,this}verbose(){return this.globalCommand.option("-v, --verbose","Enable verbose output"),this}quiet(){return this.globalCommand.option("-q, --quiet","Suppress non-essential output"),this}debug(){return this.globalCommand.option("--debug","Enable debug mode with detailed error information"),this}noInteraction(){return this.globalCommand.option("-n, --no-interaction","Do not ask any interactive questions (for CI/CD)"),this}env(){return this.globalCommand.option("--env <environment>","Target environment (e.g., production, staging, local)"),this}dryRun(){return this.globalCommand.option("--dry-run","Preview actions without executing them"),this}force(){return this.globalCommand.option("-f, --force","Skip confirmation prompts"),this}emoji(){return this.globalCommand.option("--no-emoji","Disable emoji in output"),this}themes(){return this.globalCommand.option("--theme <theme>","Color theme (default, dracula, nord, solarized, monokai)"),this}cache(){return this.globalCommand.option("--no-cache","Disable caching"),this}usage(z){return this.globalCommand.usage(z),this}command(z,J,Z){if(!Z)Z={};let $=new M6(z,J||"",Z,this);return $.globalCommand=this.globalCommand,this.commands.push($),$}option(z,J,Z){return this.globalCommand.option(z,J,Z),this}help(z){return this.globalCommand.option("-h, --help","Display this message"),this.globalCommand.helpCallback=z,this.showHelpOnExit=!0,this}version(z,J="-v, --version"){return this.globalCommand.version(z,J),this.showVersionOnExit=!0,this}example(z){return this.globalCommand.example(z),this}outputHelp(){if(this.matchedCommand)this.matchedCommand.outputHelp();else this.globalCommand.outputHelp()}outputVersion(){this.globalCommand.outputVersion()}setParsedInfo({args:z,options:J},Z,$){if(this.args=z,this.options=J,Z)this.matchedCommand=Z;if($)this.matchedCommandName=$;return this}unsetMatchedCommand(){this.matchedCommand=void 0,this.matchedCommandName=void 0}showCommandNotFound(z){if(console.log(T.red(`
|
|
28
|
+
✗ Command "${z}" not found.
|
|
29
|
+
`)),this.enableDidYouMean){let J=[];for(let $ of this.commands){if($.displayName)J.push($.displayName);if($.aliasNames)J.push(...$.aliasNames)}let Z=z6(z,J);if(Z.length>0)console.log(T.yellow("Did you mean one of these?")),Z.forEach(($)=>console.log(` ${T.dim("•")} ${this.name} ${$}`)),console.log("")}console.log(T.dim("Run"),`${this.name} --help`,T.dim("to see all available commands")),L.exit(1)}async parse(z=l,J={}){let{run:Z=!0,exitOnError:$=!1}=J;if($)try{return await this.parse(z,{run:Z})}catch(Y){throw this.handleUsageError(Y),Y}if(this.rawArgs=z,!this.name)this.name=z[1]?V6(z[1]):"cli";let Q=!0,q=z.slice(2),X=q[0];if(X&&!X.startsWith("-")){for(let Y of this.commands)if(Y.isMatched(X)){let U=this.mri(q,Y);Q=!1;let F={...U,args:U.args.slice(1)};this.setParsedInfo(F,Y,X),this.emit(`command:${X}`,Y);break}}if(Q){for(let Y of this.commands)if(Y.name===""){Q=!1;let U=this.mri(q,Y);this.setParsedInfo(U,Y),this.emit("command:!",Y);break}}if(Q){let Y=this.mri(z.slice(2));this.setParsedInfo(Y)}if(this.options.verbose)this.isVerbose=!0;if(this.options.quiet)this.isQuiet=!0;if(this.options.debug)this.isDebug=!0;if(this.options.noInteraction)this.isNoInteraction=!0;if(this.options.env)this.environment=String(this.options.env);if(this.options.dryRun)this.isDryRun=!0;if(this.options.force)this.isForce=!0;if(this.options.noEmoji!==void 0)this.useEmoji=!this.options.noEmoji;if(this.options.theme)this.theme=String(this.options.theme);if(this.options.noCache!==void 0)this.isNoCache=Boolean(this.options.noCache);if(this.options.help&&this.showHelpOnExit)this.outputHelp(),Z=!1,this.unsetMatchedCommand();if(this.options.version&&this.showVersionOnExit&&this.matchedCommandName==null)this.outputVersion(),Z=!1,this.unsetMatchedCommand();let j={args:this.args,options:this.options};if(Z)await this.runMatchedCommand();if(!this.matchedCommand&&this.args[0]){if(this.emit("command:*"),!(this.listenerCount("command:*")>0))this.showCommandNotFound(this.args[0])}return j}mri(z,J){let Z=[...this.globalCommand.options,...J?J.options:[]],$=F6(Z),Q=[],q=z.indexOf("--");if(q>-1)Q=z.slice(q+1),z=z.slice(0,q);let X=X6(z,$),j={_:X._};for(let H of Object.keys(X))if(H!=="_")j[e(H)]=X[H];let Y=j._,U={"--":Q},F=J&&J.config.ignoreOptionDefaultValue?J.config.ignoreOptionDefaultValue:this.globalCommand.config.ignoreOptionDefaultValue,G=Object.create(null);for(let H of Z){if(!F&&H.config.default!==void 0)for(let K of H.names)U[K]=H.config.default;if(Array.isArray(H.config.type)){if(G[H.name]===void 0)G[H.name]={shouldTransform:!0,transformFunction:H.config.type[0]}}}for(let H of Object.keys(j))if(H!=="_"){let K=H.split(".");H6(U,K,j[H]),K6(U,G)}return{args:Y,options:U}}async run(z=l){return this.parse(z,{run:!0,exitOnError:!0})}handleUsageError(z){if(!(!!z&&typeof z==="object"&&z.name==="ClappError"&&z.isUsageError!==!1))return;let $=z,Q=$.message??"command-line error",q=this.name?`${this.name}: `:"",X=/--help/.test(Q)?"":`
|
|
30
|
+
Run \`${this.name??"cli"} --help\` for usage.`;L.stderr.write(`${q}${Q}${X}
|
|
31
|
+
`),L.exit($.exitCode??2)}async runMatchedCommand(){let{args:z,options:J,matchedCommand:Z}=this;if(!Z||!Z.commandAction)return;Z.checkUnknownOptions(),Z.checkOptionValue(),Z.checkRequiredArgs();let $=[];Z.args.forEach((j,Y)=>{if(j.variadic)$.push(z.slice(Y));else $.push(z[Y])}),$.push(J);let Q={command:Z,args:$,options:J};for(let j of Z.beforeHooks)await j(Q);let q,X=async()=>{let j=Z.commandAction.apply(this,$);if(j instanceof Promise)q=await j;else q=j;return q};if(Z.middleware.length>0){let j=0,Y=async()=>{if(j<Z.middleware.length){let U=Z.middleware[j++];await U({...Q,next:Y})}else await X()};await Y()}else await X();for(let j of Z.afterHooks)await j(Q);return q}removeSignalHandlers(){if(!this.signalHandlersSet)return this;return L.removeAllListeners("SIGINT"),L.removeAllListeners("SIGTERM"),this.signalHandlersSet=!1,this}destroy(){this.removeSignalHandlers(),this.commands=[],this.rawArgs=[],this.args=[],this.options={},this.matchedCommand=void 0,this.matchedCommandName=void 0,this.removeAllListeners()}}class Z6{cache;enabled;cleanupInterval=null;hits=0;misses=0;constructor(){this.cache=new Map,this.enabled=!0,this.startCleanupInterval()}startCleanupInterval(){if(this.cleanupInterval)return;this.cleanupInterval=setInterval(()=>{this.cleanup()},30000),this.cleanupInterval.unref()}stopCleanup(){if(this.cleanupInterval)clearInterval(this.cleanupInterval),this.cleanupInterval=null}isEnabled(){return this.enabled}get(z){if(!this.enabled){this.misses++;return}let J=this.cache.get(z);if(!J){this.misses++;return}if(Date.now()-J.timestamp>J.ttl){this.cache.delete(z),this.misses++;return}return this.hits++,J.value}set(z,J,Z=5000){if(!this.enabled)return;this.cache.set(z,{value:J,timestamp:Date.now(),ttl:Z})}has(z){if(!this.enabled)return!1;let J=this.cache.get(z);if(!J)return!1;if(Date.now()-J.timestamp>J.ttl)return this.cache.delete(z),!1;return!0}delete(z){this.cache.delete(z)}clear(){this.cache.clear()}disable(){this.enabled=!1,this.clear()}enable(){this.enabled=!0}stats(){return{size:this.cache.size,enabled:this.enabled,hits:this.hits,misses:this.misses}}resetStats(){this.hits=0,this.misses=0}keys(){return Array.from(this.cache.keys())}cleanup(){let z=Date.now();for(let[J,Z]of this.cache.entries())if(z-Z.timestamp>Z.ttl)this.cache.delete(J)}destroy(){this.stopCleanup(),this.clear(),this.resetStats()}}var Bz=new Z6;function O6(){if("FORCE_COLOR"in B.env)return B.env.FORCE_COLOR!=="0";if("NO_COLOR"in B.env||B.env.TERM==="dumb")return!1;if(B.platform==="win32")return!0;return r.isatty(1)&&r.isatty(2)}var $6=O6();function W(z,J){if(!$6)return(Z)=>Z;return(Z)=>z+Z+J}var T6=W("\x1B[0m","\x1B[0m"),B6=W("\x1B[31m","\x1B[39m"),E6=W("\x1B[32m","\x1B[39m"),x6=W("\x1B[33m","\x1B[39m"),I6=W("\x1B[34m","\x1B[39m"),b6=W("\x1B[35m","\x1B[39m"),A6=W("\x1B[36m","\x1B[39m"),R6=W("\x1B[37m","\x1B[39m"),C6=W("\x1B[90m","\x1B[39m"),h6=W("\x1B[1m","\x1B[22m"),k6=W("\x1B[3m","\x1B[23m"),S6=W("\x1B[4m","\x1B[24m"),y6=W("\x1B[2m","\x1B[22m"),v6=W("\x1B[7m","\x1B[27m"),g6=W("\x1B[8m","\x1B[28m"),_6=W("\x1B[9m","\x1B[29m"),f6=W("\x1B[41m","\x1B[49m"),m6=W("\x1B[42m","\x1B[49m"),d6=W("\x1B[43m","\x1B[49m"),u6=W("\x1B[44m","\x1B[49m"),c6=W("\x1B[45m","\x1B[49m"),p6=W("\x1B[46m","\x1B[49m"),n6=W("\x1B[47m","\x1B[49m"),i6=$6,l6={reset:T6,red:B6,green:E6,yellow:x6,blue:I6,magenta:b6,cyan:A6,white:R6,gray:C6,bold:h6,italic:k6,underline:S6,dim:y6,inverse:v6,hidden:g6,strikethrough:_6,bgRed:f6,bgGreen:m6,bgYellow:d6,bgBlue:u6,bgMagenta:c6,bgCyan:p6,bgWhite:n6,isColorSupported:i6},A=l6,a6=["up","down","left","right","space","enter","cancel"],R={actions:new Set(a6),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["\x03","cancel"],["escape","cancel"]]),messages:{cancel:"Canceled",error:"Something went wrong"}};function Q6(z,J){if(typeof z==="string")return R.aliases.get(z)===J;for(let Z of z){if(Z===void 0)continue;if(Q6(Z,J))return!0}return!1}function s6(z,J){if(z===J)return[];let Z=z.split(`
|
|
32
|
+
`),$=J.split(`
|
|
33
|
+
`),Q=[];for(let q=0;q<Math.max(Z.length,$.length);q++)if(Z[q]!==$[q])Q.push(q);return Q}var Rz=r6.platform.startsWith("win"),a=Symbol("clapp:cancel");function x(z,J){let Z=z;if(Z.isTTY)Z.setRawMode(J)}function zz(z){return z.replace(/\x1b\[[0-9;]*m/g,"")}function Jz(z){return zz(z).length}function t(z,J,Z){if(!J||J<1)return z;let $=Z?.hard??!1,Q=Z?.trim??!0,q=z.split(`
|
|
34
|
+
`),X=[];for(let j of q){if(Jz(j)<=J){X.push(Q?j.trimEnd():j);continue}if($){let Y="",U=0,F=0;while(F<j.length){let G=j.slice(F).match(/^\x1b\[[0-9;]*m/);if(G){Y+=G[0],F+=G[0].length;continue}if(U>=J)X.push(Q?Y.trimEnd():Y),Y="",U=0;Y+=j[F],U++,F++}if(Y)X.push(Q?Y.trimEnd():Y)}else X.push(Q?j.trimEnd():j)}return X.join(`
|
|
35
|
+
`)}class d{input;output;_abortSignal;rl;opts;_render;_track=!1;_prevFrame="";_subscribers=new Map;_cursor=0;_manualLine="";state="initial";error="";value;userInput="";constructor(z,J=!0){let{input:Z=t6,output:$=o6,render:Q,signal:q,...X}=z;this.opts=X,this.onKeypress=this.onKeypress.bind(this),this.close=this.close.bind(this),this.render=this.render.bind(this),this._render=Q.bind(this),this._track=J,this._abortSignal=q,this.input=Z,this.output=$}unsubscribe(){this._subscribers.clear()}setSubscriber(z,J){let Z=this._subscribers.get(z)??[];Z.push(J),this._subscribers.set(z,Z)}on(z,J){return this.setSubscriber(z,{cb:J}),this}once(z,J){return this.setSubscriber(z,{cb:J,once:!0}),this}emit(z,...J){let Z=this._subscribers.get(z)??[],$=[];for(let Q of Z)if(Q.cb(...J),Q.once)$.push(()=>Z.splice(Z.indexOf(Q),1));for(let Q of $)Q();return this}prompt(){return new Promise((z)=>{if(this._abortSignal){if(this._abortSignal.aborted)return this.state="cancel",this.close(),z(a);this._abortSignal.addEventListener("abort",()=>{this.state="cancel",this.close()},{once:!0})}if(this.rl=e6.createInterface({input:this.input,tabSize:2,prompt:"",escapeCodeTimeout:50,terminal:!0}),this.rl.prompt(),this.opts.initialUserInput!==void 0)this._setUserInput(this.opts.initialUserInput,!0);this.input.on("keypress",this.onKeypress),x(this.input,!0),this.output.on("resize",this.render),this.render(),this.once("submit",()=>{this.output.write(D.show),this.output.off("resize",this.render),x(this.input,!1),z(this.value)}),this.once("cancel",()=>{this.output.write(D.show),this.output.off("resize",this.render),x(this.input,!1),z(a)})})}_isActionKey(z,J){return z==="\t"}_setValue(z){this.value=z,this.emit("value",this.value)}_setUserInput(z,J){if(this.userInput=z??"",this.emit("userInput",this.userInput),J&&this._track&&this.rl)this.rl.write(this.userInput),this._cursor=this.rl.cursor}onKeypress(z,J){if(this._track&&J.name!=="return"){if(J.name&&this._isActionKey(z,J))this.rl?.write(null,{ctrl:!0,name:"h"});this._cursor=this.rl?.cursor??0;let Z=J.name==="tab"||J.name==="escape"||J.name==="backspace"||J.name==="delete"||J.name==="enter"||J.name==="return"||J.name&&["up","down","left","right"].includes(J.name);if(z&&!Z&&z.length===1&&z>=" ")this._manualLine+=z;else if(J.name==="backspace"&&this._manualLine.length>0)this._manualLine=this._manualLine.slice(0,-1);let $=this._manualLine.length>=(this.rl?.line?.length||0)?this._manualLine:this.rl?.line;this._setUserInput($)}if(this.state==="error")this.state="active",this.error="";if(J?.name){if(!this._track&&R.aliases.has(J.name))this.emit("cursor",R.aliases.get(J.name));if(R.actions.has(J.name))this.emit("cursor",J.name)}if(z&&(z.toLowerCase()==="y"||z.toLowerCase()==="n"))this.emit("confirm",z.toLowerCase()==="y");if(this.emit("key",z?.toLowerCase(),J),J?.name==="return"){if(this.opts.validate){let Z=this.opts.validate(this.value);if(Z)this.error=Z instanceof Error?Z.message:Z,this.state="error"}if(this.state!=="error")this.state="submit"}if(Q6([z,J?.name,J?.sequence],"cancel"))this.state="cancel";if(this.state==="submit"||this.state==="cancel")this.emit("finalize");if(this.render(),this.state==="submit"||this.state==="cancel")this.close()}close(){if(this.input.unpipe(),this.input.removeListener("keypress",this.onKeypress),this.output.write(`
|
|
36
|
+
`),x(this.input,!1),this.rl?.close(),this.rl=void 0,this.emit(`${this.state}`,this.value),this.state==="cancel")setTimeout(()=>{this.unsubscribe()},10);else this.unsubscribe()}restoreCursor(){let z=t(this._prevFrame,s.stdout.columns,{hard:!0,trim:!1}).split(`
|
|
37
|
+
`).length-1;this.output.write(D.move(-999,z*-1))}render(){let z=t(this._render(this)??"",s.stdout.columns,{hard:!0,trim:!1});if(z===this._prevFrame)return;if(this.state==="initial")this.output.write(D.hide);else{let J=s6(this._prevFrame,z);if(this.restoreCursor(),J&&J?.length===1){let Z=J[0];this.output.write(D.move(0,Z)),this.output.write(y.lines(1));let $=z.split(`
|
|
38
|
+
`);this.output.write($[Z]),this._prevFrame=z,this.output.write(D.move(0,$.length-Z-1));return}if(J&&J?.length>1){let Z=J[0];this.output.write(D.move(0,Z)),this.output.write(y.down());let Q=z.split(`
|
|
39
|
+
`).slice(Z);this.output.write(Q.join(`
|
|
40
|
+
`)),this._prevFrame=z;return}this.output.write(y.down())}if(this.output.write(z),this.state==="initial")this.state="active";this._prevFrame=z}}function Zz(z,J){if(z===void 0)return 0;if(J.length===0)return 0;let $=J.findIndex((Q)=>Q.value===z);return $!==-1?$:0}function $z(z,J){return(J.label??String(J.value)).toLowerCase().includes(z.toLowerCase())}function Qz(z,J){if(!J)return;if(z)return J;return J[0]}class Yz extends d{filteredOptions;multiple;isNavigating=!1;selectedValues=[];focusedValue;#z=0;#Z="";#$;#J;get cursor(){return this.#z}get userInputWithCursor(){if(!this.userInput)return A.inverse(A.hidden("_"));if(this._cursor>=this.userInput.length)return`${this.userInput}█`;let z=this.userInput.slice(0,this._cursor),[J,...Z]=this.userInput.slice(this._cursor);return`${z}${A.inverse(J)}${Z.join("")}`}get options(){if(typeof this.#J==="function")return this.#J();return this.#J}constructor(z){super(z);this.#J=z.options;let J=this.options;this.filteredOptions=[...J],this.multiple=z.multiple===!0,this.#$=z.filter??$z;let Z;if(z.initialValue&&Array.isArray(z.initialValue))if(this.multiple)Z=z.initialValue;else Z=z.initialValue.slice(0,1);else if(!this.multiple&&this.options.length>0)Z=[this.options[0].value];if(Z)for(let $ of Z){let Q=J.findIndex((q)=>q.value===$);if(Q!==-1)this.toggleSelected($),this.#z=Q}this.focusedValue=this.options[this.#z]?.value,this.on("key",($,Q)=>this.#Q($,Q)),this.on("userInput",($)=>this.#Y($))}_isActionKey(z,J){return z==="\t"||this.multiple&&this.isNavigating&&J.name==="space"&&z!==void 0&&z!==""}#Q(z,J){let Z=J.name==="up",$=J.name==="down",Q=J.name==="return";if(Z||$){if(this.#z=Math.max(0,Math.min(this.#z+(Z?-1:1),this.filteredOptions.length-1)),this.focusedValue=this.filteredOptions[this.#z]?.value,!this.multiple)this.selectedValues=[this.focusedValue];this.isNavigating=!0}else if(Q)this.value=Qz(this.multiple,this.selectedValues);else if(this.multiple)if(this.focusedValue!==void 0&&(J.name==="tab"||this.isNavigating&&J.name==="space"))this.toggleSelected(this.focusedValue);else this.isNavigating=!1;else{if(this.focusedValue)this.selectedValues=[this.focusedValue];this.isNavigating=!1}}deselectAll(){this.selectedValues=[]}toggleSelected(z){if(this.filteredOptions.length===0)return;if(this.multiple)if(this.selectedValues.includes(z))this.selectedValues=this.selectedValues.filter((J)=>J!==z);else this.selectedValues=[...this.selectedValues,z];else this.selectedValues=[z]}#Y(z){if(z!==this.#Z){this.#Z=z;let J=this.options;if(z)this.filteredOptions=J.filter((Z)=>this.#$(z,Z));else this.filteredOptions=[...J];if(this.#z=Zz(this.focusedValue,this.filteredOptions),this.focusedValue=this.filteredOptions[this.#z]?.value,!this.multiple)if(this.focusedValue!==void 0)this.toggleSelected(this.focusedValue);else this.deselectAll()}}}class qz extends d{options;cursor=0;#z;getGroupItems(z){return this.options.filter((J)=>J.group===z)}isGroupSelected(z){let J=this.getGroupItems(z),Z=this.value;if(Z===void 0)return!1;return J.every(($)=>Z.includes($.value))}toggleValue(){let z=this.options[this.cursor];if(this.value===void 0)this.value=[];if(z.group===!0){let J=String(z.value),Z=this.getGroupItems(J);if(this.isGroupSelected(J))this.value=this.value.filter(($)=>Z.findIndex((Q)=>Q.value===$)===-1);else this.value=[...this.value,...Z.map(($)=>$.value)];this.value=Array.from(new Set(this.value))}else{let J=this.value.includes(z.value);this.value=J?this.value.filter((Z)=>Z!==z.value):[...this.value,z.value]}}constructor(z){super(z,!1);let{options:J}=z;this.#z=z.selectableGroups!==!1,this.options=Object.entries(J).flatMap(([Z,$])=>[{value:Z,group:!0,label:Z},...$.map((Q)=>({...Q,group:Z}))]),this.value=[...z.initialValues??[]],this.cursor=Math.max(this.options.findIndex(({value:Z})=>Z===z.cursorAt),this.#z?0:1),this.on("cursor",(Z)=>{switch(Z){case"left":case"up":{this.cursor=this.cursor===0?this.options.length-1:this.cursor-1;let $=this.options[this.cursor]?.group===!0;if(!this.#z&&$)this.cursor=this.cursor===0?this.options.length-1:this.cursor-1;break}case"down":case"right":{this.cursor=this.cursor===this.options.length-1?0:this.cursor+1;let $=this.options[this.cursor]?.group===!0;if(!this.#z&&$)this.cursor=this.cursor===this.options.length-1?0:this.cursor+1;break}case"space":this.toggleValue();break}})}}var jz=W6();var V=(z,J)=>jz?z:J,kz=V("◆","*"),Sz=V("■","x"),yz=V("▲","x"),vz=V("◇","o"),gz=V("┌","T"),Xz=V("│","|"),_z=V("└","—"),fz=V("●",">"),mz=V("○"," "),dz=V("◻","[•]"),uz=V("◼","[+]"),cz=V("◻","[ ]"),pz=V("▪","•"),nz=V("─","-"),iz=V("╮","+"),lz=V("├","+"),rz=V("╯","+"),az=V("●","•"),sz=V("◆","*"),tz=V("▲","!"),oz=V("■","x");var ez={light:V("─","-"),heavy:V("━","="),block:V("█","#")};function Uz(){return`${A.gray(Xz)} `}var z4=Uz();var u="0.1.0";var N=new m("watch");N.command("detect","Detect connected watch devices").option("--verbose","Enable verbose output").action(async(z)=>{let J=E();if(z.verbose)console.log(`Searching for connected watch devices...
|
|
41
|
+
`);let Z=await J.detectDevices();if(Z.length===0){console.log("No watch devices found."),console.log(`
|
|
42
|
+
Make sure your watch is:`),console.log(" 1. Connected via USB, or"),console.log(" 2. Synced via Garmin Express (data will be read from cache)");return}console.log(`Found ${Z.length} device(s):
|
|
43
|
+
`);for(let $ of Z){if(console.log(` ${$.name}`),console.log(` Type: ${$.type}`),$.isGarminExpress)console.log(" Source: Garmin Express cache");else console.log(` Path: ${$.path}`);if($.model)console.log(` Model: ${$.model}`);if($.serial)console.log(` Serial: ${$.serial}`);if($.firmware)console.log(` Firmware: ${$.firmware}`);console.log()}});N.command("download","Download data from connected watch").option("-o, --output <dir>","Output directory",{default:"./watch-data"}).option("--since <date>","Only download data after this date (YYYY-MM-DD)").option("--until <date>","Only download data before this date (YYYY-MM-DD)").option("--activities","Download activity data only").option("--monitoring","Download monitoring data only").option("--no-raw","Do not copy raw FIT files").option("--verbose","Enable verbose output").action(async(z)=>{let J=E();console.log("Detecting devices...");let Z=await J.detectDevices();if(Z.length===0)console.error("No watch devices found."),console.error(`
|
|
44
|
+
Make sure your watch is:`),console.error(" 1. Connected via USB"),console.error(" 2. In Mass Storage mode (Settings > System > USB Mode)"),console.error(" 3. Garmin Express is closed (it may lock the device)"),process.exit(1);let $=Z[0];console.log(`Found ${$.name} at ${$.path}
|
|
45
|
+
`);let Q={outputDir:z.output,includeActivities:z.monitoring!==!0,includeMonitoring:z.activities!==!0,copyRawFiles:z.raw!==!1,since:z.since?new Date(z.since):void 0,until:z.until?new Date(z.until):void 0};console.log("Downloading data...");let q=await J.downloadData($,Q);if(console.log(`
|
|
46
|
+
Download complete!`),console.log(` Activities: ${q.activities.length}`),console.log(` Monitoring days: ${q.monitoring.size}`),console.log(` Raw files copied: ${q.rawFiles.length}`),q.errors.length>0){if(console.log(` Errors: ${q.errors.length}`),z.verbose)for(let X of q.errors)console.error(` - ${X.message}`)}if(q.activities.length>0&&z.verbose){console.log(`
|
|
47
|
+
Activities:`);for(let X of q.activities.slice(-10)){let j=X.startTime.toLocaleDateString(),Y=Math.round(X.totalTimerTime/60),U=(X.totalDistance/1000).toFixed(2);console.log(` ${j} - ${X.sport} - ${Y}min - ${U}km`)}if(q.activities.length>10)console.log(` ... and ${q.activities.length-10} more`)}});N.command("parse <file>","Parse a FIT file").option("-o, --output <file>","Output file (defaults to stdout)").option("-f, --format <format>","Output format: json, summary",{default:"summary"}).option("--verbose","Enable verbose output").action(async(z,J)=>{let Z=E();try{let $=await Z.parseActivityFile(z);if(!$){let Q=await Z.parseMonitoringFile(z);if(J.format==="json"){let q=JSON.stringify(Q,null,2);if(J.output)await Bun.write(J.output,q),console.log(`Written to ${J.output}`);else console.log(q)}else{if(console.log("Monitoring Data:"),Q.heartRate)console.log(` Heart Rate: avg ${Q.heartRate.avgHeartRate} bpm`);if(Q.sleep)console.log(` Sleep: ${Math.round(Q.sleep.totalSleepTime)} minutes`);if(Q.stress)console.log(` Stress: avg ${Q.stress.avgStressLevel}`);if(Q.spO2)console.log(` SpO2: avg ${Q.spO2.avgSpO2}%`)}return}if(J.format==="json"){let Q=JSON.stringify($,null,2);if(J.output)await Bun.write(J.output,Q),console.log(`Written to ${J.output}`);else console.log(Q)}else{if(console.log("Activity Summary:"),console.log(` Sport: ${$.sport}${$.subSport?` (${$.subSport})`:""}`),console.log(` Date: ${$.startTime.toLocaleString()}`),console.log(` Duration: ${Math.round($.totalTimerTime/60)} minutes`),console.log(` Distance: ${($.totalDistance/1000).toFixed(2)} km`),console.log(` Calories: ${$.totalCalories}`),$.avgHeartRate)console.log(` Avg Heart Rate: ${$.avgHeartRate} bpm`);if($.maxHeartRate)console.log(` Max Heart Rate: ${$.maxHeartRate} bpm`);if($.avgSpeed){let Q=16.666666666666668/$.avgSpeed;console.log(` Avg Pace: ${Math.floor(Q)}:${String(Math.round(Q%1*60)).padStart(2,"0")} /km`)}if($.totalAscent)console.log(` Total Ascent: ${$.totalAscent} m`);if($.avgCadence)console.log(` Avg Cadence: ${$.avgCadence} spm`);if($.avgPower)console.log(` Avg Power: ${$.avgPower} W`);console.log(`
|
|
48
|
+
Laps: ${$.laps.length}`),console.log(` Data Points: ${$.records.length}`)}}catch($){console.error(`Error parsing file: ${$ instanceof Error?$.message:$}`),process.exit(1)}});N.command("watch","Watch for device connection and auto-download").option("-o, --output <dir>","Output directory",{default:"./watch-data"}).option("--interval <ms>","Check interval in milliseconds",{default:2000}).action(async(z)=>{let J=E(),Z=null;console.log("Watching for device connection..."),console.log(`Press Ctrl+C to stop
|
|
49
|
+
`);let $=async()=>{let Q=await J.detectDevices();if(Q.length>0&&Q[0].path!==Z){let q=Q[0];Z=q.path,console.log(`
|
|
50
|
+
Device connected: ${q.name}`),console.log(`Starting download...
|
|
51
|
+
`);let X=await J.downloadData(q,{outputDir:z.output,copyRawFiles:!0});if(console.log("Download complete!"),console.log(` Activities: ${X.activities.length}`),console.log(` Monitoring days: ${X.monitoring.size}`),console.log(` Raw files: ${X.rawFiles.length}`),X.errors.length>0)console.log(` Errors: ${X.errors.length}`);console.log(`
|
|
52
|
+
Continuing to watch...`)}else if(Q.length===0&&Z!==null)console.log("Device disconnected"),Z=null};await $(),setInterval($,z.interval)});N.command("version","Show the version").action(()=>{console.log(u)});N.version(u);N.help();N.parse();
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createRequire as s}from"node:module";var qq=s(import.meta.url);var S=14,D=12,E=".FIT",O={ENUM:0,SINT8:1,UINT8:2,SINT16:131,UINT16:132,SINT32:133,UINT32:134,STRING:7,FLOAT32:136,FLOAT64:137,UINT8Z:10,UINT16Z:139,UINT32Z:140,BYTE:13,SINT64:142,UINT64:143,UINT64Z:144},Qq={[O.ENUM]:1,[O.SINT8]:1,[O.UINT8]:1,[O.SINT16]:2,[O.UINT16]:2,[O.SINT32]:4,[O.UINT32]:4,[O.STRING]:1,[O.FLOAT32]:4,[O.FLOAT64]:8,[O.UINT8Z]:1,[O.UINT16Z]:2,[O.UINT32Z]:4,[O.BYTE]:1,[O.SINT64]:8,[O.UINT64]:8,[O.UINT64Z]:8},R={FILE_ID:0,CAPABILITIES:1,DEVICE_SETTINGS:2,USER_PROFILE:3,HRM_PROFILE:4,SDM_PROFILE:5,BIKE_PROFILE:6,ZONES_TARGET:7,HR_ZONE:8,POWER_ZONE:9,MET_ZONE:10,SPORT:12,GOAL:15,SESSION:18,LAP:19,RECORD:20,EVENT:21,DEVICE_INFO:23,WORKOUT:26,WORKOUT_STEP:27,SCHEDULE:28,WEIGHT_SCALE:30,COURSE:31,COURSE_POINT:32,TOTALS:33,ACTIVITY:34,SOFTWARE:35,FILE_CAPABILITIES:37,MESG_CAPABILITIES:38,FIELD_CAPABILITIES:39,FILE_CREATOR:49,BLOOD_PRESSURE:51,SPEED_ZONE:53,MONITORING:55,TRAINING_FILE:72,HRV:78,ANT_RX:80,ANT_TX:81,ANT_CHANNEL_ID:82,LENGTH:101,MONITORING_INFO:103,PAD:105,SLAVE_DEVICE:106,CONNECTIVITY:127,WEATHER_CONDITIONS:128,WEATHER_ALERT:129,CADENCE_ZONE:131,HR:132,SEGMENT_LAP:142,MEMO_GLOB:145,SEGMENT_ID:148,SEGMENT_LEADERBOARD_ENTRY:149,SEGMENT_POINT:150,SEGMENT_FILE:151,WORKOUT_SESSION:158,WATCHFACE_SETTINGS:159,GPS_METADATA:160,CAMERA_EVENT:161,TIMESTAMP_CORRELATION:162,GYROSCOPE_DATA:164,ACCELEROMETER_DATA:165,THREE_D_SENSOR_CALIBRATION:167,VIDEO_FRAME:169,OBDII_DATA:174,NMEA_SENTENCE:177,AVIATION_ATTITUDE:178,VIDEO:184,VIDEO_TITLE:185,VIDEO_DESCRIPTION:186,VIDEO_CLIP:187,OHR_SETTINGS:188,EXD_SCREEN_CONFIGURATION:200,EXD_DATA_FIELD_CONFIGURATION:201,EXD_DATA_CONCEPT_CONFIGURATION:202,FIELD_DESCRIPTION:206,DEVELOPER_DATA_ID:207,MAGNETOMETER_DATA:208,BAROMETER_DATA:209,ONE_D_SENSOR_CALIBRATION:210,MONITORING_HR_DATA:211,TIME_IN_ZONE:216,SET:225,STRESS_LEVEL:227,MAX_MET_DATA:229,DIVE_SETTINGS:258,DIVE_GAS:259,DIVE_ALARM:262,EXERCISE_TITLE:264,DIVE_SUMMARY:268,SPO2_DATA:269,SLEEP_LEVEL:275,JUMP:285,BEAT_INTERVALS:290,RESPIRATION_RATE:297,SPLIT:312,CLIMB_PRO:317,TANK_UPDATE:319,TANK_SUMMARY:323,SLEEP_ASSESSMENT:346,HRV_STATUS_SUMMARY:370,HRV_VALUE:371,DEVICE_AUX_BATTERY_INFO:375,DIVE_APNEA_ALARM:393,SKIN_TEMP_OVERNIGHT:398},$={FILE_ID:{TYPE:0,MANUFACTURER:1,PRODUCT:2,SERIAL_NUMBER:3,TIME_CREATED:4,NUMBER:5,PRODUCT_NAME:8},SESSION:{TIMESTAMP:253,START_TIME:2,START_POSITION_LAT:3,START_POSITION_LONG:4,TOTAL_ELAPSED_TIME:7,TOTAL_TIMER_TIME:8,TOTAL_DISTANCE:9,TOTAL_CYCLES:10,TOTAL_CALORIES:11,TOTAL_FAT_CALORIES:13,AVG_SPEED:14,MAX_SPEED:15,AVG_HEART_RATE:16,MAX_HEART_RATE:17,AVG_CADENCE:18,MAX_CADENCE:19,AVG_POWER:20,MAX_POWER:21,TOTAL_ASCENT:22,TOTAL_DESCENT:23,SPORT:5,SUB_SPORT:6,EVENT:0,EVENT_TYPE:1,FIRST_LAP_INDEX:25,NUM_LAPS:26,NORMALIZED_POWER:34,TRAINING_STRESS_SCORE:35,INTENSITY_FACTOR:36,AVG_TEMPERATURE:57,MAX_TEMPERATURE:58},LAP:{TIMESTAMP:253,EVENT:0,EVENT_TYPE:1,START_TIME:2,START_POSITION_LAT:3,START_POSITION_LONG:4,END_POSITION_LAT:5,END_POSITION_LONG:6,TOTAL_ELAPSED_TIME:7,TOTAL_TIMER_TIME:8,TOTAL_DISTANCE:9,TOTAL_CYCLES:10,TOTAL_CALORIES:11,TOTAL_FAT_CALORIES:12,AVG_SPEED:13,MAX_SPEED:14,AVG_HEART_RATE:15,MAX_HEART_RATE:16,AVG_CADENCE:17,MAX_CADENCE:18,AVG_POWER:19,MAX_POWER:20,TOTAL_ASCENT:21,TOTAL_DESCENT:22,SPORT:23,SUB_SPORT:24,AVG_TEMPERATURE:50,MAX_TEMPERATURE:51},RECORD:{TIMESTAMP:253,POSITION_LAT:0,POSITION_LONG:1,ALTITUDE:2,HEART_RATE:3,CADENCE:4,DISTANCE:5,SPEED:6,POWER:7,COMPRESSED_SPEED_DISTANCE:8,GRADE:9,RESISTANCE:10,TIME_FROM_COURSE:11,CYCLE_LENGTH:12,TEMPERATURE:13,SPEED_1S:17,CYCLES:18,TOTAL_CYCLES:19,COMPRESSED_ACCUMULATED_POWER:28,ACCUMULATED_POWER:29,LEFT_RIGHT_BALANCE:30,GPS_ACCURACY:31,VERTICAL_SPEED:32,CALORIES:33,VERTICAL_OSCILLATION:39,STANCE_TIME_PERCENT:40,STANCE_TIME:41,ACTIVITY_TYPE:42,LEFT_TORQUE_EFFECTIVENESS:43,RIGHT_TORQUE_EFFECTIVENESS:44,LEFT_PEDAL_SMOOTHNESS:45,RIGHT_PEDAL_SMOOTHNESS:46,COMBINED_PEDAL_SMOOTHNESS:47,TIME128:48,STROKE_TYPE:49,ZONE:50,BALL_SPEED:51,CADENCE256:52,FRACTIONAL_CADENCE:53,TOTAL_HEMOGLOBIN_CONC:54,TOTAL_HEMOGLOBIN_CONC_MIN:55,TOTAL_HEMOGLOBIN_CONC_MAX:56,SATURATED_HEMOGLOBIN_PERCENT:57,SATURATED_HEMOGLOBIN_PERCENT_MIN:58,SATURATED_HEMOGLOBIN_PERCENT_MAX:59,DEVICE_INDEX:62,LEFT_PCO:67,RIGHT_PCO:68,LEFT_POWER_PHASE:69,LEFT_POWER_PHASE_PEAK:70,RIGHT_POWER_PHASE:71,RIGHT_POWER_PHASE_PEAK:72,ENHANCED_SPEED:73,ENHANCED_ALTITUDE:78,BATTERY_SOC:81,MOTOR_POWER:82,VERTICAL_RATIO:83,STANCE_TIME_BALANCE:84,STEP_LENGTH:85,ABSOLUTE_PRESSURE:91,DEPTH:92,NEXT_STOP_DEPTH:93,NEXT_STOP_TIME:94,TIME_TO_SURFACE:95,NDL_TIME:96,CNS_LOAD:97,N2_LOAD:98,RESPIRATION_RATE:99,ENHANCED_RESPIRATION_RATE:108,GRIT:114,FLOW:115,CURRENT_STRESS:116,EBIKE_TRAVEL_RANGE:117,EBIKE_BATTERY_LEVEL:118,EBIKE_ASSIST_MODE:119,EBIKE_ASSIST_LEVEL_PERCENT:120,AIR_TIME_REMAINING:123,PRESSURE_SAC:124,VOLUME_SAC:125,RMV:126,ASCENT_RATE:127,PO2:129,CORE_TEMPERATURE:139},ACTIVITY:{TIMESTAMP:253,TOTAL_TIMER_TIME:0,NUM_SESSIONS:1,TYPE:2,EVENT:3,EVENT_TYPE:4,LOCAL_TIMESTAMP:5,EVENT_GROUP:6},MONITORING:{TIMESTAMP:253,DEVICE_INDEX:0,CALORIES:1,DISTANCE:2,CYCLES:3,ACTIVE_TIME:4,ACTIVITY_TYPE:5,ACTIVITY_SUBTYPE:6,ACTIVITY_LEVEL:7,DISTANCE_16:8,CYCLES_16:9,ACTIVE_TIME_16:10,LOCAL_TIMESTAMP:11,TEMPERATURE:12,TEMPERATURE_MIN:14,TEMPERATURE_MAX:15,ACTIVITY_TIME:16,ACTIVE_CALORIES:19,CURRENT_ACTIVITY_TYPE_INTENSITY:24,TIMESTAMP_MIN_8:25,TIMESTAMP_16:26,HEART_RATE:27,INTENSITY:28,DURATION_MIN:29,DURATION:30,ASCENT:31,DESCENT:32,MODERATE_ACTIVITY_MINUTES:33,VIGOROUS_ACTIVITY_MINUTES:34},HRV:{TIME:0},STRESS_LEVEL:{STRESS_LEVEL_VALUE:0,STRESS_LEVEL_TIME:1},SPO2_DATA:{TIMESTAMP:253,READING_SPO2:0,READING_CONFIDENCE:1,MODE:2},SLEEP_LEVEL:{TIMESTAMP:253,SLEEP_LEVEL:0},RESPIRATION_RATE:{TIMESTAMP:253,RESPIRATION_RATE:0},DEVICE_INFO:{TIMESTAMP:253,DEVICE_INDEX:0,DEVICE_TYPE:1,MANUFACTURER:2,SERIAL_NUMBER:3,PRODUCT:4,SOFTWARE_VERSION:5,HARDWARE_VERSION:6,CUM_OPERATING_TIME:7,BATTERY_VOLTAGE:10,BATTERY_STATUS:11,SENSOR_POSITION:18,DESCRIPTOR:19,ANT_TRANSMISSION_TYPE:20,ANT_DEVICE_NUMBER:21,ANT_NETWORK:22,SOURCE_TYPE:25,PRODUCT_NAME:27,BATTERY_LEVEL:32}},B={GENERIC:0,RUNNING:1,CYCLING:2,TRANSITION:3,FITNESS_EQUIPMENT:4,SWIMMING:5,BASKETBALL:6,SOCCER:7,TENNIS:8,AMERICAN_FOOTBALL:9,TRAINING:10,WALKING:11,CROSS_COUNTRY_SKIING:12,ALPINE_SKIING:13,SNOWBOARDING:14,ROWING:15,MOUNTAINEERING:16,HIKING:17,MULTISPORT:18,PADDLING:19,FLYING:20,E_BIKING:21,MOTORCYCLING:22,BOATING:23,DRIVING:24,GOLF:25,HANG_GLIDING:26,HORSEBACK_RIDING:27,HUNTING:28,FISHING:29,INLINE_SKATING:30,ROCK_CLIMBING:31,SAILING:32,ICE_SKATING:33,SKY_DIVING:34,SNOWSHOEING:35,SNOWMOBILING:36,STAND_UP_PADDLEBOARDING:37,SURFING:38,WAKEBOARDING:39,WATER_SKIING:40,KAYAKING:41,RAFTING:42,WINDSURFING:43,KITESURFING:44,TACTICAL:45,JUMPMASTER:46,BOXING:47,FLOOR_CLIMBING:48,DIVING:53,HIIT:62,RACKET:64,WHEELCHAIR_PUSH_WALK:65,WHEELCHAIR_PUSH_RUN:66,MEDITATION:67,DISC_GOLF:69,CRICKET:82,RUGBY:83,HOCKEY:84,LACROSSE:85,VOLLEYBALL:86,WATER_TUBING:87,WAKESURFING:88,MIXED_MARTIAL_ARTS:105,SNORKELING:109,YOGA:123,PILATES:124,INDOOR_RUNNING:125,INDOOR_CYCLING:126,BREATHWORK:134,ALL:254},M={GENERIC:0,TREADMILL:1,STREET:2,TRAIL:3,TRACK:4,SPIN:5,INDOOR_CYCLING:6,ROAD:7,MOUNTAIN:8,DOWNHILL:9,RECUMBENT:10,CYCLOCROSS:11,HAND_CYCLING:12,TRACK_CYCLING:13,INDOOR_ROWING:14,ELLIPTICAL:15,STAIR_CLIMBING:16,LAP_SWIMMING:17,OPEN_WATER:18,FLEXIBILITY_TRAINING:19,STRENGTH_TRAINING:20,WARM_UP:21,MATCH:22,EXERCISE:23,CHALLENGE:24,INDOOR_SKIING:25,CARDIO_TRAINING:26,INDOOR_WALKING:27,E_BIKE_FITNESS:28,BMX:29,CASUAL_WALKING:30,SPEED_WALKING:31,BIKE_TO_RUN_TRANSITION:32,RUN_TO_BIKE_TRANSITION:33,SWIM_TO_BIKE_TRANSITION:34,ATV:35,MOTOCROSS:36,BACKCOUNTRY:37,RESORT:38,RC_DRONE:39,WINGSUIT:40,WHITEWATER:41,SKATE_SKIING:42,YOGA:43,PILATES:44,INDOOR_RUNNING:45,GRAVEL_CYCLING:46,E_BIKE_MOUNTAIN:47,COMMUTING:48,MIXED_SURFACE:49,NAVIGATE:50,TRACK_ME:51,MAP:52,SINGLE_GAS_DIVING:53,MULTI_GAS_DIVING:54,GAUGE_DIVING:55,APNEA_DIVING:56,APNEA_HUNTING:57,VIRTUAL_ACTIVITY:58,OBSTACLE:59,BREATHING:62,SAIL_RACE:65,ULTRA:67,INDOOR_CLIMBING:68,BOULDERING:69,HIIT:70,AMRAP:73,EMOM:74,TABATA:75,PICKLEBALL:84,PADEL:85,INDOOR_WHEELCHAIR_WALK:86,INDOOR_WHEELCHAIR_RUN:87,INDOOR_HAND_CYCLING:88,FLY:110,ALL:254},v={UNMEASURABLE:0,AWAKE:1,LIGHT:2,DEEP:3,REM:4},l=Date.UTC(1989,11,31,0,0,0),Wq={GARMIN:1,GARMIN_FR405_ANTFS:2,ZEPHYR:3,DAYTON:4,IDT:5,SRM:6,QUARQ:7,IBIKE:8,SARIS:9,SPARK_HK:10,TANITA:11,ECHOWELL:12,DYNASTREAM_OEM:13,NAUTILUS:14,DYNASTREAM:15,TIMEX:16,METRIGEAR:17,XELIC:18,BEURER:19,CARDIOSPORT:20,A_AND_D:21,HMM:22,SUUNTO:23,THITA_ELEKTRONIK:24,GPULSE:25,CLEAN_MOBILE:26,PEDAL_BRAIN:27,PEAKSWARE:28,SAXONAR:29,LEMOND_FITNESS:30,DEXCOM:31,WAHOO_FITNESS:32,OCTANE_FITNESS:33,ARCHINOETICS:34,THE_HURT_BOX:35,CITIZEN_SYSTEMS:36,MAGELLAN:37,OSYNCE:38,HOLUX:39,CONCEPT2:40,SHIMANO:72,PIONEER:86,BRYTON:267,ZWIFT:260,WAHOO:32,TACX:89};class P extends Error{offset;constructor(Q,J){super(J!==void 0?`${Q} at offset ${J}`:Q);this.offset=J;this.name="FitParseError"}}class _{buffer;offset=0;definitions=new Map;littleEndian=!0;constructor(Q){if(Q instanceof ArrayBuffer)this.buffer=Buffer.from(Q);else if(Q instanceof Uint8Array)this.buffer=Buffer.from(Q);else this.buffer=Q}parse(){let Q=this.parseHeader(),J=[],k=Q.headerSize+Q.dataSize;while(this.offset<k)try{let X=this.parseRecord();if(X)J.push(X)}catch(X){if(X instanceof P)this.offset++;else throw X}return{header:Q,messages:J}}parseHeader(){if(this.buffer.length<D)throw new P("File too small to contain FIT header");let Q=this.buffer.readUInt8(0);if(Q!==S&&Q!==D)throw new P(`Invalid header size: ${Q}`);let J=this.buffer.readUInt8(1),k=this.buffer.readUInt16LE(2),X=this.buffer.readUInt32LE(4),C=this.buffer.toString("ascii",8,12);if(C!==E)throw new P(`Invalid FIT signature: ${C}`);let q;if(Q===S)q=this.buffer.readUInt16LE(12);return this.offset=Q,{headerSize:Q,protocolVersion:J,profileVersion:k,dataSize:X,dataType:C,crc:q}}parseRecord(){if(this.offset>=this.buffer.length)return null;let Q=this.buffer.readUInt8(this.offset);if(this.offset++,(Q&128)===128){let q=Q>>5&3,H=Q&31;return this.parseDataMessage(q,H)}let k=(Q&64)===64,X=(Q&32)===32,C=Q&15;if(k)return this.parseDefinitionMessage(C,X),null;return this.parseDataMessage(C)}parseDefinitionMessage(Q,J){let k=this.buffer.readUInt8(this.offset);this.offset++;let X=this.buffer.readUInt8(this.offset);this.offset++,this.littleEndian=X===0;let C=this.readUInt16(),q=this.buffer.readUInt8(this.offset);this.offset++;let H=[];for(let W=0;W<q;W++){let b=this.buffer.readUInt8(this.offset);this.offset++;let Z=this.buffer.readUInt8(this.offset);this.offset++;let G=this.buffer.readUInt8(this.offset);this.offset++,H.push({fieldDefNum:b,size:Z,baseType:G})}if(J){let W=this.buffer.readUInt8(this.offset);this.offset++,this.offset+=W*3}this.definitions.set(Q,{reserved:k,arch:this.littleEndian?"little":"big",globalMsgNum:C,numFields:q,fields:H})}parseDataMessage(Q,J){let k=this.definitions.get(Q);if(!k)return null;let X={};for(let C of k.fields){let q=this.readFieldValue(C);if(q!==null&&q!==void 0)X[C.fieldDefNum]=q}return{globalMsgNum:k.globalMsgNum,fields:X}}readFieldValue(Q){let{size:J,baseType:k}=Q;switch(k&31){case O.ENUM:case O.UINT8:case O.UINT8Z:{if(J===1){let q=this.buffer.readUInt8(this.offset);return this.offset++,q===255?null:q}let C=[];for(let q=0;q<J;q++)C.push(this.buffer.readUInt8(this.offset)),this.offset++;return C}case O.SINT8:{if(J===1){let q=this.buffer.readInt8(this.offset);return this.offset++,q===127?null:q}let C=[];for(let q=0;q<J;q++)C.push(this.buffer.readInt8(this.offset)),this.offset++;return C}case O.UINT16:case O.UINT16Z:{if(J===2){let q=this.readUInt16();return q===65535?null:q}let C=[];for(let q=0;q<J/2;q++)C.push(this.readUInt16());return C}case O.SINT16:{if(J===2){let q=this.readInt16();return q===32767?null:q}let C=[];for(let q=0;q<J/2;q++)C.push(this.readInt16());return C}case O.UINT32:case O.UINT32Z:{if(J===4){let q=this.readUInt32();return q===4294967295?null:q}let C=[];for(let q=0;q<J/4;q++)C.push(this.readUInt32());return C}case O.SINT32:{if(J===4){let q=this.readInt32();return q===2147483647?null:q}let C=[];for(let q=0;q<J/4;q++)C.push(this.readInt32());return C}case O.FLOAT32:{if(J===4){let q=this.readFloat32();return Number.isNaN(q)?null:q}let C=[];for(let q=0;q<J/4;q++)C.push(this.readFloat32());return C}case O.FLOAT64:{if(J===8){let q=this.readFloat64();return Number.isNaN(q)?null:q}let C=[];for(let q=0;q<J/8;q++)C.push(this.readFloat64());return C}case O.STRING:{let C=this.buffer.slice(this.offset,this.offset+J);this.offset+=J;let q=C.indexOf(0),H=C.toString("utf8",0,q>=0?q:J);return H.length>0?H:null}case O.UINT64:case O.UINT64Z:{if(J===8)return this.readUInt64();return this.offset+=J,null}case O.SINT64:{if(J===8)return this.readInt64();return this.offset+=J,null}case O.BYTE:default:{let C=this.buffer.slice(this.offset,this.offset+J);return this.offset+=J,C}}}readUInt16(){let Q=this.littleEndian?this.buffer.readUInt16LE(this.offset):this.buffer.readUInt16BE(this.offset);return this.offset+=2,Q}readInt16(){let Q=this.littleEndian?this.buffer.readInt16LE(this.offset):this.buffer.readInt16BE(this.offset);return this.offset+=2,Q}readUInt32(){let Q=this.littleEndian?this.buffer.readUInt32LE(this.offset):this.buffer.readUInt32BE(this.offset);return this.offset+=4,Q}readInt32(){let Q=this.littleEndian?this.buffer.readInt32LE(this.offset):this.buffer.readInt32BE(this.offset);return this.offset+=4,Q}readFloat32(){let Q=this.littleEndian?this.buffer.readFloatLE(this.offset):this.buffer.readFloatBE(this.offset);return this.offset+=4,Q}readFloat64(){let Q=this.littleEndian?this.buffer.readDoubleLE(this.offset):this.buffer.readDoubleBE(this.offset);return this.offset+=8,Q}readUInt64(){let Q=this.littleEndian?this.buffer.readBigUInt64LE(this.offset):this.buffer.readBigUInt64BE(this.offset);return this.offset+=8,Q}readInt64(){let Q=this.littleEndian?this.buffer.readBigInt64LE(this.offset):this.buffer.readBigInt64BE(this.offset);return this.offset+=8,Q}}function h(Q){return new Date(l+Q*1000)}function g(Q){return Q*(180/Math.pow(2,31))}function Cq(Q){return Q*(Math.pow(2,31)/180)}class f{messages;fileType="unknown";deviceInfo=null;constructor(Q){this.messages=Q.messages,this.extractFileInfo()}extractFileInfo(){let Q=this.messages.find((k)=>k.globalMsgNum===R.FILE_ID);if(Q){let k=Q.fields[$.FILE_ID.TYPE];this.fileType=this.resolveFileType(k)}let J=this.messages.find((k)=>k.globalMsgNum===R.DEVICE_INFO);if(J)this.deviceInfo={manufacturer:J.fields[$.DEVICE_INFO.MANUFACTURER],product:J.fields[$.DEVICE_INFO.PRODUCT],serialNumber:J.fields[$.DEVICE_INFO.SERIAL_NUMBER],softwareVersion:J.fields[$.DEVICE_INFO.SOFTWARE_VERSION],productName:J.fields[$.DEVICE_INFO.PRODUCT_NAME]}}resolveFileType(Q){return{1:"device",2:"settings",3:"sport",4:"activity",5:"workout",6:"course",7:"schedules",9:"weight",10:"totals",11:"goals",14:"blood_pressure",15:"monitoring_a",20:"activity_summary",28:"monitoring_daily",32:"monitoring_b",34:"segment",35:"segment_list",40:"exd_configuration"}[Q]||"unknown"}getFileType(){return this.fileType}getDeviceInfo(){return this.deviceInfo}decodeActivity(){if(!["activity","activity_summary"].includes(this.fileType))return null;let Q=this.messages.filter((Z)=>Z.globalMsgNum===R.SESSION),J=this.messages.filter((Z)=>Z.globalMsgNum===R.LAP),k=this.messages.filter((Z)=>Z.globalMsgNum===R.RECORD),X=this.messages.find((Z)=>Z.globalMsgNum===R.ACTIVITY);if(Q.length===0)return null;let q=Q[0].fields,H=q[$.SESSION.START_TIME],W=q[$.SESSION.TIMESTAMP],b={id:this.generateActivityId(H),sport:this.mapSport(q[$.SESSION.SPORT]),subSport:this.mapSubSport(q[$.SESSION.SUB_SPORT]),startTime:h(H),endTime:h(W),totalElapsedTime:q[$.SESSION.TOTAL_ELAPSED_TIME]/1000,totalTimerTime:q[$.SESSION.TOTAL_TIMER_TIME]/1000,totalDistance:q[$.SESSION.TOTAL_DISTANCE]/100,totalCalories:q[$.SESSION.TOTAL_CALORIES],avgHeartRate:q[$.SESSION.AVG_HEART_RATE],maxHeartRate:q[$.SESSION.MAX_HEART_RATE],avgSpeed:q[$.SESSION.AVG_SPEED]!=null?q[$.SESSION.AVG_SPEED]/1000:void 0,maxSpeed:q[$.SESSION.MAX_SPEED]!=null?q[$.SESSION.MAX_SPEED]/1000:void 0,avgCadence:q[$.SESSION.AVG_CADENCE],maxCadence:q[$.SESSION.MAX_CADENCE],avgPower:q[$.SESSION.AVG_POWER],maxPower:q[$.SESSION.MAX_POWER],normalizedPower:q[$.SESSION.NORMALIZED_POWER],totalAscent:q[$.SESSION.TOTAL_ASCENT],totalDescent:q[$.SESSION.TOTAL_DESCENT],trainingStressScore:q[$.SESSION.TRAINING_STRESS_SCORE]!=null?q[$.SESSION.TRAINING_STRESS_SCORE]/10:void 0,intensityFactor:q[$.SESSION.INTENSITY_FACTOR]!=null?q[$.SESSION.INTENSITY_FACTOR]/1000:void 0,laps:J.map((Z)=>this.decodeLap(Z)),records:k.map((Z)=>this.decodeRecord(Z)),source:"garmin"};return this.cleanUndefined(b)}decodeLap(Q){let J=Q.fields,k=J[$.LAP.START_TIME],X=J[$.LAP.TIMESTAMP],C=J[$.LAP.START_POSITION_LAT],q=J[$.LAP.START_POSITION_LONG],H=J[$.LAP.END_POSITION_LAT],W=J[$.LAP.END_POSITION_LONG],b={startTime:h(k),endTime:h(X),totalElapsedTime:J[$.LAP.TOTAL_ELAPSED_TIME]/1000,totalTimerTime:J[$.LAP.TOTAL_TIMER_TIME]/1000,totalDistance:J[$.LAP.TOTAL_DISTANCE]/100,totalCalories:J[$.LAP.TOTAL_CALORIES],avgHeartRate:J[$.LAP.AVG_HEART_RATE],maxHeartRate:J[$.LAP.MAX_HEART_RATE],avgSpeed:J[$.LAP.AVG_SPEED]!=null?J[$.LAP.AVG_SPEED]/1000:void 0,maxSpeed:J[$.LAP.MAX_SPEED]!=null?J[$.LAP.MAX_SPEED]/1000:void 0,avgCadence:J[$.LAP.AVG_CADENCE],maxCadence:J[$.LAP.MAX_CADENCE],avgPower:J[$.LAP.AVG_POWER],maxPower:J[$.LAP.MAX_POWER],totalAscent:J[$.LAP.TOTAL_ASCENT],totalDescent:J[$.LAP.TOTAL_DESCENT],startPosition:C!=null&&q!=null?{lat:g(C),lng:g(q)}:void 0,endPosition:H!=null&&W!=null?{lat:g(H),lng:g(W)}:void 0};return this.cleanUndefined(b)}decodeRecord(Q){let J=Q.fields,k=J[$.RECORD.TIMESTAMP],X=J[$.RECORD.POSITION_LAT],C=J[$.RECORD.POSITION_LONG],q=J[$.RECORD.ENHANCED_ALTITUDE];if(q==null){if(q=J[$.RECORD.ALTITUDE],q!=null)q=q/5-500}else q=q/5-500;let H=J[$.RECORD.ENHANCED_SPEED];if(H==null){if(H=J[$.RECORD.SPEED],H!=null)H=H/1000}else H=H/1000;let W={timestamp:h(k),position:X!=null&&C!=null?{lat:g(X),lng:g(C),altitude:q}:void 0,heartRate:J[$.RECORD.HEART_RATE],cadence:J[$.RECORD.CADENCE],speed:H,power:J[$.RECORD.POWER],altitude:q,distance:J[$.RECORD.DISTANCE]!=null?J[$.RECORD.DISTANCE]/100:void 0,temperature:J[$.RECORD.TEMPERATURE],grade:J[$.RECORD.GRADE]!=null?J[$.RECORD.GRADE]/100:void 0,calories:J[$.RECORD.CALORIES]};return this.cleanUndefined(W)}decodeMonitoring(){let Q={},J=this.messages.filter((W)=>W.globalMsgNum===R.MONITORING);if(J.length>0){let W=[],b=1/0,Z=0,G=0,U=0;for(let K of J){let V=K.fields[$.MONITORING.HEART_RATE],w=K.fields[$.MONITORING.TIMESTAMP];if(V!=null&&w!=null)W.push({timestamp:h(w),heartRate:V}),b=Math.min(b,V),Z=Math.max(Z,V),G+=V,U++}if(W.length>0)Q.heartRate={date:W[0].timestamp,minHeartRate:b,maxHeartRate:Z,avgHeartRate:Math.round(G/U),samples:W}}let k=this.messages.filter((W)=>W.globalMsgNum===R.SLEEP_LEVEL);if(k.length>0){let W=[],b=null;for(let Z of k){let G=Z.fields[$.SLEEP_LEVEL.SLEEP_LEVEL],U=Z.fields[$.SLEEP_LEVEL.TIMESTAMP];if(G!=null&&U!=null){let K=this.mapSleepLevel(G),V=h(U);if(b&&b.stage!==K)b.endTime=V,W.push(b),b={stage:K,startTime:V,endTime:V};else if(!b)b={stage:K,startTime:V,endTime:V}}}if(b)W.push(b);if(W.length>0){let Z=W[0].startTime,G=W[W.length-1].endTime,U=0,K=0,V=0,w=0;for(let Y of W){let x=(Y.endTime.getTime()-Y.startTime.getTime())/60000;switch(Y.stage){case"deep":U+=x;break;case"light":K+=x;break;case"rem":V+=x;break;case"awake":w+=x;break}}Q.sleep={date:Z,startTime:Z,endTime:G,totalSleepTime:U+K+V,deepSleepTime:U,lightSleepTime:K,remSleepTime:V,awakeTime:w,stages:W}}}let X=this.messages.filter((W)=>W.globalMsgNum===R.STRESS_LEVEL);if(X.length>0){let W=[],b=0,Z=0;for(let G of X){let U=G.fields[$.STRESS_LEVEL.STRESS_LEVEL_VALUE],K=G.fields[$.STRESS_LEVEL.STRESS_LEVEL_TIME];if(U!=null&&U>=0&&U<=100&&K!=null)W.push({timestamp:h(K),stressLevel:U}),b+=U,Z=Math.max(Z,U)}if(W.length>0)Q.stress={date:W[0].timestamp,avgStressLevel:Math.round(b/W.length),maxStressLevel:Z,restStressDuration:0,lowStressDuration:0,mediumStressDuration:0,highStressDuration:0,samples:W}}let C=this.messages.filter((W)=>W.globalMsgNum===R.SPO2_DATA);if(C.length>0){let W=[],b=100,Z=0,G=0;for(let U of C){let K=U.fields[$.SPO2_DATA.READING_SPO2],V=U.fields[$.SPO2_DATA.TIMESTAMP];if(K!=null&&V!=null&&K>0&&K<=100)W.push({timestamp:h(V),spO2:K}),b=Math.min(b,K),Z=Math.max(Z,K),G+=K}if(W.length>0)Q.spO2={date:W[0].timestamp,avgSpO2:Math.round(G/W.length),minSpO2:b,maxSpO2:Z,samples:W}}let q=this.messages.filter((W)=>W.globalMsgNum===R.RESPIRATION_RATE);if(q.length>0){let W=[],b=1/0,Z=0,G=0;for(let U of q){let K=U.fields[$.RESPIRATION_RATE.RESPIRATION_RATE],V=U.fields[$.RESPIRATION_RATE.TIMESTAMP];if(K!=null&&V!=null&&K>0){let w=K/100;W.push({timestamp:h(V),respirationRate:w}),b=Math.min(b,w),Z=Math.max(Z,w),G+=w}}if(W.length>0)Q.respiration={date:W[0].timestamp,avgRespirationRate:G/W.length,minRespirationRate:b,maxRespirationRate:Z,samples:W}}let H=this.messages.filter((W)=>W.globalMsgNum===R.HRV);if(H.length>0){let W=[];for(let b of H){let Z=b.fields[$.HRV.TIME];if(Array.isArray(Z)){for(let G of Z)if(G!=null&&G!==65535)W.push({timestamp:new Date,hrv:G/1024*1000})}else if(Z!=null&&Z!==65535)W.push({timestamp:new Date,hrv:Z/1024*1000})}if(W.length>0)Q.hrv={date:new Date,samples:W}}return Q}mapSport(Q){return{[B.RUNNING]:"running",[B.CYCLING]:"cycling",[B.SWIMMING]:"swimming",[B.HIKING]:"hiking",[B.WALKING]:"walking",[B.TRAINING]:"strength_training",[B.FITNESS_EQUIPMENT]:"cardio",[B.YOGA]:"yoga",[B.GENERIC]:"generic"}[Q]||"other"}mapSubSport(Q){return{[M.GENERIC]:"generic",[M.TREADMILL]:"treadmill",[M.STREET]:"street",[M.TRAIL]:"trail",[M.TRACK]:"track",[M.SPIN]:"spin",[M.INDOOR_CYCLING]:"indoor_cycling",[M.ROAD]:"road",[M.MOUNTAIN]:"mountain",[M.GRAVEL_CYCLING]:"gravel",[M.LAP_SWIMMING]:"lap_swimming",[M.OPEN_WATER]:"open_water"}[Q]}mapSleepLevel(Q){return{[v.AWAKE]:"awake",[v.LIGHT]:"light",[v.DEEP]:"deep",[v.REM]:"rem"}[Q]||"light"}generateActivityId(Q){return`garmin_${Q}`}cleanUndefined(Q){let J={};for(let[k,X]of Object.entries(Q))if(X!==void 0&&X!==null)J[k]=X;return J}}import{existsSync as y,readdirSync as T,statSync as u,mkdirSync as p,copyFileSync as c,readFileSync as F}from"fs";import{join as N,extname as r}from"path";import{homedir as i}from"os";var m="/Volumes",d=N(i(),"Library/Application Support/Garmin/Express/RegisteredDevices"),t=["GARMIN","ENDURO","FENIX","FORERUNNER","VENU","INSTINCT","VIVOACTIVE","EPIX","TACTIX","MARQ","QUATIX","DESCENT","APPROACH","LILY","VIVOSMART","VIVOMOVE"],n={ACTIVITY:"GARMIN/Activity",MONITOR:"GARMIN/Monitor",SPORTS:"GARMIN/Sports",SETTINGS:"GARMIN/Settings",TOTALS:"GARMIN/Totals",RECORDS:"GARMIN/Records",METRICS:"GARMIN/Metrics",SLEEP:"GARMIN/Sleep",WORKOUTS:"GARMIN/Workouts",COURSES:"GARMIN/Courses",LOCATIONS:"GARMIN/Locations",GOALS:"GARMIN/Goals",SCHEDULES:"GARMIN/Schedules"};class a{name="Garmin";type="garmin";async detectDevices(){let Q=[];if(y(m)){let k=T(m);for(let X of k){let C=N(m,X);try{if(!u(C).isDirectory())continue}catch{continue}let q=X.toUpperCase(),H=t.some((W)=>q.includes(W));if(!H){let W=N(C,"GARMIN");H=y(W)}if(H){let W=await this.getDeviceInfo(C,X);if(W)Q.push(W)}}}let J=await this.detectGarminExpressDevices();return Q.push(...J),Q}async detectGarminExpressDevices(){let Q=[];if(!y(d))return Q;let J=T(d);for(let k of J){let X=N(d,k);try{if(!u(X).isDirectory())continue}catch{continue}let C=N(X,"GarminDevice.xml");if(!y(C))continue;try{let q=F(C,"utf8"),H=q.match(/<Description>([^<]+)<\/Description>/i),W=q.match(/<Id>([^<]+)<\/Id>/i),b=q.match(/<DisplayName>([^<]+)<\/DisplayName>/i),Z=q.match(/<PartNumber>([^<]+)<\/PartNumber>/i),G=q.match(/<SoftwareVersion>([^<]+)<\/SoftwareVersion>/i),U=b?.[1]||H?.[1]||k,K=W?.[1]||k,V=G?.[1],w=N(X,"PendingSyncUploads/Garmin");if(!y(w))continue;Q.push({name:U,path:X,type:"garmin",model:U,serial:k,unitId:K,firmware:V,partNumber:Z?.[1],isGarminExpress:!0,garminExpressPath:w})}catch{}}return Q}async getDeviceInfo(Q,J){let k=N(Q,"GARMIN");if(!y(k))return null;let X=J,C,q,H=N(k,"GarminDevice.xml");if(y(H))try{let b=F(H,"utf8"),Z=b.match(/<Model>([^<]+)<\/Model>/i),G=b.match(/<Id>([^<]+)<\/Id>/i);if(Z)X=Z[1];if(G)q=G[1]}catch{}let W=N(Q,n.ACTIVITY);if(y(W)){let b=T(W).filter((Z)=>Z.toLowerCase().endsWith(".fit"));if(b.length>0)try{let Z=N(W,b[0]),G=F(Z),K=new _(G).parse(),w=new f(K).getDeviceInfo();if(w?.serialNumber)C=w.serialNumber.toString();if(w?.productName)X=w.productName}catch{}}return{name:X,path:Q,type:"garmin",model:X,serial:C,unitId:q}}async downloadData(Q,J={}){let k=Q,{outputDir:X="./garmin-data",includeActivities:C=!0,includeMonitoring:q=!0,since:H,until:W,copyRawFiles:b=!0}=J,Z={device:Q,activities:[],monitoring:new Map,rawFiles:[],errors:[]},G=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19),U=N(X,G);if(b)p(U,{recursive:!0});if(k.isGarminExpress&&k.garminExpressPath)return this.downloadFromGarminExpress(k,J,Z,U);if(C){let K=N(Q.path,n.ACTIVITY);if(y(K)){let V=T(K).filter((w)=>w.toLowerCase().endsWith(".fit"));for(let w of V){let Y=N(K,w);try{let z=u(Y).mtime;if(H&&z<H)continue;if(W&&z>W)continue;let j=await this.parseActivityFile(Y);if(j)j.rawFilePath=Y,Z.activities.push(j);if(b){let A=N(U,"Activity");p(A,{recursive:!0});let L=N(A,w);c(Y,L),Z.rawFiles.push(L)}}catch(x){Z.errors.push(x instanceof Error?x:Error(String(x)))}}}}if(q){let K=N(Q.path,n.MONITOR);if(y(K)){let w=T(K).filter((Y)=>Y.toLowerCase().endsWith(".fit"));for(let Y of w){let x=N(K,Y);try{let j=u(x).mtime;if(H&&j<H)continue;if(W&&j>W)continue;let A=await this.parseMonitoringFile(x);if(A){let L=j.toISOString().slice(0,10),I=Z.monitoring.get(L)||{};Z.monitoring.set(L,{...I,...A})}if(b){let L=N(U,"Monitor");p(L,{recursive:!0});let I=N(L,Y);c(x,I),Z.rawFiles.push(I)}}catch(z){Z.errors.push(z instanceof Error?z:Error(String(z)))}}}let V=N(Q.path,"GARMIN/Sleep");if(y(V)){let w=T(V).filter((Y)=>Y.toLowerCase().endsWith(".fit"));for(let Y of w){let x=N(V,Y);try{let z=await this.parseMonitoringFile(x);if(z?.sleep){let j=z.sleep.date.toISOString().slice(0,10),A=Z.monitoring.get(j)||{};Z.monitoring.set(j,{...A,sleep:z.sleep})}if(b){let j=N(U,"Sleep");p(j,{recursive:!0});let A=N(j,Y);c(x,A),Z.rawFiles.push(A)}}catch(z){Z.errors.push(z instanceof Error?z:Error(String(z)))}}}}return Z.activities.sort((K,V)=>K.startTime.getTime()-V.startTime.getTime()),Z}async downloadFromGarminExpress(Q,J,k,X){let{includeActivities:C=!0,includeMonitoring:q=!0,since:H,until:W,copyRawFiles:b=!0}=J,Z=Q.garminExpressPath,G={Activity:N(Z,"Activity"),Monitor:N(Z,"Monitor"),Sleep:N(Z,"Sleep"),HRVStatus:N(Z,"HRVStatus"),SkinTemp:N(Z,"SkinTemp"),Metrics:N(Z,"Metrics"),Coach:N(Z,"Coach")},U=async(K,V,w)=>{if(!y(K))return;let Y=T(K).filter((x)=>x.toLowerCase().endsWith(".fit"));for(let x of Y){let z=N(K,x);try{let A=u(z).mtime;if(H&&A<H)continue;if(W&&A>W)continue;let L=await this.parseMonitoringFile(z);if(L)V(z,L);if(b){let I=N(X,w);p(I,{recursive:!0});let o=N(I,x);c(z,o),k.rawFiles.push(o)}}catch(j){k.errors.push(j instanceof Error?j:Error(String(j)))}}};if(C&&y(G.Activity)){let K=T(G.Activity).filter((V)=>V.toLowerCase().endsWith(".fit"));for(let V of K){let w=N(G.Activity,V);try{let x=u(w).mtime;if(H&&x<H)continue;if(W&&x>W)continue;let z=await this.parseActivityFile(w);if(z)z.rawFilePath=w,k.activities.push(z);if(b){let j=N(X,"Activity");p(j,{recursive:!0});let A=N(j,V);c(w,A),k.rawFiles.push(A)}}catch(Y){k.errors.push(Y instanceof Error?Y:Error(String(Y)))}}}if(q)await U(G.Monitor,(K,V)=>{let w=new Date().toISOString().slice(0,10),Y=k.monitoring.get(w)||{};k.monitoring.set(w,{...Y,...V})},"Monitor"),await U(G.HRVStatus,(K,V)=>{if(V.hrv){let w=new Date().toISOString().slice(0,10),Y=k.monitoring.get(w)||{};k.monitoring.set(w,{...Y,hrv:V.hrv})}},"HRVStatus"),await U(G.SkinTemp,(K,V)=>{let w=new Date().toISOString().slice(0,10),Y=k.monitoring.get(w)||{};k.monitoring.set(w,{...Y})},"SkinTemp"),await U(G.Sleep,(K,V)=>{if(V.sleep){let w=V.sleep.date?.toISOString().slice(0,10)||new Date().toISOString().slice(0,10),Y=k.monitoring.get(w)||{};k.monitoring.set(w,{...Y,sleep:V.sleep})}},"Sleep");return k.activities.sort((K,V)=>K.startTime.getTime()-V.startTime.getTime()),k}async parseActivityFile(Q){let J=r(Q).toLowerCase();if(J!==".fit")throw Error(`Unsupported file format: ${J}`);let k=F(Q),C=new _(k).parse();return new f(C).decodeActivity()}async parseMonitoringFile(Q){let J=r(Q).toLowerCase();if(J!==".fit")throw Error(`Unsupported file format: ${J}`);let k=F(Q),C=new _(k).parse();return new f(C).decodeMonitoring()}}function zq(){return new a}
|
|
2
|
+
export{qq as a,S as b,D as c,E as d,O as e,Qq as f,R as g,$ as h,B as i,M as j,v as k,l,Wq as m,P as n,_ as o,h as p,g as q,Cq as r,f as s,a as t,zq as u};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a PKCE verifier and its S256 challenge.
|
|
3
|
+
*
|
|
4
|
+
* Only the challenge travels to Garmin. Because the authorization code comes
|
|
5
|
+
* back through the athlete's browser, someone able to read it there still
|
|
6
|
+
* cannot redeem it without the verifier, which never left the server.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createPkcePair(): PkcePair;
|
|
9
|
+
/**
|
|
10
|
+
* Read activity summaries out of a push payload.
|
|
11
|
+
*
|
|
12
|
+
* Garmin posts `{ activities: [...] }`, and has historically also posted a
|
|
13
|
+
* bare array. Accepting both costs nothing and avoids dropping a batch over a
|
|
14
|
+
* shape the caller did not anticipate. Entries without a `summaryId` are
|
|
15
|
+
* dropped, since without it a redelivery cannot be recognised.
|
|
16
|
+
*/
|
|
17
|
+
export declare function extractActivitySummaries(body: unknown): GarminActivitySummary[];
|
|
18
|
+
/** Read deregistration notices out of a payload. */
|
|
19
|
+
export declare function extractDeregistrations(body: unknown): GarminDeregistration[];
|
|
20
|
+
/**
|
|
21
|
+
* Compare a presented webhook secret against the expected one.
|
|
22
|
+
*
|
|
23
|
+
* Constant-time: a byte-by-byte comparison leaks how much of a guess was
|
|
24
|
+
* correct, which is enough to construct the rest. Returns false when no secret
|
|
25
|
+
* is configured, so an endpoint that forgot to set one rejects everything
|
|
26
|
+
* rather than accepting everything.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isAuthenticWebhook(presented: string | null | undefined, secret: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Sign a value so it can be handed to a browser and trusted on return.
|
|
31
|
+
*
|
|
32
|
+
* The OAuth callback is a top-level navigation carrying no session, so the
|
|
33
|
+
* application has to stash who was connecting somewhere the browser holds.
|
|
34
|
+
* Signing is what stops someone editing that stash to attach their watch to
|
|
35
|
+
* another person's account.
|
|
36
|
+
*/
|
|
37
|
+
export declare function signState(payload: string, secret: string): string;
|
|
38
|
+
/** Verify and decode a value produced by {@link signState}, or null if it was altered. */
|
|
39
|
+
export declare function verifyState(token: string | undefined, secret: string): string | null;
|
|
40
|
+
/** Is a token past, or nearly past, its expiry? */
|
|
41
|
+
export declare function isTokenExpired(tokens: GarminTokens, skewSeconds?: number, now?: unknown): boolean;
|
|
42
|
+
/** Endpoints from Garmin's OAuth 2.0 PKCE specification. */
|
|
43
|
+
export declare const GARMIN_ENDPOINTS: {
|
|
44
|
+
authorize: 'https://connect.garmin.com/oauth2Confirm';
|
|
45
|
+
token: 'https://diauth.garmin.com/di-oauth2-service/oauth/token';
|
|
46
|
+
userId: 'https://apis.garmin.com/wellness-api/rest/user/id';
|
|
47
|
+
permissions: 'https://apis.garmin.com/wellness-api/rest/user/permissions';
|
|
48
|
+
registration: 'https://apis.garmin.com/wellness-api/rest/user/registration';
|
|
49
|
+
backfillActivities: 'https://apis.garmin.com/wellness-api/rest/backfill/activities'
|
|
50
|
+
};
|
|
51
|
+
export declare interface GarminActivityApiConfig {
|
|
52
|
+
clientId: string
|
|
53
|
+
clientSecret: string
|
|
54
|
+
redirectUri: string
|
|
55
|
+
scope?: string
|
|
56
|
+
endpoints?: Partial<typeof GARMIN_ENDPOINTS>
|
|
57
|
+
fetch?: typeof globalThis.fetch
|
|
58
|
+
}
|
|
59
|
+
export declare interface GarminTokens {
|
|
60
|
+
accessToken: string
|
|
61
|
+
refreshToken?: string
|
|
62
|
+
expiresAt?: number
|
|
63
|
+
scope?: string
|
|
64
|
+
}
|
|
65
|
+
/** A PKCE verifier and the challenge derived from it. */
|
|
66
|
+
export declare interface PkcePair {
|
|
67
|
+
verifier: string
|
|
68
|
+
challenge: string
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* An activity summary as the Activity API pushes it.
|
|
72
|
+
*
|
|
73
|
+
* Field names are Garmin's. Everything except `summaryId` is optional, because
|
|
74
|
+
* a summary reflects what the device recorded: a treadmill run carries no GPS,
|
|
75
|
+
* a manually entered activity carries almost nothing.
|
|
76
|
+
*/
|
|
77
|
+
export declare interface GarminActivitySummary {
|
|
78
|
+
summaryId: string
|
|
79
|
+
activityId?: number
|
|
80
|
+
userId?: string
|
|
81
|
+
userAccessToken?: string
|
|
82
|
+
activityType?: string
|
|
83
|
+
activityName?: string
|
|
84
|
+
startTimeInSeconds?: number
|
|
85
|
+
startTimeOffsetInSeconds?: number
|
|
86
|
+
durationInSeconds?: number
|
|
87
|
+
distanceInMeters?: number
|
|
88
|
+
activeKilocalories?: number
|
|
89
|
+
averageHeartRateInBeatsPerMinute?: number
|
|
90
|
+
maxHeartRateInBeatsPerMinute?: number
|
|
91
|
+
averageSpeedInMetersPerSecond?: number
|
|
92
|
+
maxSpeedInMetersPerSecond?: number
|
|
93
|
+
averageRunCadenceInStepsPerMinute?: number
|
|
94
|
+
averageBikeCadenceInRoundsPerMinute?: number
|
|
95
|
+
averagePaceInMinutesPerKilometer?: number
|
|
96
|
+
totalElevationGainInMeters?: number
|
|
97
|
+
totalElevationLossInMeters?: number
|
|
98
|
+
startingLatitudeInDegree?: number
|
|
99
|
+
startingLongitudeInDegree?: number
|
|
100
|
+
steps?: number
|
|
101
|
+
deviceName?: string
|
|
102
|
+
manual?: boolean
|
|
103
|
+
isParent?: boolean
|
|
104
|
+
parentSummaryId?: string
|
|
105
|
+
isWebUpload?: boolean
|
|
106
|
+
}
|
|
107
|
+
/** Garmin tells an application when an athlete revokes it. */
|
|
108
|
+
export declare interface GarminDeregistration {
|
|
109
|
+
userId: string
|
|
110
|
+
userAccessToken?: string
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A client for the official Garmin Activity API.
|
|
114
|
+
*
|
|
115
|
+
* Holds no state beyond its configuration: tokens are passed in and returned,
|
|
116
|
+
* so the caller decides where they live. That keeps the client usable from a
|
|
117
|
+
* request handler, a job, or a test without assuming a database.
|
|
118
|
+
*/
|
|
119
|
+
export declare class GarminActivityApiClient {
|
|
120
|
+
constructor(config: GarminActivityApiConfig);
|
|
121
|
+
get isConfigured(): boolean;
|
|
122
|
+
buildAuthorizationUrl(options: { state: string, challenge: string, scope?: string }): string;
|
|
123
|
+
exchangeCode(code: string, verifier: string): Promise<GarminTokens>;
|
|
124
|
+
refreshTokens(refreshToken: string): Promise<GarminTokens>;
|
|
125
|
+
getUserId(accessToken: string): Promise<string>;
|
|
126
|
+
getPermissions(accessToken: string): Promise<string[]>;
|
|
127
|
+
deregister(accessToken: string): Promise<void>;
|
|
128
|
+
requestBackfill(accessToken: string, start: Date, end: Date): Promise<void>;
|
|
129
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Activity, SleepData, StressData, BodyBattery, HRVData, WeightData } from '../types';
|
|
2
|
+
export declare function createGarminConnectClient(config?: GarminConnectConfig): GarminConnectClient;
|
|
3
|
+
export declare interface GarminConnectConfig {
|
|
4
|
+
username: string
|
|
5
|
+
password: string
|
|
6
|
+
}
|
|
7
|
+
export declare interface GarminActivitySummary {
|
|
8
|
+
activityId: number
|
|
9
|
+
activityName: string
|
|
10
|
+
activityType: { typeKey: string }
|
|
11
|
+
startTimeLocal: string
|
|
12
|
+
startTimeGMT: string
|
|
13
|
+
duration: number
|
|
14
|
+
distance: number
|
|
15
|
+
calories: number
|
|
16
|
+
averageHR?: number
|
|
17
|
+
maxHR?: number
|
|
18
|
+
averageSpeed?: number
|
|
19
|
+
maxSpeed?: number
|
|
20
|
+
elevationGain?: number
|
|
21
|
+
elevationLoss?: number
|
|
22
|
+
averageRunningCadenceInStepsPerMinute?: number
|
|
23
|
+
maxRunningCadenceInStepsPerMinute?: number
|
|
24
|
+
avgPower?: number
|
|
25
|
+
maxPower?: number
|
|
26
|
+
normPower?: number
|
|
27
|
+
trainingStressScore?: number
|
|
28
|
+
intensityFactor?: number
|
|
29
|
+
}
|
|
30
|
+
export declare interface DailyHeartRate {
|
|
31
|
+
date: Date
|
|
32
|
+
restingHeartRate?: number
|
|
33
|
+
minHeartRate?: number
|
|
34
|
+
maxHeartRate?: number
|
|
35
|
+
avgHeartRate?: number
|
|
36
|
+
samples?: Array<{ timestamp: Date, heartRate: number }>
|
|
37
|
+
}
|
|
38
|
+
export declare interface DailySummary {
|
|
39
|
+
date: Date
|
|
40
|
+
heartRate?: DailyHeartRate
|
|
41
|
+
sleep?: SleepData
|
|
42
|
+
stress?: StressData
|
|
43
|
+
bodyBattery?: BodyBattery
|
|
44
|
+
hrv?: HRVData
|
|
45
|
+
steps?: {
|
|
46
|
+
total: number
|
|
47
|
+
goal: number
|
|
48
|
+
distance: number
|
|
49
|
+
calories: number
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export declare class GarminConnectClient {
|
|
53
|
+
constructor(config?: GarminConnectConfig);
|
|
54
|
+
login(username?: string, password?: string): Promise<void>;
|
|
55
|
+
getUserProfile(): Promise<{ displayName: string, profileId: string }>;
|
|
56
|
+
getActivities(start?: number, limit?: number): Promise<GarminActivitySummary[]>;
|
|
57
|
+
getActivity(activityId: number): Promise<Activity>;
|
|
58
|
+
downloadActivityFit(activityId: number, outputDir: string): Promise<void>;
|
|
59
|
+
getDailySummary(date: Date): Promise<DailySummary>;
|
|
60
|
+
getHeartRateData(date: Date): Promise<DailyHeartRate | undefined>;
|
|
61
|
+
getSleepData(date: Date): Promise<SleepData | undefined>;
|
|
62
|
+
getStepsData(date: Date): Promise<{ total: number, goal: number, distance: number, calories: number } | undefined>;
|
|
63
|
+
getStressData(date: Date): Promise<StressData | undefined>;
|
|
64
|
+
getBodyBatteryData(date: Date): Promise<BodyBattery | undefined>;
|
|
65
|
+
getHrvData(date: Date): Promise<HRVData | undefined>;
|
|
66
|
+
getWeightData(date: Date): Promise<WeightData | undefined>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Activity } from '../types';
|
|
2
|
+
export declare function createStravaClient(config: StravaConfig): StravaClient;
|
|
3
|
+
// Helper to convert ts-watches Activity to Strava upload
|
|
4
|
+
export declare function activityToStravaUpload(activity: Activity): {
|
|
5
|
+
name: string
|
|
6
|
+
type: string
|
|
7
|
+
description: string
|
|
8
|
+
};
|
|
9
|
+
export declare interface StravaConfig {
|
|
10
|
+
clientId: string
|
|
11
|
+
clientSecret: string
|
|
12
|
+
redirectUri?: string
|
|
13
|
+
}
|
|
14
|
+
export declare interface StravaTokens {
|
|
15
|
+
accessToken: string
|
|
16
|
+
refreshToken: string
|
|
17
|
+
expiresAt: number
|
|
18
|
+
athleteId: number
|
|
19
|
+
}
|
|
20
|
+
export declare interface StravaAthlete {
|
|
21
|
+
id: number
|
|
22
|
+
username: string
|
|
23
|
+
firstname: string
|
|
24
|
+
lastname: string
|
|
25
|
+
city: string
|
|
26
|
+
country: string
|
|
27
|
+
profile: string
|
|
28
|
+
profileMedium: string
|
|
29
|
+
}
|
|
30
|
+
export declare interface StravaActivitySummary {
|
|
31
|
+
id: number
|
|
32
|
+
name: string
|
|
33
|
+
type: string
|
|
34
|
+
sport_type: string
|
|
35
|
+
start_date: string
|
|
36
|
+
start_date_local: string
|
|
37
|
+
elapsed_time: number
|
|
38
|
+
moving_time: number
|
|
39
|
+
distance: number
|
|
40
|
+
total_elevation_gain: number
|
|
41
|
+
average_speed: number
|
|
42
|
+
max_speed: number
|
|
43
|
+
average_heartrate?: number
|
|
44
|
+
max_heartrate?: number
|
|
45
|
+
average_cadence?: number
|
|
46
|
+
average_watts?: number
|
|
47
|
+
weighted_average_watts?: number
|
|
48
|
+
kilojoules?: number
|
|
49
|
+
calories?: number
|
|
50
|
+
}
|
|
51
|
+
export declare interface StravaUploadResponse {
|
|
52
|
+
id: number
|
|
53
|
+
id_str: string
|
|
54
|
+
external_id: string
|
|
55
|
+
error: string | null
|
|
56
|
+
status: string
|
|
57
|
+
activity_id: number | null
|
|
58
|
+
}
|
|
59
|
+
export declare class StravaClient {
|
|
60
|
+
constructor(config: StravaConfig);
|
|
61
|
+
getAuthorizationUrl(scope?: string): string;
|
|
62
|
+
exchangeCode(code: string): Promise<StravaTokens>;
|
|
63
|
+
refreshTokens(): Promise<StravaTokens>;
|
|
64
|
+
setTokens(tokens: StravaTokens): void;
|
|
65
|
+
getAthlete(): Promise<StravaAthlete>;
|
|
66
|
+
getActivities(page?: number, perPage?: number, before?: Date, after?: Date): Promise<StravaActivitySummary[]>;
|
|
67
|
+
getActivity(activityId: number, includeAllEfforts?: boolean): Promise<Activity>;
|
|
68
|
+
uploadActivity(file: ArrayBuffer, fileName: string, options?: {
|
|
69
|
+
name?: string
|
|
70
|
+
description?: string
|
|
71
|
+
type?: string
|
|
72
|
+
private?: boolean
|
|
73
|
+
externalId?: string
|
|
74
|
+
}): Promise<StravaUploadResponse>;
|
|
75
|
+
checkUploadStatus(uploadId: number): Promise<StravaUploadResponse>;
|
|
76
|
+
waitForUpload(uploadId: number, maxAttempts?: number, delayMs?: number): Promise<StravaUploadResponse>;
|
|
77
|
+
updateActivity(activityId: number, updates: {
|
|
78
|
+
name?: string
|
|
79
|
+
type?: string
|
|
80
|
+
description?: string
|
|
81
|
+
private?: boolean
|
|
82
|
+
commute?: boolean
|
|
83
|
+
trainer?: boolean
|
|
84
|
+
gearId?: string
|
|
85
|
+
}): Promise<Activity>;
|
|
86
|
+
deleteActivity(activityId: number): Promise<void>;
|
|
87
|
+
getActivityStreams(activityId: number, streams?: string[]): Promise<Map<string, number[]>>;
|
|
88
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TrainingPeaks Cloud Integration
|
|
3
|
+
*
|
|
4
|
+
* Re-exports from ts-trainingpeaks package which provides
|
|
5
|
+
* browser-based authentication with the TrainingPeaks API.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { TrainingPeaks } from 'ts-watches/cloud'
|
|
10
|
+
*
|
|
11
|
+
* const client = new TrainingPeaks({
|
|
12
|
+
* username: 'your-username',
|
|
13
|
+
* password: 'your-password',
|
|
14
|
+
* cookiePath: './tp-cookies.json', // Optional: persist session
|
|
15
|
+
* headless: true, // Run browser headlessly
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* await client.login()
|
|
19
|
+
*
|
|
20
|
+
* // Get workouts for the current user
|
|
21
|
+
* const workouts = await client.getWorkouts(startDate, endDate)
|
|
22
|
+
*
|
|
23
|
+
* // For coach accounts, get coached athletes
|
|
24
|
+
* const athleteIds = await client.getCoachedAthleteIds()
|
|
25
|
+
* const athleteWorkouts = await client.getWorkouts(startDate, endDate, athleteIds[0])
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export {
|
|
29
|
+
TrainingPeaks,
|
|
30
|
+
type TrainingPeaksConfig,
|
|
31
|
+
} from 'ts-trainingpeaks';
|
|
32
|
+
export {
|
|
33
|
+
type TPAthlete,
|
|
34
|
+
type TPAthleteGroup,
|
|
35
|
+
type TPCoachAthlete,
|
|
36
|
+
type TPWorkout,
|
|
37
|
+
type TPWorkoutStructureResponse,
|
|
38
|
+
type TPWorkoutStructureStep,
|
|
39
|
+
type TPWorkoutType,
|
|
40
|
+
type TPWorkoutTypeId,
|
|
41
|
+
type TPMetrics,
|
|
42
|
+
type TPPerformanceChart,
|
|
43
|
+
type TPUploadResult,
|
|
44
|
+
type TPLibraryWorkout,
|
|
45
|
+
type TPCookie,
|
|
46
|
+
TPWorkoutTypeIds,
|
|
47
|
+
getWorkoutTypeName,
|
|
48
|
+
} from 'ts-trainingpeaks';
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { WatchDriver, WatchDevice, DownloadOptions, DownloadResult, Activity, MonitoringData } from '../types';
|
|
2
|
+
export declare function createAppleDriver(): AppleDriver;
|
|
3
|
+
export declare interface AppleDevice extends WatchDevice {
|
|
4
|
+
type: 'apple'
|
|
5
|
+
}
|
|
6
|
+
export declare class AppleDriver implements WatchDriver {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly type: unknown;
|
|
9
|
+
detectDevices(): Promise<AppleDevice[]>;
|
|
10
|
+
downloadData(_device: WatchDevice, _options?: DownloadOptions): Promise<DownloadResult>;
|
|
11
|
+
parseHealthExport(exportPath: string): Promise<{
|
|
12
|
+
activities: Activity[]
|
|
13
|
+
monitoring: Map<string, MonitoringData>
|
|
14
|
+
}>;
|
|
15
|
+
parseActivityFile(_filePath: string): Promise<Activity | null>;
|
|
16
|
+
parseMonitoringFile(_filePath: string): Promise<MonitoringData>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WatchDriver, WatchDevice, DownloadOptions, DownloadResult, Activity, MonitoringData } from '../types';
|
|
2
|
+
export declare function createCorosDriver(): CorosDriver;
|
|
3
|
+
export declare interface CorosDevice extends WatchDevice {
|
|
4
|
+
type: 'coros'
|
|
5
|
+
}
|
|
6
|
+
export declare class CorosDriver implements WatchDriver {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly type: unknown;
|
|
9
|
+
detectDevices(): Promise<CorosDevice[]>;
|
|
10
|
+
downloadData(device: WatchDevice, options?: DownloadOptions): Promise<DownloadResult>;
|
|
11
|
+
parseActivityFile(filePath: string): Promise<Activity | null>;
|
|
12
|
+
parseMonitoringFile(filePath: string): Promise<MonitoringData>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WatchDriver, GarminDevice, WatchDevice, DownloadOptions, DownloadResult, Activity, MonitoringData } from '../types';
|
|
2
|
+
// Factory function
|
|
3
|
+
export declare function createGarminDriver(): GarminDriver;
|
|
4
|
+
export declare class GarminDriver implements WatchDriver {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly type: unknown;
|
|
7
|
+
detectDevices(): Promise<GarminDevice[]>;
|
|
8
|
+
detectGarminExpressDevices(): Promise<GarminDevice[]>;
|
|
9
|
+
downloadData(device: WatchDevice, options?: DownloadOptions): Promise<DownloadResult>;
|
|
10
|
+
parseActivityFile(filePath: string): Promise<Activity | null>;
|
|
11
|
+
parseMonitoringFile(filePath: string): Promise<MonitoringData>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { WatchDriver, WatchDevice, DownloadOptions, DownloadResult, Activity, MonitoringData } from '../types';
|
|
2
|
+
export declare function createPolarDriver(): PolarDriver;
|
|
3
|
+
export declare interface PolarDevice extends WatchDevice {
|
|
4
|
+
type: 'polar'
|
|
5
|
+
deviceId?: string
|
|
6
|
+
}
|
|
7
|
+
export declare class PolarDriver implements WatchDriver {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly type: unknown;
|
|
10
|
+
detectDevices(): Promise<PolarDevice[]>;
|
|
11
|
+
downloadData(device: WatchDevice, options?: DownloadOptions): Promise<DownloadResult>;
|
|
12
|
+
parseActivityFile(filePath: string): Promise<Activity | null>;
|
|
13
|
+
parseMonitoringFile(_filePath: string): Promise<MonitoringData>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WatchDriver, WatchDevice, DownloadOptions, DownloadResult, Activity, MonitoringData } from '../types';
|
|
2
|
+
export declare function createSuuntoDriver(): SuuntoDriver;
|
|
3
|
+
export declare interface SuuntoDevice extends WatchDevice {
|
|
4
|
+
type: 'suunto'
|
|
5
|
+
}
|
|
6
|
+
export declare class SuuntoDriver implements WatchDriver {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly type: unknown;
|
|
9
|
+
detectDevices(): Promise<SuuntoDevice[]>;
|
|
10
|
+
downloadData(device: WatchDevice, options?: DownloadOptions): Promise<DownloadResult>;
|
|
11
|
+
parseActivityFile(filePath: string): Promise<Activity | null>;
|
|
12
|
+
parseMonitoringFile(_filePath: string): Promise<MonitoringData>;
|
|
13
|
+
}
|