diff --git a/install-plugins.sh b/install-plugins.sh index 1d14597..38c5e7f 100644 --- a/install-plugins.sh +++ b/install-plugins.sh @@ -582,6 +582,8 @@ else fi # ctx7 auth — detect, then offer login ONLY in an interactive TTY. A non-interactive # run (CI / headless / re-run) must never open a browser or block on OAuth. +# The test reads stdin alone: stdout is the tee pipe set up at the top of +# this script, never a terminal. if command -v ctx7 &>/dev/null; then # Deterministic offline oracle: ctx7's OAuth token lives here (XDG-aware). # Present => authenticated; absent => anonymous. No subprocess, no network, no browser. @@ -590,7 +592,7 @@ if command -v ctx7 &>/dev/null; then ok "ctx7 authenticated (full rate limits)" else info "ctx7 works anonymously — docs + library already usable, no auth required." - if [ -t 0 ] && [ -t 1 ]; then + if [ -t 0 ]; then # Interactive terminal: offer to log in now (opens a browser). printf '%b' "${BLUE}→${NC} Authenticate ctx7 now for higher rate limits? [y/N] " read -r ctx7_ans || ctx7_ans="" @@ -990,6 +992,76 @@ for _stray in "$REPO/.agents/skills" "$REPO/.claude/skills"; do done echo "" +# ============================================================ +# STEP 8.6 — HIGGSFIELD CLI + SKILL PACK +# ============================================================ +# `@higgsfield/cli` (bins `higgsfield`, `higgs`): image, video, audio and +# brand media generation from the terminal, one browser login, metered +# credits. Its skills come from github.com/higgsfield-ai/skills, cloned by +# lib/higgsfield-skills.sh into skills-external/higgsfield-* (gitignored). +# +# Nothing is linked here. The pack is OFF by default and belongs to no +# profile: `lib/toggle-external.sh enable higgsfield` turns the media skills +# on, `enable higgsfield-websites` the landing-page aid. Keeping it out of +# link.sh and of every profile is what stops a re-run from re-enabling it +# (BDR-093). This step runs before Step 8.7 so the effort pins are still +# re-applied after the last vendoring step (BDR-108). +echo "── Step 8.6: Higgsfield CLI + skill pack ───────────────────" +echo "" +# shellcheck source=lib/higgsfield-skills.sh disable=SC1091 +source "$REPO/lib/higgsfield-skills.sh" +HF_PKG="@higgsfield/cli" +# The package vendors its binary in a postinstall script that npm may hold +# back; this form lets that one script run. +HF_REMEDY="npm install -g --allow-scripts=${HF_PKG} ${HF_PKG}" + +# higgsfield_cli_ok, not `command -v`: the npm shim can sit on PATH with no +# binary behind it, and only a probe tells the two apart. +if higgsfield_cli_ok; then + ok "Higgsfield CLI already installed" +else + HF_VER=$(pinned_version "higgsfield") + [ "$HF_VER" = "latest" ] || HF_PKG="${HF_PKG}@${HF_VER}" + info "Installing ${HF_PKG} (version from plugins.lock.json: ${HF_VER})..." + npm install -g "$HF_PKG" || true + if higgsfield_cli_ok; then + ok "Higgsfield CLI installed" + else + err "Higgsfield CLI install failed — run manually: $HF_REMEDY" + fi +fi + +if higgsfield_cli_ok; then + # Skill pack — cloned to a stage, then moved under skills-external/. + if HF_N=$(higgsfield_sync_skills "$REPO"); then + ok "Higgsfield skill pack synced to skills-external/ ($HF_N skills)" + else + warn "Higgsfield skill pack sync failed — existing copies kept (check: git clone $HIGGSFIELD_SKILLS_URL)" + fi + + # Auth — offer the login only when stdin is a terminal: a non-interactive + # run (CI / headless) must never open a browser or block on OAuth. + if higgsfield_signed_in; then + ok "Higgsfield: signed in" + elif [ -t 0 ]; then + printf '%b' "${BLUE}→${NC} Sign in to Higgsfield now? (opens a browser) [y/N] " + read -r hf_ans || hf_ans="" + if [[ "$hf_ans" =~ ^[Yy]([Ee][Ss])?$ ]]; then + if higgsfield auth login; then + ok "Higgsfield authenticated" + else + warn "Higgsfield login did not finish — re-run 'higgsfield auth login' anytime" + fi + else + info "Skipped — sign in later with: higgsfield auth login" + fi + else + info "Not signed in. Generation needs: higgsfield auth login" + fi + info "Pack is off by default — enable: bash lib/toggle-external.sh enable higgsfield" +fi +echo "" + # ============================================================ # STEP 8.7 — 21ST.DEV CLI + SKILL PACK # ============================================================ @@ -1065,13 +1137,13 @@ apply_effort_pins "$REPO" || warn "effort pins: map lines rejected — fix lib/e # Auth — detect, then offer login ONLY in an interactive TTY. A non-interactive # run (CI / headless / re-run) must never open a browser or block on OAuth. # Search and logo lookup are free; retrieving component code and 21st AI need -# the session. Mirrors the ctx7 auth block (Step 6). +# the session. Mirrors the ctx7 auth block (Step 6), stdin-only test included. if command -v 21st &>/dev/null; then # `whoami` is a local token read (no network): "Logged in as (saved …)." TFD_WHO="$(21st whoami 2>/dev/null | head -1)" if [[ "$TFD_WHO" == "Logged in as "* ]]; then ok "21st: ${TFD_WHO%.}" - elif [ -t 0 ] && [ -t 1 ]; then + elif [ -t 0 ]; then printf '%b' "${BLUE}→${NC} Sign in to 21st now? (opens a browser) [y/N] " read -r tfd_ans || tfd_ans="" if [[ "$tfd_ans" =~ ^[Yy]([Ee][Ss])?$ ]]; then @@ -1236,6 +1308,7 @@ echo " 🔄 agent-skills trio — observability-and-instrumentation, deprec echo " 🔄 mengto scroll skills — scroll-world-storytelling, build-threejs-scroll-worlds, scroll-scrubbed-visual-sequence, scroll-scrubbed-word-reveal, scroll-progress-timeline (curl → symlink, pinned commit)" echo " 🔄 darwin-skill — autonomous skill optimizer (npx skills, ~/.agents/skills/)" echo " 🔄 21st skill pack — 21st.dev CLI skills; design ones follow the profile (full by default), publishing ones on demand (toggle: lib/toggle-external.sh enable 21st)" +echo " 🔄 higgsfield pack — Higgsfield CLI media skills (image, video, audio, brand), OFF by default (toggle: lib/toggle-external.sh enable higgsfield; landing-page aid: enable higgsfield-websites)" echo "" echo " All plugins installed at: user scope (~/.claude/plugins/)" echo " GStack skills symlinked individually into ~/.claude/skills/ (→ submodule)" diff --git a/lib/tests/higgsfield.test.sh b/lib/tests/higgsfield.test.sh index 4ba5d82..1d6ebf4 100644 --- a/lib/tests/higgsfield.test.sh +++ b/lib/tests/higgsfield.test.sh @@ -312,5 +312,29 @@ expect profiles \ expect pins-map "$(count lib/effort-pins.txt higgsfield)" 0 verdict OFF_BY_DEFAULT_WIRING +# ln_first / ln_last — line number of a match. +ln_first() { grep -nF -- "$2" "$ROOT/$1" | head -1 | cut -d: -f1; } +ln_last() { grep -nF -- "$2" "$ROOT/$1" | tail -1 | cut -d: -f1; } +# shellcheck disable=SC2016 # a literal to grep for, not an expansion +PINS='apply_effort_pins "$REPO"' + +# install-plugins.sh: the sync sits in Step 8.6, before the effort pins +# (BDR-108); the CLI is proven by a probe, not by its shim; every login +# offer tests stdin alone (stdout is the tee pipe). +sync_ln="$(ln_last install-plugins.sh 'higgsfield_sync_skills')" +expect after-8.5 "$(yn test "$sync_ln" -gt \ + "$(ln_first install-plugins.sh 'Step 8.5: External skills')")" yes +expect before-8.7 "$(yn test "$sync_ln" -lt \ + "$(ln_first install-plugins.sh 'Step 8.7: 21st.dev')")" yes +expect before-pins "$(yn test "$sync_ln" -lt \ + "$(ln_last install-plugins.sh "$PINS")")" yes +expect probe-gates \ + "$(yn test "$(count install-plugins.sh 'if higgsfield_cli_ok')" -ge 3)" yes +expect control "$(echo 'if [ -t 0 ] && [ -t 1 ]; then' | grep -cF -- '-t 1')" 1 +expect no-stdout-test "$(count install-plugins.sh '-t 1')" 0 +expect stdin-tests \ + "$(yn test "$(count install-plugins.sh '[ -t 0 ]')" -ge 3)" yes +verdict INSTALL_WIRING + # ── tally ─────────────────────────────────────────────────── printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]