Skip to content

Proposal: extension hooks to built-in commands: /share, /expose, /new, /fork, /copy ... #1020

@justram

Description

@justram

What do you want to change?

Following #984
Provide general hook point before/after a internal command for extension authors
s.t. extensions can initiate some actions before/after a command is sent by the users
Would love to hear suggestions and get some guidance from the team

Why?

Add extensibility around built‑in commands so extensions can add safety, compliance, and workflow automation without forking core code.
Some use cases:

  • Pre‑processing before the data arrive the UI layer of /share or /export: redact PII, show diffs, or require confirmation in the UI.
  • Post‑processing after the event happened /copy, /share, or /export: send audit logs to compliance systems.
  • Alternative share targets after /share in parallel: push to Google Drive, S3, or internal storage instead of Gist.
  • Workflow automation on /new or /fork: run git commit, stash, or branch setup.
  • Clipboard transformations on /copy: remove markdown, normalize whitespace, or extract code blocks.

Just as important, users will be able to compose and order multiple extensions for the same command.
Extension authors can ship independent handlers, while users decide which ones run and in what order.

How? (optional)

Today we already have a small set of ad‑hoc events (session_before_switch, session_before_fork, etc.).
There could be more and more internal commands added/removed during the development.
Extending this pattern requires a new event for every command and still would not allow chained transformations across multiple extensions.

Some ideas implemented in #984

Introduce a unified command pipeline API that:

  1. Registers handlers by command name (not by bespoke event types).
  2. Passes typed command data through a chain of “before” handlers.
  3. Allows cancellation of the built‑in command ({ cancel: true }).
  4. Runs “after” handlers in parallel to observe results without mutating them.
  5. Exposes handler metadata (id/label/transforms) for ordering, disablement, and UI introspection.

Example:

  // Before phase: transform data, cancel, or redirect
  pi.beforeCommand("export", { id: "my-handler", transforms: ["entries"] }, async (data) => {
    return { data: { entries: transform(data.entries) } };
  });

  // After phase: observe results (notification only)
  pi.afterCommand("export", { id: "my-observer" }, async (data) => {
    await auditLog(data.result);
  });

Each handler declares metadata for introspection and conflict detection:

  interface CommandHandlerOptions<K extends keyof CommandDataMap> {
    id: string;
    label?: string;
    transforms?: Array<keyof CommandDataMap[K]>;
  }

Execution model:

  • Before handlers run sequentially and receive the previous handler’s output.
  • After handlers run in parallel and only observe the result.
  • If any before handler cancels, the built‑in command and all after handlers are skipped.

Chained Execution for "Before" extensions

Initial data
    │
    ▼
extension A (transforms entries)
    │
    ▼
extension B (receives A's output, transforms further)
    │
    ▼
extension C (can cancel or pass through)
    │
    ▼
Built-in command (receives final transformed data)

If any before handler returns { cancel: true }, the chain stops, the built-in command is skipped, and no after handlers run.

Parallel Execution for "After" Handlers

After handlers can execute in parallel since they don't modify data:

Built-in command completes
    │
    ├──► After extension A (audit log)
    ├──► After extension B (notification)
    └──► After extension C (analytics)

Ordering configuration still lists after handlers for display and enable/disable, but it does not change their parallel execution. After handlers do not run when a before handler cancels the command.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions