Skip to content

WIP: Add gh-style plugin system (skeleton + dispatch + local install)#1116

Closed
ashtom wants to merge 1 commit intomainfrom
ashtom/plugin-exploration
Closed

WIP: Add gh-style plugin system (skeleton + dispatch + local install)#1116
ashtom wants to merge 1 commit intomainfrom
ashtom/plugin-exploration

Conversation

@ashtom
Copy link
Copy Markdown
Member

@ashtom ashtom commented May 5, 2026

https://entire.io/gh/entireio/cli/trails/298

Summary

  • New cmd/entire/cli/plugin package with manager/classification (binary/script/local), YAML manifest, dispatcher (Unix direct exec, Windows sh.exe for scripts), local symlink install, and remove.
  • entire plugin {install,list,remove,exec} subcommands wired into root.
  • main.go now calls plugin.Dispatch before the unknown-command suggestion path; cobra resolves built-ins first, so plugins can never shadow built-ins. plugin exec is the escape hatch.

Foundational milestones from docs/plugin-system-plan.md. Network-dependent paths (GitHub release binary install, git-clone script install, upgrade, create scaffolds, search/browse/pin, update notifier, docs page) are deferred.

Test plan

  • mise run lint passes (0 issues)
  • mise run test passes
  • mise run test:integration passes
  • mise run test:e2e:canary passes
  • Manual end-to-end: install → list → dispatch → exec
  • Built-in shadowing: entire status resolves to built-in even when entire-status plugin is installed; entire plugin exec status invokes the plugin
  • Conflict check: entire plugin install refuses a directory whose name matches a built-in
  • Exit code propagation: child exits 42 → parent exits 42

🤖 Generated with Claude Code


Note

Medium Risk
Adds a new plugin execution path for unknown commands and introduces filesystem-based install/remove/list behavior, which can affect CLI command resolution and process exit codes. Main risk is dispatch/exec edge cases across platforms (Windows sh.exe routing) and potential regressions in unknown-command handling.

Overview
Adds an initial gh-style plugin system: a new plugin package can discover installed plugins (binary/script/local), parse/write manifest.yml, track pins via .pin-*, and execute plugins with stdio inheritance, ENTIRE_PLUGIN_DATA_DIR injection, and exit-code propagation (including Windows sh.exe routing for script plugins).

Wires new entire plugin {install,list,remove,exec} commands into the CLI and updates main.go to attempt plugin.Dispatch on unknown commands/flags before showing suggestions, while ensuring built-in Cobra commands/aliases always take precedence. Includes unit tests covering classification, local install via symlink, remove semantics, dispatch precedence, argv handling, env injection, and exit-code propagation, plus adds gopkg.in/yaml.v3 dependency.

Reviewed by Cursor Bugbot for commit 7376f07. Configure here.

Implements the foundational milestones from docs/plugin-system-plan.md.
External executables named entire-<name> are discovered under
$ENTIRE_PLUGIN_DIR (or XDG_DATA_HOME/entire/plugins) and dispatched to
when an unknown subcommand is invoked. Cobra resolves built-ins first;
'entire plugin exec <name>' is the escape hatch for collisions.

Local install (symlinking a dev directory), list, remove, and exec are
wired up. GitHub-release binary install, git-clone script install,
upgrade, create scaffolds, search/browse/pin, and the update notifier
remain deferred per the plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Entire-Checkpoint: eb3926f7bf64
Copilot AI review requested due to automatic review settings May 5, 2026 09:49
@ashtom ashtom changed the title Add gh-style plugin system (skeleton + dispatch + local install) WIP: Add gh-style plugin system (skeleton + dispatch + local install) May 5, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a foundational, gh-style plugin system for the Entire CLI: plugins are discovered from a user plugin directory and can be dispatched to when the user invokes an unknown subcommand, plus a new entire plugin command group for basic lifecycle operations.

