# Taskivities > Taskivities (Tasks + Activities) is an open-source, keyboard-fast task manager for small teams. Everyone records their daily tasks against a project; tasks can be assigned or transferred to anyone on the project; overdue tasks roll into today until someone finishes or moves them; every change is kept in an append-only history; and every open browser updates on its own. Each team runs its own copy on its own Cloudflare account (Workers + D1), and AI assistants such as Claude connect to it over MCP and act as the signed-in person. MIT licensed. - Website: https://www.taskivities.com - Features: https://www.taskivities.com/features - Open source and self-hosting: https://www.taskivities.com/open-source - Source code: https://github.com/taskivities/taskivities-app - License: MIT, © 2026 Yusuf Bhabhrawala This file describes what the app does and how it is built, for language models and the people using them. Names in `code` are real file, table, route and tool names from the repository. ## At a glance - One team per instance. Each instance is its own Cloudflare Worker with its own D1 database, so teams never see each other's data. The same code deploys to as many instances as you like (one per team or client). - Sign-in with Google. Only people an admin has added can sign in. - Two roles: admins (see every project, manage people, projects and settings) and members (see only the projects they are on). - Every task always has a project, a due date, an assignee and a priority. None can be empty. - Views: Today, Week and Board, for yourself or anyone you can see; a Projects page; a Team page and a Settings page for admins. - Repeating tasks, comments, one checklist per task, file attachments, archive (not delete), webhooks that create tasks, invite emails sent from the admin's Gmail, a custom logo and home-screen icon, light and dark themes, and an installable phone app. - Nearly everything can be done from the keyboard. - A remote MCP server at `/mcp` with 14 tools, so an AI assistant can read and change tasks as the person who connected it. ## Stack - App: React 19 + TypeScript with zustand for state, built with Vite (`@cloudflare/vite-plugin`), served as static assets by the Worker. - API: one Cloudflare Worker using Hono (`worker/`). - Database: Cloudflare D1 (SQLite). Schema in `migrations/0001`–`0010`. - Sign-in: Google OAuth (authorization code + PKCE), handled in the Worker. Sessions are HttpOnly cookies (`tv_session`, `SameSite=Lax`, 30 days, renewed when less than half remains); only a SHA-256 of the token is stored in `sessions`. - Live updates: the browser polls `/api/sync?since=` every 10 seconds (every 60 seconds while the tab is hidden, and at once when it becomes visible or focused). - AI assistants: Cloudflare's `agents` SDK (`McpAgent`, one Durable Object per MCP session, class `TaskivitiesMCP`, binding `MCP_OBJECT`) behind `@cloudflare/workers-oauth-provider` (OAuth 2.1, dynamic client registration, tokens in the KV namespace `OAUTH_KV`). Streamable HTTP transport. - Files: an optional R2 bucket (binding `FILES`) for attachments. - Validation: zod for MCP tool inputs. - No other runtime services: no queues, no cron jobs, no separate server. ## Repository layout - `worker/index.ts`: routes and the OAuth provider wrapper. - `worker/auth.ts`: Google sign-in, sessions, the local development sign-in. - `worker/api.ts`: REST for tasks and sync. - `worker/tasks.ts`: the task rules shared by REST and MCP (`createTask`, `applyPatch`, `addComment`, `changeChecklist`, `setArchived`, `loadEditableTask`). - `worker/mcp.ts`: the MCP tools. - `worker/oauth.ts`: the `/authorize` sign-in and consent page for assistants. - `worker/admin.ts`: people, projects and settings (admins only). - `worker/settings.ts`: logo, home-screen icons and the web app manifest. - `worker/gmail.ts`, `worker/invite-mail.ts`: invite emails through the admin's Gmail. - `worker/webhooks.ts`: incoming webhooks and their admin API. - `worker/attachments.ts`: attachments in R2 and one-time upload links. - `worker/lib.ts`: shared helpers (errors, audit log, version bumps, column lists). - `shared/`: code used by both the Worker and the app: `types.ts`, `dates.ts`, `rules.ts` (visibility and rollover), `repeat.ts` (repeat rules), `webhook.ts` (webhook mapping). - `src/`: the React app: `pages/` (Tasks, Projects, Team, Settings, Login), `components/` (Drawer, Popover, QuickAdd, WebhookSheet, ConnectSheet, HelpSheet, rows, ui), `actions.ts` (every write), `store.ts`, `keyboard.ts`, `focus.ts`, `history.ts`, `theme.ts`, `install.ts`, `brand.ts`, `wordmark.ts`, `styles.css`. - `public/`: `favicon.svg` (the built-in logo), `default-icons/` (180, 192, 512 px PNGs), `sw.js` (service worker). - `migrations/`, `seed/dev.sql` (local sample data), `scripts/tenant.mjs` (per-instance deploys), `tenants/example.env`. ## Rules the code enforces - Every task has a project, a due date, an assignee and a priority. The columns are `NOT NULL`, and the API rejects any attempt to clear them. - A task's assignee must be on the task's project. - A task can't be given a due date in the past. - "Today" is each person's own today. The browser sends its IANA time zone with every request in the `x-tz` header; the server uses it for rollover, the past-date check and the day a task counts as done (`done_on`). It is also saved on the person (`users.time_zone`) so their AI assistant uses the same zone. `TEAM_TZ` is only a fallback for someone who hasn't opened the app yet. - Rollover is computed, never stored. An open task whose due date has passed is shown on today and its "days late" keeps counting (`shared/rules.ts` → `shownOn`). There is no nightly job. - History is append-only. Every write updates `tasks` and inserts into `task_events` in the same D1 batch, and database triggers (`task_events_no_update`, `task_events_no_delete`) refuse any UPDATE or DELETE on `task_events`. - One set of task rules. The REST API and the MCP tools both call `worker/tasks.ts`, so visibility, "assignee must be on the project" and "no past due dates" can't diverge. - Writes only come from the app's own pages: they need the same origin plus a custom header, and the session cookie is `SameSite=Lax`. `/mcp` is the one exception; it takes an OAuth bearer token instead. - Limits: title 300 characters, notes 10,000, comment 4,000, project description 600, checklist item 300, 100 checklist items per task, 50 attachments per task, 25 MB per file. ## Roles and people - Admins see every project and are the only ones who manage people, projects and settings. - Members see only the projects they are on, and only tasks in those projects. - People have a status: `invited` (added, not signed in yet), `active` or `removed`. Removing someone (or setting them inactive) ends their sessions and cuts their AI assistant off immediately. - Only email addresses added by an admin can sign in. On a brand-new install the first person to sign in becomes the admin; the check is one atomic insert, so only one person can win. The optional `ADMIN_EMAILS` secret names addresses that are always let in as admins (useful if you lock yourself out). - Each person gets a colour; their Google name and photo are used where available. - Admin actions (adding or removing people, role and project changes, logo and icon changes, webhook changes) are written to `audit_log`. Removing a person also deletes their sessions. ## Priorities 1 Urgent · 2 High · 3 Normal (the default) · 4 Low. In quick add, starting a title with `!` makes it High and `!!` makes it Urgent. Keys 1–4 set the priority of the selected task. ## The Tasks page - Three views, switched with V or the Today / Week / Board control: - Today ("My day"): today's tasks, with carried-over tasks first. - Week ("My week"): the week as a list of days, Monday to Sunday. Previous and next week with [ and ]. - Board: the week as columns, one per day. ← → (or H L) move between days. - Each view has two sections: "Assigned to me" (tasks you must do) and "Assigned by me" (open tasks you gave to others, with a "View week →" link to that person). - Carried over: overdue open tasks appear in an orange "Carried over" block on today with how many days late each one is, the oldest first, and a "Move all" button that reschedules them together. - Switch person (P): look at anyone else's day or week that you can see. A banner reminds you that your changes are recorded under your name. - Quick add (N): type a title and press Enter. Optional chips set the project, due date, repeat, priority and assignee; notes can be added. Adding for someone else assigns it to them, and they get a notice. - Rows show the title, priority, project, assignee, due date or days late, and small counts for checklist progress ("2/5"), attachments (📎) and comments. - Upcoming copies of repeating tasks show as faint previews in the week views (calculated in the browser, never saved). - Undo (⌘Z / Ctrl+Z, or the Undo button in the toast) reverses the last change such as done, move, transfer or archive. - Live notices: " assigned you " when someone gives you a new task, and "New comment on " (with Open) when someone comments on a task assigned to you or created by you. Rows changed by someone else flash briefly. ## The task panel (drawer) Opened with Enter or a click. It shows: - Title (E to rename), status, due date (D, or ⇧← / ⇧→ to move a day), priority, project, assignee (T to transfer), and the repeat rule for repeating tasks (R to change, S to skip this copy). - Notes (⇧N) and the checklist (⇧C). When empty, they fold into small "Add notes" and "Add checklist" buttons. - Attachments (⇧A, or drop or paste files anywhere on the panel). - Comments (C) and the full history, grouped so several changes in a row by one person read as one entry. Changes made by an AI assistant or webhook are marked "via Claude", "via webhook" and so on. - A ⋯ menu with Notes, Checklist, Attach files and Archive task. ## Repeating tasks - Repeating is set when a task is created (in quick add or with the MCP `create_task` tool). A normal task can't be turned into a repeating one later. The rule can be changed or stopped later; stopping is permanent. - Rules (`shared/repeat.ts`, type `Repeat`): `freq` day / week / month / year; `every` N; weekly `days` (0 = Sunday … 6 = Saturday); monthly `monthBy` "date" (the 15th) or "weekday" (the 2nd Tuesday, the last Friday); `afterDone` (count from the day it was finished, e.g. "7 days after done"); an optional end date `until`; an optional total `count`. Presets plus a custom editor. - A `series` row holds the rule and the defaults for future copies (title, notes, project, assignee, priority). Each copy is an ordinary task with its own history and a `series_id`. - One open copy at a time. Marking a copy done inserts the next copy in the same D1 batch, guarded by "no open copy in this series", so two people finishing it at once can't create two. No scheduled job. - Missed dates aren't piled up: an unfinished copy rolls over like any task, and the next copy always lands after today. - Skip moves the open copy to the next scheduled date. Reopening a finished copy leaves the next copy in place. - Edits to the open copy (title, notes, priority, project, assignee) carry forward to future copies, except a transfer marked "just this one". Date moves only affect that copy. - The next copy starts with the same checklist items, unticked. - A repeating task whose open copy is archived pauses (no next copy) and carries on when restored. ## Comments - Comments live in the task's history (`task_events.type = 'comment'`), so they are permanent: no editing or deleting. - Anyone who can see the task can comment, on open or done tasks, up to 4,000 characters. - A comment moves the task's version on, so everyone's app picks it up on the next poll; the assignee and the creator get a "New comment" notice. ## Checklists - One checklist per task (`checklist_items`, migration 0007). Anyone who can see the task can add items (pasting several lines adds them all), tick, rename (click the text) and remove them. - Every change is in the history (`check_added`, `check_done`, `check_undone`, `check_renamed`, `check_removed`). - Rows show progress ("2/5"). ## Attachments - Optional per instance. Files live in an R2 bucket (binding `FILES`, set by `R2_BUCKET` in the tenant file). Without a bucket the app hides "Attach". - Drop files on a task, paste a screenshot, or press ⇧A. Up to 25 MB per file and 50 per task. - Every download goes through the Worker, which checks the person can see the task. Only images, PDFs and plain text open in the browser (under a sandboxing Content-Security-Policy); everything else downloads, so an uploaded HTML or SVG file can't run code. - Only the uploader or an admin can remove a file. It is deleted from R2 and the history keeps its name (`attached`, `detached` events). ## Archive (instead of delete) - Tasks are never deleted. "Archive task" is in the task's ⋯ menu, deliberately not a one-click button or a keyboard shortcut. - An archived task leaves every list, count, sync and assistant listing at once (with Undo), and nothing is lost (`tasks.archived_at`, `archived_by`, migration 0010). - Archived tasks, open or done, are listed only on the Projects page in a collapsed "Archived" section, where anyone on the project can restore them. - An archived task can't be edited, commented on or ticked until it is restored. ## The Projects page - Pick a project (or all of yours) and a day: what got finished that day, sorted by priority and then due date, and what's still open. - "Copy summary" copies a plain-text summary for pasting into chat or email. - The collapsed "Archived" section lists the project's archived tasks with Restore. ## The Team page (admins) - Add people by email, choose admin or member and the projects they are on. With invite emails on, each new person is emailed an invite; otherwise the app offers the sign-in link to share. A resend button sends it again. - Change a person's role and projects, or remove them. - Create and edit projects: name, colour, description (the description also tells AI assistants what belongs in the project) and members. ## The Settings page (admins) - Logo: SVG only, up to 256 KB. Shown in the top bar, on the sign-in page and as the browser tab icon. "Use the built-in logo" goes back to the default. Checked in the browser and again on the server: no scripts, event handlers, embedded HTML, DOCTYPEs or links to outside files. `/logo.svg` is served with a locked-down Content-Security-Policy (`sandbox`, no scripts), so even opening it directly can't run anything. - App icon: a square image (PNG, JPEG or WebP) for phone home screens. The browser resizes it to 180, 192 and 512 px PNGs (Workers can't resize images); the server checks each is a PNG of exactly that size. - Invite emails: connect the admin's own Gmail (see below). - Webhooks: create tasks from other services (see below). - Every settings change is in the audit log, and everyone's app picks it up on its next poll. ## Top bar - The team's logo and the TASKIVITIES wordmark (a green check forms the K) go to the Tasks page. - A ⋯ menu next to them lists Projects, Team and Settings (Team and Settings for admins), and Tasks when you are on another page. - On the right: MCP (how to connect an AI assistant), keyboard shortcuts (?), a "Live" indicator, and the account menu (theme, connect an AI assistant, install app, sign out). ## Keyboard shortcuts Press ? anywhere for the list. - Move around: ↑ ↓ or J K previous / next task · ← → or H L previous / next day (board) · Enter open the task · Tab next control · Esc close panel or menu. - Selected task: X or Space done / not done · D change date · ⇧← ⇧→ one day earlier / later · T transfer · R change repeat · S skip this copy · 1 2 3 4 Urgent / High / Normal / Low · E rename · ⇧N notes · ⇧C checklist · ⇧A attach files · C comment. - Anywhere: N new task · V Today → Week → Board · P switch person (or project) · [ ] previous / next week (or day) · G T / G P / G U / G S go to Tasks / Projects / Team / Settings · ⌘Z undo. - Menus and the calendar: ↑ ↓ choose · arrow keys pick a day · Enter select · type to search people · Esc back without changing. - Focus is managed so the keyboard always lands somewhere sensible after every action, and there is a "Skip to tasks" link. ## Webhooks (create tasks from other services) - Settings → Webhooks. Each webhook has a secret address, `POST /hooks/`, accepting JSON, form data, or GitHub/Slack-style `payload=` forms, up to 64 KB. "New address" replaces the token and the old one stops working. - A new webhook only collects samples. The last 15 requests are stored, and the editor shows each one's fields as `body.…`, `headers.…` and `query.…`. Clicking a field puts `{{body.issue.title}}` into the title or notes template, or picks it for the assignee, due date, priority, "only when" or "skip repeats" options. A live preview shows the task the selected request would create. - Saving the mapping makes it live: each request then creates a task, made by the admin who saved it and marked "via webhook" in the history. - Mapping (`shared/webhook.ts`, type `Mapping`): title template (required), notes template, project, assignee (a fixed person, or matched by an email or name in the request, falling back to the fixed person), due date (today, N days from today, or read from a field; past or missing dates become today), priority (fixed, or read from words like `high`, `P1` or `2`), "only when" a field equals a value, and "skip repeats" on a field such as an issue id. - Results: created (HTTP 201), sample stored (202), skipped by "only when" (200), duplicate (200), paused (logged, no task) and error such as a missing title (422; no half-empty tasks). Every request is logged with its result. - Credentials and IP addresses in headers (`authorization`, cookies, `x-forwarded-*`, Cloudflare's client IP headers) are never stored. - Pause keeps logging without creating tasks; "Stop & edit samples" goes back to collecting. Stored requests can be replayed. ## Invite emails through Gmail - Optional, per admin. On the Settings page an admin connects their Gmail; Google is asked for `gmail.send` only, so the app can send mail as them but never read it. After that, adding people (and the resend button on the Team page) emails each one an invite from the admin's own address, with a sign-in button. - The refresh token is stored encrypted (AES-GCM) in `google_tokens`, with a key derived from `GOOGLE_CLIENT_SECRET` (or the optional `TOKEN_KEY` secret). Disconnecting deletes it and revokes it at Google; removing the person deletes it too. - It never blocks adding people. If Gmail is not connected, the permission was unticked, the token was revoked or expired, the Gmail API is off, or Gmail returns an error, the person is still added and the admin is offered the sign-in link to share. A connection Google no longer accepts is dropped so the app shows "not connected" again. - It reuses the sign-in callback (`/auth/callback`), so there's no extra redirect URI to register. ## Branding, themes and the phone app - Logo: the built-in logo is the Taskivities check-K mark (navy tile, green check). Admins can replace it with their team's own SVG logo, which then appears in the top bar, on the sign-in page and as the tab icon. - Themes: each person picks Light, Dark or System (follows the operating system, live) in the account menu; the choice is saved in that browser. All colours come from CSS variables in `src/styles.css`, swapped by `[data-theme="dark"]`; `index.html` applies the choice before the page draws, so there's no flash. Logos sit on a light tile in the dark theme. The assistant consent page follows the OS setting. - Install on a phone: `/manifest.webmanifest` and `/icons/<180|192|512>.png` are served by the Worker, so the app opens full screen from a home-screen icon. `public/sw.js` is a minimal service worker so phones offer to install it; it caches nothing and only shows a "you're offline" page when a page can't load. The account menu offers "Install app" where the browser supports it and explains Share → Add to Home Screen on iPhones. Safe-area padding keeps the top bar clear of the notch and the home bar. - Responsive layout: on phones the top bar collapses to the logo, ⋯, MCP, Live and account; the keyboard shortcuts button is hidden. ## Live updates and sync - `meta.version` is a single counter bumped by every task write; each task stores the version of its last write. `/api/sync?since=` returns the tasks that changed since then (and `gone` ids for tasks that left the person's view, such as archived ones) plus the current `today`. - `meta.directory_version` is bumped by changes to people, projects and settings, so every app reloads them when they change. - The app ignores its own in-flight writes when a sync arrives, so your own changes don't flash or notify you. - When the network drops, an "Offline" bar shows until the connection is back. ## AI assistants (MCP) - Endpoint: `https:///mcp` (Streamable HTTP). In Claude, add it as a custom connector with that URL; other MCP clients take the same URL. - The client registers itself (dynamic client registration), the person signs in with the same Google account (or is already signed in) and approves the assistant on a consent page at `/authorize`. From then on the assistant acts as that person: it sees only what they see, follows the same rules, and every change it makes is written to the task history with `task_events.via` set to the client's registered name (shown as "via Claude"). - People and projects are given by name, email or "me", not ids. Access is checked on every call, so removing someone or taking them off a project applies to their assistant at once. - Access tokens last an hour; a connection lasts a year from approval, after which the person signs in again. Removing a person (or setting them inactive) cuts their assistant off immediately. - OAuth endpoints served by the provider: `/authorize`, `/token`, `/register` and the `/.well-known` metadata. The MCP resource is the Worker's own origin + `/mcp`, so workers.dev and custom domains both work. Tools: - `list_projects()`: your projects with name, description, colour and who can be assigned there. The descriptions tell the assistant where tasks belong. - `list_tasks(user?, project?, from?, to?, status?)`: tasks by the day the app shows them on, with the same rollover rule, so "today" includes carried-over tasks. Defaults to today; at most 92 days. - `create_task(title, project, due_date, assignee, priority, notes?, repeat?)`: all five main fields are required. The tool description includes the project descriptions, so the assistant can pick the project without asking. `repeat` makes it a repeating task. - `update_task(id, title?, notes?, project?, due_date?, priority?, status?, repeat?, stop_repeating?, skip?)`: only the fields given change; `status: "open"` reopens. On a repeating task it can change, stop or skip the repeat. - `complete_task(id)`: marks it done today; for a repeating task it returns the new copy as `next_copy`. - `transfer_task(id, to, note?, this_only?)`: the new assignee must be on the task's project. `this_only` leaves future copies of a repeating task with the current person. - `archive_task(id, restore?)`: archives a task (hidden everywhere, nothing lost) or restores it. - `list_comments(id)`: the task's comments, oldest first. - `add_comment(id, text)`: posts a comment as the person, marked "via ". - `update_checklist(id, add?, check?, uncheck?, remove?)`: changes the task's checklist; items are referred to by id or exact text. Every task result includes its `checklist`. - `attach_file(id, name?, text | base64 | url)`: attaches a file: text it writes, a small base64 file (up to 2 MB), or a public https link the server downloads (up to 25 MB; no local addresses). - `get_upload_url(id, name?)`: a one-time link, valid 15 minutes, to POST a file to directly, e.g. `curl --data-binary @report.pdf ` from an assistant that can run commands. Only a hash of the link is stored; it works once, as the person who asked, and stops working if they lose access. - `read_attachment(id, file)`: reads a text file attached to a task (up to 200 KB). Every task result lists its `attachments`. - `project_day(project, date)`: what got finished in a project that day (by priority, then due date), plus what's still open. ## HTTP routes Public: - `GET /logo.svg`: the team's logo or the built-in one (ETag; `?v=` versions are cached for good). - `GET /manifest.webmanifest`, `GET /icons/:size.png`: the installable app. - `ALL /hooks/:token`: incoming webhooks. - `POST /upload/:secret`: one-time upload links for assistants. - `/auth/login`, `/auth/callback`, `/auth/gmail`, `POST /auth/logout`, and `/auth/dev-login` (only when `DEV_LOGIN=1` and the request comes from localhost; 404 in production). - `/authorize` (assistant sign-in and consent), `/token`, `/register`, `/.well-known/…`, `/mcp`. App API (same origin + custom header + session): - `GET /api/bootstrap`: the signed-in person, people, projects, today, time zone, versions, settings, whether attachments are on, and the connected Gmail. - `GET /api/tasks?from&to`, `GET /api/sync?since`. - `POST /api/tasks`, `PATCH /api/tasks/:id`, `POST /api/tasks/move` (several tasks to one date). - `POST /api/tasks/:id/archive`, `POST /api/tasks/:id/restore`, `GET /api/archived`. - `POST /api/tasks/:id/comments`. - `POST /api/tasks/:id/checklist`, `PATCH|DELETE /api/tasks/:id/checklist/:item`. - `GET /api/tasks/:id/events`: the task's history. - `POST /api/tasks/:id/attachments`, `GET|DELETE /api/tasks/:id/attachments/:aid`. - Admin: `POST /api/admin/users`, `PATCH|DELETE /api/admin/users/:id`, `POST /api/admin/users/:id/invite`, `POST /api/admin/projects`, `PATCH /api/admin/projects/:id`, `PUT|DELETE /api/admin/settings/logo`, `PUT|DELETE /api/admin/settings/icon`, `DELETE /api/admin/gmail`, and the webhook admin API (list, create, update, rotate the address, delete, list requests, replay a request). ## Data model (D1) - `users`: id, email (unique, case-insensitive), name, avatar_url, color, role (admin | member), status (invited | active | removed), added_by, added_at, last_seen_at, time_zone. - `projects`: id, name (unique among active projects), color, description, archived, created_at. `project_members`: user_id, project_id. - `tasks`: id, title, notes, project_id, assignee_id, created_by, assigned_by, due_date (YYYY-MM-DD), priority (1–4), status (open | done), done_at, done_on, done_by, created_at, updated_at, version, series_id, archived_at, archived_by. - `task_events` (append-only): id, task_id, actor_id, via, type, from_value, to_value, note, at. Types: created, transferred, date_moved, priority_changed, project_changed, renamed, notes_edited, done, reopened, repeat_set, repeat_changed, repeat_stopped, skipped, comment, check_added, check_done, check_undone, check_renamed, check_removed, attached, detached, archived, restored. - `series`: the repeat rule (JSON), starts_on, defaults for future copies, made (copies used so far), active. - `checklist_items`, `attachments` (R2 key, name, size, type, uploader, removed_by/removed_at), `upload_links` (hashed one-time upload links). - `webhooks` (name, token, mapping JSON, active, last_received_at), `webhook_requests` (stored requests and their results), `webhook_keys` ("skip repeats" keys). - `settings` (logo, icons), `google_tokens` (encrypted Gmail refresh tokens), `sessions` (hashed tokens), `audit_log`, `meta` (version, directory_version). - Migrations: 0001 schema · 0002 time zones · 0003 repeating tasks · 0004 settings · 0005 Gmail tokens · 0006 webhooks · 0007 checklists · 0008 attachments · 0009 upload links · 0010 archive. ## Run it locally ``` npm install cp .dev.vars.example .dev.vars npm run db:reset:local # creates the local D1 database and loads the sample data npm run dev # http://localhost:5173 ``` Open http://localhost:5173/login and use "Local development sign-in", e.g. `jordan@example.com` (admin) or `sara@example.com` (member). Google isn't needed locally. To try the MCP server, run `npx @modelcontextprotocol/inspector`, choose Streamable HTTP and connect to `http://localhost:5173/mcp`. Other commands: `npm run typecheck`, `npm run build`, `npm run db:migrate:local`. ## Deploy to Cloudflare Each instance is described by a git-ignored file, `tenants/.env`. `wrangler.jsonc` holds only shared settings and placeholder IDs, so a plain `wrangler deploy` fails rather than deploying somewhere by accident. Values in `tenants/.env`: - `WORKER_NAME`: the Worker; the app lives at `https://..workers.dev`. - `D1_DATABASE_NAME`, `D1_DATABASE_ID`: the instance's database. - `OAUTH_KV_ID`: where AI assistant sign-ins are stored. - `TEAM_TZ` (optional): the fallback time zone. - `CUSTOM_DOMAINS` (optional), `WORKERS_DEV` (optional, `false` to serve only on custom domains). - `CLOUDFLARE_ACCOUNT_ID` (optional), `CLOUDFLARE_PROFILE` (optional, a different Cloudflare login for this instance). - `R2_BUCKET` (optional): storage for attachments; empty turns attachments off. Steps for a new instance: 1. `npx wrangler login` 2. `npm run tenant -- create acme --tz Europe/London`: creates the D1 database `taskivities-acme`, the KV namespace `taskivities-acme-oauth` and (if R2 is on) the bucket `taskivities-acme-files`, and writes `tenants/acme.env`. Options: `--name`, `--domain`, `--account`, `--profile`. 3. Create a Google OAuth client (Web application) with the redirect URI `https:///auth/callback` for each host. Scopes: `openid`, `email`, `profile`. Optionally enable the Gmail API and add `https://www.googleapis.com/auth/gmail.send` for invite emails. One client can serve several instances. 4. `npm run tenant -- secret acme GOOGLE_CLIENT_ID` and `npm run tenant -- secret acme GOOGLE_CLIENT_SECRET`. Optional secrets: `ADMIN_EMAILS`, `TOKEN_KEY`. 5. `npm run deploy -- acme`: runs the database migrations, builds the app and deploys it. `--dry-run` builds and checks without deploying. 6. Sign in: on an empty install the first person to sign in becomes the admin. Add the team on the Team page and upload a logo on the Settings page. 7. Connect an assistant with `https:///mcp`. Day to day: `npm run tenant -- list`, `npm run db:migrate:remote -- `, `npm run tenant -- secret `, `npm run tenant -- wrangler ` (e.g. `tail` for live logs). With a single file in `tenants/` the instance name can be left out; `TENANT=acme npm run deploy` also works. Migrations always run before the deploy, so an instance can't end up on a newer app with an older database. `DEV_LOGIN` is never set in production. ## Not built yet - A list of connected assistants in the app, with a way to disconnect them. For now a connection ends after a year, when the person is removed, or when an admin deletes its grant from the `OAUTH_KV` namespace. - Archiving projects from the UI (the column exists, but there's no button yet). - Automated tests.