Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
41871df
feat(nix): add flake, devbox, and CI for building acryl-tui
levonk Sep 1, 2026
e48b24c
docs: record Nix flake support in DEVELOPMENT-LOG.md
levonk Sep 1, 2026
a30f194
feat(nix): add acryl-desktop (Electron) as separate Nix package output
levonk Sep 1, 2026
0861309
docs: record acryl-desktop Nix output in DEVELOPMENT-LOG.md
levonk Sep 1, 2026
d9734a9
fix(nix): align nixify artifacts with skill rules
levonk Sep 2, 2026
924dd03
docs: record nixify skill alignment in DEVELOPMENT-LOG.md
levonk Sep 2, 2026
cb501e3
fix(devbox): pin all packages to nixpkgs-26.05-darwin for x86_64-darwin
levonk Sep 2, 2026
25080ed
fix(devbox): isolate x86_64-darwin workaround from normal platforms
levonk Sep 2, 2026
5d2c35c
docs: update DEVELOPMENT-LOG with per-package scoping approach
levonk Sep 2, 2026
db214b1
fix(nix): use upstream acryldev/acryl in flake and README references
levonk Sep 2, 2026
b4f48ec
feat(nix): add #prebuilt output for prebuilt CLI release tarballs
levonk Sep 2, 2026
482451e
docs: record #prebuilt output in DEVELOPMENT-LOG.md
levonk Sep 2, 2026
c0b0da8
fix(nix): rebase on upstream/main, fix FlakeHub auth, timeout, and FO…
levonk Sep 2, 2026
f791178
fix(nix): replace isolated linker with hoisted, not just insert
levonk Sep 2, 2026
5cedf22
fix(nix): add musl to prebuilt buildInputs for koffi musl binary
levonk Sep 2, 2026
504ff9a
ci(nix): disable magic-nix-cache on darwin runners
levonk Sep 3, 2026
81e4e5e
docs(dev-log): re-point Nix flake entries at the rebased commit hashes
musichen Sep 11, 2026
79efcf5
docs(i18n): re-record README.md blob hash after rebase onto main
musichen Sep 11, 2026
8033f7d
ci(nix): drop x86_64-darwin job - Determinate Nix cannot install on I…
musichen Sep 11, 2026
1789c9e
fix(nix): refresh pnpmDeps FOD hash for main's current pnpm-lock.yaml
musichen Sep 11, 2026
9179326
fix(nix): follow upstream acryl-tui -> acryl-cli package rename
musichen Sep 11, 2026
780937b
fix(nix): update prebuilt to v0.1.36, add hash automation, use newest…
levonk Sep 12, 2026
93d90a3
docs(nix): update READMEs to v0.1.36 and note #prebuilt platform scope
levonk Sep 12, 2026
9a172dc
docs(dev-log): record v0.1.36 bump, hash automation, and rebase
levonk Sep 12, 2026
6cb0ceb
fix(nix): refresh pnpmDeps FOD hash after rebase onto 227c3f9
levonk Sep 12, 2026
dd1452e
chore(nix): trigger CI after FOD hash refresh
levonk Sep 12, 2026
47b5c79
fix(nix): adapt flake to apps/runtime/plugins reorg on main
levonk Sep 15, 2026
6b6d5ca
fix(i18n): re-record README.md blob hash after rebase onto current main
levonk Sep 15, 2026
4c03e80
fix(ci): build dsh-community-market before typecheck
levonk Sep 15, 2026
4dfb71c
fix(ci): build dsh-client-ui-brand-acryl before test step
levonk Sep 15, 2026
f6f1d38
Merge upstream/main into feature/nix-flake-support
levonk Sep 17, 2026
85b61e8
fix(nix): refresh pnpmDeps hash for merged main lockfile
levonk Sep 17, 2026
96221e2
ci(nix): restore x86_64-darwin job on macos-26-intel with upstream in…
levonk Sep 17, 2026
dfcb188
ci(nix): skip #prebuilt steps on systems without a release asset
levonk Sep 17, 2026
916658c
Merge remote-tracking branch 'upstream/main' into feature/nix-flake-s…
levonk Sep 20, 2026
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
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,16 @@ 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) cannot typecheck until the providers are built.
# runtime -> acryl-control, and acryl-cli -> cordis-plugin-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 cordis-plugin-market run build
corepack pnpm --filter dsh-client-ui-brand-acryl run build

- name: Typecheck
run: corepack pnpm run typecheck
Expand Down
170 changes: 170 additions & 0 deletions .github/workflows/nix-release.yml
Original file line number Diff line number Diff line change
@@ -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 "<tarball>.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.
124 changes: 124 additions & 0 deletions .github/workflows/nix.yml
Original file line number Diff line number Diff line change
@@ -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
15 changes: 14 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,21 @@ release-artifacts/
.sctxx
.ccompactor

# 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/

# graft's local graph cache — regenerable, not committed (run `graft build`).
/graft/

#worktrees are used by git to manage multiple working trees attached to the same repository. They are not typically needed in the main repository and can be ignored.
.worktrees/
.worktrees/
38 changes: 38 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<system>.acryl` (TUI, from source, also `#default`), `packages.<system>.prebuilt` (prebuilt release binary with bundled Node runtime; available on `x86_64-linux`, `aarch64-linux`, and `aarch64-darwin`), `packages.<system>.acryl-desktop`, and `devShells.<system>.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:
Expand Down
4 changes: 2 additions & 2 deletions README.i18n.yaml
Original file line number Diff line number Diff line change
@@ -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: 0bea54202779097244d9f02bc625634526fa8b8f
README.en.md: 39c92c40c3e35ea03b97ba660978c6d577edd994
README.md: d6cd26d1dcab08d562480cbac55756de4c0bbd24
README.en.md: be5cdd4bd0531118f8cf21a422a245c8db7d0ffb
Loading
Loading