Changes:

  • Add a new cmd/entire/cli/plugin package implementing plugin discovery/classification, local install (symlink), removal, manifest support, and cross-platform dispatch (including sh.exe routing for Windows script plugins).
  • Wire entire plugin {install,list,remove,exec} into the Cobra root, and hook plugin dispatch into main.go’s unknown-command path.
  • Add initial unit tests covering manager classification, local install/remove, and dispatch behavior (including exit-code propagation in the implicit-dispatch path).

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
go.mod Add YAML dependency for binary plugin manifests.
docs/plugin-system-plan.md Document intended architecture/milestones for the plugin system.
cmd/entire/main.go Attempt plugin dispatch before unknown-command/flag suggestion handling.
cmd/entire/cli/root.go Register new top-level plugin command group.
cmd/entire/cli/plugin_group.go Implement entire plugin install/list/remove/exec commands.
cmd/entire/cli/plugin/manager.go Implement plugin root resolution, discovery, classification, and name validation.
cmd/entire/cli/plugin/manifest.go Define/read/write YAML manifest for binary plugins.
cmd/entire/cli/plugin/dispatch.go Implement dispatch resolution and execution, env injection, and exit-code extraction.
cmd/entire/cli/plugin/dispatch_unix.go Unix exec implementation (direct).
cmd/entire/cli/plugin/dispatch_windows.go Windows exec implementation (script routing via sh.exe).
cmd/entire/cli/plugin/install_local.go Implement local install via symlink with built-in conflict checks.
cmd/entire/cli/plugin/remove.go Implement plugin removal semantics (unlink local vs remove dir).
cmd/entire/cli/plugin/pin.go Implement pin marker detection helper.
cmd/entire/cli/plugin/manager_test.go Add tests for name validation, default root env override, and classification.
cmd/entire/cli/plugin/install_test.go Add tests for local install behavior and remove semantics.
cmd/entire/cli/plugin/dispatch_test.go Add tests for dispatch fallthrough, precedence, env injection, and exit-code propagation.

Comment thread cmd/entire/main.go
Comment on lines 43 to +45
case strings.Contains(err.Error(), "unknown command") || strings.Contains(err.Error(), "unknown flag"):
handled, pluginErr := plugin.Dispatch(ctx, rootCmd, os.Args[1:])
if handled {
Comment on lines +175 to +183
if err := plugin.Exec(cmd.Context(), p, rest, mgr.Root); err != nil {
code := plugin.PropagateExitCode(err)
if code > 0 {
// Plugin returned a non-zero exit code. Surface it via a
// SilentError so main.go preserves the user's intent
// without printing extra noise.
return NewSilentError(errors.New(p.FullName() + " exited with non-zero status"))
}
return fmt.Errorf("exec plugin: %w", err)
Comment on lines +123 to +128
continue
}
p, err := m.classify(name)
if err != nil || p == nil {
continue
}
Comment on lines +12 to +16
func readPinSHA(dir string) string {
entries, err := os.ReadDir(dir)
if err != nil {
return ""
}
Comment on lines +137 to +139
func pluginDataDir(root, name string) string {
return root + string(os.PathSeparator) + Prefix + name + string(os.PathSeparator) + "data"
}
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 7376f07. Configure here.

// without printing extra noise.
return NewSilentError(errors.New(p.FullName() + " exited with non-zero status"))
}
return fmt.Errorf("exec plugin: %w", err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

plugin exec loses child's non-zero exit code

High Severity

When entire plugin exec runs a plugin that exits non-zero (e.g., 42), PropagateExitCode correctly extracts the code, but NewSilentError(errors.New(...)) wraps a new error that discards the original *exec.ExitError. In main.go, the SilentError case simply skips printing and falls through to the unconditional os.Exit(1), so the child's actual exit code is always replaced with 1. The dispatch path in main.go correctly calls os.Exit(code), but the entire plugin exec cobra subcommand path does not — contradicting the stated "child exits 42 → parent exits 42" guarantee.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7376f07. Configure here.

@ashtom
Copy link
Copy Markdown
Member Author

ashtom commented May 5, 2026

Closing in favor of #1104, which is the right starting point. The kubectl-style PATH dispatcher there is lower-friction, ships now, and migrates cleanly to a managed-install layer later — exactly what the plan doc anticipated.

I'll repurpose this branch as a follow-up that builds additively on top of #1104, cherry-picking only the parts that are missing there:

  • ENTIRE_PLUGIN_DATA_DIR env var so plugins have a sanctioned per-plugin data dir.
  • A managed-install directory (~/.local/share/entire/plugins/bin) that's auto-prepended to PATH, plus entire plugin install/list/remove for managed installs. The kubectl dispatcher discovers managed installs the same way as PATH binaries, so this is purely additive.

Deferring (per discussion):

  • entire plugin exec escape hatch — no real demand yet.
  • Stricter name validation — Soph's path-traversal + agent-prefix checks are sufficient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants