fix(ai-cli): install current upstream Codex and Claude releases
- Pin official Codex 0.161.0 and Claude Code 2.1.292 binaries - Preserve Codex helpers and Claude's NixOS compatibility wrapper - Add an updater for stable upstream versions and checksums - Document CLI updates independently of stable NixOS inputs
This commit is contained in:
1 parent
2759278aff
commit
0aac36b7de
6 files changed
+155
-5
No files matched your search
@@ -104,9 +104,28 @@ Studio remains on the ThinkPads as an existing IDE/workflow choice, but its
|
|||||||
bundled SDK is worth reviewing if moving fully to project-local tooling.
|
bundled SDK is worth reviewing if moving fully to project-local tooling.
|
||||||
|
|
||||||
Codex CLI (`codex`) and Claude Code (`claude`) are installed on every host.
|
Codex CLI (`codex`) and Claude Code (`claude`) are installed on every host.
|
||||||
Run each command as your normal user and follow its sign-in flow. Update these
|
Run each command as your normal user and follow its sign-in flow.
|
||||||
Nix-managed packages through the flake and rebuild rather than their standalone
|
They use official upstream binaries, with versions and SHA-256 checksums pinned
|
||||||
installers. Authentication stays in your home directory, outside this repo.
|
in `packages/ai-cli-versions.json`, independently of the stable NixOS release.
|
||||||
|
`packages/ai-cli.nix` packages Codex and reuses Nixpkgs' Claude Code wrapper.
|
||||||
|
Authentication stays in your home directory, outside this repo.
|
||||||
|
|
||||||
|
To update both to the latest stable upstream releases, run from this repo:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix shell nixpkgs#python3 -c python3 scripts/update-ai-cli.py
|
||||||
|
git diff -- packages/ai-cli-versions.json
|
||||||
|
nix build .#codex .#claude-code --no-link
|
||||||
|
sudo nixos-rebuild switch --flake .#framework
|
||||||
|
codex --version
|
||||||
|
claude --version
|
||||||
|
git add packages/ai-cli-versions.json
|
||||||
|
git commit -m "chore(ai-cli): update upstream releases"
|
||||||
|
```
|
||||||
|
|
||||||
|
Use your own host name when rebuilding. The updater changes only the CLI pins;
|
||||||
|
`nix flake update` updates NixOS inputs but does not update these pins.
|
||||||
|
Updates remain explicit and reproducible; no upstream self-updaters are used.
|
||||||
|
|
||||||
The official ChatGPT Linux desktop preview does not formally support NixOS.
|
The official ChatGPT Linux desktop preview does not formally support NixOS.
|
||||||
Our pinned `chatgpt` package supports macOS only, so it is not installed here.
|
Our pinned `chatgpt` package supports macOS only, so it is not installed here.
|
||||||
|
|||||||
@@ -19,6 +19,14 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
# Build the CLI packages separately before switching a host.
|
||||||
|
packages.x86_64-linux = import ./packages/ai-cli.nix {
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
config.allowUnfree = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
nixosConfigurations = {
|
nixosConfigurations = {
|
||||||
t450s = nixpkgs.lib.nixosSystem {
|
t450s = nixpkgs.lib.nixosSystem {
|
||||||
specialArgs.unstable = import nixpkgs-unstable {
|
specialArgs.unstable = import nixpkgs-unstable {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
aiCli = import ../packages/ai-cli.nix { inherit pkgs; };
|
||||||
|
in
|
||||||
{
|
{
|
||||||
# Workstation tools only; language runtimes and SDKs belong in projects.
|
# Workstation tools only; language runtimes and SDKs belong in projects.
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
@@ -19,8 +22,8 @@
|
|||||||
zip
|
zip
|
||||||
|
|
||||||
# Coding assistants; authenticate separately as your normal user.
|
# Coding assistants; authenticate separately as your normal user.
|
||||||
codex
|
aiCli.codex
|
||||||
claude-code
|
aiCli.claude-code
|
||||||
|
|
||||||
# Infrastructure, containers, and structured data.
|
# Infrastructure, containers, and structured data.
|
||||||
ansible
|
ansible
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"codex": {
|
||||||
|
"version": "0.161.0",
|
||||||
|
"sha256": "04d8ab9dbcb9df0edf3c67dca5072a374babfdf762a9bc4ae649ae140b8e2cf0"
|
||||||
|
},
|
||||||
|
"claude-code": {
|
||||||
|
"version": "2.1.292",
|
||||||
|
"sha256": "a967e7b1d8b4e47ee421d5433027880347952b0c0857abf880e2c942a4ec93b3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{ pkgs }:
|
||||||
|
let
|
||||||
|
versions = builtins.fromJSON (builtins.readFile ./ai-cli-versions.json);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Use the complete official package, including Code Mode and voice helpers.
|
||||||
|
codex = pkgs.stdenvNoCC.mkDerivation {
|
||||||
|
pname = "codex";
|
||||||
|
inherit (versions.codex) version;
|
||||||
|
src = pkgs.fetchurl {
|
||||||
|
url = "https://github.com/openai/codex/releases/download/rust-v${versions.codex.version}/codex-package-x86_64-unknown-linux-musl.tar.gz";
|
||||||
|
inherit (versions.codex) sha256;
|
||||||
|
};
|
||||||
|
sourceRoot = ".";
|
||||||
|
nativeBuildInputs = [ pkgs.autoPatchelfHook pkgs.makeWrapper ];
|
||||||
|
buildInputs = [ pkgs.glibc pkgs.ncurses ];
|
||||||
|
dontBuild = true;
|
||||||
|
dontStrip = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
mkdir -p "$out"
|
||||||
|
cp -r bin codex-path codex-resources codex-package.json "$out/"
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
postFixup = ''
|
||||||
|
wrapProgram "$out/bin/codex" \
|
||||||
|
--prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.ripgrep pkgs.bubblewrap ]}
|
||||||
|
'';
|
||||||
|
doInstallCheck = true;
|
||||||
|
installCheckPhase = ''
|
||||||
|
runHook preInstallCheck
|
||||||
|
"$out/bin/codex" --version | grep -F "codex-cli ${versions.codex.version}"
|
||||||
|
runHook postInstallCheck
|
||||||
|
'';
|
||||||
|
meta = {
|
||||||
|
description = "OpenAI coding agent, official upstream binary package";
|
||||||
|
homepage = "https://github.com/openai/codex";
|
||||||
|
license = pkgs.lib.licenses.asl20;
|
||||||
|
mainProgram = "codex";
|
||||||
|
platforms = [ "x86_64-linux" ];
|
||||||
|
sourceProvenance = [ pkgs.lib.sourceTypes.binaryNativeCode ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Keep Nixpkgs' NixOS compatibility fixes and sandbox dependencies.
|
||||||
|
claude-code = pkgs.claude-code.override {
|
||||||
|
manifest = {
|
||||||
|
inherit (versions.claude-code) version;
|
||||||
|
platforms.linux-x64.checksum = versions.claude-code.sha256;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Pin the latest stable upstream CLI releases without updating NixOS."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_json(url):
|
||||||
|
request = urllib.request.Request(
|
||||||
|
url, headers={"User-Agent": "nixos-config-ai-cli-updater"}
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(request, timeout=60) as response:
|
||||||
|
return json.load(response)
|
||||||
|
|
||||||
|
|
||||||
|
def latest_release(repository):
|
||||||
|
release = fetch_json(f"https://api.github.com/repos/{repository}/releases/latest")
|
||||||
|
if release["prerelease"] or release["draft"]:
|
||||||
|
raise ValueError(f"Expected a stable release from {repository}")
|
||||||
|
return release
|
||||||
|
|
||||||
|
|
||||||
|
codex = latest_release("openai/codex")
|
||||||
|
codex_version = codex["tag_name"].removeprefix("rust-v")
|
||||||
|
claude = latest_release("anthropics/claude-code")
|
||||||
|
claude_version = claude["tag_name"].removeprefix("v")
|
||||||
|
for version in (codex_version, claude_version):
|
||||||
|
if not re.fullmatch(r"\d+\.\d+\.\d+", version):
|
||||||
|
raise ValueError(f"Unexpected release version: {version}")
|
||||||
|
|
||||||
|
asset = next(
|
||||||
|
item for item in codex["assets"]
|
||||||
|
if item["name"] == "codex-package-x86_64-unknown-linux-musl.tar.gz"
|
||||||
|
)
|
||||||
|
digest = asset["digest"]
|
||||||
|
if not re.fullmatch(r"sha256:[0-9a-f]{64}", digest):
|
||||||
|
raise ValueError("Codex release asset has no valid SHA-256 digest")
|
||||||
|
|
||||||
|
manifest = fetch_json(
|
||||||
|
f"https://downloads.claude.ai/claude-code-releases/{claude_version}/manifest.json"
|
||||||
|
)
|
||||||
|
if manifest["version"] != claude_version:
|
||||||
|
raise ValueError("Claude release and download manifest versions differ")
|
||||||
|
claude_hash = manifest["platforms"]["linux-x64"]["checksum"]
|
||||||
|
if not re.fullmatch(r"[0-9a-f]{64}", claude_hash):
|
||||||
|
raise ValueError("Claude manifest has no valid SHA-256 checksum")
|
||||||
|
|
||||||
|
versions = {
|
||||||
|
"codex": {"version": codex_version, "sha256": digest.removeprefix("sha256:")},
|
||||||
|
"claude-code": {"version": claude_version, "sha256": claude_hash},
|
||||||
|
}
|
||||||
|
destination = Path(__file__).resolve().parent.parent / "packages" / "ai-cli-versions.json"
|
||||||
|
destination.write_text(json.dumps(versions, indent=2) + "\n")
|
||||||
|
print(f"Pinned Codex {codex_version} and Claude Code {claude_version}.")
|
||||||
|
print("Review the diff, build the packages, rebuild your host and commit the pins.")
|
||||||
Reference in new issue
Block a user