Skip to content

feat(plugins): add CLI enable/disable for plugin.json#693

Open
Ayush7614 wants to merge 3 commits into
Gitlawb:mainfrom
Ayush7614:feat/plugins-enable-disable
Open

feat(plugins): add CLI enable/disable for plugin.json#693
Ayush7614 wants to merge 3 commits into
Gitlawb:mainfrom
Ayush7614:feat/plugins-enable-disable

Conversation

@Ayush7614

@Ayush7614 Ayush7614 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add zero plugins enable|disable <id> to toggle the enabled field in plugin.json (with --user / --json)
  • Add plugins.SetEnabled / SetEnabledByID helpers used by the CLI
  • Update docs/EXTENDING.md so the documented gap matches the new commands

Test plan

  • go test ./internal/plugins/ ./internal/cli/ -count=1
  • go vet ./internal/plugins/ ./internal/cli/
  • golangci-lint (unused/ineffassign/staticcheck) on ./internal/plugins/ — 0 issues
  • govulncheck ./... — no vulnerabilities
  • Manual: install a local plugin, zero plugins disable <id>, confirm list shows disabled and tools are not activated; then enable again

Summary by CodeRabbit

  • New Features
    • Added zero plugins enable <id> and zero plugins disable <id> commands.
    • Supports targeting user-installed plugins with --user.
    • Provides JSON output and clearer “already/now” status messages for enable/disable actions.
    • Enables/disables plugins by toggling their enabled flag (without removing the plugin).
  • Bug Fixes
    • Preserves existing plugin.json file permissions when toggling enablement.
  • Documentation
    • Updated plugin documentation with new enable/disable command examples and activation rules.

Add zero plugins enable|disable so installed plugins can be toggled
without removing them, matching hooks/MCP parity and EXTENDING docs.
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 98f89670-c807-45e2-96bf-a84cad6e1c37

📥 Commits

Reviewing files that changed from the base of the PR and between 7a1a5b5 and d7357a8.

📒 Files selected for processing (2)
  • internal/plugins/enabled.go
  • internal/plugins/enabled_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/plugins/enabled_test.go
  • internal/plugins/enabled.go

Walkthrough

Adds zero plugins enable and disable commands backed by manifest updates. Plugin discovery supports project precedence and user-only filtering, with JSON/text output, help text, persistence tests, and updated activation documentation.

Changes

Plugin enablement controls

Layer / File(s) Summary
Manifest state management
internal/plugins/enabled.go, internal/plugins/enabled_test.go
Adds plugin discovery, manifest enabled updates, idempotent results, source filtering, atomic persistence, permission preservation, and filesystem-backed tests.
CLI commands and output
internal/cli/extensions.go, internal/cli/extensions_test.go
Adds enable and disable routing, flag parsing, help output, JSON/text responses, error handling, and CLI coverage.
Plugin activation documentation
docs/EXTENDING.md
Documents the new commands, default activation semantics, and --user behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant zero plugins
  participant plugins.SetEnabledByID
  participant plugin.json
  User->>zero plugins: enable or disable plugin id
  zero plugins->>plugins.SetEnabledByID: resolve plugin and requested state
  plugins.SetEnabledByID->>plugin.json: update enabled field
  plugin.json-->>zero plugins: return toggle result
  zero plugins-->>User: display text or redacted JSON status
Loading

Possibly related PRs

  • Gitlawb/zero#61: Provides the plugin loading and CLI infrastructure extended by these enable/disable commands.

Suggested reviewers: vasanthdev2004

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding CLI enable/disable commands that toggle plugin.json.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/cli/extensions_test.go (1)

338-395: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Solid happy-path/usage-error coverage; consider adding a --user and no-op-message case.

