Sync Slack Lists into Things3 with two-way completion sync
Project description
Slack List -> Things3 Sync
slingsync pulls Slack List items and creates Things3 to-dos.
What It Does
- Reads Slack List items using
slackLists.items.list. - Creates Things3 to-dos via AppleScript.
- Avoids duplicates using a persisted state file.
- By default, deletes previously synced Things3 to-dos if the Slack item is gone or marked completed (
--delete-missingenabled by default). - By default, runs two-way completion sync: marks Slack items completed when mapped Things to-dos are completed (
--two-way-syncenabled by default).
Requirements
- macOS with Things3 installed.
- Python 3.9+.
uvinstalled (docs).- Slack token with
lists:read. - For two-way completion sync, token also needs
lists:write.
Setup (uv-managed venv)
cd /path/to/things3_syncer
uv venv
uv sync
Run commands either with activation:
source .venv/bin/activate
slingsync --help
Or directly with uv run:
uv run slingsync --help
Compatibility wrapper (same behavior):
python syncer.py --help
Slack Setup
- Create app: api.slack.com/apps
- Add scope
lists:read - Install/reinstall app to workspace
- Copy token (
xoxb-...or user token)
IDs You Need
How to find List ID and Team ID
- Open the Slack List in your browser.
- Copy the URL. It looks like:
https://XXX.slack.com/lists/TXXX/FXXX
- Extract:
- Team ID: the
T...segment (workspace/team) - List ID: the
F...segment (the list itself)
- Team ID: the
Use in config:
- Required: set
list_idin your config JSON. - Recommended: set
team_idin your config JSON.
Config File (Global Recommended)
Config lookup order:
--config /path/to/config.json$SLINGSYNC_CONFIG./.sync_config.json(current working directory)~/.config/slingsync/config.json
Global config (recommended)
Create:
mkdir -p ~/.config/slingsync
cp .sync_config.example.json ~/.config/slingsync/config.json
Then edit ~/.config/slingsync/config.json.
Local config (project-specific)
Store your core Slack values in a local file:
./.sync_config.json(auto-loaded by the script)- Ignored by git (
.gitignoreincludes it)
Create it from the example:
cp .sync_config.example.json .sync_config.json
Then edit:
{
"slack_token": "xoxb-XXX",
"list_id": "FXXX",
"team_id": "TXXX",
"assigned_user_id": "UXXX"
}
Look up your Slack member ID (for assigned_user_id):
curl -sS -G https://slack.com/api/users.lookupByEmail \
-H "Authorization: Bearer xoxb-XXX" \
--data-urlencode "email=you@XXX.com"
Read user.id from the response and put it in your config file.
If you get missing_scope, add users:read.email (and usually users:read) then reinstall the app.
assigned_user_id is used when you pass --filter-assigned-user-id.
Common Runs
Minimal run
uv run slingsync \
--dry-run
Dry run + open items + configured assignee filter + deadline mapping
uv run slingsync \
--filter-completed false \
--filter-assigned-user-id \
--deadline-column "todo_due_date" \
--dry-run
Default behavior includes deletion reconcile (preview first)
uv run slingsync \
--filter-completed false \
--filter-assigned-user-id \
--dry-run
Remove --dry-run to apply.
Disable Two-way completion sync
uv run slingsync \
--no-two-way-sync \
--dry-run
Disable deletion reconcile
uv run slingsync \
--no-delete-missing
Argument Reference
Slack
--config: explicit config JSON path (highest priority).$SLINGSYNC_CONFIG: config JSON path env override.- Default lookup order:
./.sync_config.json, then~/.config/slingsync/config.json. - Config values:
slack_token,list_id, optionalteam_id, optionalassigned_user_id. --include-archived: Include archived Slack items.--lookup-user-id: Lookup Slack member ID by email and exit.--lookup-email: Email for--lookup-user-id.
Field Mapping And Filters
--title-column: Column key/id used as Things title.--notes-column: Column key/id used as Things notes.--deadline-column: Column key/id used for Things deadline.--completed-column: Column key/id for completion state.--assigned-column: Column key/id for assignee.--status-column: Column key/id for status.--filter-completed {true|false|open|completed|...}: include only matching completion state.--filter-assigned-user: match assignee by name substring (case-insensitive).--filter-assigned-user-id/--no-filter-assigned-user-id: enable/disable assignee filter by configured Slack member ID. If enabled and noassigned_user_idis set in the resolved config file, the script exits with an error.--filter-status: include only status value.--exclude-status: exclude status value.--debug-filter-fields: print parsed status/assignee/completed fields per item.
Things Target
--target-list: Things list destination (defaultInbox).--target-project: Destination project (mutually exclusive with area).--target-area: Destination area (mutually exclusive with project).
Execution
--dry-run: Preview only, no writes.--state-file: Custom state file path.--max-items: Process only first N fetched items.--two-way-sync/--no-two-way-sync: enable/disable pushing Things completion back to Slack (default: enabled).--delete-missing/--no-delete-missing: enable/disable deletion reconcile (default: enabled).- Reconcile deletes when a previously synced Slack item is either missing from the list or marked completed.
Safety rule:
--delete-missing(default on) cannot be used with--max-items.- If you need
--max-items, add--no-delete-missing.
State File
Default:
- If config is local (
./.sync_config.json):./.sync_state/slack_to_things_<LIST_ID>.json - Otherwise:
~/.config/slingsync/state/slack_to_things_<LIST_ID>.json
Use --state-file to override.
Quick API check:
curl -sS -X POST https://slack.com/api/slackLists.items.list \
-H "Authorization: Bearer xoxb-XXX" \
-H "Content-Type: application/json; charset=utf-8" \
-d '{"list_id":"FXXX","team_id":"TXXX","limit":1}'
Automation Permission
First run may prompt macOS automation permission to control Things3. If denied accidentally, reset and retry:
tccutil reset AppleEvents com.apple.Terminal
(or reset iTerm2 bundle id if you use iTerm).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file slingsync-0.1.0.tar.gz.
File metadata
- Download URL: slingsync-0.1.0.tar.gz
- Upload date:
- Size: 15.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c13e43779133d14edc7a8400a13583d8231a8cc42193c80b836ef6354301294
|
|
| MD5 |
200cb3cb29f230c6c7cc833e06b0b47f
|
|
| BLAKE2b-256 |
6dd2aca07cc40a4747d185145001650ba04aab27d9f9087c87fa685bd8bc95b6
|
File details
Details for the file slingsync-0.1.0-py3-none-any.whl.
File metadata
- Download URL: slingsync-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2dc20339ad30f8a2d5373f4814769f453277b7cec275a7f9297155c43497e08
|
|
| MD5 |
191243f9d0abef3ab319546d6887a02b
|
|
| BLAKE2b-256 |
735e370467d0e93d57bb35a6b0c06f232d5bbb454f564d5f4788f1a0cd28d904
|