diff --git a/.goreleaser.yml b/.goreleaser.yml index a0949a7..6cf2bcb 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -22,8 +22,9 @@ archives: - formats: - tar.gz name_template: "flux_{{ .Os }}_{{ .Arch }}" + # The binary embeds skill/SKILL.md; `flux install` installs it. files: - - skill/SKILL.md + - none* checksum: name_template: "checksums.txt" diff --git a/docs/specs/install-command.md b/docs/specs/install-command.md index e9ab154..d753b7e 100644 --- a/docs/specs/install-command.md +++ b/docs/specs/install-command.md @@ -1,7 +1,8 @@ # Spec: `flux install claude` / `omp` / `codex` -- **Status:** implemented (step 1 of "Switching over"). Step 2 waits for the - `v0.5.0` release. +- **Status:** implemented. Released in `v0.5.0` (#19); `install.sh` switched + over in the follow-up PR. +- **Issue:** [Install the Flux skill with flux install claude, omp or codex](https://app.basecamp.com/5734045/buckets/45557517/todos/10394794625) - **Date:** 2026-10-12 - **Base:** `eyra/flux-cli` `master` at `2aac1bb`; the latest release is `v0.4.0` - **Checked against:** @@ -124,21 +125,28 @@ installed, the duplicate rules above apply, and the native omp copy wins. `curl … | bash` install fails. CI does not catch this: `install-check.yml` tests a locally built archive. So this ships in two steps. -1. First PR, followed by a release (`v0.5.0`). **Done on `feat/install-command`.** +1. First PR, followed by a release (`v0.5.0`). **Done: #19, released as + `v0.5.0` on 2026-10-12; the cask in `eyra/homebrew-tap` has the caveats.** - add the command, `skill/skill.go`, the tests and the README "Agent skill" subsection - add cask `caveats` telling users to run `flux install` with `claude`, `omp` and/or `codex`, and to rerun it after `brew upgrade` - leave `install.sh` and the archive `files` unchanged -2. Second PR, after `v0.5.0` is released: - - `install.sh`: replace `install_skill` with `"$INSTALL_DIR/flux" install - claude`, and drop `SKILL_DIR` (`CLAUDE_CONFIG_DIR` replaces it) - - `.goreleaser.yml`: remove `skill/SKILL.md` from the archive `files`. - This must happen in the same PR as the `install.sh` change, because - `install.sh` currently reads the skill from the archive. +2. Second PR, after `v0.5.0` is released. **Done.** + - `install.sh`: replace the copy in `install_skill` with + `"$INSTALL_DIR/flux" install claude`, and drop `SKILL_DIR` + (`CLAUDE_CONFIG_DIR` replaces it). If the skill install fails (for + example on a symlink), `install.sh` prints a warning and keeps the + installed binary. + - `.goreleaser.yml`: replace `skill/SKILL.md` in the archive `files` with + `none*`, so archives hold only the binary (without a `files` list, + GoReleaser would add `README.md`). This must happen in the same PR as the + `install.sh` change, because `install.sh` used to read the skill from the + archive. - `scripts/test-install.sh`: drop the "archive contains skill/SKILL.md" check, and keep the install, no-backup and backup checks, now run through - `flux install claude` + `flux install claude`. Unset `CLAUDE_CONFIG_DIR` instead of `SKILL_DIR`, + so a developer's environment can't leak into the sandbox. ## Out of scope @@ -181,5 +189,13 @@ the binary built from this branch. 6. **`goreleaser check` (v2.18.3) accepts the cask `caveats`.** 7. **Not fixed here: errors are printed twice.** The whole CLI prints errors as `Error: ` and then `` again: cobra prints the error, and - `Execute` in `cmd/root.go` prints it once more. Fix it separately with - `rootCmd.SilenceErrors = true`. + `Execute` in `cmd/root.go` prints it once more. This is already tracked as + [flux CLI prints every error twice](https://app.basecamp.com/5734045/buckets/45557517/todos/10390432942). +8. **The published `v0.5.0` works with the new `install.sh`.** Under a + temporary `HOME`, the step 2 `install.sh` downloaded `v0.5.0` and installed + the binary and a skill identical to `skill/SKILL.md`. With a symlinked + `~/.claude/skills/flux`, it printed the warning, left the link target + empty, kept the binary and exited 0. +9. **Related:** [flux update should also update the Flux skill](https://app.basecamp.com/5734045/buckets/45557517/todos/10385129256) + (Specification) can now call the same `installSkill` with the embedded + skill. diff --git a/install.sh b/install.sh index fe47051..dd16ba1 100755 --- a/install.sh +++ b/install.sh @@ -8,9 +8,11 @@ # Install from source (requires Go): # git clone https://github.com/eyra/flux-cli && cd flux-cli && ./install.sh # +# The Claude Code skill is installed with `flux install claude`, into +# ~/.claude/skills/flux (or $CLAUDE_CONFIG_DIR/skills/flux). +# # Overrides: # INSTALL_DIR where the binary goes (default: ~/.local/bin) -# SKILL_DIR where the Claude Code skill goes (default: ~/.claude/skills/flux) # FLUX_ARCHIVE install this local release archive instead of downloading one # (used by scripts/test-install.sh) # @@ -18,33 +20,13 @@ set -e INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" -SKILL_DIR="${SKILL_DIR:-$HOME/.claude/skills/flux}" REPO="eyra/flux-cli" -# install_skill : copy the skill from /skill/SKILL.md, the layout of -# both the repo and the release archive (see .goreleaser.yml). /SKILL.md is -# accepted as a fallback for a flat archive. +# install_skill: install the skill embedded in the binary. A failure (such as a +# symlinked skill folder) leaves the installed binary in place. install_skill() { - SRC="" - for f in "$1/skill/SKILL.md" "$1/SKILL.md"; do - if [ -f "$f" ]; then - SRC="$f" - break - fi - done - if [ -z "$SRC" ]; then - echo "Warning: no SKILL.md found in $1; skipping the Claude Code skill" - return - fi - echo "Installing Claude Code skill to $SKILL_DIR..." - mkdir -p "$SKILL_DIR" - # Keep a locally edited skill instead of silently overwriting it. - if [ -f "$SKILL_DIR/SKILL.md" ] && ! cmp -s "$SRC" "$SKILL_DIR/SKILL.md"; then - BACKUP="$SKILL_DIR/SKILL.md.bak-$(date +%Y%m%d-%H%M%S)" - cp "$SKILL_DIR/SKILL.md" "$BACKUP" - echo "Your existing SKILL.md differs from this release; backed it up to $BACKUP" - fi - cp "$SRC" "$SKILL_DIR/SKILL.md" + "$INSTALL_DIR/flux" install claude || + echo "Warning: the Claude Code skill was not installed; see the error above" } check_path() { @@ -76,7 +58,7 @@ install_from_release() { mv "$TMP/flux" "$INSTALL_DIR/flux" chmod +x "$INSTALL_DIR/flux" - install_skill "$TMP" + install_skill rm -rf "$TMP" echo "Installed $LATEST to $INSTALL_DIR/flux" @@ -118,7 +100,7 @@ install_from_source() { mv flux "$INSTALL_DIR/flux" echo "Installed to $INSTALL_DIR/flux" - install_skill . + install_skill } # Build from source if in the repo and Go is available; otherwise download a release diff --git a/scripts/test-install.sh b/scripts/test-install.sh index 394becf..6f817aa 100755 --- a/scripts/test-install.sh +++ b/scripts/test-install.sh @@ -1,15 +1,16 @@ #!/bin/bash # # Checks that the goreleaser archives and install.sh agree: every archive in -# dist/ must contain the binary and skill/SKILL.md, and installing the archive -# for this machine through install.sh must put both in place. +# dist/ must contain the binary, and installing the archive for this machine +# through install.sh must put the binary and, through `flux install claude`, +# the skill in place. # # Usage: scripts/test-install.sh [dist-dir] # Build the archives first: goreleaser release --snapshot --clean # -# Runs with a temporary HOME, and INSTALL_DIR and SKILL_DIR unset so they -# default into it, from a directory without go.mod, so it never touches the -# real installation. +# Runs with a temporary HOME, and INSTALL_DIR and CLAUDE_CONFIG_DIR unset so +# they default into it, from a directory without go.mod, so it never touches +# the real installation. set -euo pipefail @@ -28,8 +29,7 @@ ARCHIVES=("$DIST"/flux_*.tar.gz) for a in "${ARCHIVES[@]}"; do CONTENTS="$(tar -tzf "$a")" grep -qx "flux" <<<"$CONTENTS" || fail "$(basename "$a") has no flux binary" - grep -qx "skill/SKILL.md" <<<"$CONTENTS" || fail "$(basename "$a") has no skill/SKILL.md" - echo "ok: $(basename "$a") contains flux and skill/SKILL.md" + echo "ok: $(basename "$a") contains flux" done OS=$(uname -s | tr '[:upper:]' '[:lower:]') @@ -44,12 +44,12 @@ trap 'rm -rf "$SANDBOX"' EXIT mkdir -p "$SANDBOX/home" "$SANDBOX/work" # run_install: install $ARCHIVE through install.sh inside the sandbox. A -# developer's exported INSTALL_DIR or SKILL_DIR must never leak in. +# developer's exported INSTALL_DIR or CLAUDE_CONFIG_DIR must never leak in. run_install() { ( cd "$SANDBOX/work" [ ! -f go.mod ] || fail "work directory has a go.mod" - env -u INSTALL_DIR -u SKILL_DIR HOME="$SANDBOX/home" FLUX_ARCHIVE="$ARCHIVE" \ + env -u INSTALL_DIR -u CLAUDE_CONFIG_DIR HOME="$SANDBOX/home" FLUX_ARCHIVE="$ARCHIVE" \ bash "$ROOT/install.sh" ) }