fix(higgsfield): report upstream drift on every enable; final review fixes

This commit is contained in:
bastien
2026-09-30 16:35:45 +02:00
parent 142f73b08e
commit 4c7db8893b
6 changed files with 63 additions and 28 deletions
+1 -1
View File
@@ -401,7 +401,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
`verification-before-completion` to the verifier gates.
### Security
- `settings.json` `permissions.deny` now refuses `npm i -g`, `npm install --global` and `npm i --global`: the rule matched `npm install -g` only, so the other spellings of the same global install went through.
- `settings.json` `permissions.deny` now refuses three more spellings of a global npm install (`npm i -g`, `npm install --global`, `npm i --global`): the rule matched `npm install -g` only. Not a complete list: forms with the flag after the package name, such as `npm i <pkg> -g`, still pass.
- **Ten secret-reader deny rules added**: `sed`, `awk`, `cut`, `tr`,
`sort`, `uniq`, `diff`, `od`, `xxd`, `strings` against `.env*`. Six of
those tools sat in `permissions.allow`, so reading a `.env` through
+9 -4
View File
@@ -380,11 +380,12 @@ bash lib/toggle-external.sh disable higgsfield
`higgsfield` links a fixed list of seven media skills: generate, soul-id,
product-photoshoot, brandkit, marketplace-cards, video-explainer and
youtube-thumbnail. The list is `HIGGSFIELD_MEDIA_SKILLS` in
`lib/toggle-external.sh`. A skill that upstream adds later is synced and
reported, and stays unlinked until it is added there.
`lib/toggle-external.sh`. A skill that upstream adds later is synced, and
every `enable higgsfield` names it, the pack being on or not. It stays
unlinked until it is added to the list.
`higgsfield-websites` is kept apart. Here it helps with landing pages inside
the design stack (assets, references), and `higgsfield website
`higgsfield-websites` is kept apart. It helps with landing pages inside the
design stack (assets, references), and `higgsfield website
create|deploy|publish` stays unused. Claude enables either toggle itself on
an explicit ask (Skill routing in `CLAUDE.global.md`) and checks the price
with `higgsfield generate cost` before a paid run.
@@ -393,6 +394,10 @@ The skills are cloned, not installed with `npx skills add`: that installer
links every skill into `~/.claude/skills` on each refresh, which would undo
the off-by-default state.
After the first login, select a workspace once: `higgsfield workspace list`,
then `higgsfield workspace set <id>`. Until then the account commands answer
"No workspace selected", even though the session is active.
The package ships its binary through a postinstall script. If npm holds that
script back, `higgsfield` exists on PATH and fails at once; reinstall with
`npm install -g --allow-scripts=@higgsfield/cli @higgsfield/cli`.
+11 -7
View File
@@ -59,15 +59,19 @@ higgsfield_sync_skills() {
}
# _higgsfield_probe <args...>
# Run `higgsfield <args>` silently, 15 s at most when `timeout` exists. The
# CLI is closed source: a probe must never hang an installer, and what it
# prints (a token, for `auth token`) must never reach a terminal or a log.
# Run `higgsfield <args>` silently, 15 s at most when a timeout tool exists
# (`timeout`, or `gtimeout` from Homebrew coreutils on macOS). The CLI is
# closed source: a probe must never hang an installer, and what it prints
# (a token, for `auth token`) must never reach a terminal or a log.
_higgsfield_probe() {
if command -v timeout >/dev/null 2>&1; then
timeout 15 higgsfield "$@" </dev/null >/dev/null 2>&1
else
higgsfield "$@" </dev/null >/dev/null 2>&1
local tool
for tool in timeout gtimeout; do
if command -v "$tool" >/dev/null 2>&1; then
"$tool" 15 higgsfield "$@" </dev/null >/dev/null 2>&1
return
fi
done
higgsfield "$@" </dev/null >/dev/null 2>&1
}
# higgsfield_cli_ok — 0 when the binary answers. `command -v` alone only
+12
View File
@@ -110,6 +110,7 @@ probe() {
# ── sync ────────────────────────────────────────────────────
UP="$WORK/upstream"; mk_upstream "$UP"
# The repo path carries a space on purpose: every expansion must be quoted.
R1="$WORK/r 1"; mkdir -p "$R1/skills" "$R1/skills-disabled"
EXT="$R1/skills-external"
@@ -162,6 +163,12 @@ expect shim-only \
"$(FAKE_HF_BINARY=missing probe "$BIN:$PATH" higgsfield_cli_ok)" "rc=1"
expect no-cli "$(probe "$CLEAN" higgsfield_cli_ok)" "rc=127"
expect no-timeout "$(probe "$BIN:$CLEAN" higgsfield_cli_ok)" "rc=0"
# macOS spelling: only `gtimeout` exists. A wrapper, not a symlink: a
# multi-call coreutils binary dispatches on the name it is invoked under.
GT="$WORK/gtbin"; mkdir -p "$GT"
printf '#!/bin/sh\nexec %s "$@"\n' "$(command -v timeout)" > "$GT/gtimeout"
chmod +x "$GT/gtimeout"
expect gtimeout "$(probe "$BIN:$GT:$CLEAN" higgsfield_signed_in)" "rc=0"
verdict PROBES_SILENT
# ── toggle ──────────────────────────────────────────────────
@@ -227,6 +234,11 @@ expect brandkit-off "$(yn test -e "$F8/skills/higgsfield-brandkit")" no
expect_has reported "$out" "higgsfield-newcomer"
expect_not noskill-quiet "$out" "higgsfield-noskill"
expect links "$(entries "$F8/skills")" 2
# Enabled is the steady state: a re-run must still name the drift.
out="$(tog "$F8" enable higgsfield)"; rc=$?
expect again-rc "$rc" 0
expect_has again-state "$out" "higgsfield already enabled"
expect_has again-reported "$out" "higgsfield-newcomer"
verdict UNLISTED_NOT_LINKED
F3="$WORK/f3"; mk_toggle_fx "$F3" "${PACK[@]}"
+16 -9
View File
@@ -113,17 +113,21 @@ pack_skills() {
esac
}
# bounded <cmd...> — run a CLI probe silently, 15 s at most when `timeout`
# exists: a closed-source binary must never hang a toggle, and what it
# prints (a token) must never reach the terminal. Twin of
# _higgsfield_probe in lib/higgsfield-skills.sh, kept here because this
# script takes no extra `source` (the fixture suites copy it alone).
# bounded <cmd...> — run a CLI probe silently, 15 s at most when a timeout
# tool exists (`timeout`, or `gtimeout` from Homebrew coreutils on macOS):
# a closed-source binary must never hang a toggle, and what it prints (a
# token) must never reach the terminal. Twin of _higgsfield_probe in
# lib/higgsfield-skills.sh, kept here because this script takes no extra
# `source` (the fixture suites copy it alone).
bounded() {
if command -v timeout >/dev/null 2>&1; then
timeout 15 "$@" </dev/null >/dev/null 2>&1
else
"$@" </dev/null >/dev/null 2>&1
local tool
for tool in timeout gtimeout; do
if command -v "$tool" >/dev/null 2>&1; then
"$tool" 15 "$@" </dev/null >/dev/null 2>&1
return
fi
done
"$@" </dev/null >/dev/null 2>&1
}
# Post-enable notes for a pack. Its skills shell out to a CLI: without it
@@ -322,6 +326,9 @@ enable_tool() {
return 1
fi
warn "$tool already enabled"
# Enabled is the steady state, and Claude re-runs this on every
# media ask: the hints (upstream drift, CLI, session) show here too.
if [ "$tool" = "higgsfield" ]; then pack_hints higgsfield; fi
return 0
fi
ok "$tool enabled ($((restored + linked)) skills: $restored restored, $linked linked)"
+10 -3
View File
@@ -489,14 +489,21 @@ print(d.get('higgsfield',{}).get('version','latest'))
HF_PKG="@higgsfield/cli@latest"
[ -n "$HF_VER" ] && [ "$HF_VER" != "latest" ] \
&& HF_PKG="@higgsfield/cli@${HF_VER}"
HF_NPM_OK=true
npm install -g "$HF_PKG" 2>/dev/null || HF_NPM_OK=false
# npm updates only a copy npm installed: a CLI that came from Homebrew
# or the vendor's installer would otherwise gain a second, competing copy.
HF_NPM=skipped
if npm ls -g @higgsfield/cli >/dev/null 2>&1; then
HF_NPM=ok
npm install -g "$HF_PKG" 2>/dev/null || HF_NPM=failed
fi
# The probe first, then npm's status: an update that skips the package's
# postinstall script exits 0 and leaves the shim with no binary behind it.
if ! higgsfield_cli_ok; then
warn "Higgsfield CLI does not answer after the update — run: npm install -g --allow-scripts=@higgsfield/cli @higgsfield/cli"
elif [ "$HF_NPM_OK" = true ]; then
elif [ "$HF_NPM" = ok ]; then
ok "Higgsfield CLI updated (${HF_VER:-latest})"
elif [ "$HF_NPM" = skipped ]; then
info "Higgsfield CLI was not installed through npm — left to its own updater"
else
warn "Higgsfield CLI update failed — existing binary kept"
fi