From jarvis
Universal intent router. Takes any natural language request and routes it to the highest-ROI skill automatically. One line of output explaining the choice, then full execution. Use when the user says "/jarvis I want to..." or asks for anything without specifying a workflow.
npx claudepluginhub upayanghosh/claude-jarvisThis skill is limited to using the following tools:
You are the single entry point. The user said something. Your job:
Orchestrates complex tasks by analyzing requirements, discovering plugins/agents/skills/MCPs, matching with confidence scores, and generating strategic execution plans with alternatives.
Guides skill discovery and invocation before any response (1% relevance threshold), with platform instructions for Claude Code (Skill tool), Copilot CLI (skill tool), Gemini CLI (activate_skill). Use at conversation start.
Share bugs, ideas, or general feedback.
You are the single entry point. The user said something. Your job:
→ [skill-name]: [reason] — [roast]No asking for permission beyond what's defined. One line, then go.
JARVIS_DIR=~/.claude/skills/jarvis
JARVIS_LOG="$JARVIS_DIR/update.log"
# ── Rotate log if over 1MB ──
if [ -f "$JARVIS_LOG" ] && [ "$(wc -c < "$JARVIS_LOG" 2>/dev/null || echo 0)" -gt 1048576 ]; then
mv "$JARVIS_LOG" "${JARVIS_LOG}.bak" 2>/dev/null
: > "$JARVIS_LOG"
fi
# ── Single Node.js call for all bootstrap work ──
# Node is guaranteed (this is an npm package). No Python dependency.
BOOTSTRAP=$(node -e "
var fs=require('fs'),path=require('path'),os=require('os');
// Read and coerce config
var configPath=path.join(os.homedir(),'.claude','skills','jarvis','config.json');
var config={auto_update:null,last_check:0,deps_asked:false,pending:null};
try{config=JSON.parse(fs.readFileSync(configPath,'utf8'));}catch(e){}
var au='null';
if(config.auto_update===true)au='true';
else if(config.auto_update===false)au='false';
else if(config.auto_update!==null)au='null';
var lc=0;
if(typeof config.last_check==='number'&&config.last_check>=0)lc=Math.floor(config.last_check);
// deps_asked: whether user has been asked about optional skill installs
var da=config.deps_asked===true?'true':'false';
// pending: which question is awaiting a text answer from the user
var pn=config.pending||'none';
// Resolve Superpowers path with proper numeric semver sort
var spBase='';
var spDir=path.join(os.homedir(),'.claude','plugins','cache','superpowers-dev','superpowers');
try{
var dirs=fs.readdirSync(spDir).filter(function(d){return fs.statSync(path.join(spDir,d)).isDirectory();});
dirs.sort(function(a,b){
var pa=a.split(/[.-]/).map(function(x){return parseInt(x)||0;});
var pb=b.split(/[.-]/).map(function(x){return parseInt(x)||0;});
for(var i=0;i<Math.max(pa.length,pb.length);i++){
if((pa[i]||0)!==(pb[i]||0))return(pa[i]||0)-(pb[i]||0);
}
return 0;
});
if(dirs.length)spBase=path.join(spDir,dirs[dirs.length-1]);
}catch(e){}
// Timestamp — always works, no Python needed
var now=Math.floor(Date.now()/1000);
var hoursSince=lc>0?Math.max(0,Math.floor((now-lc)/3600)):999;
console.log('AUTO_UPDATE='+au);
console.log('HOURS_SINCE='+hoursSince);
console.log('SUPERPOWERS_BASE='+spBase);
console.log('DEPS_ASKED='+da);
console.log('PENDING='+pn);
" 2>>"$JARVIS_LOG")
# Parse output
AUTO_UPDATE=$(echo "$BOOTSTRAP" | grep '^AUTO_UPDATE=' | cut -d= -f2)
HOURS_SINCE=$(echo "$BOOTSTRAP" | grep '^HOURS_SINCE=' | cut -d= -f2)
SUPERPOWERS_BASE=$(echo "$BOOTSTRAP" | grep '^SUPERPOWERS_BASE=' | cut -d= -f2-)
DEPS_ASKED=$(echo "$BOOTSTRAP" | grep '^DEPS_ASKED=' | cut -d= -f2)
PENDING=$(echo "$BOOTSTRAP" | grep '^PENDING=' | cut -d= -f2)
# Fallback if Node.js failed entirely — default to 999 so updates still trigger
[ -z "$AUTO_UPDATE" ] && AUTO_UPDATE="null"
[ -z "$HOURS_SINCE" ] && HOURS_SINCE="999"
[ -z "$DEPS_ASKED" ] && DEPS_ASKED="false"
[ -z "$PENDING" ] && PENDING="none"
echo "AUTO_UPDATE=$AUTO_UPDATE"
echo "HOURS_SINCE=$HOURS_SINCE"
echo "SUPERPOWERS_BASE=$SUPERPOWERS_BASE"
echo "DEPS_ASKED=$DEPS_ASKED"
echo "PENDING=$PENDING"
Capture the output. You will use
AUTO_UPDATE,HOURS_SINCE,SUPERPOWERS_BASE,DEPS_ASKED, andPENDINGin the blocks below.
PENDING is not "none" (user was asked a question last run and re-ran /jarvis with their answer)Check this BEFORE Steps 0a and 0d. If PENDING is not "none", the user is responding to a previous question — do not route to skill execution yet.
The user's answer is in $ARGUMENTS (their original message to /jarvis, e.g. "yes", "no", "all", "gsd", "none").
If PENDING is "auto_update":
$ARGUMENTS to lowercase and check:
auto_update=true, clear pendingauto_update=false, clear pendingJarvis setup (1/2): I didn't understand that. Please re-run /jarvis yes or /jarvis no. Then stop.Run this bash block with the literal true or false substituted for TRUE_OR_FALSE:
node -e "var fs=require('fs'),path=require('path'),os=require('os');var p=path.join(os.homedir(),'.claude','skills','jarvis','config.json');var c;try{c=JSON.parse(fs.readFileSync(p,'utf8'));}catch(e){c={auto_update:null,last_check:0,deps_asked:false,pending:null};}c.auto_update=TRUE_OR_FALSE;c.pending=null;fs.writeFileSync(p,JSON.stringify(c,null,2));console.log('saved auto_update='+c.auto_update);" 2>>~/.claude/skills/jarvis/update.log
After saving, print: Got it! Auto-update set to [yes/no]. Continuing setup...
Then fall through to Step 0d (deps question) if DEPS_ASKED is "false", otherwise continue to Step 1.
If PENDING is "deps":
$ARGUMENTS to lowercase and check:
Jarvis setup (2/2): I didn't understand that. Please re-run /jarvis with one of: all, gsd, superpowers, gstack, none. Then stop.Run the matching install blocks (see Step 0d below), then run the "mark deps_asked" block. Then continue to Step 1.
AUTO_UPDATE is "null" (first time ever) and PENDING is "none"Save pending="auto_update" to config, then print the question as plain text and stop. Do NOT use AskUserQuestion.
node -e "var fs=require('fs'),path=require('path'),os=require('os');var p=path.join(os.homedir(),'.claude','skills','jarvis','config.json');var c;try{c=JSON.parse(fs.readFileSync(p,'utf8'));}catch(e){c={auto_update:null,last_check:0,deps_asked:false,pending:null};}c.pending='auto_update';fs.writeFileSync(p,JSON.stringify(c,null,2));console.log('pending saved');" 2>>~/.claude/skills/jarvis/update.log
Then output this to the user (as plain text, no tool call):
Jarvis setup (1/2): Want Jarvis to keep itself, GSD, Superpowers, and gstack automatically up to date?
→ Type: /jarvis yes (auto-update on)
→ Type: /jarvis no (manual updates)
Stop here. Do not proceed further this run.
DEPS_ASKED is "false" (recommended skills not yet offered) and PENDING is "none"Save pending="deps" to config, then print the question as plain text and stop. Do NOT use AskUserQuestion.
node -e "var fs=require('fs'),path=require('path'),os=require('os');var p=path.join(os.homedir(),'.claude','skills','jarvis','config.json');var c;try{c=JSON.parse(fs.readFileSync(p,'utf8'));}catch(e){c={auto_update:false,last_check:0,deps_asked:false,pending:null};}c.pending='deps';fs.writeFileSync(p,JSON.stringify(c,null,2));console.log('pending saved');" 2>>~/.claude/skills/jarvis/update.log
Then output this to the user (as plain text, no tool call):
Jarvis setup (2/2): Which recommended skills would you like to install?
→ Type: /jarvis all (GSD + Superpowers + gstack)
→ Type: /jarvis gsd (GSD only — task execution)
→ Type: /jarvis superpowers (Superpowers only)
→ Type: /jarvis gstack (gstack only)
→ Type: /jarvis none (skip — use only what I have)
Stop here. Do not proceed further this run.
The install blocks (used by the pre-check in Step 0 when PENDING="deps") are:
Run if answer is "all" or "gsd":
JARVIS_LOG=~/.claude/skills/jarvis/update.log
gsd --version 2>/dev/null && echo "GSD already installed" || { npm install -g get-shit-done 2>>"$JARVIS_LOG" && echo "✓ GSD installed" || echo "⚠ GSD install failed — run: npm install -g get-shit-done"; }
Run if answer is "all" or "superpowers":
JARVIS_LOG=~/.claude/skills/jarvis/update.log
if claude --version 2>/dev/null; then
claude plugin marketplace add obra/superpowers 2>>"$JARVIS_LOG" || true
claude plugin install superpowers@superpowers-dev 2>>"$JARVIS_LOG" && echo "✓ Superpowers installed" || echo "⚠ Superpowers install failed"
else
echo "⚠ Claude Code CLI not found — install it first: https://claude.ai/code"
fi
Run if answer is "all" or "gstack":
JARVIS_LOG=~/.claude/skills/jarvis/update.log
GSTACK_DIR=~/.claude/skills/gstack
if [ -f "$GSTACK_DIR/setup" ]; then
echo "gstack already installed"
elif git --version 2>/dev/null; then
git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git "$GSTACK_DIR" 2>>"$JARVIS_LOG" \
&& (cd "$GSTACK_DIR" && ./setup 2>>"$JARVIS_LOG") \
&& echo "✓ gstack installed" || echo "⚠ gstack install failed — see $JARVIS_LOG"
else
echo "⚠ git not found — install git first"
fi
If answer is "none": skip all install blocks above.
Run this last ONLY when a valid selection was made — marks deps as asked so this never fires again:
node -e "var fs=require('fs'),path=require('path'),os=require('os');var p=path.join(os.homedir(),'.claude','skills','jarvis','config.json');var c;try{c=JSON.parse(fs.readFileSync(p,'utf8'));}catch(e){c={auto_update:false,last_check:0,deps_asked:false,pending:null};}c.deps_asked=true;c.pending=null;fs.writeFileSync(p,JSON.stringify(c,null,2));console.log('saved deps_asked=true');" 2>>~/.claude/skills/jarvis/update.log
AUTO_UPDATE is "true" AND HOURS_SINCE ≥ 24JARVIS_LOG=~/.claude/skills/jarvis/update.log
UPDATED_LIST=""
# ── Node.js semver comparison helper ──
# Returns "yes" if $2 is strictly newer than $1, "no" otherwise.
# Handles pre-release tags correctly (strips them for core comparison).
_semver_newer() {
node -e "
var p=function(v){return v.replace(/^[^0-9]*/,'').split(/[.-]/).map(function(x){return parseInt(x)||0;});};
var a=p(process.argv[1]),b=p(process.argv[2]);
for(var i=0;i<Math.max(a.length,b.length);i++){
if((b[i]||0)>(a[i]||0)){process.stdout.write('yes');process.exit();}
if((b[i]||0)<(a[i]||0)){process.stdout.write('no');process.exit();}
}
process.stdout.write('no');
" "$1" "$2" 2>/dev/null
}
# Update jarvis itself
JARVIS_LATEST=$(npm show claude-jarvis version 2>>"$JARVIS_LOG" || true)
JARVIS_CURRENT=$(npm list -g claude-jarvis --depth=0 2>>"$JARVIS_LOG" | grep -o 'claude-jarvis@[^ ]*' | grep -o '@.*' | tr -d '@' || true)
if [ -n "$JARVIS_LATEST" ] && [ -n "$JARVIS_CURRENT" ] && [ "$(_semver_newer "$JARVIS_CURRENT" "$JARVIS_LATEST")" = "yes" ]; then
npm install -g claude-jarvis 2>>"$JARVIS_LOG" && UPDATED_LIST="$UPDATED_LIST claude-jarvis"
fi
# Update GSD
GSD_LATEST=$(npm show get-shit-done version 2>>"$JARVIS_LOG" || true)
GSD_CURRENT=$(npm list -g get-shit-done --depth=0 2>>"$JARVIS_LOG" | grep -o 'get-shit-done@[^ ]*' | grep -o '@.*' | tr -d '@' || true)
if [ -n "$GSD_LATEST" ] && [ -n "$GSD_CURRENT" ] && [ "$(_semver_newer "$GSD_CURRENT" "$GSD_LATEST")" = "yes" ]; then
npm install -g get-shit-done 2>>"$JARVIS_LOG" && UPDATED_LIST="$UPDATED_LIST GSD"
fi
# Update Superpowers
claude plugin update superpowers 2>>"$JARVIS_LOG" && UPDATED_LIST="$UPDATED_LIST Superpowers" || true
# Update gstack — explicit subshell, cwd never leaks
if [ -d ~/.claude/skills/gstack/.git ]; then
(cd ~/.claude/skills/gstack && git pull --quiet 2>>"$JARVIS_LOG" && ./setup --quiet 2>>"$JARVIS_LOG") \
&& UPDATED_LIST="$UPDATED_LIST gstack" || true
fi
# Stamp last check time — preserves auto_update on error (no bare except: losing keys)
node -e "
var fs=require('fs'),path=require('path'),os=require('os');
var p=path.join(os.homedir(),'.claude','skills','jarvis','config.json');
var config;
try{config=JSON.parse(fs.readFileSync(p,'utf8'));}catch(e){config={auto_update:null,last_check:0};}
config.last_check=Math.floor(Date.now()/1000);
fs.writeFileSync(p,JSON.stringify(config,null,2));
" 2>>"$JARVIS_LOG"
[ -n "$UPDATED_LIST" ] && echo "↑ updated:$UPDATED_LIST — you're welcome."
AUTO_UPDATE is "false" OR HOURS_SINCE < 24Skip entirely. Move to Step 1.
git branch --show-current 2>/dev/null || echo "no git"
[ -d .planning ] && head -20 .planning/STATE.md 2>/dev/null || echo "no .planning"
Use $SUPERPOWERS_BASE from Step 0 for all Superpowers paths.
If $SUPERPOWERS_BASE is empty, skip every row that uses it and fall through to Step 3.
Conflict rules (apply before matching):
| Intent signals | Skill | Path |
|---|---|---|
| "debug", "fix", "broken", "error", "failing", "exception", "crash", "why is X not", "stopped working" | systematic-debugging | $SUPERPOWERS_BASE/skills/systematic-debugging/SKILL.md |
| "investigate", "root cause", "trace", "logs show", "diagnose" | investigate | ~/.claude/skills/investigate/SKILL.md |
| Intent signals | Skill | Path |
|---|---|---|
| "build", "implement", "create", "add" + code/feature/API/endpoint/system/module/component + complex/unclear scope | brainstorm | $SUPERPOWERS_BASE/commands/brainstorm.md |
| "build", "implement", "add" + code/feature/function/script/endpoint + clear/simple/known | gsd-quick | ~/.claude/skills/gsd-quick/SKILL.md |
| "refactor", "rewrite", "restructure" a non-trivial codebase/system/module | brainstorm | $SUPERPOWERS_BASE/commands/brainstorm.md |
| Intent signals | Skill | Path |
|---|---|---|
| "plan", "design", "figure out how to", "what's the best way", "architect" | brainstorm | $SUPERPOWERS_BASE/commands/brainstorm.md |
| "add a phase", "new phase", "next phase", "track this as a phase" | gsd-add-phase | ~/.claude/skills/gsd-add-phase/SKILL.md |
| "plan this phase", "make a plan for" (phase already exists) | gsd-plan-phase | ~/.claude/skills/gsd-plan-phase/SKILL.md |
| Intent signals | Skill | Path |
|---|---|---|
| "execute", "run the plan", "do the phase", "start working on phase" | gsd-execute-phase | ~/.claude/skills/gsd-execute-phase/SKILL.md |
| "run all phases", "do everything", "autonomous", "just do it all" | gsd-autonomous | ~/.claude/skills/gsd-autonomous/SKILL.md |
| "write tests", "add tests", "test coverage", "TDD" | test-driven-development | $SUPERPOWERS_BASE/skills/test-driven-development/SKILL.md |
| Intent signals | Skill | Path |
|---|---|---|
| "ship", "push", "PR", "deploy", "merge", "create pull request" | ship | ~/.claude/skills/ship/SKILL.md |
| "review", "code review", "check my code", "audit this diff" | review | ~/.claude/skills/review/SKILL.md |
| "verify", "is this done", "check if it works", "validate" | verification-before-completion | $SUPERPOWERS_BASE/skills/verification-before-completion/SKILL.md |
| Intent signals | Skill | Path |
|---|---|---|
| "where are we", "status", "progress", "what's left", "catch me up" | gsd-progress | ~/.claude/skills/gsd-progress/SKILL.md |
| "manage phases", "dashboard", "coordinate", multiple phases active | gsd-manager | ~/.claude/skills/gsd-manager/SKILL.md |
| "new project", "start from scratch", "initialize project" | gsd-new-project | ~/.claude/skills/gsd-new-project/SKILL.md |
| "health", "quality", "linting", "code quality check" | health | ~/.claude/skills/health/SKILL.md |
| Intent signals | Skill | Path |
|---|---|---|
| "browse", "open", "check site", "go to URL", "test the frontend" | browse | ~/.claude/skills/browse/SKILL.md |
| "QA", "test the app", "find bugs in the UI", "click through" | qa | ~/.claude/skills/qa/SKILL.md |
Only runs if Step 2 matched nothing.
# Depth-limited scan to prevent hangs on deep trees or symlink loops
for skill_file in ~/.claude/skills/*/SKILL.md; do
[ -f "$skill_file" ] || continue
skill_name=$(basename "$(dirname "$skill_file")")
desc=$(awk '/^---/{f=!f; next} f && /^description:/{found=1; sub(/^description:\s*[|>]?\s*/, ""); print; next} found && /^ /{print; next} found{exit}' "$skill_file" 2>/dev/null | head -3 | tr '\n' ' ' | sed 's/ */ /g')
[ -n "$desc" ] && echo "SKILL: $skill_name | $desc"
done
for skill_file in ~/.claude/plugins/cache/*/*/skills/*/SKILL.md; do
[ -f "$skill_file" ] || continue
skill_name=$(basename "$(dirname "$skill_file")")
desc=$(awk '/^---/{f=!f; next} f && /^description:/{found=1; sub(/^description:\s*[|>]?\s*/, ""); print; next} found && /^ /{print; next} found{exit}' "$skill_file" 2>/dev/null | head -3 | tr '\n' ' ' | sed 's/ */ /g')
[ -n "$desc" ] && echo "PLUGIN-SKILL: $skill_name | $desc"
done
If the scan returns zero results, tell the user:
"No skills found. Make sure GSD, gstack, or Superpowers are installed. Run:
npm install -g claude-jarvisto reinstall." Then stop — do not attempt to route.
If results are returned, pick the skill whose description best matches the intent:
# Depth-limited find to prevent hangs
find ~/.claude/skills ~/.claude/plugins/cache -maxdepth 6 -name "SKILL.md" \
-path "*/<CHOSEN_SKILL_NAME>/SKILL.md" 2>/dev/null | head -1
Print exactly one line, then execute immediately:
→ [skill-name]: [reason in ≤8 words] — [roast]
If discovered dynamically, add (discovered):
→ [skill-name] (discovered): [reason] — [roast]
Every routing line ends with a friendly roast after the em dash. Be specific to what they asked, not generic. Think: a friend who thinks you're ridiculous for needing this and loves you for it.
Debugging / fixing:
Building something:
Planning:
Shipping:
Status / progress:
Review:
Autonomous / run everything:
Tests:
Discovered skill:
Pick the most contextually fitting one. If none fit, write a fresh one in the same voice. Under 12 words. Never mean, always affectionate.
SKILL_PATH="<resolved path from Step 2 or Step 3>"
if [ -z "$SKILL_PATH" ] || [ ! -f "$SKILL_PATH" ]; then
echo "ERROR: Skill not found at: ${SKILL_PATH:-<unresolved>}"
echo "Possible causes:"
echo " - Superpowers not installed (run: claude plugin install superpowers@superpowers-dev)"
echo " - GSD not installed (run: npm install -g get-shit-done)"
echo " - Skill was deleted or moved"
echo "Check ~/.claude/skills/jarvis/update.log for more details."
exit 1
fi
If the file exists, read it fully and follow its instructions as if the user had invoked it directly. Pass the user's original message as $ARGUMENTS.
.planning/ → prefer GSD skills.planning/ → prefer Superpowers skills$SUPERPOWERS_BASE empty → skip all Superpowers rows, go to Step 3