/ccc-update — Update CC Commander
CC Commander · /ccc-update · Detect → update → restart → verify
The end-user counterpart to /ccc-doctor (which also detects staleness, but
as one row in a bigger diagnostic report) — this skill does ONE thing:
get the user onto the latest version, correctly, in the fewest steps.
Looking for the vendor-submodule updater instead (
vendor/*git submodules under this repo, maintainer-only)? That's/ccc-upgrade— a different tool with a similarly-spelled name. This skill (/ccc-update) is for end users updating the installed plugin itself.
Step 1 — Detect installed vs latest
CLONE="$HOME/.claude/plugins/marketplaces/commander-hub"
if [ ! -d "$CLONE" ]; then
CLONE=$(node -e "
try {
const d=JSON.parse(require('fs').readFileSync(process.env.HOME+'/.claude/plugins/known_marketplaces.json','utf8'));
const m=d['commander-hub'];
if(m&&m.installLocation) process.stdout.write(m.installLocation);
} catch(e) {}
" 2>/dev/null || echo "")
fi
[ -z "$CLONE" ] && CLONE="n/a"
[ "$CLONE" != "n/a" ] && [ ! -d "$CLONE" ] && CLONE="n/a"
INSTALLED_JSON="$HOME/.claude/plugins/installed_plugins.json"
INSTALLED_VERSION="n/a"
if [ -f "$INSTALLED_JSON" ]; then
INSTALLED_VERSION=$(node -e "
try {
const d = JSON.parse(require('fs').readFileSync('$INSTALLED_JSON','utf8'));
const k = Object.keys(d.plugins||{}).find(k => k.startsWith('commander@'));
process.stdout.write(k && d.plugins[k][0] ? (d.plugins[k][0].version||'n/a') : 'n/a');
} catch(e) { process.stdout.write('n/a'); }
" 2>/dev/null || echo "n/a")
fi
# Desktop-managed (account-synced) install: Cowork/Claude Desktop provisions the
# plugin under Application Support per session — NO marketplace clone and NO
# installed_plugins.json entry exist in that mode. Detect it and read its version.
DESKTOP_VERSION="n/a"
if [ "$CLONE" = "n/a" ] && [ "$INSTALLED_VERSION" = "n/a" ]; then
DESKTOP_MANIFEST=$(/bin/ls -t "$HOME/Library/Application Support/Claude/"local-agent-mode-sessions/*/*/rpm/plugin_*/.claude-plugin/plugin.json 2>/dev/null | head -50 | while read -r f; do
if /usr/bin/grep -q '"name"[[:space:]]*:[[:space:]]*"commander"' "$f" 2>/dev/null; then echo "$f"; break; fi
done)
if [ -n "$DESKTOP_MANIFEST" ]; then
DESKTOP_VERSION=$(node -e "
try { process.stdout.write(JSON.parse(require('fs').readFileSync('$DESKTOP_MANIFEST','utf8')).version||'n/a'); }
catch(e) { process.stdout.write('n/a'); }
" 2>/dev/null || echo "n/a")
fi
fi
# Dev checkout: running inside the cc-commander repo itself (maintainer mode).
DEV_REPO="n/a"
if [ -f "package.json" ] && /usr/bin/grep -q '"name": "cc-commander"' package.json 2>/dev/null; then
DEV_REPO="$(pwd)"
fi
REMOTE_VERSION="n/a"
if [ "$CLONE" != "n/a" ]; then
REMOTE_VERSION=$(node "$CLONE/commander/update-check.js" --remote-only 2>/dev/null || echo "n/a")
else
# No clone to run the checker from — ask GitHub directly (raw manifest on main).
REMOTE_VERSION=$(curl -fsSL --max-time 8 "https://raw.githubusercontent.com/KevinZai/commander/main/commander/cowork-plugin/.claude-plugin/plugin.json" 2>/dev/null | node -e "
let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{try{const v=JSON.parse(s).version;process.stdout.write(/^\d+\.\d+\.\d+$/.test(v)?v:'n/a');}catch(e){process.stdout.write('n/a');}});
" 2>/dev/null || echo "n/a")
fi
echo "CLONE=$CLONE"
echo "INSTALLED_VERSION=$INSTALLED_VERSION"
echo "DESKTOP_VERSION=$DESKTOP_VERSION"
echo "DEV_REPO=$DEV_REPO"
echo "REMOTE_VERSION=$REMOTE_VERSION"
Route by install mode — there are three, and only the first uses Step 2:
| Mode | Signals | What to do |
|---|---|---|
| CLI marketplace | CLONE ≠ n/a or INSTALLED_VERSION ≠ n/a |
Continue to Step 2 (the marketplace-update sequence) |
| Desktop-managed | both n/a, DESKTOP_VERSION ≠ n/a |
No local clone exists by design — the Desktop app provisions the plugin from your claude.ai account. Report Installed: vDESKTOP_VERSION · Latest: vREMOTE_VERSION. If behind: update from Desktop → Settings → Plugin Marketplace (re-install/update the commander entry there), then fully quit (Cmd+Q) and relaunch. If Desktop keeps showing an old version after that, the known fix is: remove the plugin at claude.ai (web) → Cmd+Q → reinstall. Do NOT run the claude plugin marketplace CLI sequence — there is no clone for it to update in this mode. |
| Dev checkout | DEV_REPO ≠ n/a (and the others n/a) |
You're the maintainer running from the repo: git pull + restart the session. Version truth is package.json. |
If ALL of INSTALLED_VERSION, DESKTOP_VERSION, DEV_REPO are n/a: Commander genuinely isn't installed on this machine — point at the install guide (/plugin marketplace add KevinZai/commander or Desktop → Settings → Plugin Marketplace → Add from GitHub) rather than /ccc-doctor.
Report the result plainly: Installed: vX.Y.Z · Latest: vA.B.C.
- If
REMOTE_VERSIONisn/a: say you couldn't reach GitHub (offline or rate-limited) and stop — nothing else to do until network is back. - If
INSTALLED_VERSIONequalsREMOTE_VERSION(or is newer): say "You're already on the latest version — nothing to do" and stop. - Otherwise, continue to Step 2.
Step 2 — Confirm and run the update
Call AskUserQuestion:
question: "Update CC Commander vINSTALLED_VERSION -> vREMOTE_VERSION?"
header: "CC Commander Update"
multiSelect: false
options:
- label: "Update now"
description: "Runs the marketplace-update sequence, then tells you to restart."
preview: "claude plugin marketplace update commander-hub && claude plugin update commander"
- label: "Not now"
description: "Skip — you can run /ccc-update again anytime."
On "Update now", tell the user to run these two commands themselves (this
skill does NOT and cannot invoke /plugin commands on your behalf — they
are Claude Code CLI/Desktop built-ins, not something a skill can shell out
to):
/plugin marketplace update commander-hub
/plugin update commander
(CLI-only equivalent, if you're scripting: claude plugin marketplace update commander-hub && claude plugin update commander.)
Step 3 — Restart reminder
⚠️ A restart is required.
/plugin updatestages the new version — it does not hot-swap the running session. Fully quit Claude Code / Cowork Desktop (Cmd+Q, not just closing the window) and reopen, or start a new CLI session.
Step 4 — Verify after restart
Once the user confirms they've restarted, verify the installed runtime —
NOT the marketplace clone. A successful marketplace update with a
failed/unapplied plugin update leaves the clone showing the new version
while the old runtime keeps running; probing the clone would falsely declare
victory.
# The client's own record of what is INSTALLED (authoritative). The cache
# installPath IS the plugin source directly — no commander/cowork-plugin
# wrapper like the clone has.
RUNTIME_SRC=$(node -e "
try {
const j=JSON.parse(require('fs').readFileSync(process.env.HOME+'/.claude/plugins/installed_plugins.json','utf8'));
const rows=(j.plugins&&(j.plugins['commander@commander-hub']||j.plugins['commander']))||[];
const r=Array.isArray(rows)?rows[0]:rows;
if(r&&r.installPath) process.stdout.write(r.installPath);
} catch(e) {}
" 2>/dev/null || echo "")
{ [ -z "$RUNTIME_SRC" ] || [ ! -d "$RUNTIME_SRC" ]; } && RUNTIME_SRC="n/a"
NEW_VERSION="n/a"
[ "$RUNTIME_SRC" != "n/a" ] && NEW_VERSION=$(node -e "
try { process.stdout.write(JSON.parse(require('fs').readFileSync('$RUNTIME_SRC/.claude-plugin/plugin.json','utf8')).version||'n/a'); }
catch(e) { process.stdout.write('n/a'); }
" 2>/dev/null || echo "n/a")
echo "NEW_VERSION=$NEW_VERSION"
# Prove the hook chain in the INSTALLED runtime actually runs.
[ "$RUNTIME_SRC" != "n/a" ] && echo '{}' | node "$RUNTIME_SRC/hooks/session-end.js" | head -c 200
NEW_VERSION(the installed runtime) should now equalREMOTE_VERSIONfrom Step 1. If the CLONE shows the new version butNEW_VERSIONis still old, say exactly that: the marketplace refreshed but the plugin update didn't apply (or the app wasn't fully restarted) — re-runclaude plugin update commanderand restart again. If it doesn't, the update didn't apply — most likely cause: Desktop wasn't fully quit (a closed window keeps the old process, and the old process keeps the old plugin loaded). Point the user atinstall-recovery.mdxor/ccc-doctorfor the deeper cache-clearing sequence.- The hook pipe-test should print one JSON line (e.g.
{"continue":true,...}). No output or a crash means the updated clone's hooks aren't wired — again,/ccc-doctoris the next step.
Step 5 — Optional: never do this by hand again
If the user updates often, offer the one-time autoUpdate toggle (same
consent-gated flow /ccc-doctor offers — see that skill for the exact
backup + edit + verify sequence). Point them there rather than duplicating
the mutation logic in two places.
Anti-patterns — DO NOT
- Never tell the user to
git pull— there is no working tree in a marketplace install. - Never claim to have run
/plugin updateyourself — skills cannot invoke Claude Code CLI built-ins; always hand the exact commands to the user. - Never skip the restart reminder —
/plugin updatewithout a restart is the #1 cause of "I updated but nothing changed" reports. - Never claim success from Step 2 alone — verification (Step 4) is what actually confirms the update landed.
⚙️ Fable contract: plan before build · verifier ≠ worker · prove before alarm · loops need gates · leave durable state —
rules/fable-method.md