Initial release — APIClient - Agent v2.0.0

AI-first API testing desktop client built with Python + PyQt6.

Features:
- Multi-tab HTTP request editor with params/headers/body/auth/tests
- KeyValueTable with per-row enable/disable checkboxes and 36px rows
- Format JSON button, syntax highlighting, pre-request & test scripts
- Collections, environments, history, import/export (Postman v2.1, cURL)
- OpenAPI 3.x / Swagger 2.0 local parser (no AI tokens)
- EKIKA Odoo API Framework generator — JSON-API, REST JSON, GraphQL,
  Custom REST JSON with all auth types (instant, no AI tokens)
- Persistent AI chat sidebar (Claude-powered co-pilot) with streaming,
  context-aware suggestions, and one-click Apply to request editor
- AI collection generator from any docs URL or pasted spec
- WebSocket client, Mock server, Collection runner, Code generator
- Dark/light theme engine (global QSS, object-name selectors)
- SSL error detection with actionable hints
- MIT License

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-28 17:34:18 +05:30
parent 1dbbb4320b
commit 01662f7e0e
37 changed files with 7822 additions and 1 deletions

66
app/core/exporter.py Normal file
View File

@@ -0,0 +1,66 @@
"""Export collections to Postman Collection v2.1 JSON."""
import json
from app.models import HttpRequest
from app.core import storage
def export_collection(collection_id: int) -> str:
collections = storage.get_collections()
col = next((c for c in collections if c["id"] == collection_id), None)
if not col:
raise ValueError(f"Collection {collection_id} not found")
items = []
def make_request_item(r: dict) -> dict:
header = [{"key": k, "value": v} for k, v in r.get("headers", {}).items()]
url_raw = r.get("url", "")
params = r.get("params", {})
query = [{"key": k, "value": v} for k, v in params.items()]
body_obj = None
body = r.get("body", "")
body_type = r.get("body_type", "raw")
if body:
if body_type == "urlencoded":
pairs = []
for line in body.split("&"):
if "=" in line:
k, _, v = line.partition("=")
pairs.append({"key": k, "value": v})
body_obj = {"mode": "urlencoded", "urlencoded": pairs}
else:
body_obj = {"mode": "raw", "raw": body}
item = {
"name": r.get("name") or url_raw,
"request": {
"method": r.get("method", "GET"),
"header": header,
"url": {"raw": url_raw, "query": query},
}
}
if body_obj:
item["request"]["body"] = body_obj
return item
# Top-level requests (no folder)
for r in storage.get_requests(collection_id):
items.append(make_request_item(r))
# Folders
for folder in storage.get_folders(collection_id):
folder_item = {
"name": folder["name"],
"item": [make_request_item(r) for r in storage.get_requests(collection_id, folder["id"])]
}
items.append(folder_item)
collection = {
"info": {
"name": col["name"],
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": items
}
return json.dumps(collection, indent=2)