Skip to content

Community plugins for task events#626

Open
bborn wants to merge 4 commits into
mainfrom
task/4574-add-llmtrim-extension-to-taskyou-project
Open

Community plugins for task events#626
bborn wants to merge 4 commits into
mainfrom
task/4574-add-llmtrim-extension-to-taskyou-project

Conversation

@bborn

@bborn bborn commented Jun 30, 2026

Copy link
Copy Markdown
Owner

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 about task.done collide on the same file — no way to install several side by side.

Plugins

A plugin is a directory under ~/.config/task/plugins/ with a plugin.yaml manifest:

name: desktop-notify
version: 0.1.0
description: Native desktop notifications when a task finishes or needs you.
hooks:                      # fire automatically on events
  task.done: notify.sh
  task.blocked: notify.sh
actions:                    # user-invoked, on demand
  - id: test
    label: Send a test notification
    command: test-notify.sh

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.

cp -R examples/plugins/desktop-notify ~/.config/task/plugins/
ty plugins list
ty plugins run desktop-notify test 42     # run an action against task #42

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 in ty 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.
  • Hooks — on each event the Runner fans out to the legacy hook plus every matching plugin, concurrently in the background. Plugin hooks get TASK_PLUGIN_NAME / TASK_PLUGIN_DIR and run with cwd = the plugin dir. (Also moved the 30s timeout into the goroutine so a hook isn't cancelled the instant Run() returns.)
  • Actionsinternal/hooks/actions.go: RunAction runs an action synchronously (60s) with the task+plugin env; FindAction resolves plugin/action. Exposed via ty plugins run <plugin> <action> [task-id], the detail-view picker (A), and the command palette (> prefix) — all on the one runner.
  • ty plugins list / ty plugins dir to 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)

  • Action UI surfaces ✅ — reachable three ways, all on the shared RunAction runner: ty plugins run, the detail-view picker (press A), and the command palette (> prefix).
  • Plugin panes — turning the detail view's aux/shell slot into a tabbed container so a plugin (or the user) can add a command pane without touching the executor's "one per task" model. This is a focused refactor of detail.go and 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 in docs/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 + RunAction env injection (with and without a task) + FindAction.
  • go build ./..., go vet, gofmt clean.
  • golangci-lint run (v2.8.0, matching CI) — 0 issues.
  • Verified ty plugins list / ty plugins run against an installed copy of the example plugin.

🤖 Generated with Claude Code

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>
@bborn bborn force-pushed the task/4574-add-llmtrim-extension-to-taskyou-project branch from 8389ed4 to 046ce06 Compare June 30, 2026 16:13
@bborn bborn changed the title Add ty-llmtrim extension + plugin-architecture discussion Community plugins for task events Jul 1, 2026
bborn and others added 3 commits July 1, 2026 07:38
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>
Press A in the detail view to open a modal list of the current task's plugin
actions; selecting one runs it (with the task's env) off the UI loop and shows
the result in the notification banner.

- internal/ui/action_picker.go: ActionPickerModel, a self-contained sub-model in
  the command-palette style (own View + result accessors), so it avoids the huh
  form-router path entirely. gatherPluginActions() flattens installed plugins.
- internal/ui/app.go: ViewActionPicker + Actions key (A); routes the picker like
  the command palette; runs the action via runPluginActionCmd; actionFinishedMsg
  is exempted from the view routers (isSystemMsg) so its result reaches the
  banner. When no plugins have actions, shows a hint instead of an empty modal.

Next surface: command-palette entries on the same runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Type a leading ">" in the Cmd+P palette to switch from task search to plugin
action search; Enter runs the selected action with the current task's context
and reports the result in the notification banner. Task-switching is unchanged
when the query has no ">" prefix (strictly additive).

- internal/ui/command_palette.go: action mode (filter/filterActions/activeLen),
  SelectedAction accessor, action-list rendering, "> for plugin actions" hint.
- internal/ui/app.go: updateCommandPalette runs the chosen action via the shared
  runPluginActionCmd.
- docs/plugins.md: document both TUI action surfaces.

Completes the three action surfaces (CLI, detail-view picker, command palette).
Next: the pane-tabs refactor (separate PR).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant