nandi/frqpublic Fork 0
ca3afbe
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Build the client with Nix, not just a shell for it

frq is Jolt source, so a package is three things rather than one: the
runtime that reads it, the two Rust shared objects it loads, and this
tree with its deps resolved to store paths.

The parts that did not come for free:

  - jolt's own flake declares `self.submodules`, which Nix rejects over
    the github scheme, so its source comes in as git+https and the
    derivation lives here.
  - jolt-native's .cargo/config.toml runs the build through DotSlash — a
    pinned rustc, sccache, zig as compiler and linker — all fetched from
    the network on first use, which a sandbox has none of.
  - jolt resolves deps.edn by shelling out to git at startup, so glimmer
    and glimmer-vidya are Nix inputs handed back as :local/root.
  - the launcher is a written script, not makeWrapper: --add-flags pastes
    in unquoted, which splits the -Sdeps map across five arguments and
    leaves jolt reading one of them to EOF.
  - egui dlopens libX11 and libGL, so the launcher puts them on the
    loader path itself.

Off NixOS the GL driver is the host's and the loader will not find it;
the flake header says to launch through nixGL there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-08-30T20:54:01-07:00 Browse files
ca3afbe parent: f3d5db6
modified .gitignore +4 -0
@@ -5,3 +5,7 @@
55 /.jolt/
66 /.lsp/
77 /.clj-kondo/
8+
9+# Nix build symlinks.
10+/result
11+/result-*
@@ -5,3 +5,7 @@
5 /.jolt/5 /.jolt/
6 /.lsp/6 /.lsp/
7 /.clj-kondo/7 /.clj-kondo/
8+
9+# Nix build symlinks.
10+/result
11+/result-*
added flake.lock +81 -0
new file mode 100644
@@ -0,0 +1,81 @@
1+{
2+ "nodes": {
3+ "glimmer": {
4+ "flake": false,
5+ "locked": {
6+ "lastModified": 1788135429,
7+ "narHash": "sha256-20W4kmcL05O7V7EQKXuH+kVBpBtQZhhj5yTm9QqxQss=",
8+ "ref": "refs/heads/main",
9+ "rev": "399df371c790d690fb6e4560c3d4d7f838502857",
10+ "revCount": 32,
11+ "type": "git",
12+ "url": "https://gitlab.com/nandithebull/glimmer"
13+ },
14+ "original": {
15+ "rev": "399df371c790d690fb6e4560c3d4d7f838502857",
16+ "type": "git",
17+ "url": "https://gitlab.com/nandithebull/glimmer"
18+ }
19+ },
20+ "jolt-native": {
21+ "flake": false,
22+ "locked": {
23+ "lastModified": 1788142815,
24+ "narHash": "sha256-ehaIvgeA7ce9m7T9AxyPTHpnYVrG2lQRH3RkkF612GY=",
25+ "ref": "refs/heads/main",
26+ "rev": "70072e8e48ad396caeab6f6bd18b999cf449ec8f",
27+ "revCount": 13,
28+ "type": "git",
29+ "url": "https://gitlab.com/nandithebull/jolt-native"
30+ },
31+ "original": {
32+ "type": "git",
33+ "url": "https://gitlab.com/nandithebull/jolt-native"
34+ }
35+ },
36+ "jolt-src": {
37+ "flake": false,
38+ "locked": {
39+ "lastModified": 1788135812,
40+ "narHash": "sha256-DvVmjNN09mfxp67Y9zOyXNbAIYR9h2gpuyTbdj777a8=",
41+ "ref": "refs/heads/main",
42+ "rev": "6f1d436f6e4cb1e2f878c5fc90c39a48a8bf4310",
43+ "revCount": 2803,
44+ "submodules": true,
45+ "type": "git",
46+ "url": "https://github.com/jolt-lang/jolt"
47+ },
48+ "original": {
49+ "submodules": true,
50+ "type": "git",
51+ "url": "https://github.com/jolt-lang/jolt"
52+ }
53+ },
54+ "nixpkgs": {
55+ "locked": {
56+ "lastModified": 1788039129,
57+ "narHash": "sha256-pa4Q0qErvCvzCaaUph7Sm37RhR4xvPrYI8Lgz6k85+A=",
58+ "owner": "NixOS",
59+ "repo": "nixpkgs",
60+ "rev": "d2f67949798825fe853f7c5d0492b8bf016d3f88",
61+ "type": "github"
62+ },
63+ "original": {
64+ "owner": "NixOS",
65+ "ref": "nixos-unstable",
66+ "repo": "nixpkgs",
67+ "type": "github"
68+ }
69+ },
70+ "root": {
71+ "inputs": {
72+ "glimmer": "glimmer",
73+ "jolt-native": "jolt-native",
74+ "jolt-src": "jolt-src",
75+ "nixpkgs": "nixpkgs"
76+ }
77+ }
78+ },
79+ "root": "root",
80+ "version": 7
81+}
new file mode 100644
@@ -0,0 +1,81 @@
1+{
2+ "nodes": {
3+ "glimmer": {
4+ "flake": false,
5+ "locked": {
6+ "lastModified": 1788135429,
7+ "narHash": "sha256-20W4kmcL05O7V7EQKXuH+kVBpBtQZhhj5yTm9QqxQss=",
8+ "ref": "refs/heads/main",
9+ "rev": "399df371c790d690fb6e4560c3d4d7f838502857",
10+ "revCount": 32,
11+ "type": "git",
12+ "url": "https://gitlab.com/nandithebull/glimmer"
13+ },
14+ "original": {
15+ "rev": "399df371c790d690fb6e4560c3d4d7f838502857",
16+ "type": "git",
17+ "url": "https://gitlab.com/nandithebull/glimmer"
18+ }
19+ },
20+ "jolt-native": {
21+ "flake": false,
22+ "locked": {
23+ "lastModified": 1788142815,
24+ "narHash": "sha256-ehaIvgeA7ce9m7T9AxyPTHpnYVrG2lQRH3RkkF612GY=",
25+ "ref": "refs/heads/main",
26+ "rev": "70072e8e48ad396caeab6f6bd18b999cf449ec8f",
27+ "revCount": 13,
28+ "type": "git",
29+ "url": "https://gitlab.com/nandithebull/jolt-native"
30+ },
31+ "original": {
32+ "type": "git",
33+ "url": "https://gitlab.com/nandithebull/jolt-native"
34+ }
35+ },
36+ "jolt-src": {
37+ "flake": false,
38+ "locked": {
39+ "lastModified": 1788135812,
40+ "narHash": "sha256-DvVmjNN09mfxp67Y9zOyXNbAIYR9h2gpuyTbdj777a8=",
41+ "ref": "refs/heads/main",
42+ "rev": "6f1d436f6e4cb1e2f878c5fc90c39a48a8bf4310",
43+ "revCount": 2803,
44+ "submodules": true,
45+ "type": "git",
46+ "url": "https://github.com/jolt-lang/jolt"
47+ },
48+ "original": {
49+ "submodules": true,
50+ "type": "git",
51+ "url": "https://github.com/jolt-lang/jolt"
52+ }
53+ },
54+ "nixpkgs": {
55+ "locked": {
56+ "lastModified": 1788039129,
57+ "narHash": "sha256-pa4Q0qErvCvzCaaUph7Sm37RhR4xvPrYI8Lgz6k85+A=",
58+ "owner": "NixOS",
59+ "repo": "nixpkgs",
60+ "rev": "d2f67949798825fe853f7c5d0492b8bf016d3f88",
61+ "type": "github"
62+ },
63+ "original": {
64+ "owner": "NixOS",
65+ "ref": "nixos-unstable",
66+ "repo": "nixpkgs",
67+ "type": "github"
68+ }
69+ },
70+ "root": {
71+ "inputs": {
72+ "glimmer": "glimmer",
73+ "jolt-native": "jolt-native",
74+ "jolt-src": "jolt-src",
75+ "nixpkgs": "nixpkgs"
76+ }
77+ }
78+ },
79+ "root": "root",
80+ "version": 7
81+}
added flake.nix +206 -0
new file mode 100644
@@ -0,0 +1,206 @@
1+{
2+ # frq is Jolt source, so "building" it is three things, not one:
3+ #
4+ # jolt the runtime that reads it (github:jolt-lang/jolt)
5+ # jolt-native libvidya and libjoltmoq, in Rust (gitlab:nandithebull/jolt-native)
6+ # frq this tree, with its deps resolved to store paths
7+ #
8+ # Jolt resolves deps.edn by running git at startup, which a build sandbox has
9+ # no network for — so every dep is fetched by Nix instead and handed back as
10+ # a :local/root through -Sdeps.
11+ #
12+ # nix build .#frq && ./result/bin/frq
13+ #
14+ # On a machine that is not NixOS the GL driver is the host's and the loader
15+ # will not find it, so the window never opens ("GL display: argument does not
16+ # name a valid config"). Launch through nixGL there:
17+ #
18+ # nix run --impure "git+https://github.com/nix-community/nixGL#nixGLIntel" -- ./result/bin/frq
19+ description = "frq a freeq client in jolt";
20+
21+ inputs = {
22+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
23+ # Jolt's own flake declares `self.submodules`, which this Nix rejects when
24+ # the flake is fetched through the github scheme — so take the source and
25+ # build it here. `vendor/` is a submodule and the build needs it.
26+ jolt-src = {
27+ url = "git+https://github.com/jolt-lang/jolt?submodules=1";
28+ flake = false;
29+ };
30+
31+ jolt-native = {
32+ url = "git+https://gitlab.com/nandithebull/jolt-native";
33+ flake = false;
34+ };
35+
36+ # The sha deps.edn pins, on the fork with the reconciler fixes.
37+ glimmer = {
38+ url = "git+https://gitlab.com/nandithebull/glimmer?rev=399df371c790d690fb6e4560c3d4d7f838502857";
39+ flake = false;
40+ };
41+ };
42+
43+ outputs = { self, nixpkgs, jolt-src, jolt-native, glimmer }:
44+ let
45+ systems = [ "x86_64-linux" "aarch64-linux" ];
46+ forEachSystem = f:
47+ nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
48+ in
49+ {
50+ packages = forEachSystem (pkgs:
51+ let
52+ inherit (pkgs) lib;
53+
54+ # libvidya (the retained-tree ABI glimmer-vidya binds, on egui) and
55+ # libjoltmoq (the AV media plane). One workspace, two cdylibs.
56+ native = pkgs.rustPlatform.buildRustPackage {
57+ pname = "jolt-native";
58+ version = "0.1.0";
59+ src = jolt-native;
60+
61+ cargoLock = {
62+ lockFile = "${jolt-native}/Cargo.lock";
63+ allowBuiltinFetchGit = true;
64+ };
65+
66+ nativeBuildInputs = with pkgs; [
67+ pkg-config
68+ cmake
69+ rustPlatform.bindgenHook
70+ ];
71+
72+ buildInputs = with pkgs; [
73+ alsa-lib
74+ pipewire
75+ openssl
76+ libxkbcommon
77+ wayland
78+ libGL
79+ ];
80+
81+ # Upstream's .cargo/config.toml drives the whole build through
82+ # DotSlash: a pinned rustc, sccache, and zig as the C/C++ compiler
83+ # and linker, each fetched from the network on first use. None of
84+ # that survives a build sandbox, and none of it is needed when the
85+ # toolchain comes from the store — so drop it and let stdenv's cc
86+ # link (openh264 is C++, which stdenv covers too).
87+ postPatch = ''
88+ rm -f .cargo/config.toml
89+ '';
90+
91+ # bindgen reads linux/videodev2.h directly; upstream points it at
92+ # zig's bundled headers, which under Nix is just the kernel headers.
93+ V4L2R_VIDEODEV2_H_PATH = "${pkgs.linuxHeaders}/include";
94+
95+ doCheck = false;
96+
97+ installPhase = ''
98+ runHook preInstall
99+ mkdir -p "$out/lib"
100+ install -m644 target/*/release/*.so "$out/lib/"
101+ runHook postInstall
102+ '';
103+ };
104+
105+ # Jolt itself: Clojure on Chez, built the way its own flake builds it.
106+ joltRuntime = pkgs.stdenv.mkDerivation {
107+ pname = "jolt";
108+ version = "dev";
109+ src = jolt-src;
110+
111+ strictDeps = true;
112+ nativeBuildInputs = with pkgs; [ chez makeWrapper pkg-config xxd ];
113+ buildInputs = with pkgs; [ lz4 zlib ncurses openssl libuuid ];
114+
115+ JOLT_VERSION = "dev";
116+ dontConfigure = true;
117+
118+ buildPhase = ''
119+ runHook preBuild
120+ scheme --script host/chez/build-jolt.ss release target/release/jolt
121+ runHook postBuild
122+ '';
123+
124+ installPhase = ''
125+ runHook preInstall
126+ mkdir -p "$out/bin"
127+ install -m755 target/release/jolt "$out/bin/jolt"
128+ runHook postInstall
129+ '';
130+
131+ # jolt.deps shells out to git and unzip, and jolt.mvn-http dlopens
132+ # OpenSSL through the JOLT_OPENSSL_LIBDIR seam.
133+ postFixup = ''
134+ wrapProgram "$out/bin/jolt" \
135+ --prefix PATH : "${pkgs.lib.makeBinPath [ pkgs.git pkgs.unzip ]}" \
136+ --set-default JOLT_OPENSSL_LIBDIR "${pkgs.lib.makeLibraryPath [ pkgs.openssl ]}" \
137+ --set-default SSL_CERT_FILE "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
138+ '';
139+ };
140+
141+ # glimmer-vidya lives inside the jolt-native checkout, and its own
142+ # deps.edn asks for glimmer by git — the top-level override below
143+ # answers for both.
144+ glimmerVidya = "${jolt-native}/jolt/glimmer-vidya";
145+
146+ # egui reaches for these with dlopen rather than linking them, so
147+ # being in the cdylib's buildInputs is not enough — the launcher has
148+ # to put them on the loader path itself. Without libX11 here, vidya
149+ # reports "X11 unavailable", falls back to Wayland, and winit refuses
150+ # to build a second event loop after the failed first one.
151+ runtimeLibs = with pkgs; [
152+ libGL
153+ libxkbcommon
154+ wayland
155+ xorg.libX11
156+ xorg.libXcursor
157+ xorg.libXi
158+ xorg.libXrandr
159+ vulkan-loader
160+ ];
161+
162+ # The project as jolt sees it: source, deps.edn, nothing else.
163+ frqSource = pkgs.runCommand "frq-source" { } ''
164+ mkdir -p "$out"
165+ cp -r ${self}/src ${self}/deps.edn "$out/"
166+ '';
167+
168+ # Jolt resolves deps.edn from the working directory, so the launcher
169+ # runs from the store copy. Its .jolt/cpcache write lands on a
170+ # read-only directory and jolt treats that as a quiet cache miss, so
171+ # the only cost is re-resolving the (already local) graph per start.
172+ frqScript = pkgs.writeShellScript "frq" ''
173+ export LD_LIBRARY_PATH="${native}/lib:${lib.makeLibraryPath runtimeLibs}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
174+ cd ${frqSource}
175+ exec ${joltRuntime}/bin/jolt \
176+ -Sdeps '{:deps {jolt-lang/glimmer {:local/root "${glimmer}"} nandi/glimmer-vidya {:local/root "${glimmerVidya}"}}}' \
177+ -M:frq "$@"
178+ '';
179+
180+ frq = pkgs.runCommand "frq-0.1.0"
181+ {
182+ meta = {
183+ description = "A freeq client in jolt";
184+ mainProgram = "frq";
185+ platforms = systems;
186+ };
187+ }
188+ ''
189+ mkdir -p "$out/bin"
190+ ln -s ${frqScript} "$out/bin/frq"
191+ '';
192+ in
193+ {
194+ inherit native frq;
195+ jolt = joltRuntime;
196+ default = frq;
197+ });
198+
199+ apps = forEachSystem (pkgs: {
200+ default = {
201+ type = "app";
202+ program = "${self.packages.${pkgs.stdenv.hostPlatform.system}.frq}/bin/frq";
203+ };
204+ });
205+ };
206+}
new file mode 100644
@@ -0,0 +1,206 @@
1+{
2+ # frq is Jolt source, so "building" it is three things, not one:
3+ #
4+ # jolt the runtime that reads it (github:jolt-lang/jolt)
5+ # jolt-native libvidya and libjoltmoq, in Rust (gitlab:nandithebull/jolt-native)
6+ # frq this tree, with its deps resolved to store paths
7+ #
8+ # Jolt resolves deps.edn by running git at startup, which a build sandbox has
9+ # no network for — so every dep is fetched by Nix instead and handed back as
10+ # a :local/root through -Sdeps.
11+ #
12+ # nix build .#frq && ./result/bin/frq
13+ #
14+ # On a machine that is not NixOS the GL driver is the host's and the loader
15+ # will not find it, so the window never opens ("GL display: argument does not
16+ # name a valid config"). Launch through nixGL there:
17+ #
18+ # nix run --impure "git+https://github.com/nix-community/nixGL#nixGLIntel" -- ./result/bin/frq
19+ description = "frq a freeq client in jolt";
20+
21+ inputs = {
22+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
23+ # Jolt's own flake declares `self.submodules`, which this Nix rejects when
24+ # the flake is fetched through the github scheme — so take the source and
25+ # build it here. `vendor/` is a submodule and the build needs it.
26+ jolt-src = {
27+ url = "git+https://github.com/jolt-lang/jolt?submodules=1";
28+ flake = false;
29+ };
30+
31+ jolt-native = {
32+ url = "git+https://gitlab.com/nandithebull/jolt-native";
33+ flake = false;
34+ };
35+
36+ # The sha deps.edn pins, on the fork with the reconciler fixes.
37+ glimmer = {
38+ url = "git+https://gitlab.com/nandithebull/glimmer?rev=399df371c790d690fb6e4560c3d4d7f838502857";
39+ flake = false;
40+ };
41+ };
42+
43+ outputs = { self, nixpkgs, jolt-src, jolt-native, glimmer }:
44+ let
45+ systems = [ "x86_64-linux" "aarch64-linux" ];
46+ forEachSystem = f:
47+ nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
48+ in
49+ {
50+ packages = forEachSystem (pkgs:
51+ let
52+ inherit (pkgs) lib;
53+
54+ # libvidya (the retained-tree ABI glimmer-vidya binds, on egui) and
55+ # libjoltmoq (the AV media plane). One workspace, two cdylibs.
56+ native = pkgs.rustPlatform.buildRustPackage {
57+ pname = "jolt-native";
58+ version = "0.1.0";
59+ src = jolt-native;
60+
61+ cargoLock = {
62+ lockFile = "${jolt-native}/Cargo.lock";
63+ allowBuiltinFetchGit = true;
64+ };
65+
66+ nativeBuildInputs = with pkgs; [
67+ pkg-config
68+ cmake
69+ rustPlatform.bindgenHook
70+ ];
71+
72+ buildInputs = with pkgs; [
73+ alsa-lib
74+ pipewire
75+ openssl
76+ libxkbcommon
77+ wayland
78+ libGL
79+ ];
80+
81+ # Upstream's .cargo/config.toml drives the whole build through
82+ # DotSlash: a pinned rustc, sccache, and zig as the C/C++ compiler
83+ # and linker, each fetched from the network on first use. None of
84+ # that survives a build sandbox, and none of it is needed when the
85+ # toolchain comes from the store — so drop it and let stdenv's cc
86+ # link (openh264 is C++, which stdenv covers too).
87+ postPatch = ''
88+ rm -f .cargo/config.toml
89+ '';
90+
91+ # bindgen reads linux/videodev2.h directly; upstream points it at
92+ # zig's bundled headers, which under Nix is just the kernel headers.
93+ V4L2R_VIDEODEV2_H_PATH = "${pkgs.linuxHeaders}/include";
94+
95+ doCheck = false;
96+
97+ installPhase = ''
98+ runHook preInstall
99+ mkdir -p "$out/lib"
100+ install -m644 target/*/release/*.so "$out/lib/"
101+ runHook postInstall
102+ '';
103+ };
104+
105+ # Jolt itself: Clojure on Chez, built the way its own flake builds it.
106+ joltRuntime = pkgs.stdenv.mkDerivation {
107+ pname = "jolt";
108+ version = "dev";
109+ src = jolt-src;
110+
111+ strictDeps = true;
112+ nativeBuildInputs = with pkgs; [ chez makeWrapper pkg-config xxd ];
113+ buildInputs = with pkgs; [ lz4 zlib ncurses openssl libuuid ];
114+
115+ JOLT_VERSION = "dev";
116+ dontConfigure = true;
117+
118+ buildPhase = ''
119+ runHook preBuild
120+ scheme --script host/chez/build-jolt.ss release target/release/jolt
121+ runHook postBuild
122+ '';
123+
124+ installPhase = ''
125+ runHook preInstall
126+ mkdir -p "$out/bin"
127+ install -m755 target/release/jolt "$out/bin/jolt"
128+ runHook postInstall
129+ '';
130+
131+ # jolt.deps shells out to git and unzip, and jolt.mvn-http dlopens
132+ # OpenSSL through the JOLT_OPENSSL_LIBDIR seam.
133+ postFixup = ''
134+ wrapProgram "$out/bin/jolt" \
135+ --prefix PATH : "${pkgs.lib.makeBinPath [ pkgs.git pkgs.unzip ]}" \
136+ --set-default JOLT_OPENSSL_LIBDIR "${pkgs.lib.makeLibraryPath [ pkgs.openssl ]}" \
137+ --set-default SSL_CERT_FILE "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
138+ '';
139+ };
140+
141+ # glimmer-vidya lives inside the jolt-native checkout, and its own
142+ # deps.edn asks for glimmer by git — the top-level override below
143+ # answers for both.
144+ glimmerVidya = "${jolt-native}/jolt/glimmer-vidya";
145+
146+ # egui reaches for these with dlopen rather than linking them, so
147+ # being in the cdylib's buildInputs is not enough — the launcher has
148+ # to put them on the loader path itself. Without libX11 here, vidya
149+ # reports "X11 unavailable", falls back to Wayland, and winit refuses
150+ # to build a second event loop after the failed first one.
151+ runtimeLibs = with pkgs; [
152+ libGL
153+ libxkbcommon
154+ wayland
155+ xorg.libX11
156+ xorg.libXcursor
157+ xorg.libXi
158+ xorg.libXrandr
159+ vulkan-loader
160+ ];
161+
162+ # The project as jolt sees it: source, deps.edn, nothing else.
163+ frqSource = pkgs.runCommand "frq-source" { } ''
164+ mkdir -p "$out"
165+ cp -r ${self}/src ${self}/deps.edn "$out/"
166+ '';
167+
168+ # Jolt resolves deps.edn from the working directory, so the launcher
169+ # runs from the store copy. Its .jolt/cpcache write lands on a
170+ # read-only directory and jolt treats that as a quiet cache miss, so
171+ # the only cost is re-resolving the (already local) graph per start.
172+ frqScript = pkgs.writeShellScript "frq" ''
173+ export LD_LIBRARY_PATH="${native}/lib:${lib.makeLibraryPath runtimeLibs}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
174+ cd ${frqSource}
175+ exec ${joltRuntime}/bin/jolt \
176+ -Sdeps '{:deps {jolt-lang/glimmer {:local/root "${glimmer}"} nandi/glimmer-vidya {:local/root "${glimmerVidya}"}}}' \
177+ -M:frq "$@"
178+ '';
179+
180+ frq = pkgs.runCommand "frq-0.1.0"
181+ {
182+ meta = {
183+ description = "A freeq client in jolt";
184+ mainProgram = "frq";
185+ platforms = systems;
186+ };
187+ }
188+ ''
189+ mkdir -p "$out/bin"
190+ ln -s ${frqScript} "$out/bin/frq"
191+ '';
192+ in
193+ {
194+ inherit native frq;
195+ jolt = joltRuntime;
196+ default = frq;
197+ });
198+
199+ apps = forEachSystem (pkgs: {
200+ default = {
201+ type = "app";
202+ program = "${self.packages.${pkgs.stdenv.hostPlatform.system}.frq}/bin/frq";
203+ };
204+ });
205+ };
206+}