Metadata-Version: 2.5
Name: utilix-sdk
Version: 0.47.0
Summary: 154+ developer utility tools for Python: JSON, encoding, hashing, color, CSS, images, network and more
Project-URL: Homepage, https://utilix.tech
Project-URL: Documentation, https://www.utilix.tech/docs#python-sdk
Project-URL: Repository, https://github.com/utilix-tech/utilix-sdk
Project-URL: Bug Tracker, https://github.com/utilix-tech/utilix-sdk/issues
Project-URL: Changelog, https://utilix.tech/changelog
Author-email: Utilix <hello@utilix.tech>
License-Expression: MIT
License-File: LICENSE
Keywords: ai-agent,api-client,base64,cli,color,css,developer-tools,devtools,encoding,hashing,image,json,jwt,llm-tools,network,rag,regex,sdk,utilities,uuid,yaml
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Requires-Dist: bcrypt>=4.0
Requires-Dist: beautifulsoup4>=4.12
Requires-Dist: croniter>=2.0
Requires-Dist: jsonpath-ng>=1.6
Requires-Dist: jsonschema>=4.0
Requires-Dist: lxml>=5.0
Requires-Dist: markdown>=3.5
Requires-Dist: markdownify>=0.11
Requires-Dist: openapi-spec-validator>=0.7
Requires-Dist: pillow>=10.0
Requires-Dist: pyjwt>=2.8
Requires-Dist: python-dateutil>=2.9
Requires-Dist: pyyaml>=6.0
Requires-Dist: qrcode>=7.4
Requires-Dist: requests>=2.31
Requires-Dist: rjsmin>=1.2
Requires-Dist: scour>=0.38
Requires-Dist: semver>=3.0
Requires-Dist: sqlparse>=0.5
Requires-Dist: tinycss2>=1.2
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Description-Content-Type: text/markdown

# utilix-sdk

**486 developer utility functions for Python: runs entirely locally, no API key required.**

