Source Script Protocol¶
This document defines the contract between gtdhelper and external source scripts.
Overview¶
Source scripts are executable files in ~/.gtdhelper/sources/ that produce task data. gtdhelper discovers and runs them on each refresh cycle, parsing their stdout as JSON Lines.
Discovery¶
Any executable file in ~/.gtdhelper/sources/ is treated as a source script. The source name is derived from the filename stem (e.g. github.py -> github, jira.sh -> jira, email-checker -> email-checker).
Subdirectories and non-executable files are ignored.
Execution Mode¶
One-off: gtdhelper spawns the script, reads stdout until EOF, then collects the exit code. The script runs once per refresh cycle (every 5 minutes, or on manual refresh).
Scripts have a 30-second timeout. If a script doesn't exit within 30 seconds, it is killed (SIGKILL) and treated as a failure.
Output Format¶
Scripts write JSON Lines to stdout — one JSON object per line:
{"id":"github:org/repo/#42","title":"Fix login flow","reference":"#42","project":"org/repo","url":"https://github.com/org/repo/pull/42","type":"pull_request","created_at":"2025-01-15T10:00:00Z","updated_at":"2025-02-20T14:30:00Z"}
Required Fields¶
| Field | Type | Description |
|---|---|---|
id |
string | Globally unique task identifier. Must be stable across runs for the same logical task (e.g. github:org/repo/#42, trello:abc123). |
title |
string | Human-readable task title displayed in the overlay. |
reference |
string | Short reference shown alongside the title (e.g. #42, JIRA-123). |
project |
string | Project or group name used for grouping in the overlay (e.g. repository name, board name). |
url |
string | URL opened in the default browser when the user clicks the task. |
created_at |
string | ISO 8601 timestamp of when the task was created. |
updated_at |
string | ISO 8601 timestamp of the last activity on the task. Used for sorting. |
Optional Fields¶
| Field | Type | Description |
|---|---|---|
type |
string | Task type for categorization (e.g. pull_request, issue, email) |
is_draft |
boolean | Whether the task is a draft (e.g. draft PR). Affects icon styling. |
is_bot |
boolean | Whether the task author is a bot. Used for filtering. |
Notes¶
- The
sourcefield is set by gtdhelper to the script's filename stem. Anysourcevalue in the JSON is ignored. - Lines that fail JSON parsing or lack required fields are silently skipped (with a warning in gtdhelper logs).
- Empty lines are skipped.
- The
idmust be stable across runs for the same logical task, so gtdhelper can track snooze/archive state.
stdio Contract¶
| Stream | Behavior |
|---|---|
| stdin | Piped from parent but unused. Reserved for future commands (see long-running mode). |
| stdout | Captured by gtdhelper. Write JSON Lines here. |
| stderr | Inherited from parent process. Use for logging/diagnostics. |
Signals¶
| Signal | Behavior |
|---|---|
| SIGINT | Graceful shutdown. Sent on app exit. Scripts should clean up and exit promptly. |
Error Handling¶
- Non-zero exit code: Script is removed from the active pool (disabled). Its tasks from the previous run remain in the task list until the next successful run.
- Reintegration: If a disabled script's file is modified on disk (mtime change), gtdhelper retries it on the next refresh cycle.
- Timeout: Treated the same as a non-zero exit (disabled, can be reintegrated).
Two-tier Script Discovery¶
gtdhelper combines scripts from two locations:
- User scripts (
~/.gtdhelper/sources/): Always included. Any executable file here runs on every refresh. - Bundled scripts (app resource
scripts/sources/): Only activated when a matching config file<stem>.tomlexists in~/.gtdhelper/. For example, the bundledgithub.pyruns only if~/.gtdhelper/github.tomlexists.
If a user script has the same stem as a bundled script (e.g. github.sh vs github.py), the user script takes priority and the bundled one is skipped.
No automatic bootstrap — the user must create the config file to activate a bundled script.
Bundled Script Runtime Environment¶
Bundled Python (.py) scripts run inside an auto-managed virtualenv. The venv is transparent to the script — gtdhelper handles creation and dependency installation automatically.
- Venv location:
~/.gtdhelper/env/ - Creation:
uv venv --python >=3.12on first refresh (requiresuv) - Dependencies: Synced from
scripts/requirements.lockviauv pip sync. This lock file is generated fromscripts/requirements.txt(the human-edited input) usinguv pip compile --generate-hashesand contains pinned versions with SHA256 hashes. Only re-synced when the lock file's SHA256 hash changes. - Interpreter injection: Bundled
.pyscripts are executed as<venv>/bin/python <script>instead of running the script directly. - User scripts are NOT affected: Scripts in
~/.gtdhelper/sources/always run directly (as executable files). They manage their own interpreters and dependencies. - Graceful degradation: If
uvis not installed, bundled Python scripts run without a venv. They will likely fail on import if they depend on packages fromrequirements.lock.
Bundled Script: github.py¶
The default github.py script ships with gtdhelper at scripts/sources/github.py.
Configuration¶
github.py reads its repo list from ~/.gtdhelper/github.toml:
[[repos]]
name = "org/repo"
types = ["pull_request", "issue"]
[[repos]]
name = "other/repo"
types = ["pull_request"]
name: GitHub repository inowner/repoformattypes: list of"pull_request"and/or"issue"- If
typesis omitted, defaults to["pull_request"]
Bundled Script: trello.py¶
The trello.py script ships with gtdhelper at scripts/sources/trello.py.
Configuration¶
trello.py reads credentials from ~/.gtdhelper/trello.toml:
api_key = "your-api-key"
token = "your-token"
member = "your-trello-username"
boards = ["Product Board", "Engineering"]
api_key: Trello API key (get one at https://trello.com/power-ups/admin)token: Trello API token (generated alongside the API key)member: Trello username or member ID — cards assigned to this member are fetchedboards: list of board names to include (exact match, case-sensitive). If omitted or empty, no cards are shown — you must opt in per board.
Output¶
Each Trello card produces a task with:
id:trello:<card-id>title: card namereference:#<card-short-id>project: board name (fetched and cached per unique board)url: card short URLtype:cardcreated_at: derived from the card's ObjectId (embedded creation timestamp)updated_at:dateLastActivityfrom the Trello API