From 18db3e467f97342447f124490d87af0a4c0f2ce2 Mon Sep 17 00:00:00 2001 From: Yi Lu Date: Tue, 28 Apr 2026 23:03:17 -0300 Subject: [PATCH 1/3] chore: gate release on npm auth before bump/commit/tag Add check-npm-auth target that runs npm whoami and falls back to interactive npm login if needed. Wire it into release between check-clean and bump so an unauthenticated release fails fast, before any version files are edited or commits/tags are created. --- Makefile | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 798e11d..8c6b7d1 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ # - git (for the release flow) .PHONY: help bump release publish publish-npm publish-pypi publish-dry \ - check-version check-clean ensure-remote-reflexio unskip-worktree + check-version check-clean check-npm-auth ensure-remote-reflexio unskip-worktree VERSION_FILES := package.json plugin/pyproject.toml \ plugin/.claude-plugin/plugin.json .claude-plugin/marketplace.json \ @@ -36,6 +36,14 @@ check-clean: @git diff --quiet && git diff --cached --quiet \ || { echo "error: working tree is dirty — commit or stash first" >&2; exit 1; } +check-npm-auth: ## Verify npm login; run `npm login` interactively if not authenticated + @if npm whoami >/dev/null 2>&1; then \ + echo "→ npm: logged in as $$(npm whoami)"; \ + else \ + echo "→ npm: not logged in, running npm login"; \ + npm login || { echo "error: npm login failed" >&2; exit 1; }; \ + fi + unskip-worktree: ## Clear skip-worktree on plugin/pyproject.toml and plugin/uv.lock so release edits land in git @echo "→ clearing skip-worktree on $(PYPROJECT) $(LOCK_FILES)" @git update-index --no-skip-worktree $(PYPROJECT) $(LOCK_FILES) 2>/dev/null || true @@ -87,7 +95,7 @@ publish-dry: unskip-worktree ensure-remote-reflexio ## Show what would be publis publish: publish-npm publish-pypi ## Publish to both npm and PyPI -release: check-version check-clean bump ## Bump + commit + tag + publish + push +release: check-version check-clean check-npm-auth bump ## Bump + commit + tag + publish + push @echo "→ committing release v$(VERSION)" git add $(VERSION_FILES) $(LOCK_FILES) git commit -m "Release v$(VERSION)" From cea060845cac21cce736d9b193d0ce54efe41dd5 Mon Sep 17 00:00:00 2001 From: Yi Lu Date: Sun, 3 May 2026 09:18:52 -0700 Subject: [PATCH 2/3] fix: route slash commands through cli.sh wrapper so uv is on PATH Claude Code runs the `!` bash directives in slash command .md files in a non-interactive, non-login shell that does not source ~/.zshrc, so a freshly-installed `uv` at ~/.local/bin is invisible until the user re-sources their shell rc. The four slash commands now go through a new plugin/scripts/cli.sh wrapper that bootstraps PATH the same way hook_entry.sh does, and surfaces ~/.claude-smart/install-failed when the Setup hook recorded a broken install. Also rename the adapter's two internal client calls from search_profiles to search_user_profiles to match the canonical method on ReflexioClient (the old name is deprecated upstream), and bump the reflexio submodule pointer to 0c1eced where that rename landed. --- plugin/commands/clear-all.md | 4 +-- plugin/commands/learn.md | 4 +-- plugin/commands/restart.md | 4 +-- plugin/commands/show.md | 4 +-- plugin/scripts/cli.sh | 39 +++++++++++++++++++++ plugin/src/claude_smart/reflexio_adapter.py | 8 ++--- reflexio | 2 +- 7 files changed, 52 insertions(+), 13 deletions(-) create mode 100755 plugin/scripts/cli.sh diff --git a/plugin/commands/clear-all.md b/plugin/commands/clear-all.md index 752da1d..67d2338 100644 --- a/plugin/commands/clear-all.md +++ b/plugin/commands/clear-all.md @@ -1,8 +1,8 @@ --- description: Delete ALL reflexio interactions, profiles, and user playbooks (destructive) -allowed-tools: Bash(uv run:*) +allowed-tools: Bash(bash:*) --- Run this bash command and show its output verbatim: -!`uv run --project "$HOME/.reflexio/plugin-root" --quiet python -m claude_smart.cli clear-all --yes` +!`bash "$HOME/.reflexio/plugin-root/scripts/cli.sh" clear-all --yes` diff --git a/plugin/commands/learn.md b/plugin/commands/learn.md index 890a0e1..28bd2e1 100644 --- a/plugin/commands/learn.md +++ b/plugin/commands/learn.md @@ -1,10 +1,10 @@ --- description: Flag the last turn as a correction so reflexio learns from it -allowed-tools: Bash(uv run:*) +allowed-tools: Bash(bash:*) argument-hint: [note] --- Flag the user's previous turn as a correction and force reflexio to run extraction on this session's interactions now. Run the bash command below and show its output verbatim. -!`uv run --project "$HOME/.reflexio/plugin-root" --quiet python -m claude_smart.cli learn "$ARGUMENTS"` +!`bash "$HOME/.reflexio/plugin-root/scripts/cli.sh" learn "$ARGUMENTS"` diff --git a/plugin/commands/restart.md b/plugin/commands/restart.md index e01c640..5cf58b9 100644 --- a/plugin/commands/restart.md +++ b/plugin/commands/restart.md @@ -1,8 +1,8 @@ --- description: Restart the reflexio backend and dashboard to pick up new changes -allowed-tools: Bash(uv run:*) +allowed-tools: Bash(bash:*) --- Run this bash command and show its output verbatim: -!`uv run --project "$HOME/.reflexio/plugin-root" --quiet python -m claude_smart.cli restart` +!`bash "$HOME/.reflexio/plugin-root/scripts/cli.sh" restart` diff --git a/plugin/commands/show.md b/plugin/commands/show.md index f3ae4d3..84478c2 100644 --- a/plugin/commands/show.md +++ b/plugin/commands/show.md @@ -1,8 +1,8 @@ --- description: Show the current project playbook and session user profiles -allowed-tools: Bash(uv run:*) +allowed-tools: Bash(bash:*) --- Run this bash command and show its output verbatim: -!`uv run --project "$HOME/.reflexio/plugin-root" --quiet python -m claude_smart.cli show` +!`bash "$HOME/.reflexio/plugin-root/scripts/cli.sh" show` diff --git a/plugin/scripts/cli.sh b/plugin/scripts/cli.sh new file mode 100755 index 0000000..c23914c --- /dev/null +++ b/plugin/scripts/cli.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Wrapper for slash commands that invoke the claude_smart CLI via uv. +# Claude Code runs `!` bash directives in slash command .md files in a +# non-interactive, non-login shell that does NOT source ~/.zshrc or +# ~/.bash_profile. As a result, binaries installed by smart-install.sh +# at ~/.local/bin (e.g. uv from the astral.sh installer) are invisible +# to those directives until the user manually re-sources their shell rc. +# This wrapper bootstraps PATH the same way hook_entry.sh does so the +# slash commands work on a fresh install. +set -eu + +HERE="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=_lib.sh +. "$HERE/_lib.sh" +claude_smart_source_login_path +claude_smart_prepend_astral_bins + +PLUGIN_ROOT="$(cd "$HERE/.." && pwd)" + +# If the Setup hook recorded an install failure, surface that reason +# instead of falling through to a generic "uv not found" — mirrors the +# branch at hook_entry.sh so slash commands and hooks behave consistently +# on a broken install. +FAILURE_MARKER="$HOME/.claude-smart/install-failed" +if [ -f "$FAILURE_MARKER" ]; then + msg="$(cat "$FAILURE_MARKER" 2>/dev/null || echo "")" + [ -n "$msg" ] || msg="unknown error" + echo "claude-smart is not installed correctly: $msg" >&2 + echo "Re-run the plugin's Setup (restart Claude Code) or fix the underlying issue and delete $FAILURE_MARKER to retry." >&2 + exit 1 +fi + +if ! command -v uv >/dev/null 2>&1; then + echo "claude-smart: 'uv' not found on PATH." >&2 + echo "Install it from https://docs.astral.sh/uv/ or restart Claude Code so the Setup hook can install it." >&2 + exit 1 +fi + +exec uv run --project "$PLUGIN_ROOT" --quiet python -m claude_smart.cli "$@" diff --git a/plugin/src/claude_smart/reflexio_adapter.py b/plugin/src/claude_smart/reflexio_adapter.py index c11eb25..323b733 100644 --- a/plugin/src/claude_smart/reflexio_adapter.py +++ b/plugin/src/claude_smart/reflexio_adapter.py @@ -192,13 +192,13 @@ def fetch_project_profiles(self, project_id: str, top_k: int = 20) -> list[Any]: if client is None: return [] try: - response = client.search_profiles( + response = client.search_user_profiles( user_id=project_id, query="", top_k=top_k, ) except Exception as exc: # noqa: BLE001 - _LOGGER.debug("search_profiles failed: %s", exc) + _LOGGER.debug("search_user_profiles failed: %s", exc) return [] return _extract_items(response, "user_profiles") @@ -250,13 +250,13 @@ def search_profiles( if client is None: return [] try: - response = client.search_profiles( + response = client.search_user_profiles( user_id=project_id, query=query, top_k=top_k, ) except Exception as exc: # noqa: BLE001 - _LOGGER.debug("search_profiles failed: %s", exc) + _LOGGER.debug("search_user_profiles failed: %s", exc) return [] return _extract_items(response, "user_profiles") diff --git a/reflexio b/reflexio index 9143d1b..0c1eced 160000 --- a/reflexio +++ b/reflexio @@ -1 +1 @@ -Subproject commit 9143d1bf74584d0ecced69216c5b50773363c8e0 +Subproject commit 0c1eced05f512463de7f7e9237658784f3415d20 From 688df8c81f5fd9ed23d21450b48da7821670ad83 Mon Sep 17 00:00:00 2001 From: Yi Lu Date: Sun, 3 May 2026 09:34:31 -0700 Subject: [PATCH 3/3] fix(release): require NPM_TOKEN or npm whoami; never invoke interactive npm login --- Makefile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 8c6b7d1..eb0c1dc 100644 --- a/Makefile +++ b/Makefile @@ -36,12 +36,14 @@ check-clean: @git diff --quiet && git diff --cached --quiet \ || { echo "error: working tree is dirty — commit or stash first" >&2; exit 1; } -check-npm-auth: ## Verify npm login; run `npm login` interactively if not authenticated - @if npm whoami >/dev/null 2>&1; then \ +check-npm-auth: ## Verify npm auth via NPM_TOKEN or `npm whoami`; fail if neither is available + @if [ -n "$$NPM_TOKEN" ]; then \ + echo "→ npm: NPM_TOKEN is set"; \ + elif npm whoami >/dev/null 2>&1; then \ echo "→ npm: logged in as $$(npm whoami)"; \ else \ - echo "→ npm: not logged in, running npm login"; \ - npm login || { echo "error: npm login failed" >&2; exit 1; }; \ + echo "error: not authenticated via npm whoami and NPM_TOKEN is not set; set NPM_TOKEN for CI or run npm login locally" >&2; \ + exit 1; \ fi unskip-worktree: ## Clear skip-worktree on plugin/pyproject.toml and plugin/uv.lock so release edits land in git