Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
40 changes: 28 additions & 12 deletions docs/specs/install-command.md
Original file line number Diff line number Diff line change
@@ -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:**
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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: <msg>` and then `<msg>` 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.
36 changes: 9 additions & 27 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,25 @@
# 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)
#

set -e

INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}"
SKILL_DIR="${SKILL_DIR:-$HOME/.claude/skills/flux}"
REPO="eyra/flux-cli"

# install_skill <dir>: copy the skill from <dir>/skill/SKILL.md, the layout of
# both the repo and the release archive (see .goreleaser.yml). <dir>/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() {
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
18 changes: 9 additions & 9 deletions scripts/test-install.sh
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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:]')
Expand All @@ -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"
)
}
Expand Down
Loading