Skip to content

fix(#335): implement run <plugin> <resource> <action> one-shot command - #365

Open
javimosch wants to merge 1 commit into
masterfrom
am/am-f17c27-dkdoeotqzvn8-3120cd1a
Open

fix(#335): implement run <plugin> <resource> <action> one-shot command#365
javimosch wants to merge 1 commit into
masterfrom
am/am-f17c27-dkdoeotqzvn8-3120cd1a

Conversation

@javimosch

@javimosch javimosch commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Automated maintenance run by automaintainer.

Focus: ISSUE FIX OBJECTIVE (GitHub Issue #335: "feat: sc run <plugin> <action> — one-shot discover+install+execute")

ISSUE DESCRIPTION:

Problem

Promoting SuperCLI on social media needs a tweetable one-liner. Currently, discovering and running a plugin for the first time requires 3 chained commands:

npx superacli plugins update && npx superacli plugins install claude-session-optimizer && npx superacli claude-session-optimizer self auto

Thats ~240 characters — too long for a tweet, and too verbose for a first-time user to remember.

Desired UX

# one-shot: updates catalog, installs plugin, runs the action
npx superacli run claude-session-optimizer self auto

# even shorter alias
npx scrun claude-session-optimizer self auto

A single run (or go, x, !) command that:

  1. plugins update — syncs the latest plugin catalog from GitHub master (so new plugins are discoverable even on stale npm releases)
  2. plugins install <plugin> — registers the plugin commands if not already installed (idempotent — no-op if already installed)
  3. Executes the requested <resource> <action> with given args

Requirements

  • Idempotent: if plugin is already installed and catalog is current, skip steps 1-2 and just execute
  • Fast path: if local catalog is fresh (e.g., <1h old), skip the update fetch
  • Works with npx: must not require global install — npx superacli run <plugin> <action> should be the entry point
  • Error handling: if plugin not found even after update, show clear error with available plugins

Example

# Tweetable one-liner for claude-session-optimizer
npx superacli run claude-session-optimizer self auto

# Works for any plugin
npx superacli run github-mcp self mcp
npx superacli run a2a-skill project init

Why This Matters

A 240-char shell pipeline wont fit in a tweet, wont get retweeted, and wont convert casual users. A single run command at ~80 chars will.

Real use case: promoting claude-session-optimizer — see https://github.com/javimosch/claude-session-optimizer

APPROACH: Minimal fix — implement the described change with the smallest safe diff. Stay as close to the issue description as possible.

PROCEDURE (follow in order, do not deviate):

  1. Read the issue and orient to the affected code (at most ~4 commands, then stop).
  2. Implement the fix described above — stay close to the issue description.
  3. If a test suite exists, verify the fix passes it.
  4. COMMIT referencing the issue: fix(feat: sc run <plugin> <action> — one-shot discover+install+execute #335):
  5. The PR body MUST include 'Fixes feat: sc run <plugin> <action> — one-shot discover+install+execute #335' so GitHub auto-closes the issue on merge.
  6. One focused fix is a complete win. Do not scope-creep.

Branch: am/am-f17c27-dkdoeotqzvn8-3120cd1a

Diff:

__tests__/run-command.test.js | 208 +++++++++++++++++++++++++++++++++++++++++-
 cli/help-json.js              |   1 +
 cli/help.js                   |   2 +
 cli/run.js                    |  72 +++++++++++----
 cli/supercli.js               |   6 +-
 5 files changed, 270 insertions(+), 19 deletions(-)

Summary by CodeRabbit

  • New Features

    • Added the run command to CLI help and capability metadata.
    • The command now reuses a fresh plugin catalog and synchronizes it only when needed.
    • Plugin-not-found errors now provide catalog context and suggested alternatives.
    • Command execution now uses the loaded configuration.
  • Bug Fixes

    • The CLI continues starting when environment-file loading is unavailable or fails.

Implements `supercli run <plugin> <resource> <action>`, which syncs the
plugin catalog from GitHub, installs the plugin if it is not already
registered, and executes the requested command in a single invocation.

Key details:
- Adds a 1-hour freshness check for the local remote catalog, skipping
the network fetch when the catalog is current.
- Adds fuzzy plugin suggestions when a requested plugin is not found.
- Defends `supercli.js` against a missing `dotenv` dependency so `npx` works.
- Surfaces the new `run` command in help and help-json output.
- Expands `__tests__/run-command.test.js` to cover validation, fast-path,
install, execute, and not-found paths.

Fixes #335

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The run command now skips fresh catalog synchronization, updates stale catalogs, installs missing plugins, suggests alternatives, and passes configuration into execution. Help output documents the command. CLI startup tolerates dotenv loading failures.

Changes

Run command

Layer / File(s) Summary
Catalog freshness and plugin discovery
cli/run.js, __tests__/run-command.test.js
The command checks catalog age, synchronizes missing or stale catalogs, and returns exact or fuzzy plugin suggestions.
Plugin installation and execution
cli/run.js, __tests__/run-command.test.js, cli/help.js, cli/help-json.js
The command handles installation and not-found errors, passes configuration to execution, exports helper functions, and documents the run command.
Optional environment loading
cli/supercli.js
CLI startup continues when dotenv loading fails.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant RunCommand
  participant Catalog
  participant PluginInstaller
  participant PluginExecutor
  User->>RunCommand: Request plugin resource action
  RunCommand->>Catalog: Check and update catalog when stale
  RunCommand->>PluginInstaller: Install plugin when absent
  RunCommand->>PluginExecutor: Execute action with configuration
  PluginExecutor-->>User: Return result or resource error
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the implementation of the run <plugin> <resource> <action> one-shot command.
Linked Issues check ✅ Passed The changes implement catalog synchronization, freshness checks, plugin installation, execution, suggestions, npx compatibility, help output, and tests required by issue #335.
Out of Scope Changes check ✅ Passed The changes are directly related to issue #335 and support the run command, npx compatibility, documentation, and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch am/am-f17c27-dkdoeotqzvn8-3120cd1a

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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/run.js (1)

90-112: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not report all setup failures as plugin absence.

This try block catches loadConfig failures and all installPlugin failures. A malformed config or install conflict is then reported as resource_not_found with code 92.

Only generate plugin suggestions for the installer’s actual not-found error. Preserve the original code, type, message, and suggestions for configuration and installation failures.

🤖 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 `@cli/run.js` around lines 90 - 112, Update the error handling around
loadConfig and installPlugin so only the installer’s actual plugin-not-found
error enters the findSuggestions/outputError resource_not_found path. Preserve
the original error code, type, message, and suggestions for configuration
failures and other installation errors, using the existing error symbols or
properties exposed by the installer to distinguish not-found failures.
🧹 Nitpick comments (1)
__tests__/run-command.test.js (1)

189-192: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert configuration forwarding to execute.

The test only verifies that execute was called. It does not verify the new config field passed by cli/run.js Line 162, so removal of that field would pass this test.

Store the mocked configuration in a variable. Assert that the third execute argument contains that exact config object.

🤖 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 `@__tests__/run-command.test.js` around lines 189 - 192, Update the test around
the execute expectation to retain the mocked configuration in a variable and
assert that execute’s third argument includes that exact config object. Keep the
existing call-count and other interaction assertions unchanged.
🤖 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 `@cli/help-json.js`:
- Line 12: Update the run help usage to invoke the required sc-zig default
executor instead of supercli in cli/help-json.js (line 12), cli/help.js (line
23), and cli/help.js (line 79). Keep the command syntax and descriptions
consistent across all machine-readable, structured JSON, and console help
entries.

In `@cli/run.js`:
- Around line 32-36: Update findSuggestions in cli/run.js (lines 32-36) to
filter name-only results by normalized exact name equality before returning;
only perform the fuzzy lookup when no exact match remains. Update
__tests__/run-command.test.js (lines 147-152) to mock empty exact results and
fuzzy results, then assert both listRegistryPlugins calls.
- Around line 25-26: Update the catalog freshness check to require a nonnegative
age in addition to age being below CATALOG_FRESH_MS, so future mtimeMs values
are treated as stale. Add a test covering a future catalog timestamp and verify
it does not suppress synchronization.
- Around line 74-75: Update the successful no-op path around updatePlugins so an
unchanged remote catalog still persists the local catalog or its
successful-check timestamp before marking the run updated. Preserve the existing
behavior for catalogs with changes, and add a regression test that runs twice
with an unchanged stale catalog and verifies the second run does not repeat
synchronization.

In `@cli/supercli.js`:
- Around line 5-9: Update the dotenv initialization around the top-level require
so only a missing-module error from require("dotenv") is handled as an optional
dependency; rethrow any other require failure, retain the loaded module, and
invoke its config({ quiet: true }) method outside the catch so configuration
errors propagate.

---

Outside diff comments:
In `@cli/run.js`:
- Around line 90-112: Update the error handling around loadConfig and
installPlugin so only the installer’s actual plugin-not-found error enters the
findSuggestions/outputError resource_not_found path. Preserve the original error
code, type, message, and suggestions for configuration failures and other
installation errors, using the existing error symbols or properties exposed by
the installer to distinguish not-found failures.

---

Nitpick comments:
In `@__tests__/run-command.test.js`:
- Around line 189-192: Update the test around the execute expectation to retain
the mocked configuration in a variable and assert that execute’s third argument
includes that exact config object. Keep the existing call-count and other
interaction assertions unchanged.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c8efc35b-6acb-4463-8447-4c910547dae1

📥 Commits

Reviewing files that changed from the base of the PR and between 6df5369 and 3147a74.

📒 Files selected for processing (5)
  • __tests__/run-command.test.js
  • cli/help-json.js
  • cli/help.js
  • cli/run.js
  • cli/supercli.js

Comment thread cli/help-json.js
mcp: { subcommands: ["list", "add", "tools", "call", "bind", "doctor", "remove"], description: "Manage local MCP server registry and invoke MCP tools" },
commands: { description: "List all commands" },
inspect: { description: "Inspect command details", usage: "supercli inspect <ns> <res> <act>" },
run: { description: "Sync catalog, install plugin, and execute a command", usage: "supercli run <plugin> <resource> <action> [--args]" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the required default command executor.

These entries direct users to execute plugins with supercli run. Update them to use the required sc-zig default invocation.

  • cli/help-json.js#L12-L12: update the machine-readable run usage.
  • cli/help.js#L23-L23: update structured JSON help.
  • cli/help.js#L79-L79: update console help.

As per coding guidelines, use sc-zig by default for plugin discovery and command execution.

📍 Affects 2 files
  • cli/help-json.js#L12-L12 (this comment)
  • cli/help.js#L23-L23
  • cli/help.js#L79-L79
🤖 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 `@cli/help-json.js` at line 12, Update the run help usage to invoke the
required sc-zig default executor instead of supercli in cli/help-json.js (line
12), cli/help.js (line 23), and cli/help.js (line 79). Keep the command syntax
and descriptions consistent across all machine-readable, structured JSON, and
console help entries.

Source: Coding guidelines

Comment thread cli/run.js
Comment on lines +25 to +26
const age = now - st.mtimeMs;
return age < CATALOG_FRESH_MS;

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 | 🟡 Minor | ⚡ Quick win

Treat future catalog timestamps as stale.

A future mtimeMs produces a negative age, which passes the current check. Clock skew or restored metadata can then suppress synchronization until the system clock passes that timestamp.

Require age >= 0 before accepting the catalog as fresh. Add a future-timestamp test.

🤖 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 `@cli/run.js` around lines 25 - 26, Update the catalog freshness check to
require a nonnegative age in addition to age being below CATALOG_FRESH_MS, so
future mtimeMs values are treated as stale. Add a test covering a future catalog
timestamp and verify it does not suppress synchronization.

Comment thread cli/run.js
Comment on lines +32 to +36
function findSuggestions(pluginName) {
const exact = listRegistryPlugins({ name: pluginName, nameOnly: true }).slice(0, 5);
if (exact.length > 0) return exact.map((p) => p.name);
const fuzzy = listRegistryPlugins({ name: pluginName }).slice(0, 5);
return fuzzy.map((p) => p.name);

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 | 🟡 Minor | ⚡ Quick win

Use equality before the fuzzy lookup.

listRegistryPlugins({ name, nameOnly: true }) uses substring matching. A query such as git returns github-* entries in the first lookup, so the fuzzy lookup never runs despite no exact plugin name.

  • cli/run.js#L32-L36: filter name-only results for normalized equality before returning them, then run the fuzzy lookup when no equal name exists.
  • __tests__/run-command.test.js#L147-L152: mock an empty first lookup and fuzzy results in the second lookup. Assert both filter calls.
📍 Affects 2 files
  • cli/run.js#L32-L36 (this comment)
  • __tests__/run-command.test.js#L147-L152
🤖 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 `@cli/run.js` around lines 32 - 36, Update findSuggestions in cli/run.js (lines
32-36) to filter name-only results by normalized exact name equality before
returning; only perform the fuzzy lookup when no exact match remains. Update
__tests__/run-command.test.js (lines 147-152) to mock empty exact results and
fuzzy results, then assert both listRegistryPlugins calls.

Comment thread cli/run.js
Comment on lines +74 to +75
const updateResult = await updatePlugins({ check: false });
updated = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Persist successful no-op catalog checks.

updatePlugins returns before writeLocalCatalog(remoteCatalog) when the remote catalog has no changes. The catalog mtime then remains stale, so every later run repeats the remote synchronization.

Persist a successful check timestamp, or rewrite/touch the local catalog after a successful no-op update. Add a regression test that runs twice after an unchanged stale-catalog sync.

🤖 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 `@cli/run.js` around lines 74 - 75, Update the successful no-op path around
updatePlugins so an unchanged remote catalog still persists the local catalog or
its successful-check timestamp before marking the run updated. Preserve the
existing behavior for catalogs with changes, and add a regression test that runs
twice with an unchanged stale catalog and verifies the second run does not
repeat synchronization.

Comment thread cli/supercli.js
Comment on lines +5 to +9
try {
require("dotenv").config({ quiet: true });
} catch (e) {
// dotenv not installed, proceed without .env loading
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files matching supercli =="
fd -a 'supercli\.js$' . || true

echo "== target file excerpt =="
if [ -f cli/supercli.js ]; then
  sed -n '1,80p' cli/supercli.js | cat -n
fi

echo "== dotenv references =="
rg -n "dotenv|config\\(\\{|MODULE_NOT_FOUND" -S . --glob '!node_modules' --glob '!dist' --glob '!build' || true

echo "== package metadata =="
for f in package.json cli/package.json package-lock.json npm-shrinkwrap.json yarn.lock pnpm-lock.yaml; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,120p' "$f"
  fi
done

echo "== behavioral probe: catch behavior for require/config failures =="
node - <<'JS'
const cases = [
  "missing package require",
  "missing package config",
  "sync require failing before export",
  ".config() throwing",
];
for (const name of cases) {
  const errors = [];
  const handlers = [];
  const fn = () => {
    handlers.push("loaded");
    (function () {
      if (name.startsWith(".config")) throw new Error("config failed");
      if (name === "missing package require") throw Object.assign(new Error(), { code: "MODULE_NOT_FOUND", message: "Cannot find module 'dotenv'" });
      if (name === "missing package config") throw Object.assign(new Error(), { code: "MODULE_NOT_FOUND", message: "Cannot find module 'dotenv'" });
      if (name.includes("synchronous") || name === "sync require failing before export") throw new TypeError("require failed");
      handlers.push("configured");
    })();
    throw new Error("post-config");
  });
  try { fn(); } catch (e) { errors.push(String(typeof e === 'object' && e.code ? e.code : e.message), e.code); }
  console.log(name, JSON.stringify({ errors, handlers }));
}
JS

Repository: javimosch/supercli

Length of output: 41592


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== behavioral probe: Node catch behavior for chained require/config errors =="
node - <<'JS'
function simulateMissingPackage() {
  const errors = [];
  const handlers = [];
  let dotenv;
  try {
    handlers.push("before require");
    try {
      dotenv = require("dotenv");
    } catch (error) {
      handlers.push("require caught");
      if (error?.code !== "MODULE_NOT_FOUND" || !String(error.message).includes("'dotenv'")) throw error;
    }
    dotenv?.config({ quiet: true });
    handlers.push("configure complete");
  } catch (error) {
    errors.push(String(typeof error === "object" && error.code ? error.code : error.message));
  }
  return { errors, handlers };
}

function simulateConfigFailure() {
  const errors = [];
  const handlers = [];
  let dotenv;
  const fakeModule = {
    config: function () {
      handlers.push("configure entered");
      throw new Error("dot configuration failed");
    }
  };
  try {
    handlers.push("before require");
    try {
      dotenv = fakeModule;
    } catch (error) {
      handlers.push("require caught");
      if (error?.code !== "MODULE_NOT_FOUND" || !String(error.message).includes("'dotenv'")) throw error;
    }
    dotenv?.config({ quiet: true });
    handlers.push("configure complete");
  } catch (error) {
    errors.push(String(typeof error === "object" && error.code ? error.code : error.message));
  }
  return { errors, handlers };
}

console.log("missing-package path:", JSON.stringify(simulateMissingPackage()));
console.log("config failure path:", JSON.stringify(simulateConfigFailure()));
JS

echo "== read-only check: catch shape in cli/supercli.js =="
python3 - <<'PY'
from pathlib import Path
text = Path("cli/supercli.js").read_text()
start = text.index("try {\n  require(\"dotenv\").config({ quiet: true });\n} catch (e) {")
end = text.index("}", start) + 1
print("first catch block:", text[start:end])
PY

Repository: javimosch/supercli

Length of output: 571


Do not swallow all dotenv initialization errors.

This catch covers both require("dotenv") and .config(), so any thrown dotenv error is treated as a missing package and ignored. Catch the missing-module case from require("dotenv") separately, then call .config({ quiet: true }) outside that block.

🤖 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 `@cli/supercli.js` around lines 5 - 9, Update the dotenv initialization around
the top-level require so only a missing-module error from require("dotenv") is
handled as an optional dependency; rethrow any other require failure, retain the
loaded module, and invoke its config({ quiet: true }) method outside the catch
so configuration errors propagate.

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.

feat: sc run <plugin> <action> — one-shot discover+install+execute

1 participant