From cd3dd76e43085a0aa7c9489f94186ee41ac5f0be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D7=A9=D7=9C=D7=9E=D7=94=20=D7=A2=D7=A8=D7=A0=D7=98=D7=A8?= =?UTF-8?q?=D7=95=D7=99?= Date: Fri, 17 Jul 2026 00:16:19 +0300 Subject: [PATCH] Add Fedora KDE integration --- CHANGELOG.md | 2 + Makefile | 8 +- README.md | 2 + Scripts/install-fedora-kde.sh | 346 ++++++++++++++++++ .../codexbar-plasmoid-plasma-6.6.patch | 85 +++++ docs/linux-kde.md | 95 +++++ 6 files changed, 537 insertions(+), 1 deletion(-) create mode 100755 Scripts/install-fedora-kde.sh create mode 100644 Scripts/linux-kde/codexbar-plasmoid-plasma-6.6.patch create mode 100644 docs/linux-kde.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c2be8419d..6bcc14d81b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 0.43.1 — Unreleased +- Linux: add a Fedora KDE Plasma 6 installer for the verified static CLI and panel widget, including Plasma 6.6 QML compatibility fixes. + ### Added - ZenMux: add Management API usage with five-hour and weekly quotas, subscription expiry, and USD PAYG balance. Thanks @kays0x! diff --git a/Makefile b/Makefile index 55d28ce76e..1cad9ff367 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL := /bin/bash -.PHONY: build check docs-list format lint release restart start start-debug start-release stop test test-live test-tty +.PHONY: build check docs-list format lint linux-kde linux-kde-check release restart start start-debug start-release stop test test-live test-tty start: ./Scripts/compile_and_run.sh @@ -30,6 +30,12 @@ docs-list: build: swift build +linux-kde: + ./Scripts/install-fedora-kde.sh + +linux-kde-check: + ./Scripts/install-fedora-kde.sh --check + test: ./Scripts/test.sh diff --git a/README.md b/README.md index ebfebf8de2..0e860be30c 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,8 @@ CLI install: - [Win-CodexBar](https://github.com/Finesssee/Win-CodexBar) ## Linux desktop integration? +- Fedora KDE Plasma 6: run `./Scripts/install-fedora-kde.sh --add-to-panel` from + this checkout. See [Fedora KDE integration](docs/linux-kde.md). - [codexbar-waybar](https://github.com/Marouan-chak/codexbar-waybar) — Waybar custom module + GTK4 popover for Hyprland / Sway / other Wayland compositors, built on top of the bundled Linux CLI. - [Codexbar GNOME](https://extensions.gnome.org/extension/9841/codexbar/) — GNOME Shell extension that brings CodexBar usage into the desktop panel. - [codexbar-cinnamon-applet](https://github.com/jacobcalvert/codexbar-cinnamon-applet) — Linux Mint Cinnamon panel applet powered by CodexBar's JSON output. diff --git a/Scripts/install-fedora-kde.sh b/Scripts/install-fedora-kde.sh new file mode 100755 index 0000000000..e54f3dc7e6 --- /dev/null +++ b/Scripts/install-fedora-kde.sh @@ -0,0 +1,346 @@ +#!/usr/bin/env bash +set -euo pipefail + +readonly CODEXBAR_REPOSITORY="steipete/CodexBar" +readonly KDE_WIDGET_REPOSITORY="psimaker/codexbar-plasmoid" +readonly KDE_WIDGET_ID="com.github.psimaker.codexbar" +readonly DEFAULT_KDE_WIDGET_REF="4ab8e365b789243b12fe853a53f0987efd1d54af" +readonly script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +readonly plasma_compatibility_patch="$script_dir/linux-kde/codexbar-plasmoid-plasma-6.6.patch" + +mode="install" +install_cli=true +install_widget=true +add_to_panel=false +restart_plasma=true + +usage() { + cat <<'EOF' +Install CodexBar's Linux CLI and KDE Plasma 6 widget on Fedora. + +Usage: + ./Scripts/install-fedora-kde.sh [options] + +Options: + --check Verify the current Fedora/KDE installation without changing it. + --add-to-panel Add the widget to the first Plasma panel after installation. + --skip-cli Do not install or update the CodexBar CLI. + --skip-widget Do not install or update the Plasma widget. + --no-restart Do not restart plasmashell after installing the widget. + -h, --help Show this help. + +Environment: + CODEXBAR_VERSION Release tag to install, for example v0.43.0. Defaults to latest. + CODEXBAR_KDE_REF Widget git ref. Defaults to the tested pinned commit. + CODEXBAR_BIN_DIR CLI destination. Defaults to ~/.local/bin. + CODEXBAR_LINUX_VARIANT + musl (default, static) or glibc. +EOF +} + +log() { + printf '[codexbar-kde] %s\n' "$*" +} + +fail() { + printf '[codexbar-kde] error: %s\n' "$*" >&2 + exit 1 +} + +have() { + command -v "$1" >/dev/null 2>&1 +} + +while (($# > 0)); do + case "$1" in + --check) + mode="check" + ;; + --add-to-panel) + add_to_panel=true + ;; + --skip-cli) + install_cli=false + ;; + --skip-widget) + install_widget=false + ;; + --no-restart) + restart_plasma=false + ;; + -h | --help) + usage + exit 0 + ;; + *) + fail "unknown option: $1" + ;; + esac + shift +done + +readonly bin_dir="${CODEXBAR_BIN_DIR:-$HOME/.local/bin}" +readonly codexbar_path="$bin_dir/codexbar" +readonly widget_ref="${CODEXBAR_KDE_REF:-$DEFAULT_KDE_WIDGET_REF}" + +require_fedora_kde() { + [[ -r /etc/os-release ]] || fail "cannot identify the Linux distribution" + + # shellcheck disable=SC1091 + source /etc/os-release + [[ "${ID:-}" == "fedora" ]] || fail "this installer currently supports Fedora; detected ${ID:-unknown}" + + have kpackagetool6 || fail "kpackagetool6 is missing; install KDE Plasma 6" + have plasmashell || fail "plasmashell is missing; install KDE Plasma 6" + have curl || fail "curl is required" + have patch || fail "patch is required" + have tar || fail "tar is required" + have sha256sum || fail "sha256sum is required" + + local plasma_version + plasma_version="$(plasmashell --version 2>/dev/null | awk '{print $2}')" + [[ "$plasma_version" == 6.* ]] || fail "Plasma 6 is required; detected ${plasma_version:-unknown}" +} + +linux_asset_arch() { + case "$(uname -m)" in + x86_64 | amd64) + printf 'x86_64\n' + ;; + aarch64 | arm64) + printf 'aarch64\n' + ;; + *) + fail "unsupported CPU architecture: $(uname -m)" + ;; + esac +} + +latest_release_tag() { + if [[ -n "${CODEXBAR_VERSION:-}" ]]; then + printf '%s\n' "$CODEXBAR_VERSION" + return + fi + + if have gh && gh auth status >/dev/null 2>&1; then + gh release view --repo "$CODEXBAR_REPOSITORY" --json tagName --jq .tagName + return + fi + + local effective_url + effective_url="$( + curl --fail --silent --show-error --location \ + --output /dev/null --write-out '%{url_effective}' \ + "https://github.com/$CODEXBAR_REPOSITORY/releases/latest" + )" + [[ "$effective_url" == */tag/* ]] || fail "could not resolve the latest CodexBar release" + printf '%s\n' "${effective_url##*/}" +} + +download_file() { + local url="$1" + local destination="$2" + curl --fail --location --retry 3 --retry-delay 1 \ + --output "$destination" "$url" +} + +verify_checksum() { + local directory="$1" + local checksum_file="$2" + local asset_name="$3" + + ( + cd "$directory" + local expected + expected="$(awk '{print $1}' "$checksum_file")" + [[ "$expected" =~ ^[0-9a-fA-F]{64}$ ]] || fail "invalid checksum file for $asset_name" + printf '%s %s\n' "$expected" "$asset_name" | sha256sum --check - + ) +} + +install_codexbar_cli() { + local work_dir="$1" + local release_tag release_version arch variant asset_platform asset_name release_base + + release_tag="$(latest_release_tag)" + release_version="${release_tag#v}" + arch="$(linux_asset_arch)" + variant="${CODEXBAR_LINUX_VARIANT:-musl}" + case "$variant" in + musl) + asset_platform="linux-musl" + ;; + glibc) + asset_platform="linux" + ;; + *) + fail "CODEXBAR_LINUX_VARIANT must be musl or glibc" + ;; + esac + asset_name="CodexBarCLI-v${release_version}-${asset_platform}-${arch}.tar.gz" + release_base="https://github.com/$CODEXBAR_REPOSITORY/releases/download/$release_tag" + + log "downloading CodexBar CLI $release_tag for Linux $arch ($variant)" + download_file "$release_base/$asset_name" "$work_dir/$asset_name" + download_file "$release_base/$asset_name.sha256" "$work_dir/$asset_name.sha256" + verify_checksum "$work_dir" "$asset_name.sha256" "$asset_name" + + mkdir -p "$work_dir/cli" + tar -xzf "$work_dir/$asset_name" -C "$work_dir/cli" + + local binary + binary="$(find "$work_dir/cli" -type f -name CodexBarCLI -print -quit)" + [[ -n "$binary" ]] || fail "CodexBarCLI was not found in $asset_name" + + mkdir -p "$bin_dir" + install -m 0755 "$binary" "$codexbar_path" + + local version_file + version_file="$(find "$work_dir/cli" -type f -name VERSION -print -quit)" + if [[ -n "$version_file" ]]; then + install -m 0644 "$version_file" "$bin_dir/VERSION" + fi + + log "installed $("$codexbar_path" --version) at $codexbar_path" +} + +widget_is_installed() { + kpackagetool6 -t Plasma/Applet --list 2>/dev/null | grep -Fq "$KDE_WIDGET_ID" +} + +patch_widget_for_plasma_6_6() { + local package_dir="$1" + + # Plasma 6.6 rejects JavaScript null/undefined values assigned to bool QML + # properties. Coerce optional payload guards so empty provider data remains + # a clean false instead of logging "Unable to assign [undefined] to bool". + [[ -f "$plasma_compatibility_patch" ]] || fail "missing Plasma compatibility patch" + patch --directory "$package_dir" --strip=1 --forward --silent \ + < "$plasma_compatibility_patch" \ + || fail "the Plasma compatibility patch does not apply to widget ref $widget_ref" +} + +install_kde_widget() { + local work_dir="$1" + local archive="$work_dir/codexbar-plasmoid.tar.gz" + local package_dir="$work_dir/codexbar-plasmoid" + local archive_url="https://github.com/$KDE_WIDGET_REPOSITORY/archive/$widget_ref.tar.gz" + + log "downloading KDE Plasma widget at $widget_ref" + download_file "$archive_url" "$archive" + mkdir -p "$package_dir" + tar -xzf "$archive" --strip-components=1 -C "$package_dir" + + [[ -f "$package_dir/metadata.json" ]] || fail "invalid Plasma widget archive" + [[ -f "$package_dir/contents/ui/main.qml" ]] || fail "Plasma widget has no main.qml" + patch_widget_for_plasma_6_6 "$package_dir" + + if widget_is_installed; then + log "updating installed Plasma widget" + kpackagetool6 -t Plasma/Applet --upgrade "$package_dir" + else + log "installing Plasma widget" + kpackagetool6 -t Plasma/Applet --install "$package_dir" + fi + + if "$restart_plasma" && systemctl --user is-active plasma-plasmashell.service >/dev/null 2>&1; then + log "restarting plasmashell to load the updated widget" + systemctl --user restart plasma-plasmashell.service + fi +} + +add_widget_to_panel() { + systemctl --user is-active plasma-plasmashell.service >/dev/null 2>&1 \ + || fail "plasmashell is not running" + + local script + script=$( + cat <&2 + failed=true + fi + + if widget_is_installed; then + log "Plasma widget: installed ($KDE_WIDGET_ID)" + else + printf '[codexbar-kde] missing: Plasma widget %s\n' "$KDE_WIDGET_ID" >&2 + failed=true + fi + + "$failed" && return 1 + log "Fedora/KDE integration is installed" +} + +require_fedora_kde + +if [[ "$mode" == "check" ]]; then + check_installation + exit +fi + +work_dir="$(mktemp -d "${TMPDIR:-/tmp}/codexbar-kde.XXXXXX")" +trap 'rm -rf "$work_dir"' EXIT + +"$install_cli" && install_codexbar_cli "$work_dir" +"$install_widget" && install_kde_widget "$work_dir" +"$add_to_panel" && add_widget_to_panel +check_installation + +log "Open the widget settings to choose providers and refresh behavior." +if ! "$add_to_panel"; then + log "Add CodexBar from Plasma's “Add Widgets…” dialog, or rerun with --add-to-panel." +fi diff --git a/Scripts/linux-kde/codexbar-plasmoid-plasma-6.6.patch b/Scripts/linux-kde/codexbar-plasmoid-plasma-6.6.patch new file mode 100644 index 0000000000..617813af40 --- /dev/null +++ b/Scripts/linux-kde/codexbar-plasmoid-plasma-6.6.patch @@ -0,0 +1,85 @@ +--- a/contents/ui/AboutPage.qml ++++ b/contents/ui/AboutPage.qml +@@ -37 +37 @@ +- text: i18n("Plasma port, v0.1.0 — data via codexbar CLI") ++ text: i18n("Plasma port, v0.2.0 — data via codexbar CLI") +--- a/contents/ui/ProviderCard.qml ++++ b/contents/ui/ProviderCard.qml +@@ -188 +188 @@ +- visible: (card.entry && card.entry.credits && card.entry.credits.remaining !== undefined) ++ visible: Boolean((card.entry && card.entry.credits && card.entry.credits.remaining !== undefined) +@@ -190 +190 @@ +- && card.usage.codexResetCredits.availableCount > 0) ++ && card.usage.codexResetCredits.availableCount > 0)) +@@ -206 +206 @@ +- visible: card.entry && card.entry.credits && card.entry.credits.remaining !== undefined ++ visible: Boolean(card.entry && card.entry.credits && card.entry.credits.remaining !== undefined) +@@ -214,2 +214,2 @@ +- visible: card.usage && card.usage.codexResetCredits +- && card.usage.codexResetCredits.availableCount > 0 ++ visible: Boolean(card.usage && card.usage.codexResetCredits ++ && card.usage.codexResetCredits.availableCount > 0) +@@ -244,2 +244,2 @@ +- visible: Plasmoid.configuration.showCost +- && card.d && card.d.cost !== undefined && card.d.cost !== null ++ visible: Boolean(Plasmoid.configuration.showCost ++ && card.d && card.d.cost !== undefined && card.d.cost !== null) +@@ -274 +274 @@ +- visible: card.d && card.d.cost && card.d.cost.last30DaysCostUSD !== undefined ++ visible: Boolean(card.d && card.d.cost && card.d.cost.last30DaysCostUSD !== undefined) +@@ -290,2 +290,2 @@ +- visible: Plasmoid.configuration.showStatus +- && card.entry && card.entry.status !== undefined && card.entry.status !== null ++ visible: Boolean(Plasmoid.configuration.showStatus ++ && card.entry && card.entry.status !== undefined && card.entry.status !== null) +@@ -313 +313 @@ +- visible: card.usage && card.usage.identity && card.usage.identity.accountEmail !== undefined ++ visible: Boolean(card.usage && card.usage.identity && card.usage.identity.accountEmail !== undefined) +--- a/contents/ui/configGeneral.qml ++++ b/contents/ui/configGeneral.qml +@@ -9,0 +10,12 @@ ++ property string cfg_enabledProviders ++ property int cfg_refreshIntervalMinutesDefault ++ property string cfg_enabledProvidersDefault ++ property bool cfg_showPercentInPanelDefault ++ property string cfg_panelPercentSourceDefault ++ property string cfg_percentStyleDefault ++ property bool cfg_hideCrittersDefault ++ property bool cfg_separateIconsDefault ++ property bool cfg_showCostDefault ++ property bool cfg_showStatusDefault ++ property string cfg_cliPathDefault ++ +--- a/contents/ui/configProviders.qml ++++ b/contents/ui/configProviders.qml +@@ -10,0 +11,20 @@ ++ property int cfg_refreshIntervalMinutes ++ property bool cfg_showPercentInPanel ++ property string cfg_panelPercentSource ++ property string cfg_percentStyle ++ property bool cfg_hideCritters ++ property bool cfg_separateIcons ++ property bool cfg_showCost ++ property bool cfg_showStatus ++ property string cfg_cliPath ++ property int cfg_refreshIntervalMinutesDefault ++ property string cfg_enabledProvidersDefault ++ property bool cfg_showPercentInPanelDefault ++ property string cfg_panelPercentSourceDefault ++ property string cfg_percentStyleDefault ++ property bool cfg_hideCrittersDefault ++ property bool cfg_separateIconsDefault ++ property bool cfg_showCostDefault ++ property bool cfg_showStatusDefault ++ property string cfg_cliPathDefault ++ +--- a/contents/ui/main.qml ++++ b/contents/ui/main.qml +@@ -144,0 +145,2 @@ ++ } else if (parsed && parsed.length > 0 && parsed[0].error && parsed[0].error.message) { ++ d.error = parsed[0].error.message +@@ -252,2 +254,2 @@ +- onExpandedChanged: { +- if (expanded && (currentTab === "" || currentTab === "about")) ++ onExpandedChanged: function () { ++ if (root.expanded && (currentTab === "" || currentTab === "about")) diff --git a/docs/linux-kde.md b/docs/linux-kde.md new file mode 100644 index 0000000000..3deb9cf750 --- /dev/null +++ b/docs/linux-kde.md @@ -0,0 +1,95 @@ +--- +summary: "Install CodexBar's Linux backend and a native KDE Plasma 6 panel UI on Fedora." +read_when: + - "You want CodexBar usage in a Fedora KDE panel." + - "Updating or debugging the Fedora/KDE installer." +--- + +# Fedora KDE integration + +CodexBar's provider and parsing layer already supports Linux through the standalone +`CodexBarCLI` release. The macOS app UI uses SwiftUI and AppKit, so Fedora uses a +native Plasma 6/Kirigami panel widget backed by the same JSON output. + +## Install + +Run from the CodexBar checkout: + +```bash +./Scripts/install-fedora-kde.sh --add-to-panel +``` + +The installer: + +- verifies Fedora and Plasma 6; +- downloads the latest official static Linux CLI release; +- verifies the published SHA-256 checksum; +- installs `codexbar` into `~/.local/bin`; +- installs the tested KDE Plasma widget; +- applies the Plasma 6.6 boolean-binding compatibility fixes; +- optionally adds it to the first Plasma panel. + +The Plasma widget is the MIT-licensed +[`psimaker/codexbar-plasmoid`](https://github.com/psimaker/codexbar-plasmoid), +pinned by default to a revision tested with this repository. Override +`CODEXBAR_KDE_REF` to test a newer widget revision. + +## Verify + +The check is read-only and does not probe live provider accounts: + +```bash +./Scripts/install-fedora-kde.sh --check +``` + +To verify a provider separately: + +```bash +codexbar usage --provider codex --format json --pretty +codexbar usage --provider claude --format json --pretty +``` + +Provider probes can invoke local provider CLIs. Run those commands only when the +relevant provider is already signed in. + +## Configure + +Open the widget settings from the Plasma panel. The KDE UI supports: + +- all provider IDs exposed by the Linux CLI; +- merged or per-provider panel meters; +- provider tabs and an overview; +- session, weekly, monthly, and named quota windows; +- reset countdowns and pace; +- credits and Codex reset credits; +- local Codex/Claude cost summaries; +- provider status and account identity; +- provider-specific CLI errors instead of a generic empty state; +- dashboard/status links and refresh controls. + +The CLI and widget use `~/.config/codexbar/config.json` (or the legacy +`~/.codexbar/config.json`) for provider credentials and source settings. Browser +cookie import and WebKit scraping remain macOS-specific; on Linux use provider +CLI/OAuth/API authentication or manual cookies where the provider supports them. + +## Update or customize + +Rerun the installer to update the CLI and widget: + +```bash +./Scripts/install-fedora-kde.sh +``` + +Useful overrides: + +```bash +CODEXBAR_VERSION=v0.43.0 ./Scripts/install-fedora-kde.sh +CODEXBAR_KDE_REF= ./Scripts/install-fedora-kde.sh +CODEXBAR_BIN_DIR="$HOME/bin" ./Scripts/install-fedora-kde.sh +CODEXBAR_LINUX_VARIANT=glibc ./Scripts/install-fedora-kde.sh +``` + +Use `--skip-cli`, `--skip-widget`, or `--no-restart` for development workflows. +The static `musl` CLI is the Fedora default because it avoids host `libcurl` +ABI/version mismatches. Set `CODEXBAR_LINUX_VARIANT=glibc` only when you +specifically need the dynamically linked build.