Coverage for --user targeting and the "was already %s" no-op branch currently only exists at the plugins package level (enabled_test.go), not at the CLI layer. A quick additional case exercising zero plugins disable <id> --user against dual user/project roots, and re-running disable on an already-disabled plugin to check the "was already disabled" message, would close that gap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/extensions_test.go` around lines 338 - 395, The
TestRunPluginsEnableDisable CLI test only covers project-scoped changes and the
initial disable message. Extend it with separate user and project plugin roots,
invoke the disable command with --user to verify the user manifest is updated
without changing the project manifest, then repeat disable on the
already-disabled plugin and assert the “was already disabled” no-op message.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/plugins/enabled.go`:
- Around line 89-110: Introduce or reuse a sentinel error for the missing-plugin
case in SetEnabledByID and wrap it with the plugin ID context. In
internal/plugins/enabled.go:89-110, update the !ok return; in
internal/cli/extensions.go:122-128, replace strings.Contains(err.Error(), "is
not installed") with errors.Is against that sentinel, adding the required
import. Preserve distinct handling for the empty-ID validation error.

---

Nitpick comments:
In `@internal/cli/extensions_test.go`:
- Around line 338-395: The TestRunPluginsEnableDisable CLI test only covers
project-scoped changes and the initial disable message. Extend it with separate
user and project plugin roots, invoke the disable command with --user to verify
the user manifest is updated without changing the project manifest, then repeat
disable on the already-disabled plugin and assert the “was already disabled”
no-op message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d03e2bd7-3ab1-4c50-914f-11de8cd3057f

📥 Commits

Reviewing files that changed from the base of the PR and between 6fc1220 and 3d70c0c.

📒 Files selected for processing (5)
  • docs/EXTENDING.md
  • internal/cli/extensions.go
  • internal/cli/extensions_test.go
  • internal/plugins/enabled.go
  • internal/plugins/enabled_test.go

Comment on lines +89 to +110
// SetEnabledByID finds a plugin by id (respecting LoadOptions) and toggles its
// manifest enabled field.
func SetEnabledByID(options LoadOptions, id string, enabled bool) (SetEnabledResult, error) {
plugin, ok, err := FindByID(options, id)
if err != nil {
return SetEnabledResult{}, err
}
if !ok {
return SetEnabledResult{}, fmt.Errorf("plugin %q is not installed", id)
}
changed, err := SetEnabled(plugin.ManifestPath, enabled)
if err != nil {
return SetEnabledResult{}, err
}
return SetEnabledResult{
ID: plugin.ID,
Enabled: enabled,
Changed: changed,
Source: plugin.Source,
ManifestPath: plugin.ManifestPath,
}, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant files and nearby code paths.
sed -n '1,220p' internal/plugins/enabled.go
printf '\n--- extensions.go ---\n'
sed -n '1,220p' internal/cli/extensions.go

Repository: Gitlawb/zero

Length of output: 9719


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for the error strings and any sentinel/type already used elsewhere.
rg -n '"is not installed"|plugin id is required|ErrPlugin|errors.Is|Contains\(err.Error' internal -S

Repository: Gitlawb/zero

Length of output: 47876


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all callers and tests around plugin enable/disable so we can judge reachability.
rg -n 'SetEnabledByID\(|runPluginsToggle\(|plugins enable|plugins disable|plugin id is required|is not installed' internal -S

Repository: Gitlawb/zero

Length of output: 1703


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the toggle parser and any tests that cover empty or missing IDs.
sed -n '100,170p' internal/cli/extensions.go
printf '\n--- cli tests ---\n'
rg -n 'plugins (enable|disable)|is not installed|plugin id is required|usage: zero plugins (enable|disable)' internal/cli internal/plugins -S

Repository: Gitlawb/zero

Length of output: 2710


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the plugin enable tests and the install code for matching error-handling patterns.
sed -n '1,220p' internal/plugins/enabled_test.go
printf '\n--- install.go excerpt ---\n'
sed -n '160,230p' internal/plugins/install.go

Repository: Gitlawb/zero

Length of output: 5170


Use a typed error for the missing-plugin path. SetEnabledByID should wrap plugin %q is not installed with a sentinel, and internal/cli/extensions.go should switch from strings.Contains(err.Error(), "is not installed") to errors.Is(...); otherwise the empty-id path (plugin id is required) still falls into the crash branch.

📍 Affects 2 files
  • internal/plugins/enabled.go#L89-L110 (this comment)
  • internal/cli/extensions.go#L122-L128
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/plugins/enabled.go` around lines 89 - 110, Introduce or reuse a
sentinel error for the missing-plugin case in SetEnabledByID and wrap it with
the plugin ID context. In internal/plugins/enabled.go:89-110, update the !ok
return; in internal/cli/extensions.go:122-128, replace
strings.Contains(err.Error(), "is not installed") with errors.Is against that
sentinel, adding the required import. Preserve distinct handling for the
empty-ID validation error.

Replace string matching with errors.Is for missing plugins, and extend
CLI tests for --user targeting and the already-disabled no-op message.
@Ayush7614

Ayush7614 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review the latest commits and changes

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@Ayush7614 I see you've tagged me but there's no specific request in your comment — could you clarify what you'd like me to do? For example, I can:

  • Review the PR changes (internal/cli/extensions.go, internal/plugins/enabled.go, and their tests)
  • Answer a question about the plugin enable/disable implementation
  • Help with something else specific

Let me know and I'll jump right in.

(ノ・ヮ・)ノ

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
fd . -e go internal/plugins internal/cli | xargs echo
echo "---enabled.go---"
cat -n internal/plugins/enabled.go
echo "---extensions.go (relevant portion)---"
cat -n internal/cli/extensions.go | sed -n '1,400p'

Length of output: 23976


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "---activate.go (look for similar manifest write patterns)---"
rg -n "WriteFile|os.Rename|Chmod|optionalBool" internal/plugins/*.go
echo "---optionalBool def---"
rg -n "func optionalBool" -A 20 internal/plugins/*.go

Length of output: 3794


@Ayush7614 Reviewed the latest commits. Overall the implementation is solid — errors.Is-based error handling, atomic manifest writes (temp file + rename in the same directory), and the --user/--json flag plumbing all look correct, and the new test coverage (TestSetEnabledTogglesManifestAndIsIdempotent, TestSetEnabledByIDRespectsUserOnlyFilter, TestSetEnabledByIDMissingPlugin, TestRunPluginsEnableDisable, TestRunPluginsDisableUserIgnoresProject) exercises the important paths (idempotency, project-vs-user precedence, missing plugin, JSON output).

One functional concern worth fixing before merge:

File permission regression in internal/plugins/enabled.go (SetEnabled, Line 83)

if err := os.WriteFile(tempPath, append(data, '\n'), 0o600); err != nil {

This hardcodes 0o600 for the rewritten plugin.json, but every other manifest write in this codebase (e.g., internal/plugins/install.go Line 242, and test fixtures) uses 0o644. Since SetEnabled replaces the manifest via os.Rename, the first enable/disable toggle will silently downgrade a project-tracked plugin.json from 0o644 to 0o600 — which could break group/CI read access or diverge from the mode a user/tool originally set, and it's inconsistent with the rest of the plugin file-writing code.

Suggested fix: preserve the original file's mode instead of hardcoding it, e.g.:

mode := os.FileMode(0o644)
if info, statErr := os.Stat(resolved); statErr == nil {
    mode = info.Mode().Perm()
}
if err := os.WriteFile(tempPath, append(data, '\n'), mode); err != nil {
    return false, err
}

Everything else — the ErrNotInstalled sentinel/wrapping, FindByID/SetEnabledByID composition, CLI flag parsing, help text, and JSON/text output branching on result.Changed — looks correct and consistent with the rest of the CLI.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Keep the original manifest permissions when rewriting enabled, falling
back to 0o644 to match install and avoid silently tightening to 0o600.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice feature — the atomic manifest rewrite, idempotency, and project/user precedence all look right, and the new CLI tests pass on my end. One blocker: TestSetEnabledPreservesManifestMode asserts a 0644 mode after os.Chmod, but on Windows os.Stat reports 0666 regardless of chmod, so it fails on windows-latest (which is in the CI matrix and runs go test ./...). Guard that test with a runtime.GOOS != "windows" skip (or drop the exact-bits assertion) and I think this is good to go.

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