[RELEASE] Final version.

This commit is contained in:
2026-03-28 18:24:08 +05:30
parent a90e3a0d84
commit 1993d7eea3
12 changed files with 379 additions and 47 deletions

85
.gitignore vendored
View File

@@ -1,17 +1,94 @@
venv/
# ── Python ────────────────────────────────────────────────────────────────────
__pycache__/
*.pyc
*.pyo
*.py[cod]
*$py.class
*.pyd
.Python
*.pyo
*.so
*.egg
*.egg-info/
.eggs/
pip-wheel-metadata/
MANIFEST
# ── Virtual environments ──────────────────────────────────────────────────────
venv/
.venv/
env/
.env/
ENV/
# ── Distribution / build ──────────────────────────────────────────────────────
dist/
build/
*.spec
# ── PyInstaller work files (keep *.spec only if custom; generated ones are noise)
# Uncomment if you commit a hand-crafted spec file:
# !APIClient-Agent.spec
# ── Database & secrets ───────────────────────────────────────────────────────
*.db
*.sqlite
*.sqlite3
.env
.env.*
!.env.example
secrets.json
credentials.json
# ── Logs ─────────────────────────────────────────────────────────────────────
*.log
logs/
# ── OS artifacts ─────────────────────────────────────────────────────────────
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
desktop.ini
# ── IDE / editor ─────────────────────────────────────────────────────────────
.idea/
.vscode/
*.swp
*.swo
*~
.project
.pydevproject
*.sublime-project
*.sublime-workspace
# ── Testing & coverage ───────────────────────────────────────────────────────
.pytest_cache/
.mypy_cache/
.ruff_cache/
htmlcov/
.coverage
.coverage.*
coverage.xml
*.cover
nosetests.xml
test-results/
# ── Jupyter ───────────────────────────────────────────────────────────────────
.ipynb_checkpoints/
*.ipynb
# ── Packaging scratch ─────────────────────────────────────────────────────────
*.tar.gz
*.zip
*.deb
*.rpm
*.dmg
*.AppImage
*.exe
*.msi
# ── Assets: generated logo PNGs are committed (app requires them at runtime).
# Only ignore temp/intermediate files produced during asset generation.
assets/*.webp
assets/app-ss.png

25
LICENSE
View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2026 EKIKA.co
Copyright (c) 2025-2026 EKIKA.co
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -19,3 +19,26 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-------------------------------------------------------------------------------
Third-Party Notices
-------------------
This software bundles or depends on the following open-source packages.
Each package retains its own license.
PyQt6 - GPL v3 / Commercial https://riverbankcomputing.com/software/pyqt
httpx - BSD 3-Clause https://github.com/encode/httpx
websockets - BSD 3-Clause https://github.com/python-websockets/websockets
anthropic-sdk - MIT https://github.com/anthropics/anthropic-sdk-python
PyYAML - MIT https://github.com/yaml/pyyaml
PyInstaller - GPL v2+ with bootloader exception
https://github.com/pyinstaller/pyinstaller
Font licenses
Quicksand - Open Font License 1.1 https://fonts.google.com/specimen/Quicksand
Open Sans - Apache License 2.0 https://fonts.google.com/specimen/Open+Sans
The full text of each third-party license is available in the respective
package source repository or distribution.

View File

@@ -59,21 +59,16 @@ class EnvBar(QWidget):
layout.setContentsMargins(12, 0, 12, 0)
layout.setSpacing(6)
# EKIKA logo
logo_path = os.path.join(_ASSETS_DIR, "ekika_logo.png")
if os.path.exists(logo_path):
# App logo icon
app_logo_path = os.path.join(_ASSETS_DIR, "app_logo_32.png")
if os.path.exists(app_logo_path):
logo_lbl = QLabel()
logo_lbl.setObjectName("ekikaLogo")
pix = QPixmap(logo_path)
logo_lbl.setPixmap(pix.scaledToHeight(26, Qt.TransformationMode.SmoothTransformation))
logo_lbl.setToolTip("EKIKA")
logo_lbl.setObjectName("appLogo")
pix = QPixmap(app_logo_path)
logo_lbl.setPixmap(pix.scaledToHeight(32, Qt.TransformationMode.SmoothTransformation))
logo_lbl.setToolTip("APIClient - Agent")
layout.addWidget(logo_lbl)
# Thin separator
sep = QLabel("|")
sep.setObjectName("brandSub")
sep.setFixedWidth(12)
sep.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(sep)
layout.addSpacing(4)
brand = QLabel("APIClient")
brand.setObjectName("brandName")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

BIN
assets/app_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
assets/app_logo_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

BIN
assets/app_logo_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/app_logo_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -1,34 +1,258 @@
#!/bin/bash
set -e
#!/usr/bin/env bash
# =============================================================================
# build_installer.sh - Build APIClient-Agent standalone distribution
#
# Usage:
# ./build_installer.sh [--onefile] [--clean] [--deb] [--dmg]
#
# Options:
# --onefile Bundle into a single executable (larger, slower start) instead
# of the default one-directory layout (recommended for Linux/Mac)
# --clean Remove previous dist/ and build/ before building
# --deb Also build a .deb package after the build (requires fpm)
# --dmg Also build a .dmg image after the build (macOS only, requires
# create-dmg: brew install create-dmg)
#
# Requirements:
# Python 3.11+, a virtual environment at ./venv, and all pip deps installed.
# PyInstaller is installed automatically from requirements.txt.
# =============================================================================
set -euo pipefail
echo "=== Building API Client installer ==="
APP_NAME="APIClient-Agent"
APP_VERSION="2.0.0"
APP_BUNDLE_ID="co.ekika.apiclient-agent"
MAIN_SCRIPT="main.py"
ICON_PNG="assets/app_logo.png"
ICON_ICO="assets/app_logo.ico"
ICON_ICNS="assets/app_logo.icns"
# Install deps
pip install -r requirements.txt
PYTHON="${PYTHON:-venv/bin/python}"
PIP="${PIP:-venv/bin/pip}"
# Build with PyInstaller
pyinstaller \
--onedir \
--windowed \
--name "APIClient" \
--add-data "app:app" \
main.py
# ── Argument parsing ──────────────────────────────────────────────────────────
OPT_ONEFILE=false
OPT_CLEAN=false
OPT_DEB=false
OPT_DMG=false
echo ""
echo "=== Build complete ==="
echo "Executable: dist/APIClient/APIClient"
echo ""
for arg in "$@"; do
case $arg in
--onefile) OPT_ONEFILE=true ;;
--clean) OPT_CLEAN=true ;;
--deb) OPT_DEB=true ;;
--dmg) OPT_DMG=true ;;
*)
echo "Unknown option: $arg"
echo "Usage: $0 [--onefile] [--clean] [--deb] [--dmg]"
exit 1
;;
esac
done
# Optional: create .deb (requires fpm: gem install fpm)
if command -v fpm &> /dev/null; then
echo "Creating .deb package..."
fpm -s dir -t deb \
-n api-client \
-v 1.0.0 \
--description "Postman-like API client" \
dist/APIClient/=/opt/api-client \
--after-install /dev/null
echo "Package: api-client_1.0.0_amd64.deb"
else
echo "Tip: install fpm (gem install fpm) to also generate a .deb package"
# ── Helpers ───────────────────────────────────────────────────────────────────
info() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
ok() { echo -e "\033[1;32m[ OK ]\033[0m $*"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
die() { echo -e "\033[1;31m[FAIL]\033[0m $*" >&2; exit 1; }
# ── Platform detection ────────────────────────────────────────────────────────
OS="$(uname -s)"
case "$OS" in
Linux*) PLATFORM=linux ;;
Darwin*) PLATFORM=macos ;;
MINGW*|CYGWIN*|MSYS*) PLATFORM=windows ;;
*) die "Unsupported platform: $OS" ;;
esac
info "Platform: $PLATFORM"
# ── Preflight checks ──────────────────────────────────────────────────────────
[[ -f "$MAIN_SCRIPT" ]] || die "$MAIN_SCRIPT not found. Run from the project root."
[[ -d "venv" ]] || die "Virtual environment 'venv/' not found. Create it first:
python3 -m venv venv && venv/bin/pip install -r requirements.txt"
[[ -f "requirements.txt" ]] || die "requirements.txt not found."
info "Python: $($PYTHON --version)"
# ── Clean ─────────────────────────────────────────────────────────────────────
if $OPT_CLEAN; then
info "Cleaning previous build artifacts..."
rm -rf dist/ build/ "${APP_NAME}.spec"
ok "Cleaned."
fi
# ── Install / upgrade dependencies ───────────────────────────────────────────
info "Installing/verifying dependencies..."
$PIP install -q -r requirements.txt
ok "Dependencies ready."
# ── Prepare icon ─────────────────────────────────────────────────────────────
# PyInstaller needs .ico on Windows, .icns on macOS, .png on Linux.
ICON_ARG=""
if [[ "$PLATFORM" == "windows" ]]; then
if [[ -f "$ICON_ICO" ]]; then
ICON_ARG="--icon=$ICON_ICO"
else
warn ".ico icon not found at $ICON_ICO - building without icon."
fi
elif [[ "$PLATFORM" == "macos" ]]; then
if [[ -f "$ICON_ICNS" ]]; then
ICON_ARG="--icon=$ICON_ICNS"
elif [[ -f "$ICON_PNG" ]]; then
# Convert PNG -> ICNS using sips + iconutil if available
if command -v sips &>/dev/null && command -v iconutil &>/dev/null; then
info "Converting PNG to ICNS..."
ICONSET_DIR="/tmp/${APP_NAME}.iconset"
mkdir -p "$ICONSET_DIR"
for sz in 16 32 64 128 256 512; do
sips -z $sz $sz "$ICON_PNG" --out "$ICONSET_DIR/icon_${sz}x${sz}.png" &>/dev/null
sips -z $((sz*2)) $((sz*2)) "$ICON_PNG" --out "$ICONSET_DIR/icon_${sz}x${sz}@2x.png" &>/dev/null
done
iconutil -c icns "$ICONSET_DIR" -o "$ICON_ICNS" && ok "ICNS created."
ICON_ARG="--icon=$ICON_ICNS"
else
warn "sips/iconutil not available; building without .icns icon."
fi
fi
elif [[ "$PLATFORM" == "linux" ]]; then
[[ -f "$ICON_PNG" ]] && ICON_ARG="--icon=$ICON_PNG" || warn "PNG icon not found."
fi
# ── PyInstaller arguments ─────────────────────────────────────────────────────
PYINSTALLER_ARGS=(
$([[ $OPT_ONEFILE == true ]] && echo "--onefile" || echo "--onedir")
"--windowed"
"--name" "$APP_NAME"
"--add-data" "app:app"
"--add-data" "assets:assets"
"--clean"
"--noconfirm"
)
[[ -n "$ICON_ARG" ]] && PYINSTALLER_ARGS+=("$ICON_ARG")
# macOS bundle metadata
if [[ "$PLATFORM" == "macos" ]]; then
PYINSTALLER_ARGS+=(
"--osx-bundle-identifier" "$APP_BUNDLE_ID"
)
fi
# Hidden imports that PyInstaller may miss
PYINSTALLER_ARGS+=(
"--hidden-import" "PyQt6.QtSvg"
"--hidden-import" "PyQt6.QtNetwork"
"--hidden-import" "httpx._transports.default"
"--hidden-import" "websockets.legacy.client"
"--hidden-import" "anthropic"
)
PYINSTALLER_ARGS+=("$MAIN_SCRIPT")
# ── Build ─────────────────────────────────────────────────────────────────────
info "Running PyInstaller..."
$PYTHON -m PyInstaller "${PYINSTALLER_ARGS[@]}"
# ── Verify output ─────────────────────────────────────────────────────────────
if [[ $OPT_ONEFILE == true ]]; then
BIN="dist/${APP_NAME}"
[[ "$PLATFORM" == "windows" ]] && BIN="${BIN}.exe"
[[ -f "$BIN" ]] || die "Build failed: expected $BIN not found."
ok "Executable: $BIN ($(du -sh "$BIN" | cut -f1))"
else
OUT_DIR="dist/${APP_NAME}"
[[ -d "$OUT_DIR" ]] || die "Build failed: expected $OUT_DIR not found."
ok "Distribution directory: $OUT_DIR ($(du -sh "$OUT_DIR" | cut -f1))"
fi
# ── .deb package ─────────────────────────────────────────────────────────────
if $OPT_DEB; then
if [[ "$PLATFORM" != "linux" ]]; then
warn ".deb packaging is only supported on Linux. Skipping."
elif ! command -v fpm &>/dev/null; then
warn "fpm not found. Install it with: gem install fpm
Then re-run with --deb."
else
info "Building .deb package..."
DEB_NAME="apiclient-agent_${APP_VERSION}_amd64.deb"
# Write a post-install script that creates a desktop entry
POSTINST=$(mktemp)
cat > "$POSTINST" <<'EOF'
#!/bin/bash
cat > /usr/share/applications/apiclient-agent.desktop <<DESK
[Desktop Entry]
Name=APIClient - Agent
Comment=AI-first API testing client
Exec=/opt/apiclient-agent/APIClient-Agent
Icon=/opt/apiclient-agent/_internal/assets/app_logo.png
Terminal=false
Type=Application
Categories=Development;Network;
Keywords=API;REST;HTTP;AI;
DESK
update-desktop-database /usr/share/applications 2>/dev/null || true
EOF
chmod +x "$POSTINST"
fpm \
--input-type dir \
--output-type deb \
--name "apiclient-agent" \
--version "$APP_VERSION" \
--architecture amd64 \
--maintainer "EKIKA.co <hello@ekika.co>" \
--description "APIClient - Agent: AI-first API testing desktop client (Python/PyQt6)" \
--url "https://git.ekika.co/EKIKA.co/APIClient-Agent" \
--license "MIT" \
--category "Development" \
--after-install "$POSTINST" \
--package "$DEB_NAME" \
"dist/${APP_NAME}/=/opt/apiclient-agent"
rm -f "$POSTINST"
[[ -f "$DEB_NAME" ]] && ok ".deb package: $DEB_NAME ($(du -sh "$DEB_NAME" | cut -f1))" \
|| warn ".deb build may have failed."
fi
fi
# ── .dmg image (macOS) ───────────────────────────────────────────────────────
if $OPT_DMG; then
if [[ "$PLATFORM" != "macos" ]]; then
warn ".dmg packaging is only supported on macOS. Skipping."
elif ! command -v create-dmg &>/dev/null; then
warn "create-dmg not found. Install it with: brew install create-dmg
Then re-run with --dmg."
else
info "Building .dmg image..."
DMG_NAME="${APP_NAME}-${APP_VERSION}.dmg"
APP_BUNDLE="dist/${APP_NAME}.app"
[[ -d "$APP_BUNDLE" ]] || die ".app bundle not found at $APP_BUNDLE"
create-dmg \
--volname "$APP_NAME $APP_VERSION" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--icon "${APP_NAME}.app" 175 190 \
--hide-extension "${APP_NAME}.app" \
--app-drop-link 425 190 \
"$DMG_NAME" \
"$APP_BUNDLE"
[[ -f "$DMG_NAME" ]] && ok ".dmg image: $DMG_NAME ($(du -sh "$DMG_NAME" | cut -f1))" \
|| warn ".dmg build may have failed."
fi
fi
# ── Summary ───────────────────────────────────────────────────────────────────
echo ""
echo " ┌─────────────────────────────────────────────┐"
printf " │ %-43s│\n" "${APP_NAME} v${APP_VERSION} build complete"
printf " │ Platform : %-31s│\n" "$PLATFORM"
if [[ $OPT_ONEFILE == true ]]; then
printf " │ Output : %-31s│\n" "dist/${APP_NAME}"
else
printf " │ Output : %-31s│\n" "dist/${APP_NAME}/"
fi
echo " └─────────────────────────────────────────────┘"
echo ""

15
main.py
View File

@@ -1,5 +1,6 @@
import sys
import sys, os
from PyQt6.QtWidgets import QApplication
from PyQt6.QtGui import QIcon
from app.core.fonts import load_fonts
from app.ui.theme import apply
from app.ui.main_window import MainWindow
@@ -7,11 +8,23 @@ from app.ui.main_window import MainWindow
APP_NAME = "APIClient - Agent"
APP_VERSION = "2.0.0"
_ASSETS = os.path.join(os.path.dirname(__file__), "assets")
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setApplicationName(APP_NAME)
app.setApplicationVersion(APP_VERSION)
app.setOrganizationName("EKIKA")
# App icon (taskbar + window title bar)
icon = QIcon()
for size, fname in [(16, "app_logo_16.png"), (32, "app_logo_32.png"),
(48, "app_logo_48.png"), (256, "app_logo.png")]:
path = os.path.join(_ASSETS, fname)
if os.path.exists(path):
icon.addFile(path)
app.setWindowIcon(icon)
load_fonts()
apply(app, dark=True)
window = MainWindow()