-
Notifications
You must be signed in to change notification settings - Fork 7
fix(#335): implement run <plugin> <resource> <action> one-shot command
#365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,11 +7,34 @@ | |
| * Designed for first-time / npx users — a single tweetable command. | ||
| */ | ||
|
|
||
| const fs = require("fs"); | ||
| const { updatePlugins } = require("./plugins-update"); | ||
| const { installPlugin, getPlugin } = require("./plugins-install"); | ||
| const { loadConfig } = require("./config"); | ||
| const { execute } = require("./executor"); | ||
| const { makeOutput, makeOutputError, makeStreamEmitter } = require("./output"); | ||
| const { makeStreamEmitter } = require("./output"); | ||
| const { REMOTE_CATALOG_FILE } = require("./plugins-store"); | ||
| const { listRegistryPlugins } = require("./plugins-registry"); | ||
|
|
||
| const CATALOG_FRESH_MS = 60 * 60 * 1000; // 1 hour | ||
|
|
||
| function catalogIsFresh(filePath = REMOTE_CATALOG_FILE, now = Date.now()) { | ||
| try { | ||
| if (!fs.existsSync(filePath)) return false; | ||
| const st = fs.statSync(filePath); | ||
| const age = now - st.mtimeMs; | ||
| return age < CATALOG_FRESH_MS; | ||
|
Comment on lines
+25
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Require 🤖 Prompt for AI Agents |
||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| 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); | ||
|
Comment on lines
+32
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Use equality before the fuzzy lookup.
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| async function handleRunCommand({ positional, flags, humanMode, output, outputError }) { | ||
| const pluginName = positional[1]; | ||
|
|
@@ -40,16 +63,25 @@ async function handleRunCommand({ positional, flags, humanMode, output, outputEr | |
| return; | ||
| } | ||
|
|
||
| // ── Step 1: Sync plugin catalog from GitHub ────────────────────────── | ||
| try { | ||
| if (humanMode) process.stderr.write("Syncing plugin catalog...\n"); | ||
| const updateResult = await updatePlugins({ check: false }); | ||
| if (humanMode && updateResult.added + updateResult.changed > 0) { | ||
| process.stderr.write(` → ${updateResult.added} new, ${updateResult.changed} changed\n`); | ||
| const fullCommand = `${pluginName}.${resource}.${action}`; | ||
| const fresh = catalogIsFresh(); | ||
| let updated = false; | ||
|
|
||
| // ── Step 1: Sync plugin catalog from GitHub if stale or missing ──────── | ||
| if (!fresh) { | ||
| try { | ||
| if (humanMode) process.stderr.write("Syncing plugin catalog...\n"); | ||
| const updateResult = await updatePlugins({ check: false }); | ||
| updated = true; | ||
|
Comment on lines
+74
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win Persist successful no-op catalog checks.
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 |
||
| if (humanMode && updateResult.added + updateResult.changed > 0) { | ||
| process.stderr.write(` → ${updateResult.added} new, ${updateResult.changed} changed\n`); | ||
| } | ||
| } catch (err) { | ||
| // Non-fatal: if update fails, try anyway with local catalog | ||
| if (humanMode) process.stderr.write(` ⚠ Catalog sync failed (${err.message}), continuing with local catalog\n`); | ||
| } | ||
| } catch (err) { | ||
| // Non-fatal: if update fails, try anyway with local catalog | ||
| if (humanMode) process.stderr.write(` ⚠ Catalog sync failed (${err.message}), continuing with local catalog\n`); | ||
| } else if (humanMode) { | ||
| process.stderr.write("Using fresh local catalog.\n"); | ||
| } | ||
|
|
||
| // ── Step 2: Install plugin if not already ───────────────────────────── | ||
|
|
@@ -64,23 +96,27 @@ async function handleRunCommand({ positional, flags, humanMode, output, outputEr | |
| }); | ||
| if (humanMode) process.stderr.write(` → ${result.installed_commands} commands registered\n`); | ||
| } catch (err) { | ||
| const available = findSuggestions(pluginName); | ||
| const suggestions = ["Check the plugin name and try again"]; | ||
| if (available.length > 0) { | ||
| suggestions.unshift(`Available plugins: ${available.join(", ")}`); | ||
| } | ||
| suggestions.push("Run: supercli plugins explore --json"); | ||
| outputError({ | ||
| code: 92, | ||
| type: "resource_not_found", | ||
| message: `Plugin '${pluginName}' not found after catalog sync.`, | ||
| suggestions: [ | ||
| "Check the plugin name and try again", | ||
| "Run: supercli plugins explore --json", | ||
| ], | ||
| message: `Plugin '${pluginName}' not found${updated ? " after catalog sync" : " in the local catalog"}.`, | ||
| suggestions, | ||
| recoverable: false, | ||
| }); | ||
| return; | ||
| } | ||
| } else if (humanMode) { | ||
| process.stderr.write(`Plugin already installed: ${pluginName}\n`); | ||
| } | ||
|
|
||
| // ── Step 3: Reload config and execute ──────────────────────────────── | ||
| const config = await loadConfig(); | ||
| const fullCommand = `${pluginName}.${resource}.${action}`; | ||
| const cmd = config.commands.find( | ||
| (c) => c.namespace === pluginName && c.resource === resource && c.action === action | ||
| ); | ||
|
|
@@ -122,6 +158,8 @@ async function handleRunCommand({ positional, flags, humanMode, output, outputEr | |
| const start = Date.now(); | ||
| try { | ||
| const result = await execute(cmd, cmdFlags, { | ||
| server: process.env.SUPERCLI_SERVER || "", | ||
| config, | ||
| onStreamEvent: cmd.adapterConfig?.stream === "jsonl" | ||
| ? makeStreamEmitter(`${pluginName}.run`, { humanMode, output }) : null, | ||
| }); | ||
|
|
@@ -146,4 +184,4 @@ async function handleRunCommand({ positional, flags, humanMode, output, outputEr | |
| } | ||
| } | ||
|
|
||
| module.exports = { handleRunCommand }; | ||
| module.exports = { handleRunCommand, catalogIsFresh, findSuggestions }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,11 @@ | |
|
|
||
| "use strict"; | ||
|
|
||
| require("dotenv").config({ quiet: true }); | ||
| try { | ||
| require("dotenv").config({ quiet: true }); | ||
| } catch (e) { | ||
| // dotenv not installed, proceed without .env loading | ||
| } | ||
|
Comment on lines
+5
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 }));
}
JSRepository: 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])
PYRepository: javimosch/supercli Length of output: 571 Do not swallow all This 🤖 Prompt for AI Agents |
||
| const { loadConfig, syncConfig, showConfig, setMcpServer, removeMcpServer, listMcpServers, upsertCommand, getClientId } = require("./config"); | ||
| const { handleMcpRegistryCommand } = require("./mcp-local"); | ||
| const { handlePluginsCommand } = require("./plugins-command"); | ||
|
|
||
There was a problem hiding this comment.
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 requiredsc-zigdefault invocation.cli/help-json.js#L12-L12: update the machine-readablerunusage.cli/help.js#L23-L23: update structured JSON help.cli/help.js#L79-L79: update console help.As per coding guidelines, use
sc-zigby default for plugin discovery and command execution.📍 Affects 2 files
cli/help-json.js#L12-L12(this comment)cli/help.js#L23-L23cli/help.js#L79-L79🤖 Prompt for AI Agents
Source: Coding guidelines