Community plugins for task events#626
Open
bborn wants to merge 2 commits into
Open
Conversation
Today task event hooks are one script per event in a shared dir, so two integrations that both want e.g. task.done collide on the same file. This adds plugins: a plugin is a self-contained directory under ~/.config/task/plugins/ with a plugin.yaml manifest declaring which events it handles. Drop it in and it's active — any number of plugins can handle the same event and all run. - internal/hooks/plugins.go: manifest schema + forgiving discovery/validation (a bad community plugin is skipped, not fatal). - internal/hooks: Runner fans out each event to the legacy single-script hook plus every matching plugin, in the background. Plugin hooks get TASK_PLUGIN_NAME/TASK_PLUGIN_DIR and run with cwd set to the plugin dir. - Move the 30s hook timeout into the background goroutine so the hook isn't cancelled the instant Run() returns. - cmd/task: `ty plugins list` / `ty plugins dir` to inspect what's installed. - examples/plugins/desktop-notify: a complete, copy-pasteable plugin. - docs/plugins.md + README: manifest format and authoring guide. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8389ed4 to
046ce06
Compare
Extends the plugin manifest with `actions`: user-triggered commands (vs. hooks, which fire automatically on events). Each action has an id, optional label, and a command script in the plugin dir. Plugins may now be actions-only. - internal/hooks: Action type + validation (drop malformed/missing, keep rest); RunAction runs synchronously with task+plugin env, 60s timeout; FindAction. - cmd/task: `ty plugins run <plugin> <action> [task-id]`; `ty plugins list` now shows actions with their run command. - examples/plugins/desktop-notify: a `test` action. - docs/plugins.md: actions section. This is the shared runner for all three action surfaces; the CLI ships here, the detail-view picker and command-palette entries build on it next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Makes TaskYou's event-hook system pluggable: community integrations install as self-contained, droppable directories instead of hand-placed scripts. Two capabilities so far — reactive hooks and user-invoked actions.
The gap
Task event hooks today are one script per event in a shared dir (
~/.config/task/hooks/task.done). Two integrations that both care abouttask.donecollide on the same file — no way to install several side by side.Plugins
A plugin is a directory under
~/.config/task/plugins/with aplugin.yamlmanifest:Drop the directory in and it's live. Any number of plugins can handle the same event, and all of them run — alongside the legacy single-script hook, which still works.
How it works
internal/hooks/plugins.go— manifest schema + forgiving discovery/validation. A malformed manifest, missing script, nameless plugin, or bad action entry is skipped (surfaced inty plugins list/ the daemon log), so one bad community plugin can't break a user's pipeline. A plugin needs ≥1 usable hook or action.TASK_PLUGIN_NAME/TASK_PLUGIN_DIRand run with cwd = the plugin dir. (Also moved the 30s timeout into the goroutine so a hook isn't cancelled the instantRun()returns.)internal/hooks/actions.go:RunActionruns an action synchronously (60s) with the task+plugin env;FindActionresolves plugin/action. Exposed viaty plugins run <plugin> <action> [task-id]. This is the shared runner all action surfaces use.ty plugins list/ty plugins dirto inspect what's installed.examples/plugins/desktop-notify/— a complete plugin (hooks + an action).docs/plugins.md+ README — manifest format and authoring guide.Built entirely on the existing hooks pipeline; no changes to the executor launch path.
Roadmap (discussed, not in this PR)
RunActionrunner above.detail.goand will land as its own PR.Scope note
Plugins fire for the events the Runner dispatches today (
task.started,task.done,task.blocked,task.failed,task.auth_required), documented indocs/plugins.md.Test plan
go test ./internal/hooks/— passing. Covers hook discovery (valid/multiple/sorted/missing-dir/invalid-skipped/partial-drop), an end-to-end fan-out test (legacy hook + two plugins fire on one event, plugin env asserted), and action validation +RunActionenv injection (with and without a task) +FindAction.go build ./...,go vet,gofmtclean.golangci-lint run(v2.8.0, matching CI) — 0 issues.ty plugins list/ty plugins runagainst an installed copy of the example plugin.🤖 Generated with Claude Code