⏱️ Setup time: 30 min 💰 Cost: ~$1-3/month (Claude API only, Apps Script is free) 👥 Users: iOS-only (Shortcuts is Apple ecosystem)
What you're building
Setup Overview
1Create Apps Script Web App
Apps Script is Google's built-in scripting environment - free, no server needed.
- Open your Google Sheet
- Extensions → Apps Script (opens a new tab)
- Delete the default
myFunctioncode - Paste this:
// ============================================
// TS-03 Tracker Web App
// ============================================
const VALID_SUBJECTS = [
"FoVL-Language",
"GPU Programming",
"Ethics and Professional Life",
"Innovation and IP Management",
"Advanced AI"
];
const SHEET_ID = 'YOUR_SHEET_ID_HERE'; // Replace with your sheet ID
const SHEET_TAB = 'TS-03 Quiz & Assignment';
// Optional: shared secret to prevent unauthorized calls
const AUTH_SECRET = 'CHANGE_ME_TO_RANDOM_STRING_32_CHARS';
function doPost(e) {
try {
const data = JSON.parse(e.postData.contents);
// Auth check
if (data.secret !== AUTH_SECRET) {
return jsonResponse({ error: "Unauthorized" }, 401);
}
const action = data.action;
if (action === 'add') {
return handleAdd(data.row);
} else if (action === 'read') {
return handleRead(data.query);
} else if (action === 'update_status') {
return handleUpdateStatus(data.row_num, data.new_status);
}
return jsonResponse({ error: "Unknown action: " + action });
} catch (err) {
return jsonResponse({ error: err.toString() });
}
}
function handleAdd(row) {
// Validate subject
if (!VALID_SUBJECTS.includes(row.subject)) {
return jsonResponse({
error: `Invalid subject "${row.subject}". Valid: ${VALID_SUBJECTS.join(", ")}`
});
}
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_TAB);
// Find next # (count existing data rows)
const existingNums = sheet.getRange('A5:A').getValues()
.filter(r => r[0] && !isNaN(r[0]));
const nextNum = existingNums.length + 1;
// Append row
sheet.appendRow([
nextNum,
row.subject,
row.type || 'Assignment',
row.title || '',
row.due_date || '',
row.time || '23:59 IST',
'Google Classroom',
'GCR',
'Upcoming',
'', // Days Left (formula will populate)
row.notes || ''
]);
return jsonResponse({
success: true,
row_num: nextNum,
message: `Added: ${row.subject} - ${row.title}`
});
}
function handleRead(query) {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_TAB);
const data = sheet.getRange('A4:K').getValues();
const headers = data[0];
const rows = data.slice(1).filter(r => r[0] !== '');
// Simple filter examples
const today = new Date();
const weekFromNow = new Date(today.getTime() + 7 * 24 * 60 * 60 * 1000);
let filtered = rows;
const q = query.toLowerCase();
if (q.includes('week') || q.includes('7 day')) {
filtered = rows.filter(r => {
const due = new Date(r[4]);
return due >= today && due <= weekFromNow;
});
} else if (q.includes('today')) {
filtered = rows.filter(r => {
const due = new Date(r[4]);
return due.toDateString() === today.toDateString();
});
} else if (q.includes('upcoming')) {
filtered = rows.filter(r => r[8] === 'Upcoming');
}
// Format for iPhone display
const summary = filtered.map(r =>
`#${r[0]} ${r[1]} - ${r[3]} (due ${r[4]}, ${r[9]} days left)`
).join('\n');
return jsonResponse({
success: true,
count: filtered.length,
summary: summary || 'No matching items'
});
}
function handleUpdateStatus(rowNum, newStatus) {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_TAB);
// Row #N in the sheet is at row (N + 4) accounting for headers
const sheetRow = parseInt(rowNum) + 4;
sheet.getRange(sheetRow, 9).setValue(newStatus); // Column I = Status
return jsonResponse({
success: true,
message: `Updated row #${rowNum} status to ${newStatus}`
});
}
function jsonResponse(obj, status = 200) {
return ContentService
.createTextOutput(JSON.stringify(obj))
.setMimeType(ContentService.MimeType.JSON);
}
- Save (Cmd+S) - name the project
TS-03 Tracker Web App - Update:
-
SHEET_IDto your actual sheet ID -AUTH_SECRETto a random 32-char string (generate withopenssl rand -hex 16in Terminal)
2Deploy as Web App
- Top-right → Deploy → New deployment
- Click the gear icon → select Web app
- Configure:
- Description:
TS-03 Tracker v1- Execute as: Me (your Google account - so it has sheet access) - Who has access: Anyone - Click Deploy
- First time only: Authorize access → sign in → advanced → allow
- Copy the Web app URL - looks like:
https://script.google.com/macros/s/AKfycb.../exec - Save this URL - needed in Step 4
⚠️ "Anyone" access means anyone with the URL can call it - that's why we have AUTH_SECRET. Don't skip the secret check.
3Get Claude API Key
Same as Option 2 Step 2:
1. console.anthropic.com → API Keys → Create
2. Copy key (starts with sk-ant-...)
3. Add ~$5 credit for testing
4Build the iOS Shortcut
Open Shortcuts app on iPhone → + (top right) → Add Action
Add these 6 actions in order:
Action 1: Get input from user
Dictate Text (for voice) OR Ask for Input (for typing) - If Dictate: set "Stop Listening" to "On Tap" - If Ask: prompt = "What to add or query?"
Action 2: Get Claude to parse
Get Contents of URL
- URL: https://api.anthropic.com/v1/messages
- Method: POST
- Headers:
x-api-key: sk-ant-YOUR_KEY_HERE
anthropic-version: 2023-06-01
content-type: application/json
- Request Body: JSON
json
{
"model": "claude-sonnet-4-5",
"max_tokens": 512,
"system": "You parse tracker requests into JSON. Valid subjects: FoVL-Language, GPU Programming, Ethics and Professional Life, Innovation and IP Management, Advanced AI. Return ONLY JSON with keys: action (add/read/update_status), subject, type, title, due_date (DD-Mmm-YYYY), time (HH:MM IST), notes. For reads, use action=read with a query field.",
"messages": [
{"role": "user", "content": "REPLACE_WITH_ACTION_1_OUTPUT"}
]
}
Where REPLACE_WITH_ACTION_1_OUTPUT = tap the variable magic wand → select "Dictated Text" (or "Provided Input")
Action 3: Extract Claude's response
Get Dictionary Value - Get: Value for content in Contents of URL (from Action 2) - Then chain: Get Dictionary Value → Get: Value for 0.text
Result = the JSON string Claude generated.
Action 4: POST to Apps Script
Get Contents of URL
- URL: paste your Apps Script Web App URL from Step 2
- Method: POST
- Headers: content-type: application/json
- Request Body: File → the extracted text from Action 3
- Wrap in a dictionary that adds the secret:
Actually easier - add another Dictionary action first:
json
{
"secret": "YOUR_AUTH_SECRET",
"action": "add",
"row": {"subject": "...", ...}
}
But simplest: modify the Claude system prompt to always include "secret": "YOUR_SECRET" in output.
Action 5: Parse response
Get Dictionary Value → get message field from Action 4's response.
Action 6: Speak / Show result
Speak Text (or Show Notification) - Text: value from Action 5
5Test the Shortcut
- Tap the shortcut's play button
- Speak or type: "Add GPU Programming quiz on Oct 15 at 6pm, 10 questions"
- Watch the actions execute
- Verify: - Response speaks/shows: "Added row #N: GPU Programming - Quiz" - Open your Google Sheet → new row appears
Try: - "What's due this week?" - "Show upcoming items" - "Mark row 5 as completed"
6Add to Siri and Home Screen
Add to Siri
- Shortcuts app → tap your shortcut → settings icon (top right)
- Add to Siri → record phrase: "tracker update" or "log this"
Add to Home Screen
- Same settings → Add to Home Screen
- Rename and pick an icon
- Now it's a one-tap app
Add to Apple Watch
- iPhone Shortcuts app → tap shortcut → toggle Show on Apple Watch
- Complication placement optional
Works on:
- iPhone ✅
- iPad ✅
- Apple Watch ✅
- Mac (Shortcuts app) ✅
- HomePod ✅ (via Siri)
- CarPlay ✅
Advantages over Options 1, 2, 4
- No servers to maintain - Apps Script hosted by Google, free forever
- No cloud vendor lock-in - everything runs in your Google account
- Fast setup - 30 min vs 45-60 min for bots
- Works offline for trigger - shortcut works, then syncs when online
- Native iOS integration - Siri, Shortcuts, Focus Modes, Automations
Limitations
- Apps Script has ~5-10 sec cold-start delay on first call after idle
- iOS Shortcuts is iOS-only - Android friends need Option 2 or 4
- Complex logic hard in Shortcuts - no if/else nesting like real code
- Sharing = each user builds their own shortcut - no central deployment
Security note
- Never commit the Apps Script URL to a public repo without the
AUTH_SECRETcheck - Rotate
AUTH_SECRETif you suspect compromise - Apps Script logs every call - check via Apps Script → Executions
Summary
You now have a Siri-callable, home-screen-launchable, Apple Watch-compatible way to update your sheet without any cloud infrastructure beyond Google's own free Apps Script. Best for personal iOS-heavy users.
Next: - Add Option 5 (Siri Voice) on top for full voice interaction - Or move to Option 2/4 (bots) if you need Android/desktop friends included
Questions or corrections: [email protected]