mirror of
https://github.com/coleam00/Archon
synced 2026-04-21 13:37:41 +00:00
* feat(providers): replace Claude SDK embed with explicit binary-path resolver Drop `@anthropic-ai/claude-agent-sdk/embed` and resolve Claude Code via CLAUDE_BIN_PATH env → assistants.claude.claudeBinaryPath config → throw with install instructions. The embed's silent failure modes on macOS (#1210) and Windows (#1087) become actionable errors with a documented recovery path. Dev mode (bun run) remains auto-resolved via node_modules. The setup wizard auto-detects Claude Code by probing the native installer path (~/.local/bin/claude), npm global cli.js, and PATH, then writes CLAUDE_BIN_PATH to ~/.archon/.env. Dockerfile pre-sets CLAUDE_BIN_PATH so extenders using the compiled binary keep working. Release workflow gets negative and positive resolver smoke tests. Docs, CHANGELOG, README, .env.example, CLAUDE.md, test-release and archon skills all updated to reflect the curl-first install story. Retires #1210, #1087, #1091 (never merged, now obsolete). Implements #1176. * fix(providers): only pass --no-env-file when spawning Claude via Bun/Node `--no-env-file` is a Bun flag that prevents Bun from auto-loading `.env` from the subprocess cwd. It is only meaningful when the Claude Code executable is a `cli.js` file — in which case the SDK spawns it via `bun`/`node` and the flag reaches the runtime. When `CLAUDE_BIN_PATH` points at a native compiled Claude binary (e.g. `~/.local/bin/claude` from the curl installer, which is Anthropic's recommended default), the SDK executes the binary directly. Passing `--no-env-file` then goes straight to the native binary, which rejects it with `error: unknown option '--no-env-file'` and the subprocess exits code 1. Emit `executableArgs` only when the target is a `.js` file (dev mode or explicit cli.js path). Caught by end-to-end smoke testing against the curl-installed native Claude binary. * docs: record env-leak validation result in provider comment Verified end-to-end with sentinel `.env` and `.env.local` files in a workflow CWD that the native Claude binary (curl installer) does not auto-load `.env` files. With Archon's full spawn pathway and parent env stripped, the subprocess saw both sentinels as UNSET. The first-layer protection in `@archon/paths` (#1067) handles the inheritance leak; `--no-env-file` only matters for the Bun-spawned cli.js path, where it is still emitted. * chore(providers): cleanup pass — exports, docs, troubleshooting Final-sweep cleanup tied to the binary-resolver PR: - Mirror Codex's package surface for the new Claude resolver: add `./claude/binary-resolver` subpath export and re-export `resolveClaudeBinaryPath` + `claudeFileExists` from the package index. Renames the previously single `fileExists` re-export to `codexFileExists` for symmetry; nothing outside the providers package was importing it. - Add a "Claude Code not found" entry to the troubleshooting reference doc with platform-specific install snippets and pointers to the AI Assistants binary-path section. - Reframe the example claudeBinaryPath in reference/configuration.md away from cli.js-only language; it accepts either the native binary or cli.js. * test+refactor(providers, cli): address PR review feedback Two test gaps and one doc nit from the PR review (#1217): - Extract the `--no-env-file` decision into a pure exported helper `shouldPassNoEnvFile(cliPath)` so the native-binary branch is unit testable without mocking `BUNDLED_IS_BINARY` or running the full sendQuery pathway. Six new tests cover undefined, cli.js, native binary (Linux + Windows), Homebrew symlink, and suffix-only matching. Also adds a `claude.subprocess_env_file_flag` debug log so the security-adjacent decision is auditable. - Extract the three install-location probes in setup.ts into exported wrappers (`probeFileExists`, `probeNpmRoot`, `probeWhichClaude`) and export `detectClaudeExecutablePath` itself, so the probe order can be spied on. Six new tests cover each tier winning, fall-through ordering, npm-tier skip when not installed, and the which-resolved-but-stale-path edge case. - CLAUDE.md `claudeBinaryPath` placeholder updated to reflect that the field accepts either the native binary or cli.js (the example value was previously `/absolute/path/to/cli.js`, slightly misleading now that the curl-installer native binary is the default). Skipped from the review by deliberate scope decision: - `resolveClaudeBinaryPath` async-with-no-await: matches Codex's resolver signature exactly. Changing only Claude breaks symmetry; if pursued, do both providers in a separate cleanup PR. - `isAbsolute()` validation in parseClaudeConfig: Codex doesn't do it either. Resolver throws on non-existence already. - Atomic `.env` writes in setup wizard: pre-existing pattern this PR touched only adjacently. File as separate issue if needed. - classifyError branch in dag-executor for setup errors: scope creep. - `.env.example` "missing #" claim: false positive (verified all CLAUDE_BIN_PATH lines have proper comment prefixes). * fix(test): use path.join in Windows-compatible probe-order test The "tier 2 wins (npm cli.js)" test hardcoded forward-slash path comparisons, but `path.join` produces backslashes on Windows. Caused the Windows CI leg of the test suite to fail while macOS and Linux passed. Use `path.join` for both the mock return value and the expectation so the separator matches whatever the platform produces.
357 lines
13 KiB
YAML
357 lines
13 KiB
YAML
name: Release CLI Binaries
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: 'Version tag (e.g., v0.2.0)'
|
|
required: true
|
|
type: string
|
|
|
|
concurrency:
|
|
group: release-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
build:
|
|
strategy:
|
|
matrix:
|
|
include:
|
|
- os: ubuntu-latest
|
|
target: bun-linux-x64
|
|
binary: archon-linux-x64
|
|
- os: ubuntu-latest
|
|
target: bun-linux-arm64
|
|
binary: archon-linux-arm64
|
|
- os: ubuntu-latest
|
|
target: bun-windows-x64
|
|
binary: archon-windows-x64.exe
|
|
- os: macos-latest
|
|
target: bun-darwin-x64
|
|
binary: archon-darwin-x64
|
|
- os: macos-latest
|
|
target: bun-darwin-arm64
|
|
binary: archon-darwin-arm64
|
|
|
|
runs-on: ${{ matrix.os }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: 1.3.11
|
|
|
|
- name: Install dependencies
|
|
run: bun install --frozen-lockfile
|
|
|
|
- name: Build binary
|
|
env:
|
|
# On workflow_dispatch, github.ref_name is the branch name (e.g. 'main'),
|
|
# not the version tag — fall back to the user-supplied `version` input.
|
|
VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}
|
|
GIT_COMMIT: ${{ github.sha }}
|
|
TARGET: ${{ matrix.target }}
|
|
OUTFILE: dist/${{ matrix.binary }}
|
|
run: |
|
|
# Strip 'v' prefix from tag (e.g. v0.3.1 → 0.3.1)
|
|
VERSION="${VERSION#v}"
|
|
# Short commit (first 8 chars of SHA)
|
|
GIT_COMMIT="${GIT_COMMIT::8}"
|
|
mkdir -p dist
|
|
VERSION="$VERSION" GIT_COMMIT="$GIT_COMMIT" TARGET="$TARGET" OUTFILE="$OUTFILE" bash scripts/build-binaries.sh
|
|
|
|
- name: Smoke-test built binary
|
|
if: matrix.target == 'bun-linux-x64' && runner.os == 'Linux'
|
|
env:
|
|
RAW_VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}
|
|
run: |
|
|
chmod +x dist/${{ matrix.binary }}
|
|
if ! VERSION_OUTPUT=$(./dist/${{ matrix.binary }} version 2>&1); then
|
|
echo "::error::Binary failed to execute"
|
|
echo "$VERSION_OUTPUT"
|
|
exit 1
|
|
fi
|
|
echo "$VERSION_OUTPUT"
|
|
|
|
# Must not error with "Failed to read version" or similar
|
|
if echo "$VERSION_OUTPUT" | grep -qE "Failed to read version|package\.json not found|bad installation"; then
|
|
echo "::error::Binary is broken — version command cannot read embedded version"
|
|
echo "::error::This means BUNDLED_IS_BINARY was not set to true at build time."
|
|
exit 1
|
|
fi
|
|
|
|
# Must report 'Build: binary', not 'Build: source'
|
|
if ! echo "$VERSION_OUTPUT" | grep -q "Build: binary"; then
|
|
echo "::error::Binary reports wrong build type"
|
|
echo "::error::Expected 'Build: binary' in version output"
|
|
exit 1
|
|
fi
|
|
|
|
# Must report the (stripped) tag version. Compare against the same
|
|
# value that was baked into the binary (VERSION#v), not the raw ref,
|
|
# so the check doesn't rely on the CLI re-adding a 'v' prefix.
|
|
EXPECTED_VERSION="${RAW_VERSION#v}"
|
|
if echo "$VERSION_OUTPUT" | grep -qE "v?${EXPECTED_VERSION}(\s|$)"; then
|
|
echo "::notice::Binary correctly reports version ${EXPECTED_VERSION}"
|
|
else
|
|
echo "::error::Binary does not report version ${EXPECTED_VERSION}"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Smoke-test bundled defaults load
|
|
if: matrix.target == 'bun-linux-x64' && runner.os == 'Linux'
|
|
run: |
|
|
# `workflow list` requires running from a git repo
|
|
BIN="$PWD/dist/${{ matrix.binary }}"
|
|
TMP_REPO=$(mktemp -d)
|
|
cd "$TMP_REPO"
|
|
git init -q
|
|
git -c user.email=ci@example.com -c user.name=ci commit --allow-empty -q -m init
|
|
if ! OUTPUT=$("$BIN" workflow list 2>&1); then
|
|
echo "::error::workflow list failed to execute"
|
|
echo "$OUTPUT"
|
|
exit 1
|
|
fi
|
|
echo "$OUTPUT"
|
|
if echo "$OUTPUT" | grep -q "archon-assist"; then
|
|
echo "::notice::Bundled workflows loaded correctly"
|
|
else
|
|
echo "::error::Bundled workflows did not load — embedded JSON may be missing from the binary"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Smoke-test Claude binary-path resolver (negative case)
|
|
if: matrix.target == 'bun-linux-x64' && runner.os == 'Linux'
|
|
run: |
|
|
# With no CLAUDE_BIN_PATH and no config, running a Claude workflow must
|
|
# fail with a clear, user-facing error — NOT with "Module not found
|
|
# /Users/runner/..." which would indicate the resolver was bypassed.
|
|
BIN="$PWD/dist/${{ matrix.binary }}"
|
|
TMP_REPO=$(mktemp -d)
|
|
cd "$TMP_REPO"
|
|
git init -q
|
|
git -c user.email=ci@example.com -c user.name=ci commit --allow-empty -q -m init
|
|
|
|
# Run without CLAUDE_BIN_PATH set. Expect a clean resolver error.
|
|
# Capture both stdout and stderr; we only care that the resolver message is present.
|
|
set +e
|
|
OUTPUT=$(env -u CLAUDE_BIN_PATH "$BIN" workflow run archon-assist "hello" 2>&1)
|
|
EXIT_CODE=$?
|
|
set -e
|
|
echo "$OUTPUT"
|
|
|
|
if echo "$OUTPUT" | grep -qE 'Module not found.*Users/runner'; then
|
|
echo "::error::Resolver was bypassed — SDK hit the import.meta.url fallback (regression of #1210)"
|
|
exit 1
|
|
fi
|
|
if ! echo "$OUTPUT" | grep -q "Claude Code not found"; then
|
|
echo "::error::Expected 'Claude Code not found' error when CLAUDE_BIN_PATH is unset"
|
|
exit 1
|
|
fi
|
|
if ! echo "$OUTPUT" | grep -q "CLAUDE_BIN_PATH"; then
|
|
echo "::error::Error message does not reference CLAUDE_BIN_PATH remediation"
|
|
exit 1
|
|
fi
|
|
echo "::notice::Resolver error path works (exit code: $EXIT_CODE)"
|
|
|
|
- name: Smoke-test Claude subprocess spawn (positive case)
|
|
if: matrix.target == 'bun-linux-x64' && runner.os == 'Linux'
|
|
run: |
|
|
# Install Claude Code via the native installer (Anthropic's recommended
|
|
# default) and run a workflow with CLAUDE_BIN_PATH set. The subprocess
|
|
# must spawn cleanly. We do NOT require the query to succeed (no auth
|
|
# in CI — an auth error is fine and expected); we only fail if the SDK
|
|
# can't find the executable, which would indicate a resolver regression.
|
|
curl -fsSL https://claude.ai/install.sh | bash
|
|
CLI_PATH="$HOME/.local/bin/claude"
|
|
if [ ! -x "$CLI_PATH" ]; then
|
|
echo "::error::Claude Code binary not found after curl install at $CLI_PATH"
|
|
ls -la "$HOME/.local/bin/" || true
|
|
exit 1
|
|
fi
|
|
echo "Using CLAUDE_BIN_PATH=$CLI_PATH"
|
|
|
|
BIN="$PWD/dist/${{ matrix.binary }}"
|
|
TMP_REPO=$(mktemp -d)
|
|
cd "$TMP_REPO"
|
|
git init -q
|
|
git -c user.email=ci@example.com -c user.name=ci commit --allow-empty -q -m init
|
|
|
|
set +e
|
|
OUTPUT=$(CLAUDE_BIN_PATH="$CLI_PATH" "$BIN" workflow run archon-assist "hello" 2>&1)
|
|
EXIT_CODE=$?
|
|
set -e
|
|
echo "$OUTPUT"
|
|
|
|
if echo "$OUTPUT" | grep -qE 'Module not found.*(cli\.js|Users/runner)'; then
|
|
echo "::error::Subprocess could not find the executable (resolver regression)"
|
|
exit 1
|
|
fi
|
|
if echo "$OUTPUT" | grep -q "Claude Code not found"; then
|
|
echo "::error::Resolver failed even though CLAUDE_BIN_PATH was set to an existing file"
|
|
exit 1
|
|
fi
|
|
# Any of these outcomes are acceptable — they prove the subprocess spawned:
|
|
# - auth error ("credit balance", "unauthorized", "authentication")
|
|
# - rate-limit / API error
|
|
# - successful query (if auth was injected via some other mechanism)
|
|
echo "::notice::Claude subprocess spawn path is healthy (exit code: $EXIT_CODE)"
|
|
|
|
- name: Upload binary artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ matrix.binary }}
|
|
path: dist/${{ matrix.binary }}
|
|
retention-days: 1
|
|
|
|
release:
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: dist
|
|
merge-multiple: true
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: 1.3.11
|
|
|
|
- name: Install dependencies
|
|
run: bun install --frozen-lockfile
|
|
|
|
- name: Build web UI
|
|
run: bun --filter @archon/web build
|
|
|
|
- name: Package web dist
|
|
run: |
|
|
tar czf dist/archon-web.tar.gz -C packages/web/dist .
|
|
|
|
- name: Generate checksums
|
|
run: |
|
|
cd dist
|
|
sha256sum archon-* archon-web.tar.gz > checksums.txt
|
|
cat checksums.txt
|
|
|
|
- name: Get version
|
|
id: version
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Create Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: ${{ steps.version.outputs.version }}
|
|
name: Archon CLI ${{ steps.version.outputs.version }}
|
|
draft: false
|
|
prerelease: ${{ contains(steps.version.outputs.version, '-') }}
|
|
generate_release_notes: true
|
|
files: |
|
|
dist/archon-*
|
|
dist/archon-web.tar.gz
|
|
dist/checksums.txt
|
|
body: |
|
|
## Installation
|
|
|
|
### Quick Install (Recommended)
|
|
|
|
**macOS / Linux**
|
|
```bash
|
|
curl -fsSL https://archon.diy/install | bash
|
|
```
|
|
|
|
**Windows (PowerShell)**
|
|
```powershell
|
|
irm https://archon.diy/install.ps1 | iex
|
|
```
|
|
|
|
**Homebrew (macOS / Linux)**
|
|
```bash
|
|
brew install coleam00/archon/archon
|
|
```
|
|
|
|
**Docker**
|
|
```bash
|
|
docker run --rm -v "$PWD:/workspace" ghcr.io/coleam00/archon:latest workflow list
|
|
```
|
|
|
|
### Manual Installation
|
|
|
|
**macOS (Apple Silicon)**
|
|
```bash
|
|
curl -fsSL https://github.com/coleam00/Archon/releases/latest/download/archon-darwin-arm64 -o /usr/local/bin/archon
|
|
chmod +x /usr/local/bin/archon
|
|
```
|
|
|
|
**macOS (Intel)**
|
|
```bash
|
|
curl -fsSL https://github.com/coleam00/Archon/releases/latest/download/archon-darwin-x64 -o /usr/local/bin/archon
|
|
chmod +x /usr/local/bin/archon
|
|
```
|
|
|
|
**Linux (x64)**
|
|
```bash
|
|
curl -fsSL https://github.com/coleam00/Archon/releases/latest/download/archon-linux-x64 -o /usr/local/bin/archon
|
|
chmod +x /usr/local/bin/archon
|
|
```
|
|
|
|
**Linux (ARM64)**
|
|
```bash
|
|
curl -fsSL https://github.com/coleam00/Archon/releases/latest/download/archon-linux-arm64 -o /usr/local/bin/archon
|
|
chmod +x /usr/local/bin/archon
|
|
```
|
|
|
|
**Windows (Manual)**
|
|
Download `archon-windows-x64.exe` from the assets below, rename to `archon.exe`, and add to your PATH.
|
|
|
|
### Verify installation
|
|
```bash
|
|
archon version
|
|
```
|
|
|
|
update-homebrew:
|
|
needs: release
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: dev
|
|
|
|
- name: Get version
|
|
id: version
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Wait for release assets
|
|
run: sleep 30
|
|
|
|
- name: Update Homebrew formula
|
|
run: bash scripts/update-homebrew.sh ${{ steps.version.outputs.version }}
|
|
|
|
- name: Commit updated formula
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
git add homebrew/archon.rb
|
|
git diff --cached --quiet || git commit -m "chore: update Homebrew formula for ${{ steps.version.outputs.version }}"
|
|
git push origin dev
|