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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ Nix packages for AI coding agents and development tools. Automatically updated d
- **Usage**: `nix run github:numtide/llm-agents.nix#bb-app -- --help`
- **Nix**: [packages/bb-app/package.nix](packages/bb-app/package.nix)

</details>
<details>
<summary><strong>chatgpt</strong> - Desktop application for ChatGPT and Codex</summary>

- **Source**: binary
- **License**: unfree
- **Homepage**: https://developers.openai.com/codex/app
- **Usage**: `nix run github:numtide/llm-agents.nix#chatgpt -- --help`
- **Nix**: [packages/chatgpt/package.nix](packages/chatgpt/package.nix)

</details>
<details>
<summary><strong>claude-code</strong> - Agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster</summary>
Expand Down
5 changes: 5 additions & 0 deletions lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ inputs."nixpkgs".lib.extend (
githubId = 20773762;
name = "JachinShen";
};
whazor = {
github = "whazor";
githubId = 184182;
name = "Nanne";
};
};
}
)
14 changes: 14 additions & 0 deletions packages/chatgpt/hashes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"sources": {
"aarch64-linux": {
"version": "26.810.50856",
"url": "https://persistent.oaistatic.com/codex-app-prod/linux/deb/pool/main/c/chatgpt/chatgpt_26.810.50856_arm64.deb",
"hash": "sha256-q4UbLrczdCKfjaoXmT0GydAxCU0O6sXn9OAsByoRD2I="
},
"x86_64-linux": {
"version": "26.810.50856",
"url": "https://persistent.oaistatic.com/codex-app-prod/linux/deb/pool/main/c/chatgpt/chatgpt_26.810.50856_amd64.deb",
"hash": "sha256-47R8EpjgHkoqpU8SDrFpg0xpEb0pUSK8Q+XNFkLBpLo="
}
}
}
28 changes: 28 additions & 0 deletions packages/chatgpt/openai-archive-key.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBGpypFUBEACi1Vvzq9pIpA6lj7chbqELuxJtVuzUzxrasa6ZU0yF4yhq7jf8
3YkJRHwbezBKeQyzJ5lkX0EhXS8aXxUhMAm3PFpAlwcInfKzmV7atJwvaxIw6Rmd
GYe9fBWKjTN/SmPIjtyxrTznZY97+TfD1AeGZpLaJ8fsnhrC+HkiN2TACiTocgpe
hFiP0OWK7mWZeTWnY2scpIYXP1Ro7nQv4KacmY4JacTQ7m/HM0Qej/3olhuEv2Cw
lMVWw57/oHhmTllfLDQOogFQyIVqaaR98y/Eu6cAabSfcsqAAZ2A8vfHYD27z28J
vLO2PZEJd5ThlnX4Zqv0eIpZdBj//8Sl/MSqTshFZ1NDsRoqwdqw284X5MpnOJ4k
4Sc2Se8tJxt/nCeibH3dJ504Fb1X/mnOqhCAQ6pVJz4RB5HRlFPSkxVPyag1v1m/
7T4vie+OR4eqFQNz6mudrOoMmeVIfyL5fbe4cOr4fk/FyvEE2xMgkFatPqXn7vM9
og+zremPCfwRAFpBPyX74VowFY7llcdaj/w8K5T8PzM14Hb3E4ZKizMluKmTvTq9
WE1/eSQJLLQqXD5VmtmdUaC/VyE/1ZlIxcA1LWqvEQ327UXREvX/nHsrkKrl956W
jzkiHFUTsD1NJ0dMfs+csOt8Furb5jZj+HsMmCm9jLdfz5b/4WKLPbvxIwARAQAB
tBZDb2RleCBMaW51eCBSZXBvc2l0b3J5iQJRBBMBCgA7FiEEO/oOSui4zBai2bpo
SjtKVmxGYOQFAmpypFUCGwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQ
SjtKVmxGYORlCQ/9FyikZo8HQcJBP9E/oXVPds/fQnIFB2qJR2z3DrfYEonNt/ev
SAySkPPq4/mEOjaI0pFlDDGSaps+FTcJFgoVRTasBIF7JJivvjW9ap8iWEbhhVLe
IrFLbMLpUcTRntUx7R4fVMJ/1/cGn+NWZmNwS9ORorzSyCH0IAgCw1Xc3ZrjuMbF
VjdToMC1TiXXCEmlYpQakmQ3Ay1cH0FHC2BBNn1MNVkJdPhpZIZCdhaMPHfYFpyo
pg8wFvZ5iIcvlbMgyuy8CPJVRWUcYy2dOhEOGnYJnXRPkE3E1hf8YOHNzRlduH89
6lT9qcEK2+fpLfrVGoc4zscLZ+Ey+Ko6iQRdVE1j67+wNR3hX8ukue574v1N/xxu
i575jumSE19lEj1sH4+P4gFHOtTbF0JhKKzLctbga0IAwTPKhnt3qzj1U5Yj/MZS
uEVjrLhdRauOuFBXUclgyVf2w/lE85UUOdlcollsYA6Huq7xDamqf8SslZQGre3E
I+lhpqJR1cOwDMUzzcl40uTyhrxXXd/bk4QSlhZbwHR25Pnt+ZMtWavlQWS0eDEV
8djuXAURCmx5WOqAFB/TJe1mn5EvyWg4VFzrY/NVNOpzgY5+Xp7J28z7f637r712
Eu9j4imVcdPigwS+jf/0f81i2o9b82Y26TN8+EtDLCY841MJ1lrjDrX/dno=
=Y+3h
-----END PGP PUBLIC KEY BLOCK-----
192 changes: 192 additions & 0 deletions packages/chatgpt/package.nix
Comment thread
whazor marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
{
lib,
flake,
stdenv,
fetchurl,
coreutils,
dpkg,
formatelf,
makeWrapper,
python3,
wrapGAppsHook3,
alsa-lib,
at-spi2-atk,
at-spi2-core,
atk,
cairo,
cups,
dbus,
expat,
fontconfig,
freetype,
gdk-pixbuf,
glib,
gtk3,
libGL,
libdrm,
libgbm,
libnotify,
libpulseaudio,
libsecret,
libusb1,
libxkbcommon,
nspr,
nss,
pango,
pipewire,
qt5,
qt6,
systemd,
wayland,
xdg-utils,
libx11,
libxcomposite,
libxdamage,
libxext,
libxfixes,
libxrandr,
libxcb,
}:

