Enter PIN to Unlock
Not connected
Session Type
7
Nutrition
anchor: chicken + rice
Body Metrics (optional)
Recent Sessions
Connect your Google Sheet and click Refresh to load history.
This Week
Gym sessions
Circuits
Walk days
Day streak
Avg protein g
Walk mins total
Load data from Sheet
Pulls last 30 rows
Google Sheet Structure

Create one Google Sheet with these tabs (exact names matter for the Apps Script):

Tab 1: SessionsMain log
A: date | B: session_type | C: energy_level | D: bodyweight_kg | E: steps | F: sleep_hours | G: notes
Tab 2: GymSetsGym detail
A: date | B: exercise_name | C: pool | D: top_set_kg | E: top_set_reps | F: backoff_kg | G: backoff_reps | H: rpe | I: notes
Tab 3: CircuitsCircuit detail
A: date | B: combo_letter | C: combo_name | D: equipment_type | E: rounds | F: duration_min | G: notes
Tab 4: WalksWalk detail
A: date | B: duration_min | C: incline_pct | D: speed_kmh | E: avg_hr | F: est_calories | G: notes
Tab 5: NutritionFood log
A: date | B: lunch_done | C: protein_g | D: calories_total | E: food_notes
Apps Script (Code.gs)

In your Google Sheet → Extensions → Apps Script → paste this, then Deploy as Web App (execute as: Me, access: Anyone).

// NEO 2.0 — Google Apps Script // Deploy → Web App → Execute as Me · Access: Anyone const SHEET_ID = 'YOUR_GOOGLE_SHEET_ID_HERE'; function doPost(e) { const data = JSON.parse(e.postData.contents); const ss = SpreadsheetApp.openById(SHEET_ID); const action = data.action; if (action === 'log_session') { logSession(ss, data); } return ContentService .createTextOutput(JSON.stringify({status:'ok'})) .setMimeType(ContentService.MimeType.JSON); } function doGet(e) { const ss = SpreadsheetApp.openById(SHEET_ID); const action = e.parameter.action || 'history'; if (action === 'history') { return getHistory(ss); } if (action === 'stats') { return getStats(ss); } if (action === 'ping') { return ContentService .createTextOutput(JSON.stringify({status:'ok',msg:'Connected'})) .setMimeType(ContentService.MimeType.JSON); } } function logSession(ss, data) { const date = data.date; // Sessions tab ss.getSheetByName('Sessions').appendRow([ date, data.session_type, data.energy_level, data.bodyweight_kg, data.steps, data.sleep_hours, data.notes ]); // GymSets tab if (data.gym_sets && data.gym_sets.length > 0) { const gymSheet = ss.getSheetByName('GymSets'); data.gym_sets.forEach(s => { gymSheet.appendRow([ date, s.exercise, s.pool, s.top_kg, s.top_reps, s.backoff_kg, s.backoff_reps, s.rpe, s.notes ]); }); } // Circuits tab if (data.circuit) { ss.getSheetByName('Circuits').appendRow([ date, data.circuit.combo_letter, data.circuit.combo_name, data.circuit.equipment_type, data.circuit.rounds, data.circuit.duration_min, data.circuit.notes ]); } // Walks tab if (data.walk) { ss.getSheetByName('Walks').appendRow([ date, data.walk.duration_min, data.walk.incline_pct, data.walk.speed_kmh, data.walk.avg_hr, data.walk.est_calories, data.walk.notes ]); } // Nutrition tab if (data.nutrition) { ss.getSheetByName('Nutrition').appendRow([ date, data.nutrition.lunch_done, data.nutrition.protein_g, data.nutrition.calories_total, data.nutrition.food_notes ]); } } function getHistory(ss) { const sheet = ss.getSheetByName('Sessions'); const rows = sheet.getDataRange().getValues(); const headers = rows[0]; const data = rows.slice(-30).map(r => Object.fromEntries(headers.map((h,i) => [h, r[i]])) ); return ContentService .createTextOutput(JSON.stringify(data)) .setMimeType(ContentService.MimeType.JSON); } function getStats(ss) { const gymRows = ss.getSheetByName('GymSets').getDataRange().getValues().slice(1); const circuitRows = ss.getSheetByName('Circuits').getDataRange().getValues().slice(1); const walkRows = ss.getSheetByName('Walks').getDataRange().getValues().slice(1); const nutRows = ss.getSheetByName('Nutrition').getDataRange().getValues().slice(1); const now = new Date(); const weekAgo = new Date(now - 7*24*60*60*1000); const inWeek = rows => rows.filter(r => new Date(r[0]) >= weekAgo); const avgProt = nutRows.length ? Math.round(nutRows.reduce((s,r)=>s+(r[2]||0),0)/nutRows.length) : 0; const walkMins = inWeek(walkRows).reduce((s,r)=>s+(r[1]||0),0); return ContentService.createTextOutput(JSON.stringify({ gym_sessions_week: [...new Set(inWeek(gymRows).map(r=>r[0]))].length, circuit_sessions_week: inWeek(circuitRows).length, walk_days_week: inWeek(walkRows).length, avg_protein_g: avgProt, walk_mins_week: walkMins })).setMimeType(ContentService.MimeType.JSON); }
Quick Setup Steps
1. Create a new Google Sheet
2. Rename tabs: Sessions · GymSets · Circuits · Walks · Nutrition
3. Add the column headers (row 1) exactly as shown above
4. Go to Extensions → Apps Script → paste Code.gs above
5. Replace YOUR_GOOGLE_SHEET_ID_HERE with your Sheet's ID (from URL)
6. Deploy → New Deployment → Web App → Execute as Me · Anyone
7. Copy the Web App URL → paste in the bar at the top of this app