[![PyPI version](https://img.shields.io/pypi/v/utilix-sdk?color=blue)](https://pypi.org/project/utilix-sdk/)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)

---

## Installation

```bash
pip install utilix-sdk
```

Requires Python 3.11 or later.

---

## Quick Start

### Encoding

```python
from utilix.tools.encoding import encode_base64, decode_base64

result = encode_base64("hello world")
# {"ok": True, "output": "aGVsbG8gd29ybGQ=", "inputBytes": 11, "outputChars": 16}

result = decode_base64("aGVsbG8gd29ybGQ=")
# {"ok": True, "output": "hello world", "outputBytes": 11}
```

### Hashing

```python
from utilix.tools.hashing import hash_all, hash_one

result = hash_all("my-secret")
# Returns MD5, SHA-1, SHA-256, SHA-384, SHA-512 digests in one call

sha256 = hash_one("SHA-256", "my-secret")
# {"ok": True, "algorithm": "SHA-256", "hex": "...", "bits": 256}

from utilix.tools.hashing import generate_sri_all

# Subresource Integrity hashes for a script/stylesheet's content (SHA-256/384/512)
generate_sri_all("console.log(1)")
# {"ok": True, "output": [{"algorithm": "SHA-256", "base64": "...", "integrity": "sha256-..."}, ...]}

from utilix.tools.hashing import generate_etag

# Content-hash ETag header value (MD5/SHA-1/SHA-256), with an optional weak (W/) prefix
generate_etag("console.log(1)")
# {"ok": True, "output": {"algorithm": "SHA-1", "weak": False, "hash": "...", "etag": "\"...\"", "snippet": "ETag: \"...\""}}
```

### JSON Tools

```python
from utilix.tools.json_tools import format_json, minify_json, yaml_to_json, resolve_json_pointer

pretty = format_json('{"name":"utilix","version":"0.1.0"}', indent=2)
# {"status": "valid", "output": "{\n  \"name\": \"utilix\", ...", ...}

minified = minify_json('{\n  "a": 1,\n  "b": 2\n}')
# {"ok": True, "output": "{\"a\":1,\"b\":2}", ...}

as_json = yaml_to_json("name: utilix\nversion: 0.1.0")
# {"ok": True, "output": "{\"name\": \"utilix\", \"version\": \"0.1.0\"}"}

pointer = resolve_json_pointer('{"users":[{"name":"Alice"}]}', "/users/0/name")
# {"ok": True, "found": True, "value": "Alice", "tokens": ["users", "0", "name"]}
```

### Color

```python
from utilix.tools.color import hex_to_rgb, check_contrast, generate_palette

rgb = hex_to_rgb("#3B82F6")
# {"ok": True, "r": 59, "g": 130, "b": 246}

contrast = check_contrast("#FFFFFF", "#3B82F6")
# {"ratio": 3.94, "aa_normal": False, "aa_large": True, "aaa_normal": False, ...}

palette = generate_palette("#3B82F6", scheme="complementary")
# Returns list of hex colors forming a complementary palette
```

### Media

```python
from utilix.tools.media import compress_image, convert_image, read_image_info, read_exif_data, read_pdf_metadata, read_pdf_page_dimensions, read_wav_info, read_id3_tags, read_ico_info, read_flac_info, read_video_info, inspect_gif_frames

with open("photo.jpg", "rb") as f:
    image_bytes = f.read()

result = compress_image(image_bytes, quality=75, format="JPEG")
# {"ok": True, "output": bytes, "original_size": 204800, "compressed_size": 61440, "ratio": 0.3}

converted = convert_image(image_bytes, target_format="WEBP")
# {"ok": True, "output": bytes, "format": "WEBP"}

# Read format, dimensions, bit depth, and alpha channel straight from file
# bytes: no decoding, no Pillow required. Supports PNG, JPEG, GIF, WebP, BMP.
info = read_image_info(image_bytes)
# {"ok": True, "format": "jpeg", "width": 1920, "height": 1080, "bitDepth": 8, "colorType": "rgb"}

# Read camera make/model, orientation, timestamps, exposure settings, and
# GPS coordinates directly from a JPEG's EXIF data. JPEG only.
exif = read_exif_data(image_bytes)
# {"ok": True, "make": "Canon", "model": "EOS R5", "exposureTime": "1/125", "fNumber": 2.8, ...}

# Read title, author, dates, page count, and encryption flag from a PDF's
# trailer/Info dictionary: no PDF rendering library required.
with open("report.pdf", "rb") as f:
    pdf_metadata = read_pdf_metadata(f.read())
# {"ok": True, "version": "1.7", "pageCount": 12, "title": "Q3 Report", "author": "Jane Doe", ...}

# List each page's MediaBox width/height (points and inches), rotation, and
# orientation by walking the Root -> Pages -> Kids tree, inheriting from
# ancestor nodes as the PDF spec requires. Also flags common paper sizes.
with open("report.pdf", "rb") as f:
    page_dimensions = read_pdf_page_dimensions(f.read())
# {"ok": True, "version": "1.7", "pageCount": 12, "pages": [{"pageNumber": 1, "widthPt": 612, "heightPt": 792, "rotation": 0, "orientation": "portrait", "paperSize": "Letter"}, ...]}

# Read sample rate, channels, bit depth, and duration from a WAV file's
# RIFF/fmt/data chunk headers: no audio library required.
with open("recording.wav", "rb") as f:
    wav_info = read_wav_info(f.read())
# {"ok": True, "audioFormat": 1, "audioFormatLabel": "PCM", "channels": 2, "sampleRate": 44100, "bitsPerSample": 16, "durationSeconds": 12.4, ...}

# Read title, artist, album, year, genre, comment, and track number from an
# MP3's ID3 tags. Prefers ID3v2.3/2.4 text frames, falls back to the classic
# 128-byte ID3v1/1.1 trailer.
with open("track.mp3", "rb") as f:
    id3_tags = read_id3_tags(f.read())
# {"ok": True, "version": "ID3v2.3.0", "title": "Track Name", "artist": "Artist Name", "genre": "Rock", ...}

# Read how many images a .ico file embeds and each one's dimensions, color
# depth, and size directly from its ICONDIR/ICONDIRENTRY header table.
with open("favicon.ico", "rb") as f:
    ico_info = read_ico_info(f.read())
# {"ok": True, "imageCount": 3, "images": [{"width": 16, "height": 16, "bitCount": 32, ...}, ...]}

# Read sample rate, channels, bit depth, total samples, and duration from a
# FLAC file's STREAMINFO block, plus Vorbis comment tags (artist, title,
# album, ...) from its VORBIS_COMMENT block if present.
with open("track.flac", "rb") as f:
    flac_info = read_flac_info(f.read())
# {"ok": True, "sampleRate": 44100, "channels": 2, "bitsPerSample": 16, "durationSeconds": 214.7, "artist": "Artist Name", ...}

# Read duration, resolution, and video/audio codec identifiers from an MP4
# (ISO BMFF moov box) or WebM (EBML/Matroska Segment) container header: no
# frame decoding, demuxing, or transcoding.
with open("clip.mp4", "rb") as f:
    video_info = read_video_info(f.read())
# {"ok": True, "format": "mp4", "durationSeconds": 12.5, "width": 1920, "height": 1080, "videoCodec": "avc1", "audioCodec": "mp4a", ...}

# Walk a GIF87a/89a file's block structure (image descriptors, graphic
# control extensions, the NETSCAPE2.0 application extension) to report frame
# count, total animation duration, and loop count, without decoding any
# LZW-compressed pixel data.
with open("animation.gif", "rb") as f:
    gif_info = inspect_gif_frames(f.read())
# {"ok": True, "version": "89a", "width": 480, "height": 270, "frameCount": 24, "animated": True, "loopCount": 0, "totalDurationMs": 2400, ...}
```

### CSS

```python
from utilix.tools.css import generate_gradient, calc_specificity, minify_css

gradient = generate_gradient({
    "type": "linear",
    "angle": 135,
    "stops": [{"color": "#667eea", "position": 0}, {"color": "#764ba2", "position": 100}]
})
# "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"

specificity = calc_specificity("#nav .item:hover")
# {"score": (0, 1, 1, 1), "display": "0,1,1,1", "explanation": [...]}

minified = minify_css("body {\n  margin: 0;\n  padding: 0;\n}")
# {"ok": True, "output": "body{margin:0;padding:0}", "saved_bytes": 14}
```

### Time Tools

```python
from utilix.tools.time_tools import from_unix, diff_dates, get_next_runs

parsed = from_unix(1735689600)
# {"ok": True, "iso": "2025-01-01T00:00:00+00:00", "relative": "6 months ago", ...}

delta = diff_dates("2024-01-01", "2024-12-31")
# {"ok": True, "days": 365, "months": 12, "human": "12 months"}

schedule = get_next_runs("0 9 * * MON-FRI", count=5)
# Next 5 weekday 9am runs as ISO strings

from utilix.tools.time_tools import parse_iso8601_duration, format_iso8601_duration

parse_iso8601_duration("P3DT4H30M")  # {"ok": True, "output": {"totalSeconds": 275400.0, ...}}
format_iso8601_duration(90061)       # {"ok": True, "output": {"iso": "P1DT1H1M1S", ...}}
```

### Network

```python
from utilix.tools.network import ip_to_decimal, cidr_info, is_valid_ipv4

decimal = ip_to_decimal("192.168.1.1")
# {"ok": True, "output": 3232235777}

subnet = cidr_info("10.0.0.0/24")
# {"ok": True, "network": "10.0.0.0", "broadcast": "10.0.0.255",
#  "hosts": 254, "netmask": "255.255.255.0", ...}

print(is_valid_ipv4("256.0.0.1"))  # False

from utilix.tools.network import parse_har, validate_sitemap, validate_robots_txt

# Parse a DevTools .har network export into entries + summary stats
with open("network.har") as f:
    result = parse_har(f.read())
# {"ok": True, "output": {"entries": [...], "summary": {"totalRequests": 12, ...}}}

# Validate a sitemap.xml (or sitemap index) against the sitemaps.org protocol
validate_sitemap('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>https://example.com/</loc></url></urlset>')
# {"ok": True, "output": {"type": "urlset", "urlCount": 1, "entries": [...], "issues": [...], "valid": True}}

# Validate a robots.txt file: User-agent groups, rules, and syntax mistakes
validate_robots_txt("User-agent: *\nDisallow: /admin\nSitemap: https://example.com/sitemap.xml")
# {"ok": True, "output": {"groups": [...], "sitemaps": [...], "issues": [...], "valid": True}}

from utilix.tools.network import parse_user_agent

# Parse a browser User-Agent string into browser, engine, OS, and device type
parse_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
# {"ok": True, "output": {"browser": {"name": "Chrome", "version": "120.0.0.0"},
#  "engine": {"name": "Blink", "version": "120.0.0.0"}, "os": {"name": "Windows", "version": "10"},
#  "device": {"type": "desktop"}, "raw": "..."}}

from utilix.tools.network import build_cache_control

# Build and explain a Cache-Control response header from directives
build_cache_control({"visibility": "public", "maxAge": 3600, "mustRevalidate": True})
# {"ok": True, "output": {"header": "public, max-age=3600, must-revalidate", "directives": [...], "warnings": []}}

from utilix.tools.network import parse_ipv6_address

# Expand/compress an IPv6 address (RFC 5952) and classify its address type
parse_ipv6_address("2001:db8::ff00:42:8329")
# {"ok": True, "output": {"expanded": "2001:0db8:0000:0000:0000:ff00:0042:8329",
#  "compressed": "2001:db8::ff00:42:8329", "groups": [...], "zoneId": None, "addressType": "documentation"}}

from utilix.tools.network import build_referrer_policy

# Build and explain a Referrer-Policy header from one or more fallback-chain tokens
build_referrer_policy("strict-origin-when-cross-origin")
# {"ok": True, "output": {"header": "strict-origin-when-cross-origin", "metaTag": "<meta name=\"referrer\" content=\"...\">", "policies": [...], "warnings": []}}

from utilix.tools.network import build_link_header

# Build an HTTP Link header from URL/rel pairs (preload, preconnect, canonical, pagination, etc.)
build_link_header([{"url": "/fonts/inter.woff2", "rel": "preload", "as": "font", "type": "font/woff2", "crossorigin": True}])
# {"ok": True, "output": {"header": '</fonts/inter.woff2>; rel="preload"; as="font"; type="font/woff2"; crossorigin', "links": [...], "warnings": []}}

from utilix.tools.network import check_set_cookie_header

# Parse and validate a Set-Cookie header's attribute combination
check_set_cookie_header("session=abc123; SameSite=None")
# {"ok": True, "output": {"cookie": {...}, "issues": ["SameSite=None requires the Secure attribute..."], "warnings": [...], "valid": False}}

from utilix.tools.network import analyze_email_headers

# Parse a raw email header block: fields, Received hop chain, SPF/DKIM/DMARC results
analyze_email_headers(raw_header_text)
# {"ok": True, "output": {"from": "...", "receivedHops": [...], "hopCount": 2, "spf": "pass", "dkim": "pass", "dmarc": "pass", "warnings": []}}

from utilix.tools.code import test_gitignore_patterns

# Test paths against a .gitignore file's patterns
test_gitignore_patterns("node_modules/\n*.log", ["node_modules/foo.js", "debug.log", "src/index.js"])
# {"ok": True, "output": [{"path": "node_modules/foo.js", "ignored": True, "matchedPattern": "node_modules/", "matchedLine": 1}, ...]}

from utilix.tools.data import generate_markdown_toc

# Markdown table of contents: ATX headings -> nested links with GitHub-style slugs
generate_markdown_toc("# Title\n\n## Section One\n\n## Section Two\n")
# {"ok": True, "output": {"toc": "- [Title](#title)\n  - [Section One](#section-one)\n  - [Section Two](#section-two)", "entries": [...]}}

from utilix.tools.units import compare_unit_prices

# True cost per unit across package options, flags the cheapest
compare_unit_prices([{"label": "12-pack", "price": 6, "quantity": 12}, {"label": "24-pack", "price": 10, "quantity": 24}])
# {"ok": True, "output": {"items": [...], "bestLabel": "24-pack"}}

from utilix.tools.data import csv_to_sql_insert

# CSV to SQL INSERT statements, with basic type inference and identifier quoting
csv_to_sql_insert("name,age\nAlice,30\nBob,25", {"tableName": "users"})
# {"ok": True, "output": {"sql": "INSERT INTO \"users\" (\"name\", \"age\") VALUES ('Alice', 30);\n...", "rowCount": 2, ...}}

from utilix.tools.network import build_range_header, parse_content_range_header

# Build a Range request header from explicit, open-ended, or suffix ("last N bytes") byte ranges
build_range_header([{"start": 0, "end": 1023}], {"totalSize": 146515})
# {"ok": True, "output": {"header": "Range: bytes=0-1023", "ranges": [...], "totalBytesRequested": 1024, "warnings": []}}

# Parse a Content-Range response header to verify what a server actually sent back
parse_content_range_header("bytes 200-1000/67589")
# {"ok": True, "output": {"unit": "bytes", "start": 200, "end": 1000, "total": 67589, "byteLength": 801, "isUnsatisfiable": False}}

from utilix.tools.network import build_vary_header, parse_vary_header

# Build a Vary response header from request-header names, with a plain-English caching note per header
build_vary_header(["Accept-Encoding", "Accept-Language"])
# {"ok": True, "output": {"header": "Vary: Accept-Encoding, Accept-Language", "entries": [...], "warnings": []}}

# Parse an existing Vary header value into the same shape
parse_vary_header("Vary: Accept-Encoding, Accept-Language")
# {"ok": True, "output": {"header": "Vary: Accept-Encoding, Accept-Language", "entries": [...], "warnings": []}}

from utilix.tools.network import validate_manifest

# Validate a web app manifest.json against the W3C spec's installability requirements
validate_manifest('{"name": "My App", "start_url": "/", "display": "standalone", "icons": [{"src": "/icon-512.png", "sizes": "512x512", "purpose": "any maskable"}]}')
# {"ok": True, "output": {"issues": [...], "valid": True, "summary": {"hasMaskableIcon": True, "has512Icon": True, ...}}}

from utilix.tools.data import find_csv_duplicate_rows

# Find duplicate rows in CSV/TSV data, by the whole row or a chosen key column set
find_csv_duplicate_rows("name,email\nAlice,a@x.com\nBob,b@x.com\nAlice,a@x.com")
# {"ok": True, "output": {"duplicateGroups": [{"key": ["Alice", "a@x.com"], "rowNumbers": [1, 3], "count": 2}], "duplicateRowCount": 2, ...}}

from utilix.tools.network import build_permissions_policy, parse_permissions_policy

# Build a Permissions-Policy header from features and their allowlists, with a plain-English note per feature
build_permissions_policy([{"feature": "camera", "allowlist": []}, {"feature": "geolocation", "allowlist": ["self"]}])
# {"ok": True, "output": {"header": "Permissions-Policy: camera=(), geolocation=(self)", "directives": [...], "warnings": []}}

# Parse an existing Permissions-Policy header value into the same shape
parse_permissions_policy("camera=(), geolocation=(self)")
# {"ok": True, "output": {"header": "Permissions-Policy: camera=(), geolocation=(self)", "directives": [...], "warnings": []}}

from utilix.tools.units import calculate_payback_period, calculate_cd_maturity

# Payback period: simple + discounted, from an initial investment and periodic cash flows
calculate_payback_period(1000, [400, 400, 400], discount_rate_percent=8)
# {"ok": True, "output": {"paybackPeriod": 2.5, "paybackPeriodRounded": 3, "recovered": True, "discountedPaybackPeriod": 2.9029, ...}}

# Certificate of deposit maturity value, APY, and net early-withdrawal payout
calculate_cd_maturity(1000, 12, 12, 12, early_withdrawal_penalty_months=3)
# {"ok": True, "output": {"maturityValue": 1126.83, "totalInterest": 126.83, "apy": 12.6825, "earlyWithdrawalPayout": 1096.83, ...}}

from utilix.tools.network import build_client_hints, parse_client_hint_headers

# Build an Accept-CH header from a list of client hint names, with a plain-English note per hint
build_client_hints(["Sec-CH-UA-Platform", "Sec-CH-DPR"])
# {"ok": True, "output": {"acceptCH": "Sec-CH-UA-Platform, Sec-CH-DPR", "hints": [...]}}

# Decode raw Client Hints request-header text into structured values
parse_client_hint_headers("Sec-CH-UA-Mobile: ?0")
# {"ok": True, "output": {"hints": [{"name": "Sec-CH-UA-Mobile", "value": False, ...}], "unrecognized": []}}

from utilix.tools.units import calculate_depreciation_schedule

# Year-by-year depreciation schedule: straight-line, double-declining-balance, or sum-of-years-digits
calculate_depreciation_schedule(10000, 1000, 5, "straight-line")
# {"ok": True, "output": {"totalDepreciation": 9000.0, "schedule": [{"year": 1, "depreciationExpense": 1800.0, "accumulatedDepreciation": 1800.0, "bookValue": 8200.0}, ...]}}

from utilix.tools.text import analyze_keyword_density

# Keyword/phrase frequency and density (occurrences per 100 words); ngram_size 2 or 3 for phrases
analyze_keyword_density("machine learning models power machine learning systems")
# {"ok": True, "output": {"totalWords": 7, "uniqueKeywords": 5, "ngramSize": 1, "keywords": [{"keyword": "learning", "count": 2, "density": 28.6}, ...]}}

from utilix.tools.network import build_content_disposition, parse_content_disposition

# Build a Content-Disposition header; non-ASCII filenames get an RFC 5987
# filename* parameter alongside an ASCII-sanitized fallback
build_content_disposition({"type": "attachment", "filename": "résumé.pdf"})
# {"ok": True, "output": {"header": "attachment; filename=\"r_sum_.pdf\"; filename*=UTF-8''r%C3%A9sum%C3%A9.pdf", "type": "attachment", "filename": "résumé.pdf", "filenameEncoded": True}}

# Parse an existing Content-Disposition header, decoding filename* in preference to filename
parse_content_disposition('attachment; filename="report.pdf"')
# {"ok": True, "output": {"type": "attachment", "filename": "report.pdf", "filenameIsExtended": False, "params": {"filename": "report.pdf"}}}
```

---

## Modules

| Module | Description |
|---|---|
| `encoding` | Base64, Base32, Base58, Base62, URL encoding/decoding, HTML entity encoding |
| `hashing` | MD5, SHA-1/256/384/512 digests, bcrypt password hashing, htpasswd, SRI hashes, content-hash ETag generation |
| `json_tools` | JSON formatting, minification, diffing, CSV conversion, JSONPath, JSON Pointer (RFC 6901), JSON Schema, YAML-JSON |
| `color` | Color conversion (hex/RGB/HSL/HSV), palettes, contrast ratios, shades/tints, blending |
| `css` | Gradients, box shadows, border radius, animations, cubic bezier, clamp, specificity, minifier |
| `media` | Image compression, format conversion, favicon generation, SVG optimization, header-based format/dimension reading, PDF metadata reading, ICO favicon inspection, FLAC metadata reading, MP4/WebM video info, GIF frame/loop inspection |
| `time_tools` | Unix timestamp parsing, cron expression parsing, date diffing, timezone conversion, ISO 8601 duration parsing/formatting |
| `network` | IPv4 conversion, CIDR calculator, DNS lookup (DoH), IP geolocation, HAR file parsing, sitemap.xml validation, robots.txt validation, User-Agent parsing, Cache-Control header builder, Permissions-Policy header builder, Client Hints header builder/parser, Content-Disposition header builder/parser |
| `api_tools` | cURL builder/parser, cURL-to-code, JWT decode/sign, JWKS parsing, JWK thumbprint (RFC 7638), HTTP status codes, CORS/CSP builders |
| `code` | Regex tester, regex ReDoS detector, SQL formatter, HTML formatter/minifier, GraphQL formatter, semver, URL parser, JS minifier, MIME type lookup, GitHub URL parser, Conventional Commits linter |
| `data` | YAML, TOML, XML, CSV, INI, NDJSON, and `.env` file parsing, validation, and conversion, SRT/WebVTT subtitle conversion, Markdown front matter parsing |
| `generators` | UUID v4/v7, ULID, password generator, password strength check, password entropy estimator, random data, QR code generation |
| `text` | Word counter, case converter, lorem ipsum, slugifier, string escaping, diff viewer, Markdown/HTML, passive voice detection, readability scoring, filler word & cliché detection, Levenshtein/Damerau string distance, sentence length distribution, keyword density analyzer, emoji lookup |
| `misc` | Unicode analysis, ASCII art, Morse code, JSON-to-TypeScript/Go/Python/Zod schema generation, JSON Schema-to-TypeScript |
| `units` | px/rem/vw conversions, byte formatter, number base conversion, aspect ratio, chmod calculator, credit card Luhn/network validator, IBAN validator, loan/mortgage calculator, EMI calculator, barcode checksum validator, ISBN validator, VAT/sales tax calculator, compound/simple interest calculators, present/future value calculator, percentage change calculator, APR/APY converter, margin & markup calculator, discount/markdown calculator, tip/bill split calculator, break-even point calculator, video bitrate & file size calculator, Rule of 72 calculator, savings goal calculator, ROI calculator, cash denomination/till calculator, payback period calculator, CD maturity calculator, depreciation schedule calculator |
| `ai_agent` | Token estimate/trim, chunk text, extract URLs/JSON/keywords, sanitize HTML, flatten/merge JSON, dedupe lines, validate schema, PII/secret/injection detect, vector similarity, few-shot prompt formatter, tool-definition schema linter |

---

## Surface A vs Surface B

### Surface A: this package (`utilix-sdk`)

Everything in this package runs **locally in your Python process**. There are no network calls for the core utilities (DNS lookup and IP geolocation are the only exceptions, and both hit public free APIs). No account, no API key, no rate limits.

```
pip install utilix-sdk
```

Ideal for: scripts, CI pipelines, offline environments, CLIs, and any situation where you want deterministic, zero-cost utility functions.

### REST API (`api.utilix.tech/v1`)

The same 140+ tools are also available as a hosted REST API at `https://api.utilix.tech/v1`. This surface requires an API key and is subject to rate limits and pricing tiers.

```bash
curl -X POST https://api.utilix.tech/v1/tools/hash \
  -H "Authorization: Bearer $UTILIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": "hello world", "algorithm": "sha256"}'
```

- **Free**: 1,000 requests/day: no credit card required
- **Pro**: 10,000 requests/day: $9/month
- Try it live at **[utilix.tech/api](https://utilix.tech/api)**: no signup needed for the first 10 endpoints
- Get your API key at **[utilix.tech/dashboard](https://utilix.tech/dashboard)**

Ideal for: polyglot teams, environments where installing Python packages is not possible, and browser-based tooling that needs a backend.

---

## Contributing

The source for this package is maintained in a private monorepo; this repository holds examples, quickstarts, and the issue tracker. Found a bug or want to request a tool? Open an issue at [github.com/utilix-tech/utilix-sdk/issues](https://github.com/utilix-tech/utilix-sdk/issues) or email [hello@utilix.tech](mailto:hello@utilix.tech).

---

## Publishing to PyPI

### Build the distribution

```bash
python -m build
# Produces dist/utilix_sdk-x.y.z.tar.gz and dist/utilix_sdk-x.y.z-py3-none-any.whl
```

### Test on TestPyPI first

```bash
python -m twine upload --repository testpypi dist/*
# Install from TestPyPI to verify
pip install --index-url https://test.pypi.org/simple/ utilix-sdk
```

### Publish to PyPI

```bash
python -m twine upload dist/*
```

### GitHub Actions with OIDC trusted publisher (recommended)

Store no tokens. Configure a trusted publisher on PyPI (Settings > Publishing > Add a new publisher) and use the official PyPA action:

```yaml
# .github/workflows/publish.yml
name: Publish to PyPI

on:
  push:
    tags:
      - "v*"

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # Required for OIDC

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Build
        run: |
          pip install build
          python -m build

      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        # No api-token needed: OIDC trusted publisher handles auth
```

Tag a release (`git tag v0.2.0 && git push --tags`) and the workflow publishes automatically with no stored credentials.

---

## License

MIT. See [LICENSE](LICENSE) for the full text.
