Setup guide · Your AI Work Tracker
The page you came to steal from — and you should. Three layers working as one system: a task list your AI keeps honest, a tiny website (just a shell and a data file) hosted free, and a capture backend for thoughts that arrive mid-meeting. You talk about your day in plain conversation; it stays organized, prioritized, password-locked, and open from any browser. By the bottom of this page, you own one.
Three layers, one conversation. Your task list is where the truth lives. The website — a shell plus a data file — is where you look at it. The capture backend (optional, Step 6) is how you talk to it from a meeting. The website part really is this simple:
A plain to-do list tells you what exists. This one tells you what to do — because your AI reads every item through a few lenses at once, and lets priority fall out instead of guessing it. You don't set any of this up; it's just how the check-in thinks now.
And it grows views as your work gets busier — a "who owes me" list grouped by person, a per-project overview, a running log of the decisions you've made. That's the whole shift: you stop maintaining a list and start talking to something that already understands how your work flows.
One ZIP, five folders and a VERSION stamp. Each folder goes to a different place — the steps below explain exactly where. Nothing gets installed in the traditional sense; things mostly get copied and pointed at.
Inside: vault-setup/ · github-pages/ · apps-script/ · skills/ · tools/ · VERSION (currently 2026-07-01.2)
claude.ai/download if you haven't — everything below assumes it's installed.
This is the folder Claude reads and writes every session. The recommended setup is an Obsidian vault — Obsidian is a free note-taking app (obsidian.md) that stores everything as plain text files in a folder you own. When you create a new vault, Obsidian creates the folder for you. You then copy the contents of vault-setup/ into it.
Download Obsidian from obsidian.md. Open it and create a new vault — give it a name like "Work" or your name. Obsidian creates a folder on your computer automatically.
From the ZIP, open vault-setup/. Copy everything inside — TASKS.md, index.html, and data.json — directly into your vault folder. These are the files Claude will read and update each session.
In the Claude desktop app, open Cowork and create a new project. When it asks for a folder, point it at your Obsidian vault. Cowork will now be able to read and write files there directly — no copy-pasting needed.
vault-setup/ contents in there instead. You lose the pretty note-taking layer, the tracker doesn't notice or care. It works exactly the same.
index.html and data.json — the same two files that are also in your vault. Upload these to a free GitHub Pages repo so you can open your tracker from any browser. Step 2 walks you through this.
Kept for reference only. The old capture backend was a Google Sheet + Apps Script — we retired it. The tracker now captures right in the browser (localStorage) with a "Copy for check-in" button: no Google account, no web app, nothing to host. You can skip this folder entirely — see the updated Step 6.
One file: work-tracker-pipeline-friend.skill — the full architecture reference for this system: the three layers (vault → Apps Script → GitHub Pages), the exact tagging conventions, and the logic that turns your task list into data.json. Drag it into your first Claude chat and say "install this skill" (see Skills for the full install steps) so Claude has this as a standing reference in every future session — not just this one.
You already have both from Step 0 — but you should know what you're holding before you deploy it. Meet the skeleton and the heartbeat.
The skeleton. Filters, program view, stat tiles, quick capture, the password gate — all of it, and none of your data. Upload it to GitHub Pages once and never touch it again. It's the part of the tracker that doesn't know anything about you; it just knows how to display whatever the data file hands it.
The heartbeat. Claude rewrites this file every session with your current work — programs, open items, priorities, what got finished. Push a new version to GitHub and your live tracker updates in about thirty seconds. And once you've set a password, this file travels in disguise: what actually gets uploaded is data.enc.json, its encrypted twin, while the readable original never leaves your computer.
GitHub Pages: a free, permanent URL for your tracker, from the same place that hosts half the internet's code. No coding involved — you'll mostly be clicking buttons with reassuring names.
Go to github.com and sign up. Use your personal email — your account stays with you even if you change jobs.
Click the + in the top right → New repository. Name it something like work-tracker. Set it to Public. Click Create repository.
On the repository page, click Add file → Upload files. Drag in both index.html and data.json. Scroll down and click Commit changes.
Go to Settings → Pages (left sidebar). Under "Source", select Deploy from a branch, choose main, folder / (root). Click Save.
After ~30 seconds, refresh the Pages settings. Your URL will appear — something like yourusername.github.io/work-tracker. Open it. Your tracker is live.
GitHub Pages sites are public — anyone with the URL can read them, and making the repo private just unpublishes the site on a free plan. The kit solves this with real encryption instead.
Anything you'll remember. It never leaves your machine except as the key that unlocks the page in your own browser. If you ever forget it, nothing is lost — the plain data.json still lives in your folder; you just re-encrypt with a new password.
The kit's tools/encrypt_data.py turns data.json into data.enc.json (AES-256 encryption — the real thing, not a hidden-field trick). Say: "Encrypt my data.json into data.enc.json using encrypt_data.py with the password ______".
data.enc.json — and never the plain data.json
From now on the repo holds index.html + data.enc.json only. If a plain data.json is ever in the repo, delete it — it would defeat the whole gate. The plain file stays on your computer as the working copy.
You'll see a password box and nothing else. Enter your password once — your browser remembers it after that. Visitors without the password see an empty page. Bonus: your Apps Script URL is inside the encrypted file, so even your capture endpoint stays hidden.
Do not fill in data.json by hand — that's a job for the AI, and frankly it enjoys it. Paste the prompt below and let Claude interview you; you talk, it builds.
📋 Copy this prompt — paste it to Claude at your very first session
I'm setting up a personal work tracker. My tracker folder is at: [PASTE YOUR FOLDER PATH — e.g. C:\Users\yourname\Documents\Work Tracker\] The files in that folder are: - data.json — the one you update each session (Claude reads and rewrites this) - index.html — the display shell, never needs to change - tools/parse_tasks.py + tools/encrypt_data.py — the generator and the password-gate encryptor - work-tracker-pipeline-friend.skill — the architecture reference for this whole system Before we start, please check four things: 0. VERSION CHECK: open index.html and confirm its second line says "KIT_VERSION: 2026-07-01.2". Also check the VERSION file from the starter zip. Then, if you have web access, fetch https://shadiaalkhaledi-dot.github.io/AIworkflow/work-tracker-guide.html and confirm no newer KIT_VERSION is mentioned there. If my files are missing the stamp, show an older version, or the live guide lists a newer one, STOP and tell me — I need to re-download work-tracker-starter.zip from that page before we set anything up. 1. Do I have a Cowork folder selected that points to the folder above? If not, remind me to add it in the Claude desktop app (Cowork panel → select folder) before continuing. 2. Is there a work-tracker-pipeline-friend.skill file in this folder? If so, read it now (or tell me to attach it and say "install this skill" if you can't read .skill files directly) — it explains the tagging conventions and generator logic so you don't have to guess at them each session. 3. Does data.json have a real Apps Script URL in the "apiUrl" field, or does it still say "PASTE_YOUR_APPS_SCRIPT_URL_HERE"? If it's still a placeholder, remind me to set up Google Sheets + Apps Script (see Step 6 of the setup guide) and paste the URL before our first real session. We can still do the onboarding interview now, but Quick Capture won't work until that's set. Now, interview me about my work so you can fill in data.json properly. Ask me one topic at a time: 1. What is my project or role — what am I responsible for? 2. What are the main workstreams or programs I'm tracking? (e.g. different building packages, client files, product areas) 3. Who are the key people I work with — names, roles, and what I need from them? 4. What are the open items currently on my plate — what's in progress, what's blocked, what am I waiting on? 5. What meetings or decisions happened recently that I should capture? 6. Are there any notes from Microsoft Copilot, Teams, or email that I want to pull in? After the interview, write a complete data.json and save it to my tracker folder. Then tell me how to upload it to GitHub so my tracker site updates.
The daily ritual — and the whole reason this system stays alive while app-based trackers gather guilt. Paste this at the start of each session; Claude reads the current state, asks what changed, hands back the update.
📋 Copy this prompt — use it at the start of every work session
Work session check-in. My tracker folder is at: [YOUR FOLDER PATH — e.g. C:\Users\yourname\Documents\Work Tracker\] My live tracker site is at: [YOUR GITHUB PAGES URL] If you have work-tracker-pipeline-friend.skill installed or available in this folder, use it as your reference for the tagging conventions (status/court/horizon/action/cat/disc) so things stay consistent as this grows. Please read the current data.json and then ask me: - What happened since last time? - Anything new that's blocking me? - Any items I can mark done? - Any new items to add? - Any meetings or decisions to capture? After I fill you in, update data.json to reflect today's state — new items, completed items, updated notes, and a "what we sorted out today" summary. Set meta.generated to today's date. Then, if I've set a site password: re-encrypt with tools/encrypt_data.py (data.json → data.enc.json) and give me data.enc.json to upload to GitHub — never the plain data.json. If I haven't set a password yet, give me data.json as before.
Already drowning in Teams meetings and Copilot transcripts? Good news — that's not a problem, that's an input. Those notes feed straight into the tracker.
Copy the Copilot summary from Teams. Paste it into your check-in conversation with Claude and say: "Here's the Copilot summary from today's meeting — pull any action items or decisions into my tracker."
Copy the relevant email thread or paste key lines. Say: "Add this to the relevant item in my tracker as a note." Claude will file it under the right item and program.
If you jot notes in Teams chat or a notepad during the day, paste them all at once during your check-in. Say: "Here's everything that happened today — sort it into my tracker." Claude knows where things belong.
The tracker's ears — for the thoughts that arrive mid-meeting, when opening a chat would be rude and a sticky note would be a lie. The capture panel is built right into your live page: it queues what you type in the browser and gives you a "Copy for check-in" button. Next check-in, you paste that and your AI folds it in.
Go to sheets.google.com → New blank sheet. Name it Work Tracker Captures. You don't need to add headers by hand — the Apps Script below creates a "Log" tab with the right columns automatically the first time anything is captured.
In the sheet, go to Extensions → Apps Script. A code editor will open. Delete everything in the editor and paste the script below. Click the save icon (or Ctrl+S).
Click Deploy → New deployment. Select type Web app. Set "Execute as" to Me and "Who has access" to Anyone. Click Deploy. Copy the long URL it gives you — this is your Apps Script URL. Keep it handy for the next step.
Open data.json in your Cowork folder. Find the first line: "apiUrl": "PASTE_YOUR_APPS_SCRIPT_URL_HERE". Replace the placeholder with your real URL. Save, then push the updated data.json to GitHub. The tracker now knows where to send captures — no other file needs to change.
At your next check-in, say: "Also check for any pending Quick Captures from my sheet." Claude reads the unsynced rows (via the apiUrl in data.json), folds them into your task list, then marks them synced so they don't get pulled twice.
📋 Apps Script code — paste this into Extensions → Apps Script
var LOG_HEADER = ['Timestamp', 'Type', 'Program', 'Package', 'ItemRef', 'Text', 'Synced'];
function getSheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Log');
if (!sheet) {
sheet = ss.insertSheet('Log');
sheet.appendRow(LOG_HEADER);
sheet.setFrozenRows(1);
}
return sheet;
}
function doPost(e) {
var sheet = getSheet();
var data = JSON.parse(e.postData.contents);
if (data.action === 'edit') {
return handleEdit(sheet, data);
}
sheet.appendRow([
new Date(), data.type || '', data.program || '', data.pkg || '',
data.itemRef || '', data.text || '', 'N'
]);
return ContentService.createTextOutput(JSON.stringify({ok: true}))
.setMimeType(ContentService.MimeType.JSON);
}
function handleEdit(sheet, data) {
var row = Number(data.row);
if (!row || row < 2 || row > sheet.getLastRow()) {
return ContentService.createTextOutput(JSON.stringify({ok: false, error: 'Invalid row'}))
.setMimeType(ContentService.MimeType.JSON);
}
sheet.getRange(row, 2, 1, 5).setValues([[
data.type || '', data.program || '', data.pkg || '', data.itemRef || '', data.text || ''
]]);
return ContentService.createTextOutput(JSON.stringify({ok: true}))
.setMimeType(ContentService.MimeType.JSON);
}
function doGet(e) {
var sheet = getSheet();
var action = e.parameter.action || 'pending';
if (action === 'markSynced') {
var rows = JSON.parse(e.parameter.rows || '[]');
rows.forEach(function(r){ sheet.getRange(r, 7).setValue('Y'); });
return ContentService.createTextOutput(JSON.stringify({ok: true, marked: rows.length}))
.setMimeType(ContentService.MimeType.JSON);
}
var values = sheet.getDataRange().getValues();
var out = [];
for (var i = 1; i < values.length; i++) {
if (String(values[i][6]) === 'N') {
out.push({row: i+1, ts: values[i][0], type: values[i][1],
program: values[i][2], pkg: values[i][3], itemRef: values[i][4], text: values[i][5]});
}
}
return ContentService.createTextOutput(JSON.stringify(out))
.setMimeType(ContentService.MimeType.JSON);
}
function out(data) {
return ContentService.createTextOutput(JSON.stringify(data))
.setMimeType(ContentService.MimeType.JSON);
}
The blank slate — every placeholder below gets replaced during your onboarding interview, so this is for reading, not typing. The // ← comments explain what lives where.
{
"apiUrl": "[PASTE YOUR APPS SCRIPT URL HERE — or leave as-is until you set up Quick Capture]",
"meta": {
"title": "Work Tracker — [Your Name]",
"lede": "[One sentence describing what you're tracking, e.g. 'Airport terminal fitout — Concourse B and Central PBS']",
"sessionDate": "[e.g. Tuesday, July 1]",
"sprintEnd": "[e.g. 2026-07-10]",
"sprintEndLabel": "[e.g. Jul 10]"
},
"programs": [
{
"name": "[Program or workstream name, e.g. 'Project Alpha']",
"color": "var(--teal)",
"packages": [
"[Sub-area or package, e.g. 'Design Development']",
"[Another sub-area, e.g. 'Client Coordination']"
]
},
{
"name": "[Second program, e.g. 'Project Beta']",
"color": "var(--mauve)",
"packages": [
"[Sub-area]"
]
}
],
"items": [
{
"id": "i01",
"title": "[Action item title — what needs to happen]",
"status": "open",
"program": "[Must match a program name above]",
"pkg": "[Must match a package name above]",
"court": "You",
"delegate": "[Optional — name of who you've handed this off to. Omit the field entirely if nobody's been delegated to.]",
"horizon": "sprint",
"action": "chase",
"cat": "coordination",
"disc": ["AR"],
"due": "2026-07-03",
"isNew": true,
"why": "[Why this matters or what's at stake]",
"meta": "[Extra context — who raised it, what meeting it came from]"
}
],
"details": {
"i01": {
"next": "[The specific next action — what exactly do you do next]",
"subtasks": [
"[Sub-task 1]",
"[Sub-task 2]"
],
"updates": [
"2026-07-01|[What happened on this date]"
]
}
},
"voice": [
{
"h4": "[Topic or decision heading]",
"p": "[What was decided or sorted out — written as a plain sentence]"
}
],
"completedThisWeek": [
{
"h3": "[Program name]",
"accent": "var(--teal)",
"h3color": "var(--teal)",
"items": [
{
"h4": "[Completed item title]",
"p": "[What was resolved and how]"
}
]
}
],
"completedLastWeek": []
}
Field reference
| Field | What it means |
|---|---|
| status | "open", "blocking", "unverified", or "watch" |
| court | Who it's waiting on — "You", or a person's name |
| delegate | Optional — if you've handed this off to someone, their name. Shows as "Delegated to: [name]" next to court on the card. Leave the field out entirely if nobody's been delegated to. |
| horizon | Urgency — "today", "sprint" (before sprint end date), "week", or leave blank |
| action | What kind of task — "model", "review", "coordinate", "chase", "admin" |
| cat | Category — "decisions", "meetings", "coordination", "documentation", "drawings", "rfi" |
| disc | Discipline tags — "AR", "ST", "ME", "EL", "signage", "safety", "security", "owner" |
| isNew | true = shows an N badge (new since last check-in). Set to false after first upload. |
| due | Due date in YYYY-MM-DD format — drives the stat tiles and urgency sort |
[cat:: coordination] [disc:: AR] [action:: chase] [court:: You] [horizon:: today] [delegate:: Jordan]. Claude reads these mechanically instead of re-inferring them, which keeps your tracker consistent as it grows. See the example rows already in your TASKS.md. Not required — Claude can still triage without them — but the more you tag, the less guesswork there is.
Pick a color for each program from this list. Paste the full token (including "var(--") into the "color" field.
var(--teal)
var(--sage)
var(--mauve)
var(--gold)
var(--terracotta)
var(--rose)