let
sourceData = builtins.fromJSON (builtins.readFile ./hashes.json);
platform = stdenv.hostPlatform.system;
source = sourceData.sources.${platform} or (throw "Unsupported system: ${platform}");
in
stdenv.mkDerivation {
Comment thread
whazor marked this conversation as resolved.
pname = "chatgpt";
inherit (source) version;

src = fetchurl {
inherit (source) url hash;
};

dontStrip = true;
dontWrapGApps = true;

nativeBuildInputs = [
formatelf
dpkg
makeWrapper
python3
wrapGAppsHook3
];

buildInputs = [
alsa-lib
at-spi2-atk
at-spi2-core
atk
cairo
cups
dbus
expat
fontconfig
freetype
gdk-pixbuf
glib
gtk3
libGL
libdrm
libgbm
libnotify
libpulseaudio
libusb1
libxkbcommon
nspr
nss
pango
pipewire
stdenv.cc.cc.lib
systemd
wayland
libx11
libxcomposite
libxdamage
libxext
libxfixes
libxrandr
libxcb
];

# Electron loads these at runtime rather than linking them directly. Put
# them on each ELF object's RPATH without leaking a broad LD_LIBRARY_PATH
# into Electron's Node and Chromium children.
runtimeDependencies = [
libGL
libgbm
libsecret
pipewire
wayland
];
Comment thread
whazor marked this conversation as resolved.

# The archive includes musl, glibc, and Android prebuilds for a few Node
# modules. NixOS uses the glibc variants, so the other runtimes are
# intentionally absent.
# The Qt shims are optional and selected dynamically, so autoPatchelf cannot
# resolve both of their runtimes during its direct dependency pass. Their
# version-specific RPATHs are added in postFixup below.
autoPatchelfIgnoreMissingDeps = [
"libc++_shared.so"
"libc.musl-x86_64.so.1"
"liblog.so"
"libQt5Core.so.5"
"libQt5Gui.so.5"
"libQt5Widgets.so.5"
"libQt6Core.so.6"
"libQt6Gui.so.6"
"libQt6Widgets.so.6"
];

unpackPhase = ''
runHook preUnpack
dpkg-deb -x "$src" .
runHook postUnpack
'';

installPhase = ''
runHook preInstall

mkdir -p "$out/bin" "$out/lib" "$out/share"
cp -r usr/lib/chatgpt "$out/lib/"
cp -r usr/share/applications usr/share/pixmaps "$out/share/"
ln -s ../lib/chatgpt/codex-launcher "$out/bin/chatgpt"

# See patch-asar.py for the NixOS-specific source patches.
python3 ${./patch-asar.py} "$out/lib/chatgpt/resources/app.asar"

wrapProgram "$out/lib/chatgpt/ChatGPT" \
"''${gappsWrapperArgs[@]}" \
--prefix PATH : ${
lib.makeBinPath [
coreutils
xdg-utils
]
}

runHook postInstall
'';

postFixup = ''
patchelf --add-rpath ${lib.makeLibraryPath [ qt5.qtbase ]} \
"$out/lib/chatgpt/libqt5_shim.so"
patchelf --add-rpath ${lib.makeLibraryPath [ qt6.qtbase ]} \
"$out/lib/chatgpt/libqt6_shim.so"
'';

passthru.category = "AI Coding Agents";

meta = with lib; {
description = "Desktop application for ChatGPT and Codex";
homepage = "https://developers.openai.com/codex/app";
changelog = "https://learn.chatgpt.com/docs/changelog";
license = flake.lib.licenses.unfree;
sourceProvenance = with sourceTypes; [ binaryNativeCode ];
maintainers = with flake.lib.maintainers; [ whazor ];
mainProgram = "chatgpt";
platforms = [
"x86_64-linux"
"aarch64-linux"
];
};
}
42 changes: 42 additions & 0 deletions packages/chatgpt/patch-asar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
"""Apply byte-length-preserving source patches inside app.asar.

The asar header records file offsets, so every replacement is padded with
spaces to the original's exact byte length instead of re-packing the archive.
"""

