endurance-coach 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 +94 -0
- package/bin/claude-coach.js +10 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +1077 -0
- package/dist/db/client.d.ts +8 -0
- package/dist/db/client.js +111 -0
- package/dist/db/migrate.d.ts +1 -0
- package/dist/db/migrate.js +14 -0
- package/dist/db/schema.sql +105 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +13 -0
- package/dist/lib/config.d.ts +27 -0
- package/dist/lib/config.js +86 -0
- package/dist/lib/logging.d.ts +13 -0
- package/dist/lib/logging.js +28 -0
- package/dist/schema/training-plan.d.ts +288 -0
- package/dist/schema/training-plan.js +88 -0
- package/dist/schema/training-plan.schema.d.ts +1875 -0
- package/dist/schema/training-plan.schema.js +418 -0
- package/dist/strava/api.d.ts +5 -0
- package/dist/strava/api.js +63 -0
- package/dist/strava/oauth.d.ts +4 -0
- package/dist/strava/oauth.js +113 -0
- package/dist/strava/types.d.ts +46 -0
- package/dist/strava/types.js +1 -0
- package/dist/viewer/lib/UpdatePlan.d.ts +48 -0
- package/dist/viewer/lib/UpdatePlan.js +209 -0
- package/dist/viewer/lib/export/erg.d.ts +26 -0
- package/dist/viewer/lib/export/erg.js +208 -0
- package/dist/viewer/lib/export/fit.d.ts +25 -0
- package/dist/viewer/lib/export/fit.js +308 -0
- package/dist/viewer/lib/export/ics.d.ts +13 -0
- package/dist/viewer/lib/export/ics.js +142 -0
- package/dist/viewer/lib/export/index.d.ts +50 -0
- package/dist/viewer/lib/export/index.js +229 -0
- package/dist/viewer/lib/export/zwo.d.ts +21 -0
- package/dist/viewer/lib/export/zwo.js +233 -0
- package/dist/viewer/lib/utils.d.ts +14 -0
- package/dist/viewer/lib/utils.js +123 -0
- package/dist/viewer/main.d.ts +5 -0
- package/dist/viewer/main.js +6 -0
- package/dist/viewer/stores/changes.d.ts +21 -0
- package/dist/viewer/stores/changes.js +49 -0
- package/dist/viewer/stores/plan.d.ts +11 -0
- package/dist/viewer/stores/plan.js +40 -0
- package/dist/viewer/stores/settings.d.ts +53 -0
- package/dist/viewer/stores/settings.js +215 -0
- package/package.json +74 -0
- package/templates/plan-viewer.html +70 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initialize the SQLite backend. Must be called before using other functions.
|
|
3
|
+
*/
|
|
4
|
+
export declare function initDatabase(): Promise<void>;
|
|
5
|
+
export declare function query(sql: string): string;
|
|
6
|
+
export declare function queryJson<T>(sql: string): T[];
|
|
7
|
+
export declare function execute(sql: string): void;
|
|
8
|
+
export declare function runScript(script: string): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { execSync, spawnSync } from "child_process";
|
|
2
|
+
import { getDbPath } from "../lib/config.js";
|
|
3
|
+
let cachedBackend = null;
|
|
4
|
+
/**
|
|
5
|
+
* Try to use Node's built-in SQLite module (Node 22.5+).
|
|
6
|
+
* Falls back to shelling out to sqlite3 CLI if not available.
|
|
7
|
+
*/
|
|
8
|
+
async function detectBackend() {
|
|
9
|
+
// Try Node.js built-in SQLite first (Node 22.5+)
|
|
10
|
+
try {
|
|
11
|
+
// Dynamic import to avoid syntax errors on older Node versions
|
|
12
|
+
const sqlite = await import("node:sqlite");
|
|
13
|
+
const dbPath = getDbPath();
|
|
14
|
+
const db = new sqlite.DatabaseSync(dbPath);
|
|
15
|
+
return {
|
|
16
|
+
query(sql) {
|
|
17
|
+
const stmt = db.prepare(sql);
|
|
18
|
+
const rows = stmt.all();
|
|
19
|
+
if (rows.length === 0)
|
|
20
|
+
return "";
|
|
21
|
+
// Format as simple text output (column values separated by |)
|
|
22
|
+
return rows
|
|
23
|
+
.map((row) => Object.values(row)
|
|
24
|
+
.map((v) => (v === null ? "" : String(v)))
|
|
25
|
+
.join("|"))
|
|
26
|
+
.join("\n");
|
|
27
|
+
},
|
|
28
|
+
queryJson(sql) {
|
|
29
|
+
const stmt = db.prepare(sql);
|
|
30
|
+
return stmt.all();
|
|
31
|
+
},
|
|
32
|
+
execute(sql) {
|
|
33
|
+
db.exec(sql);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Node.js built-in SQLite not available, try CLI
|
|
39
|
+
}
|
|
40
|
+
// Fallback: Use sqlite3 CLI
|
|
41
|
+
try {
|
|
42
|
+
// Check if sqlite3 is available
|
|
43
|
+
execSync("sqlite3 --version", { stdio: "ignore" });
|
|
44
|
+
return {
|
|
45
|
+
query(sql) {
|
|
46
|
+
const dbPath = getDbPath();
|
|
47
|
+
return execSync(`sqlite3 "${dbPath}" "${sql.replace(/"/g, '\\"')}"`, {
|
|
48
|
+
encoding: "utf-8",
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
queryJson(sql) {
|
|
52
|
+
const dbPath = getDbPath();
|
|
53
|
+
const result = execSync(`sqlite3 -json "${dbPath}" "${sql.replace(/"/g, '\\"')}"`, {
|
|
54
|
+
encoding: "utf-8",
|
|
55
|
+
});
|
|
56
|
+
if (!result.trim())
|
|
57
|
+
return [];
|
|
58
|
+
return JSON.parse(result);
|
|
59
|
+
},
|
|
60
|
+
execute(sql) {
|
|
61
|
+
const dbPath = getDbPath();
|
|
62
|
+
const result = spawnSync("sqlite3", [dbPath], {
|
|
63
|
+
input: sql,
|
|
64
|
+
encoding: "utf-8",
|
|
65
|
+
});
|
|
66
|
+
if (result.error)
|
|
67
|
+
throw result.error;
|
|
68
|
+
if (result.status !== 0) {
|
|
69
|
+
throw new Error(`SQLite error: ${result.stderr}`);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw new Error("SQLite is not available. Please either:\n" +
|
|
76
|
+
" 1. Use Node.js 22.5+ (has built-in SQLite)\n" +
|
|
77
|
+
" 2. Install sqlite3 CLI (brew install sqlite3 / apt install sqlite3)");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Initialize the SQLite backend. Must be called before using other functions.
|
|
82
|
+
*/
|
|
83
|
+
export async function initDatabase() {
|
|
84
|
+
if (!cachedBackend) {
|
|
85
|
+
cachedBackend = await detectBackend();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get the backend, throwing if not initialized.
|
|
90
|
+
*/
|
|
91
|
+
function getBackend() {
|
|
92
|
+
if (!cachedBackend) {
|
|
93
|
+
throw new Error("Database not initialized. Call initDatabase() first.");
|
|
94
|
+
}
|
|
95
|
+
return cachedBackend;
|
|
96
|
+
}
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// Public API (synchronous after initialization)
|
|
99
|
+
// ============================================================================
|
|
100
|
+
export function query(sql) {
|
|
101
|
+
return getBackend().query(sql);
|
|
102
|
+
}
|
|
103
|
+
export function queryJson(sql) {
|
|
104
|
+
return getBackend().queryJson(sql);
|
|
105
|
+
}
|
|
106
|
+
export function execute(sql) {
|
|
107
|
+
getBackend().execute(sql);
|
|
108
|
+
}
|
|
109
|
+
export function runScript(script) {
|
|
110
|
+
execute(script);
|
|
111
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function migrate(): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { runScript } from "./client.js";
|
|
5
|
+
import { ensureConfigDir } from "../lib/config.js";
|
|
6
|
+
import { log } from "../lib/logging.js";
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
export function migrate() {
|
|
9
|
+
ensureConfigDir();
|
|
10
|
+
const schemaPath = join(__dirname, "schema.sql");
|
|
11
|
+
const schema = readFileSync(schemaPath, "utf-8");
|
|
12
|
+
runScript(schema);
|
|
13
|
+
log.success("Database schema initialized");
|
|
14
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
-- Core activity data
|
|
2
|
+
CREATE TABLE IF NOT EXISTS activities (
|
|
3
|
+
id INTEGER PRIMARY KEY, -- Strava activity ID
|
|
4
|
+
name TEXT,
|
|
5
|
+
sport_type TEXT, -- Run, Ride, Swim, etc.
|
|
6
|
+
start_date TEXT, -- ISO 8601 UTC
|
|
7
|
+
elapsed_time INTEGER, -- seconds
|
|
8
|
+
moving_time INTEGER, -- seconds
|
|
9
|
+
distance REAL, -- meters
|
|
10
|
+
total_elevation_gain REAL, -- meters
|
|
11
|
+
average_speed REAL, -- m/s
|
|
12
|
+
max_speed REAL, -- m/s
|
|
13
|
+
average_heartrate REAL,
|
|
14
|
+
max_heartrate REAL,
|
|
15
|
+
average_watts REAL, -- cycling/running power
|
|
16
|
+
max_watts REAL,
|
|
17
|
+
weighted_average_watts REAL, -- normalized power
|
|
18
|
+
kilojoules REAL,
|
|
19
|
+
suffer_score INTEGER, -- Strava's relative effort
|
|
20
|
+
average_cadence REAL,
|
|
21
|
+
calories REAL,
|
|
22
|
+
description TEXT,
|
|
23
|
+
workout_type INTEGER, -- 0=default, 1=race, 2=workout, 3=long run
|
|
24
|
+
gear_id TEXT,
|
|
25
|
+
raw_json TEXT, -- full Strava response as JSON
|
|
26
|
+
synced_at TEXT DEFAULT (datetime('now'))
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
-- Time-series streams (HR, power, pace over time)
|
|
30
|
+
CREATE TABLE IF NOT EXISTS streams (
|
|
31
|
+
activity_id INTEGER PRIMARY KEY,
|
|
32
|
+
time_data TEXT, -- JSON array: seconds from start
|
|
33
|
+
distance_data TEXT, -- JSON array: cumulative meters
|
|
34
|
+
heartrate_data TEXT, -- JSON array
|
|
35
|
+
watts_data TEXT, -- JSON array
|
|
36
|
+
cadence_data TEXT, -- JSON array
|
|
37
|
+
altitude_data TEXT, -- JSON array
|
|
38
|
+
velocity_data TEXT, -- JSON array: m/s
|
|
39
|
+
FOREIGN KEY (activity_id) REFERENCES activities(id)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
-- Athlete profile
|
|
43
|
+
CREATE TABLE IF NOT EXISTS athlete (
|
|
44
|
+
id INTEGER PRIMARY KEY,
|
|
45
|
+
firstname TEXT,
|
|
46
|
+
lastname TEXT,
|
|
47
|
+
weight REAL, -- kg
|
|
48
|
+
ftp INTEGER, -- functional threshold power (watts)
|
|
49
|
+
max_heartrate INTEGER,
|
|
50
|
+
raw_json TEXT,
|
|
51
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
-- Training goals
|
|
55
|
+
CREATE TABLE IF NOT EXISTS goals (
|
|
56
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
57
|
+
event_name TEXT, -- "Ironman 70.3 Oceanside"
|
|
58
|
+
event_date TEXT, -- ISO 8601
|
|
59
|
+
event_type TEXT, -- triathlon, marathon, ultra, century
|
|
60
|
+
notes TEXT, -- constraints, injuries, etc.
|
|
61
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
-- Sync metadata
|
|
65
|
+
CREATE TABLE IF NOT EXISTS sync_log (
|
|
66
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
67
|
+
started_at TEXT,
|
|
68
|
+
completed_at TEXT,
|
|
69
|
+
activities_synced INTEGER,
|
|
70
|
+
status TEXT -- success, failed, partial
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
-- Indexes for common queries
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_activities_date ON activities(start_date);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_activities_sport ON activities(sport_type);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_activities_sport_date ON activities(sport_type, start_date);
|
|
77
|
+
|
|
78
|
+
-- Useful views
|
|
79
|
+
DROP VIEW IF EXISTS weekly_volume;
|
|
80
|
+
CREATE VIEW weekly_volume AS
|
|
81
|
+
SELECT
|
|
82
|
+
strftime('%Y-W%W', start_date) AS week,
|
|
83
|
+
sport_type,
|
|
84
|
+
COUNT(*) AS sessions,
|
|
85
|
+
ROUND(SUM(moving_time) / 3600.0, 1) AS hours,
|
|
86
|
+
ROUND(SUM(distance) / 1000.0, 1) AS km,
|
|
87
|
+
ROUND(AVG(average_heartrate), 0) AS avg_hr,
|
|
88
|
+
ROUND(AVG(suffer_score), 0) AS avg_effort
|
|
89
|
+
FROM activities
|
|
90
|
+
GROUP BY week, sport_type
|
|
91
|
+
ORDER BY week DESC, sport_type;
|
|
92
|
+
|
|
93
|
+
DROP VIEW IF EXISTS recent_activities;
|
|
94
|
+
CREATE VIEW recent_activities AS
|
|
95
|
+
SELECT
|
|
96
|
+
date(start_date) AS date,
|
|
97
|
+
sport_type,
|
|
98
|
+
name,
|
|
99
|
+
moving_time / 60 AS minutes,
|
|
100
|
+
ROUND(distance / 1000.0, 1) AS km,
|
|
101
|
+
ROUND(average_heartrate, 0) AS hr,
|
|
102
|
+
suffer_score
|
|
103
|
+
FROM activities
|
|
104
|
+
ORDER BY start_date DESC
|
|
105
|
+
LIMIT 50;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Endurance Coach - Training Plan Generator
|
|
3
|
+
*
|
|
4
|
+
* Public API for validating and working with training plans.
|
|
5
|
+
*/
|
|
6
|
+
export { validatePlan, validatePlanOrThrow, formatValidationErrors, getJsonSchema, TrainingPlanSchema, WorkoutSchema, TrainingWeekSchema, TrainingDaySchema, TrainingPhaseSchema, AthleteAssessmentSchema, AthleteZonesSchema, RaceStrategySchema, UnitPreferencesSchema, PlanMetaSchema, type TrainingPlan, type Workout, type TrainingWeek, type TrainingDay, type TrainingPhase, type AthleteAssessment, type AthleteZones, type RaceStrategy, type UnitPreferences, type ValidationResult, type ValidationError, } from "./schema/training-plan.schema.js";
|
|
7
|
+
export type { Sport, WorkoutType, IntensityUnit, DurationUnit, StepType, SwimDistanceUnit, LandDistanceUnit, FirstDayOfWeek, IntensityTarget, DurationTarget, WorkoutStep, IntervalSet, StructuredWorkout, WeekSummary, HeartRateZones, PowerZones, SwimZones, PaceZones, } from "./schema/training-plan.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Endurance Coach - Training Plan Generator
|
|
3
|
+
*
|
|
4
|
+
* Public API for validating and working with training plans.
|
|
5
|
+
*/
|
|
6
|
+
// Schema validation
|
|
7
|
+
export {
|
|
8
|
+
// Validation functions
|
|
9
|
+
validatePlan, validatePlanOrThrow, formatValidationErrors, getJsonSchema,
|
|
10
|
+
// Main schema
|
|
11
|
+
TrainingPlanSchema,
|
|
12
|
+
// Component schemas (for partial validation)
|
|
13
|
+
WorkoutSchema, TrainingWeekSchema, TrainingDaySchema, TrainingPhaseSchema, AthleteAssessmentSchema, AthleteZonesSchema, RaceStrategySchema, UnitPreferencesSchema, PlanMetaSchema, } from "./schema/training-plan.schema.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface StravaConfig {
|
|
2
|
+
client_id: string;
|
|
3
|
+
client_secret: string;
|
|
4
|
+
}
|
|
5
|
+
export interface Config {
|
|
6
|
+
strava: StravaConfig;
|
|
7
|
+
sync_days: number;
|
|
8
|
+
}
|
|
9
|
+
export interface Tokens {
|
|
10
|
+
access_token: string;
|
|
11
|
+
refresh_token: string;
|
|
12
|
+
expires_at: number;
|
|
13
|
+
athlete_id: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function ensureConfigDir(): void;
|
|
16
|
+
export declare function getConfigPath(): string;
|
|
17
|
+
export declare function getTokensPath(): string;
|
|
18
|
+
export declare function getDbPath(): string;
|
|
19
|
+
export declare function configExists(): boolean;
|
|
20
|
+
export declare function tokensExist(): boolean;
|
|
21
|
+
export declare function loadConfig(): Config;
|
|
22
|
+
export declare function saveConfig(config: Config): void;
|
|
23
|
+
export declare function loadTokens(): Tokens;
|
|
24
|
+
export declare function saveTokens(tokens: Tokens): void;
|
|
25
|
+
export declare function tokensExpired(tokens: Tokens): boolean;
|
|
26
|
+
export declare function promptForConfig(): Promise<Config>;
|
|
27
|
+
export declare function createConfig(client_id: string, client_secret: string, sync_days?: number): Config;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import * as readline from "readline";
|
|
5
|
+
const CONFIG_DIR = join(homedir(), ".endurance-coach");
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7
|
+
const TOKENS_FILE = join(CONFIG_DIR, "tokens.json");
|
|
8
|
+
const DB_FILE = join(CONFIG_DIR, "coach.db");
|
|
9
|
+
export function ensureConfigDir() {
|
|
10
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
11
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function getConfigPath() {
|
|
15
|
+
return CONFIG_FILE;
|
|
16
|
+
}
|
|
17
|
+
export function getTokensPath() {
|
|
18
|
+
return TOKENS_FILE;
|
|
19
|
+
}
|
|
20
|
+
export function getDbPath() {
|
|
21
|
+
return DB_FILE;
|
|
22
|
+
}
|
|
23
|
+
export function configExists() {
|
|
24
|
+
return existsSync(CONFIG_FILE);
|
|
25
|
+
}
|
|
26
|
+
export function tokensExist() {
|
|
27
|
+
return existsSync(TOKENS_FILE);
|
|
28
|
+
}
|
|
29
|
+
export function loadConfig() {
|
|
30
|
+
if (!configExists()) {
|
|
31
|
+
throw new Error(`Config not found at ${CONFIG_FILE}. Run setup first.`);
|
|
32
|
+
}
|
|
33
|
+
const data = readFileSync(CONFIG_FILE, "utf-8");
|
|
34
|
+
return JSON.parse(data);
|
|
35
|
+
}
|
|
36
|
+
export function saveConfig(config) {
|
|
37
|
+
ensureConfigDir();
|
|
38
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
39
|
+
}
|
|
40
|
+
export function loadTokens() {
|
|
41
|
+
if (!tokensExist()) {
|
|
42
|
+
throw new Error(`Tokens not found at ${TOKENS_FILE}. Run auth first.`);
|
|
43
|
+
}
|
|
44
|
+
const data = readFileSync(TOKENS_FILE, "utf-8");
|
|
45
|
+
return JSON.parse(data);
|
|
46
|
+
}
|
|
47
|
+
export function saveTokens(tokens) {
|
|
48
|
+
ensureConfigDir();
|
|
49
|
+
writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2));
|
|
50
|
+
}
|
|
51
|
+
export function tokensExpired(tokens) {
|
|
52
|
+
// Add 60 second buffer
|
|
53
|
+
return Date.now() / 1000 > tokens.expires_at - 60;
|
|
54
|
+
}
|
|
55
|
+
async function prompt(question) {
|
|
56
|
+
const rl = readline.createInterface({
|
|
57
|
+
input: process.stdin,
|
|
58
|
+
output: process.stdout,
|
|
59
|
+
});
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
rl.question(question, (answer) => {
|
|
62
|
+
rl.close();
|
|
63
|
+
resolve(answer.trim());
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export async function promptForConfig() {
|
|
68
|
+
console.log("\n🚴 Endurance Coach Setup\n");
|
|
69
|
+
console.log("To use this tool, you need a Strava API application.");
|
|
70
|
+
console.log("Create one at: https://www.strava.com/settings/api");
|
|
71
|
+
console.log('Set "Authorization Callback Domain" to: localhost\n');
|
|
72
|
+
const client_id = await prompt("Enter your Strava Client ID: ");
|
|
73
|
+
const client_secret = await prompt("Enter your Strava Client Secret: ");
|
|
74
|
+
const sync_days_str = await prompt("Days of history to sync (default 730): ");
|
|
75
|
+
const sync_days = parseInt(sync_days_str) || 730;
|
|
76
|
+
return {
|
|
77
|
+
strava: { client_id, client_secret },
|
|
78
|
+
sync_days,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function createConfig(client_id, client_secret, sync_days = 730) {
|
|
82
|
+
return {
|
|
83
|
+
strava: { client_id, client_secret },
|
|
84
|
+
sync_days,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const log: {
|
|
2
|
+
info: (message: string, ...args: unknown[]) => void;
|
|
3
|
+
success: (message: string, ...args: unknown[]) => void;
|
|
4
|
+
warn: (message: string, ...args: unknown[]) => void;
|
|
5
|
+
error: (message: string, ...args: unknown[]) => void;
|
|
6
|
+
debug: (message: string, ...args: unknown[]) => void;
|
|
7
|
+
box: (message: string) => void;
|
|
8
|
+
start: (message: string) => void;
|
|
9
|
+
ready: (message: string) => void;
|
|
10
|
+
progress: (message: string) => void;
|
|
11
|
+
progressEnd: () => void;
|
|
12
|
+
};
|
|
13
|
+
export type Logger = typeof log;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { consola } from "consola";
|
|
2
|
+
// Configure consola with pretty formatting
|
|
3
|
+
const logger = consola.create({
|
|
4
|
+
level: process.env.LOG_LEVEL === "debug" ? 4 : 3,
|
|
5
|
+
formatOptions: {
|
|
6
|
+
date: false,
|
|
7
|
+
colors: true,
|
|
8
|
+
compact: false,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
export const log = {
|
|
12
|
+
info: (message, ...args) => logger.info(message, ...args),
|
|
13
|
+
success: (message, ...args) => logger.success(message, ...args),
|
|
14
|
+
warn: (message, ...args) => logger.warn(message, ...args),
|
|
15
|
+
error: (message, ...args) => logger.error(message, ...args),
|
|
16
|
+
debug: (message, ...args) => logger.debug(message, ...args),
|
|
17
|
+
box: (message) => logger.box(message),
|
|
18
|
+
start: (message) => logger.start(message),
|
|
19
|
+
ready: (message) => logger.ready(message),
|
|
20
|
+
// Progress-style logging (overwrites current line)
|
|
21
|
+
progress: (message) => {
|
|
22
|
+
process.stdout.write(`\r${message}`);
|
|
23
|
+
},
|
|
24
|
+
// End progress line
|
|
25
|
+
progressEnd: () => {
|
|
26
|
+
process.stdout.write("\n");
|
|
27
|
+
},
|
|
28
|
+
};
|