Skip to content

feat(plugin): add lifecycle hooks API (onStartup, onBeforeExecute, onAfterExecute)#376

Merged
jackwener merged 3 commits intomainfrom
feat/plugin-lifecycle-hooks
Mar 24, 2026
Merged

feat(plugin): add lifecycle hooks API (onStartup, onBeforeExecute, onAfterExecute)#376
jackwener merged 3 commits intomainfrom
feat/plugin-lifecycle-hooks

Conversation

@ByteYue
Copy link
Collaborator

@ByteYue ByteYue commented Mar 24, 2026

Summary

Add a lifecycle hooks system that allows plugins to tap into opencli's execution lifecycle without modifying core code. This is a key step toward evolving the plugin system from "command extensions" to a full "plugin ecosystem platform."

Changes

New files

  • src/hooks.ts — Core hooks module with globalThis.__opencli_hooks__ singleton (consistent with registry pattern), 3 hook registration functions, and safe emitHook() that catches individual handler errors
  • src/hooks.test.ts — 10 unit tests covering registration, ordered execution, async support, error isolation, and globalThis singleton sharing

Modified files

  • src/execution.ts — Emit onBeforeExecute / onAfterExecute around command execution in executeCommand(). Refactored to capture result from both browser and non-browser paths.
  • src/main.ts — Emit onStartup after discoverPlugins(), before CLI execution begins
  • src/registry-api.ts — Export hooks API (onStartup, onBeforeExecute, onAfterExecute) via @jackwener/opencli/registry

Hook Types

Hook When Use case
onStartup After all commands/plugins discovered Cookie warmup, env checks
onBeforeExecute Before every command runs Timing, caching, logging
onAfterExecute After every command completes (with result) Audit logs, analytics, error reporting

Design Decisions

  • globalThis singleton: Same pattern as __opencli_registry__ — guarantees plugin hooks work across npm link / symlink boundaries
  • Error isolation: Each hook handler is wrapped in try/catch — a failing hook never blocks command execution
  • Async-safe: All hooks are properly awaited
  • Zero breaking changes: Existing execution flow and all 233 tests pass unchanged

Example Plugins

4 example plugins demonstrate the hooks API in action:

Plugin Description Hooks Used
audit-log Auto-record all command executions to ~/.opencli/audit.jsonl onBeforeExecute + onAfterExecute
data-diff Track changes between command runs — see what's new since last time onAfterExecute
slack-notify Push alerts to Slack/Telegram when results match keywords onAfterExecute
usage-analytics Track command frequency, duration, and performance stats onAfterExecute

Install any of them to try:

opencli plugin install github:ByteYue/opencli-plugin-audit-log
opencli plugin install github:ByteYue/opencli-plugin-data-diff
opencli plugin install github:ByteYue/opencli-plugin-slack-notify
opencli plugin install github:ByteYue/opencli-plugin-usage-analytics

Test Results

✓  unit  18 test files passed
   Tests  233 passed (233)

@ByteYue ByteYue requested a review from jackwener March 24, 2026 15:49
ByteYue and others added 3 commits March 25, 2026 00:03
…AfterExecute)

Introduce a hooks system that allows plugins to tap into opencli's
execution lifecycle without modifying core code.

New files:
- src/hooks.ts: hook registration, emission, and globalThis singleton
- src/hooks.test.ts: 10 unit tests covering registration, ordering,
  error isolation, async support, and globalThis sharing

Modified files:
- src/execution.ts: emit onBeforeExecute/onAfterExecute around command execution
- src/main.ts: emit onStartup after discoverPlugins()
- src/registry-api.ts: export hooks API for plugin consumption

Example plugin: https://github.com/ByteYue/opencli-plugin-audit-log
The isCliModule() check only matched files containing 'cli(' calls,
silently skipping hook-only files like audit-hooks.ts that register
onBeforeExecute/onAfterExecute without any cli() command registration.

Renamed CLI_MODULE_PATTERN → PLUGIN_MODULE_PATTERN and extended the
regex to also match onStartup(, onBeforeExecute(, onAfterExecute(.
@jackwener jackwener force-pushed the feat/plugin-lifecycle-hooks branch from cf2ce99 to 2bb3fc6 Compare March 24, 2026 16:05
Copy link
Owner

@jackwener jackwener left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:方向对,而且我已经在当前分支补了两个关键行为修正;现在这版可以合。

深度 review 结论:

  • hooks.ts 这层抽象本身是成立的:global singleton、ordered execution、error isolation、plugin-facing API 都合理
  • discovery.ts 补 hook-only plugin file 的加载条件也是必要修复,否则只注册 hooks 的插件会被静默跳过
  • P1(已修):原实现里 onAfterExecute 只会在命令成功时触发;命令失败时,plugin 无法做 audit / metrics / error reporting
  • P1(已修):onStartup 原来放在 completion fast-path 之前,意味着 shell completion 这种高频路径也会触发 plugin startup side effects,不合适

我直接推到分支上的 follow-up:

  • 2bb3fc6fix(plugin): tighten lifecycle hook semantics

具体改动:

  • executeCommand() 现在在失败路径也会触发 onAfterExecute,并在 HookContext 里带上 error / finishedAt
  • onStartup 移到 --get-completions fast-path 之后,避免 completion 时误触发 plugin startup hooks
  • 补了回归测试,覆盖 failed execution 也会触发 onAfterExecute

我认为这也是更好的方案:

  • 保持现有三种 hook API 不变,不额外引入 onError 之类新 surface area
  • 但把 after-hook 的语义收紧成“命令结束后都会触发”,这样插件作者更容易依赖

本地验证已过:

  • npx vitest run src/hooks.test.ts src/engine.test.ts
  • npm run typecheck
  • npm test
  • npm run build

@jackwener jackwener merged commit 1512016 into main Mar 24, 2026
11 checks passed
@jackwener jackwener deleted the feat/plugin-lifecycle-hooks branch March 24, 2026 16:10
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.

2 participants