diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71e53ad3..ab0e835e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,13 +65,17 @@ jobs: # The workspace packages export their types from a built lib/. On a fresh # checkout those are absent, so consumers (acryl-cli -> acryl-harness- - # runtime -> acryl-control, cordis-plugin-market) cannot typecheck until - # the providers are built. + # runtime -> acryl-control, cordis-plugin-market, and acryl-cli -> dsh- + # community-market) cannot typecheck until the providers are built. The + # web serve test also loads dsh-client-ui-brand-acryl as a profile plugin, + # so its lib/ must exist before the test step. - name: Build workspace type providers run: | corepack pnpm --filter acryl-control run build corepack pnpm --filter acryl-harness-runtime run build + corepack pnpm --filter dsh-community-market run build corepack pnpm --filter cordis-plugin-market run build + corepack pnpm --filter dsh-client-ui-brand-acryl run build - name: Typecheck run: corepack pnpm run typecheck diff --git a/.github/workflows/nix-release.yml b/.github/workflows/nix-release.yml new file mode 100644 index 00000000..f3ba7d08 --- /dev/null +++ b/.github/workflows/nix-release.yml @@ -0,0 +1,170 @@ +name: Update Nix flake + +# Checks whether flake.nix lags behind the latest GitHub release. If it does, +# prefetches the new release's per-platform SRI hashes, rewrites flake.nix, +# and opens a PR. +# +# Runs on a schedule instead of release: published because releases are +# created with GITHUB_TOKEN via softprops/action-gh-release, which does not +# start new workflow runs. A daily lag-check is fully decoupled from how +# releases are created and needs no PAT. + +on: + schedule: + - cron: "17 6 * * *" + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: nix-flake-release + cancel-in-progress: true + +jobs: + update-flake: + name: Bump flake version + hashes if lagging + runs-on: ubuntu-latest + if: github.repository == 'acryldev/acryl' + steps: + - name: Checkout + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + persist-credentials: false + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22 + + - name: Check for lag and rewrite flake.nix + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # system|asset-substring — one per line. The substring must uniquely + # match the release asset filename for that system. + ASSET_MAP: | + x86_64-linux|acryl-cli-linux-x64.tar.gz + aarch64-linux|acryl-cli-linux-arm64.tar.gz + aarch64-darwin|acryl-cli-darwin-arm64.tar.gz + run: | + set -euo pipefail + python3 <<'PYEOF' + import json, os, re, subprocess, urllib.request + repo = os.environ["GITHUB_REPOSITORY"] + token = os.environ["GH_TOKEN"] + req = urllib.request.Request( + f"https://api.github.com/repos/{repo}/releases/latest", + headers={ + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {token}", + "X-GitHub-Api-Version": "2022-11-28", + }) + with urllib.request.urlopen(req) as r: + release = json.load(r) + tag = release["tag_name"] + version = tag.lstrip("v") + src = open("flake.nix").read() + m = re.search(r'releaseVersion = "([^"]*)";', src) + if not m: + raise SystemExit('could not find releaseVersion = "..." in flake.nix') + current = m.group(1) + print(f"flake.nix releaseVersion: {current} | latest release: {version} (tag {tag})") + if current == version: + print("flake.nix is up to date; nothing to do.") + with open(os.environ["GITHUB_ENV"], "a") as f: + f.write("LAGGING=no\n") + raise SystemExit(0) + with open(os.environ["GITHUB_ENV"], "a") as f: + f.write("LAGGING=yes\n") + f.write(f"VERSION={version}\n") + # Drop sibling checksum files (.sha256) so a tarball substring does + # not also match its ".sha256" companion. + names = {a["name"] for a in release["assets"] + if not a["name"].endswith(".sha256")} + asset_map = {} + for line in os.environ["ASSET_MAP"].splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + sys_, sub = line.split("|", 1) + asset_map[sys_.strip()] = sub.strip() + # Reverse-check guard: flag any .tar.gz/.zip release asset that no + # ASSET_MAP substring matches. If a project ships for 4 platforms + # but ASSET_MAP only lists 3, the omitted platform's hash goes stale + # while its URL still gets the version bump — users on that platform + # get a hash mismatch. This catches the omission class of bug that + # CI cannot see (nix flake check --all-systems --no-build evaluates + # without realising fetchurl derivations, and nix build only runs on + # the runner's own system). + # Nix does not support native Windows (only WSL), so Windows + # .zip assets are always intentionally excluded from the flake. + # acryl-web-* archives are separate web runtime tarballs, not CLI. + IGNORED = ("musl", "pnpr-", "-source", "source-code", "win32", "win-x64", + "win-arm64", "windows", "acryl-web", "acryl-release-manifest", + "checksums", "release-receipt", ".receipt.json") + matched_subs = set(asset_map.values()) + unmatched = [ + name for name in sorted(names) + if name.endswith((".tar.gz", ".zip")) + and not any(sub in name for sub in matched_subs) + and not any(ign in name.lower() for ign in IGNORED) + ] + if unmatched: + plats = ", ".join(unmatched) + raise SystemExit( + f"RELEASE ASSET COMPLETENESS CHECK FAILED: release {tag} has " + f"platform archives not covered by ASSET_MAP: {plats}. " + f"Add them to ASSET_MAP or the hash for those platforms will " + f"go stale while the URL gets the version bump — users on the " + f"omitted platform get a hash mismatch. ASSET_MAP currently " + f"covers: {sorted(asset_map.keys())}") + src = open("flake.nix").read() + src, n = re.subn(r'releaseVersion = "[^"]*";', f'releaseVersion = "{version}";', src, count=1) + if n != 1: + raise SystemExit('could not find releaseVersion = "..." in flake.nix') + for sys_, sub in asset_map.items(): + match = next((n for n in names if sub in n), None) + if not match: + raise SystemExit(f"no asset for {sys_} ({sub}) in {tag}; have: {sorted(names)}") + url = f"https://github.com/{repo}/releases/download/{tag}/{match}" + # Cross-check against the published .sha256 sibling asset before + # pinning the hash. Without this, a corrupted or replaced release + # artifact would be pinned silently. + expected = None + if f"{match}.sha256" in {a["name"] for a in release["assets"]}: + with urllib.request.urlopen(f"{url}.sha256") as r: + hexd = r.read().decode().split()[0] + expected = "sha256:" + hexd + out = json.loads(subprocess.check_output( + ["nix", "store", "prefetch-file", "--json", "--hash-type", "sha256"] + + (["--expected-hash", expected] if expected else []) + + [url])) + sri = out["hash"] + pat = re.compile(r'("' + re.escape(sys_) + r'" = \{[^}]*\})', re.S) + def repl(m): + b = m.group(1) + b = re.sub(r'hash = "[^"]*";', f'hash = "{sri}";', b, count=1) + return b + src, n = pat.subn(repl, src, count=1) + if n != 1: + raise SystemExit(f"could not find prebuiltAssets block for {sys_} in flake.nix") + open("flake.nix", "w").write(src) + print(f"bumped flake.nix to {version}: {list(asset_map)}") + PYEOF + + - name: Open PR + if: env.LAGGING == 'yes' + uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v8 + with: + commit-message: "chore(nix): bump flake to v${{ env.VERSION }}" + title: "chore(nix): bump flake to v${{ env.VERSION }}" + branch: chore/nix-flake-v${{ env.VERSION }} + base: main + body: | + Auto-generated by the `Update Nix flake` workflow (daily lag-check). + The latest GitHub release is v${{ env.VERSION }} but `flake.nix` was + pinned to an older version. This PR bumps `releaseVersion` and refreshes + the per-platform SRI hashes by prefetching the new release assets. + + Note: PRs opened by `GITHUB_TOKEN` do not trigger downstream workflow runs (e.g. CI), + so this PR will show no checks. Review the diff before merging — it should be a + version bump plus per-platform hash refresh with no source changes. diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 00000000..c82cb793 --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,124 @@ +name: Nix + +on: + pull_request: + branches: [main] + paths: + - flake.nix + - flake.lock + - "**/*.nix" + - .github/workflows/nix.yml + - pnpm-lock.yaml + - pnpm-workspace.yaml + - package.json + push: + branches: [main] + paths: + - flake.nix + - flake.lock + - "**/*.nix" + - .github/workflows/nix.yml + - pnpm-lock.yaml + - pnpm-workspace.yaml + - package.json + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: nix-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build (${{ matrix.system }}) + runs-on: ${{ matrix.runner }} + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - system: x86_64-linux + runner: ubuntu-latest + - system: aarch64-linux + runner: ubuntu-24.04-arm + - system: aarch64-darwin + runner: macos-26 + # x86_64-darwin uses the nixpkgs-26.05-darwin legacy pin. + # macos-26-intel is the current Intel macOS runner label (GA Feb + # 2026). Determinate dropped Intel *hosts* in Nov 2025 + # (DeterminateSystems/nix-src#224), so this leg installs upstream + # Nix via cachix/install-nix-action instead of the Determinate + # installer below. + - system: x86_64-darwin + runner: macos-26-intel + + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + submodules: recursive + + # Determinate Nix installer — not usable on x86_64-darwin hosts since + # Nov 2025 (DeterminateSystems/nix-src#224). + - uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22 + if: matrix.system != 'x86_64-darwin' + + # Upstream Nix installer for the Intel macOS leg (Determinate no longer + # ships an x86_64-darwin host build). Vanilla Nix needs flakes enabled + # explicitly; the Determinate installer does it implicitly. + - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 + if: matrix.system == 'x86_64-darwin' + with: + extra_nix_config: | + experimental-features = nix-command flakes + + # magic-nix-cache is Linux-only: the v14 static binary on arm64-darwin + # fails with `dyld: Symbol not found: __ZNSt13exception_ptr...` because + # it was built against a newer libc++ than the macos-14 runner ships. + # See DeterminateSystems/nix-installer#1684 and llvm/llvm-project#86077. + # Darwin builds work without the cache, just slower. + - uses: DeterminateSystems/magic-nix-cache-action@908b263ff629f4cc17666315b7fd3ec127c6244d # v14 + if: runner.os == 'Linux' + with: + use-flakehub: false + + - name: Check flake + run: nix flake check --no-build + + - name: Build acryl TUI + run: nix build .#acryl --system ${{ matrix.system }} + + - name: Build acryl-desktop + run: nix build .#acryl-desktop --system ${{ matrix.system }} + + # #prebuilt exists only on systems with a release asset — x86_64-darwin + # has no darwin-x64 tarball, so the attribute is absent there. + - name: Build prebuilt + run: | + if nix flake show --json 2>/dev/null | jq -e '.packages["${{ matrix.system }}"].prebuilt' >/dev/null; then + nix build .#prebuilt --system ${{ matrix.system }} + else + echo "No #prebuilt output for ${{ matrix.system }} — skipping" + fi + + - name: Test default --help + if: github.event_name != 'pull_request' + run: nix run .#default -- --help + + - name: Test TUI --help + if: github.event_name != 'pull_request' + run: nix run .#acryl -- --help + + - name: Test prebuilt --help + if: github.event_name != 'pull_request' + run: | + if nix flake show --json 2>/dev/null | jq -e '.packages["${{ matrix.system }}"].prebuilt' >/dev/null; then + nix run .#prebuilt -- --help + else + echo "No #prebuilt output for ${{ matrix.system }} — skipping" + fi + + - name: Test desktop --help + if: github.event_name != 'pull_request' + run: nix run .#acryl-desktop -- --help diff --git a/.gitignore b/.gitignore index d01733bf..ea9216ae 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,16 @@ release-artifacts/ .worktrees/ plugins/acryl-ui/.scratch-validate-registry-tests/ + +# Throwaway ACRYL_HOME for reproducing fresh-install/cold-start behavior +# (never-used package-fallback links, first profile creation, etc.) without +# touching a real ~/.acryl or Desktop's own persistent ~/.acryl-dev, both of +# which mask that class of bug once their fallback links are established. +.acryl-home-test/ + +# Nix build outputs +/result +/result-* + +# Devbox generated artifacts (machine-local paths and scripts) +.devbox/ diff --git a/README.en.md b/README.en.md index a6165b69..1b1ffdc0 100644 --- a/README.en.md +++ b/README.en.md @@ -67,6 +67,44 @@ acryl The `acryl` command starts the TUI. It is separate from the Desktop app so terminal users do not need Electron, and desktop users do not receive an unexpected global executable. +### Nix (Flake) + +The project provides a Nix flake that builds the TUI and Desktop from source. Nix with flakes enabled is required. + +```bash +# Run the TUI (default output) +nix run github:acryldev/acryl + +# Run the Desktop GUI +nix run github:acryldev/acryl#acryl-desktop + +# Install to your Nix profile +nix profile install github:acryldev/acryl + +# Specific release (the flake builds from source at every git tag) +nix run github:acryldev/acryl/v0.1.36 + +# Enter a development shell +nix develop github:acryldev/acryl +``` + +The flake exposes `packages..acryl` (TUI, from source, also `#default`), `packages..prebuilt` (prebuilt release binary with bundled Node runtime; available on `x86_64-linux`, `aarch64-linux`, and `aarch64-darwin`), `packages..acryl-desktop`, and `devShells..default`. + +### Devbox + +For a reproducible development environment without managing Nix tooling manually, use [Devbox](https://www.jetify.com/devbox): + +```bash +# Install Devbox (if not already installed) +curl -fsSL https://get.jetify.dev/devbox | bash + +# Enter the development environment +devbox shell + +# Build the project +corepack pnpm build +``` + ### Local Web surface Start the browser surface explicitly when you want it: diff --git a/README.i18n.yaml b/README.i18n.yaml index 061e7c13..24fb2d6e 100644 --- a/README.i18n.yaml +++ b/README.i18n.yaml @@ -1,5 +1,5 @@ # Bilingual-pair consistency record: the git blob hash of each side as of the last # confirmed-consistent state. Both languages carry equal authority. Update both files # and re-record their hashes after editing either side. -README.md: 583aa69ea97b093f36ccadad0fb7e6824691e96d -README.en.md: a6165b692aaf57f4374f2a557ff09e604831cb58 +README.md: 30f31a88cc22a53089ce593dd3d7b1276e46e03e +README.en.md: 1b1ffdc0a9b64b9952f121c136f9c0f51ae61afb diff --git a/README.md b/README.md index 583aa69e..30f31a88 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,44 @@ acryl The `acryl` command starts ACRYL's terminal UI. It is separate from the Desktop app so terminal users do not need Electron, and desktop users do not receive an unexpected global executable. +### Nix (Flake) + +The project provides a Nix flake that builds the TUI and Desktop from source. Nix with flakes enabled is required. + +```bash +# Run the TUI (default output) +nix run github:acryldev/acryl + +# Run the Desktop GUI +nix run github:acryldev/acryl#acryl-desktop + +# Install to your Nix profile +nix profile install github:acryldev/acryl + +# Specific release (the flake builds from source at every git tag) +nix run github:acryldev/acryl/v0.1.36 + +# Enter a development shell +nix develop github:acryldev/acryl +``` + +The flake exposes `packages..acryl` (TUI, from source, also `#default`), `packages..prebuilt` (prebuilt release binary with bundled Node runtime; available on `x86_64-linux`, `aarch64-linux`, and `aarch64-darwin`), `packages..acryl-desktop`, and `devShells..default`. + +### Devbox + +For a reproducible development environment without managing Nix tooling manually, use [Devbox](https://www.jetify.com/devbox): + +```bash +# Install Devbox (if not already installed) +curl -fsSL https://get.jetify.dev/devbox | bash + +# Enter the development environment +devbox shell + +# Build the project +corepack pnpm build +``` + ### Local Web surface Start the browser surface explicitly when you want it: diff --git a/README.zh.md b/README.zh.md index 70b38434..e80f4d69 100644 --- a/README.zh.md +++ b/README.zh.md @@ -8,6 +8,8 @@ - 桌面 GUI 下载:[ACRYL v0.1.19 GitHub Release](https://github.com/acryldev/acryl/releases/tag/v0.1.19)([macOS Apple Silicon](https://github.com/acryldev/acryl/releases/download/v0.1.19/acryl-desktop-mac-arm64.dmg)、[macOS Intel](https://github.com/acryldev/acryl/releases/download/v0.1.19/acryl-desktop-mac-x64.dmg)、[Windows x64](https://github.com/acryldev/acryl/releases/download/v0.1.19/acryl-desktop-win-x64.exe)、[Linux x64 DEB](https://github.com/acryldev/acryl/releases/download/v0.1.19/dsh-plugin-desktop_0.1.9_amd64.deb)、[Linux arm64 DEB](https://github.com/acryldev/acryl/releases/download/v0.1.19/dsh-plugin-desktop_0.1.9_arm64.deb))。桌面应用自带所需运行时,但不会把 `acryl` 安装到 shell PATH,也不会在退出后持续运行 Web 服务。 - 终端 CLI(推荐):使用独立安装脚本(无需 Node.js/npm,无安装警告,并自动把 `acryl` 加入 shell PATH):`curl -fsSL https://acryl.dev/install | bash`。它会把预编译的 `acryl` 安装到 `~/.acryl/bin` 并校验校验和,之后在新终端运行 `acryl`。 - npm 终端 CLI(备选):[acryl](https://www.npmjs.com/package/acryl),`npm install -g acryl`,然后运行 `acryl`。npm 11+ 会打印关于原生依赖(`node-pty`、`koffi` 等)的 `install-scripts` 安全提示,属正常现象,包仍会正常安装运行;如需无警告安装,请运行 `npm install -g acryl --allow-scripts=@deepseek-ai/dsh-subprocess-local,@google/genai,koffi,node-pty,protobufjs`。 +- Nix(Flake):`nix run github:acryldev/acryl`(TUI,从源码构建,默认输出)或 `nix run github:acryldev/acryl#prebuilt`(预编译二进制,自带 Node 运行时;支持 `x86_64-linux`、`aarch64-linux`、`aarch64-darwin`)或 `nix run github:acryldev/acryl#acryl-desktop`(桌面 GUI)。也可 `nix profile install github:acryldev/acryl` 安装到 Nix profile。Flake 从源码构建,支持 tag pinning(如 `github:acryldev/acryl/v0.1.36`)。 +- Devbox:`devbox shell` 进入可复现开发环境(需先安装 Devbox:`curl -fsSL https://get.jetify.dev/devbox | bash`)。 - 本地 Web 界面:运行 `acryl web`。它启动本地服务并打印 URL,直到命令停止为止,不是托管的云服务。 - `acryl gui` 预留给未来从 CLI 跳转到桌面应用的功能;目前请直接启动已安装的桌面应用。 - Discord:[加入 ACRYL 社区](https://discord.gg/cY9KXMex69) diff --git a/devbox.json b/devbox.json index adeee859..66b1e449 100644 --- a/devbox.json +++ b/devbox.json @@ -1,9 +1,34 @@ { "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/main/plugins/schemas/devbox.json", "packages": { - "nodejs_22": "latest", - "pnpm_11": "latest", - "esbuild": "latest" + "nodejs_22": { + "version": "latest", + "excluded_platforms": ["x86_64-darwin"] + }, + "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#nodejs_22": { + "platforms": ["x86_64-darwin"] + }, + "pnpm_11": { + "version": "latest", + "excluded_platforms": ["x86_64-darwin"] + }, + "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#pnpm_11": { + "platforms": ["x86_64-darwin"] + }, + "esbuild": { + "version": "latest", + "excluded_platforms": ["x86_64-darwin"] + }, + "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#esbuild": { + "platforms": ["x86_64-darwin"] + }, + "act": { + "version": "latest", + "excluded_platforms": ["x86_64-darwin"] + }, + "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#act": { + "platforms": ["x86_64-darwin"] + } }, "shell": { "init_hook": [ @@ -16,6 +41,6 @@ } }, "nixpkgs": { - "commit": "nixpkgs-unstable" + "commit": "f6107e546a5012172d93e79f1f7950da02ad798f" } } diff --git a/devbox.lock b/devbox.lock new file mode 100644 index 00000000..57a7e1db --- /dev/null +++ b/devbox.lock @@ -0,0 +1,64 @@ +{ + "lockfile_version": "1", + "packages": { + "github:NixOS/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f": { + "last_modified": "1970-01-01T00:00:00Z", + "resolved": "github:NixOS/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f" + }, + "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#act": { + "last_modified": "1970-01-01T00:00:00Z", + "resolved": "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#act" + }, + "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#esbuild": { + "last_modified": "1970-01-01T00:00:00Z", + "resolved": "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#esbuild" + }, + "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#nodejs_22": { + "last_modified": "1970-01-01T00:00:00Z", + "resolved": "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#nodejs_22" + }, + "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#pnpm_11": { + "last_modified": "1970-01-01T00:00:00Z", + "resolved": "github:nixos/nixpkgs/f6107e546a5012172d93e79f1f7950da02ad798f#pnpm_11" + }, + "nodejs_22@latest": { + "last_modified": "2026-08-27T07:16:00Z", + "plugin_version": "0.0.4", + "resolved": "github:NixOS/nixpkgs/c27cdad491a991b11ed731760aa2ef8db0cb0410#nodejs_22", + "source": "devbox-search", + "version": "22.23.2", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/6hbiyfa4gzcjr6bnr440nnwznlsaldlc-nodejs-22.23.2", + "default": true + } + ], + "store_path": "/nix/store/6hbiyfa4gzcjr6bnr440nnwznlsaldlc-nodejs-22.23.2" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/v6xnbaql2wc91k13xcfhr60r52ich6bw-nodejs-22.23.2", + "default": true + } + ], + "store_path": "/nix/store/v6xnbaql2wc91k13xcfhr60r52ich6bw-nodejs-22.23.2" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/gf597zf0ysgbngwb92baxgxjd02px6jh-nodejs-22.23.2", + "default": true + } + ], + "store_path": "/nix/store/gf597zf0ysgbngwb92baxgxjd02px6jh-nodejs-22.23.2" + } + } + } + } +} diff --git a/docs/DEVELOPMENT-LOG.md b/docs/DEVELOPMENT-LOG.md index ccdf5a02..b0067b97 100644 --- a/docs/DEVELOPMENT-LOG.md +++ b/docs/DEVELOPMENT-LOG.md @@ -440,6 +440,49 @@ This is a decision-pending milestone: the grilling ticket *Lock the interchangeable harness-engine destination (DSH <-> pi)* (`specs/000-wayfinding/issues/04-lock-harness-engine-swap.md`) must resolve before `specs/028-acryl-harness-engine-swap/` is created with `/speckit-specify`. +## 2026-09-02 - add #prebuilt output for prebuilt CLI release tarballs + +Commit: `c01bbfc73bfc8c1ca7edfd5ec4a76aee6c7d6a51` + +Added `packages..prebuilt` to the Nix flake — fetches the prebuilt +CLI tarball from GitHub releases (v0.1.19) with per-platform SRI hashes. +Each tarball bundles its own Node runtime and native addons (node-pty, +koffi, sharp), so the prebuilt path needs no from-source build. Uses +`autoPatchelfHook` on Linux for glibc linking. The default output remains +`#acryl` (from-source build), following Nix convention; `#prebuilt` is an +optional fast path. CI now builds and tests `#prebuilt` on all 4 platforms. +READMEs updated to document the `#prebuilt` output. + +## 2026-09-02 - align nixify artifacts with nixify skill rules + +Commits: `a3ebe71686e01efa923f8b0df44068527752e401`, +`122f207` (devbox x86_64-darwin first attempt), +`2d895f6` (devbox x86_64-darwin per-package scoping) + +Audited the `feature/nix-flake-support` branch against the nixify skill's +Definition of Done and fixed nine findings. SHA-pinned all GitHub Actions +in `nix.yml` to 40-char commit SHAs (checkout@v5, nix-installer-action@v22, +magic-nix-cache-action@v14) instead of mutable `@v4`/`@main` refs. Added +`if: github.event_name != 'pull_request'` guards on `nix run` steps to +prevent PR-controlled code from reaching `GITHUB_TOKEN`/OIDC. Added path +filtering so Nix CI only fires when `flake.nix`, `flake.lock`, `**/*.nix`, +`pnpm-lock.yaml`, or `package.json` change. Added `nix run .#default -- +--help` test. Added `act` to `devbox.json` for local CI validation. +Added `.devbox/` to `.gitignore`. Added Nix (Flake) and Devbox install +sections to `README.md`, `README.en.md`, and `README.zh.md`; updated the +bilingual-docs hash record in `README.i18n.yaml`. + +The `devbox.lock` blocker was resolved with per-package platform scoping: +clean package names (`nodejs_22`, `pnpm_11`, `esbuild`, `act`) for normal +platforms (Linux, aarch64-darwin), and flake URL references to +nixpkgs-26.05-darwin only for x86_64-darwin. The `nixpkgs.commit` field +is set to the 26.05-darwin pin for the shell infrastructure (`mkShell`). +On normal platforms, packages resolve from nixpkgs-unstable via devbox's +index — unchanged from before. On x86_64-darwin, the flake URL references +bypass devbox 0.18's hardcoded nixpkgs 26.11 (which dropped x86_64-darwin) +and pull from 26.05-darwin instead. `devbox.lock` is committed with both +resolution paths; Linux/aarch64-darwin entries will be populated when a +user on that platform runs `devbox install`. ## 2026-09-02 - align exact PNPM pins to the 11.8.0 root release @@ -656,7 +699,93 @@ re-basing the control plane onto the native `@deepseek-ai/dsh-*` / Cordis seams, the plugin path with one real model-facing Tool as the hard gate. Follow-on differentiators (authorization pipeline, room identity, relay/handoff, capability package, agent-agnostic canvas) are intentionally recorded as subsequent ledgers. +## 2026-08-31 - Nix flake: add acryl-desktop (Electron) output +**Commit:** [`7383dd9d9e6b619fcec51c5f7567d6b1488d02b6`](https://github.com/acryldev/acryl/commit/7383dd9d9e6b619fcec51c5f7567d6b1488d02b6) + +Extended the Nix flake to also build the Electron desktop app as +`packages.${system}.acryl-desktop`, alongside the existing TUI output. + +### Approach + +- Uses nixpkgs `electron` (43.1.0) as the runtime instead of the npm + `electron` package (which downloads a platform binary via postinstall, + blocked by `--ignore-scripts` in the Nix sandbox) +- Creates a CJS shim at `node_modules/electron/index.js` that exports the + nixpkgs electron path, replacing the real npm package. The desktop + launcher (`bin.ts`) does `import('electron')` to get the binary path, + then spawns it with `main.js` — the shim makes this work without the + npm electron binary +- Skips the `generate-*` build scripts (they use `sharp` for image + processing) since `build/` assets are already tracked in git +- Builds the full dependency chain: `acryl-control -> + acryl-harness-runtime -> dsh-community-market -> + acryl-development-canvas -> acryl-desktop` +- Refactors shared derivation attrs into `commonDerivationAttrs` to + avoid duplication between TUI and desktop derivations + +### Usage + +```sh +nix build .#acryl-desktop +nix run .#acryl-desktop -- --help +nix run .#acryl-desktop -- --version +``` + +--- + +## 2026-08-31 - Nix flake support for acryl-tui + +**Commit:** [`4f15a00d778c8b01af8f536b78e04fbf3372f8ee`](https://github.com/acryldev/acryl/commit/4f15a00d778c8b01af8f536b78e04fbf3372f8ee) + +Added Nix flake support targeting the `acryl-tui` terminal client. The flake +builds the TUI and its workspace dependencies (`acryl-control`, +`acryl-harness-runtime`) using nixpkgs' modern PNPM hooks, producing a +runnable `acryl` binary. + +### What was added + +- `flake.nix` — Nix flake with `packages.${system}.acryl` (default) and + `devShells.${system}.default` +- `flake.lock` — Locked inputs (nixpkgs-unstable, nixpkgs-26.05-darwin, + nix-systems/default) +- `devbox.json` — Reproducible development environment +- `.github/workflows/nix.yml` — CI for all 4 supported systems +- `.gitignore` — `/result` and `/result-*` entries + +### Key design decisions + +- **TUI target, not Electron:** The flake builds `acryl-tui` (the terminal + client) as the default package. Packaging the Electron desktop app via Nix + is a separate, harder problem deferred to future work. + +- **Intel macOS support:** nixpkgs-unstable (26.11) dropped `x86_64-darwin`. + The flake pins `nixpkgs-26.05-darwin` for Intel macOS and uses unstable for + all other systems. + +- **Modern PNPM API:** Uses `fetchPnpmDeps` with `fetcherVersion = 4`, + `pnpmConfigHook`, and `pnpm_11` (not the deprecated `pnpm.fetchDeps`). + +- **Hoisted node-linker:** Forces `nodeLinker: hoisted` in + `pnpm-workspace.yaml` during the build (pnpm 11 moved this setting from + `.npmrc`). This flattens `node_modules/` so the install phase can copy it + without resolving pnpm's `.pnpm/` virtual store symlinks. + +- **Selective build:** Builds only `acryl-control -> acryl-harness-runtime -> + acryl-tui` instead of the full workspace (which includes Electron). + +- **Performance:** `dontStrip` and `dontFixup` skip Nix's strip and fixup + phases, which are extremely slow on thousands of JS files in node_modules. + +### Usage + +```sh +nix build .#acryl +nix run .#acryl -- --help +nix run .#acryl -- --version +``` + +--- ## 2026-08-31 - shared coding capability composition implementation plan Commit: `8b6a955` @@ -4528,7 +4657,51 @@ CLI's own install verb (spec 034 T006's original "at least one non-Electron surface" wording covers either) remains open - Web was the one driven to completion because it was the one under active user testing. -## 2026-09-13 - fix: Desktop's Market install was blocked by pnpm-workspace.yaml, not just a missing flag (spec 034 T006) +--- + +## 2026-09-11 - Nix flake: bump prebuilt to v0.1.36, add hash automation, newest runner + +**Commits:** [`cfa0c03a55adb34e3db4251cdd35ef50073d78a3`](https://github.com/acryldev/acryl/commit/cfa0c03a55adb34e3db4251cdd35ef50073d78a3) (implementation), [`d2e7ef2d0f6b6f8246be48a621bb59407bc31ddd`](https://github.com/acryldev/acryl/commit/d2e7ef2d0f6b6f8246be48a621bb59407bc31ddd) (docs) + +Rebased `feature/nix-flake-support` onto the latest `upstream/main` +(`0822873`) and applied the remaining nixify skill compliance fixes. + +### Changes + +- Bumped the prebuilt CLI version from v0.1.19 to v0.1.36 (latest + release). Refreshed all three per-platform SRI hashes by prefetching + the new release assets. +- Removed `x86_64-darwin` from `prebuiltAssets` because v0.1.36 does not + ship a darwin-x64 CLI tarball. Made `#prebuilt` conditional via + `optionalAttrs` so it is only exposed on platforms with a release + asset (`x86_64-linux`, `aarch64-linux`, `aarch64-darwin`). On + `x86_64-darwin`, `nix run .#prebuilt` correctly errors "package not + available" instead of failing with a missing attribute error. +- Added `.github/workflows/nix-release.yml` — daily hash automation + workflow (scheduled lag-check template, required by nixify Step 16 + for prebuilt tarball flakes). Detects when `flake.nix` lags behind + the latest GitHub release, prefetches new SRI hashes, and opens a + PR. Uses `GITHUB_TOKEN` (releases are created with `GITHUB_TOKEN` + via `softprops/action-gh-release`, so `release: published` would + never fire). +- Updated the `aarch64-darwin` CI runner from `macos-14` to `macos-26` + (nixify Step 16: always use the newest runner). +- Updated all three READMEs to reference v0.1.36 in the Nix tag-pinning + example and noted the `#prebuilt` platform scope. Re-recorded the + bilingual-docs blob hashes in `README.i18n.yaml`. +- Re-pointed the two earlier Nix development-log entries at their + rebased commit hashes. + +### Validation + +- `nix flake check --no-build` passes on `x86_64-darwin`. +- `nix build .#default` succeeds (from-source TUI build). +- `nix run .#default -- --help` shows the correct help output. +- `nix build .#prebuilt` correctly errors on `x86_64-darwin` (no + prebuilt asset for this platform). +- `validate-action-pins.sh` — all actions pinned to commit SHAs. +- `validate-pre-push.sh` — magic-nix-cache guard, timeout, runner + labels, action pins, and branch-not-stale checks all pass.## 2026-09-13 - fix: Desktop's Market install was blocked by pnpm-workspace.yaml, not just a missing flag (spec 034 T006) Commits: `71c7df7`, `e321be1` @@ -4598,6 +4771,50 @@ spec 034 to close: T007 (retire `acryl-desktop`'s now-dead private plugin-lifecycle duplication), T008 (the cross-surface duplicate-loader-entry parity gap), and the real end-to-end GUI retest on Desktop. +## 2026-09-11 - Nix flake: bump prebuilt to v0.1.36, add hash automation, newest runner + +**Commits:** [`cfa0c03a55adb34e3db4251cdd35ef50073d78a3`](https://github.com/acryldev/acryl/commit/cfa0c03a55adb34e3db4251cdd35ef50073d78a3) (implementation), [`d2e7ef2d0f6b6f8246be48a621bb59407bc31ddd`](https://github.com/acryldev/acryl/commit/d2e7ef2d0f6b6f8246be48a621bb59407bc31ddd) (docs) + +Rebased `feature/nix-flake-support` onto the latest `upstream/main` +(`0822873`) and applied the remaining nixify skill compliance fixes. + +### Changes + +- Bumped the prebuilt CLI version from v0.1.19 to v0.1.36 (latest + release). Refreshed all three per-platform SRI hashes by prefetching + the new release assets. +- Removed `x86_64-darwin` from `prebuiltAssets` because v0.1.36 does not + ship a darwin-x64 CLI tarball. Made `#prebuilt` conditional via + `optionalAttrs` so it is only exposed on platforms with a release + asset (`x86_64-linux`, `aarch64-linux`, `aarch64-darwin`). On + `x86_64-darwin`, `nix run .#prebuilt` correctly errors "package not + available" instead of failing with a missing attribute error. +- Added `.github/workflows/nix-release.yml` — daily hash automation + workflow (scheduled lag-check template, required by nixify Step 16 + for prebuilt tarball flakes). Detects when `flake.nix` lags behind + the latest GitHub release, prefetches new SRI hashes, and opens a + PR. Uses `GITHUB_TOKEN` (releases are created with `GITHUB_TOKEN` + via `softprops/action-gh-release`, so `release: published` would + never fire). +- Updated the `aarch64-darwin` CI runner from `macos-14` to `macos-26` + (nixify Step 16: always use the newest runner). +- Updated all three READMEs to reference v0.1.36 in the Nix tag-pinning + example and noted the `#prebuilt` platform scope. Re-recorded the + bilingual-docs blob hashes in `README.i18n.yaml`. +- Re-pointed the two earlier Nix development-log entries at their + rebased commit hashes. + +### Validation + +- `nix flake check --no-build` passes on `x86_64-darwin`. +- `nix build .#default` succeeds (from-source TUI build). +- `nix run .#default -- --help` shows the correct help output. +- `nix build .#prebuilt` correctly errors on `x86_64-darwin` (no + prebuilt asset for this platform). +- `validate-action-pins.sh` — all actions pinned to commit SHAs. +- `validate-pre-push.sh` — magic-nix-cache guard, timeout, runner + labels, action pins, and branch-not-stale checks all pass. + ## 2026-09-14 - fix(desktop): report restartRequired for a live plugin disable/enable Commit: `46ff326` @@ -4788,6 +5005,7 @@ into the temp profile rather than symlinking them, making them resolvable in the isolated context. Verified: `corepack pnpm --filter acryl-desktop run check` now passes end to end with exit code 0. +<<<<<<< HEAD ## 2026-09-15/16 - feat(cli): /plugins fuzzy search + navigation-while-filtering fix Commits: `82d316c`, `2785545` @@ -5134,3 +5352,5 @@ binary directly rather than through whatever bare `pnpm` resolves to on - Not done, on purpose or by decision: steps 9-11 of T045's own checklist - push the registry, resync both sites (acryldev.github.io/ui and acrylblends.github.io/ui) and push the commits - were reserved for the operator at review time, so everything above is committed locally and unPushed. `questionnaire` and `message-scroller` stay blocked rather than ported narrower. Nothing else in T045 is open: the 26 ports are in the manifest, contracted, categorised (both sites render a component only inside a category, so an uncategorised port is invisible - a bug class that shipped twice before the ingest gate checked it), tested and browser-verified. +======= +>>>>>>> 9a172dc (docs(dev-log): record v0.1.36 bump, hash automation, and rebase) diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..35a68c49 --- /dev/null +++ b/flake.lock @@ -0,0 +1,60 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1787964612, + "narHash": "sha256-0N9nghg3nwzX6b6qc77EzjR9cu/Z+UR66FlfsCqiURs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e8be7818e19ada32105a8af937a6a473b38167ca", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-darwin-legacy": { + "locked": { + "lastModified": 1787940818, + "narHash": "sha256-vFzVv78WlyEbTyV5BbrnicnV159wC0YPOcBe3QagyBI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "f6107e546a5012172d93e79f1f7950da02ad798f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-26.05-darwin", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "nixpkgs-darwin-legacy": "nixpkgs-darwin-legacy", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..bcbaee41 --- /dev/null +++ b/flake.nix @@ -0,0 +1,373 @@ +{ + description = "ACRYL local-first plugin-native agent workspace"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + # x86_64-darwin was dropped from nixpkgs-unstable after 26.05. + # Pin the 26.05-darwin stable branch for Intel macOS. + nixpkgs-darwin-legacy.url = "github:NixOS/nixpkgs/nixpkgs-26.05-darwin"; + systems.url = "github:nix-systems/default"; + }; + + outputs = { self, nixpkgs, nixpkgs-darwin-legacy, systems, ... }: + let + allSystems = import systems; + + # Use the legacy pin for x86_64-darwin, unstable for everything else. + nixpkgsFor = system: + if system == "x86_64-darwin" + then nixpkgs-darwin-legacy + else nixpkgs; + + forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f { + inherit system; + pkgs = (nixpkgsFor system).legacyPackages.${system}; + }); + + # Filter out the deepseek-harness git submodule and other directories + # that are not needed for building the TUI. The patches/ directory MUST + # be included because pnpm-workspace.yaml references patch files there. + filterSrc = root: + let + base = toString root; + in + pkgs': path: type: + let + rel = pkgs'.lib.removePrefix base path; + in + ( + # Exclude the deepseek-harness submodule (large, not in workspace) + !pkgs'.lib.hasPrefix "/deepseek-harness" rel && + # Exclude VCS and tooling directories + !pkgs'.lib.hasPrefix "/.git" rel && + !pkgs'.lib.hasPrefix "/.agents" rel && + !pkgs'.lib.hasPrefix "/.claude" rel && + !pkgs'.lib.hasPrefix "/.specify" rel && + !pkgs'.lib.hasPrefix "/.github" rel && + # Exclude documentation and spec directories + !pkgs'.lib.hasPrefix "/specs" rel && + !pkgs'.lib.hasPrefix "/docs" rel && + # Exclude media assets + !pkgs'.lib.hasPrefix "/assets" rel && + rel != "/acryl-logo.png" && + rel != "/acryl-logo-white.png" && + # Exclude Nix build outputs + !pkgs'.lib.hasPrefix "/result" rel + ); + + # Prebuilt CLI release tarballs (v0.1.36). Each tarball bundles its + # own Node runtime and native addons (node-pty, koffi, sharp). + # v0.1.36 does not ship a darwin-x64 tarball, so #prebuilt is not + # available on x86_64-darwin — use #default or #acryl there. + releaseVersion = "0.1.36"; + prebuiltAssets = { + "x86_64-linux" = { + url = "https://github.com/acryldev/acryl/releases/download/v${releaseVersion}/acryl-cli-linux-x64.tar.gz"; + hash = "sha256-hGYEZPmdPC9IfRyIcwWsvq3wiDGHPJ2AHGumKI2UJSE="; + }; + "aarch64-linux" = { + url = "https://github.com/acryldev/acryl/releases/download/v${releaseVersion}/acryl-cli-linux-arm64.tar.gz"; + hash = "sha256-6Op6wXArwHQuGd42feZeDXRMEsLRE2He4TvxzmcbFOk="; + }; + "aarch64-darwin" = { + url = "https://github.com/acryldev/acryl/releases/download/v${releaseVersion}/acryl-cli-darwin-arm64.tar.gz"; + hash = "sha256-aP2zpAdfML8VJXinM0AqSKPdHNECB4EqarPoGAbXJyo="; + }; + }; + in + { + packages = forAllSystems ({ pkgs, system }: + let + pnpm = pkgs.pnpm_11; + version = (pkgs.lib.importJSON ./package.json).version; + + src = pkgs.lib.cleanSourceWith { + src = ./.; + filter = filterSrc ./. pkgs; + name = "acryl-source"; + }; + + pnpmDeps = pkgs.fetchPnpmDeps { + pname = "acryl"; + inherit version src pnpm; + fetcherVersion = 4; + hash = "sha256-vBmWX7p/3ic/8VQj0tqlEIGHll3x8UJ4kQuT3fEWKlQ="; + }; + + # Shared flags for both TUI and desktop derivations. + commonDerivationAttrs = { + inherit version src pnpmDeps pnpm; + + nativeBuildInputs = [ + pkgs.nodejs_22 + pkgs.pnpmConfigHook + pnpm + pkgs.makeWrapper + ]; + + # esbuild's postinstall (which downloads a platform binary) is + # skipped by --ignore-scripts in pnpmConfigHook. Point + # tsdown/esbuild at the nixpkgs-provided binary instead. + env.ESBUILD_BINARY_PATH = "${pkgs.esbuild}/bin/esbuild"; + + # Force hoisted node-linker so all dependencies are flattened + # into a single node_modules/ directory (like npm's layout). + # In pnpm 11, this setting moved from .npmrc to pnpm-workspace.yaml + # as "nodeLinker". The project's pnpm-workspace.yaml has + # nodeLinker: isolated (symlink-based .pnpm/ virtual store) which + # doesn't survive the Nix store copy — symlinks break. Replace it + # with hoisted so all packages are real directories. + preConfigure = '' + if grep -q "nodeLinker" pnpm-workspace.yaml; then + sed -i.bak 's/nodeLinker:.*/nodeLinker: hoisted/' pnpm-workspace.yaml + rm -f pnpm-workspace.yaml.bak + else + sed -i.bak '1i nodeLinker: hoisted' pnpm-workspace.yaml + rm -f pnpm-workspace.yaml.bak + fi + ''; + + # Don't let pnpmBuildHook run the root "build" script (which + # builds ALL workspace packages). We build only the needed + # dependency chains manually in each derivation's buildPhase. + dontPnpmBuild = true; + + # Don't strip debug symbols from JS files in node_modules — + # Nix's strip phase runs `strip -S` on every file, which is + # extremely slow for thousands of .js files and unnecessary + # for interpreted JavaScript. + dontStrip = true; + + # Skip the entire fixup phase (strip, patchShebangs on output, + # broken symlink check). The build phase already runs + # patchShebangs via pnpmConfigHook, and the fixup phase's + # `find -type l` scan over node_modules is very slow. + dontFixup = true; + }; + + # Helper to install a built workspace package into a node_modules + # directory, replacing any symlink/dir that the hoisted linker + # may have created. srcDir is the workspace path (e.g. + # "runtime/acryl-control"), pkgName is the node_modules name + # (e.g. "acryl-control"). extraDirs is a list of additional + # subdirectories to copy (e.g. ["docs"] for packages that read + # non-compiled assets at runtime; pass [] when not needed). + installWorkspacePackage = srcDir: pkgName: destDir: extraDirs: + let copyExtra = pkgs.lib.concatMapStrings (d: '' + cp -r ${srcDir}/${d} ${destDir}/${pkgName}/${d} + '') extraDirs; + in '' + rm -rf ${destDir}/${pkgName} + mkdir -p ${destDir}/${pkgName} + cp -r ${srcDir}/lib ${destDir}/${pkgName}/lib + cp ${srcDir}/package.json ${destDir}/${pkgName}/ + ${copyExtra} + ''; + in + { + default = self.packages.${system}.acryl; + + acryl = pkgs.stdenv.mkDerivation (finalAttrs: { + pname = "acryl"; + inherit (commonDerivationAttrs) version src pnpmDeps pnpm nativeBuildInputs preConfigure dontPnpmBuild dontStrip dontFixup; + env.ESBUILD_BINARY_PATH = commonDerivationAttrs.env.ESBUILD_BINARY_PATH; + + buildPhase = '' + runHook preBuild + + # Build the TUI dependency chain. + # acryl-harness-runtime imports types from acryl-control, so + # acryl-control must be built first. Then acryl-harness-runtime, + # then dsh-community-market (imported by acryl-cli's /market + # command), then acryl-cli (which depends on all three; + # renamed from acryl-tui upstream). + pnpm --filter acryl-control run build + pnpm --filter acryl-harness-runtime run build + pnpm --filter dsh-community-market run build + pnpm --filter acryl-cli run build + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/lib/acryl $out/bin + + # Copy the built TUI + cp -r apps/acryl-cli/lib $out/lib/acryl/lib + cp apps/acryl-cli/package.json $out/lib/acryl/ + + # With node-linker=hoisted, node_modules/ is a flat directory + # (like npm's layout) with no .pnpm/ virtual store symlinks. + # Just copy it as-is. + cp -a node_modules $out/lib/acryl/node_modules + + # Replace workspace package symlinks/dirs with actual built + # packages (the hoisted linker may have created symlinks for + # workspace packages that point to source directories). + ${installWorkspacePackage "runtime/acryl-control" "acryl-control" "$out/lib/acryl/node_modules" []} + ${installWorkspacePackage "runtime/acryl-harness-runtime" "acryl-harness-runtime" "$out/lib/acryl/node_modules" []} + ${installWorkspacePackage "plugins/dsh-community-market" "dsh-community-market" "$out/lib/acryl/node_modules" ["docs"]} + + # Wrap the TUI entry point + makeWrapper ${pkgs.nodejs_22}/bin/node $out/bin/acryl \ + --add-flags "$out/lib/acryl/lib/bin.js" + + runHook postInstall + ''; + + meta = with pkgs.lib; { + description = "ACRYL local-first plugin-native agent workspace (TUI)"; + homepage = "https://github.com/acryldev/acryl"; + license = licenses.mit; + mainProgram = "acryl"; + platforms = platforms.unix; + }; + }); + + acryl-desktop = pkgs.stdenv.mkDerivation (finalAttrs: { + pname = "acryl-desktop"; + inherit (commonDerivationAttrs) version src pnpmDeps pnpm nativeBuildInputs preConfigure dontPnpmBuild dontStrip dontFixup; + env.ESBUILD_BINARY_PATH = commonDerivationAttrs.env.ESBUILD_BINARY_PATH; + + # Electron is needed at build time for vite and at runtime. + # nixpkgs electron (43.1.0) is close to the project's 43.4.0. + buildInputs = [ pkgs.electron ]; + + buildPhase = '' + runHook preBuild + + # Build the full dependency chain for the desktop app. + # Order matters: each package imports types from its deps. + pnpm --filter acryl-control run build + pnpm --filter acryl-harness-runtime run build + pnpm --filter dsh-community-market run build + + # Build the desktop package. Skip the generate-* scripts + # (they use sharp for image processing) since the build/ + # directory with pre-generated assets is already tracked + # in git. Run tsdown, vite, and tsc directly. + pnpm --filter acryl-desktop exec tsdown + pnpm --filter acryl-desktop exec vite build --config vite.native-ui.config.ts + pnpm --filter acryl-desktop exec tsc -p tsconfig.json --emitDeclarationOnly + pnpm --filter acryl-desktop exec tsc -p tsconfig.client.json --emitDeclarationOnly + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/lib/acryl-desktop $out/bin + + # Copy the built desktop package + cp -r apps/acryl-desktop/lib $out/lib/acryl-desktop/lib + cp -r apps/acryl-desktop/build $out/lib/acryl-desktop/build + cp apps/acryl-desktop/package.json $out/lib/acryl-desktop/ + cp apps/acryl-desktop/cordis.patch.yml $out/lib/acryl-desktop/ 2>/dev/null || true + + # Copy node_modules (hoisted, flat layout) + cp -a node_modules $out/lib/acryl-desktop/node_modules + + # Replace workspace packages with built versions + ${installWorkspacePackage "runtime/acryl-control" "acryl-control" "$out/lib/acryl-desktop/node_modules" []} + ${installWorkspacePackage "runtime/acryl-harness-runtime" "acryl-harness-runtime" "$out/lib/acryl-desktop/node_modules" []} + ${installWorkspacePackage "plugins/dsh-community-market" "dsh-community-market" "$out/lib/acryl-desktop/node_modules" ["docs"]} + ${installWorkspacePackage "apps/acryl-desktop" "acryl-desktop" "$out/lib/acryl-desktop/node_modules" []} + + # Create a shim for the 'electron' npm package that exports + # the nixpkgs electron path. The desktop launcher does + # `import('electron')` to get the binary path; the real npm + # package downloads a platform binary via postinstall (blocked + # by --ignore-scripts), so we replace it with a CJS module + # that returns the nixpkgs electron path. + rm -rf $out/lib/acryl-desktop/node_modules/electron + mkdir -p $out/lib/acryl-desktop/node_modules/electron + cat > $out/lib/acryl-desktop/node_modules/electron/index.js < $out/lib/acryl-desktop/node_modules/electron/package.json <<'ELECTRON_PKG' + { "name": "electron", "version": "43.1.0", "main": "index.js" } + ELECTRON_PKG + + # Wrap the desktop launcher. The launcher (bin.ts) imports + # 'electron' to get the path, then spawns it with main.js. + makeWrapper ${pkgs.nodejs_22}/bin/node $out/bin/acryl-desktop \ + --add-flags "$out/lib/acryl-desktop/lib/bin.js" + + runHook postInstall + ''; + + meta = with pkgs.lib; { + description = "ACRYL local-first plugin-native agent workspace (Desktop)"; + homepage = "https://github.com/acryldev/acryl"; + license = licenses.mit; + mainProgram = "acryl-desktop"; + platforms = platforms.unix; + }; + }); + } + // pkgs.lib.optionalAttrs (prebuiltAssets ? ${system}) { + # Prebuilt CLI tarball — bundles its own Node runtime and native + # addons (node-pty, koffi, sharp). Fast path for users who want + # the exact release binary without a from-source build. + # Only exposed on platforms with a release asset. + prebuilt = pkgs.stdenv.mkDerivation { + pname = "acryl-prebuilt"; + inherit (commonDerivationAttrs) version; + + src = pkgs.fetchurl { + url = prebuiltAssets.${system}.url; + hash = prebuiltAssets.${system}.hash; + }; + + sourceRoot = "."; + + # The prebuilt tarball bundles both glibc-linked and musl-linked + # native addons (koffi ships musl_x64/koffi.node alongside + # linux_x64/koffi.node). autoPatchelf needs both glibc and musl + # available to satisfy all shared library dependencies. + nativeBuildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.autoPatchelfHook ]; + buildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [ + pkgs.stdenv.cc.cc.lib + pkgs.musl + ]; + + dontConfigure = true; + dontBuild = true; + + installPhase = '' + runHook preInstall + mkdir -p $out + cp -r acryl-cli-*/bin $out/bin + cp -r acryl-cli-*/lib $out/lib + cp -r acryl-cli-*/node_modules $out/node_modules + cp acryl-cli-*/package.json $out/ + chmod +x $out/bin/acryl + runHook postInstall + ''; + + meta = with pkgs.lib; { + description = "ACRYL TUI (prebuilt release binary)"; + homepage = "https://github.com/acryldev/acryl"; + downloadPage = "https://github.com/acryldev/acryl/releases"; + license = licenses.mit; + mainProgram = "acryl"; + platforms = builtins.attrNames prebuiltAssets; + sourceProvenance = [ sourceTypes.binaryNativeCode ]; + }; + }; + }); + + devShells = forAllSystems ({ pkgs, system }: { + default = pkgs.mkShell { + packages = with pkgs; [ + nodejs_22 + pnpm_11 + esbuild + ]; + }; + }); + }; +} diff --git a/runtime/acryl-control/tests/plugin-lifecycle.spec.ts b/runtime/acryl-control/tests/plugin-lifecycle.spec.ts index 02c3fc19..5e271d44 100644 --- a/runtime/acryl-control/tests/plugin-lifecycle.spec.ts +++ b/runtime/acryl-control/tests/plugin-lifecycle.spec.ts @@ -100,12 +100,13 @@ async function harness(overrides: Partial = {}): Promise }