import sys
from pathlib import Path

# @parcel/watcher uses detect-libc in a named worker. Its process.report
# fallback trips a CFI guard in the bundled Owl/Electron runtime on NixOS.
# detect-libc falls back to its ELF/filesystem/ldd probes instead.
SKIP_PROCESS_REPORT = (
b"isLinux() && process.report",
b"false /* nix:skip report */",
)

# The app materializes bundled plugins in ~/.codex and rewrites selected
# manifests there. Node's fs.cp preserves the Nix store's read-only modes,
# so copy with coreutils and make only the user-owned destination writable.
COPY_PLUGINS_WRITABLE = (
b'async function Mne(e,t){if(S.default.platform===`darwin`){await lne(`/usr/bin/ditto`,[`--noqtn`,e,t]);return}if(S.default.platform!==`win32`){await y.default.cp(e,t,{recursive:!0,verbatimSymlinks:!0});return}let{copyDirectoryAllowDecryptedDestinationOnEncryptionFailure:n}=await Promise.resolve().then(()=>require("./windows-file-copy-Bw9CB6bJ.js"));await n({copy:()=>y.default.cp(e,t,{recursive:!0,verbatimSymlinks:!0}),destination:t,source:e})}',
b'async function Mne(e,t){let r=S.default.platform;if(r===`darwin`){await lne(`/usr/bin/ditto`,[`--noqtn`,e,t]);return}if(r!==`win32`){await lne(`cp`,[`-r`,e+`/.`,t]);await lne(`chmod`,[`-R`,`u+w`,t]);return}let{copyDirectoryAllowDecryptedDestinationOnEncryptionFailure:n}=await Promise.resolve().then(()=>require("./windows-file-copy-Bw9CB6bJ.js"));await n({copy:()=>y.default.cp(e,t,{recursive:!0,verbatimSymlinks:!0}),destination:t,source:e})}',
)


def main() -> None:
"""Patch the asar archive given as the only argument."""
asar = Path(sys.argv[1])
data = asar.read_bytes()
for original, replacement in (SKIP_PROCESS_REPORT, COPY_PLUGINS_WRITABLE):
if len(replacement) > len(original):
sys.exit(f"replacement longer than original: {replacement[:60]!r}...")
if original not in data:
sys.exit(f"pattern not found in {asar}: {original[:60]!r}...")
data = data.replace(original, replacement.ljust(len(original), b" "))
asar.write_bytes(data)


if __name__ == "__main__":
main()
Loading
Loading