Skip to content

Make the Claude Code plugin self-contained: fall back to pip/uv-installed skillopt so no repo clone is needed #106

Description

@ichoosetoaccept

Make the Claude Code plugin self-contained: fall back to pip/uv-installed skillopt so no repo clone is needed

Goal

After /plugin marketplace add + /plugin install, the Claude Code plugin should work with no additional setup — no repo clone, no SKILLOPT_SLEEP_REPO env var. The plugin shell should auto-update via Claude Code's plugin marketplace, and the engine should auto-update via pip/uv. Two independent update channels, neither requiring manual git pull.

Current state

The plugin's runner (plugins/claude-code/scripts/run-sleep.sh, a bundled copy of the shared plugins/run-sleep.sh) resolves the engine by searching for a skillopt_sleep/ source directory on disk in four locations:

  1. Co-located ($SCRIPT_DIR/../skillopt_sleep)
  2. $CLAUDE_PLUGIN_ROOT/../../skillopt_sleep
  3. $SKILLOPT_SLEEP_REPO/skillopt_sleep
  4. Upward from $PWD

If none contains the source dir, it errors:

if [ -z "${REPO_ROOT:-}" ]; then
  echo "[sleep] ERROR: could not locate the skillopt_sleep package. Set SKILLOPT_SLEEP_REPO to the repo root." >&2
  exit 1
fi
cd "$REPO_ROOT"
exec "$PY" -m skillopt_sleep "$@"

There is no fallback to the pip/uv-installed package. The error message mentions "pip install skillopt," but pip install skillopt does not actually make the plugin work — run-sleep.sh's resolution logic never checks for the installed package.

This means the documented v0.2.0 install path (uv tool install skillopt / pip install skillopt for the engine, /plugin marketplace add + /plugin install for the shell) does not produce a working plugin. The user must also git clone the repo and either set SKILLOPT_SLEEP_REPO or rely on the upward-from-CWD search finding the source dir — which defeats the point of publishing the engine to PyPI and doesn't compose with Claude Code's plugin auto-update, since the engine lives outside the plugin's tracked source.

Issue #52 (fixed in PR #72) bundled run-sleep.sh inside the plugin so the runner itself is found after a marketplace install, but the runner's engine-resolution logic was not updated to fall back to the installed package. This issue is the remaining half of that fix.

Desired state

run-sleep.sh falls back to the installed engine before erroring:

  1. skillopt-sleep CLI on PATH — covers uv tool install skillopt, pipx install skillopt, pip install skillopt. All three put the skillopt-sleep console script on PATH.
  2. python -m skillopt_sleep when importable — covers pip install skillopt into the active Python (edge case where the console script isn't on PATH but the package is importable).

Fallback 1 must be checked before fallback 2, because uv tool install / pipx isolate the package from the system Python's import path — python -c "import skillopt_sleep" would fail even though the skillopt-sleep CLI is available.

The existing source-checkout resolution path stays unchanged — repo-clone users keep working as before. The fallbacks only trigger when no source dir is found.

Suggested fix

~10 lines in run-sleep.sh, before the existing error:

# Fallback 1: skillopt-sleep CLI on PATH (uv tool install / pipx / pip install)
if command -v skillopt-sleep >/dev/null 2>&1; then
  exec skillopt-sleep "$@"
fi

# Fallback 2: importable as a module (pip install into the active Python)
if "$PY" -c "import skillopt_sleep" >/dev/null 2>&1; then
  exec "$PY" -m skillopt_sleep "$@"
fi

echo "[sleep] ERROR: could not locate the skillopt_sleep package." >&2
echo "[sleep] Install it with 'uv tool install skillopt' or 'pip install skillopt'," >&2
echo "[sleep] or set SKILLOPT_SLEEP_REPO to a clone of the SkillOpt repo." >&2
exit 1

The cd "$REPO_ROOT" is dropped for the fallback paths — the engine takes --project explicitly and writes outputs relative to the project dir, not cwd.

Scope

The same run-sleep.sh logic is shared by the Codex plugin (plugins/codex/install.sh calls the shared plugins/run-sleep.sh), so the fix should land in both:

  • plugins/run-sleep.sh (shared, used by Codex)
  • plugins/claude-code/scripts/run-sleep.sh (bundled copy, byte-identical, used by the Claude Code marketplace install which fetches only the plugins/claude-code/ subtree)

Fixing both covers Claude Code and Codex in one change.

Out of scope:

  • OpenClaw uses a separate Python runner (plugins/openclaw/run_sleep.py), not the shell runner.
  • Devin and Copilot MCP plugins (plugins/devin/mcp_server.py, plugins/copilot/mcp_server.py) already handle the pip-installed case correctly — they set PYTHONPATH=REPO_ROOT as an addition and run python -m skillopt_sleep, which falls through to site-packages when the repo dir doesn't contain the package. The shell runner is the outlier.

Why this matters

  • The v0.2.0 release notes headline SkillOpt-Sleep as a PyPI-published CLI (skillopt-sleep), but the Claude Code plugin — the primary documented install path for Claude Code users — can't actually use that PyPI package. The two install paths are effectively decoupled.
  • Users who enable plugin auto-updates (a common Claude Code setting) get the shell updated automatically, but the engine still requires a manual git pull of a repo clone — an awkward split that surprises users who reasonably expect pip install + plugin install to be sufficient.
  • The Devin and Copilot MCP plugins already handle this correctly. The shell runner is the outlier.

Additional context

  • Verified against main as of 2026-07-06 (v0.2.0).
  • I have a PR ready that implements the suggested fix in both files, with shellcheck passing and the fallback paths verified against uv tool install skillopt.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions