Sideist is a lightweight local sync bridge for Todoist and Google Calendar.
The first version focuses on the sync engine: it runs on your Windows PC, polls both APIs, stores local sync state, and keeps mapped Todoist projects and Google calendars aligned.
Sideist is an independent open-source project. It is not affiliated with, endorsed by, or sponsored by Todoist, Google, or Google Calendar.
- .NET 10
- No external NuGet packages
- Todoist API v1 Sync endpoint
- Google Calendar API v3
- Local JSON state store
- Configurable polling interval, default 15 seconds
- Configurable default time zone, default
Asia/Seoul - Exponential backoff after API/network failures
- Google refresh token support for long-running sync
src/Sideist.Core Pure sync models, ports, and sync engine
src/Sideist.Infrastructure Todoist/Google HTTP clients and local state store
src/Sideist.Cli Console runner for sync-once and background loop
src/Sideist.Tray Windows tray background app
dotnet build Sideist.slnxdotnet run --project src\Sideist.Cli -- init-configThis creates sideist.config.json. That file is ignored by git because it can contain tokens.
You can also copy sideist.config.example.json.
dotnet run --project src\Sideist.Cli -- sync-oncedotnet run --project src\Sideist.Cli -- list-todoist-projects
dotnet run --project src\Sideist.Cli -- list-google-calendarsUse those IDs in sideist.config.json.
Create a Google OAuth client with application type Desktop app, then put its client ID and client secret in sideist.config.json.
{
"googleClientId": "...apps.googleusercontent.com",
"googleClientSecret": "..."
}Then run:
dotnet run --project src\Sideist.Cli -- google-loginSideist opens a browser, waits on a local loopback callback, and stores the returned access token and refresh token in sideist.config.json.
If Google shows invalid_client, check that:
- The OAuth client type is
Desktop app googleClientIdis the value that ends with.apps.googleusercontent.comgoogleClientSecretis the matching secret from the same OAuth client- The OAuth client was not deleted or copied from another Google Cloud project by mistake
- You copied values without extra spaces, quotes inside quotes, or line breaks
dotnet run --project src\Sideist.Cli -- runStop with Ctrl+C.
dotnet run --project src\Sideist.TrayThe tray app starts syncing immediately and adds a Sideist icon to the Windows notification area.
On first run, Sideist opens an onboarding wizard when required credentials are missing. The wizard guides you through:
- Todoist personal token setup
- Google Calendar API and Desktop app OAuth setup
- Browser-based Google login
- Secure token storage confirmation
Tray menu actions:
SettingsEisenhower MatrixSync nowPause/ResumeOpen project folderStart with WindowsExit
The Settings window lets you edit account tokens, tray icon click behavior, sync interval, default time zone, completion/deletion/conflict policies, project/calendar mappings, and Eisenhower Matrix filters without editing JSON by hand. The account tab includes Todoist token and Google OAuth setup guides, quick links, a Google login button, and reveal buttons for checking secret fields while typing. The JSON file can still be opened from inside the settings window for advanced edits.
The tray app includes an Eisenhower Matrix window based on Todoist priority:
- Todoist P1 -> Do now
- Todoist P2 -> Schedule
- Todoist P3 -> Delegate/reduce
- Todoist P4 -> Hold/remove
Open it from the tray menu, or set the tray icon left-click action to OpenEisenhowerMatrix in Settings. Matrix settings can filter specific Todoist projects, include/exclude tasks with due dates, include/exclude tasks without due dates, and limit the due-date range around today.
Official languages are Korean and English. Change Language in the settings window, or set it in JSON:
{
"language": "ko"
}Use ko for Korean or en for English.
Create a self-contained Windows build:
.\scripts\publish.ps1The output is written to artifacts\Sideist-win-x64.
When settings are saved from the tray settings window, sensitive tokens are stored in Windows Credential Manager under Sideist/* entries and removed from sideist.config.json.
If a secret field is cleared and saved from Settings, the matching Windows Credential Manager entry is removed too.
Never commit sideist.config.json, OAuth tokens, personal API tokens, or published binaries. The repository .gitignore excludes local config, build outputs, and published artifacts.
If a token was pasted into an issue, chat, or commit history, revoke and regenerate it from the provider dashboard.
MIT. See LICENSE.
The tray app writes recent sync messages to:
%LOCALAPPDATA%\Sideist\sync-history.jsonl
You can view and clear this from the Recent Sync tab in Settings.
Do not run Sideist.Cli -- run and Sideist.Tray at the same time for the same config, because both processes would try to sync the same account pair.
To pass a custom config path:
dotnet run --project src\Sideist.Tray -- --config D:\Projects\Sideist\sideist.config.jsonTokens can be supplied through environment variables instead of the config file.
SIDEIST_TODOIST_TOKEN
SIDEIST_GOOGLE_ACCESS_TOKEN
SIDEIST_GOOGLE_CLIENT_ID
SIDEIST_GOOGLE_CLIENT_SECRET
SIDEIST_GOOGLE_REFRESH_TOKEN
For long-running sync, prefer google-login so googleClientId, googleClientSecret, and googleRefreshToken are configured. A raw Google access token is short-lived.
- Todoist task with due date -> Google Calendar event
- Todoist task with due datetime -> timed Google Calendar event
- Todoist task with due date only -> all-day Google Calendar event
- Google Calendar event -> Todoist task in the mapped project
- Removing a Todoist due date removes the linked Google Calendar event
- Todoist completion keeps the linked Google event unchanged by default
- Deleting a Google event deletes the linked Todoist task by default
- Recurring tasks/events are not deeply normalized yet
sideist.config.json supports safer policy controls:
{
"sync": {
"todoistCompletedAction": "keepGoogleEvent",
"todoistCompletedMarkerPrefix": "[Done] ",
"googleEventDeletedAction": "deleteTodoistTask",
"conflictResolutionAction": "preferLatest"
}
}Allowed todoistCompletedAction values:
markGoogleEventDonekeepGoogleEventdeleteGoogleEvent
When todoistCompletedAction is markGoogleEventDone, todoistCompletedMarkerPrefix is added to the Google event title. For a checkmark-style prefix, use \u2713 in JSON.
Allowed googleEventDeletedAction values:
deleteTodoistTaskremoveTodoistDuekeepTodoistTaskcompleteTodoistTask
Allowed conflictResolutionAction values:
preferLatestpreferTodoistpreferGoogleskipAndLog
Conflicts are detected when the same linked Todoist task and Google Calendar event both changed before the next sync cycle. The default keeps the most recently updated side.
- Add Todoist OAuth
- Add conflict review and dry-run preview UI
- Replace JSON state with SQLite when mapping/log volume grows
- Add signed installer
- Add deeper recurring task/event handling