# Tools reference

_Generated by `npm run docs` from the registry — 162 tools, s3eidat-tools-mcp 0.1.0. Do not edit by hand._

Examples were run with the clock set to 2026-01-15 09:30 UTC. File inputs take a local path or base64; file outputs go to `output` (a file, or a folder ending in `/`) or a temp folder, and the result returns the path. Everything runs locally.

## Contents

- [PDF](#pdf) (17)
- [Images](#image) (16)
- [Text](#text) (15)
- [Developer: data formats](#data) (15)
- [Developer: utilities](#dev) (23)
- [Calculators](#calc) (24)
- [Everyday & security](#everyday) (15)
- [QR & barcodes](#qr) (4)
- [Document makers](#docs) (7)
- [For AI agents](#ai) (26)

<a id="pdf"></a>
## PDF

Merge, split, organise, compress, watermark, number, sign and fill PDFs; text and images out.

| Tool | What it does |
|---|---|
| [`pdf_compress`](#pdf_compress) | Compress PDF |
| [`pdf_extract_pages`](#pdf_extract_pages) | Extract PDF pages |
| [`pdf_form_fields`](#pdf_form_fields) | List PDF form fields |
| [`pdf_form_fill`](#pdf_form_fill) | Fill a PDF form |
| [`pdf_from_images`](#pdf_from_images) | Images to PDF |
| [`pdf_info`](#pdf_info) | PDF info |
| [`pdf_merge`](#pdf_merge) | Merge PDFs |
| [`pdf_metadata_read`](#pdf_metadata_read) | Read PDF metadata |
| [`pdf_metadata_write`](#pdf_metadata_write) | Edit or strip PDF metadata |
| [`pdf_organize`](#pdf_organize) | Organize PDF pages |
| [`pdf_page_numbers`](#pdf_page_numbers) | Add page numbers |
| [`pdf_rotate`](#pdf_rotate) | Rotate PDF pages |
| [`pdf_sign_image`](#pdf_sign_image) | Sign PDF with an image |
| [`pdf_split`](#pdf_split) | Split PDF |
| [`pdf_to_images`](#pdf_to_images) | PDF to images |
| [`pdf_to_text`](#pdf_to_text) | PDF to text |
| [`pdf_watermark`](#pdf_watermark) | Watermark PDF |

<a id="pdf_compress"></a>
### `pdf_compress` — Compress PDF

Shrink a PDF by re-rendering every page as a JPEG image (best for scans and photo-heavy files). Presets like the website: email (96 DPI, quality 55), balanced (144 DPI, 72), high (220 DPI, 85); or set dpi/quality yourself; optional grayscale. The result loses selectable text, links and form fields. Reports before/after size and a verdict ("bigger" means keep the original: the PDF was already efficient). Web version: https://s3eidat.github.io/pdf-compress/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `preset` | "email" \| "balanced" \| "high" | no (default `"balanced"`) | Quality preset. Default "balanced". |
| `dpi` | integer | no | Override the preset resolution (36-300 DPI). |
| `quality` | integer | no | Override the preset JPEG quality (30-95). |
| `grayscale` | boolean | no (default `false`) | Convert pages to grayscale. |
| `password` | string | no | Password, if the PDF is protected. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size of the written PDF in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `pages` | number |  |
| `before` | number | Original size in bytes. |
| `after` | number | Compressed size in bytes. |
| `percentSmaller` | number | How much smaller, in % (negative when bigger). |
| `verdict` | "smaller" \| "same" \| "bigger" | "same" = under 5% saved; "bigger" = keep the original. |
| `dpi` | number |  |
| `quality` | number |  |

**Example** — small enough to attach to an email

```json
{
  "file": "scan.pdf",
  "preset": "email"
}
```

<a id="pdf_extract_pages"></a>
### `pdf_extract_pages` — Extract PDF pages

Make one new PDF from selected pages of a PDF, e.g. "1-3, 7, 10-" (order as written, each page once). The rest of the file is left out. To reorder, duplicate or delete pages use pdf_organize. Web version: https://s3eidat.github.io/pdf-split/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `pages` | string | yes | Pages to keep: "1-3, 7, 10-" ("5-" = to the end; also "odd", "even", "last"). |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size of the written PDF in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `pages` | number[] | 1-based source pages, in output order. |

**Example** — writes report-pages-1-3_7.pdf

```json
{
  "file": "report.pdf",
  "pages": "1-3, 7"
}
```

<a id="pdf_form_fields"></a>
### `pdf_form_fields` — List PDF form fields

List the fillable fields of a PDF form in reading order: name, readable label, type (text, checkbox, radio, dropdown, list, signature, button), page, current value, options, and whether it is required / read-only / multi-line. Use the names with pdf_form_fill. Web version: https://s3eidat.github.io/pdf-fill/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |

**Output**

| Field | Type | Description |
|---|---|---|
| `count` | number |  |
| `xfa` | boolean | The form also has an XFA (LiveCycle) layer; filling removes it. |
| `fields` | object[] |  |

**Example**

```json
{
  "file": "application.pdf"
}
```

<a id="pdf_form_fill"></a>
### `pdf_form_fill` — Fill a PDF form

Fill PDF form fields by name (get names from pdf_form_fields): text as strings, checkboxes as true/false, radio groups and dropdowns by option text, lists as arrays. Optionally flatten (bake the answers into the page, no longer editable). Only changed fields are redrawn. Text the standard font cannot draw (Arabic…) is kept and viewers are asked to draw it, but cannot be flattened. Web version: https://s3eidat.github.io/pdf-fill/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `values` | object | yes | Field name -> value, e.g. {"name": "Sara", "agree": true, "plan": "pro", "langs": ["Arabic"]}. |
| `flatten` | boolean | no (default `false`) | Flatten the form after filling (fields become plain page content). |
| `ignoreUnknown` | boolean | no (default `false`) | Skip names that are not in the form instead of failing. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size of the written PDF in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `filled` | string[] | Fields whose value was set. |
| `unknown` | string[] | Names not found in the form (only with ignoreUnknown). |
| `needAppearances` | string[] | Fields whose text the standard font cannot draw; the viewer draws them. |
| `flattened` | boolean |  |

**Example**

```json
{
  "file": "application.pdf",
  "values": {
    "name": "Sara Haddad",
    "agree": true,
    "plan": "pro",
    "country": "Egypt"
  },
  "flatten": true
}
```

<a id="pdf_from_images"></a>
### `pdf_from_images` — Images to PDF

Make a PDF from images (JPEG, PNG, WebP, GIF, TIFF, AVIF, SVG…), one image per page, in the order given. Page size A4, Letter, or "fit" (page = image size at 96 dpi); orientation auto/portrait/landscape; margin in mm; quality "original" (JPEG/PNG kept as-is), high (≤3000 px, JPEG 90), medium (≤2000 px, 75) or small (≤1400 px, 60). Photos are turned upright from their EXIF orientation. Web version: https://s3eidat.github.io/images-to-pdf/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `images` | (string \| object)[] | yes | Images in page order: path/base64 strings, or {file, rotate}. |
| `pageSize` | "a4" \| "letter" \| "fit" | no (default `"a4"`) | Page size. Default "a4". |
| `orientation` | "auto" \| "portrait" \| "landscape" | no (default `"auto"`) | "auto" turns the page to match each image. Ignored for "fit". |
| `margin` | number | no (default `10`) | Margin around each image in mm. Default 10. |
| `quality` | "original" \| "high" \| "medium" \| "small" | no (default `"high"`) | Image quality / size trade-off. Default "high". |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size of the written PDF in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `pages` | number |  |

**Example**

```json
{
  "images": [
    "receipt1.jpg",
    "receipt2.jpg"
  ],
  "output": "receipts.pdf"
}
```

**Example**

```json
{
  "images": [
    "scan.png",
    {
      "file": "photo.jpg",
      "rotate": 90
    }
  ],
  "pageSize": "fit",
  "margin": 0,
  "quality": "original"
}
```

<a id="pdf_info"></a>
### `pdf_info` — PDF info

Quick facts about a PDF for planning the next step: page count, page sizes (points, mm, paper name such as "A4 portrait", grouped by size), rotated pages, encryption, PDF version, form field count, title/author, and which pages have no text layer (scans that would need OCR). Related web tool: https://s3eidat.github.io/pdf-metadata/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `password` | string | no | Password, for the text check on a protected PDF. |
| `textCheck` | boolean | no (default `true`) | Check which pages have a text layer (reads up to 500 pages). Default true. |

**Output**

| Field | Type | Description |
|---|---|---|
| `pages` | number |  |
| `fileBytes` | number |  |
| `pdfVersion` | string |  |
| `encrypted` | boolean |  |
| `sizes` | object[] | Shown page sizes in points (rotation applied), grouped; "pages" lists the 1-based pages with that size. |
| `rotatedPages` | object[] |  |
| `formFields` | number |  |
| `title` | string \| null |  |
| `author` | string \| null |  |
| `producer` | string \| null |  |
| `pagesWithoutText` | number[] \| null | 1-based pages with no text layer; null when not checked. |
| `textChecked` | number | How many pages the text check looked at. |

**Example** — { pages: 12, sizes: [{ paper: "A4 portrait", pages: "1-12" }], pagesWithoutText: [1, …] }

```json
{
  "file": "scan.pdf"
}
```

<a id="pdf_merge"></a>
### `pdf_merge` — Merge PDFs

Combine several PDFs into one, in the order given. Each entry can be a file (path or base64) or {file, pages} to take only some pages ("1-3, 5, 8-"; "3-1" keeps reverse order; a page can repeat). Password-protected PDFs must be unlocked first. Web version: https://s3eidat.github.io/pdf-merge/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `files` | (string \| object)[] | yes | PDFs to merge, in order: a path/base64 string, or {file, pages}. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size of the written PDF in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `pages` | number | Pages in the merged PDF. |
| `sources` | object[] |  |

**Example** — report + pages 2-4 of the appendix

```json
{
  "files": [
    "report.pdf",
    {
      "file": "appendix.pdf",
      "pages": "2-4"
    }
  ],
  "output": "merged.pdf"
}
```

<a id="pdf_metadata_read"></a>
### `pdf_metadata_read` — Read PDF metadata

Show what a PDF reveals about itself: Title, Author, Subject, Keywords, Creator, Producer, creation/modification dates (with time zone), custom info fields, XMP metadata (size, edit history, creator) and the document ID. Optionally returns the XMP XML. Web version: https://s3eidat.github.io/pdf-metadata/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `includeXmp` | boolean | no (default `false`) | Also return the decoded XMP XML. |

**Output**

| Field | Type | Description |
|---|---|---|
| `pages` | number |  |
| `pdfVersion` | string |  |
| `fileBytes` | number |  |
| `fields` | object | Title, Author, Subject, Keywords, Creator, Producer ("" when empty). |
| `dates` | object | CreationDate and ModDate. |
| `custom` | object | Non-standard info fields (Company, SourceModified…). |
| `xmp` | object \| null |  |
| `pageLevelMetadata` | number | Pages (plus the catalog) carrying their own XMP/PieceInfo. |
| `documentId` | boolean | Whether the trailer has a document ID (a fingerprint linking copies). |
| `xml` | string \| null |  |

**Example**

```json
{
  "file": "contract.pdf"
}
```

<a id="pdf_metadata_write"></a>
### `pdf_metadata_write` — Edit or strip PDF metadata

Change PDF info fields (title, author, subject, keywords, creator, producer, dates, custom fields) or strip all metadata: info fields, XMP, page-level data and the document ID, and drop unreachable leftovers from earlier edits so no trace stays in the bytes. Untouched values keep their exact original form. Web version: https://s3eidat.github.io/pdf-metadata/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `strip` | boolean | no (default `false`) | Remove ALL metadata (other edits are ignored). |
| `title` | string | no | Title. "" removes it; omit to keep it. |
| `author` | string | no | Author. "" removes it; omit to keep it. |
| `subject` | string | no | Subject. "" removes it; omit to keep it. |
| `keywords` | string | no | Keywords. "" removes it; omit to keep it. |
| `creator` | string | no | Creator (the app that made it). "" removes it; omit to keep it. |
| `producer` | string | no | Producer (the PDF engine). "" removes it; omit to keep it. |
| `creationDate` | string | no | Creation date as ISO 8601, e.g. "2026-03-14T09:30:00+03:00". "" removes it; omit to keep it. |
| `modDate` | string | no | Modification date as ISO 8601. "" removes it; omit to keep it. |
| `custom` | object | no | Custom info fields to set; null removes one. E.g. {"Company": null}. |
| `removeXmp` | boolean | no (default `false`) | Remove the XMP metadata stream (and page-level XMP/PieceInfo). |
| `removeDocumentId` | boolean | no (default `false`) | Remove the document ID from the trailer. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size of the written PDF in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `removedObjects` | number | Unreachable objects dropped. |
| `metadata` | object |  |

**Example**

```json
{
  "file": "report.pdf",
  "title": "Final report",
  "author": "Sara Haddad"
}
```

**Example** — writes cv-clean.pdf with no metadata

```json
{
  "file": "cv.pdf",
  "strip": true
}
```

<a id="pdf_organize"></a>
### `pdf_organize` — Organize PDF pages

Rebuild a PDF from a page list: reorder, delete (leave pages out), duplicate (list a page twice) and insert blank pages. "3, 1-2, blank, 5-" puts page 3 first, then 1-2, a blank page (same size as the page before it), then 5 to the end; page 4 is dropped. Optionally rotate pages of the result. Existing page rotation is kept. Web version: https://s3eidat.github.io/pdf-organizer/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `pages` | string | yes | New page order (1-based source pages): e.g. "3, 1-2, blank, 2, 5-". Pages not listed are removed; "blank" adds an empty page. |
| `rotate` | object[] | no | Optional rotations applied to pages of the new order. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size of the written PDF in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `pages` | number | Pages in the result. |
| `order` | (number \| "blank")[] | Source page (1-based) of each output page, or "blank". |

**Example** — swap the first two pages, turn page 3

```json
{
  "file": "scan.pdf",
  "pages": "2, 1, 3-",
  "rotate": [
    {
      "pages": "3",
      "angle": 90
    }
  ]
}
```

<a id="pdf_page_numbers"></a>
### `pdf_page_numbers` — Add page numbers

Add page numbers to a PDF, plus an optional header and footer line. Format tokens: {n}, {total}, {date}, {filename} (e.g. "Page {n} of {total}", "{n} / {total}", "- {n} -"). Choose the corner/edge, start number, how many leading pages to skip (e.g. a cover), a page range, font, size, colour and margin. Numbers stay upright on rotated pages. Web version: https://s3eidat.github.io/pdf-page-numbers/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `format` | string | no (default `"Page {n} of {total}"`) | Number format with {n}, {total}, {date}, {filename}. Default "Page {n} of {total}". Empty = no number (header/footer only). |
| `position` | "tl" \| "tc" \| "tr" \| "bl" \| "bc" \| "br" | no (default `"bc"`) | Where the number goes: t/b = top/bottom, l/c/r = left/centre/right. Default "bc". |
| `start` | integer | no (default `1`) | Number of the first numbered page. Default 1. |
| `skip` | integer | no (default `0`) | Leave this many first pages without a number (e.g. 1 for a cover). Default 0. |
| `pages` | string | no | Pages to number: 1-based list like "1-3, 5, 8-" ("5-" = to the end, "-3" = first three, "3-1" = reversed). Also "all", "odd", "even", "last". Empty = all pages. |
| `font` | "Helvetica" \| "HelveticaBold" \| "TimesRoman" \| "Courier" | no (default `"Helvetica"`) |  |
| `size` | number | no (default `10`) | Text size in points. Default 10. |
| `color` | string | no (default `"#333333"`) | Colour as hex "#rrggbb". Default #333333. |
| `margin` | number | no (default `28`) | Distance from the page edge in points. Default 28. |
| `header` | string | no (default `""`) | Optional header text on numbered pages (tokens allowed). |
| `headerAlign` | "l" \| "c" \| "r" | no (default `"c"`) | Header alignment: l, c or r. |
| `footer` | string | no (default `""`) | Optional footer text on numbered pages (tokens allowed). |
| `footerAlign` | "l" \| "c" \| "r" | no (default `"c"`) | Footer alignment: l, c or r. |
| `date` | string | no | Text for {date}. Default: today in the server locale. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size of the written PDF in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `numbered` | number | How many pages got a number. |
| `first` | number \| null |  |
| `last` | number \| null |  |

**Example** — no number on the cover

```json
{
  "file": "report.pdf",
  "format": "Page {n} of {total}",
  "position": "br",
  "skip": 1
}
```

**Example**

```json
{
  "file": "thesis.pdf",
  "format": "{n}",
  "footer": "Draft, {date}",
  "footerAlign": "l"
}
```

<a id="pdf_rotate"></a>
### `pdf_rotate` — Rotate PDF pages

Rotate selected pages of a PDF by 90, 180 or 270 degrees clockwise (-90 = counter-clockwise), on top of any rotation they already have. Other pages, links and form fields are left as they are. Web version: https://s3eidat.github.io/pdf-organizer/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `angle` | 90 \| 180 \| 270 \| -90 | yes | Clockwise degrees: 90, 180, 270, or -90. |
| `pages` | string | no | Pages to rotate: 1-based list like "1-3, 5, 8-" ("5-" = to the end, "-3" = first three, "3-1" = reversed). Also "all", "odd", "even", "last". Empty = all pages. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size of the written PDF in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `rotated` | number[] | 1-based pages that were rotated. |
| `rotations` | number[] | Final /Rotate of every page, in degrees. |

**Example**

```json
{
  "file": "scan.pdf",
  "angle": 90,
  "pages": "even"
}
```

<a id="pdf_sign_image"></a>
### `pdf_sign_image` — Sign PDF with an image

Place a signature (or initials, stamp, logo) image on PDF pages at x/y/width in points, measured from the top-left of the page as it is shown (rotated pages are handled). A light paper background is removed by default so scanned signatures sit cleanly on the page. Optional text marks (name, date…) can be added too. This is a visual signature, not a cryptographic digital signature. To draw a signature by hand, use the web version: https://s3eidat.github.io/pdf-sign/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `signature` | string | no | Signature image: local path or base64 (PNG, JPEG, WebP…). |
| `pages` | string | no (default `"last"`) | Pages to sign: "last" (default), "1", "all", "1-3"… |
| `x` | number | no (default `72`) | Left edge in points from the left of the page (72 pt = 1 inch). Default 72. |
| `y` | number | no | Top edge in points from the TOP of the page. Default: 72 pt above the bottom margin. |
| `width` | number | no (default `150`) | Width in points. Default 150 (about 5.3 cm). |
| `height` | number | no | Height in points. Default: keeps the image aspect ratio. |
| `removeBackground` | boolean | no (default `true`) | Make light (paper) pixels transparent and trim to the ink. Default true. |
| `threshold` | integer | no (default `210`) | Brightness (0-255) above which pixels become transparent. Default 210. |
| `texts` | object[] | no | Optional text marks, e.g. the printed name or the date. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size of the written PDF in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `signedPages` | number[] |  |
| `signature` | object \| null | Placed size in points. |

**Example**

```json
{
  "file": "contract.pdf",
  "signature": "signature.png",
  "pages": "last",
  "x": 360,
  "y": 700,
  "width": 160
}
```

**Example**

```json
{
  "file": "form.pdf",
  "signature": "sig.jpg",
  "pages": "2",
  "x": 80,
  "y": 620,
  "texts": [
    {
      "text": "29/10/2026",
      "page": 2,
      "x": 300,
      "y": 640
    }
  ]
}
```

<a id="pdf_split"></a>
### `pdf_split` — Split PDF

Split one PDF into several PDFs written to a folder: by page ranges (one file per range, e.g. "1-3, 4-6, 7-"), every N pages, or one file per page. Files are named like the website: <name>-pages-1-3.pdf, <name>-part-01.pdf, <name>-page-001.pdf. Use pdf_extract_pages to get a single PDF with chosen pages. Web version: https://s3eidat.github.io/pdf-split/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `mode` | "ranges" \| "every" \| "single" | yes | "ranges": one PDF per range in `ranges`; "every": a new PDF every `every` pages; "single": one PDF per page. |
| `ranges` | string | no | For mode "ranges": comma-separated ranges, each becomes one PDF, e.g. "1-3, 4-6, 7-". |
| `every` | integer | no | For mode "every": pages per file (e.g. 2 makes 1-2, 3-4…). |
| `output` | string | no | Folder to write the PDFs into (created if missing). Default: a new folder in the server output folder. |

**Output**

| Field | Type | Description |
|---|---|---|
| `folder` | string | Folder the PDFs were written to. |
| `files` | object[] |  |

**Example** — three PDFs

```json
{
  "file": "book.pdf",
  "mode": "ranges",
  "ranges": "1-10, 11-20, 21-"
}
```

**Example**

```json
{
  "file": "scans.pdf",
  "mode": "every",
  "every": 2
}
```

<a id="pdf_to_images"></a>
### `pdf_to_images` — PDF to images

Render PDF pages to PNG, JPEG or WebP images in a folder (one file per page, named <name>-p001.png…). Choose the DPI (72 = screen size, 150 = default, 300 = print), JPEG/WebP quality, a page range, and a transparent background for PNG/WebP. Very large pages are capped at 16384 px a side / 16.7 megapixels, like the website. Web version: https://s3eidat.github.io/pdf-to-images/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `format` | "png" \| "jpeg" \| "webp" | no (default `"png"`) | Image format. Default "png". |
| `dpi` | number | no (default `150`) | Resolution in dots per inch. Default 150. |
| `quality` | integer | no (default `90`) | JPEG/WebP quality 1-100. Default 90. |
| `transparent` | boolean | no (default `false`) | Keep the page background transparent (PNG/WebP only). |
| `pages` | string | no | Pages to render: 1-based list like "1-3, 5, 8-" ("5-" = to the end, "-3" = first three, "3-1" = reversed). Also "all", "odd", "even", "last". Empty = all pages. |
| `password` | string | no | Password, if the PDF is protected. |
| `output` | string | no | Folder to write the images into (created if missing). Default: a new folder in the server output folder. |

**Output**

| Field | Type | Description |
|---|---|---|
| `folder` | string |  |
| `files` | object[] |  |

**Example**

```json
{
  "file": "slides.pdf",
  "format": "jpeg",
  "dpi": 96,
  "pages": "1-5"
}
```

**Example**

```json
{
  "file": "logo.pdf",
  "format": "png",
  "dpi": 300,
  "transparent": true
}
```

<a id="pdf_to_text"></a>
### `pdf_to_text` — PDF to text

Extract the text of a PDF with lines and paragraphs rebuilt (right-to-left Arabic/Hebrew lines in reading order), as plain text or Markdown, with optional page separators and reflow (join lines, mend hyphenated words). Returns per-page word counts and flags pages with no text layer (scans: this tool does no OCR). Supports page ranges and passwords. Give "output" to write a .txt/.md file instead of returning the text inline. Web version: https://s3eidat.github.io/pdf-to-text/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `pages` | string | no | Pages to read: 1-based list like "1-3, 5, 8-" ("5-" = to the end, "-3" = first three, "3-1" = reversed). Also "all", "odd", "even", "last". Empty = all pages. |
| `password` | string | no | Password, if the PDF is protected. |
| `format` | "text" \| "markdown" | no (default `"text"`) | "text" or "markdown" (## Page N headings, reflowed paragraphs). |
| `separators` | boolean | no (default `true`) | Put a "——— Page N ———" line before each page. Default true. |
| `reflow` | boolean | no (default `false`) | Join the lines of each paragraph and mend words split by a hyphen. |
| `perPage` | boolean | no (default `false`) | Also return each page's text separately. |
| `maxChars` | integer | no (default `200000`) | Cut the inline text after this many characters (the file output is never cut). |
| `output` | string | no | Write the text to this file (or folder ending in "/") instead of returning it inline. |

**Output**

| Field | Type | Description |
|---|---|---|
| `pageCount` | number | Pages in the PDF. |
| `words` | number |  |
| `characters` | number |  |
| `pagesWithText` | number |  |
| `pagesWithoutText` | number[] | 1-based pages with no text layer (probably scanned images). |
| `pages` | object[] |  |
| `text` | string |  |
| `truncated` | boolean |  |
| `path` | string |  |
| `bytes` | number |  |

**Example**

```json
{
  "file": "article.pdf",
  "reflow": true
}
```

**Example**

```json
{
  "file": "book.pdf",
  "pages": "1-20",
  "format": "markdown",
  "output": "book.md"
}
```

<a id="pdf_watermark"></a>
### `pdf_watermark` — Watermark PDF

Stamp a text or image watermark on PDF pages: centred or tiled across the page, with opacity, angle, size, colour and a page range. It reads upright on rotated pages. Text the standard PDF fonts cannot draw (Arabic, CJK…) is stamped as an image. Defaults match the website: "CONFIDENTIAL", Helvetica Bold 72 pt, #d64545, 25% opacity, 45°. Web version: https://s3eidat.github.io/pdf-watermark/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `text` | string | no | Watermark text (default "CONFIDENTIAL"). Ignored when "image" is given. |
| `image` | string | no | Image watermark instead of text: a local path or base64 (PNG, JPEG, WebP, SVG…). |
| `font` | "HelveticaBold" \| "Helvetica" \| "TimesRomanBold" \| "CourierBold" | no (default `"HelveticaBold"`) | Font for text. |
| `size` | number | no (default `72`) | Text size in points. Default 72. |
| `color` | string | no (default `"#d64545"`) | Colour as hex "#rrggbb". Default #d64545. |
| `scale` | number | no (default `40`) | Image width as % of the page width. Default 40. |
| `opacity` | number | no (default `25`) | Opacity in % (1-100). Default 25. |
| `angle` | number | no (default `45`) | Rotation in degrees, counter-clockwise as shown. Default 45. |
| `mode` | "center" \| "tile" | no (default `"center"`) | "center": one stamp in the middle; "tile": repeated across the page. |
| `gap` | number | no (default `80`) | Space between tiles in points (tile mode). Default 80. |
| `pages` | string | no | Pages to stamp: 1-based list like "1-3, 5, 8-" ("5-" = to the end, "-3" = first three, "3-1" = reversed). Also "all", "odd", "even", "last". Empty = all pages. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size of the written PDF in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `pagesStamped` | number |  |

**Example**

```json
{
  "file": "roadmap.pdf",
  "text": "DRAFT",
  "opacity": 20
}
```

**Example**

```json
{
  "file": "brochure.pdf",
  "image": "logo.png",
  "mode": "tile",
  "scale": 15,
  "angle": 0,
  "opacity": 10
}
```

<a id="image"></a>
## Images

Convert, resize, crop, compress, watermark, EXIF, palettes, favicons, placeholders.

| Tool | What it does |
|---|---|
| [`image_ascii_art`](#image_ascii_art) | Image to ASCII art |
| [`image_ascii_banner`](#image_ascii_banner) | Text to ASCII banner |
| [`image_compress`](#image_compress) | Compress image |
| [`image_convert`](#image_convert) | Convert image format |
| [`image_crop`](#image_crop) | Crop image |
| [`image_exif_read`](#image_exif_read) | Read photo metadata (EXIF) |
| [`image_exif_strip`](#image_exif_strip) | Remove photo metadata (EXIF) |
| [`image_favicon_set`](#image_favicon_set) | Favicon set generator |
| [`image_from_base64`](#image_from_base64) | Base64 / data URI to image file |
| [`image_palette`](#image_palette) | Colour palette from image |
| [`image_placeholder`](#image_placeholder) | Placeholder image |
| [`image_resize`](#image_resize) | Resize image |
| [`image_rotate`](#image_rotate) | Rotate / flip image |
| [`image_svg_to_png`](#image_svg_to_png) | SVG to PNG |
| [`image_to_base64`](#image_to_base64) | Image to Base64 / data URI |
| [`image_watermark`](#image_watermark) | Watermark image |

<a id="image_ascii_art"></a>
### `image_ascii_art` — Image to ASCII art

Turn an image into ASCII art: each character cell takes the average brightness of its area (cells are twice as tall as wide) and maps it onto a character ramp: standard " .:-=+*#%@", detailed (70 characters), blocks " ░▒▓█", simple, binary, or your own. Contrast, brightness and invert (for light text on dark backgrounds). Returns the text; format "png" also renders it to an image (optionally in the image's colours). Transparent areas count as white paper. Web version: https://s3eidat.github.io/ascii-art/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The image: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `columns` | integer | no (default `100`) | Characters per line. Default 100. |
| `ramp` | "standard" \| "detailed" \| "blocks" \| "simple" \| "binary" \| "custom" | no (default `"standard"`) | Character set, light → dark. Default standard. |
| `customRamp` | string | no (default `" .oO0@"`) | Characters for ramp "custom", from no ink to most ink (at least 2). |
| `contrast` | number | no (default `0`) | -100 … 100. Default 0. |
| `brightness` | number | no (default `0`) | -100 … 100. Default 0. |
| `invert` | boolean | no (default `false`) | For light text on a dark background. Default false. |
| `format` | "text" \| "png" | no (default `"text"`) | text (default) or png (also writes <name>-ascii.png). |
| `color` | boolean | no (default `false`) | PNG only: colour each character with the image colour. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string |  |
| `columns` | number |  |
| `rows` | number |  |
| `path` | string | Written .txt or .png file, if any. |

**Example**

```json
{
  "image": "/photos/cat.jpg",
  "columns": 80,
  "contrast": 20
}
```

**Example**

```json
{
  "image": "/photos/logo.png",
  "ramp": "blocks",
  "format": "png",
  "color": true
}
```

<a id="image_ascii_banner"></a>
### `image_ascii_banner` — Text to ASCII banner

Big banner letters from text, using the website's own bitmap fonts: block (█ 5×7), char (any fill character, e.g. #), shadow (█ with ░ shade), compact (half-block 5×7), small (█ 3×5), tiny (half-block 3×5). Letters A-Z, digits and common punctuation (unknown characters show as "?"); one banner per input line. Returns the text; format "png" also renders it to banner.png. Web version: https://s3eidat.github.io/ascii-art/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | yes | Text to render (case-insensitive). New lines make separate banners. |
| `style` | "block" \| "char" \| "shadow" \| "compact" \| "small" \| "tiny" | no (default `"block"`) | Letter style. Default block. |
| `fill` | string | no (default `"#"`) | Fill character(s) for style "char". Default "#". |
| `format` | "text" \| "png" | no (default `"text"`) | text (default) or png (also writes banner.png). |
| `theme` | "dark" \| "light" | no (default `"dark"`) | PNG colours: dark (teal on near-black, default) or light. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string |  |
| `lines` | number |  |
| `path` | string |  |

**Example** — A 7-line banner: "##      ##  ######" …

```json
{
  "text": "HI",
  "style": "char",
  "fill": "#"
}
```

Result:

```json
{
  "lines": 7
}
```

<a id="image_compress"></a>
### `image_compress` — Compress image

Make an image file smaller: re-encode at a quality (default 80) and optionally cap the longest side (maxSide), as JPEG, WebP, AVIF or PNG ("keep" = same format; GIF/BMP/TIFF become PNG). Or give targetBytes to find the highest quality that fits that size. With keepSmaller (default on) the original is kept when re-encoding would not make it smaller. Reports before/after sizes and % saved. Transparent areas become white in JPEG. Metadata (EXIF, GPS) is not copied. PNG is lossless (quality ignored). Web version: https://s3eidat.github.io/image-squeeze/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The image: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `format` | "keep" \| "jpeg" \| "webp" \| "avif" \| "png" | no (default `"keep"`) | Output format. Default keep (same as the input). |
| `quality` | integer | no (default `80`) | Quality 1-100 for JPEG / WebP / AVIF. Default 80 (website default). |
| `maxSide` | integer | no | Scale down so the longest side is at most this many pixels (e.g. 1920). |
| `targetBytes` | integer | no | Aim for a file of at most this many bytes: searches the highest quality that fits (lossy formats only; overrides quality). |
| `keepSmaller` | boolean | no (default `true`) | Keep the original bytes when the result would be larger (same format and size only). Default true. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `width` | number | Output width in pixels. |
| `height` | number | Output height in pixels. |
| `format` | string | Output format. |
| `inputBytes` | number | Size of the input file. |
| `savedBytes` | number | Bytes saved (negative when the file grew). |
| `savedPercent` | number | Percent saved, rounded. |
| `quality` | number | Quality used (lossy formats). |
| `kept` | boolean | True when the original was kept because it was already smaller. |
| `targetReached` | boolean | With targetBytes: whether the target was met. |
| `summary` | string |  |

**Example**

```json
{
  "image": "/photos/IMG_0042.jpg",
  "quality": 75,
  "maxSide": 1920
}
```

**Example** — Highest WebP quality that fits 150 KB.

```json
{
  "image": "/site/hero.png",
  "format": "webp",
  "targetBytes": 150000
}
```

<a id="image_convert"></a>
### `image_convert` — Convert image format

Convert an image to PNG, JPEG, WebP, AVIF, GIF or TIFF. Reads JPEG, PNG, WebP, AVIF/HEIF, GIF (first frame), TIFF, SVG, BMP and ICO. Transparent areas are filled with `background` when writing JPEG (or always with `flatten`). EXIF rotation is applied. SVGs are rasterised at their own size (width/height or viewBox; 1024×1024 if neither), scripts never run. Images larger than 16384 px a side or 100 MP are scaled down to fit. Web version: https://s3eidat.github.io/image-convert/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The image: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `format` | "png" \| "jpeg" \| "webp" \| "avif" \| "gif" \| "tiff" | no (default `"png"`) | Output format. Default png (as on the website). |
| `quality` | integer | no (default `92`) | Quality 1-100 for JPEG / WebP / AVIF (ignored for PNG). Default 92, same as the website. |
| `background` | string | no (default `"#ffffff"`) | Colour used for transparent areas when writing JPEG or when flatten is on. Default #ffffff. |
| `flatten` | boolean | no (default `false`) | Fill transparency with the background for every format, not only JPEG. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `width` | number | Output width in pixels. |
| `height` | number | Output height in pixels. |
| `format` | string | Output format. |
| `inputFormat` | string | Detected input format. |
| `scaled` | boolean | True when the image was scaled down to fit the size limit. |

**Example** — Writes logo.jpg with transparency on white.

```json
{
  "image": "/photos/logo.png",
  "format": "jpeg",
  "quality": 90,
  "background": "#ffffff"
}
```

**Example**

```json
{
  "image": "/icons/icon.svg",
  "format": "webp"
}
```

<a id="image_crop"></a>
### `image_crop` — Crop image

Crop an image to a pixel box (x, y, width, height) or to an aspect ratio (1:1, 4:5, 3:2, 16:9, 9:16, "W:H" or a number), taking the largest centred region (or aligned to an edge with `position`). Boxes are kept inside the image. Output JPEG/PNG/WebP/AVIF, name <name>-<W>x<H>.<ext>. Web version: https://s3eidat.github.io/image-resize/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The image: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `x` | integer | no | Left edge of the crop box (pixels). Use with y, width, height. |
| `y` | integer | no | Top edge of the crop box (pixels). |
| `width` | integer | no | Width of the crop box (pixels). |
| `height` | integer | no | Height of the crop box (pixels). |
| `ratio` | string \| number | no | Aspect ratio as "W:H" (e.g. "1:1", "4:5", "3:2", "16:9", "9:16"), a number (width/height), "orig" or "free". |
| `position` | "center" \| "top" \| "bottom" \| "left" \| "right" \| "top-left" \| "top-right" \| "bottom-left" \| "bottom-right" | no (default `"center"`) | Where the ratio crop sits. Default center (the website default). |
| `format` | "keep" \| "jpeg" \| "png" \| "webp" \| "avif" | no (default `"keep"`) | Output format. "keep" = same as the input when it is JPEG/PNG/WebP (else PNG). Default keep. |
| `quality` | integer | no (default `90`) | Quality 1-100 for JPEG / WebP / AVIF (ignored for PNG). Default 90, same as the website. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `width` | number | Output width in pixels. |
| `height` | number | Output height in pixels. |
| `format` | string | Output format. |
| `crop` | object |  |

**Example** — Largest centred square.

```json
{
  "image": "/photos/group.jpg",
  "ratio": "1:1"
}
```

**Example**

```json
{
  "image": "/photos/group.jpg",
  "x": 100,
  "y": 50,
  "width": 800,
  "height": 600
}
```

<a id="image_exif_read"></a>
### `image_exif_read` — Read photo metadata (EXIF)

Show the hidden metadata in a photo: camera make/model, lens, date taken, exposure, ISO, software, author/copyright, serial numbers, comments, PNG text chunks, and GPS coordinates (where the photo was taken). Lists every metadata block (EXIF, XMP, ICC, IPTC, embedded previews) with its size and flags privacy-sensitive fields. JPEG and PNG fully; WebP/AVIF/HEIC/TIFF EXIF via sharp. Web version: https://s3eidat.github.io/exif-cleaner/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The photo: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |

**Output**

| Field | Type | Description |
|---|---|---|
| `format` | string |  |
| `width` | number |  |
| `height` | number |  |
| `fields` | object[] | Readable metadata fields in the website order. |
| `gps` | object \| null | Location the photo reveals, if any. |
| `orientation` | number | EXIF orientation (1 = normal, 6 = rotate 90° clockwise, …). |
| `blocks` | object[] | Metadata blocks found in the file. |
| `hasMetadata` | boolean |  |
| `revealsLocation` | boolean |  |

**Example** — Camera Google Pixel 9, Date taken 2026:10:21 18:42:05, GPS 31.953900, 35.910600.

```json
{
  "image": "/photos/IMG_20261021_184205.jpg"
}
```

<a id="image_exif_strip"></a>
### `image_exif_strip` — Remove photo metadata (EXIF)

Remove EXIF, GPS location, XMP, IPTC, comments, PNG text chunks and embedded previews from a photo before sharing it. JPEG and PNG are cleaned losslessly (image data copied byte for byte, like the website); by default the rotation is kept as a tiny orientation-only EXIF and the ICC colour profile is kept. applyOrientation re-encodes JPEGs with the rotation baked into the pixels. WebP/AVIF/HEIC/TIFF are re-encoded without metadata. Output name <name>-clean.<ext>. Web version: https://s3eidat.github.io/exif-cleaner/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The photo: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `keepOrientation` | boolean | no (default `true`) | Keep only the Orientation tag so the photo still shows the right way up. Default true. |
| `keepIcc` | boolean | no (default `true`) | Keep the ICC colour profile (no personal data; keeps colours right). Default true. |
| `applyOrientation` | boolean | no (default `false`) | Instead of keeping the Orientation tag, rotate the pixels (re-encodes JPEG at `quality`; PNG is unaffected). Default false. |
| `quality` | integer | no (default `92`) | JPEG/WebP/AVIF quality when re-encoding is needed. Default 92. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string |  |
| `bytes` | number |  |
| `mimeType` | string |  |
| `base64` | string |  |
| `inputBytes` | number |  |
| `format` | string |  |
| `removed` | object[] | Metadata blocks that were removed. |
| `hadLocation` | boolean | True when the original revealed a GPS location. |
| `orientation` | number | Orientation of the input (1 = normal). |
| `reencoded` | boolean | True when the pixels were re-encoded (non-JPEG/PNG input or applyOrientation). |

**Example** — Writes IMG_1234-clean.jpg: no GPS, camera or date; same pixels.

```json
{
  "image": "/photos/IMG_1234.jpg"
}
```

**Example**

```json
{
  "image": "/photos/IMG_1234.jpg",
  "applyOrientation": true
}
```

<a id="image_favicon_set"></a>
### `image_favicon_set` — Favicon set generator

Make a complete favicon set from a letter/emoji/short text or from an image: favicon.ico (16, 32, 48 px), favicon-16x16.png, favicon-32x32.png, apple-touch-icon.png (180, square, solid), android-chrome-192x192.png and -512x512.png, site.webmanifest and head-snippet.html with the <link> tags. Rounded square with background colour (or transparent), corner radius and padding in %. Writes everything into one folder. Web version: https://s3eidat.github.io/favicon-forge/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no (default `"S"`) | Letter(s) or emoji drawn on the icon (when no image is given). Default "S". |
| `image` | string | no | Use this image (logo) instead of text: a local path or base64. Square, 512 px+ works best. |
| `weight` | "400" \| "500" \| "600" \| "700" \| "800" \| "900" | no (default `"900"`) | Font weight for text. Default 900. |
| `color` | string | no (default `"#ffffff"`) | Text colour. Default #ffffff. |
| `background` | string | no (default `"#0e7c86"`) | Background colour. Default #0e7c86. |
| `transparent` | boolean | no (default `false`) | Transparent background (the Apple icon still gets white). Default false. |
| `radius` | number | no (default `22`) | Corner radius in % of the size (50 = circle). Default 22. |
| `padding` | number | no (default `14`) | Padding around the text/image in %. Default 14. |
| `fit` | "contain" \| "cover" | no (default `"contain"`) | How an image fills the padded square. Default contain. |
| `name` | string | no (default `"My Website"`) | App name for site.webmanifest. Default "My Website". |
| `shortName` | string | no (default `"Site"`) | Short name for site.webmanifest. Default "Site". |
| `output` | string | no | Folder to write the files into. Default: a new "favicons" folder in the output folder. |

**Output**

| Field | Type | Description |
|---|---|---|
| `folder` | string |  |
| `files` | object[] |  |
| `snippet` | string | The <head> tags to paste. |

**Example**

```json
{
  "text": "S3",
  "background": "#0e7c86",
  "radius": 24,
  "padding": 16,
  "name": "S3eidat Tools",
  "shortName": "S3eidat"
}
```

Result:

```json
{
  "folder": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-HmqiFx\\favicons\\",
  "files": [
    {
      "name": "favicon.ico",
      "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-HmqiFx\\favicons\\favicon.ico",
      "bytes": 4462
    },
    {
      "name": "favicon-16x16.png",
      "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-HmqiFx\\favicons\\favicon-16x16.png",
      "bytes": 688
    },
    {
      "name": "favicon-32x32.png",
      "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-HmqiFx\\favicons\\favicon-32x32.png",
      "bytes": 1394
    },
    {
      "name": "apple-touch-icon.png",
      "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-HmqiFx\\favicons\\apple-touch-icon.png",
      "bytes": 6647
    },
    {
      "name": "android-chrome-192x192.png",
      "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-HmqiFx\\favicons\\android-chrome-192x192.png",
      "bytes": 9738
    },
    {
      "name": "android-chrome-512x512.png",
      "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-HmqiFx\\favicons\\android-chrome-512x512.png",
      "bytes": 20070
    },
    {
      "name": "site.webmanifest",
      "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-HmqiFx\\favicons\\site.webmanifest",
      "bytes": 380
    },
    {
      "name": "head-snippet.html",
      "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-HmqiFx\\favicons\\head-snippet.html",
      "bytes": 368
    }
  ],
  "snippet": "<link rel=\"icon\" href=\"/favicon.ico\" sizes=\"48x48\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\">\n<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-touch-icon.png\">\n<link rel=\"manifest\" href=\"/site.webmanifest\">\n<meta name=\"theme-color\" content=\"#0e7c86\">"
}
```

**Example**

```json
{
  "image": "/brand/logo.png",
  "transparent": true,
  "radius": 0,
  "output": "/site/public/"
}
```

<a id="image_from_base64"></a>
### `image_from_base64` — Base64 / data URI to image file

Decode Base64 back into a file: accepts a data URI, a CSS url("data:…") rule, an <img src="data:…"> tag, URL-encoded SVG data URIs, or raw Base64 (standard or URL-safe, line breaks allowed). Detects the real type from the bytes, warns when it differs from the declared one, and writes decoded.<ext>. Give the text inline or as a path to a text file. Web version: https://s3eidat.github.io/image-to-base64/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The data URI / CSS / <img> tag / raw Base64. |
| `path` | string | no | Or: a text file containing it. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string |  |
| `bytes` | number |  |
| `mimeType` | string |  |
| `declaredMimeType` | string | Type named in the data URI, if any. |
| `detectedMimeType` | string | Type detected from the bytes. |
| `isImage` | boolean |  |
| `width` | number |  |
| `height` | number |  |
| `status` | string | Same message as the website: "Valid image", or a warning. |

**Example** — Writes decoded.png (1×1).

```json
{
  "text": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-BKoKKG\\decoded.png",
  "bytes": 70,
  "mimeType": "image/png",
  "declaredMimeType": "image/png",
  "detectedMimeType": "image/png",
  "isImage": true,
  "width": 1,
  "height": 1,
  "status": "Valid image"
}
```

<a id="image_palette"></a>
### `image_palette` — Colour palette from image

Extract the main colours of an image with their share of the picture: k-means (default, deterministic) or median cut, 1-16 colours, sorted by share. Transparent pixels are ignored. Returns hex + rgb + % per colour, CSS custom properties (:root { --color-1 … }) or JSON, and optionally writes the website's swatch strip as palette.png. Same algorithm and sampling as the website, so the colours match. Web version: https://s3eidat.github.io/palette-from-image/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The image: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `count` | integer | no (default `6`) | Number of colours. Default 6. |
| `method` | "kmeans" \| "median" | no (default `"kmeans"`) | kmeans (default) or median (median cut). |
| `format` | "css" \| "json" | no (default `"css"`) | Export text format. Default css. |
| `swatchPng` | boolean | no (default `false`) | Also write palette.png (160×200 swatch per colour with its hex code). |
| `output` | string | no | Where to write palette.png (file path or folder ending in "/"). Implies swatchPng. |

**Output**

| Field | Type | Description |
|---|---|---|
| `colors` | object[] |  |
| `text` | string | CSS custom properties or JSON, as exported by the website. |
| `pixelsSampled` | number |  |
| `path` | string | palette.png, when written. |

**Example** — colors: [{ hex: "#1f2f6e", percent: "31.2%" }, …]

```json
{
  "image": "/photos/sunset.jpg",
  "count": 5
}
```

<a id="image_placeholder"></a>
### `image_placeholder` — Placeholder image

Make a placeholder image of any size (up to 8000 px a side): solid background with centred text (default "W × H", "\n" for line breaks), auto-fitted font size or a fixed one, PNG / JPEG / WebP / SVG. Size presets for web, social media, IAB ads and devices. Optionally returns a data URI / <img> tag for inlining. Text uses Inter when installed, else a system sans-serif. Web version: https://s3eidat.github.io/placeholder-image/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `width` | integer | no (default `1200`) | Width in pixels. Default 1200. |
| `height` | integer | no (default `630`) | Height in pixels. Default 630. |
| `preset` | "open-graph-link-preview" \| "hero-16-9-full-hd" \| "hd-16-9" \| "blog-image-4-3" \| "thumbnail-3-2" \| "avatar" \| "instagram-square" \| "instagram-portrait-4-5" \| "story-reel-9-16" \| "x-twitter-header" \| "linkedin-banner" \| "youtube-thumbnail" \| "medium-rectangle" \| "leaderboard" \| "wide-skyscraper" \| "mobile-banner" \| "half-page" \| "billboard" \| "iphone-15-screen" \| "desktop-1440" \| "4k-uhd" | no | Size preset (overrides width/height): open-graph-link-preview 1200×630, hero-16-9-full-hd 1920×1080, hd-16-9 1280×720, blog-image-4-3 800×600, thumbnail-3-2 600×400, avatar 256×256, instagram-square 1080×1080, instagram-portrait-4-5 1080×1350, story-reel-9-16 1080×1920, x-twitter-header 1500×500, linkedin-banner 1584×396, youtube-thumbnail 1280×720, medium-rectangle 300×250, leaderboard 728×90, wide-skyscraper 160×600, mobile-banner 320×50, half-page 300×600, billboard 970×250, iphone-15-screen 1179×2556, desktop-1440 1440×900, 4k-uhd 3840×2160. |
| `background` | string | no (default `"#1f2937"`) | Background colour (#rgb or #rrggbb). Default #1f2937. |
| `color` | string | no (default `"#9ca3af"`) | Text colour (#rgb or #rrggbb). Default #9ca3af. |
| `text` | string | no (default `""`) | Text to show. Empty = "W × H". Use "\n" (backslash n) or a real newline for a line break. |
| `fontSize` | number | no (default `0`) | Font size in px; 0 = auto-fit (default). |
| `fontWeight` | "400" \| "500" \| "600" \| "700" \| "800" \| "900" | no (default `"600"`) | Font weight. Default 600. |
| `format` | "png" \| "jpeg" \| "webp" \| "svg" | no (default `"png"`) | Output format. Default png. |
| `dataUri` | boolean | no (default `false`) | Also return the image as a data URI and an <img> tag (for small images). |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string |  |
| `bytes` | number |  |
| `mimeType` | string |  |
| `base64` | string |  |
| `width` | number |  |
| `height` | number |  |
| `format` | string |  |
| `aspectRatio` | string | Reduced ratio, e.g. "40:21". |
| `megapixels` | string |  |
| `fontSize` | number | Font size used, in px. |
| `dataUri` | string |  |
| `imgTag` | string |  |

**Example** — placeholder-1200x630.png with "1200 × 630".

```json
{
  "width": 1200,
  "height": 630
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-yPrhd0\\placeholder-1200x630.png",
  "bytes": 29305,
  "mimeType": "image/png",
  "width": 1200,
  "height": 630,
  "format": "png",
  "aspectRatio": "40:21",
  "megapixels": "0.76",
  "fontSize": 133
}
```

**Example**

```json
{
  "preset": "leaderboard",
  "background": "#0e9f9a",
  "color": "#e6fffd",
  "text": "Ad space",
  "format": "svg"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-FqID82\\placeholder-728x90.svg",
  "bytes": 421,
  "mimeType": "image/svg+xml",
  "width": 728,
  "height": 90,
  "format": "svg",
  "aspectRatio": "364:45",
  "megapixels": "0.07",
  "fontSize": 31
}
```

<a id="image_resize"></a>
### `image_resize` — Resize image

Resize an image by pixels or percent, keeping the aspect ratio. Give width or height (the other side follows), or both: fit "cover" (default, as on the website) centre-crops to that ratio first so nothing is stretched, "contain" fits inside the box, "fill" stretches. Optional crop ratio (1:1, 4:5, 3:2, 16:9, 9:16, orig) or crop box before resizing, social-media presets, output JPEG/PNG/WebP/AVIF. Up to 16384 px a side. Output name: <name>-<W>x<H>.<ext>. Web version: https://s3eidat.github.io/image-resize/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The image: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `width` | integer | no | Target width in pixels. |
| `height` | integer | no | Target height in pixels. |
| `percent` | number | no | Scale by percent instead (e.g. 50 halves both sides, 200 doubles). |
| `preset` | "instagram-post" \| "instagram-portrait" \| "story" \| "youtube-thumbnail" \| "x-header" \| "facebook-cover" \| "linkedin-banner" \| "full-hd" \| "passport-35x45" \| "us-passport-2x2" | no | Exact output size preset: instagram-post 1080×1080, instagram-portrait 1080×1350, story 1080×1920, youtube-thumbnail 1280×720, x-header 1500×500, facebook-cover 851×315, linkedin-banner 1584×396, full-hd 1920×1080, passport-35x45 413×531, us-passport-2x2 600×600. |
| `fit` | "cover" \| "contain" \| "fill" | no (default `"cover"`) | When both width and height are set: cover (crop to fill, default), contain (fit inside), fill (stretch). |
| `ratio` | string \| number | no | Aspect ratio as "W:H" (e.g. "1:1", "4:5", "3:2", "16:9", "9:16"), a number (width/height), "orig" or "free". |
| `crop` | object | no | Crop box (x, y, width, height in pixels) applied before resizing; overrides ratio. |
| `format` | "keep" \| "jpeg" \| "png" \| "webp" \| "avif" | no (default `"keep"`) | Output format. "keep" = same as the input when it is JPEG/PNG/WebP (else PNG). Default keep. |
| `quality` | integer | no (default `90`) | Quality 1-100 for JPEG / WebP / AVIF (ignored for PNG). Default 90, same as the website. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `width` | number | Output width in pixels. |
| `height` | number | Output height in pixels. |
| `format` | string | Output format. |
| `crop` | object | Region of the input that was used. |

**Example** — Keeps the aspect ratio: 1600×900 → 1080×608.

```json
{
  "image": "/photos/beach.jpg",
  "width": 1080
}
```

**Example** — Centre-crops to 1:1 and writes beach-1080x1080.jpg.

```json
{
  "image": "/photos/beach.jpg",
  "preset": "instagram-post"
}
```

**Example**

```json
{
  "image": "/photos/beach.jpg",
  "percent": 50,
  "format": "webp"
}
```

<a id="image_rotate"></a>
### `image_rotate` — Rotate / flip image

Rotate an image clockwise by 90, 180, 270 or any angle (the canvas grows to fit; new corners get `background`), and/or flip it horizontally (mirror) or vertically. Flips apply after the rotation, as seen on screen. EXIF orientation is applied first (autoOrient, default on), so with angle 0 this simply "fixes" sideways phone photos. Output JPEG/PNG/WebP/AVIF ("keep" = same as input), name <name>-rotated.<ext>. Web version: https://s3eidat.github.io/photo-filters/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The image: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `angle` | number | no (default `90`) | Clockwise rotation in degrees (negative = counter-clockwise). Default 90. |
| `flipHorizontal` | boolean | no (default `false`) | Mirror left-right (after rotating). |
| `flipVertical` | boolean | no (default `false`) | Mirror top-bottom (after rotating). |
| `autoOrient` | boolean | no (default `true`) | Apply the EXIF Orientation tag first (what browsers show). Default true. |
| `background` | string | no (default `"#00000000"`) | Fill for the corners uncovered by a non-right-angle rotation. Default transparent (white in JPEG). |
| `format` | "keep" \| "jpeg" \| "png" \| "webp" \| "avif" | no (default `"keep"`) | Output format. Default keep (JPEG/PNG/WebP stay, others become PNG). |
| `quality` | integer | no (default `92`) | Quality 1-100 for JPEG / WebP / AVIF (ignored for PNG). Default 92, same as the website. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `width` | number | Output width in pixels. |
| `height` | number | Output height in pixels. |
| `format` | string | Output format. |
| `orientationApplied` | number | EXIF orientation that was applied (1 = none). |

**Example**

```json
{
  "image": "/photos/scan.jpg",
  "angle": 90
}
```

**Example** — Un-mirror a selfie.

```json
{
  "image": "/photos/selfie.jpg",
  "angle": 0,
  "flipHorizontal": true
}
```

**Example** — Bake the EXIF rotation into the pixels.

```json
{
  "image": "/photos/IMG_1234.jpg",
  "angle": 0
}
```

<a id="image_svg_to_png"></a>
### `image_svg_to_png` — SVG to PNG

Rasterise an SVG to a crisp PNG: scale (1×, 2×, 3×, 4×… default 2×), a target width or height (aspect kept), or an exact size (stretched). Units (px, pt, mm, cm, in) and viewBox-only SVGs are understood; transparent background or a solid colour. Scripts, event handlers and external references are removed first. Name <name>@2x.png. Web version: https://s3eidat.github.io/svg-to-png/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `svg` | string | no | The SVG: a local path or base64 / data URI. |
| `svgText` | string | no | Or: the SVG code itself. |
| `mode` | "scale" \| "width" \| "height" \| "exact" | no (default `"scale"`) | How to size the PNG. Default scale. |
| `scale` | number | no (default `2`) | Scale factor for mode "scale". Default 2. |
| `width` | integer | no | Width in px for modes width / exact. |
| `height` | integer | no | Height in px for modes height / exact. |
| `transparent` | boolean | no (default `true`) | Keep the background transparent. Default true. |
| `background` | string | no (default `"#ffffff"`) | Background colour when transparent is false. Default #ffffff. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string |  |
| `bytes` | number |  |
| `mimeType` | string |  |
| `base64` | string |  |
| `width` | number |  |
| `height` | number |  |
| `svgWidth` | number | Natural width of the SVG. |
| `svgHeight` | number | Natural height of the SVG. |

**Example** — Writes logo@3x.png.

```json
{
  "svg": "/icons/logo.svg",
  "scale": 3
}
```

**Example** — 200×100 PNG.

```json
{
  "svgText": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 50\"/>",
  "mode": "width",
  "width": 200
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-sMIPxv\\image.png",
  "bytes": 179,
  "mimeType": "image/png",
  "width": 200,
  "height": 100,
  "svgWidth": 100,
  "svgHeight": 50
}
```

<a id="image_to_base64"></a>
### `image_to_base64` — Image to Base64 / data URI

Encode an image (or any file) as Base64: a data URI, bare Base64, a CSS background-image rule, an HTML <img> tag (with width/height), Markdown, or for SVG a URL-encoded data URI (usually ~30% smaller). Detects the type from the bytes and warns when the result is too big to inline (over 10 KB: slows pages; over 1 MB: use a normal file). Results longer than maxChars are written to a .txt file instead of being returned. Web version: https://s3eidat.github.io/image-to-base64/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The image: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `as` | "dataUri" \| "base64" \| "css" \| "html" \| "markdown" \| "svgUri" | no (default `"dataUri"`) | What to return: dataUri (default), base64, css, html, markdown, or svgUri (SVG only). |
| `mimeType` | string | no | Override the detected MIME type (e.g. image/svg+xml). |
| `maxChars` | integer | no (default `200000`) | Longest text returned inline; longer results go to a file. Default 200000. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string | The requested snippet (omitted when written to a file). |
| `path` | string | Text file holding the snippet, when written to a file. |
| `mimeType` | string |  |
| `width` | number |  |
| `height` | number |  |
| `fileBytes` | number | Size of the original file. |
| `dataUriLength` | number | Length of the Base64 data URI in characters. |
| `overheadPercent` | number | How much bigger the data URI is than the file, in %. |
| `textLength` | number |  |
| `warning` | object |  |

**Example** — background-image: url("data:image/png;base64,…");

```json
{
  "image": "/site/icons/logo.png",
  "as": "css"
}
```

**Example**

```json
{
  "image": "/site/icons/arrow.svg",
  "as": "svgUri"
}
```

<a id="image_watermark"></a>
### `image_watermark` — Watermark image

Add a text or logo watermark to a photo. Size is a % of the photo width, margin a % of the shorter side, so the result looks the same on any resolution. One of 9 positions or tiled across the whole image (brick pattern), with opacity, rotation, colour, weight and a soft shadow for text. Logo: any image (PNG with transparency works best). Output keeps the input format (JPEG/PNG/WebP) or as chosen; name <name>-watermarked.<ext>. Text uses the Inter font when installed, else a system sans-serif. Web version: https://s3eidat.github.io/watermark/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The photo: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `text` | string | no (default `"© Your Name"`) | Watermark text (ignored when a logo is given). Default "© Your Name". |
| `logo` | string | no | Use this image as the watermark instead of text: a local path or base64. |
| `color` | string | no (default `"#ffffff"`) | Text colour. Default #ffffff. |
| `weight` | "400" \| "500" \| "600" \| "700" \| "800" \| "900" | no (default `"700"`) | Font weight. Default 700. |
| `shadow` | boolean | no (default `true`) | Soft drop shadow behind text. Default true. |
| `position` | "top-left" \| "top" \| "top-right" \| "left" \| "center" \| "right" \| "bottom-left" \| "bottom" \| "bottom-right" | no (default `"bottom-right"`) | Where the watermark sits (ignored when tiled). Default bottom-right. |
| `tiled` | boolean | no (default `false`) | Repeat the watermark across the whole photo. |
| `opacity` | number | no (default `60`) | Opacity in %. Default 60. |
| `size` | number | no (default `28`) | Watermark width as % of the photo width. Default 28. |
| `rotation` | number | no (default `0`) | Rotation in degrees (e.g. -30 for a diagonal tile). Default 0. |
| `margin` | number | no (default `4`) | Margin from the edge in % of the shorter side; spacing between tiles when tiled. Default 4. |
| `format` | "keep" \| "jpeg" \| "png" \| "webp" | no (default `"keep"`) | Output format. Default keep. |
| `quality` | integer | no (default `92`) | Quality 1-100 for JPEG / WebP / AVIF (ignored for PNG). Default 92, same as the website. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `width` | number | Output width in pixels. |
| `height` | number | Output height in pixels. |
| `format` | string | Output format. |

**Example**

```json
{
  "image": "/photos/shoot-01.jpg",
  "text": "© Studio Lina",
  "position": "bottom-right",
  "opacity": 60
}
```

**Example**

```json
{
  "image": "/photos/proof.jpg",
  "text": "PROOF",
  "tiled": true,
  "rotation": -25,
  "opacity": 35,
  "size": 26,
  "margin": 3
}
```

**Example**

```json
{
  "image": "/photos/product.png",
  "logo": "/brand/logo.png",
  "position": "top-left",
  "size": 20
}
```

<a id="text"></a>
## Text

Counting, case, slugs, cleaning, diffs, readability, Arabic tools, numbers to words, citations.

| Tool | What it does |
|---|---|
| [`text_arabic`](#text_arabic) | Arabic text tools |
| [`text_case`](#text_case) | Case convert |
| [`text_citation`](#text_citation) | Citation generator |
| [`text_clean`](#text_clean) | Text cleaner |
| [`text_diff`](#text_diff) | Diff two texts |
| [`text_escape`](#text_escape) | Escape / unescape text |
| [`text_fancy`](#text_fancy) | Fancy text (Unicode styles) |
| [`text_layout_fix`](#text_layout_fix) | Keyboard layout fixer (EN ⇄ AR) |
| [`text_lorem`](#text_lorem) | Lorem ipsum generator |
| [`text_morse`](#text_morse) | Morse code translator |
| [`text_number_to_words`](#text_number_to_words) | Number to words (English & Arabic) |
| [`text_readability`](#text_readability) | Readability score |
| [`text_slugify`](#text_slugify) | Slugify |
| [`text_social_count`](#text_social_count) | Social media character counter |
| [`text_word_count`](#text_word_count) | Word count |

<a id="text_arabic"></a>
### `text_arabic` — Arabic text tools

Arabic text utilities. mode "clean": remove tashkeel (harakat and Quranic marks, optionally keeping shadda), remove tatweel (kashida), normalise letters (alef with hamza/madda/wasla → bare alef; alef maqsura and Farsi ya → ya; ta marbuta → ha; hamza on waw/ya → waw/ya), switch Latin , ; ? to Arabic punctuation, and convert digits (Arabic-Indic ↔ Western). mode "arabizi": turn Arabizi / Franco-Arabic chat text (mar7aba, 3arabi, 5ala9, el bait) into Arabic script. Returns the result with letter/word/character counts. Give "text" or a text file "path". Web version: https://s3eidat.github.io/arabic-tools/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The text to transform. |
| `path` | string | no | Or: a path to a UTF-8 text file. |
| `mode` | "clean" \| "arabizi" | no (default `"clean"`) | "clean" = clean & normalise Arabic text (default); "arabizi" = Arabizi (Latin letters + numbers) → Arabic script. |
| `removeTashkeel` | boolean | no (default `true`) | clean: remove tashkeel / harakat (default true). |
| `keepShadda` | boolean | no (default `false`) | clean: when removing tashkeel, keep the shadda. |
| `removeTatweel` | boolean | no (default `true`) | clean: remove tatweel / kashida (default true). |
| `normalizeAlef` | boolean | no (default `false`) | clean: alef with hamza above/below, madda or wasla → bare alef. |
| `normalizeYa` | boolean | no (default `false`) | clean: alef maqsura and Farsi ya → Arabic ya. |
| `taMarbutaToHa` | boolean | no (default `false`) | clean: ta marbuta → ha. |
| `normalizeHamza` | boolean | no (default `false`) | clean: hamza on waw → waw, hamza on ya → ya. |
| `arabicPunctuation` | boolean | no (default `false`) | clean: Latin comma, semicolon and question mark → Arabic ones. |
| `digits` | "keep" \| "western" \| "arabic" | no (default `"keep"`) | clean: keep digits, convert Arabic-Indic/Persian digits to 0-9 ("western"), or 0-9 to Arabic-Indic ("arabic"). |
| `output` | string | no | Optional: write the result to this file path (or folder ending in "/") instead of returning it. |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string | The result (omitted when written to "output"). |
| `path` | string |  |
| `bytes` | number |  |
| `arabicLetters` | number |  |
| `words` | number |  |
| `characters` | number |  |
| `marksRemoved` | number | clean mode: how many tashkeel marks were removed. |

**Example**

```json
{
  "mode": "arabizi",
  "text": "mar7aba, kifak?"
}
```

Result:

```json
{
  "text": "مرحبا، كيفك؟",
  "arabicLetters": 9,
  "words": 2,
  "characters": 12
}
```

**Example**

```json
{
  "text": "2026",
  "digits": "arabic"
}
```

Result:

```json
{
  "text": "٢٠٢٦",
  "arabicLetters": 0,
  "words": 1,
  "characters": 4,
  "marksRemoved": 0
}
```

<a id="text_case"></a>
### `text_case` — Case convert

Convert text case: upper, lower, title (smart: keeps small words lower and iPhone/NASA as typed), sentence, camel, pascal, snake, kebab, constant, alternate, inverse. Works line by line and handles Unicode (Arabic, accents, ß). Web version: https://s3eidat.github.io/case-convert/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | yes | Text to convert. |
| `mode` | "upper" \| "lower" \| "title" \| "sentence" \| "camel" \| "pascal" \| "snake" \| "kebab" \| "constant" \| "alternate" \| "inverse" | yes | Target case. |

**Output**

| Field | Type | Description |
|---|---|---|
| `result` | string |  |

**Example**

```json
{
  "text": "a tale OF two cities",
  "mode": "title"
}
```

Result:

```json
{
  "result": "A Tale of Two Cities"
}
```

**Example**

```json
{
  "text": "XMLParser handler",
  "mode": "snake"
}
```

Result:

```json
{
  "result": "xml_parser_handler"
}
```

<a id="text_citation"></a>
### `text_citation` — Citation generator

Format references from structured fields in APA 7, MLA 9, Chicago author-date (17th), Harvard (Cite Them Right) or IEEE, for books, book chapters, journal articles, web pages, newspaper/magazine articles, videos and reports. Handles author-count rules (et al.), initials, organisations as authors, editions, page ranges (MLA 127-53), DOIs, URLs and access dates, and title case for MLA/Chicago/IEEE. Returns each citation as plain text and HTML (italics as <i>), the in-text citation, missing-field hints, and the whole list as a bibliography sorted for the style (numbered for IEEE). Web version: https://s3eidat.github.io/citation-gen/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `style` | "apa" \| "mla" \| "chicago" \| "harvard" \| "ieee" | yes | Citation style: apa, mla, chicago, harvard or ieee. |
| `sources` | object[] | yes | One or more sources. |
| `autoCase` | boolean | no (default `true`) | Apply title case where the style wants it (MLA, Chicago, IEEE book titles). Default true. |

**Output**

| Field | Type | Description |
|---|---|---|
| `style` | string |  |
| `citations` | object[] | In the order given. |
| `bibliographyTitle` | string | References / Works Cited / Reference list. |
| `bibliography` | string | All entries sorted for the style (IEEE: numbered in the order given), separated by blank lines. |

**Example** — Abelson, H., & Sussman, G. J. (1996). Structure and interpretation of computer programs (2nd ed.). MIT Press.

```json
{
  "style": "apa",
  "sources": [
    {
      "type": "book",
      "authors": [
        {
          "first": "Harold",
          "last": "Abelson"
        },
        {
          "first": "Gerald Jay",
          "last": "Sussman"
        }
      ],
      "title": "Structure and interpretation of computer programs",
      "edition": "2",
      "year": "1996",
      "publisher": "MIT Press",
      "place": "Cambridge, MA, USA"
    }
  ]
}
```

Result:

```json
{
  "style": "APA 7",
  "citations": [
    {
      "text": "Abelson, H., & Sussman, G. J. (1996). Structure and interpretation of computer programs (2nd ed.). MIT Press.",
      "html": "Abelson, H., &amp; Sussman, G. J. (1996). <i>Structure and interpretation of computer programs</i> (2nd ed.). MIT Press.",
      "inText": "(Abelson & Sussman, 1996)",
      "missing": []
    }
  ],
  "bibliographyTitle": "References",
  "bibliography": "Abelson, H., & Sussman, G. J. (1996). Structure and interpretation of computer programs (2nd ed.). MIT Press."
}
```

<a id="text_clean"></a>
### `text_clean` — Text cleaner

Clean up text line by line with a fixed pipeline (same order as the website): strip HTML tags and decode entities → remove emojis → trim each line → collapse repeated spaces → remove empty lines → remove duplicate lines (optionally ignoring case) → sort (A–Z, Z–A, natural, by length) → reverse → shuffle (seedable) → add line numbers. Returns the cleaned text plus counts, or writes it to a file when "output" is given. Give "text" or a text file "path". Web version: https://s3eidat.github.io/text-cleaner/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The text to clean. |
| `path` | string | no | Or: a path to a UTF-8 text file. |
| `stripHtml` | boolean | no (default `false`) | Strip HTML tags (drops script/style/comments, block ends become line breaks) and decode entities (default false). |
| `removeEmoji` | boolean | no (default `false`) | Remove emojis (©, ® and ™ are kept) (default false). |
| `trim` | boolean | no (default `true`) | Trim spaces at the start and end of each line (default true). |
| `collapseSpaces` | boolean | no (default `true`) | Collapse runs of spaces/tabs into one space (default true). |
| `removeEmptyLines` | boolean | no (default `true`) | Remove empty (whitespace-only) lines (default true). |
| `removeDuplicates` | boolean | no (default `false`) | Remove duplicate lines, keeping the first (default false). |
| `ignoreCase` | boolean | no (default `false`) | When removing duplicates, treat lines that differ only in case as duplicates (default false). |
| `sort` | "none" \| "az" \| "za" \| "natural" \| "length" | no (default `"none"`) | Sort lines: none (default), az, za, natural (item 2 before item 10), length (shortest first). |
| `reverse` | boolean | no (default `false`) | Reverse the line order (default false). |
| `shuffle` | boolean | no (default `false`) | Shuffle the lines (default false). |
| `seed` | integer | no | Seed for a reproducible shuffle (default: random). |
| `numberLines` | boolean | no (default `false`) | Prefix each line with its number ("1. ", padded) (default false). |
| `output` | string | no | Optional: write the cleaned text to this file path (or folder ending in "/") instead of returning it. |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string | The cleaned text (omitted when written to "output"). |
| `path` | string |  |
| `bytes` | number |  |
| `linesIn` | number |  |
| `linesOut` | number |  |
| `duplicatesRemoved` | number |  |
| `emptyLinesRemoved` | number |  |
| `charactersOut` | number |  |
| `seed` | number | Seed used for the shuffle. |

**Example**

```json
{
  "text": "item 10\nitem 2\nitem 2\n\nitem 1",
  "removeDuplicates": true,
  "sort": "natural"
}
```

Result:

```json
{
  "text": "item 1\nitem 2\nitem 10",
  "linesIn": 5,
  "linesOut": 3,
  "duplicatesRemoved": 1,
  "emptyLinesRemoved": 1,
  "charactersOut": 21
}
```

<a id="text_diff"></a>
### `text_diff` — Diff two texts

Compare two texts line by line (Myers diff) and return a unified diff (3 lines of context, like `diff -u`), counts of added/removed/unchanged lines, and change blocks where each removed line is paired with the added line that replaced it and the changed words are marked [-old-] / {+new+}. Can ignore whitespace and case. Works with any language (Arabic included). Up to 50,000 lines per side. Web version: https://s3eidat.github.io/diff-view/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `original` | string | no | The original text ("before"). |
| `originalPath` | string | no | Or: path to the original text file. |
| `changed` | string | no | The changed text ("after"). |
| `changedPath` | string | no | Or: path to the changed text file. |
| `ignoreWhitespace` | boolean | no (default `false`) | Treat lines that differ only in spacing as equal. |
| `ignoreCase` | boolean | no (default `false`) | Treat lines that differ only in letter case as equal. |
| `wordDiff` | boolean | no (default `true`) | Mark the changed words inside paired lines (default true). |
| `nameA` | string | no | Label for the "---" header (default "original"). |
| `nameB` | string | no | Label for the "+++" header (default "changed"). |

**Output**

| Field | Type | Description |
|---|---|---|
| `identical` | boolean | True when there is no difference (with the ignore options applied). |
| `added` | number |  |
| `removed` | number |  |
| `unchanged` | number |  |
| `unified` | string | Unified diff text (empty when identical). |
| `changes` | object[] | Change blocks; line numbers are 1-based. |

**Example**

```json
{
  "original": "one\ntwo\nthree",
  "changed": "zero\none\nthree"
}
```

Result:

```json
{
  "identical": false,
  "added": 1,
  "removed": 1,
  "unchanged": 2,
  "unified": "--- original\n+++ changed\n@@ -1,3 +1,3 @@\n+zero\n one\n-two\n three\n",
  "changes": [
    {
      "removed": [],
      "added": [
        {
          "line": 1,
          "text": "zero"
        }
      ]
    },
    {
      "removed": [
        {
          "line": 2,
          "text": "two"
        }
      ],
      "added": []
    }
  ]
}
```

**Example** — changes[0]: removed marked "the [-quick-] fox", added marked "the {+slow+} fox"

```json
{
  "original": "the quick fox",
  "changed": "the slow fox"
}
```

Result:

```json
{
  "identical": false,
  "added": 1,
  "removed": 1,
  "unchanged": 0,
  "unified": "--- original\n+++ changed\n@@ -1 +1 @@\n-the quick fox\n+the slow fox\n",
  "changes": [
    {
      "removed": [
        {
          "line": 1,
          "text": "the quick fox",
          "marked": "the [-quick-] fox"
        }
      ],
      "added": [
        {
          "line": 1,
          "text": "the slow fox",
          "marked": "the {+slow+} fox"
        }
      ]
    }
  ]
}
```

<a id="text_escape"></a>
### `text_escape` — Escape / unescape text

Escape or unescape a string for a target: JSON (string body), JavaScript (incl. template literals), HTML, XML, URL (encodeURIComponent, optional form +), SQL (doubled quotes, optional MySQL backslashes and wrapping quotes), CSV field (RFC 4180; comma, semicolon or tab), regex, POSIX shell single quotes, C/C++, Java/C#, and Unicode escapes (\uXXXX, \u{X}, \UXXXXXXXX, &#xX;, U+XXXX). Options per target as on the website. Set "allTargets" to get the escaped form for every target at once. Web version: https://s3eidat.github.io/text-escape/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | yes | The text to escape, or the escaped text to unescape. |
| `target` | "json" \| "js" \| "html" \| "xml" \| "url" \| "sql" \| "csv" \| "regex" \| "shell" \| "c" \| "java" \| "unicode" | yes | Format: json, js, html, xml, url, sql, csv, regex, shell, c, java, unicode. |
| `direction` | "escape" \| "unescape" | no (default `"escape"`) | escape (default) or unescape. |
| `ascii` | boolean | no (default `false`) | json/js/html/c/java: also escape non-ASCII characters (c: as UTF-8 octal). |
| `named` | boolean | no (default `false`) | html (with ascii): prefer named entities such as &copy;. |
| `form` | boolean | no (default `false`) | url: form style, + for spaces. |
| `wrap` | boolean | no (default `false`) | sql: wrap in single quotes (unescape: strip them). |
| `mysql` | boolean | no (default `false`) | sql: escape backslashes too (MySQL). |
| `alwaysQuote` | boolean | no (default `false`) | csv: always quote the field. |
| `delimiter` | "," \| ";" \| "\t" | no (default `","`) | csv: field delimiter (comma, semicolon or tab). |
| `unicodeFormat` | "u4" \| "ubrace" \| "u8" \| "html" \| "uplus" | no (default `"u4"`) | unicode: u4 = \uXXXX (JS, JSON, Java), ubrace = \u{X} (ES6, Rust, Swift), u8 = \UXXXXXXXX (Python, C), html = &#xX;, uplus = U+XXXX. |
| `every` | boolean | no (default `false`) | unicode: escape every character, not only non-ASCII. |
| `allTargets` | boolean | no (default `false`) | escape only: also return the text escaped for every target. |

**Output**

| Field | Type | Description |
|---|---|---|
| `result` | string |  |
| `note` | string | A short hint about the target. |
| `inputCharacters` | number |  |
| `outputCharacters` | number |  |
| `allTargets` | object[] |  |

**Example**

```json
{
  "text": "O'Brien",
  "target": "sql",
  "wrap": true
}
```

Result:

```json
{
  "result": "'O''Brien'",
  "note": "Standard SQL doubles single quotes. Prefer parameterised queries over escaping.",
  "inputCharacters": 7,
  "outputCharacters": 10
}
```

**Example**

```json
{
  "text": "a%20b%26c",
  "target": "url",
  "direction": "unescape"
}
```

Result:

```json
{
  "result": "a b&c",
  "note": "encodeURIComponent: for one query value or path segment.",
  "inputCharacters": 9,
  "outputCharacters": 5
}
```

<a id="text_fancy"></a>
### `text_fancy` — Fancy text (Unicode styles)

Restyle text with Unicode look-alike characters for bios and posts (28 styles): Bold, Italic, Bold italic, Script, Bold script, Fraktur, Bold fraktur, Double-struck, Sans-serif, Sans bold, Sans italic, Sans bold italic, Monospace, Circled, Circled dark, Squared, Squared dark, Parenthesized, Fullwidth, Small caps, Superscript, Upside down, Strikethrough, Underline, Double underline, Slashed, Overline, Wide spaced. Only Latin letters and digits change (other scripts such as Arabic and emoji pass through); some styles cover only capitals or only some symbols. Give one "style", or none to get every style. Up to 20,000 characters. Web version: https://s3eidat.github.io/fancy-text/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | yes | The text to restyle. |
| `style` | "Bold" \| "Italic" \| "Bold italic" \| "Script" \| "Bold script" \| "Fraktur" \| "Bold fraktur" \| "Double-struck" \| "Sans-serif" \| "Sans bold" \| "Sans italic" \| "Sans bold italic" \| "Monospace" \| "Circled" \| "Circled dark" \| "Squared" \| "Squared dark" \| "Parenthesized" \| "Fullwidth" \| "Small caps" \| "Superscript" \| "Upside down" \| "Strikethrough" \| "Underline" \| "Double underline" \| "Slashed" \| "Overline" \| "Wide spaced" | no | One style name (exact, e.g. "Bold", "Script", "Upside down"). Omit to get all styles. |

**Output**

| Field | Type | Description |
|---|---|---|
| `results` | object[] |  |

**Example**

```json
{
  "text": "Hello 2026",
  "style": "Bold"
}
```

Result:

```json
{
  "results": [
    {
      "style": "Bold",
      "text": "𝐇𝐞𝐥𝐥𝐨 𝟐𝟎𝟐𝟔"
    }
  ]
}
```

**Example** — Returns all 28 styles.

```json
{
  "text": "Stay curious"
}
```

Result:

```json
{
  "results": [
    {
      "style": "Bold",
      "text": "𝐒𝐭𝐚𝐲 𝐜𝐮𝐫𝐢𝐨𝐮𝐬"
    },
    {
      "style": "Italic",
      "text": "𝑆𝑡𝑎𝑦 𝑐𝑢𝑟𝑖𝑜𝑢𝑠"
    },
    {
      "style": "Bold italic",
      "text": "𝑺𝒕𝒂𝒚 𝒄𝒖𝒓𝒊𝒐𝒖𝒔"
    },
    {
      "style": "Script",
      "text": "𝒮𝓉𝒶𝓎 𝒸𝓊𝓇𝒾ℴ𝓊𝓈"
    },
    {
      "style": "Bold script",
      "text": "𝓢𝓽𝓪𝔂 𝓬𝓾𝓻𝓲𝓸𝓾𝓼"
    },
    {
      "style": "Fraktur",
      "text": "𝔖𝔱𝔞𝔶 𝔠𝔲𝔯𝔦𝔬𝔲𝔰"
    },
    {
      "style": "Bold fraktur",
      "text": "𝕾𝖙𝖆𝖞 𝖈𝖚𝖗𝖎𝖔𝖚𝖘"
    },
    {
      "style": "Double-struck",
      "text": "𝕊𝕥𝕒𝕪 𝕔𝕦𝕣𝕚𝕠𝕦𝕤"
    },
    {
      "style": "Sans-serif",
      "text": "𝖲𝗍𝖺𝗒 𝖼𝗎𝗋𝗂𝗈𝗎𝗌"
    },
    {
      "style": "Sans bold",
      "text": "𝗦𝘁𝗮𝘆 𝗰𝘂𝗿𝗶𝗼𝘂𝘀"
    },
    {
      "style": "Sans italic",
      "text": "𝘚𝘵𝘢𝘺 𝘤𝘶𝘳𝘪𝘰𝘶𝘴"
    },
    {
      "style": "Sans bold italic",
      "text": "𝙎𝙩𝙖𝙮 𝙘𝙪𝙧𝙞𝙤𝙪𝙨"
    },
    {
      "style": "Monospace",
      "text": "𝚂𝚝𝚊𝚢 𝚌𝚞𝚛𝚒𝚘𝚞𝚜"
    },
    {
      "style": "Circled",
      "text": "Ⓢⓣⓐⓨ ⓒⓤⓡⓘⓞⓤⓢ"
    },
    {
      "style": "Circled dark",
      "text": "🅢🅣🅐🅨 🅒🅤🅡🅘🅞🅤🅢"
    },
    {
      "style": "Squared",
      "text": "🅂🅃🄰🅈 🄲🅄🅁🄸🄾🅄🅂"
    },
    {
      "style": "Squared dark",
      "text": "🆂🆃🅰🆈 🅲🆄🆁🅸🅾🆄🆂"
    },
    {
      "style": "Parenthesized",
      "text": "🄢⒯⒜⒴ ⒞⒰⒭⒤⒪⒰⒮"
    },
    {
      "style": "Fullwidth",
      "text": "Ｓｔａｙ　ｃｕｒｉｏｕｓ"
    },
    {
      "style": "Small caps",
      "text": "ꜱᴛᴀʏ ᴄᴜʀɪᴏᴜꜱ"
    },
    {
      "style": "Superscript",
      "text": "Sᵗᵃʸ ᶜᵘʳⁱᵒᵘˢ"
    },
    {
      "style": "Upside down",
      "text": "snoᴉɹnɔ ʎɐʇS"
    },
    {
      "style": "Strikethrough",
      "text": "S̶t̶a̶y̶ ̶c̶u̶r̶i̶o̶u̶s̶"
    },
    {
      "style": "Underline",
      "text": "S̲t̲a̲y̲ ̲c̲u̲r̲i̲o̲u̲s̲"
    },
    {
      "style": "Double underline",
      "text": "S̳t̳a̳y̳ ̳c̳u̳r̳i̳o̳u̳s̳"
    },
    {
      "style": "Slashed",
      "text": "S̸t̸a̸y̸ ̸c̸u̸r̸i̸o̸u̸s̸"
    },
    {
      "style": "Overline",
      "text": "S̅t̅a̅y̅ ̅c̅u̅r̅i̅o̅u̅s̅"
    },
    {
      "style": "Wide spaced",
      "text": "S t a y   c u r i o u s"
    }
  ]
}
```

<a id="text_layout_fix"></a>
### `text_layout_fix` — Keyboard layout fixer (EN ⇄ AR)

Fix text typed with the wrong keyboard layout between US English (QWERTY) and Arabic (101): "hgsghl ugd;l" becomes the Arabic greeting, and Arabic gibberish typed on English keys becomes "hello". Direction "auto" detects it; in mixed text only the words that look mistyped are converted (using small English/Arabic letter-pair models), so words that are already right stay as they are. Handles the shift layer (harakat, hamza forms), lam-alef keys and Caps Lock (all-caps Latin input is treated as lowercase). Web version: https://s3eidat.github.io/layout-fixer/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The mistyped text. |
| `path` | string | no | Or: a path to a UTF-8 text file. |
| `direction` | "auto" \| "ar" \| "en" | no (default `"auto"`) | "auto" (default) detects; "ar" = typed on English keys, convert to Arabic; "en" = typed on Arabic keys, convert to English. |
| `capsLock` | boolean | no (default `false`) | Caps Lock was on: treat capital Latin letters as lowercase (detected automatically when all letters are capitals). |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string | The fixed text. |
| `direction` | "ar" \| "en" \| "mixed" \| null | ar = converted to Arabic, en = converted to English, mixed = only mistyped words fixed, null = no letters. |
| `wordsChanged` | number |  |
| `capsLockIgnored` | boolean | True when all-caps input was treated as Caps Lock. |

**Example**

```json
{
  "text": ";dt phg;?"
}
```

Result:

```json
{
  "text": "كيف حالك؟",
  "direction": "ar",
  "wordsChanged": 2,
  "capsLockIgnored": false
}
```

**Example** — Mixed text: only the mistyped Arabic-looking word becomes "hello".

```json
{
  "text": "Hi, ok اثممخ"
}
```

Result:

```json
{
  "text": "Hi, ok hello",
  "direction": "mixed",
  "wordsChanged": 1,
  "capsLockIgnored": false
}
```

<a id="text_lorem"></a>
### `text_lorem` — Lorem ipsum generator

Generate placeholder text: classic Lorem ipsum, readable English filler, or Arabic filler. Choose paragraphs (max 100), sentences (max 500) or words (max 10000); optionally start with the classic opener and wrap paragraphs in <p> tags. Give a seed for repeatable output (the same seed gives the same text). Writes a .txt/.html file instead when "output" is given. Web version: https://s3eidat.github.io/lorem-gen/

Hints: readOnly

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `style` | "lorem" \| "english" \| "arabic" | no (default `"lorem"`) | Word list: lorem (Latin), english, or arabic. |
| `unit` | "paragraphs" \| "sentences" \| "words" | no (default `"paragraphs"`) | What "count" counts. |
| `count` | integer | no (default `3`) | How many paragraphs / sentences / words (clamped to the unit limit). |
| `opener` | boolean | no (default `true`) | Start with the classic opening line ("Lorem ipsum dolor sit amet…", "The quick brown fox…", or the Arabic one). Default true. |
| `html` | boolean | no (default `false`) | Wrap each paragraph in <p>…</p>. |
| `seed` | integer | no | Seed for repeatable output (default: random). |
| `output` | string | no | Optional: write the text to this file path (or folder ending in "/") instead of returning it. |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string | The generated text (omitted when written to "output"). |
| `path` | string |  |
| `bytes` | number |  |
| `words` | number |  |
| `characters` | number |  |
| `paragraphs` | number |  |
| `seed` | number | Seed used: pass it again to get the same text. |

**Example** — 25 words starting "Lorem ipsum dolor sit amet…"

```json
{
  "unit": "words",
  "count": 25,
  "seed": 42
}
```

**Example**

```json
{
  "style": "arabic",
  "unit": "paragraphs",
  "count": 2
}
```

<a id="text_morse"></a>
### `text_morse` — Morse code translator

Translate text to International Morse code and back. Encoding: letters separated by spaces, words by " / "; accents are folded (é → E) and characters without a code are skipped and counted. Decoding accepts . and - plus look-alikes (• · – — _), with words split by /, | or 2+ spaces; unknown codes become #. Encoding also returns the playing time at a given speed (PARIS timing: dot = 1200/WPM ms). No audio. Web version: https://s3eidat.github.io/morse-code/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | yes | Text to encode, or Morse code to decode. |
| `mode` | "encode" \| "decode" \| "auto" | no (default `"auto"`) | "encode" text → Morse, "decode" Morse → text, or "auto" (default): decode if the input only has Morse symbols, spaces, / and \|. |
| `wpm` | number | no (default `18`) | Speed in words per minute for the duration estimate (default 18). |

**Output**

| Field | Type | Description |
|---|---|---|
| `mode` | "encode" \| "decode" |  |
| `result` | string |  |
| `skipped` | number | encode: characters with no Morse code (skipped). |
| `unrecognized` | number | decode: codes that were not recognised (shown as #). |
| `durationSeconds` | number | encode: time to play the Morse at the given WPM. |

**Example**

```json
{
  "text": "SOS",
  "mode": "encode"
}
```

Result:

```json
{
  "mode": "encode",
  "result": "... --- ...",
  "skipped": 0,
  "durationSeconds": 1.8
}
```

**Example**

```json
{
  "text": ".... ..  /  - .... . .-. ."
}
```

Result:

```json
{
  "mode": "decode",
  "result": "HI THERE",
  "unrecognized": 0
}
```

<a id="text_number_to_words"></a>
### `text_number_to_words` — Number to words (English & Arabic)

Spell a number in words in English and Arabic: English cardinal (with decimals, optional British "and"), ordinal (twenty-first), cheque wording for an amount in a currency (cents in words or as 25/100, optional "only"), Arabic cardinal, and Arabic tafqeet for amounts with correct gender/number agreement of the currency noun (optional "faqat … la ghayr" wrapper and the modern 100 spelling). Currencies: JOD, USD, SAR, AED, KWD, EGP, GBP (or none). Accepts Arabic-Indic digits and thousands separators. Up to 999 trillion (15 digits). Web version: https://s3eidat.github.io/number-words/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `number` | string \| number | yes | The number or amount, e.g. "1500.250", "1,234,567.89", -12.05 (Arabic-Indic digits work too). |
| `currency` | "none" \| "JOD" \| "USD" \| "SAR" \| "AED" \| "KWD" \| "EGP" \| "GBP" | no (default `"none"`) | Currency for cheque / tafqeet wording: none (default), JOD, USD, SAR, AED, KWD, EGP, GBP. |
| `only` | boolean | no (default `false`) | Add "only" (English) and the Arabic "faqat … la ghayr" wrapper to amounts. |
| `british` | boolean | no (default `false`) | British English "and": one hundred and five. |
| `subStyle` | "words" \| "fraction" | no (default `"words"`) | Cheque sub-units in words (twenty-five cents) or as a fraction (25/100). |
| `modernHundred` | boolean | no (default `false`) | Arabic: use the modern spelling of "hundred" (without the extra alif). |

**Output**

| Field | Type | Description |
|---|---|---|
| `digits` | string | The number as parsed, with thousands separators. |
| `english` | string | English cardinal words. |
| `ordinal` | string | English ordinal (whole non-negative numbers only). |
| `ordinalShort` | string | Ordinal in digits, e.g. 21st. |
| `arabic` | string | Arabic cardinal words. |
| `englishAmount` | string | Cheque wording in the currency (when a currency is set). |
| `arabicAmount` | string | Arabic tafqeet of the amount (when a currency is set). |

**Example**

```json
{
  "number": "21"
}
```

Result:

```json
{
  "digits": "21",
  "english": "twenty-one",
  "ordinal": "twenty-first",
  "ordinalShort": "21st",
  "arabic": "واحد وعشرون"
}
```

**Example** — englishAmount: "One thousand five hundred dollars and twenty-five cents", plus the Arabic tafqeet

```json
{
  "number": "1500.25",
  "currency": "USD"
}
```

Result:

```json
{
  "digits": "1,500.25 USD",
  "english": "one thousand five hundred point two five",
  "arabic": "ألف وخمسمائة فاصلة خمسة وعشرون",
  "englishAmount": "One thousand five hundred dollars and twenty-five cents",
  "arabicAmount": "ألف وخمسمائة دولار وخمسة وعشرون سنتاً"
}
```

<a id="text_readability"></a>
### `text_readability` — Readability score

Score how easy an English text is to read: Flesch Reading Ease, Flesch-Kincaid Grade, Gunning Fog, SMOG, Coleman-Liau and Automated Readability Index (same formulas, sentence splitter and syllable estimate as the website), plus a verdict, average grade level, and Hemingway-style hints: long sentences (over 20 / 30 words), passive voice and adverbs. The formulas are built for English; for Arabic text the result says so (arabicWarning). Give "text" or a text file "path". Web version: https://s3eidat.github.io/readability/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The text to score. |
| `path` | string | no | Or: a path to a UTF-8 text file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `readingEase` | number \| null | Flesch Reading Ease clamped to 0–100 and rounded (the big number on the website). |
| `verdict` | string \| null |  |
| `verdictLevel` | "ok" \| "warn" \| "bad" \| null |  |
| `advice` | string \| null |  |
| `averageGrade` | number \| null | Mean of the five grade-level formulas. |
| `averageGradeText` | string |  |
| `scores` | object \| null |  |
| `words` | number |  |
| `sentences` | number |  |
| `syllablesPerWord` | number \| null |  |
| `wordsPerSentence` | number \| null |  |
| `longSentences` | object[] | Sentences over 20 (hard) or 30 (vhard) words. |
| `passive` | string[] | Passive-voice phrases found. |
| `adverbs` | string[] | Adverbs found (-ly words, with common non-adverbs excluded). |
| `tips` | string[] |  |
| `smogNote` | string |  |
| `arabicWarning` | string |  |

**Example** — Returns all six scores, the verdict and hints.

```json
{
  "text": "Keep it short. Use plain words. Readers scan, so put the main point first and cut what they don't need."
}
```

Result:

```json
{
  "readingEase": 100,
  "verdict": "Very easy",
  "verdictLevel": "ok",
  "advice": "Short sentences and everyday words. Almost anyone can follow it.",
  "averageGrade": 1.7,
  "averageGradeText": "grade 2 · age 7–8",
  "scores": {
    "fleschReadingEase": 111.2,
    "fleschKincaidGrade": {
      "score": -0.6,
      "grade": "kindergarten"
    },
    "gunningFog": {
      "score": 2.7,
      "grade": "grade 3 · age 8–9"
    },
    "smog": {
      "score": 3.1,
      "grade": "grade 3 · age 8–9"
    },
    "colemanLiau": {
      "score": 3,
      "grade": "grade 3 · age 8–9"
    },
    "automatedReadability": {
      "score": 0.5,
      "grade": "grade 1 · age 6–7"
    }
  },
  "words": 20,
  "sentences": 3,
  "syllablesPerWord": 1.05,
  "wordsPerSentence": 6.7,
  "longSentences": [],
  "passive": [],
  "adverbs": [],
  "tips": [
    "No long sentences, passive voice or extra adverbs found."
  ],
  "smogNote": "SMOG is designed for 30+ sentences; with 3 it is a rough estimate."
}
```

<a id="text_slugify"></a>
### `text_slugify` — Slugify

Turn titles into clean URL slugs, one slug per non-empty line (bulk). Folds accents (é → e, ß → ss), transliterates Arabic to Latin (the article al- becomes its own word) or keeps Arabic letters, converts Arabic-Indic digits, drops tashkeel, turns & into "and", can remove stop words (English and Arabic) and cap the length at a word boundary. Web version: https://s3eidat.github.io/slugify/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | yes | Title(s) to slugify. Each non-empty line becomes one slug. |
| `separator` | "-" \| "_" \| "." | no (default `"-"`) | Word separator: dash (default), underscore or dot. |
| `lowercase` | boolean | no (default `true`) | Lowercase the slug (default true). |
| `foldAccents` | boolean | no (default `true`) | Transliterate accents: é → e, ß → ss, æ → ae (default true). |
| `removeStopWords` | boolean | no (default `false`) | Remove stop words in English and Arabic such as the, of, and (kept if nothing else is left). |
| `arabic` | "latin" \| "keep" | no (default `"latin"`) | "latin" transliterates Arabic letters (default); "keep" keeps them. |
| `maxLength` | integer | no (default `0`) | Maximum slug length in characters, cut at a word boundary (0 = no limit). |

**Output**

| Field | Type | Description |
|---|---|---|
| `slugs` | object[] |  |

**Example**

```json
{
  "text": "Crème Brûlée & Café au Lait: A Love Story"
}
```

Result:

```json
{
  "slugs": [
    {
      "title": "Crème Brûlée & Café au Lait: A Love Story",
      "slug": "creme-brulee-and-cafe-au-lait-a-love-story",
      "length": 42,
      "encodedLength": 42
    }
  ]
}
```

**Example** — lord-rings

```json
{
  "text": "The Lord of the Rings",
  "removeStopWords": true
}
```

Result:

```json
{
  "slugs": [
    {
      "title": "The Lord of the Rings",
      "slug": "lord-rings",
      "length": 10,
      "encodedLength": 10
    }
  ]
}
```

<a id="text_social_count"></a>
### `text_social_count` — Social media character counter

Check a post against platform limits the way the platforms count: X/Twitter weighted length (links = 23, emoji = 2, CJK = 2; limit 280), Threads 500, Bluesky 300, Instagram 2,200 (+ 30 hashtags), LinkedIn 3,000, Facebook 63,206, TikTok 4,000, YouTube title 100 / description 5,000, Pinterest 500, Google title ~60, meta description ~160. Characters are counted as user-perceived graphemes (one emoji = 1). Also: words, hashtags, mentions, links, emoji, and SMS encoding (GSM-7 vs Unicode) with the number of SMS parts and the characters that force Unicode. Give "text" or a text file "path". Web version: https://s3eidat.github.io/social-counter/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The post text. |
| `path` | string | no | Or: a path to a UTF-8 text file. |
| `platforms` | ("x" \| "threads" \| "bsky" \| "ig" \| "li" \| "fb" \| "tt" \| "yt" \| "ytd" \| "pin" \| "gt" \| "gd")[] | no | Only these meters: x, threads, bsky, ig, li, fb, tt, yt, ytd, pin, gt (Google title), gd (meta description). Default: all. |
| `gsmLookalikes` | boolean | no (default `false`) | Also return the text with curly quotes, dashes, ellipses, odd spaces etc. replaced by GSM-7 look-alikes (cheaper SMS). |

**Output**

| Field | Type | Description |
|---|---|---|
| `characters` | number |  |
| `words` | number |  |
| `hashtags` | number |  |
| `mentions` | number |  |
| `links` | number |  |
| `emoji` | number |  |
| `lines` | number |  |
| `utf16Units` | number |  |
| `xWeightedLength` | number |  |
| `platforms` | object[] |  |
| `sms` | object |  |
| `gsmText` | string |  |

**Example** — xWeightedLength 48: the link counts 23 and the emoji 2.

```json
{
  "text": "Read it here: https://example.com/blog/code-reviews 🚀 #coding",
  "platforms": [
    "x"
  ]
}
```

Result:

```json
{
  "characters": 61,
  "words": 9,
  "hashtags": 1,
  "mentions": 0,
  "links": 1,
  "emoji": 1,
  "lines": 1,
  "utf16Units": 62,
  "xWeightedLength": 48,
  "platforms": [
    {
      "id": "x",
      "name": "X (Twitter) post",
      "count": 48,
      "limit": 280,
      "softLimit": false,
      "remaining": 232,
      "over": 0,
      "status": "ok",
      "notes": [
        "Weighted: links 23, emoji 2, CJK 2. Premium allows 25,000."
      ]
    }
  ],
  "sms": {
    "encoding": "UCS-2",
    "parts": 1,
    "unitsUsed": 62,
    "perPart": 70,
    "leftInPart": 8,
    "nonGsmCharacters": [
      {
        "char": "🚀",
        "count": 1
      }
    ]
  }
}
```

<a id="text_word_count"></a>
### `text_word_count` — Word count

Count words, characters (with and without spaces), sentences, paragraphs, lines and unique words; estimate reading (238 wpm) and speaking (150 wpm) time; list the most used keywords with density. Works for English and Arabic (tashkeel ignored). Give "text" or a text file "path". Web version: https://s3eidat.github.io/word-count/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The text to analyse. |
| `path` | string | no | Or: a path to a UTF-8 text file. |
| `keywords` | integer | no (default `12`) | How many top keywords to return (0 = none). |
| `includeStopWords` | boolean | no (default `false`) | Count common words like "the"/"في" as keywords. |

**Output**

| Field | Type | Description |
|---|---|---|
| `words` | number |  |
| `characters` | number |  |
| `charactersNoSpaces` | number |  |
| `sentences` | number |  |
| `paragraphs` | number |  |
| `lines` | number |  |
| `uniqueWords` | number |  |
| `averageWordLength` | number |  |
| `longestWord` | string |  |
| `readingTimeSeconds` | number |  |
| `speakingTimeSeconds` | number |  |
| `keywords` | object[] |  |

**Example** — 11 words, 4 sentences, 2 paragraphs

```json
{
  "text": "Hello world. This is a test! Is it?\n\nNew paragraph here."
}
```

Result:

```json
{
  "words": 11,
  "characters": 56,
  "charactersNoSpaces": 45,
  "sentences": 4,
  "paragraphs": 2,
  "lines": 3,
  "uniqueWords": 10,
  "averageWordLength": 3.73,
  "longestWord": "paragraph",
  "readingTimeSeconds": 3,
  "speakingTimeSeconds": 4,
  "keywords": [
    {
      "word": "hello",
      "count": 1,
      "density": 9.09
    },
    {
      "word": "new",
      "count": 1,
      "density": 9.09
    },
    {
      "word": "paragraph",
      "count": 1,
      "density": 9.09
    },
    {
      "word": "test",
      "count": 1,
      "density": 9.09
    },
    {
      "word": "world",
      "count": 1,
      "density": 9.09
    }
  ]
}
```

<a id="data"></a>
## Developer: data formats

JSON, CSV, YAML, XML, SQL, HTML/CSS formatting and conversion, types from JSON, curl to code.

| Tool | What it does |
|---|---|
| [`css_format`](#css_format) | CSS beautify / minify |
| [`csv_to_json`](#csv_to_json) | CSV to JSON |
| [`curl_to_code`](#curl_to_code) | cURL to code |
| [`html_format`](#html_format) | HTML beautify / minify |
| [`json_format`](#json_format) | JSON format / minify / validate |
| [`json_query`](#json_query) | JSON query (JSONPath) |
| [`json_to_csv`](#json_to_csv) | JSON to CSV |
| [`json_to_types`](#json_to_types) | JSON to types (TypeScript, Go, Python, Zod) |
| [`json_to_yaml`](#json_to_yaml) | JSON to YAML |
| [`markdown_table`](#markdown_table) | Markdown table |
| [`markdown_table_parse`](#markdown_table_parse) | Markdown table to JSON / CSV |
| [`sql_format`](#sql_format) | SQL format / minify |
| [`xml_format`](#xml_format) | XML format / minify / validate / to JSON |
| [`xml_query`](#xml_query) | XML XPath query |
| [`yaml_to_json`](#yaml_to_json) | YAML to JSON |

<a id="css_format"></a>
### `css_format` — CSS beautify / minify

Beautify CSS (one declaration per line, one selector per line, blank line between top-level rules, nested @media blocks) or minify it. Strings, url(...) and escapes are kept intact; calc() spacing is preserved. Optional comment removal (/*! … */ kept). Give "text" or a file "path". Web version: https://s3eidat.github.io/html-css-format/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The CSS as inline text. |
| `path` | string | no | Or: a path to a CSS file (UTF-8; use this for big inputs). |
| `mode` | "beautify" \| "minify" | no (default `"beautify"`) | "beautify" (default) or "minify". |
| `indent` | "2" \| "4" \| "tab" \| integer | no (default `"2"`) | Indentation: 2, 4, "tab" (or any number of spaces 0-8). |
| `removeComments` | boolean | no (default `false`) | Drop comments (/*! … */ license comments are kept). |
| `output` | string | no | Optional: also write the result to this file path (or folder ending in "/"). The result text is then left out of the reply. Results over 1,000,000 characters are always written to a file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `inputBytes` | number |  |
| `outputBytes` | number |  |
| `lines` | number |  |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "a,b{color:red;margin:0 auto}"
}
```

Result:

```json
{
  "result": "a,\nb {\n  color: red;\n  margin: 0 auto;\n}"
}
```

<a id="csv_to_json"></a>
### `csv_to_json` — CSV to JSON

Convert CSV/TSV to JSON with an RFC 4180 parser (quoted fields, "" escapes, line breaks inside quotes, CR/LF/CRLF, BOM). Detects the delimiter (comma, semicolon, tab, pipe), uses the first row as headers (blank → column_N, duplicates → name_2), and can type values (numbers, true/false, null; long digit strings like IDs stay strings). Output: array of objects or array of arrays, pretty-printed. Give "text" or a file "path". Web version: https://s3eidat.github.io/csv-json/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The CSV as inline text. |
| `path` | string | no | Or: a path to a CSV/TSV file (UTF-8; use this for big inputs). |
| `delimiter` | "auto" \| "," \| ";" \| "tab" \| "\t" \| "\|" | no (default `"auto"`) | Delimiter: "auto" (detect), ",", ";", "tab" or "\|". |
| `header` | boolean | no (default `true`) | First row is a header (default true). |
| `types` | boolean | no (default `true`) | Turn numbers, true/false and empty/null into JSON types (default true). |
| `shape` | "objects" \| "arrays" | no (default `"objects"`) | "objects" (one object per row) or "arrays" (rows as arrays). |
| `output` | string | no | Optional: also write the result to this file path (or folder ending in "/"). The result text is then left out of the reply. Results over 1,000,000 characters are always written to a file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `rows` | number |  |
| `columns` | string[] |  |
| `delimiter` | string |  |
| `ragged` | boolean | Some rows have a different number of fields. |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "id,zip,ok\n1,007,true"
}
```

Result:

```json
{
  "rows": 1,
  "columns": [
    "id",
    "zip",
    "ok"
  ],
  "delimiter": "comma",
  "ragged": false,
  "result": "[\n  {\n    \"id\": 1,\n    \"zip\": \"007\",\n    \"ok\": true\n  }\n]"
}
```

<a id="curl_to_code"></a>
### `curl_to_code` — cURL to code

Convert a curl command (bash/zsh quoting incl. $'…', line continuations, "Copy as cURL" from browsers) into code for JavaScript fetch, axios, Python requests, PHP cURL, Go net/http or Node.js http/https: every target the website supports. Understands -X, -H, -d/--data-raw/--data-binary/--data-urlencode, --json, -F forms and files, -u, -b, -A, -e, -G, -I, -k, -L, -m, --connect-timeout, -x, -T, --compressed, --oauth2-bearer. Also returns the parsed request (secrets masked unless revealSecrets) and notes on what could not be converted. Only parses text; never sends the request. Web version: https://s3eidat.github.io/curl-convert/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `command` | string | yes | The curl command, e.g. "curl -X POST https://api.example.com -H 'Content-Type: application/json' -d '{"a":1}'". |
| `language` | "fetch" \| "axios" \| "python" \| "php" \| "go" \| "node" \| "all" | no (default `"fetch"`) | Target: fetch (browser/Node fetch), axios, python (requests), php (cURL), go (net/http), node (http/https), or "all". |
| `revealSecrets` | boolean | no (default `false`) | Show Authorization/Cookie/API-key header values and the basic-auth password in the parsed summary (the code always contains them). |
| `output` | string | no | Optional: write the code to this file (single language only). |

**Output**

| Field | Type | Description |
|---|---|---|
| `method` | string |  |
| `url` | string |  |
| `headers` | object[] |  |
| `body` | string \| null |  |
| `basicAuth` | object |  |
| `warnings` | string[] |  |
| `notConverted` | string[] | curl options with no equivalent in the generated code. |
| `code` | string |  |
| `codes` | object | With language "all": code per target. |
| `path` | string |  |
| `bytes` | number |  |

**Example** — requests.get(url, auth=("user", "pass"))

```json
{
  "command": "curl -u user:pass https://s3eidat.github.io",
  "language": "python"
}
```

Result:

```json
{
  "method": "GET",
  "url": "https://s3eidat.github.io",
  "headers": [],
  "body": null,
  "basicAuth": {
    "user": "user",
    "password": "••••••••"
  },
  "warnings": [],
  "notConverted": [],
  "code": "import requests\n\nurl = \"https://s3eidat.github.io\"\n\nresponse = requests.get(url, auth=(\"user\", \"pass\"))\nprint(response.status_code)\nprint(response.text)"
}
```

<a id="html_format"></a>
### `html_format` — HTML beautify / minify

Beautify (indent block elements, keep inline runs on one line) or minify HTML with a forgiving tokenizer: implied end tags (<p>, <li>, <td>…), void elements, and <script>, <style>, <pre>, <textarea> contents kept byte-for-byte. Whitespace between attributes is normalised; attribute values are never changed. Optional comment removal (IE conditional comments kept). Text only: nothing is rendered or run. Give "text" or a file "path". Web version: https://s3eidat.github.io/html-css-format/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The HTML as inline text. |
| `path` | string | no | Or: a path to a HTML file (UTF-8; use this for big inputs). |
| `mode` | "beautify" \| "minify" | no (default `"beautify"`) | "beautify" (default) or "minify". |
| `indent` | "2" \| "4" \| "tab" \| integer | no (default `"2"`) | Indentation: 2, 4, "tab" (or any number of spaces 0-8). |
| `removeComments` | boolean | no (default `false`) | Drop comments (IE conditional comments are kept). |
| `output` | string | no | Optional: also write the result to this file path (or folder ending in "/"). The result text is then left out of the reply. Results over 1,000,000 characters are always written to a file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `inputBytes` | number |  |
| `outputBytes` | number |  |
| `lines` | number |  |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "<div><p>Hi <b>there</b></p><ul><li>a<li>b</ul></div>"
}
```

Result:

```json
{
  "result": "<div>\n  <p>Hi <b>there</b></p>\n  <ul>\n    <li>a\n    <li>b\n  </ul>\n</div>"
}
```

<a id="json_format"></a>
### `json_format` — JSON format / minify / validate

Validate, pretty-print (2 or 4 spaces, tab) or minify JSON, optionally sorting object keys (deeply). Invalid JSON gives a clear error with line and column and the lines around it. Also returns size, key count, depth and value count. Give "text" or a file "path"; "output" writes the result to a file. Uses the standard JSON parser (no comments or trailing commas). Web version: https://s3eidat.github.io/json-studio/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The JSON as inline text. |
| `path` | string | no | Or: a path to a JSON file (UTF-8; use this for big inputs). |
| `mode` | "format" \| "minify" \| "validate" | no (default `"format"`) | "format" pretty-prints, "minify" removes all whitespace, "validate" only checks (never throws on bad JSON). |
| `indent` | "2" \| "4" \| "tab" \| integer | no (default `"2"`) | Indentation: 2, 4, "tab" (or any number of spaces 0-8). |
| `sortKeys` | boolean | no (default `false`) | Sort object keys alphabetically at every level. |
| `output` | string | no | Optional: also write the result to this file path (or folder ending in "/"). The result text is then left out of the reply. Results over 1,000,000 characters are always written to a file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `valid` | boolean |  |
| `error` | string |  |
| `line` | number |  |
| `column` | number |  |
| `stats` | object |  |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "{\"b\":1,\"a\":[1,2]}",
  "sortKeys": true
}
```

Result:

```json
{
  "valid": true,
  "result": "{\n  \"a\": [\n    1,\n    2\n  ],\n  \"b\": 1\n}"
}
```

**Example** — valid: false, line 2, column 1

```json
{
  "text": "{\"a\":1,\n}",
  "mode": "validate"
}
```

Result:

```json
{
  "valid": false,
  "error": "Expected double-quoted property name in JSON at position 8",
  "line": 2,
  "column": 1
}
```

<a id="json_query"></a>
### `json_query` — JSON query (JSONPath)

Pick values out of JSON with a JSONPath expression. Supported: $ (root, optional), .key, ['key'], [0], [-1], [*], .*, ..key (recursive), [start:end:step] slices, unions [0,2] / ['a','b'], and filters like [?(@.price < 10 && @.tags)] (==, !=, <, <=, >, >=, =~ /regex/i, &&, ||, !, .length). Returns each match with its path (the same path style the website's tree view copies, e.g. $.store.book[0].title). Give "text" or a file "path". Web version: https://s3eidat.github.io/json-studio/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The JSON as inline text. |
| `path` | string | no | Or: a path to a JSON file (UTF-8; use this for big inputs). |
| `query` | string | yes | JSONPath expression, e.g. "$.store.book[*].author" or "$..book[?(@.price < 10)].title". |
| `limit` | integer | no (default `1000`) | Return at most this many matches (default 1000). |
| `output` | string | no | Optional: write the matched values as a JSON array to this file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `count` | number | Total number of matches. |
| `matches` | object[] |  |
| `truncated` | boolean |  |
| `path` | string |  |
| `bytes` | number |  |

**Example**

```json
{
  "text": "{\"store\":{\"book\":[{\"title\":\"A\",\"price\":8},{\"title\":\"B\",\"price\":12}]}}",
  "query": "$..book[?(@.price < 10)].title"
}
```

Result:

```json
{
  "count": 1,
  "matches": [
    {
      "path": "$.store.book[0].title",
      "value": "A"
    }
  ]
}
```

<a id="json_to_csv"></a>
### `json_to_csv` — JSON to CSV

Convert JSON to CSV. An array of objects becomes one row per object with nested objects/arrays flattened to dotted columns (address.city, tags.0); an array of arrays is written as-is. Fields are quoted only when needed (delimiter, quotes, line breaks, leading/trailing spaces); CRLF line ends. A formula guard (on by default) prefixes cells starting with = + - @ so spreadsheets don't run them. Give "text" or a file "path". Web version: https://s3eidat.github.io/csv-json/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The JSON (array or object) as inline text. |
| `path` | string | no | Or: a path to a JSON file (UTF-8; use this for big inputs). |
| `delimiter` | "auto" \| "," \| ";" \| "tab" \| "\t" \| "\|" | no (default `"auto"`) | Delimiter: "auto" (comma), ",", ";", "tab" or "\|". |
| `header` | boolean | no (default `true`) | Include a header row (default true). |
| `safe` | boolean | no (default `true`) | Formula guard: prefix cells like =SUM(A1) with ' (default true). |
| `bom` | boolean | no (default `false`) | Start with a UTF-8 BOM so Excel reads non-English text correctly. |
| `output` | string | no | Optional: also write the result to this file path (or folder ending in "/"). The result text is then left out of the reply. Results over 1,000,000 characters are always written to a file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `rows` | number |  |
| `columns` | string[] \| null |  |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "[{\"a\":{\"b\":1},\"s\":\"say \\\"hi\\\"\"}]"
}
```

Result:

```json
{
  "rows": 1,
  "columns": [
    "a.b",
    "s"
  ],
  "result": "a.b,s\r\n1,\"say \"\"hi\"\"\"\r\n"
}
```

<a id="json_to_types"></a>
### `json_to_types` — JSON to types (TypeScript, Go, Python, Zod)

Generate type definitions from a JSON sample: TypeScript interfaces (or type aliases), Go structs with json tags (initialisms like ID/URL, pointers for optional/nullable, optional omitempty), Python dataclasses (snake_case fields with the original key noted) and Zod schemas. All items of an array are merged into one type (missing keys become optional, null makes a field nullable, int+float → number/float64, mixed values → unions); identical shapes share one name. These four are every language the website supports. Give "text" or a file "path". Web version: https://s3eidat.github.io/json-to-types/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The JSON sample as inline text. |
| `path` | string | no | Or: a path to a JSON file (UTF-8; use this for big inputs). |
| `language` | "typescript" \| "go" \| "python" \| "zod" \| "all" | no (default `"typescript"`) | Output language, or "all" for all four. |
| `rootName` | string | no (default `"Root"`) | Name of the top-level type (turned into PascalCase). |
| `export` | boolean | no (default `true`) | TypeScript / Zod: add the export keyword. |
| `tsTypeAlias` | boolean | no (default `false`) | TypeScript: use "type X = {…}" instead of "interface X {…}". |
| `goOmitEmpty` | boolean | no (default `true`) | Go: add ,omitempty to optional fields. |
| `output` | string | no | Optional: write the code to this file (with language "all", the four outputs joined by headers). |

**Output**

| Field | Type | Description |
|---|---|---|
| `types` | number | Number of named object types. |
| `typescript` | string |  |
| `go` | string |  |
| `python` | string |  |
| `zod` | string |  |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "[{\"a\":1},{\"b\":true}]",
  "rootName": "Root",
  "export": false
}
```

Result:

```json
{
  "types": 1,
  "result": "type Root = RootItem[];\n\ninterface RootItem {\n  a?: number;\n  b?: boolean;\n}\n"
}
```

<a id="json_to_yaml"></a>
### `json_to_yaml` — JSON to YAML

Convert JSON to YAML (js-yaml dump: no line wrapping, no anchors; strings that would read as numbers/booleans are quoted). Options: 2 or 4 space indent, sort keys, and write a top-level array as a multi-document YAML stream (--- per item). Invalid JSON gives line and column. Give "text" or a file "path". Web version: https://s3eidat.github.io/yaml-json/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The JSON as inline text. |
| `path` | string | no | Or: a path to a JSON file (UTF-8; use this for big inputs). |
| `indent` | 2 \| 4 \| "2" \| "4" | no (default `2`) | Indentation: 2 or 4 spaces. |
| `sortKeys` | boolean | no (default `false`) | Sort mapping keys alphabetically. |
| `multiDocument` | boolean | no (default `false`) | A top-level JSON array becomes one YAML document per item. |
| `output` | string | no | Optional: also write the result to this file path (or folder ending in "/"). The result text is then left out of the reply. Results over 1,000,000 characters are always written to a file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `documents` | number |  |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "{\"b\":1,\"a\":{\"d\":1,\"c\":2}}",
  "sortKeys": true
}
```

Result:

```json
{
  "documents": 1,
  "result": "a:\n  c: 2\n  d: 1\nb: 1\n"
}
```

<a id="markdown_table"></a>
### `markdown_table` — Markdown table

Make a GitHub-flavoured Markdown table from CSV, TSV, a JSON array (of objects or of arrays), an existing Markdown table (to re-align / re-pad it) or a "rows" array. Per-column alignment (left, center, right), columns padded to line up (optional), pipes escaped as \| and line breaks as <br>. CSV delimiter (comma, semicolon, tab) is detected from the first line. Give "text", a file "path" or "rows". Web version: https://s3eidat.github.io/markdown-table/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The CSV / TSV / JSON / Markdown table as inline text. |
| `path` | string | no | Or: a path to a CSV/TSV/JSON/Markdown file (UTF-8; use this for big inputs). |
| `rows` | ((string \| number \| boolean \| null)[])[] | no | Or: the table as an array of rows (first row = header unless header is false). |
| `format` | "auto" \| "csv" \| "tsv" \| "json" \| "markdown" | no (default `"auto"`) | Input format; "auto" detects Markdown, then JSON, then CSV/TSV. |
| `header` | boolean | no (default `true`) | First row is the header. When false, "Column 1", "Column 2"… headers are added. |
| `align` | "none" \| "left" \| "center" \| "right" \| ("none" \| "left" \| "center" \| "right")[] | no | Column alignment: one value for all columns or one per column. Default: keep a Markdown input's alignment, else none. |
| `pretty` | boolean | no (default `true`) | Pad cells so the columns line up (default true). |
| `output` | string | no | Optional: also write the result to this file path (or folder ending in "/"). The result text is then left out of the reply. Results over 1,000,000 characters are always written to a file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `columns` | number |  |
| `rows` | number | Body rows (without the header). |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "Name,Qty\nApple,3",
  "align": [
    "left",
    "right"
  ]
}
```

Result:

```json
{
  "columns": 2,
  "rows": 1,
  "result": "| Name  | Qty |\n| :---- | --: |\n| Apple |   3 |"
}
```

<a id="markdown_table_parse"></a>
### `markdown_table_parse` — Markdown table to JSON / CSV

Read a Markdown table (pipes optional at the ends, \| escapes, <br> as line breaks) back into data: JSON objects keyed by the header, JSON arrays, or CSV; also returns each column's alignment. Uses the website's Markdown import. Give "text" or a file "path". Web version: https://s3eidat.github.io/markdown-table/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The Markdown table as inline text. |
| `path` | string | no | Or: a path to a Markdown file (UTF-8; use this for big inputs). |
| `as` | "objects" \| "arrays" \| "csv" | no (default `"objects"`) | "objects" (header → keys), "arrays" (header row first) or "csv". |
| `output` | string | no | Optional: also write the result to this file path (or folder ending in "/"). The result text is then left out of the reply. Results over 1,000,000 characters are always written to a file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `columns` | string[] |  |
| `aligns` | ("none" \| "left" \| "center" \| "right")[] |  |
| `rows` | number |  |
| `data` | object[] | The rows (JSON formats, when not written to a file). |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "| Name | Qty |\n| :-- | --: |\n| Apple | 3 |"
}
```

Result:

```json
{
  "columns": [
    "Name",
    "Qty"
  ],
  "aligns": [
    "left",
    "right"
  ],
  "rows": 1,
  "data": [
    {
      "Name": "Apple",
      "Qty": "3"
    }
  ]
}
```

<a id="sql_format"></a>
### `sql_format` — SQL format / minify

Format SQL (one clause per line, indented sub-queries, CASE blocks, JOIN … ON with AND/OR, CREATE TABLE column lists) or minify it to one line per statement. Options: keyword case (UPPER, lower, keep), indent (2, 4, tab), trailing or leading commas, keep/drop comments when minifying. Dialect-neutral tokenizer: understands -- and /* */ comments, '' and E'\'' strings, "x", `x` and [x] identifiers, $$ dollar quotes, ::casts, JSON operators, :name/$1/? parameters. Only re-spaces; never runs anything. Give "text" or a file "path". Web version: https://s3eidat.github.io/sql-format/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The SQL as inline text. |
| `path` | string | no | Or: a path to a .sql file (UTF-8; use this for big inputs). |
| `mode` | "format" \| "minify" | no (default `"format"`) | "format" (default) or "minify". |
| `keywordCase` | "upper" \| "lower" \| "preserve" | no (default `"upper"`) | Keyword and built-in function case. |
| `indent` | "2" \| "4" \| "tab" \| 2 \| 4 | no (default `"2"`) | Indentation: 2, 4 or "tab". |
| `commas` | "trailing" \| "leading" | no (default `"trailing"`) | Put list commas at line ends (trailing) or starts (leading). |
| `keepComments` | boolean | no (default `true`) | Minify only: keep comments (as /* */). Optimizer hints /*+ */ and /*! */ are always kept. |
| `output` | string | no | Optional: also write the result to this file path (or folder ending in "/"). The result text is then left out of the reply. Results over 1,000,000 characters are always written to a file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `statements` | number |  |
| `lines` | number |  |
| `warning` | string | Set when a quote or comment is never closed. |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "select a, b from t where x = 1 and y = 2"
}
```

Result:

```json
{
  "statements": 1,
  "lines": 8,
  "result": "SELECT\n  a,\n  b\nFROM\n  t\nWHERE\n  x = 1\n  AND y = 2"
}
```

<a id="xml_format"></a>
### `xml_format` — XML format / minify / validate / to JSON

Validate XML (well-formedness and namespaces, with line and column for errors), pretty-print it (2, 4 spaces or tab; mixed content, CDATA and xml:space="preserve" kept exactly as written), minify it, or convert it to JSON (attributes as "@name", repeated elements as arrays, text as "#text"). Optionally removes comments. Never loads external DTDs or entities. Give "text" or a file "path". Web version: https://s3eidat.github.io/xml-format/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The XML as inline text. |
| `path` | string | no | Or: a path to a XML file (UTF-8; use this for big inputs). |
| `mode` | "format" \| "minify" \| "json" \| "validate" | no (default `"format"`) | "format" pretty-prints, "minify" strips layout whitespace, "json" converts to JSON, "validate" only checks (never throws on bad XML). |
| `indent` | "2" \| "4" \| "tab" \| integer | no (default `"2"`) | Indentation: 2, 4, "tab" (or any number of spaces 0-8). |
| `removeComments` | boolean | no (default `false`) | Drop <!-- comments --> (format and minify). |
| `output` | string | no | Optional: also write the result to this file path (or folder ending in "/"). The result text is then left out of the reply. Results over 1,000,000 characters are always written to a file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `valid` | boolean |  |
| `error` | string |  |
| `line` | number |  |
| `column` | number |  |
| `elements` | number |  |
| `depth` | number |  |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "<r><a>1</a><b x='1'  y=\"2\"><c/></b></r>"
}
```

Result:

```json
{
  "valid": true,
  "elements": 4,
  "depth": 3,
  "result": "<r>\n  <a>1</a>\n  <b x='1' y=\"2\">\n    <c/>\n  </b>\n</r>"
}
```

**Example**

```json
{
  "text": "<a>\n  <b></a>",
  "mode": "validate"
}
```

Result:

```json
{
  "valid": false,
  "error": "Opening and ending tag mismatch: b line 2 and a",
  "line": 2,
  "column": 6
}
```

<a id="xml_query"></a>
### `xml_query` — XML XPath query

Run an XPath 1.0 expression on XML: node-sets (elements, attributes, text…), numbers (count(), sum()), strings and booleans. Full XPath 1.0: all axes, predicates, operators and the core function library. Namespace prefixes declared anywhere in the document can be used as-is; a default namespace on the root gets the prefix "d" (e.g. //d:book), as on the website. Each matched node comes back as XML text plus its type, name and XPath location. Web version: https://s3eidat.github.io/xml-format/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The XML as inline text. |
| `path` | string | no | Or: a path to a XML file (UTF-8; use this for big inputs). |
| `xpath` | string | yes | XPath 1.0 expression, e.g. "//book[price > 30]/title", "count(//item)", "//d:entry/@id". |
| `namespaces` | object | no | Extra or overriding prefix → namespace URI bindings, e.g. {"atom":"http://www.w3.org/2005/Atom"}. |
| `limit` | integer | no (default `1000`) | Return at most this many nodes (default 1000). |

**Output**

| Field | Type | Description |
|---|---|---|
| `kind` | "nodes" \| "number" \| "string" \| "boolean" |  |
| `value` | number \| string \| boolean | For number / string / boolean results. |
| `count` | number | Number of matched nodes. |
| `nodes` | object[] |  |
| `truncated` | boolean |  |
| `namespaces` | object | Prefixes available to the expression. |
| `hint` | string |  |

**Example**

```json
{
  "text": "<lib><book><price>42</price><title>A</title></book><book><price>9</price><title>B</title></book></lib>",
  "xpath": "//book[price > 30]/title"
}
```

Result:

```json
{
  "kind": "nodes",
  "count": 1,
  "nodes": [
    {
      "type": "element",
      "name": "title",
      "location": "/lib/book[1]/title",
      "text": "<title>A</title>"
    }
  ]
}
```

<a id="yaml_to_json"></a>
### `yaml_to_json` — YAML to JSON

Convert YAML to JSON (YAML 1.2 core schema plus anchors/aliases, << merge keys, timestamps, multi-document files). Safe: no code tags (!!js/function, !!python… are rejected), alias bombs and recursive aliases are stopped. Several documents become a JSON array. Errors give line and column. Give "text" or a file "path". Web version: https://s3eidat.github.io/yaml-json/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The YAML as inline text. |
| `path` | string | no | Or: a path to a YAML file (UTF-8; use this for big inputs). |
| `indent` | 2 \| 4 \| "2" \| "4" | no (default `2`) | Indentation: 2 or 4 spaces. |
| `sortKeys` | boolean | no (default `false`) | Sort object keys alphabetically at every level. |
| `alwaysArray` | boolean | no (default `false`) | Always output an array of documents, even for one document. |
| `output` | string | no | Optional: also write the result to this file path (or folder ending in "/"). The result text is then left out of the reply. Results over 1,000,000 characters are always written to a file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `documents` | number |  |
| `values` | number |  |
| `result` | string | The converted / formatted text (left out when written to a file). |
| `path` | string | Absolute path of the written file, when "output" was given or the result was too big to inline. |
| `bytes` | number | Size of the written file in bytes. |

**Example**

```json
{
  "text": "base: &b {x: 1}\nchild:\n  <<: *b\n  y: 2\n"
}
```

Result:

```json
{
  "documents": 1,
  "values": 6,
  "result": "{\n  \"base\": {\n    \"x\": 1\n  },\n  \"child\": {\n    \"x\": 1,\n    \"y\": 2\n  }\n}\n"
}
```

<a id="dev"></a>
## Developer: utilities

Hashes, Base64, URLs, JWT, IDs, regex, cron, timestamps, subnets, chmod, HTTP status, .gitignore.

| Tool | What it does |
|---|---|
| [`base_convert`](#base_convert) | Number base convert |
| [`base64_decode`](#base64_decode) | Base64 decode |
| [`base64_encode`](#base64_encode) | Base64 encode |
| [`bytes_convert`](#bytes_convert) | Text ⇄ UTF-8 bytes |
| [`chmod_calc`](#chmod_calc) | chmod calculator |
| [`cron_explain`](#cron_explain) | Cron explain |
| [`css_clamp`](#css_clamp) | Fluid CSS clamp() |
| [`css_units`](#css_units) | CSS unit converter |
| [`gitignore_generate`](#gitignore_generate) | Generate .gitignore |
| [`hash_compute`](#hash_compute) | Hash (MD5 / SHA) |
| [`http_status`](#http_status) | HTTP status codes |
| [`id_generate`](#id_generate) | Generate IDs (UUID / ULID / NanoID) |
| [`id_inspect`](#id_inspect) | Inspect a UUID / ULID |
| [`ip_in_range`](#ip_in_range) | Is this IP in that range? |
| [`jwt_decode`](#jwt_decode) | JWT decode |
| [`meta_tags_generate`](#meta_tags_generate) | Meta tags generator |
| [`regex_test`](#regex_test) | Regex test |
| [`subnet_calc`](#subnet_calc) | Subnet calculator (IPv4 / IPv6) |
| [`time_convert`](#time_convert) | Timestamp convert |
| [`url_decode`](#url_decode) | URL decode |
| [`url_encode`](#url_encode) | URL encode |
| [`url_parse`](#url_parse) | URL parse |
| [`useragent_parse`](#useragent_parse) | User agent parse |

<a id="base_convert"></a>
### `base_convert` — Number base convert

Convert a whole number of any size (BigInt) between bases 2–36: binary, octal, decimal, hex, base 36 or any other. Input base can be auto-detected from 0x / 0b / 0o prefixes; spaces, _ and , separators are ignored; negatives OK. Also shows bit length and the two's complement view (signed/unsigned value, hex, bits) at 8/16/32/64 bits, and whether it fits. Fractions are not supported (same as the website). Web version: https://s3eidat.github.io/base-convert/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `value` | string | yes | The number, e.g. "0xC0FFEE", "-0b101", "1_000_000", "zz" (with from=36). |
| `from` | "auto" \| integer | no (default `"auto"`) | Input base 2–36, or "auto" (prefix, else decimal). |
| `to` | integer[] | no (default `[2,8,10,16,36]`) | Output bases. Default [2, 8, 10, 16, 36]. |
| `group` | boolean | no (default `false`) | Group digits like the website (binary/hex by 4, octal by 3, decimal with commas). |
| `uppercase` | boolean | no (default `false`) | Upper-case letters for bases above 10. |
| `width` | 8 \| 16 \| 32 \| 64 | no (default `32`) | Bit width for the two's complement view. |

**Output**

| Field | Type | Description |
|---|---|---|
| `decimal` | string |  |
| `results` | object[] |  |
| `bitLength` | number |  |
| `bytes` | number |  |
| `twosComplement` | object |  |

**Example** — decimal 12648430, binary 110000001111111111101110

```json
{
  "value": "0xC0FFEE"
}
```

Result:

```json
{
  "decimal": "12648430",
  "results": [
    {
      "base": 2,
      "name": "Binary",
      "value": "110000001111111111101110"
    },
    {
      "base": 8,
      "name": "Octal",
      "value": "60177756"
    },
    {
      "base": 10,
      "name": "Decimal",
      "value": "12648430"
    },
    {
      "base": 16,
      "name": "Hex",
      "value": "c0ffee"
    },
    {
      "base": 36,
      "name": "Base 36",
      "value": "7j3la"
    }
  ],
  "bitLength": 24,
  "bytes": 3,
  "twosComplement": {
    "width": 32,
    "signed": "12648430",
    "unsigned": "12648430",
    "hex": "0x00C0 FFEE",
    "binary": "00000000110000001111111111101110",
    "fits": true,
    "note": "Fits in 32 bits. Signed range -2147483648 to 2147483647."
  }
}
```

**Example** — two’s complement hex FF

```json
{
  "value": "-1",
  "width": 8
}
```

Result:

```json
{
  "decimal": "-1",
  "results": [
    {
      "base": 2,
      "name": "Binary",
      "value": "-1"
    },
    {
      "base": 8,
      "name": "Octal",
      "value": "-1"
    },
    {
      "base": 10,
      "name": "Decimal",
      "value": "-1"
    },
    {
      "base": 16,
      "name": "Hex",
      "value": "-1"
    },
    {
      "base": 36,
      "name": "Base 36",
      "value": "-1"
    }
  ],
  "bitLength": 1,
  "bytes": 1,
  "twosComplement": {
    "width": 8,
    "signed": "-1",
    "unsigned": "255",
    "hex": "0xFF",
    "binary": "11111111",
    "fits": true,
    "note": "Fits in 8 bits. Signed range -128 to 127."
  }
}
```

<a id="base64_decode"></a>
### `base64_decode` — Base64 decode

Decode Base64 (standard or URL-safe, padding optional, whitespace and data: URI prefix allowed). Valid UTF-8 text is returned inline; binary data (or anything when "output" is given) is written to a file whose extension comes from the detected type (PNG, JPEG, GIF, WebP, BMP, PDF, ZIP, gzip, MP3, Ogg, MP4, WOFF2, SVG, JSON). Web version: https://s3eidat.github.io/base64-kit/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `base64` | string | yes | The Base64 text to decode (may be a data: URI). |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `fileName` | string | no | Base name for the written file (extension follows the detected type). Default "decoded". |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string | Decoded UTF-8 text (when the bytes are text and no output path was given). |
| `path` | string | Written file, for binary data or when "output" was given. |
| `bytes` | number |  |
| `isText` | boolean |  |
| `detectedType` | string |  |
| `mimeType` | string |  |
| `dataUriMime` | string | MIME type the data: URI declared, if any. |

**Example**

```json
{
  "base64": "Zm9vYmFy"
}
```

Result:

```json
{
  "text": "foobar",
  "bytes": 6,
  "isText": true,
  "detectedType": "Text",
  "mimeType": "text/plain"
}
```

**Example** — Writes decoded.png

```json
{
  "base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
}
```

Result:

```json
{
  "bytes": 70,
  "isText": false,
  "detectedType": "PNG image",
  "mimeType": "image/png",
  "dataUriMime": "image/png",
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-2G2NgY\\decoded.png"
}
```

<a id="base64_encode"></a>
### `base64_encode` — Base64 encode

Encode text (as UTF-8) or a file (path or base64) to Base64. Options: URL-safe alphabet (- and _, no padding), wrap at 76 columns (MIME), or a data: URI for files (MIME type detected from magic bytes). Results over 1 MB, or when "output" is given, are written to a text file instead of returned inline. Web version: https://s3eidat.github.io/base64-kit/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | Text to encode (UTF-8). |
| `file` | string | no | Or: a file to encode: local path or base64 data. |
| `urlSafe` | boolean | no (default `false`) | Use the URL-safe alphabet (- and _) and drop "=" padding. |
| `wrap` | boolean | no (default `false`) | Insert a line break every 76 characters (MIME style). |
| `dataUri` | boolean | no (default `false`) | Return a data: URI (data:<mime>;base64,…) instead of bare Base64. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |

**Output**

| Field | Type | Description |
|---|---|---|
| `base64` | string | The encoded text (absent when written to a file). |
| `path` | string | File holding the encoded text, when written to disk. |
| `inputBytes` | number |  |
| `characters` | number |  |
| `detectedType` | string |  |
| `mimeType` | string |  |

**Example**

```json
{
  "text": "foobar"
}
```

Result:

```json
{
  "base64": "Zm9vYmFy",
  "inputBytes": 6,
  "characters": 8
}
```

**Example** — URL-safe output without padding

```json
{
  "text": "ûÿ",
  "urlSafe": true
}
```

Result:

```json
{
  "inputBytes": 4,
  "characters": 6,
  "base64": "w7vDvw"
}
```

<a id="bytes_convert"></a>
### `bytes_convert` — Text ⇄ UTF-8 bytes

Turn text into its UTF-8 bytes (hex, binary or decimal), or bytes back into text. Byte input is lenient: "48 65", "4865", "0x48,0x69", "01001000 01101001", "72 105". Invalid UTF-8 is reported and shown with � replacements. Web version: https://s3eidat.github.io/base-convert/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | Text to turn into bytes. |
| `bytes` | string | no | Or: bytes to turn into text, in the chosen format. |
| `format` | "hex" \| "bin" \| "dec" | no (default `"hex"`) | Byte format. Default hex. |
| `uppercase` | boolean | no (default `false`) | Upper-case hex digits. |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string |  |
| `bytes` | string |  |
| `byteCount` | number |  |
| `characters` | number |  |
| `validUtf8` | boolean |  |

**Example**

```json
{
  "text": "Hi ☕",
  "uppercase": true
}
```

Result:

```json
{
  "text": "Hi ☕",
  "bytes": "48 69 20 E2 98 95",
  "byteCount": 6,
  "characters": 4,
  "validUtf8": true
}
```

<a id="chmod_calc"></a>
### `chmod_calc` — chmod calculator

Convert Unix file permissions between octal (755, 0644, 4755), symbolic (rwxr-xr-x, ls -l style -rwsr-xr-x) and chmod clauses (u=rwx,g=rx,o=; relative ones like g+w or +x apply to "base"). Returns all forms, a plain-English description per owner/group/others, setuid/setgid/sticky meaning, safety warnings, and ready chmod commands. Web version: https://s3eidat.github.io/chmod-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | string | yes | Octal ("755", "0644", "2775"), symbolic ("rwxr-x---", "drwxrwxrwt") or clauses ("u=rw,go=r", "+x"). |
| `base` | string | no | Starting mode (octal) for relative clauses like "g+w". Default 000. |
| `file` | string | no (default `"file"`) | File name for the example chmod commands (shell-quoted). |
| `recursive` | boolean | no (default `false`) | Add -R to the example commands. |

**Output**

| Field | Type | Description |
|---|---|---|
| `octal` | string |  |
| `symbolic` | string |  |
| `clauses` | string |  |
| `description` | object[] |  |
| `warnings` | object[] |  |
| `commands` | object |  |

**Example** — symbolic rwsr-xr-x, clauses u=rwxs,g=rx,o=rx, setuid explained

```json
{
  "mode": "4755"
}
```

Result:

```json
{
  "octal": "4755",
  "symbolic": "rwsr-xr-x",
  "clauses": "u=rwxs,g=rx,o=rx",
  "description": [
    {
      "who": "Owner",
      "text": "can read, write and execute"
    },
    {
      "who": "Group",
      "text": "can read and execute"
    },
    {
      "who": "Others",
      "text": "can read and execute"
    },
    {
      "who": "setuid",
      "text": "Runs as the file's owner"
    }
  ],
  "warnings": [],
  "commands": {
    "octal": "chmod 4755 file",
    "symbolic": "chmod u=rwxs,g=rx,o=rx file"
  }
}
```

**Example** — octal 750

```json
{
  "mode": "g-w,o-rwx",
  "base": "775",
  "file": "/srv/app"
}
```

Result:

```json
{
  "octal": "750",
  "symbolic": "rwxr-x---",
  "clauses": "u=rwx,g=rx,o=",
  "description": [
    {
      "who": "Owner",
      "text": "can read, write and execute"
    },
    {
      "who": "Group",
      "text": "can read and execute"
    },
    {
      "who": "Others",
      "text": "has no access"
    }
  ],
  "warnings": [],
  "commands": {
    "octal": "chmod 750 /srv/app",
    "symbolic": "chmod u=rwx,g=rx,o= /srv/app"
  }
}
```

<a id="cron_explain"></a>
### `cron_explain` — Cron explain

Explain a standard 5-field cron expression (minute hour day-of-month month day-of-week; names like MON/JAN, ranges, lists, steps, ? and the @hourly/@daily/@weekly/@monthly/@yearly/@reboot macros) in plain English and list the next run times in an IANA timezone. Follows Vixie cron: when both day fields are restricted a day matches either one. Quartz 6/7-field and L/W/# syntax are rejected with a clear message; per-field errors are reported. Web version: https://s3eidat.github.io/cron-explain/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `expression` | string | yes | The cron expression, e.g. "*/30 9-17 * * 1-5" or "@daily". |
| `count` | integer | no (default `10`) | How many upcoming run times to list (0–100). Default 10. |
| `timezone` | string | no | IANA timezone the cron daemon runs in. Default: the server's (Asia/Amman). |
| `from` | string | no | Start searching after this instant (ISO 8601). Default: now. |
| `hour12` | boolean | no (default `false`) | Describe times on a 12-hour clock (9:00 AM) instead of 24-hour (09:00). |

**Output**

| Field | Type | Description |
|---|---|---|
| `valid` | boolean |  |
| `description` | string |  |
| `expanded` | string | The 5 fields after expanding a macro. |
| `macro` | string |  |
| `errors` | object | Per-field error messages (minute, hour, dom, month, dow). |
| `error` | string |  |
| `timezone` | string |  |
| `nextRuns` | object[] |  |
| `note` | string |  |

**Example** — description: "At 09:00, on Monday through Friday"

```json
{
  "expression": "0 9 * * 1-5",
  "count": 3,
  "timezone": "Europe/London"
}
```

Result:

```json
{
  "valid": true,
  "description": "At 09:00, on Monday through Friday.",
  "timezone": "Europe/London",
  "expanded": "0 9 * * 1-5",
  "nextRuns": [
    {
      "iso": "2026-01-16T09:00:00+00:00",
      "utc": "2026-01-16T09:00:00.000Z",
      "local": "Fri, Jan 16, 2026, 09:00",
      "relative": "in 24 hours"
    },
    {
      "iso": "2026-01-19T09:00:00+00:00",
      "utc": "2026-01-19T09:00:00.000Z",
      "local": "Mon, Jan 19, 2026, 09:00",
      "relative": "in 4 days"
    },
    {
      "iso": "2026-01-20T09:00:00+00:00",
      "utc": "2026-01-20T09:00:00.000Z",
      "local": "Tue, Jan 20, 2026, 09:00",
      "relative": "in 5 days"
    }
  ]
}
```

**Example** — description: "At 00:00, on day 13 of the month or on Friday"

```json
{
  "expression": "0 0 13 * 5"
}
```

Result:

```json
{
  "valid": true,
  "description": "At 00:00, on day 13 of the month or on Friday.",
  "timezone": "Asia/Amman",
  "expanded": "0 0 13 * 5",
  "nextRuns": [
    {
      "iso": "2026-01-16T00:00:00+03:00",
      "utc": "2026-01-15T21:00:00.000Z",
      "local": "Fri, Jan 16, 2026, 24:00",
      "relative": "in 12 hours"
    },
    {
      "iso": "2026-01-23T00:00:00+03:00",
      "utc": "2026-01-22T21:00:00.000Z",
      "local": "Fri, Jan 23, 2026, 24:00",
      "relative": "in 7 days"
    },
    {
      "iso": "2026-01-30T00:00:00+03:00",
      "utc": "2026-01-29T21:00:00.000Z",
      "local": "Fri, Jan 30, 2026, 24:00",
      "relative": "in 14 days"
    },
    {
      "iso": "2026-02-06T00:00:00+03:00",
      "utc": "2026-02-05T21:00:00.000Z",
      "local": "Fri, Feb 6, 2026, 24:00",
      "relative": "in 21 days"
    },
    {
      "iso": "2026-02-13T00:00:00+03:00",
      "utc": "2026-02-12T21:00:00.000Z",
      "local": "Fri, Feb 13, 2026, 24:00",
      "relative": "in 28 days"
    },
    {
      "iso": "2026-02-20T00:00:00+03:00",
      "utc": "2026-02-19T21:00:00.000Z",
      "local": "Fri, Feb 20, 2026, 24:00",
      "relative": "in 35 days"
    },
    {
      "iso": "2026-02-27T00:00:00+03:00",
      "utc": "2026-02-26T21:00:00.000Z",
      "local": "Fri, Feb 27, 2026, 24:00",
      "relative": "in 42 days"
    },
    {
      "iso": "2026-03-06T00:00:00+03:00",
      "utc": "2026-03-05T21:00:00.000Z",
      "local": "Fri, Mar 6, 2026, 24:00",
      "relative": "in 49 days"
    },
    {
      "iso": "2026-03-13T00:00:00+03:00",
      "utc": "2026-03-12T21:00:00.000Z",
      "local": "Fri, Mar 13, 2026, 24:00",
      "relative": "in 56 days"
    },
    {
      "iso": "2026-03-20T00:00:00+03:00",
      "utc": "2026-03-19T21:00:00.000Z",
      "local": "Fri, Mar 20, 2026, 24:00",
      "relative": "in 63 days"
    }
  ]
}
```

<a id="css_clamp"></a>
### `css_clamp` — Fluid CSS clamp()

Build a fluid typography/spacing value: CSS clamp() that grows linearly from a size at a small viewport to a size at a large viewport (sizes and viewports in px), expressed in rem or px. Also returns the computed size at sample viewport widths. Web version: https://s3eidat.github.io/css-units/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `minSize` | number | no (default `16`) | Size in px at the small viewport. Default 16. |
| `maxSize` | number | no (default `32`) | Size in px at the large viewport. Default 32. |
| `minViewport` | number | no (default `320`) | Small viewport width in px. Default 320. |
| `maxViewport` | number | no (default `1280`) | Large viewport width in px. Default 1280. |
| `unit` | "rem" \| "px" | no (default `"rem"`) | Output unit. Default rem. |
| `rootFontSize` | number | no (default `16`) | Root (html) font size in px, for rem. Default 16. |
| `sampleViewports` | number[] | no (default `[320,768,1280,1920]`) | Viewport widths (px) to compute the resulting size at. |

**Output**

| Field | Type | Description |
|---|---|---|
| `css` | string |  |
| `declaration` | string |  |
| `slopeVw` | number |  |
| `interceptPx` | number |  |
| `samples` | object[] |  |

**Example**

```json
{
  "minSize": 16,
  "maxSize": 32,
  "minViewport": 320,
  "maxViewport": 1280
}
```

Result:

```json
{
  "css": "clamp(1rem, 0.6667rem + 1.6667vw, 2rem)"
}
```

<a id="css_units"></a>
### `css_units` — CSS unit converter

Convert a CSS length between px, rem, em, %, pt, pc, in, cm, mm, vw, vh, vmin and vmax, using a root font size, parent font size and viewport size (defaults 16px / 16px / 1440×900). Uses the CSS definition of 96px per inch. Returns every unit, rounded to 4 decimals by default. Web version: https://s3eidat.github.io/css-units/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `value` | number | yes | The length to convert, e.g. 24. |
| `unit` | "px" \| "rem" \| "em" \| "%" \| "pt" \| "vw" \| "vh" \| "vmin" \| "vmax" \| "pc" \| "in" \| "cm" \| "mm" | no (default `"px"`) | Unit of "value". Default px. |
| `rootFontSize` | number | no (default `16`) | Root (html) font size in px, for rem. Default 16. |
| `parentFontSize` | number | no (default `16`) | Parent element font size in px, for em and %. Default 16. |
| `viewportWidth` | number | no (default `1440`) | Viewport width in px, for vw/vmin/vmax. Default 1440. |
| `viewportHeight` | number | no (default `900`) | Viewport height in px, for vh/vmin/vmax. Default 900. |
| `decimals` | integer | no (default `4`) | Decimal places to round to. Default 4. |

**Output**

| Field | Type | Description |
|---|---|---|
| `px` | number |  |
| `results` | object[] |  |

**Example** — 1.5rem, 18pt, 1.6667vw…

```json
{
  "value": 24,
  "unit": "px"
}
```

Result:

```json
{
  "px": 24,
  "results": [
    {
      "unit": "px",
      "name": "Pixels",
      "value": 24,
      "css": "24px"
    },
    {
      "unit": "rem",
      "name": "Root em",
      "value": 1.5,
      "css": "1.5rem"
    },
    {
      "unit": "em",
      "name": "Parent em",
      "value": 1.5,
      "css": "1.5em"
    },
    {
      "unit": "%",
      "name": "Percent of parent font",
      "value": 150,
      "css": "150%"
    },
    {
      "unit": "pt",
      "name": "Points",
      "value": 18,
      "css": "18pt"
    },
    {
      "unit": "vw",
      "name": "Viewport width",
      "value": 1.6667,
      "css": "1.6667vw"
    },
    {
      "unit": "vh",
      "name": "Viewport height",
      "value": 2.6667,
      "css": "2.6667vh"
    },
    {
      "unit": "vmin",
      "name": "Viewport min",
      "value": 2.6667,
      "css": "2.6667vmin"
    },
    {
      "unit": "vmax",
      "name": "Viewport max",
      "value": 1.6667,
      "css": "1.6667vmax"
    },
    {
      "unit": "pc",
      "name": "Picas",
      "value": 1.5,
      "css": "1.5pc"
    },
    {
      "unit": "in",
      "name": "Inches",
      "value": 0.25,
      "css": "0.25in"
    },
    {
      "unit": "cm",
      "name": "Centimetres",
      "value": 0.635,
      "css": "0.635cm"
    },
    {
      "unit": "mm",
      "name": "Millimetres",
      "value": 6.35,
      "css": "6.35mm"
    }
  ]
}
```

<a id="gitignore_generate"></a>
### `gitignore_generate` — Generate .gitignore

Build a .gitignore by combining the website's templates for languages, frameworks, mobile, game engines, tools, OSes and editors (node, python, java, android, ios, flutter, reactnative, unity, unreal, go, rust, cpp, dotnet, php, wordpress, rails, django, nextjs, vite, docker, terraform, macos, windows, linux, vscode, jetbrains, vim). Templates can be named by id, name or keyword (e.g. "javascript" → node). Duplicate patterns are removed, optional "### Name ###" section headers, plus your own custom lines at the end. Returns the text, or writes it to "output" (e.g. "./.gitignore"). Set "list" to see all templates. Web version: https://s3eidat.github.io/gitignore-gen/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `templates` | string[] | no (default `[]`) | Templates to combine, in order, e.g. ["node", "nextjs", "vscode", "macos"]. |
| `custom` | string | no | Extra lines to append in a "Custom" section. |
| `headers` | boolean | no (default `true`) | Add a generated-by header, section headers and template comments. Default true. |
| `dedupe` | boolean | no (default `true`) | Drop patterns that already appeared in an earlier section. Default true. |
| `list` | boolean | no (default `false`) | Just list the available templates (id, name, group, keywords). |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string |  |
| `path` | string |  |
| `templates` | string[] |  |
| `patterns` | number |  |
| `duplicatesRemoved` | number |  |
| `available` | object[] |  |

**Example**

```json
{
  "templates": [
    "node",
    "nextjs",
    "vscode",
    "macos"
  ],
  "custom": "secrets/"
}
```

Result:

```json
{
  "templates": [
    "node",
    "nextjs",
    "vscode",
    "macos"
  ],
  "patterns": 54,
  "duplicatesRemoved": 2,
  "text": "# Generated with Gitignore Gen (https://s3eidat.github.io/gitignore-gen/)\n# Templates: Node.js, Next.js, VS Code, macOS, Custom\n\n### Node.js ###\n# Dependencies\nnode_modules/\njspm_packages/\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# Logs\nlogs/\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Build output and caches\ndist/\nbuild/\ncoverage/\n.nyc_output/\n.cache/\n.eslintcache\n.stylelintcache\n*.tsbuildinfo\n.parcel-cache/\n.turbo/\n\n# Environment\n.env\n.env.*\n!.env.example\n\n### Next.js ###\n# Next.js\n/.next/\n/out/\nnext-env.d.ts\n\n# Production\n/bui…"
}
```

**Example**

```json
{
  "list": true
}
```

Result:

```json
{
  "available": [
    {
      "id": "node",
      "name": "Node.js",
      "group": "Language",
      "keywords": "npm yarn pnpm javascript typescript bun"
    },
    {
      "id": "python",
      "name": "Python",
      "group": "Language",
      "keywords": "pip venv pytest jupyter poetry conda"
    },
    {
      "id": "java",
      "name": "Java / Gradle / Maven",
      "group": "Language",
      "keywords": "kotlin jvm gradle maven spring"
    },
    {
      "id": "android",
      "name": "Android",
      "group": "Mobile",
      "keywords": "kotlin gradle apk studio"
    },
    {
      "id": "ios",
      "name": "iOS / Xcode",
      "group": "Mobile",
      "keywords": "swift xcode apple cocoapods carthage spm"
    },
    {
      "id": "flutter",
      "name": "Flutter / Dart",
      "group": "Mobile",
      "keywords": "dart pub"
    },
    {
      "id": "reactnative",
      "name": "React Native",
      "group": "Mobile",
      "keywords": "expo metro"
    },
    {
      "id": "unity",
      "name": "Unity",
      "group": "Game",
      "keywords": "game engine csharp"
    },
    {
      "id": "unreal",
      "name": "Unreal Engine",
      "group": "Game",
      "keywords": "game engine cpp ue5"
    },
    {
      "id": "go",
      "name": "Go",
      "group": "Language",
      "keywords": "golang"
    },
    {
      "id": "rust",
      "name": "Rust",
      "group": "Language",
      "keywords": "cargo"
    },
    {
      "id": "cpp",
      "name": "C / C++",
      "group": "Language",
      "keywords": "c cmake make clang gcc"
    },
    {
      "id": "dotnet",
      "name": ".NET / C#",
      "group": "Language",
      "keywords": "csharp visual studio nuget aspnet"
    },
    {
      "id": "php",
      "name": "PHP / Laravel",
      "group": "Language",
      "keywords": "composer laravel symfony"
    },
    {
      "id": "wordpress",
      "name": "WordPress",
      "group": "Framework",
      "keywords": "wp cms php"
    },
    {
      "id": "rails",
      "name": "Ruby / Rails",
      "group": "Framework",
      "keywords": "ruby bundler gem"
    },
    {
      "id": "django",
      "name": "Django",
      "group": "Framework",
      "keywords": "python web"
    },
    {
      "id": "nextjs",
      "name": "Next.js",
      "group": "Framework",
      "keywords": "react vercel node"
    },
    {
      "id": "vite",
      "name": "Vite",
      "group": "Framework",
      "keywords": "react vue svelte frontend"
    },
    {
      "id": "docker",
      "name": "Docker",
      "group": "Tool",
      "keywords": "container compose"
    },
    {
      "id": "terraform",
      "name": "Terraform",
      "group": "Tool",
      "keywords": "hcl iac infrastructure"
    },
    {
      "id": "macos",
      "name": "macOS",
      "group": "OS",
      "keywords": "mac apple finder"
    },
    {
      "id": "windows",
      "name": "Windows",
      "group": "OS",
      "keywords": "explorer microsoft"
    },
    {
      "id": "linux",
      "name": "Linux",
      "group": "OS",
      "keywords": "ubuntu unix"
    },
    {
      "id": "vscode",
      "name": "VS Code",
      "group": "Editor",
      "keywords": "visual studio code editor"
    },
    {
      "id": "jetbrains",
      "name": "JetBrains",
      "group": "Editor",
      "keywords": "intellij idea webstorm pycharm android studio rider"
    },
    {
      "id": "vim",
      "name": "Vim",
      "group": "Editor",
      "keywords": "neovim nvim"
    }
  ]
}
```

<a id="hash_compute"></a>
### `hash_compute` — Hash (MD5 / SHA)

Compute MD5, SHA-1, SHA-256 and SHA-512 of text or a file (path or base64), optionally an HMAC with a key, and check against an expected checksum (hex in any case/spacing, "SHA256 (file) = …" lines, or base64). Web version: https://s3eidat.github.io/hash-lab/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | Text to hash (UTF-8). |
| `file` | string | no | Or: a file to hash: local path or base64. |
| `hmacKey` | string | no | If set, also compute HMAC with this key. |
| `hmacAlgorithm` | "SHA-1" \| "SHA-256" \| "SHA-384" \| "SHA-512" | no (default `"SHA-256"`) |  |
| `format` | "hex" \| "HEX" \| "base64" | no (default `"hex"`) | Output encoding. |
| `expected` | string | no | A checksum to compare against; the result says which algorithm matched. |

**Output**

| Field | Type | Description |
|---|---|---|
| `bytes` | number |  |
| `hashes` | object |  |
| `match` | string \| null |  |

**Example** — SHA-256 = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

```json
{
  "text": "abc"
}
```

Result:

```json
{
  "bytes": 3,
  "hashes": {
    "MD5": "900150983cd24fb0d6963f7d28e17f72",
    "SHA-1": "a9993e364706816aba3e25717850c26c9cd0d89d",
    "SHA-256": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
    "SHA-512": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
  }
}
```

<a id="http_status"></a>
### `http_status` — HTTP status codes

Look up HTTP status codes: by exact code (e.g. 418, also non-standard ones like 499 or 524), by code prefix ("50"), or by words ("too many", "redirect post"), optionally limited to a class (1xx–5xx). Each result has the name, what it means, when to use it, common causes and the defining spec. Non-standard nginx/Cloudflare codes are only searched when "includeNonStandard" is set. Web version: https://s3eidat.github.io/http-status/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `query` | string | no (default `""`) | A code ("404"), code prefix ("5", "50") or search words. Empty = all codes (filtered by class). |
| `class` | "1" \| "2" \| "3" \| "4" \| "5" | no | Only this class: 1 informational, 2 success, 3 redirection, 4 client error, 5 server error. |
| `includeNonStandard` | boolean | no (default `false`) | Also search non-standard codes (nginx 499, Cloudflare 52x…). |
| `limit` | integer | no (default `20`) | Max results to return. Default 20. |

**Output**

| Field | Type | Description |
|---|---|---|
| `count` | number |  |
| `results` | object[] |  |

**Example** — 429 Too Many Requests with meaning, when to use and causes

```json
{
  "query": "429"
}
```

Result:

```json
{
  "count": 1,
  "results": [
    {
      "code": 429,
      "name": "Too Many Requests",
      "class": "4xx",
      "className": "Client error",
      "meaning": "You sent too many requests in a given time (rate limiting).",
      "whenToUse": "Rate limits. Send Retry-After to say when to try again.",
      "commonCauses": "Scripts and bots looping fast, missing back-off, shared IPs.",
      "spec": "RFC 6585",
      "nonStandard": false
    }
  ]
}
```

**Example**

```json
{
  "query": "teapot"
}
```

Result:

```json
{
  "count": 1,
  "results": [
    {
      "code": 418,
      "name": "I'm a teapot",
      "class": "4xx",
      "className": "Client error",
      "meaning": "An April Fools' joke: the server refuses to brew coffee because it is a teapot.",
      "whenToUse": "Easter eggs. Some sites use it to answer requests they want to refuse.",
      "commonCauses": "Hyper Text Coffee Pot Control Protocol jokes, bot blocking.",
      "spec": "RFC 2324",
      "nonStandard": false
    }
  ]
}
```

<a id="id_generate"></a>
### `id_generate` — Generate IDs (UUID / ULID / NanoID)

Generate 1–1000 unique IDs with a cryptographic RNG: UUID v4 (random), UUID v7 (time-ordered, RFC 9562, strictly increasing within a batch), ULID (26 Crockford Base32 chars, monotonic) or NanoID (custom length 2–128 and alphabet: URL-safe default, alphanumeric, hex, digits, no look-alikes, or your own). Options: uppercase, no dashes. Web version: https://s3eidat.github.io/uuid-forge/

Hints: readOnly

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `kind` | "v4" \| "v7" \| "ulid" \| "nanoid" | no (default `"v4"`) | Which ID type. |
| `count` | integer | no (default `1`) | How many IDs (1–1000). |
| `uppercase` | boolean | no (default `false`) | Upper-case output (UUID hex digits, ULID letters). Default false = lower case, like the website. |
| `noDashes` | boolean | no (default `false`) | UUIDs only: drop the dashes (32 hex chars). |
| `length` | integer | no (default `21`) | NanoID length (2–128). 21 URL-safe chars ≈ UUID v4 collision safety. |
| `alphabet` | string | no (default `"A-Za-z0-9_-"`) | NanoID alphabet: a preset ("A-Za-z0-9_-", "A-Za-z0-9", "0-9a-f", "0-9", "nolook") or your own characters (2–256 distinct; duplicates ignored). |

**Output**

| Field | Type | Description |
|---|---|---|
| `ids` | string[] |  |
| `kind` | string |  |
| `bitsOfRandomness` | number |  |

**Example** — Three time-ordered UUIDs, e.g. 019a2f3c-8d41-7a0b-9c2e-5f1d3b7a4c10

```json
{
  "kind": "v7",
  "count": 3
}
```

**Example**

```json
{
  "kind": "nanoid",
  "length": 12,
  "alphabet": "nolook"
}
```

<a id="id_inspect"></a>
### `id_inspect` — Inspect a UUID / ULID

Check whether a string is a valid UUID (any version; braces, urn:uuid: and missing dashes accepted) or ULID, and report its version, variant and canonical form. For UUID v1, v6, v7 and ULID also returns the embedded creation time. Web version: https://s3eidat.github.io/uuid-forge/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes | The UUID or ULID to inspect. |

**Output**

| Field | Type | Description |
|---|---|---|
| `valid` | boolean |  |
| `kind` | string |  |
| `version` | number |  |
| `variant` | string |  |
| `canonical` | string |  |
| `createdAt` | string |  |
| `timestampMs` | number |  |

**Example**

```json
{
  "id": "017F22E2-79B0-7CC3-98C4-DC0C0C07398F"
}
```

Result:

```json
{
  "valid": true,
  "kind": "UUID",
  "version": 7,
  "variant": "RFC 9562 / 4122",
  "canonical": "017f22e2-79b0-7cc3-98c4-dc0c0c07398f",
  "createdAt": "2022-02-22T19:22:22.000Z",
  "timestampMs": 1645557742000
}
```

<a id="ip_in_range"></a>
### `ip_in_range` — Is this IP in that range?

Check whether an IPv4 or IPv6 address falls inside a range given as CIDR ("10.0.0.0/8", "2001:db8::/32"), a dash range ("192.168.1.10 - 192.168.1.60") or a single address. Web version: https://s3eidat.github.io/subnet-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `ip` | string | yes | The address to test. |
| `range` | string | yes | CIDR, "start - end" range, or single address (same family as "ip"). |

**Output**

| Field | Type | Description |
|---|---|---|
| `inside` | boolean |  |
| `rangeStart` | string |  |
| `rangeEnd` | string |  |

**Example**

```json
{
  "ip": "192.168.10.77",
  "range": "192.168.10.64/27"
}
```

Result:

```json
{
  "inside": true,
  "rangeStart": "192.168.10.64",
  "rangeEnd": "192.168.10.95"
}
```

<a id="jwt_decode"></a>
### `jwt_decode` — JWT decode

Decode a JSON Web Token (a leading "Bearer " is fine): header, payload, iat/nbf/exp/auth_time as ISO dates with relative time, expired / not-yet-valid status, and named standard claims. If "secret" is given, verifies HS256/HS384/HS512 signatures (secret as text or Base64URL). RS/ES/PS tokens are decoded but not verified (needs a public key). Encrypted JWE tokens can't be read. Web version: https://s3eidat.github.io/jwt-inspector/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `token` | string | yes | The JWT (header.payload.signature). |
| `secret` | string | no | HMAC secret to verify an HS256/384/512 signature. |
| `secretIsBase64` | boolean | no (default `false`) | The secret is Base64/Base64URL-encoded bytes, not text. |
| `now` | number | no | Unix time in seconds to judge expiry against. Default: now. |

**Output**

| Field | Type | Description |
|---|---|---|
| `header` | object |  |
| `payload` | object |  |
| `algorithm` | string |  |
| `unsigned` | boolean |  |
| `status` | "ok" \| "warn" \| "bad" |  |
| `statusText` | string |  |
| `expired` | boolean |  |
| `times` | object[] |  |
| `claims` | object[] |  |
| `signature` | object |  |

**Example** — payload.name = "John Doe", signature.verified = true, status "No expiry (exp) claim"

```json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
  "secret": "your-256-bit-secret"
}
```

Result:

```json
{
  "header": {
    "alg": "HS256",
    "typ": "JWT"
  },
  "payload": {
    "sub": "1234567890",
    "name": "John Doe",
    "iat": 1516239022
  },
  "algorithm": "HS256",
  "unsigned": false,
  "status": "warn",
  "statusText": "No expiry (exp) claim",
  "expired": false,
  "times": [
    {
      "claim": "iat",
      "label": "Issued at",
      "value": 1516239022,
      "iso": "2018-01-18T01:30:22.000Z",
      "relative": "8 years ago"
    }
  ],
  "claims": [
    {
      "claim": "sub",
      "label": "Subject",
      "value": "1234567890"
    },
    {
      "claim": "name",
      "label": "Name",
      "value": "John Doe"
    }
  ],
  "signature": {
    "verified": true,
    "message": "Signature verified"
  }
}
```

<a id="meta_tags_generate"></a>
### `meta_tags_generate` — Meta tags generator

Generate the <head> HTML for SEO and social sharing: <title>, meta description, canonical link, Open Graph (og:type, url, title, description, image + alt, site_name, locale) and X/Twitter card tags. All values are HTML-escaped. Also checks lengths (title 30–60, description 70–160 characters), URLs and the @handle, and shows how Google, Facebook and X would truncate the title and description. URLs without a scheme get https://. Optionally writes the tags to an .html file. Web version: https://s3eidat.github.io/meta-tags/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `title` | string | no (default `""`) | Page title (aim for 30–60 characters). |
| `description` | string | no (default `""`) | Meta description (aim for 70–160 characters). |
| `url` | string | no (default `""`) | Canonical page URL. |
| `image` | string | no (default `""`) | Share image URL (1200×630 recommended). |
| `imageAlt` | string | no (default `""`) | Alt text for the share image. |
| `siteName` | string | no (default `""`) | Site name (og:site_name). |
| `twitter` | string | no (default `""`) | X/Twitter handle: "@name", "name" or a profile URL. |
| `type` | string | no (default `"website"`) | og:type, e.g. website, article, product, profile. Default website. |
| `card` | "summary_large_image" \| "summary" | no (default `"summary_large_image"`) | X card type. Default summary_large_image. |
| `locale` | string | no (default `""`) | og:locale, e.g. en_US or ar_AR. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |

**Output**

| Field | Type | Description |
|---|---|---|
| `html` | string |  |
| `path` | string |  |
| `problems` | number | How many fields have a blocking problem (level "bad"). |
| `checks` | object |  |
| `previews` | object |  |

**Example**

```json
{
  "title": "Meta Tags: free SEO and social preview generator",
  "description": "Create meta, Open Graph and X card tags with live Google, Facebook and X previews. Free, private and it runs in your browser.",
  "url": "https://s3eidat.github.io/meta-tags/",
  "image": "https://s3eidat.github.io/meta-tags/screenshot.png",
  "siteName": "S3eidat Tools",
  "twitter": "@s3eidat",
  "locale": "en_US"
}
```

Result:

```json
{
  "html": "<title>Meta Tags: free SEO and social preview generator</title>\n<meta name=\"description\" content=\"Create meta, Open Graph and X card tags with live Google, Facebook and X previews. Free, private and it runs in your browser.\">\n<link rel=\"canonical\" href=\"https://s3eidat.github.io/meta-tags/\">\n\n<!-- Open Graph (Facebook, LinkedIn, WhatsApp, Slack…) -->\n<meta property=\"og:type\" content=\"website\">\n<meta property=\"og:url\" content=\"https://s3eidat.github.io/meta-tags/\">\n<meta property=\"og:title\" content=\"Meta Tags: free SEO and social preview generator\">\n<meta property=\"og:description\" content=\"Crea…",
  "problems": 0,
  "checks": {
    "title": {
      "level": "ok",
      "message": "48 / 60"
    },
    "description": {
      "level": "ok",
      "message": "125 / 160"
    },
    "url": {
      "level": "ok",
      "message": "valid"
    },
    "image": {
      "level": "ok",
      "message": "valid"
    },
    "twitter": {
      "level": "ok",
      "message": "@s3eidat"
    }
  },
  "previews": {
    "google": {
      "site": "S3eidat Tools",
      "breadcrumb": "s3eidat.github.io › meta-tags",
      "title": "Meta Tags: free SEO and social preview generator",
      "description": "Create meta, Open Graph and X card tags with live Google, Facebook and X previews. Free, private and it runs in your browser."
    },
    "facebook": {
      "domain": "S3EIDAT.GITHUB.IO",
      "title": "Meta Tags: free SEO and social preview generator",
      "description": "Create meta, Open Graph and X card tags with live Google, Facebook and X previews. Free, private and it runs…"
    },
    "x": {
      "card": "summary_large_image",
      "domain": "s3eidat.github.io",
      "title": "Meta Tags: free SEO and social preview generator",
      "description": "Create meta, Open Graph and X card tags with live Google, Facebook and X previews. Free, private and it runs in your browser."
    }
  }
}
```

<a id="regex_test"></a>
### `regex_test` — Regex test

Run a JavaScript regular expression against text: every match with its index, length, numbered and named capture groups, plus an optional replace preview ($1, $<name>, $&, $`, $', $$). Flags g i m s u y (and d, v). Runs in an isolated worker with a timeout (default 1.5 s) so catastrophic backtracking is reported instead of hanging. Lists up to 5000 matches. Web version: https://s3eidat.github.io/regex-playground/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `pattern` | string | yes | The regex source, without slashes, e.g. "(\\d+)-(?<w>[a-z]+)". |
| `flags` | string | no (default `"g"`) | Regex flags, default "g". |
| `text` | string | yes | The text to search. |
| `replace` | string | no | Replacement string for a replace preview (JavaScript String.replace syntax). |
| `limit` | integer | no (default `500`) | Max matches to include in the result (count is still total). Default 500. |
| `timeoutMs` | integer | no (default `1500`) | Kill the regex after this many ms. Default 1500. |

**Output**

| Field | Type | Description |
|---|---|---|
| `count` | number | Number of matches (capped at 5000; see "more"). |
| `more` | boolean | True when there were more than 5000 matches. |
| `groupNames` | (string \| null)[] | Name of each capture group in order (null = unnamed). |
| `matches` | object[] |  |
| `replaced` | string |  |

**Example** — 2 matches (index 0 and 5), replaced = "ab:1 cd:22 x"

```json
{
  "pattern": "(\\d+)-(?<w>[a-z]+)",
  "flags": "g",
  "text": "1-ab 22-cd x",
  "replace": "$<w>:$1"
}
```

Result:

```json
{
  "count": 2,
  "more": false,
  "groupNames": [
    null,
    "w"
  ],
  "matches": [
    {
      "index": 0,
      "length": 4,
      "text": "1-ab",
      "groups": [
        "1",
        "ab"
      ],
      "named": {
        "w": "ab"
      }
    },
    {
      "index": 5,
      "length": 5,
      "text": "22-cd",
      "groups": [
        "22",
        "cd"
      ],
      "named": {
        "w": "cd"
      }
    }
  ],
  "replaced": "ab:1 cd:22 x"
}
```

<a id="subnet_calc"></a>
### `subnet_calc` — Subnet calculator (IPv4 / IPv6)

IPv4: from "192.168.1.0/26", "10.0.0.5 255.255.255.0" or a wildcard mask, get network, broadcast, first/last host, usable and total addresses, netmask, wildcard, class, address type (private, CGNAT, loopback, documentation…), integer/hex/binary forms; optionally split into N equal subnets or allocate VLSM blocks by host counts ("Sales:50, Ops:20"). /31 and /32 follow RFC 3021. IPv6: compressed (RFC 5952) and expanded forms, network, first/last address, address count, number of /64s, reverse DNS name and type. Web version: https://s3eidat.github.io/subnet-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes | IPv4 or IPv6 address, optionally with /prefix (or an IPv4 dotted mask after a space or slash). |
| `prefix` | integer | no | Prefix length to use when "address" has none (default /24 for IPv4, /128 for IPv6), or to override it. |
| `split` | integer | no | IPv4: split the network into at least this many equal subnets (rounded up to a power of two; first 4096 listed). |
| `vlsm` | string | no | IPv4: VLSM host requirements, e.g. "50, 20, 10" or "Sales:50, Ops:20". Largest first, each block aligned. |

**Output**

| Field | Type | Description |
|---|---|---|
| `version` | number | 4 or 6. |
| `type` | string | Address type, e.g. "Private (RFC 1918)" or "Documentation". |
| `prefix` | number |  |
| `network` | string |  |
| `broadcast` | string \| null |  |
| `firstHost` | string |  |
| `lastHost` | string |  |
| `usableHosts` | number |  |
| `totalAddresses` | number |  |
| `subnetMask` | string |  |
| `wildcardMask` | string |  |
| `compressed` | string |  |
| `expanded` | string |  |
| `firstAddress` | string |  |
| `lastAddress` | string |  |
| `addresses` | string |  |
| `split` | object |  |
| `vlsm` | object |  |

**Example** — broadcast 192.168.1.63, hosts 192.168.1.1–192.168.1.62, 62 usable, mask 255.255.255.192

```json
{
  "address": "192.168.1.0/26"
}
```

Result:

```json
{
  "version": 4,
  "address": "192.168.1.0",
  "prefix": 26,
  "network": "192.168.1.0/26",
  "broadcast": "192.168.1.63",
  "firstHost": "192.168.1.1",
  "lastHost": "192.168.1.62",
  "usableHosts": 62,
  "totalAddresses": 64,
  "subnetMask": "255.255.255.192",
  "wildcardMask": "0.0.0.63",
  "class": "C",
  "type": "Private (RFC 1918)",
  "isPublic": false,
  "integer": 3232235776,
  "hex": "0xC0A80100",
  "binary": {
    "address": "11000000.10101000.00000001.00000000",
    "mask": "11111111.11111111.11111111.11000000",
    "network": "11000000.10101000.00000001.00000000",
    "broadcast": "11000000.10101000.00000001.00111111"
  },
  "summary": "Address:      192.168.1.0/26\nNetwork:      192.168.1.0/26\nNetmask:      255.255.255.192\nWildcard:     0.0.0.63\nBroadcast:    192.168.1.63\nHost range:   192.168.1.1 - 192.168.1.62\nUsable hosts: 62\nTotal:        64\nType:         Private (RFC 1918)"
}
```

**Example** — 192.168.1.0/26, .64/27, .96/28, .112/30

```json
{
  "address": "192.168.1.0/24",
  "vlsm": "2, 50, 10, 20"
}
```

Result:

```json
{
  "version": 4,
  "address": "192.168.1.0",
  "prefix": 24,
  "network": "192.168.1.0/24",
  "broadcast": "192.168.1.255",
  "firstHost": "192.168.1.1",
  "lastHost": "192.168.1.254",
  "usableHosts": 254,
  "totalAddresses": 256,
  "subnetMask": "255.255.255.0",
  "wildcardMask": "0.0.0.255",
  "class": "C",
  "type": "Private (RFC 1918)",
  "isPublic": false,
  "integer": 3232235776,
  "hex": "0xC0A80100",
  "binary": {
    "address": "11000000.10101000.00000001.00000000",
    "mask": "11111111.11111111.11111111.00000000",
    "network": "11000000.10101000.00000001.00000000",
    "broadcast": "11000000.10101000.00000001.11111111"
  },
  "summary": "Address:      192.168.1.0/24\nNetwork:      192.168.1.0/24\nNetmask:      255.255.255.0\nWildcard:     0.0.0.255\nBroadcast:    192.168.1.255\nHost range:   192.168.1.1 - 192.168.1.254\nUsable hosts: 254\nTotal:        256\nType:         Private (RFC 1918)",
  "vlsm": {
    "used": 116,
    "free": 140,
    "subnets": [
      {
        "name": "Subnet 2",
        "needed": 50,
        "network": "192.168.1.0/26",
        "firstHost": "192.168.1.1",
        "lastHost": "192.168.1.62",
        "broadcast": "192.168.1.63",
        "usableHosts": 62,
        "mask": "255.255.255.192"
      },
      {
        "name": "Subnet 4",
        "needed": 20,
        "network": "192.168.1.64/27",
        "firstHost": "192.168.1.65",
        "lastHost": "192.168.1.94",
        "broadcast": "192.168.1.95",
        "usableHosts": 30,
        "mask": "255.255.255.224"
      },
      {
        "name": "Subnet 3",
        "needed": 10,
        "network": "192.168.1.96/28",
        "firstHost": "192.168.1.97",
        "lastHost": "192.168.1.110",
        "broadcast": "192.168.1.111",
        "usableHosts": 14,
        "mask": "255.255.255.240"
      },
      {
        "name": "Subnet 1",
        "needed": 2,
        "network": "192.168.1.112/30",
        "firstHost": "192.168.1.113",
        "lastHost": "192.168.1.114",
        "broadcast": "192.168.1.115",
        "usableHosts": 2,
        "mask": "255.255.255.252"
      }
    ]
  }
}
```

**Example**

```json
{
  "address": "2001:db8:abcd::1/48"
}
```

Result:

```json
{
  "version": 6,
  "type": "Documentation",
  "compressed": "2001:db8:abcd::1",
  "expanded": "2001:0db8:abcd:0000:0000:0000:0000:0001",
  "prefix": 48,
  "network": "2001:db8:abcd::/48",
  "firstAddress": "2001:0db8:abcd:0000:0000:0000:0000:0000",
  "lastAddress": "2001:0db8:abcd:ffff:ffff:ffff:ffff:ffff",
  "addresses": "1,208,925,819,614,629,174,706,176 (2^80)",
  "subnets64": "65,536 (2^16)",
  "reverseDns": "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.c.b.a.8.b.d.0.1.0.0.2.ip6.arpa"
}
```

<a id="time_convert"></a>
### `time_convert` — Timestamp convert

Convert a Unix timestamp (seconds, ms, µs or ns — auto-detected from the number of digits, or set "unit") or a date string to ISO 8601 (UTC and in a timezone), RFC 7231 / UTC string, local time in any IANA timezone with its offset, relative time ("3 hours ago"), day of year, ISO week, and Unix seconds/ms. A wall-clock time without an offset (e.g. "2026-10-26T14:30") is read in "timezone". Also shows the same instant in extra "zones" (world clock). No value = now. Date arithmetic is not done here. Web version: https://s3eidat.github.io/timestamp-convert/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `value` | string \| number | no | A Unix timestamp (e.g. 1700000000 or "1700000000123"), an ISO/RFC date string, or a wall-clock "YYYY-MM-DDTHH:MM[:SS]". Default: now. |
| `unit` | "auto" \| "s" \| "ms" \| "us" \| "ns" | no (default `"auto"`) | Unit of a numeric timestamp. Default: auto-detect. |
| `timezone` | string | no | IANA timezone for the local view and for wall-clock input. Default: the server's (Asia/Amman). |
| `zones` | string[] | no | Extra IANA timezones to show the same instant in, e.g. ["UTC","Asia/Tokyo"]. |
| `now` | number | no | Reference "now" in Unix ms for the relative time. Default: the current time. |

**Output**

| Field | Type | Description |
|---|---|---|
| `input` | string |  |
| `detectedUnit` | string |  |
| `unixSeconds` | number |  |
| `unixMilliseconds` | number |  |
| `iso` | string |  |
| `utc` | string |  |
| `timezone` | string |  |
| `local` | string |  |
| `localIso` | string |  |
| `offset` | string |  |
| `relative` | string |  |
| `dayOfYear` | number |  |
| `isoWeek` | number |  |
| `zones` | object[] |  |

**Example** — iso 2023-11-14T22:13:20.000Z, localIso 2023-11-15T01:13:20+03:00, detectedUnit seconds

```json
{
  "value": 1700000000,
  "timezone": "Asia/Amman"
}
```

Result:

```json
{
  "input": "1700000000",
  "unixSeconds": 1700000000,
  "unixMilliseconds": 1700000000000,
  "iso": "2023-11-14T22:13:20.000Z",
  "utc": "Tue, 14 Nov 2023 22:13:20 GMT",
  "timezone": "Asia/Amman",
  "local": "Wed, Nov 15, 2023, 01:13:20 AM",
  "localIso": "2023-11-15T01:13:20+03:00",
  "offset": "UTC+03:00",
  "relative": "2 years ago",
  "dayOfYear": 319,
  "isoWeek": 46,
  "detectedUnit": "seconds"
}
```

**Example** — unixSeconds 1782864000 (2026-07-01T00:00:00Z)

```json
{
  "value": "2026-07-01T09:00",
  "timezone": "Asia/Tokyo"
}
```

Result:

```json
{
  "input": "2026-07-01T09:00",
  "unixSeconds": 1782864000,
  "unixMilliseconds": 1782864000000,
  "iso": "2026-07-01T00:00:00.000Z",
  "utc": "Wed, 01 Jul 2026 00:00:00 GMT",
  "timezone": "Asia/Tokyo",
  "local": "Wed, Jul 1, 2026, 09:00:00 AM",
  "localIso": "2026-07-01T09:00:00+09:00",
  "offset": "UTC+09:00",
  "relative": "in 5 months",
  "dayOfYear": 182,
  "isoWeek": 27
}
```

<a id="url_decode"></a>
### `url_decode` — URL decode

Decode percent-encoded text (decodeURIComponent, or decodeURI with mode "full"). Optionally turn "+" into spaces (form encoding). Errors on malformed %-sequences. Web version: https://s3eidat.github.io/url-toolkit/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | yes | Percent-encoded text. |
| `mode` | "component" \| "full" | no (default `"component"`) | "component" = encodeURIComponent (for one query value or path segment; escapes / ? & = #). "full" = encodeURI (a whole URL; keeps those). |
| `plusAsSpace` | boolean | no (default `false`) | Treat "+" as a space (application/x-www-form-urlencoded). |

**Output**

| Field | Type | Description |
|---|---|---|
| `result` | string |  |

**Example**

```json
{
  "text": "x+y%20%E2%9C%93",
  "plusAsSpace": true
}
```

Result:

```json
{
  "result": "x y ✓"
}
```

<a id="url_encode"></a>
### `url_encode` — URL encode

Percent-encode text for URLs, exactly like JavaScript encodeURIComponent ("component", default) or encodeURI ("full"). UTF-8 based. Web version: https://s3eidat.github.io/url-toolkit/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | yes | Text to encode. |
| `mode` | "component" \| "full" | no (default `"component"`) | "component" = encodeURIComponent (for one query value or path segment; escapes / ? & = #). "full" = encodeURI (a whole URL; keeps those). |

**Output**

| Field | Type | Description |
|---|---|---|
| `result` | string |  |

**Example**

```json
{
  "text": "a b&c/d?é"
}
```

Result:

```json
{
  "result": "a%20b%26c%2Fd%3F%C3%A9"
}
```

<a id="url_parse"></a>
### `url_parse` — URL parse

Split a URL into protocol, user, host, port, path, query, fragment and origin (with decoded forms), list its query parameters in order (duplicates and bare flags kept, + as space) and flag tracking parameters (utm_*, fbclid, gclid, msclkid, mc_cid, _ga, _hsenc…). Optionally returns the URL with tracking removed. A missing scheme means https://. Passwords are never echoed. Web version: https://s3eidat.github.io/url-toolkit/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `url` | string | yes | The URL to parse. |
| `stripTracking` | boolean | no (default `false`) | Also return "cleanUrl" with tracking parameters removed. |

**Output**

| Field | Type | Description |
|---|---|---|
| `parts` | object |  |
| `params` | object[] |  |
| `trackingCount` | number |  |
| `cleanUrl` | string |  |
| `removed` | number |  |

**Example** — cleanUrl = https://example.com/shop/item?id=42#reviews, removed = 2

```json
{
  "url": "https://example.com/shop/item?id=42&utm_source=newsletter&fbclid=IwAR0xYz#reviews",
  "stripTracking": true
}
```

Result:

```json
{
  "parts": {
    "href": "https://example.com/shop/item?id=42&utm_source=newsletter&fbclid=IwAR0xYz#reviews",
    "protocol": "https",
    "username": "",
    "hasPassword": false,
    "host": "example.com",
    "port": "",
    "defaultPort": "443",
    "path": "/shop/item",
    "query": "id=42&utm_source=newsletter&fbclid=IwAR0xYz",
    "fragment": "reviews",
    "origin": "https://example.com"
  },
  "params": [
    {
      "key": "id",
      "value": "42",
      "tracking": false
    },
    {
      "key": "utm_source",
      "value": "newsletter",
      "tracking": true
    },
    {
      "key": "fbclid",
      "value": "IwAR0xYz",
      "tracking": true
    }
  ],
  "trackingCount": 2,
  "cleanUrl": "https://example.com/shop/item?id=42#reviews",
  "removed": 2
}
```

<a id="useragent_parse"></a>
### `useragent_parse` — User agent parse

Parse browser user-agent strings into browser + version, engine (Blink, WebKit, Gecko, Trident, EdgeHTML, Presto), OS + version, device type (desktop, mobile, tablet, TV, console, bot, HTTP client), vendor and model. Recognises ~50 crawlers and AI bots (Googlebot, Bingbot, GPTBot, ClaudeBot, PerplexityBot…) and HTTP clients (curl, python-requests, Go, okhttp, Postman…). Give one "userAgent", a list, or a text/log file (one per line, or the last quoted field of each access-log line); up to 5000 per call, with a count by type. Web version: https://s3eidat.github.io/ua-parser/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `userAgent` | string | no | One user-agent string. |
| `userAgents` | string[] | no | Several user-agent strings. |
| `file` | string | no | Or: a text file (path or base64) with one user agent per line, or an access log. |
| `accessLog` | boolean | no (default `false`) | Read "file" as an access log: take the last quoted field of each line. |

**Output**

| Field | Type | Description |
|---|---|---|
| `count` | number |  |
| `byType` | object |  |
| `results` | object[] |  |

**Example** — Mobile Safari 17.4, WebKit, iOS 17.4, mobile, Apple iPhone

```json
{
  "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1"
}
```

Result:

```json
{
  "count": 1,
  "byType": {
    "mobile": 1
  },
  "results": [
    {
      "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1",
      "browser": "Mobile Safari",
      "browserVersion": "17.4",
      "engine": "WebKit",
      "engineVersion": "605.1.15",
      "os": "iOS",
      "osVersion": "17.4",
      "type": "mobile",
      "vendor": "Apple",
      "model": "iPhone",
      "bot": "",
      "client": "",
      "typeLabel": "Mobile",
      "summary": {
        "browser": "Mobile Safari 17.4",
        "engine": "WebKit 605.1.15",
        "os": "iOS 17.4",
        "device": "Apple iPhone"
      }
    }
  ]
}
```

<a id="calc"></a>
## Calculators

Loans, interest, percentages, salary, GPA, zakat, BMI, dates, units, Hijri, maths.

| Tool | What it does |
|---|---|
| [`calc_age`](#calc_age) | Age calculator |
| [`calc_bmi`](#calc_bmi) | BMI calculator |
| [`calc_cash_count`](#calc_cash_count) | Cash counter |
| [`calc_compound_interest`](#calc_compound_interest) | Compound interest / savings growth |
| [`calc_date_diff`](#calc_date_diff) | Date difference / add days |
| [`calc_discount`](#calc_discount) | Discount / sale price calculator |
| [`calc_due_date`](#calc_due_date) | Pregnancy due date |
| [`calc_equation`](#calc_equation) | Equation solver |
| [`calc_expression`](#calc_expression) | Scientific calculator |
| [`calc_fraction`](#calc_fraction) | Fraction calculator |
| [`calc_fuel_cost`](#calc_fuel_cost) | Fuel cost of a trip |
| [`calc_gpa`](#calc_gpa) | GPA calculator |
| [`calc_hijri`](#calc_hijri) | Hijri ⇄ Gregorian converter |
| [`calc_hours`](#calc_hours) | Work hours / time card |
| [`calc_loan`](#calc_loan) | Loan / mortgage calculator |
| [`calc_percentage`](#calc_percentage) | Percentage calculator |
| [`calc_primes`](#calc_primes) | Prime number tools |
| [`calc_roman`](#calc_roman) | Roman numerals |
| [`calc_salary`](#calc_salary) | Salary / pay period converter |
| [`calc_sleep`](#calc_sleep) | Sleep cycle calculator |
| [`calc_split_bill`](#calc_split_bill) | Split a bill |
| [`calc_stats`](#calc_stats) | Descriptive statistics |
| [`calc_unit_convert`](#calc_unit_convert) | Unit converter |
| [`calc_zakat`](#calc_zakat) | Zakat calculator |

<a id="calc_age"></a>
### `calc_age` — Age calculator

Exact age in years, months and days from a date of birth (on today or any "on" date), with totals in months, weeks, days and hours, the weekday of birth, Hijri (Umm al-Qura) birth date and Hijri age, next birthday (Feb 29 birthdays fall on Feb 28 in common years), star sign, Chinese zodiac, the next 1,000-day milestone, and optionally the age gap to a second person. Dates are ISO YYYY-MM-DD. Web version: https://s3eidat.github.io/age-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `birthDate` | string | yes | Date of birth, YYYY-MM-DD. |
| `onDate` | string | no | Work out the age on this date (YYYY-MM-DD). Default: today. |
| `otherBirthDate` | string | no | Optional second person's date of birth, to compare. |
| `otherName` | string | no | Name of the second person (default "They"). |

**Output**

| Field | Type | Description |
|---|---|---|
| `years` | number |  |
| `months` | number |  |
| `days` | number |  |
| `text` | string |  |
| `totalMonths` | number |  |
| `totalWeeks` | number |  |
| `weeksRemainderDays` | number |  |
| `totalDays` | number |  |
| `approxHours` | number |  |
| `bornOn` | object |  |
| `hijri` | object |  |
| `birthdayToday` | boolean |  |
| `nextBirthday` | object |  |
| `starSign` | string |  |
| `chineseZodiac` | string |  |
| `nextMilestone` | object |  |
| `comparison` | string |  |
| `summary` | string |  |

**Example** — 32 years, 5 months, 11 days

```json
{
  "birthDate": "1994-05-15",
  "onDate": "2026-10-26"
}
```

Result:

```json
{
  "years": 32,
  "months": 5,
  "days": 11,
  "text": "32 years, 5 months, 11 days",
  "totalMonths": 389,
  "totalWeeks": 1693,
  "weeksRemainderDays": 1,
  "totalDays": 11852,
  "approxHours": 284448,
  "bornOn": {
    "date": "1994-05-15",
    "weekday": "Sunday",
    "long": "15 May 1994"
  },
  "hijri": {
    "birth": "Dhuʻl-Hijjah 4, 1414 AH",
    "birthArabic": "٤ ذو الحجة ١٤١٤ هـ",
    "age": 33
  },
  "birthdayToday": false,
  "nextBirthday": {
    "date": "2027-05-15",
    "weekday": "Saturday",
    "inDays": 201,
    "turning": 33
  },
  "starSign": "Taurus ♉",
  "chineseZodiac": "Dog",
  "nextMilestone": {
    "days": 12000,
    "date": "2027-03-23",
    "weekday": "Tuesday",
    "inDays": 148
  },
  "summary": "Age: 32 years, 5 months, 11 days (on 26 October 2026)\nBorn: Sunday, 15 May 1994 (Dhuʻl-Hijjah 4, 1414 AH)\nTotal: 11,852 days, 1,693 weeks\nNext birthday: 15 May 2027 (Saturday), in 201 days"
}
```

<a id="calc_bmi"></a>
### `calc_bmi` — BMI calculator

Body mass index with the WHO adult category, the healthy weight range for your height (BMI 18.5–24.9), BMI Prime, optional waist-to-height ratio, and for adults (18–120, with age) BMR by Mifflin-St Jeor plus daily calories to maintain or lose ~0.5 kg (1 lb) a week at five activity levels. Metric (cm, kg) or imperial (ft + in, lb). Height 50–272 cm, weight 10–650 kg. Not medical advice; children need percentile charts. Web version: https://s3eidat.github.io/bmi-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `units` | "metric" \| "imperial" | no (default `"metric"`) | metric: cm and kg; imperial: ft + in and lb. |
| `heightCm` | number \| string | no | Height in cm (metric). |
| `heightFt` | number \| string | no | Height, feet part (imperial). |
| `heightIn` | number \| string | no | Height, inches part (imperial). |
| `weight` | number \| string | no | Weight in kg (metric) or lb (imperial). |
| `age` | number \| string | no | Age in years (adults 18+ get BMR and calories). |
| `sex` | "m" \| "f" | no (default `"m"`) | Sex, for BMR (m or f). |
| `waist` | number \| string | no | Optional waist size in cm (metric) or inches (imperial). |

**Output**

| Field | Type | Description |
|---|---|---|
| `bmi` | number |  |
| `category` | string |  |
| `categoryClass` | number |  |
| `healthyRange` | object |  |
| `toHealthyRange` | string |  |
| `bmiPrime` | number |  |
| `waistToHeight` | object |  |
| `bmr` | number |  |
| `calories` | object[] |  |
| `note` | string |  |
| `summary` | string |  |

**Example** — BMI 22.9 Healthy weight, BMR 1649 kcal

```json
{
  "heightCm": 175,
  "weight": 70,
  "age": 30,
  "sex": "m"
}
```

Result:

```json
{
  "bmi": 22.9,
  "category": "Healthy weight",
  "categoryClass": 1,
  "healthyRange": {
    "min": 56.7,
    "max": 76.3,
    "unit": "kg"
  },
  "toHealthyRange": "you are in range",
  "bmiPrime": 0.91,
  "bmr": 1649,
  "calories": [
    {
      "activity": "Sedentary",
      "factor": 1.2,
      "maintain": 1979,
      "lose": 1500
    },
    {
      "activity": "Light",
      "factor": 1.375,
      "maintain": 2267,
      "lose": 1767
    },
    {
      "activity": "Moderate",
      "factor": 1.55,
      "maintain": 2556,
      "lose": 2056
    },
    {
      "activity": "Active",
      "factor": 1.725,
      "maintain": 2844,
      "lose": 2344
    },
    {
      "activity": "Very active",
      "factor": 1.9,
      "maintain": 3133,
      "lose": 2633
    }
  ],
  "summary": "BMI 22.9 (Healthy weight)\nHealthy weight for 175 cm: 56.7 kg – 76.3 kg\nBMR: 1,649 kcal/day"
}
```

<a id="calc_cash_count"></a>
### `calc_cash_count` — Cash counter

Count a cash drawer by denomination with exact integer maths (no float errors): JOD (3 decimals, fils), USD, EUR, SAR, AED, GBP, KWD, EGP, or a custom currency (your own note and coin values, 0/2/3 decimals). Give how many of each note/coin (by value like 50 or 0.25, by label like "50 JD" or "¼ dinar"); returns per-denomination amounts, the total, notes and coins counted, and over/short against an expected amount. list=true shows the denominations of a currency. Web version: https://s3eidat.github.io/cash-counter/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `currency` | "JOD" \| "USD" \| "EUR" \| "SAR" \| "AED" \| "GBP" \| "KWD" \| "EGP" \| "CUSTOM" | no (default `"JOD"`) | Currency preset, or CUSTOM. |
| `custom` | object | no | CUSTOM currency definition. |
| `counts` | object[] | no (default `[]`) | What you counted. |
| `expected` | number \| string | no | Expected total, to show over/short. |
| `note` | string | no | Optional note for the summary (e.g. "Evening shift"). |
| `list` | boolean | no (default `false`) | Just list the denominations. |

**Output**

| Field | Type | Description |
|---|---|---|
| `currency` | string |  |
| `name` | string |  |
| `symbol` | string |  |
| `decimals` | number |  |
| `denominations` | object[] |  |
| `total` | string |  |
| `totalMinorUnits` | number |  |
| `notes` | number |  |
| `coins` | number |  |
| `expected` | object |  |
| `summary` | string |  |

**Example** — total 151.350 JD, short by 0.050

```json
{
  "currency": "JOD",
  "counts": [
    {
      "denomination": 50,
      "count": 3
    },
    {
      "denomination": 0.5,
      "count": 2
    },
    {
      "denomination": 0.05,
      "count": 7
    }
  ],
  "expected": "151.400"
}
```

Result:

```json
{
  "currency": "JOD",
  "name": "Jordanian dinar",
  "symbol": "JD",
  "decimals": 3,
  "denominations": [
    {
      "key": "n50000",
      "label": "50 JD",
      "labelArabic": "٥٠ ديناراً",
      "kind": "note",
      "value": "50.000",
      "count": 3,
      "amount": "150.000"
    },
    {
      "key": "n20000",
      "label": "20 JD",
      "labelArabic": "٢٠ ديناراً",
      "kind": "note",
      "value": "20.000",
      "count": 0,
      "amount": "0.000"
    },
    {
      "key": "n10000",
      "label": "10 JD",
      "labelArabic": "١٠ دنانير",
      "kind": "note",
      "value": "10.000",
      "count": 0,
      "amount": "0.000"
    },
    {
      "key": "n5000",
      "label": "5 JD",
      "labelArabic": "٥ دنانير",
      "kind": "note",
      "value": "5.000",
      "count": 0,
      "amount": "0.000"
    },
    {
      "key": "n1000",
      "label": "1 JD",
      "labelArabic": "دينار",
      "kind": "note",
      "value": "1.000",
      "count": 0,
      "amount": "0.000"
    },
    {
      "key": "c500",
      "label": "½ dinar",
      "labelArabic": "نصف دينار",
      "kind": "coin",
      "value": "0.500",
      "count": 2,
      "amount": "1.000"
    },
    {
      "key": "c250",
      "label": "¼ dinar",
      "labelArabic": "ربع دينار",
      "kind": "coin",
      "value": "0.250",
      "count": 0,
      "amount": "0.000"
    },
    {
      "key": "c100",
      "label": "10 piasters",
      "labelArabic": "١٠ قروش",
      "kind": "coin",
      "value": "0.100",
      "count": 0,
      "amount": "0.000"
    },
    {
      "key": "c50",
      "label": "5 piasters",
      "labelArabic": "٥ قروش",
      "kind": "coin",
      "value": "0.050",
      "count": 7,
      "amount": "0.350"
    },
    {
      "key": "c10",
      "label": "1 piaster",
      "labelArabic": "قرش",
      "kind": "coin",
      "value": "0.010",
      "count": 0,
      "amount": "0.000"
    }
  ],
  "total": "151.350",
  "totalMinorUnits": 151350,
  "notes": 3,
  "coins": 9,
  "expected": {
    "amount": "151.400",
    "status": "short",
    "difference": "0.050"
  },
  "summary": "Cash count · Jordanian dinar (JOD)\n\n50 JD × 3 = 150.000\n½ dinar × 2 = 1.000\n5 piasters × 7 = 0.350\n\nTotal: 151.350 JD (3 notes, 9 coins)\nExpected: 151.400 JD · short by 0.050"
}
```

<a id="calc_compound_interest"></a>
### `calc_compound_interest` — Compound interest / savings growth

Project savings with compound interest: an initial deposit plus monthly contributions (at the start or end of each month), a nominal annual rate compounded yearly, half-yearly, quarterly, monthly, daily or continuously, optionally adjusted for inflation ("today's money"). Returns final balance, total deposits, interest earned and a year-by-year table. Rate -50–100 %, 1 month to 100 years. Web version: https://s3eidat.github.io/compound-interest/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `initial` | number \| string | no | Initial deposit (default 0). |
| `monthly` | number \| string | no | Monthly contribution (default 0). |
| `annualRate` | number \| string | yes | Nominal annual interest rate in percent, e.g. 7. |
| `years` | number \| string | yes | Duration in years (fractions allowed, e.g. 1.5; at least one month). |
| `compounding` | "yearly" \| "half-yearly" \| "quarterly" \| "monthly" \| "daily" \| "continuously" | no (default `"monthly"`) | How often interest compounds. |
| `timing` | "end" \| "start" | no (default `"end"`) | Contributions at the end (ordinary annuity) or start (annuity due) of each month. |
| `inflation` | number \| string | no | Optional yearly inflation in percent, to show the balance in today's money. |
| `table` | boolean | no (default `true`) | Include the year-by-year table. |

**Output**

| Field | Type | Description |
|---|---|---|
| `finalBalance` | number |  |
| `totalDeposits` | number |  |
| `interestEarned` | number |  |
| `growthMultiple` | number \| null |  |
| `todaysMoney` | number |  |
| `rows` | object[] |  |
| `summary` | string |  |

**Example** — finalBalance 16288.95

```json
{
  "initial": 10000,
  "annualRate": 5,
  "years": 10,
  "compounding": "yearly"
}
```

Result:

```json
{
  "finalBalance": 16288.95,
  "totalDeposits": 10000,
  "interestEarned": 6288.95,
  "growthMultiple": 1.63,
  "rows": [
    {
      "year": 1,
      "months": 12,
      "deposits": 0,
      "interest": 500,
      "totalDeposits": 10000,
      "totalInterest": 500,
      "balance": 10500
    },
    {
      "year": 2,
      "months": 24,
      "deposits": 0,
      "interest": 525,
      "totalDeposits": 10000,
      "totalInterest": 1025,
      "balance": 11025
    },
    {
      "year": 3,
      "months": 36,
      "deposits": 0,
      "interest": 551.25,
      "totalDeposits": 10000,
      "totalInterest": 1576.25,
      "balance": 11576.25
    },
    {
      "year": 4,
      "months": 48,
      "deposits": 0,
      "interest": 578.81,
      "totalDeposits": 10000,
      "totalInterest": 2155.06,
      "balance": 12155.06
    },
    {
      "year": 5,
      "months": 60,
      "deposits": 0,
      "interest": 607.75,
      "totalDeposits": 10000,
      "totalInterest": 2762.82,
      "balance": 12762.82
    },
    {
      "year": 6,
      "months": 72,
      "deposits": 0,
      "interest": 638.14,
      "totalDeposits": 10000,
      "totalInterest": 3400.96,
      "balance": 13400.96
    },
    {
      "year": 7,
      "months": 84,
      "deposits": 0,
      "interest": 670.05,
      "totalDeposits": 10000,
      "totalInterest": 4071,
      "balance": 14071
    },
    {
      "year": 8,
      "months": 96,
      "deposits": 0,
      "interest": 703.55,
      "totalDeposits": 10000,
      "totalInterest": 4774.55,
      "balance": 14774.55
    },
    {
      "year": 9,
      "months": 108,
      "deposits": 0,
      "interest": 738.73,
      "totalDeposits": 10000,
      "totalInterest": 5513.28,
      "balance": 15513.28
    },
    {
      "year": 10,
      "months": 120,
      "deposits": 0,
      "interest": 775.66,
      "totalDeposits": 10000,
      "totalInterest": 6288.95,
      "balance": 16288.95
    }
  ],
  "summary": "Initial 10,000.00 + 0.00/month at 5% (compounded yearly) for 10 years\nFinal balance: 16,288.95\nYour deposits: 10,000.00\nInterest earned: 6,288.95"
}
```

<a id="calc_date_diff"></a>
### `calc_date_diff` — Date difference / add days

Two date jobs. difference: days between two dates (optionally counting the end day), as years/months/days, weeks, whole months, working days and weekend days for a chosen weekend (Fri–Sat default, Sat–Sun, Fri only, Sun only; public holidays are not counted). add: add or subtract days, business days, weeks, months or years to a date (month ends clamp: Jan 31 + 1 month = Feb 28/29). Results include the weekday and Umm al-Qura Hijri date. Dates are ISO YYYY-MM-DD; for Hijri ⇄ Gregorian conversion use calc_hijri. Web version: https://s3eidat.github.io/date-diff/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "difference" \| "add" | no (default `"difference"`) | difference between two dates, or add/subtract an amount. |
| `start` | string | no | difference: start date YYYY-MM-DD (default today). |
| `end` | string | no | difference: end date YYYY-MM-DD (default today). |
| `inclusive` | boolean | no (default `false`) | difference: count the end date too. |
| `weekend` | "fri-sat" \| "sat-sun" \| "fri" \| "sun" | no (default `"fri-sat"`) | Weekend days skipped by working-day counts. |
| `date` | string | no | add: the starting date YYYY-MM-DD (default today). |
| `amount` | integer | no | add: how many units to add (use subtract to go back). |
| `unit` | "days" \| "business_days" \| "weeks" \| "months" \| "years" | no (default `"days"`) | add: unit of amount. |
| `subtract` | boolean | no (default `false`) | add: go back in time instead. |

**Output**

| Field | Type | Description |
|---|---|---|
| `mode` | string |  |
| `days` | number |  |
| `years` | number |  |
| `months` | number |  |
| `remainderDays` | number |  |
| `text` | string |  |
| `weeks` | number |  |
| `weeksRemainderDays` | number |  |
| `wholeMonths` | number |  |
| `workingDays` | number |  |
| `weekendDays` | number |  |
| `endBeforeStart` | boolean |  |
| `result` | object |  |
| `calendarDays` | number |  |
| `note` | string |  |
| `summary` | string |  |

**Example** — 5 days, 5 working days

```json
{
  "start": "2026-10-26",
  "end": "2026-10-31",
  "weekend": "sat-sun"
}
```

Result:

```json
{
  "mode": "difference",
  "days": 5,
  "years": 0,
  "months": 0,
  "remainderDays": 5,
  "text": "5 days",
  "weeks": 0,
  "weeksRemainderDays": 5,
  "wholeMonths": 0,
  "workingDays": 5,
  "weekendDays": 0,
  "endBeforeStart": false,
  "summary": "From Monday, 26 October 2026 to Saturday, 31 October 2026: 5 days (5 days), 5 working days with Sat–Sun weekends"
}
```

**Example** — 2026-11-06

```json
{
  "mode": "add",
  "date": "2026-10-30",
  "amount": 5,
  "unit": "business_days",
  "weekend": "sat-sun"
}
```

Result:

```json
{
  "mode": "add",
  "result": {
    "date": "2026-11-06",
    "weekday": "Friday",
    "long": "Friday, 6 November 2026",
    "hijri": "Jumada I 26, 1448 AH",
    "hijriArabic": "٢٦ جمادى الأولى ١٤٤٨ هـ"
  },
  "calendarDays": 7,
  "note": "Skips Sat–Sun weekends; public holidays are not counted.",
  "summary": "Friday, 30 October 2026 + 5 business days = Friday, 6 November 2026 (Jumada I 26, 1448 AH)"
}
```

<a id="calc_discount"></a>
### `calc_discount` — Discount / sale price calculator

Three shop maths jobs. sale: price after one or two stacked discounts (each a percent or a fixed amount) plus optional tax/VAT, with what you save and the effective total discount (stacked percents multiply). reverse: the discount % between an old and a new price. compare: which of two deals is cheaper per unit (price and amount of each). Money is rounded to the cent at each step like a till. Web version: https://s3eidat.github.io/discount-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "sale" \| "reverse" \| "compare" | no (default `"sale"`) | Which calculator. |
| `price` | number \| string | no | sale: original price. |
| `discount` | number \| string | no | sale: first discount (percent by default). |
| `discountType` | "percent" \| "amount" | no (default `"percent"`) | sale: first discount is a percent or a fixed amount. |
| `extraDiscount` | number \| string | no | sale: optional second discount applied after the first. |
| `extraDiscountType` | "percent" \| "amount" | no (default `"percent"`) | sale: second discount type. |
| `taxPercent` | number \| string | no | sale: optional tax / VAT percent added after the discounts. |
| `was` | number \| string | no | reverse: the old price. |
| `now` | number \| string | no | reverse: the new price. |
| `unit` | string | no | compare: the unit name (kg, L, item…). Default "unit". |
| `priceA` | number \| string | no | compare: price of deal A. |
| `amountA` | number \| string | no | compare: amount of deal A (e.g. 0.5 for 500 g when unit is kg). |
| `priceB` | number \| string | no | compare: price of deal B. |
| `amountB` | number \| string | no | compare: amount of deal B. |

**Output**

| Field | Type | Description |
|---|---|---|
| `mode` | string |  |
| `sale` | object |  |
| `reverse` | object |  |
| `compare` | object |  |

**Example** — youPay 83.52 (28% off in total)

```json
{
  "price": 100,
  "discount": 20,
  "extraDiscount": 10,
  "taxPercent": 16
}
```

Result:

```json
{
  "mode": "sale",
  "sale": {
    "originalPrice": 100,
    "discount1": 20,
    "discount2": 8,
    "priceAfterDiscounts": 72,
    "tax": 11.52,
    "youPay": 83.52,
    "youSave": 28,
    "effectiveDiscountPercent": 28,
    "wasWithTax": 116,
    "summary": "Original 100.00, Discount 20% −20.00, Extra 10% −8.00, tax +11.52\nYou pay 83.52 (save 28.00, 28%)"
  }
}
```

**Example** — B is 20% cheaper per kg

```json
{
  "mode": "compare",
  "unit": "kg",
  "priceA": 4.5,
  "amountA": 0.5,
  "priceB": 7.2,
  "amountB": 1
}
```

Result:

```json
{
  "mode": "compare",
  "compare": {
    "unitPriceA": 9,
    "unitPriceB": 7.2,
    "cheaper": "B",
    "percentCheaper": 20,
    "text": "Deal B is 20% cheaper per kg (A: 9, B: 7.2 per kg)"
  }
}
```

<a id="calc_due_date"></a>
### `calc_due_date` — Pregnancy due date

Estimated pregnancy due date from the first day of the last period (Naegele's rule, adjusted for cycle length 20–45 days), the conception date, or an IVF embryo transfer date (day-3 or day-5 embryo). Also gives the estimated conception date, full-term date, how far along you are on a given day (weeks + days, trimester, days to go) and a dated timeline of common milestones (weeks 4–42), optionally with Hijri dates. Estimate only, not medical advice. Web version: https://s3eidat.github.io/due-date/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `method` | "lmp" \| "conception" \| "ivf" | no (default `"lmp"`) | lmp = first day of last period; conception; ivf = embryo transfer date. |
| `date` | string | yes | The date for that method, YYYY-MM-DD. |
| `cycleLength` | integer | no (default `28`) | lmp: average cycle length in days (default 28). |
| `ivfEmbryoDay` | 3 \| 5 | no (default `5`) | ivf: embryo age at transfer, day 3 or day 5. |
| `today` | string | no | Work out progress on this date, YYYY-MM-DD (default today). |
| `hijri` | boolean | no (default `false`) | Also give Umm al-Qura Hijri dates. |

**Output**

| Field | Type | Description |
|---|---|---|
| `dueDate` | string |  |
| `dueDateLong` | string |  |
| `dueDateHijri` | string |  |
| `dueDateHijriArabic` | string |  |
| `conception` | string |  |
| `fullTermFrom` | string |  |
| `equivalentLastPeriod` | string |  |
| `status` | string |  |
| `progress` | object |  |
| `milestones` | object[] |  |
| `method` | string |  |
| `summary` | string |  |

**Example** — due 2026-10-08, 8 weeks 3 days, 1st trimester

```json
{
  "method": "lmp",
  "date": "2026-01-01",
  "today": "2026-03-01"
}
```

Result:

```json
{
  "dueDate": "2026-10-08",
  "dueDateLong": "Thursday, 8 October 2026",
  "conception": "2026-01-15",
  "fullTermFrom": "2026-09-17",
  "equivalentLastPeriod": "2026-01-01",
  "status": "Today you are 8 weeks and 3 days (8+3).",
  "progress": {
    "weeks": 8,
    "days": 3,
    "totalDays": 59,
    "trimester": 1,
    "daysToGo": 221,
    "percent": 21
  },
  "milestones": [
    {
      "week": 4,
      "date": "2026-01-29",
      "text": "A home pregnancy test can usually turn positive around now",
      "past": true
    },
    {
      "week": 6,
      "date": "2026-02-12",
      "text": "A heartbeat can often be seen on an early ultrasound (weeks 6–7)",
      "past": true
    },
    {
      "week": 8,
      "date": "2026-02-26",
      "text": "First prenatal visit is commonly booked between weeks 8 and 10",
      "past": true
    },
    {
      "week": 11,
      "date": "2026-03-19",
      "text": "First-trimester screening window (nuchal scan, weeks 11–13+6)",
      "past": false
    },
    {
      "week": 14,
      "date": "2026-04-09",
      "text": "Second trimester begins",
      "past": false
    },
    {
      "week": 16,
      "date": "2026-04-23",
      "text": "Some people start to feel first movements (weeks 16–22)",
      "past": false
    },
    {
      "week": 18,
      "date": "2026-05-07",
      "text": "Detailed anatomy scan window (weeks 18–22)",
      "past": false
    },
    {
      "week": 24,
      "date": "2026-06-18",
      "text": "Glucose screening for gestational diabetes is often done (weeks 24–28)",
      "past": false
    },
    {
      "week": 28,
      "date": "2026-07-16",
      "text": "Third trimester begins",
      "past": false
    },
    {
      "week": 32,
      "date": "2026-08-13",
      "text": "Growth checks and more frequent visits are common from here",
      "past": false
    },
    {
      "week": 37,
      "date": "2026-09-17",
      "text": "Early term",
      "past": false
    },
    {
      "week": 39,
      "date": "2026-10-01",
      "text": "Full term",
      "past": false
    },
    {
      "week": 40,
      "date": "2026-10-08",
      "text": "Estimated due date",
      "past": false
    },
    {
      "week": 41,
      "date": "2026-10-15",
      "text": "Late term",
      "past": false
    },
    {
      "week": 42,
      "date": "2026-10-22",
      "text": "Post-term: care teams usually plan delivery before now",
      "past": false
    }
  ],
  "method": "Due date = first day of last period + 280 days, adjusted by your cycle length minus 28.",
  "summary": "Estimated due date: Thursday, 8 October 2026\nToday: 8 weeks 3 days, trimester 1\nEstimated conception: Thursday, 15 January 2026\n(Due date = first day of last period + 280 days, adjusted by your cycle length minus 28. Estimate only, not medical advice.)"
}
```

<a id="calc_equation"></a>
### `calc_equation` — Equation solver

Solve equations exactly with worked steps, like the website: linear a·x + b = c, quadratic a·x² + b·x + c = 0 (exact rational or surd roots like 1/2 + √5/2, complex roots), cubic a·x³ + b·x² + c·x + d = 0 (rational root test, else trigonometric/Cardano to 10 significant digits), and systems of 2 or 3 linear equations (Gauss–Jordan; no solution and infinitely many solutions are detected and parametrised). Coefficients may be integers, decimals, fractions ("3/4"), mixed numbers ("-1 1/2") or e-notation. Web version: https://s3eidat.github.io/equation-solver/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `type` | "linear" \| "quadratic" \| "cubic" \| "system" | yes | Equation type. |
| `coefficients` | (number \| string)[] | no | linear: [a, b, c] for a·x + b = c; quadratic: [a, b, c]; cubic: [a, b, c, d]. |
| `system` | ((number \| string)[])[] | no | system: rows of the augmented matrix, 2 rows of [a, b, rhs] (x, y) or 3 rows of [a, b, c, rhs] (x, y, z). |

**Output**

| Field | Type | Description |
|---|---|---|
| `equation` | string |  |
| `kind` | string |  |
| `result` | string[] |  |
| `roots` | object[] |  |
| `solution` | object |  |
| `parametric` | string[] |  |
| `discriminant` | string |  |
| `steps` | object[] |  |

**Example** — x = 1/2 ± √5/2

```json
{
  "type": "quadratic",
  "coefficients": [
    1,
    -1,
    -1
  ]
}
```

Result:

```json
{
  "equation": "x² − x − 1 = 0",
  "kind": "two",
  "roots": [
    {
      "text": "1/2 − √5/2 ≈ −0.6180339887",
      "re": -0.618033988749895,
      "im": 0,
      "exact": true
    },
    {
      "text": "1/2 + √5/2 ≈ 1.618033989",
      "re": 1.61803398874989,
      "im": 0,
      "exact": true
    }
  ],
  "discriminant": "5",
  "result": [
    "x₁ = 1/2 − √5/2 ≈ −0.6180339887",
    "x₂ = 1/2 + √5/2 ≈ 1.618033989",
    "Discriminant Δ = 5"
  ],
  "steps": [
    {
      "text": "Equation: x² − x − 1 = 0, with a = 1, b = −1, c = −1"
    },
    {
      "text": "Discriminant: Δ = b² − 4ac = (−1)² − 4·(1)·(−1) = 5"
    },
    {
      "text": "√Δ = √5 = √5"
    },
    {
      "text": "Quadratic formula: x = (−b ± √Δ) / 2a = 1/2 ± √5/2"
    },
    {
      "text": "Δ > 0 and not a perfect square: two irrational real roots."
    }
  ]
}
```

**Example** — x = 5, y = 3, z = −2

```json
{
  "type": "system",
  "system": [
    [
      1,
      1,
      1,
      6
    ],
    [
      0,
      2,
      5,
      -4
    ],
    [
      2,
      5,
      -1,
      27
    ]
  ]
}
```

Result:

```json
{
  "equation": "x + y + z = 6   ·   2y + 5z = −4   ·   2x + 5y − z = 27",
  "kind": "one",
  "solution": {
    "x": {
      "exact": "5",
      "value": 5
    },
    "y": {
      "exact": "3",
      "value": 3
    },
    "z": {
      "exact": "−2",
      "value": -2
    }
  },
  "result": [
    "x = 5",
    "y = 3",
    "z = −2"
  ],
  "steps": [
    {
      "text": "Augmented matrix",
      "matrix": [
        [
          "1",
          "1",
          "1",
          "6"
        ],
        [
          "0",
          "2",
          "5",
          "−4"
        ],
        [
          "2",
          "5",
          "−1",
          "27"
        ]
      ]
    },
    {
      "text": "Column x: R3 ← R3 − 2R1",
      "matrix": [
        [
          "1",
          "1",
          "1",
          "6"
        ],
        [
          "0",
          "2",
          "5",
          "−4"
        ],
        [
          "0",
          "3",
          "−3",
          "15"
        ]
      ]
    },
    {
      "text": "Column y: R2 ← R2 ÷ 2,  R1 ← R1 − R2,  R3 ← R3 − 3R2",
      "matrix": [
        [
          "1",
          "0",
          "−3/2",
          "8"
        ],
        [
          "0",
          "1",
          "5/2",
          "−2"
        ],
        [
          "0",
          "0",
          "−21/2",
          "21"
        ]
      ]
    },
    {
      "text": "Column z: R3 ← R3 ÷ (−21/2),  R1 ← R1 + (3/2)R3,  R2 ← R2 − (5/2)R3",
      "matrix": [
        [
          "1",
          "0",
          "0",
          "5"
        ],
        [
          "0",
          "1",
          "0",
          "3"
        ],
        [
          "0",
          "0",
          "1",
          "−2"
        ]
      ]
    },
    {
      "text": "Read off the solution: x = 5, y = 3, z = −2"
    },
    {
      "text": "Check: substituting back satisfies every equation ✓"
    }
  ]
}
```

<a id="calc_expression"></a>
### `calc_expression` — Scientific calculator

Evaluate a maths expression safely with the website's own parser (never eval): + − × ÷ ^ (right-associative), implicit multiplication (2(3+4), 2x), postfix ! and %, √, brackets; functions sin cos tan sec csc cot (+ asin acos atan atan2) in degrees (default), radians or grads, sinh cosh tanh and inverses, sqrt cbrt root(x,n) abs exp ln log(x[,base]) log10 log2 floor ceil round sign min max mod nCr nPr fact gamma; constants pi π e tau phi; "ans" and your own variables. Results to 12 significant digits, plain or scientific notation. Web version: https://s3eidat.github.io/sci-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `expression` | string | yes | The expression, e.g. "sin(30) + 2^3^2", "5!/(2!×3!)", "200×15%". |
| `angle` | "deg" \| "rad" \| "grad" | no (default `"deg"`) | Angle unit for trig functions. |
| `variables` | object | no | Variables to use, e.g. {"x": 2, "r": 5}. |
| `ans` | number | no | Value of "ans" (the previous answer). Default 0. |
| `format` | "norm" \| "sci" | no (default `"norm"`) | Display: normal, or always scientific notation. |

**Output**

| Field | Type | Description |
|---|---|---|
| `value` | number \| null |  |
| `display` | string |  |
| `plain` | string |  |
| `finite` | boolean |  |

**Example**

```json
{
  "expression": "sin(30) + cos(60)"
}
```

Result:

```json
{
  "value": 1,
  "display": "1"
}
```

**Example**

```json
{
  "expression": "nCr(52, 5)"
}
```

Result:

```json
{
  "value": 2598960,
  "display": "2,598,960"
}
```

<a id="calc_fraction"></a>
### `calc_fraction` — Fraction calculator

Exact fraction maths with big integers (never rounded) and worked steps. calculate: an expression with fractions, mixed numbers ("1 1/2"), decimals and repeating decimals ("0.(3)"), + − × ÷ ^ and brackets (a/b binds tighter than ÷). decimal_to_fraction: "0.75", "0.1(6)" (brackets mark the repeating part), with close small-denominator approximations. fraction_to_decimal: exact decimal with the repeating block, and percent. compare: put two or more fractions in order. Web version: https://s3eidat.github.io/fraction-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "calculate" \| "decimal_to_fraction" \| "fraction_to_decimal" \| "compare" | no (default `"calculate"`) | What to do. |
| `input` | string | yes | calculate: "1 1/2 + 3/4 × 2/5"; decimal_to_fraction: "0.1(6)"; fraction_to_decimal: "5/7"; compare: "2/3, 3/5, 7/10". |

**Output**

| Field | Type | Description |
|---|---|---|
| `mode` | string |  |
| `fraction` | string |  |
| `numerator` | string |  |
| `denominator` | string |  |
| `mixed` | string |  |
| `decimal` | string |  |
| `repeating` | boolean |  |
| `rounded` | string |  |
| `percent` | string |  |
| `approximations` | object[] |  |
| `order` | object[] |  |
| `chain` | string |  |
| `largest` | string |  |
| `steps` | object[] |  |

**Example** — 9/4 = 2 1/4 = 2.25

```json
{
  "input": "1 1/2 + 3/4"
}
```

Result:

```json
{
  "mode": "calculate",
  "fraction": "9/4",
  "numerator": "9",
  "denominator": "4",
  "mixed": "2 1/4",
  "decimal": "2.25",
  "repeating": false,
  "percent": "225%",
  "steps": [
    {
      "text": "Mixed number to improper fraction",
      "math": "1 1/2 = 3/2 (1 × 2 + 1)"
    },
    {
      "text": "Lowest common denominator of 2 and 4 is 4",
      "math": "3/2 + 3/4 = 6/4 + 3/4"
    },
    {
      "text": "Add the numerators",
      "math": "= 9/4"
    }
  ]
}
```

**Example** — 1/6

```json
{
  "mode": "decimal_to_fraction",
  "input": "0.1(6)"
}
```

Result:

```json
{
  "mode": "decimal_to_fraction",
  "fraction": "1/6",
  "numerator": "1",
  "denominator": "6",
  "decimal": "0.1(6)",
  "repeating": true,
  "rounded": "0.166667",
  "percent": "16.(6)%",
  "steps": [
    {
      "text": "Let x = 0.1(6). The block \"6\" repeats every 1 digit."
    },
    {
      "text": "Multiply by 100 and by 10 so the repeating tails line up",
      "math": "100x = 16.(6);  10x = 1.(6)"
    },
    {
      "text": "Subtract: the repeating parts cancel",
      "math": "90x = 15"
    },
    {
      "text": "Divide",
      "math": "x = 15/90"
    },
    {
      "text": "Simplify (divide by 15)",
      "math": "x = 1/6"
    }
  ]
}
```

<a id="calc_fuel_cost"></a>
### `calc_fuel_cost` — Fuel cost of a trip

Fuel needed and cost of a trip from distance (km or miles, optionally round trip), fuel price (per litre, US gallon or UK gallon) and consumption in L/100 km, km/L, mpg (US) or mpg (UK); split between people; cost per km/mile and per 100. Optionally compare a second car (its own consumption and, if different, fuel price). Also converts the consumption to the other units. Web version: https://s3eidat.github.io/fuel-cost/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `distance` | number \| string | yes | Trip distance (one way unless roundTrip). |
| `distanceUnit` | "km" \| "mi" | no (default `"km"`) | Distance unit. |
| `roundTrip` | boolean | no (default `false`) | Double the distance (there and back). |
| `price` | number \| string | yes | Fuel price per priceUnit. |
| `priceUnit` | "L" \| "galUS" \| "galUK" | no (default `"L"`) | The price is per litre, US gallon or UK gallon. |
| `consumption` | number \| string | yes | Car consumption / economy, e.g. 7.5 (L/100 km) or 35 (mpg). |
| `consumptionUnit` | "l100" \| "kml" \| "mpgus" \| "mpguk" | no (default `"l100"`) | l100 = L/100 km, kml = km/L, mpgus, mpguk. |
| `people` | integer | no (default `1`) | Split the cost between this many people. |
| `currency` | string | no | Currency label for the text, e.g. "JD" or "$". |
| `name` | string | no | Name of the car (default "Car A"). |
| `compare` | object | no | Optional second car to compare. |

**Output**

| Field | Type | Description |
|---|---|---|
| `distanceKm` | number |  |
| `litres` | number |  |
| `gallons` | number |  |
| `gallonUnit` | string |  |
| `cost` | number |  |
| `costEach` | number |  |
| `costPerDistanceUnit` | number |  |
| `costPer100` | number |  |
| `pricePerLitre` | number |  |
| `l100km` | number |  |
| `consumptionIn` | object |  |
| `compare` | object |  |
| `summary` | string |  |

**Example** — 70.35 L, cost 63.32, 21.11 each

```json
{
  "distance": 335,
  "roundTrip": true,
  "price": 0.9,
  "consumption": 10.5,
  "people": 3,
  "currency": "JD"
}
```

Result:

```json
{
  "distanceKm": 670,
  "litres": 70.35,
  "gallons": 18.58,
  "gallonUnit": "US gal",
  "cost": 63.32,
  "costEach": 21.11,
  "costPerDistanceUnit": 0.0945,
  "costPer100": 9.45,
  "pricePerLitre": 0.9,
  "l100km": 10.5,
  "consumptionIn": {
    "l100": 10.5,
    "kml": 9.52,
    "mpgus": 22.4,
    "mpguk": 26.9
  },
  "summary": "Trip: 670 km (round trip), Car A at 10.5 L/100 km: 70.4 L = JD 63.32 (JD 21.11 each for 3)"
}
```

<a id="calc_gpa"></a>
### `calc_gpa` — GPA calculator

Credit-weighted semester GPA, cumulative GPA (with previous credits and GPA) and the average you need over the remaining credits to reach a target. Scales: 4.0 (A…F, no A+), 4.3 (A+ = 4.3), 5.0 (A+ 5, A 4.75, B+ 4.5 … F 1) with letter grades, or "percent" with numeric marks between a minimum and maximum (default 35–100). Courses with no credits or an invalid grade are skipped (and listed). Up to 40 courses. Web version: https://s3eidat.github.io/gpa-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `scale` | "4.0" \| "4.3" \| "5.0" \| "percent" | no (default `"4.0"`) | Grading scale. |
| `percentMin` | number \| string | no | percent scale: lowest passing mark (default 35). |
| `percentMax` | number \| string | no | percent scale: highest mark (default 100). |
| `courses` | object[] | yes | This semester's courses. |
| `previousCredits` | number \| string | no | Credits completed before this semester. |
| `previousGpa` | number \| string | no | GPA before this semester. |
| `targetGpa` | number \| string | no | Target cumulative GPA. |
| `remainingCredits` | number \| string | no | Credits still to take after this semester (for the target). |

**Output**

| Field | Type | Description |
|---|---|---|
| `scale` | string |  |
| `semester` | object |  |
| `cumulative` | object |  |
| `target` | object |  |
| `skipped` | object[] |  |
| `summary` | string |  |

**Example** — semester 2.99, cumulative 3.15, need 3.71 (about A-)

```json
{
  "courses": [
    {
      "credits": 3,
      "grade": "A"
    },
    {
      "credits": 3,
      "grade": "B+"
    },
    {
      "credits": 4,
      "grade": "C"
    }
  ],
  "previousCredits": 30,
  "previousGpa": 3.2,
  "targetGpa": 3.3,
  "remainingCredits": 15
}
```

Result:

```json
{
  "scale": "4.0",
  "semester": {
    "gpa": 2.99,
    "display": "2.99",
    "credits": 10,
    "points": 29.9
  },
  "cumulative": {
    "gpa": 3.15,
    "display": "3.15",
    "credits": 40
  },
  "skipped": [],
  "target": {
    "status": "needed",
    "neededAverage": 3.71,
    "letter": "A",
    "message": "You need an average of 3.71 over the next 15 credits (about A or better) to reach 3.30."
  },
  "summary": "Semester GPA: 2.99 (10 credits)\nCumulative GPA: 3.15 (40 credits)\nYou need an average of 3.71 over the next 15 credits (about A or better) to reach 3.30."
}
```

<a id="calc_hijri"></a>
### `calc_hijri` — Hijri ⇄ Gregorian converter

Convert between Gregorian and Hijri dates with the Umm al-Qura calendar (the same Intl calendar the website uses), both ways. Modes: to_hijri (a Gregorian date), to_gregorian (Hijri year, month 1–12, day), month (length of a Hijri month and its Gregorian start/end), age (age in Hijri and Gregorian years, next Hijri birthday). Results come in English and Arabic (Arabic-Indic digits by default; Gregorian month names in the Egyptian/Gulf or Levant style). Supported range 1300–1600 AH (1882–2174). Web version: https://s3eidat.github.io/hijri-converter/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "to_hijri" \| "to_gregorian" \| "month" \| "age" | yes | Which conversion. |
| `date` | string | no | to_hijri: Gregorian date YYYY-MM-DD (default today). |
| `hijriYear` | integer | no | to_gregorian / month: Hijri year (1300–1600). |
| `hijriMonth` | integer | no | to_gregorian / month: Hijri month 1–12 (1 = Muharram, 9 = Ramadan). |
| `hijriDay` | integer | no | to_gregorian: Hijri day 1–30. |
| `birthDate` | string | no | age: Gregorian date of birth YYYY-MM-DD. |
| `onDate` | string | no | age: age on this Gregorian date (default today). |
| `arabicDigits` | boolean | no (default `true`) | Use Arabic-Indic digits in the Arabic text. |
| `gregorianArabicMonths` | "intl" \| "levant" | no (default `"intl"`) | Arabic Gregorian month names: intl (Egypt/Gulf style, January = Yanayir) or levant (Syria/Jordan style, January = Kanun al-Thani). |

**Output**

| Field | Type | Description |
|---|---|---|
| `mode` | string |  |
| `day` | object |  |
| `month` | object |  |
| `age` | object |  |
| `summary` | string |  |

**Example** — 24 Ramadan 1420 AH (Saturday)

```json
{
  "mode": "to_hijri",
  "date": "2000-01-01"
}
```

Result:

```json
{
  "mode": "to_hijri",
  "day": {
    "gregorian": {
      "date": "2000-01-01",
      "weekday": "Saturday",
      "text": "Saturday, 1 January 2000",
      "arabic": "السبت ١ يناير ٢٠٠٠ م"
    },
    "hijri": {
      "year": 1420,
      "month": 9,
      "day": 24,
      "monthName": "Ramadan",
      "monthNameArabic": "رمضان",
      "iso": "1420-09-24",
      "text": "Saturday, 24 Ramadan 1420 AH",
      "arabic": "السبت ٢٤ رمضان ١٤٢٠ هـ"
    },
    "approximate": false
  },
  "summary": "Saturday, 1 January 2000 = 24 Ramadan 1420 AH · السبت ٢٤ رمضان ١٤٢٠ هـ"
}
```

**Example** — 2026-02-18 (Wednesday)

```json
{
  "mode": "to_gregorian",
  "hijriYear": 1447,
  "hijriMonth": 9,
  "hijriDay": 1
}
```

Result:

```json
{
  "mode": "to_gregorian",
  "day": {
    "gregorian": {
      "date": "2026-02-18",
      "weekday": "Wednesday",
      "text": "Wednesday, 18 February 2026",
      "arabic": "الأربعاء ١٨ فبراير ٢٠٢٦ م"
    },
    "hijri": {
      "year": 1447,
      "month": 9,
      "day": 1,
      "monthName": "Ramadan",
      "monthNameArabic": "رمضان",
      "iso": "1447-09-01",
      "text": "Wednesday, 1 Ramadan 1447 AH",
      "arabic": "الأربعاء ١ رمضان ١٤٤٧ هـ"
    },
    "approximate": false
  },
  "summary": "Wednesday, 18 February 2026 = 1 Ramadan 1447 AH · الأربعاء ١ رمضان ١٤٤٧ هـ"
}
```

<a id="calc_hours"></a>
### `calc_hours` — Work hours / time card

Weekly time card: add shifts (start and end "HH:MM", unpaid break minutes; an end at or before the start is an overnight shift), up to 6 shifts a day. Totals per shift, per day and per week in h:mm and decimal hours; optional rounding of each shift to 5, 6 or 15 minutes; overtime over N hours a day and/or N regular hours a week (daily first, then weekly), paid at a multiplier (default 1.5); pay at an hourly rate. Web version: https://s3eidat.github.io/hours-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `shifts` | object[] | yes | The week's shifts. |
| `dailyOvertimeHours` | number | no | Hours per day after which time is overtime (e.g. 8). |
| `weeklyOvertimeHours` | number | no | Regular hours per week after which time is overtime (e.g. 40). |
| `overtimeMultiplier` | number | no (default `1.5`) | Overtime pay multiplier. |
| `hourlyRate` | number | no | Pay per regular hour. |
| `currency` | string | no | Currency label for the summary. |
| `roundMinutes` | 0 \| 5 \| 6 \| 15 | no (default `0`) | Round each shift to the nearest 5, 6 or 15 minutes (0 = off). |
| `weekStart` | "saturday" \| "sunday" \| "monday" | no (default `"saturday"`) | First day of the week (order of the days). |
| `weekOf` | string | no | Optional: any date in that week (YYYY-MM-DD) to date each day. |

**Output**

| Field | Type | Description |
|---|---|---|
| `shifts` | object[] |  |
| `days` | object[] |  |
| `total` | object |  |
| `regular` | object |  |
| `overtime` | object |  |
| `pay` | object |  |
| `note` | string |  |
| `summary` | string |  |

**Example** — 15:30 total, pay 310

```json
{
  "shifts": [
    {
      "day": "monday",
      "start": "09:00",
      "end": "17:30",
      "breakMinutes": 30
    },
    {
      "day": "tuesday",
      "start": "22:00",
      "end": "06:00",
      "breakMinutes": 30
    }
  ],
  "hourlyRate": 20
}
```

Result:

```json
{
  "shifts": [
    {
      "day": "Monday",
      "start": "09:00",
      "end": "17:30",
      "breakMinutes": 30,
      "minutes": 480,
      "hm": "8:00",
      "hours": "8.00",
      "overnight": false
    },
    {
      "day": "Tuesday",
      "start": "22:00",
      "end": "06:00",
      "breakMinutes": 30,
      "minutes": 450,
      "hm": "7:30",
      "hours": "7.50",
      "overnight": true
    }
  ],
  "days": [
    {
      "day": "Saturday",
      "minutes": 0,
      "hm": "0:00",
      "hours": "0.00",
      "overtimeMinutes": 0
    },
    {
      "day": "Sunday",
      "minutes": 0,
      "hm": "0:00",
      "hours": "0.00",
      "overtimeMinutes": 0
    },
    {
      "day": "Monday",
      "minutes": 480,
      "hm": "8:00",
      "hours": "8.00",
      "overtimeMinutes": 0
    },
    {
      "day": "Tuesday",
      "minutes": 450,
      "hm": "7:30",
      "hours": "7.50",
      "overtimeMinutes": 0
    },
    {
      "day": "Wednesday",
      "minutes": 0,
      "hm": "0:00",
      "hours": "0.00",
      "overtimeMinutes": 0
    },
    {
      "day": "Thursday",
      "minutes": 0,
      "hm": "0:00",
      "hours": "0.00",
      "overtimeMinutes": 0
    },
    {
      "day": "Friday",
      "minutes": 0,
      "hm": "0:00",
      "hours": "0.00",
      "overtimeMinutes": 0
    }
  ],
  "total": {
    "minutes": 930,
    "hm": "15:30",
    "hours": "15.50"
  },
  "regular": {
    "minutes": 930,
    "hm": "15:30",
    "hours": "15.50"
  },
  "overtime": {
    "minutes": 0,
    "hm": "0:00",
    "hours": "0.00",
    "daily": 0,
    "weekly": 0
  },
  "pay": {
    "regular": 310,
    "overtime": 0,
    "total": 310
  },
  "note": "No overtime rules set; give dailyOvertimeHours or weeklyOvertimeHours to split regular and overtime hours. Rate: 20.00 per hour.",
  "summary": "Monday: 09:00–17:30 (30 min break) = 8:00 (8.00 h)\nTuesday: 22:00–06:00 (30 min break) = 7:30 (7.50 h)\nTotal: 15:30 (15.50 h) · regular 15:30 · overtime 0:00\nPay: 310.00 at 20.00/h, overtime ×1.5"
}
```

<a id="calc_loan"></a>
### `calc_loan` — Loan / mortgage calculator

Monthly payment, total interest, total paid and payoff month for a fixed-rate loan or mortgage, with an optional extra monthly payment (shows interest saved and how much sooner it ends) and an amortization schedule by year or by month (or none). Maths is in whole cents like the website; the last payment absorbs the rounding so the balance ends at 0. Rate 0–100 %, term 1 month to 100 years. Web version: https://s3eidat.github.io/loan-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `amount` | number \| string | yes | Loan amount (principal), e.g. 250000 or "250,000". |
| `annualRate` | number \| string | yes | Annual interest rate in percent, e.g. 6.5. 0 is allowed. |
| `term` | number \| string | yes | Loan term, in years (default) or months (see termUnit). |
| `termUnit` | "years" \| "months" | no (default `"years"`) | Unit of "term". |
| `extraMonthly` | number \| string | no | Optional extra principal paid every month. |
| `startMonth` | string | no | First payment month "YYYY-MM". Default: next month. |
| `schedule` | "yearly" \| "monthly" \| "none" | no (default `"yearly"`) | Amortization table: per calendar year, per month, or omit it. |

**Output**

| Field | Type | Description |
|---|---|---|
| `monthlyPayment` | number |  |
| `extraMonthly` | number |  |
| `months` | number |  |
| `duration` | string |  |
| `totalInterest` | number |  |
| `totalPaid` | number |  |
| `firstPayment` | string |  |
| `payoffMonth` | string |  |
| `payoffLabel` | string |  |
| `savings` | object |  |
| `schedule` | object[] |  |
| `summary` | string |  |

**Example** — monthlyPayment 1199.10, totalInterest 231,677.04, 360 months

```json
{
  "amount": 200000,
  "annualRate": 6,
  "term": 30,
  "schedule": "none"
}
```

Result:

```json
{
  "monthlyPayment": 1199.1,
  "extraMonthly": 0,
  "months": 360,
  "duration": "30 years",
  "totalInterest": 231677.04,
  "totalPaid": 431677.04,
  "firstPayment": "2026-02",
  "payoffMonth": "2056-01",
  "payoffLabel": "Jan 2056",
  "summary": "Loan 200,000.00 at 6% for 30 years\nMonthly payment: 1,199.10\nTotal interest: 231,677.04\nTotal paid: 431,677.04\nPaid off: Jan 2056 (30 years)"
}
```

<a id="calc_percentage"></a>
### `calc_percentage` — Percentage calculator

Every percentage question the website answers: "X% of Y" (percent_of: percent, value), "X is what % of Y" (what_percent: part, whole), percent change from A to B (change: from, to), increase/decrease a value by X% (increase/decrease: value, percent), find the original before a X% increase/decrease (reverse_increase/reverse_decrease: value, percent), and profit margin vs markup (margin_markup: cost, price). Returns the number, a display string and the worked formula. Web version: https://s3eidat.github.io/percentage-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "percent_of" \| "what_percent" \| "change" \| "increase" \| "decrease" \| "reverse_increase" \| "reverse_decrease" \| "margin_markup" | yes | Which question to answer (see the description for the fields each one uses). |
| `percent` | number \| string | no | The percentage X (percent_of, increase, decrease, reverse_*). |
| `value` | number \| string | no | The value Y (percent_of: Y in "X% of Y"; increase/decrease: the starting value; reverse_*: the final value). |
| `part` | number \| string | no | what_percent: the part X in "X is what % of Y". |
| `whole` | number \| string | no | what_percent: the whole Y. |
| `from` | number \| string | no | change: the old value. |
| `to` | number \| string | no | change: the new value. |
| `cost` | number \| string | no | margin_markup: cost price. |
| `price` | number \| string | no | margin_markup: selling price. |

**Output**

| Field | Type | Description |
|---|---|---|
| `mode` | string |  |
| `result` | number \| null |  |
| `formatted` | string |  |
| `explanation` | string |  |
| `direction` | "increase" \| "decrease" \| "no change" |  |
| `difference` | number |  |
| `profit` | number |  |
| `margin` | number \| null |  |
| `markup` | number \| null |  |

**Example**

```json
{
  "mode": "percent_of",
  "percent": 15,
  "value": 200
}
```

Result:

```json
{
  "result": 30
}
```

**Example** — original 100

```json
{
  "mode": "reverse_increase",
  "value": 115,
  "percent": 15
}
```

Result:

```json
{
  "mode": "reverse_increase",
  "result": 100,
  "formatted": "100",
  "explanation": "original = 115 ÷ (1 + 15/100) = 100 · the 15% part is 15"
}
```

<a id="calc_primes"></a>
### `calc_primes` — Prime number tools

Number theory with big integers, like the website. check: is a number prime (trial division, deterministic Miller-Rabin below 2^64, probabilistic above), its prime factorisation (Pollard rho), number of divisors, sum of divisors σ, Euler φ, the divisor list, previous/next prime and whether it is perfect; the number may be an expression like "2^61-1", "10!+1" or "1000003*1000033" (up to 400 digits). gcd_lcm: GCD and LCM of 2–50 numbers with Euclid's steps. range: list the primes between two numbers (below 10^15, at most 10,000,000 numbers per range). Web version: https://s3eidat.github.io/prime-tools/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "check" \| "gcd_lcm" \| "range" | no (default `"check"`) | What to do. |
| `number` | string \| number | no | check: the number or expression, e.g. "600851475143" or "2^89-1". |
| `numbers` | (string \| number)[] | no | gcd_lcm: the whole numbers (2–50, expressions allowed). |
| `from` | integer | no | range: start (inclusive). |
| `to` | integer | no | range: end (inclusive), below 10^15. |
| `limit` | integer | no (default `10000`) | range: return at most this many primes in the list (the count is always exact). |

**Output**

| Field | Type | Description |
|---|---|---|
| `mode` | string |  |
| `number` | string |  |
| `digits` | number |  |
| `isPrime` | boolean |  |
| `verdict` | string |  |
| `method` | string |  |
| `factorization` | string |  |
| `factors` | object[] |  |
| `unfactored` | string[] |  |
| `probablePrimeFactors` | boolean |  |
| `divisorCount` | string |  |
| `divisorSum` | string |  |
| `eulerPhi` | string |  |
| `perfect` | boolean |  |
| `divisors` | string[] |  |
| `previousPrime` | string \| null |  |
| `nextPrime` | string \| null |  |
| `gcd` | string |  |
| `lcm` | string |  |
| `coprime` | boolean |  |
| `steps` | object[] |  |
| `count` | number |  |
| `primes` | number[] |  |
| `truncated` | boolean |  |

**Example** — composite = 71 × 839 × 1471 × 6857

```json
{
  "number": "600851475143"
}
```

Result:

```json
{
  "mode": "check",
  "number": "600851475143",
  "digits": 12,
  "isPrime": false,
  "verdict": "Composite",
  "method": "Checked by trial division.",
  "factorization": "71 × 839 × 1471 × 6857",
  "factors": [
    {
      "prime": "71",
      "exponent": 1
    },
    {
      "prime": "839",
      "exponent": 1
    },
    {
      "prime": "1471",
      "exponent": 1
    },
    {
      "prime": "6857",
      "exponent": 1
    }
  ],
  "probablePrimeFactors": false,
  "divisorCount": "16",
  "divisorSum": "610544148480",
  "eulerPhi": "591194251200",
  "perfect": false,
  "divisors": [
    "1",
    "71",
    "839",
    "1471",
    "6857",
    "59569",
    "104441",
    "486847",
    "1234169",
    "5753023",
    "10086647",
    "87625999",
    "408464633",
    "716151937",
    "8462696833",
    "600851475143"
  ],
  "previousPrime": "600851475067",
  "nextPrime": "600851475149"
}
```

**Example** — gcd 42, lcm 1260

```json
{
  "mode": "gcd_lcm",
  "numbers": [
    84,
    126,
    210
  ]
}
```

Result:

```json
{
  "mode": "gcd_lcm",
  "gcd": "42",
  "lcm": "1260",
  "steps": [
    {
      "head": "gcd(84, 126)",
      "lines": [
        "126 = 1 × 84 + 42",
        "84 = 2 × 42 + 0",
        "→ 42"
      ]
    },
    {
      "head": "lcm(84, 126) = 84 × 126 ÷ 42 = 252",
      "lines": []
    },
    {
      "head": "gcd(42, 210)",
      "lines": [
        "210 = 5 × 42 + 0",
        "→ 42"
      ]
    },
    {
      "head": "lcm(252, 210) = 252 × 210 ÷ 42 = 1260",
      "lines": []
    }
  ]
}
```

**Example** — 25 primes

```json
{
  "mode": "range",
  "from": 1,
  "to": 100
}
```

Result:

```json
{
  "mode": "range",
  "count": 25,
  "primes": [
    2,
    3,
    5,
    7,
    11,
    13,
    17,
    19,
    23,
    29,
    31,
    37,
    41,
    43,
    47,
    53,
    59,
    61,
    67,
    71,
    73,
    79,
    83,
    89,
    97
  ],
  "truncated": false
}
```

<a id="calc_roman"></a>
### `calc_roman` — Roman numerals

Convert numbers to Roman numerals (1–3,999 in standard form; up to 3,999,999 with a vinculum bar, which multiplies by 1,000) and Roman numerals back to numbers with strict validation that explains mistakes (IIII, IC, VV, non-standard forms like IIX…) and the symbol-by-symbol working. Also writes a date in Roman numerals (day/month/year order and separator of your choice, years 1–3999). Web version: https://s3eidat.github.io/roman-numerals/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "to_roman" \| "from_roman" \| "date" | yes | to_roman (number → Roman), from_roman (Roman → number) or date. |
| `number` | number \| string | no | to_roman: a whole number 1–3,999,999. |
| `roman` | string | no | from_roman: the numeral, e.g. "MCMXCIV" (any case). |
| `date` | string | no | date: YYYY-MM-DD. |
| `order` | "dmy" \| "mdy" \| "ymd" | no (default `"dmy"`) | date: order of day, month, year. |
| `separator` | string | no (default `" · "`) | date: separator, e.g. " · ", ".", " - ", " / ". |

**Output**

| Field | Type | Description |
|---|---|---|
| `mode` | string |  |
| `valid` | boolean |  |
| `roman` | string |  |
| `value` | number |  |
| `vinculum` | boolean |  |
| `steps` | object[] |  |
| `error` | string |  |
| `standardForm` | string |  |
| `explanation` | string |  |
| `plain` | string |  |

**Example**

```json
{
  "mode": "to_roman",
  "number": 1994
}
```

Result:

```json
{
  "roman": "MCMXCIV"
}
```

**Example** — invalid: write 99 as XCIX

```json
{
  "mode": "from_roman",
  "roman": "IC"
}
```

Result:

```json
{
  "mode": "from_roman",
  "valid": false,
  "error": "\"IC\" is not a valid subtraction. Only IV, IX, XL, XC, CD and CM are allowed (99 is XCIX)."
}
```

<a id="calc_salary"></a>
### `calc_salary` — Salary / pay period converter

Convert pay between per hour, day, week, every 2 weeks, twice a month, month and year using your hours per week, days per week and weeks per year (defaults 40 / 5 / 52). Optional overtime (hours per week × multiplier, e.g. 1.25, 1.5, 2) and a raise given as a percent or as the new monthly pay. Amounts are rounded to 2 decimals. Web version: https://s3eidat.github.io/salary-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `amount` | number \| string | yes | The pay amount, e.g. 1200. |
| `period` | "hour" \| "day" \| "week" \| "biweek" \| "semimonth" \| "month" \| "year" | no (default `"month"`) | The period "amount" is paid for. |
| `hoursPerWeek` | number \| string | no | Working hours per week (0–168, default 40). |
| `daysPerWeek` | number \| string | no | Working days per week (0–7, default 5). |
| `weeksPerYear` | number \| string | no | Paid weeks per year (0–53, default 52). |
| `overtimeHours` | number \| string | no | Optional overtime hours per week. |
| `overtimeMultiplier` | number \| string | no | Overtime rate multiplier (default 1.5). |
| `raisePercent` | number \| string | no | Optional raise in percent (gives the new pay). |
| `newMonthlyPay` | number \| string | no | Or: the new monthly pay (gives the raise percent). |
| `currency` | string | no | Optional ISO currency code for the text summary (e.g. USD, JOD). |

**Output**

| Field | Type | Description |
|---|---|---|
| `periods` | object |  |
| `overtime` | object |  |
| `raise` | object |  |
| `summary` | string |  |

**Example** — hour 25, day 200, month 4333.33, biweek 2000

```json
{
  "amount": 52000,
  "period": "year"
}
```

Result:

```json
{
  "periods": {
    "hour": 25,
    "day": 200,
    "week": 1000,
    "biweek": 2000,
    "semimonth": 2166.67,
    "month": 4333.33,
    "year": 52000
  },
  "summary": "Per hour: 25.00\nPer day: 200.00\nPer week: 1,000.00\nEvery 2 weeks: 2,000.00\nTwice a month: 2,166.67\nPer month: 4,333.33\nPer year: 52,000.00\n(40 h/week, 5 days/week, 52 weeks/year)"
}
```

<a id="calc_sleep"></a>
### `calc_sleep` — Sleep cycle calculator

Sleep in whole cycles so you wake in light sleep: given a wake-up time, the bedtimes for 6, 5, 4 or 3 cycles; given a bedtime (or "now"), the wake-up times. Defaults: 90-minute cycles (70–110) and 15 minutes to fall asleep (0–60). With mode "bed"/"now" it also lists nap alarms (20, 30, 60 min and one full cycle). 12- or 24-hour clock. Web version: https://s3eidat.github.io/sleep-calc/

Hints: readOnly

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "wake" \| "bed" \| "now" | no (default `"wake"`) | wake: I must wake at "time"; bed: I go to bed at "time"; now: I go to bed now (server clock). |
| `time` | string | no | "HH:MM" 24 h: the wake-up time (wake) or bedtime (bed). Default 07:00 / 23:00. |
| `fallAsleepMinutes` | integer | no (default `15`) | Minutes it takes you to fall asleep. |
| `cycleMinutes` | integer | no (default `90`) | Length of one sleep cycle. |
| `clock` | "12" \| "24" | no (default `"12"`) | 12-hour (AM/PM) or 24-hour times in the output. |

**Output**

| Field | Type | Description |
|---|---|---|
| `title` | string |  |
| `options` | object[] |  |
| `naps` | object[] |  |
| `note` | string |  |
| `summary` | string |  |

**Example** — bed at 21:45 (6 cycles), 23:15 (5), 00:45 (4), 02:15 (3)

```json
{
  "mode": "wake",
  "time": "07:00",
  "clock": "24"
}
```

<a id="calc_split_bill"></a>
### `calc_split_bill` — Split a bill

Split a restaurant bill with tax/service and tip, in whole cents so the shares always add up exactly (left-over cents go to the largest remainders). simple: one bill split evenly between N people (1–100). items: list who had what; each item is split between the people who shared it (nobody listed = everyone), and tax and tip are shared in proportion. Tip can be on the subtotal or on subtotal + tax; each share can be rounded up to a step (e.g. 0.5 or 1). Returns a ready-to-send summary. Web version: https://s3eidat.github.io/split-bill/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "simple" \| "items" | no (default `"simple"`) | Even split, or itemised. |
| `bill` | number \| string | no | simple: the bill subtotal (before tax and tip). |
| `people` | integer \| string[] | no | simple: how many people (default 2). items: the names, e.g. ["Sara", "Omar", "Lina"]. |
| `items` | object[] | no | items: what was ordered. |
| `taxPercent` | number | no (default `0`) | Tax / service charge percent. |
| `tipPercent` | number | no (default `10`) | Tip percent (default 10). |
| `tipOnTax` | boolean | no (default `false`) | Calculate the tip on subtotal + tax. |
| `roundUpTo` | number | no (default `0`) | Round each share up to a multiple of this (e.g. 0.5, 1, 5). 0 = exact. |
| `currency` | string | no | Currency, e.g. "JOD" (written after) or "$" (written before). |

**Output**

| Field | Type | Description |
|---|---|---|
| `subtotal` | number |  |
| `tax` | number |  |
| `tip` | number |  |
| `total` | number |  |
| `collected` | number |  |
| `roundingExtra` | number |  |
| `shares` | object[] |  |
| `unknownNames` | string[] |  |
| `summary` | string |  |

**Example** — 110.00 total: 36.67, 36.67, 36.66

```json
{
  "bill": 100,
  "people": 3,
  "tipPercent": 10
}
```

Result:

```json
{
  "subtotal": 100,
  "tax": 0,
  "tip": 10,
  "total": 110,
  "collected": 110,
  "roundingExtra": 0,
  "shares": [
    {
      "name": "Person 1",
      "pays": 36.67
    },
    {
      "name": "Person 2",
      "pays": 36.67
    },
    {
      "name": "Person 3",
      "pays": 36.66
    }
  ],
  "summary": "🧾 *Bill split* — 3 people\nSubtotal: 100.00\nTip 10%: 10.00\nTotal: *110.00*\n\n2 × *36.67* + 1 × *36.66*"
}
```

<a id="calc_stats"></a>
### `calc_stats` — Descriptive statistics

Descriptive statistics for a list of numbers: count, sum, mean, median, mode(s), min, max, range, sample and population variance and SD, standard error, coefficient of variation, quartiles (inclusive = Excel QUARTILE.INC, exclusive = QUARTILE.EXC, or Tukey median-of-halves), IQR, 1.5×IQR outlier fences and outliers, a frequency table (values, or Sturges bins when there are more than 20 distinct values) and z-scores. Paste numbers separated by spaces, commas, semicolons, | or new lines; headers and text are skipped and reported. Commas can be separators, thousands marks or decimal commas. Up to 1,000,000 numbers. Web version: https://s3eidat.github.io/stats-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | string \| number[] | yes | The numbers: a text blob (CSV column, list…) or an array of numbers. |
| `commas` | "sep" \| "thou" \| "dec" | no (default `"sep"`) | In text: commas separate values (sep), are thousands marks (thou: 1,234), or decimal commas (dec: 1,5). |
| `quartileMethod` | "inc" \| "exc" \| "tukey" | no (default `"inc"`) | Quartile method. |
| `frequency` | boolean | no (default `true`) | Include the frequency table. |
| `zScores` | boolean | no (default `false`) | Include a z-score for each value (first 1,000). |

**Output**

| Field | Type | Description |
|---|---|---|
| `count` | number |  |
| `sum` | number \| null |  |
| `mean` | number \| null |  |
| `median` | number \| null |  |
| `modes` | number[] |  |
| `modeCount` | number |  |
| `min` | number \| null |  |
| `max` | number \| null |  |
| `range` | number \| null |  |
| `sampleVariance` | number \| null |  |
| `sampleSD` | number \| null |  |
| `populationVariance` | number \| null |  |
| `populationSD` | number \| null |  |
| `standardError` | number \| null |  |
| `coefficientOfVariation` | number \| null |  |
| `q1` | number \| null |  |
| `q3` | number \| null |  |
| `iqr` | number \| null |  |
| `quartileMethod` | string |  |
| `lowerFence` | number \| null |  |
| `upperFence` | number \| null |  |
| `outliers` | number[] |  |
| `whiskerLow` | number \| null |  |
| `whiskerHigh` | number \| null |  |
| `skipped` | string[] |  |
| `skippedCount` | number |  |
| `frequency` | object[] |  |
| `zScores` | object[] |  |
| `summary` | string |  |

**Example** — mean 5, median 4.5, mode 4, population SD 2, sample SD 2.138

```json
{
  "data": "2, 4, 4, 4, 5, 5, 7, 9"
}
```

Result:

```json
{
  "count": 8,
  "sum": 40,
  "mean": 5,
  "median": 4.5,
  "modes": [
    4
  ],
  "modeCount": 3,
  "min": 2,
  "max": 9,
  "range": 7,
  "sampleVariance": 4.57142857143,
  "sampleSD": 2.1380899353,
  "populationVariance": 4,
  "populationSD": 2,
  "standardError": 0.755928946018,
  "coefficientOfVariation": 42.761798706,
  "q1": 4,
  "q3": 5.5,
  "iqr": 1.5,
  "quartileMethod": "inclusive (Excel QUARTILE.INC, R type 7)",
  "lowerFence": 1.75,
  "upperFence": 7.75,
  "outliers": [
    9
  ],
  "whiskerLow": 2,
  "whiskerHigh": 7,
  "skipped": [],
  "skippedCount": 0,
  "frequency": [
    {
      "value": "2",
      "count": 1,
      "relative": 0.125,
      "cumulative": 1
    },
    {
      "value": "4",
      "count": 3,
      "relative": 0.375,
      "cumulative": 4
    },
    {
      "value": "5",
      "count": 2,
      "relative": 0.25,
      "cumulative": 6
    },
    {
      "value": "7",
      "count": 1,
      "relative": 0.125,
      "cumulative": 7
    },
    {
      "value": "9",
      "count": 1,
      "relative": 0.125,
      "cumulative": 8
    }
  ],
  "summary": "Count (n): 8\nSum: 40\nMean: 5\nMedian: 4.5\nMode: 4 (×3)\nMinimum: 2\nMaximum: 9\nRange: 7\nSample variance (s²): 4.571429\nSample SD (s): 2.13809\nPopulation variance (σ²): 4\nPopulation SD (σ): 2\nStandard error of the mean: 0.755929\nCoefficient of variation: 42.761799%\nQ1: 4\nQ3: 5.5\nIQR: 1.5\nQuartile method: inclusive (Excel QUARTILE.INC, R type 7)\nOutlier fences (1.5 × IQR): 1.75 and 7.75\nOutliers: 9"
}
```

<a id="calc_unit_convert"></a>
### `calc_unit_convert` — Unit converter

Convert between units in 14 categories, the same table as the website: length, mass, temperature, volume, area (incl. dunam), speed, time, data (kB/MB vs KiB/MiB, bits), pressure, energy, power, angle, fuel economy (L/100km, km/L, mpg US/UK) and cooking (tsp, tbsp, cups, metric spoons). Give value + from + to (symbols like "km", "°F", "GiB" or names like "miles"; ASCII forms like "m2", "um" work), or a query like "5 km to mi". Set all=true for the value in every unit of the category, or list=true to see every category and unit. Web version: https://s3eidat.github.io/unit-convert/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `query` | string | no | A whole question, e.g. "5 km to mi" or "72 f in c". |
| `value` | number \| string | no | The number to convert. |
| `from` | string | no | Unit to convert from (symbol or name). |
| `to` | string | no | Unit to convert to (symbol or name). Optional when all=true. |
| `category` | "length" \| "mass" \| "temp" \| "volume" \| "area" \| "speed" \| "time" \| "data" \| "pressure" \| "energy" \| "power" \| "angle" \| "fuel" \| "cooking" | no | Category, to pick the right unit when a symbol exists in two (e.g. "pt" in volume and cooking). |
| `all` | boolean | no (default `false`) | Also return the value in every unit of the category. |
| `list` | boolean | no (default `false`) | Just list every category and its units. |

**Output**

| Field | Type | Description |
|---|---|---|
| `category` | string |  |
| `from` | object |  |
| `to` | object |  |
| `value` | number |  |
| `result` | number \| null |  |
| `formatted` | string |  |
| `formula` | string |  |
| `oneUnit` | string |  |
| `note` | string |  |
| `all` | object[] |  |
| `categories` | object[] |  |

**Example** — 3.106855961 mi

```json
{
  "query": "5 km to mi"
}
```

Result:

```json
{
  "category": "length",
  "from": {
    "symbol": "km",
    "name": "kilometre"
  },
  "value": 5,
  "to": {
    "symbol": "mi",
    "name": "mile"
  },
  "result": 3.10685596119,
  "formatted": "3.106855961 mi",
  "formula": "5 km = 3.106855961 mi",
  "oneUnit": "1 km = 0.6213711922 mi"
}
```

**Example**

```json
{
  "value": 100,
  "from": "°C",
  "to": "°F"
}
```

Result:

```json
{
  "result": 212
}
```

<a id="calc_zakat"></a>
### `calc_zakat` — Zakat calculator

Zakat on wealth: 2.5 % of net zakatable wealth (cash, bank, investments, trade goods, money owed to you, gold and silver, minus debts due now) when it reaches the nisab: the value of 85 g of pure gold (default) or 595 g of silver. Gold weight is converted to pure gold by karat (24/22/21/18). You give today's gold/silver price per gram in your currency: nothing is fetched from the network. An estimate only; consult a scholar. Web version: https://s3eidat.github.io/zakat-calc/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `cash` | number \| string | no | Cash in hand. |
| `bank` | number \| string | no | Bank balances. |
| `investments` | number \| string | no | Shares, funds and other investments. |
| `tradeGoods` | number \| string | no | Value of goods held for sale. |
| `receivables` | number \| string | no | Money owed to you that you expect back. |
| `debts` | number \| string | no | Debts you must pay now (deducted). |
| `goldGrams` | number \| string | no | Gold you own, in grams. |
| `goldKarat` | 24 \| 22 \| 21 \| 18 | no (default `21`) | Karat of that gold (default 21). |
| `goldPricePerGram` | number \| string | no | Price of 1 g of pure (24k) gold. Needed for the gold nisab and to value gold. |
| `silverGrams` | number \| string | no | Silver you own, in grams. |
| `silverPricePerGram` | number \| string | no | Price of 1 g of silver. Needed for the silver nisab and to value silver. |
| `nisabBasis` | "gold" \| "silver" | no (default `"gold"`) | Which metal sets the nisab. |
| `currency` | string | no | Currency label for the summary (e.g. JOD). |

**Output**

| Field | Type | Description |
|---|---|---|
| `pureGoldGrams` | number |  |
| `goldValue` | number |  |
| `silverValue` | number |  |
| `totalAssets` | number |  |
| `netWealth` | number |  |
| `nisabBasis` | "gold" \| "silver" |  |
| `nisab` | number \| null |  |
| `due` | boolean |  |
| `zakat` | number |  |
| `shortOfNisab` | number |  |
| `message` | string |  |
| `summary` | string |  |

**Example** — net 18250, nisab 5100, zakat 456.25

```json
{
  "cash": 5000,
  "bank": 10000,
  "goldGrams": 100,
  "goldKarat": 21,
  "goldPricePerGram": 60,
  "debts": 2000
}
```

Result:

```json
{
  "pureGoldGrams": 87.5,
  "goldValue": 5250,
  "silverValue": 0,
  "totalAssets": 20250,
  "netWealth": 18250,
  "nisabBasis": "gold",
  "nisab": 5100,
  "due": true,
  "zakat": 456.25,
  "message": "456.25 is due: 2.5% of 18,250.00, above the nisab of 5,100.00.",
  "summary": "Zakat calculation\nNet zakatable wealth: 18,250.00\nNisab (gold): 5,100.00\nZakat due (2.5%): 456.25\n(Estimate only; consult a scholar.)"
}
```

<a id="everyday"></a>
## Everyday & security

Prayer times, qibla, world clock, random picks, Secret Santa, passwords, encryption.

| Tool | What it does |
|---|---|
| [`crypto_file_decrypt`](#crypto_file_decrypt) | Decrypt a file (.s3enc) |
| [`crypto_file_encrypt`](#crypto_file_encrypt) | Encrypt a file (.s3enc) |
| [`crypto_stego_hide`](#crypto_stego_hide) | Hide a message in an image |
| [`crypto_stego_reveal`](#crypto_stego_reveal) | Reveal a hidden message |
| [`crypto_text_decrypt`](#crypto_text_decrypt) | Decrypt text (AES-256-GCM) |
| [`crypto_text_encrypt`](#crypto_text_encrypt) | Encrypt text (AES-256-GCM) |
| [`password_check`](#password_check) | Password strength check |
| [`password_generate`](#password_generate) | Password & passphrase generator |
| [`prayer_times`](#prayer_times) | Prayer times |
| [`qibla_direction`](#qibla_direction) | Qibla direction |
| [`random_dice`](#random_dice) | Dice roller, coin flip, random numbers |
| [`random_pick`](#random_pick) | Wheel picker (random pick) |
| [`random_teams`](#random_teams) | Team maker |
| [`secret_santa_draw`](#secret_santa_draw) | Secret Santa draw |
| [`time_world_clock`](#time_world_clock) | World clock & meeting planner |

<a id="crypto_file_decrypt"></a>
### `crypto_file_decrypt` — Decrypt a file (.s3enc)

Decrypt an .s3enc file made by the S3eidat File Encrypt website or crypto_file_encrypt, verify every chunk, and write the original file back under its stored (sanitised) name. A wrong password, damage, truncation or reordering is reported and nothing is written. Web version: https://s3eidat.github.io/file-encrypt/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The .s3enc file: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `password` | string | yes | The password. Normalised to Unicode NFC first, like the website. |
| `output` | string | no | Where to write the decrypted file: a file path, or a folder ending in "/" (keeps the stored name). Default: the server output folder. |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `name` | string |  |
| `type` | string |  |
| `modified` | string |  |

**Example** — Writes report.pdf

```json
{
  "file": "/path/to/report.pdf.s3enc",
  "password": "correct horse battery staple"
}
```

<a id="crypto_file_encrypt"></a>
### `crypto_file_encrypt` — Encrypt a file (.s3enc)

Encrypt any file with a password into the website's .s3enc format: chunked AES-256-GCM, key from PBKDF2-SHA256 (600,000 rounds, random salt), original name/type/size stored encrypted inside, tamper/truncation/reorder-proof. Files made here open at https://s3eidat.github.io/file-encrypt/ and vice versa (crypto_file_decrypt). Whole file is processed in memory (size limit = server --max-mb). Web version: https://s3eidat.github.io/file-encrypt/

Hints: readOnly

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The file to encrypt: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `password` | string | yes | The password. Normalised to Unicode NFC first, like the website. |
| `name` | string | no | File name to store inside (restored on decrypt). Default: the input file name. |
| `mimeType` | string | no | MIME type to store. Default: guessed from the extension like a browser would ("" if unknown). |
| `chunkSize` | integer | no (default `4194304`) | Chunk size in bytes (website: 4 MiB). |
| `iterations` | integer | no (default `600000`) | PBKDF2 rounds (website: 600,000). |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `storedName` | string |  |
| `originalBytes` | number |  |
| `chunks` | number |  |

**Example** — Writes report.pdf.s3enc

```json
{
  "file": "/path/to/report.pdf",
  "password": "correct horse battery staple"
}
```

<a id="crypto_stego_hide"></a>
### `crypto_stego_hide` — Hide a message in an image

Hide a text message inside an image (LSB steganography in R, G, B; the picture looks the same) and save it as a lossless PNG, optionally encrypted with a password (AES-256-GCM, PBKDF2-SHA256 600,000). Same bit layout as the website, so the PNG reveals at https://s3eidat.github.io/stego-image/ or with crypto_stego_reveal. Capacity ≈ width×height×3/8 − 10 bytes (−44 more when encrypted). Transparency is filled with white. The result is read back and checked before returning. Sending it through apps that recompress images (JPEG, chat apps) destroys the message. Web version: https://s3eidat.github.io/stego-image/

Hints: readOnly

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The cover image (PNG, JPEG, WebP…): a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `message` | string | yes | The text to hide (UTF-8). |
| `password` | string | no | Optional password to encrypt the message. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `width` | number |  |
| `height` | number |  |
| `capacityBytes` | number |  |
| `usedBytes` | number |  |
| `encrypted` | boolean |  |
| `flattened` | boolean | True if transparency was filled with white. |
| `verified` | boolean |  |

**Example** — Writes photo-hidden.png

```json
{
  "image": "/path/to/photo.jpg",
  "message": "Meet at the old library at 7.",
  "password": "correct horse battery staple"
}
```

<a id="crypto_stego_reveal"></a>
### `crypto_stego_reveal` — Reveal a hidden message

Read a message hidden in a PNG by the S3eidat Stego Image website or crypto_stego_hide (decrypting it if a password was used). Reports when there is no message, e.g. because the image was recompressed (JPEG), resized or edited. Web version: https://s3eidat.github.io/stego-image/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The image with a hidden message (PNG): a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `password` | string | no | Password, if the message is encrypted. |

**Output**

| Field | Type | Description |
|---|---|---|
| `found` | boolean |  |
| `encrypted` | boolean |  |
| `message` | string |  |
| `characters` | number |  |
| `reason` | string |  |

**Example**

```json
{
  "image": "/path/to/photo-hidden.png",
  "password": "correct horse battery staple"
}
```

<a id="crypto_text_decrypt"></a>
### `crypto_text_decrypt` — Decrypt text (AES-256-GCM)

Decrypt an "S3E1.…" message made by the S3eidat Text Encrypt website or crypto_text_encrypt, with its password. Verifies integrity: a wrong password or any change to the text is reported, never garbage output. Accepts base64url and line breaks. Web version: https://s3eidat.github.io/text-encrypt/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `encrypted` | string | no | The encrypted text, starting with S3E1. |
| `path` | string | no | Or: a file containing it (e.g. encrypted.txt). |
| `password` | string | yes | The password. Normalised to Unicode NFC first, like the website. |
| `output` | string | no | Optional: also save the decrypted message to this file. |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string |  |
| `iterations` | number |  |
| `path` | string |  |

**Example**

```json
{
  "encrypted": "S3E1.AQEACSfAtcIyfKiQlnccWg4e2uFxBglGUizyF0IsTYNU0kSqKpIDX4xsscpbHaTSv0AGmLwAcP3l341prQNNoeukuzER22r7",
  "password": "s3eidat"
}
```

Result:

```json
{
  "text": "Hello, مرحبا 👋",
  "iterations": 600000
}
```

<a id="crypto_text_encrypt"></a>
### `crypto_text_encrypt` — Encrypt text (AES-256-GCM)

Encrypt a message with a password: AES-256-GCM, key from PBKDF2-SHA256 (600,000 rounds, random 16-byte salt, 12-byte IV), output as one "S3E1.…" base64 line. Exactly the website's format, so the result can be decrypted at https://s3eidat.github.io/text-encrypt/ or with crypto_text_decrypt. Each run gives a different output. Web version: https://s3eidat.github.io/text-encrypt/

Hints: readOnly

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The message to encrypt. |
| `path` | string | no | Or: a UTF-8 text file to encrypt. |
| `password` | string | yes | The password. Normalised to Unicode NFC first, like the website. |
| `iterations` | integer | no (default `600000`) | PBKDF2 rounds (website: 600,000). |
| `output` | string | no | Optional: also save the encrypted line to this file (website "Save" gives encrypted.txt). |

**Output**

| Field | Type | Description |
|---|---|---|
| `encrypted` | string |  |
| `bytes` | number | Plaintext size in UTF-8 bytes. |
| `path` | string |  |

**Example** — → "S3E1.AQEACSfA…"

```json
{
  "text": "Meet at the old library, 7 pm.",
  "password": "correct horse battery staple"
}
```

<a id="password_check"></a>
### `password_check` — Password strength check

Estimate how strong a password really is, the way attackers guess (zxcvbn-style): finds common passwords (1,282-entry list), dictionary words (also reversed, capitalised or l33t), keyboard walks, sequences, repeats, years and dates, then the cheapest way to guess the whole thing. Returns a 0–4 score, entropy bits, guesses, crack time for 4 attack scenarios, the pieces found, and advice. Offline only: no breach lookups, nothing stored or logged. Web version: https://s3eidat.github.io/password-check/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `password` | string | yes | The password to check (only the first 256 characters are analysed in detail). |
| `showPieces` | boolean | no (default `true`) | Include the matched pieces (they contain parts of the password). |

**Output**

| Field | Type | Description |
|---|---|---|
| `score` | number | 0 = very weak … 4 = very strong. |
| `label` | string |  |
| `bits` | number | log2 of the estimated number of guesses. |
| `guessesLog10` | number |  |
| `crackTimes` | object[] |  |
| `pieces` | object[] |  |
| `tips` | string[] |  |
| `stats` | object |  |

**Example** — Weak: dictionary word + year + "!"

```json
{
  "password": "Summer2024!"
}
```

Result:

```json
{
  "score": 1,
  "label": "Weak",
  "bits": 26.6,
  "guessesLog10": 8,
  "crackTimes": [
    {
      "attack": "Online, rate-limited (100 / hour)",
      "time": "1 century"
    },
    {
      "attack": "Online, no limit (10 / second)",
      "time": "4 months"
    },
    {
      "attack": "Offline, slow hash (10k / second, bcrypt)",
      "time": "3 hours"
    },
    {
      "attack": "Offline, fast hash (10B / second, GPU, MD5)",
      "time": "instant"
    }
  ],
  "tips": [
    "It contains a very common password. Attackers try those, and variations of them, first.",
    "Capitalizing only the first letter barely helps.",
    "Years and dates are easy to guess, especially birthdays and anniversaries.",
    "Ending with ! or 1 is the most common trick, so attackers expect it.",
    "Use at least 12–16 characters. Length beats complexity.",
    "Try a passphrase of 5–6 random words (a generator picks better than people do).",
    "Use a different password for every site. A password manager makes that painless."
  ],
  "stats": {
    "characters": 11,
    "characterTypes": 4,
    "piecesFound": 3
  },
  "pieces": [
    {
      "token": "Summer",
      "type": "common",
      "note": "common password #80",
      "bits": 7.3
    },
    {
      "token": "2024",
      "type": "year",
      "note": "year",
      "bits": 4.3
    },
    {
      "token": "!",
      "type": "bruteforce",
      "note": "random characters",
      "bits": 6.6
    }
  ]
}
```

**Example** — Strong passphrase (> 50 bits)

```json
{
  "password": "correct horse battery staple"
}
```

Result:

```json
{
  "score": 4,
  "label": "Very strong",
  "bits": 126.2,
  "guessesLog10": 38,
  "crackTimes": [
    {
      "attack": "Online, rate-limited (100 / hour)",
      "time": "10^32 years"
    },
    {
      "attack": "Online, no limit (10 / second)",
      "time": "10^29 years"
    },
    {
      "attack": "Offline, slow hash (10k / second, bcrypt)",
      "time": "10^26 years"
    },
    {
      "attack": "Offline, fast hash (10B / second, GPU, MD5)",
      "time": "10^20 years"
    }
  ],
  "tips": [
    "Repeats like aaa or abcabc add very little over the part being repeated.",
    "Excellent. Keep it unique to one account so a leak elsewhere can't unlock it.",
    "Use a different password for every site. A password manager makes that painless."
  ],
  "stats": {
    "characters": 28,
    "characterTypes": 2,
    "piecesFound": 6
  },
  "pieces": [
    {
      "token": "correct",
      "type": "word",
      "note": "dictionary word",
      "bits": 14.3
    },
    {
      "token": " ",
      "type": "bruteforce",
      "note": "random characters",
      "bits": 5.9
    },
    {
      "token": "horse",
      "type": "word",
      "note": "dictionary word",
      "bits": 14.3
    },
    {
      "token": " ba",
      "type": "bruteforce",
      "note": "random characters",
      "bits": 17.6
    },
    {
      "token": "tt",
      "type": "repeat",
      "note": "repeat ×2",
      "bits": 5.8
    },
    {
      "token": "ery staple",
      "type": "bruteforce",
      "note": "random characters",
      "bits": 58.8
    }
  ]
}
```

<a id="password_generate"></a>
### `password_generate` — Password & passphrase generator

Generate strong random passwords (8–128 chars; lower/upper/digits/symbols; skip look-alikes Il1|O0o; exclude your own characters; require every chosen set) or passphrases (3–12 words from the website's 2,381-word list, separator, capitalise, add a number). Returns 1–1000 results with exact entropy bits, a strength grade and the time to crack at 100 billion guesses/s. Always crypto random (OS CSPRNG, rejection sampling); nothing is stored or logged. Web version: https://s3eidat.github.io/pass-forge/

Hints: readOnly

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "password" \| "passphrase" | no (default `"password"`) | Random characters or random words. |
| `length` | integer | no (default `20`) | password: number of characters. |
| `lower` | boolean | no (default `true`) | password: include a–z. |
| `upper` | boolean | no (default `true`) | password: include A–Z. |
| `digits` | boolean | no (default `true`) | password: include 0–9. |
| `symbols` | boolean | no (default `true`) | password: include symbols !#$%&*+-=?@^_~()[]{}<>.,:;/\| |
| `avoidLookalikes` | boolean | no (default `true`) | password: leave out I l 1 \| O 0 o. |
| `requireEachSet` | boolean | no (default `true`) | password: at least one character from every chosen set. |
| `exclude` | string | no (default `""`) | password: characters never to use. |
| `words` | integer | no (default `6`) | passphrase: number of words. |
| `separator` | "-" \| " " \| "." \| "_" \| "" | no (default `"-"`) | passphrase: between words ("" = none, words then capitalised). |
| `capitalize` | boolean | no (default `false`) | passphrase: capitalise each word. |
| `addNumber` | boolean | no (default `false`) | passphrase: append a number 0–99 to one random word. |
| `count` | integer | no (default `1`) | How many to generate. |

**Output**

| Field | Type | Description |
|---|---|---|
| `passwords` | string[] |  |
| `bits` | number | Entropy in bits (exact for the chosen options). |
| `strength` | string |  |
| `crackTime` | string | Average time to guess at 100 billion guesses per second. |
| `poolSize` | number | password: number of possible characters. |
| `wordListSize` | number | passphrase: words in the list. |
| `note` | string |  |

**Example** — 20 chars from 83 possible, ≈127.3 bits, "Very strong"

```json
{
  "length": 20
}
```

**Example** — e.g. "maple-orbit-velvet-harbor-pencil-river", ≈67.3 bits

```json
{
  "mode": "passphrase",
  "words": 6,
  "separator": "-"
}
```

<a id="prayer_times"></a>
### `prayer_times` — Prayer times

Islamic prayer times (Fajr, Sunrise, Dhuhr, Asr, Maghrib, Isha) for any coordinates + IANA time zone, or a known city, for one day, several days or a whole month, plus the Hijri (Umm al-Qura) date and the next prayer with a countdown. Same astronomy as the website (adhan.js): 13 methods (Jordan Awqaf, MWL, Umm al-Qura, Egyptian, ISNA, Karachi, Dubai, Kuwait, Qatar, Turkey, Singapore, Tehran, Moonsighting), Shafi'i or Hanafi Asr, and the recommended high-latitude rule beyond 48° (overridable). Times are local HH:MM (24 h) in the given zone, plus UTC ISO. Coordinates are rounded to 4 decimals like the site. Web version: https://s3eidat.github.io/prayer-times/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `city` | string | no | A known city (sets coordinates, time zone and its usual method): amman, irbid, zarqa, aqaba, mecca, medina, riyadh, jeddah, jerusalem, gaza, damascus, beirut, baghdad, cairo, alexandria, dubai, abudhabi, doha, kuwait, manama, muscat, istanbul, casablanca, tunis, algiers, khartoum, karachi, lahore, dhaka, delhi, kualalumpur, jakarta, tehran, london, paris, berlin, newyork, chicago, toronto, losangeles, sydney. |
| `latitude` | number | no | Latitude in degrees (−90..90). Use with longitude and timezone. |
| `longitude` | number | no | Longitude in degrees (−180..180). |
| `timezone` | string | no | IANA time zone of the place, e.g. "Asia/Amman" (required with coordinates; overrides the city's). |
| `date` | string | no | First day, YYYY-MM-DD in the place's calendar. Default: today there. |
| `days` | integer | no (default `1`) | How many consecutive days to return. |
| `month` | string | no | Or a whole month timetable, YYYY-MM (overrides date/days). |
| `method` | "jordan" \| "mwl" \| "ummalqura" \| "egyptian" \| "isna" \| "karachi" \| "dubai" \| "kuwait" \| "qatar" \| "turkey" \| "singapore" \| "tehran" \| "moonsighting" | no | Calculation method. Default: the city's usual method, else "mwl". |
| `asr` | "shafi" \| "hanafi" | no | Asr school: "shafi" (standard: Shafi'i, Maliki, Hanbali) or "hanafi". Default shafi (hanafi for Karachi-method cities). |
| `highLatitudeRule` | "auto" \| "middleofthenight" \| "seventhofthenight" \| "twilightangle" | no (default `"auto"`) | "auto" = website behaviour (recommended rule when \|latitude\| > 48°). |
| `now` | string | no | Reference instant (ISO 8601) for "today" and the next prayer. Default: the current time. |

**Output**

| Field | Type | Description |
|---|---|---|
| `place` | string |  |
| `latitude` | number |  |
| `longitude` | number |  |
| `timezone` | string |  |
| `method` | object |  |
| `asr` | string |  |
| `highLatitudeRule` | string \| null |  |
| `days` | object[] |  |
| `next` | object |  |

**Example** — Dhuhr 12:47 (09:47 UTC)

```json
{
  "city": "amman",
  "date": "2026-01-15",
  "method": "mwl"
}
```

Result:

```json
{
  "place": "Amman",
  "latitude": 31.9539,
  "longitude": 35.9106,
  "timezone": "Asia/Amman",
  "method": {
    "id": "mwl",
    "name": "Muslim World League"
  },
  "asr": "shafi",
  "highLatitudeRule": null,
  "days": [
    {
      "date": "2026-01-15",
      "weekday": "Thursday",
      "hijri": "Rajab 26, 1447 AH",
      "hijriAr": "26 رجب 1447 هـ",
      "times": {
        "fajr": "06:11",
        "sunrise": "07:37",
        "dhuhr": "12:47",
        "asr": "15:36",
        "maghrib": "17:55",
        "isha": "19:16"
      },
      "utc": {
        "fajr": "2026-01-15T03:11:00.000Z",
        "sunrise": "2026-01-15T04:37:00.000Z",
        "dhuhr": "2026-01-15T09:47:00.000Z",
        "asr": "2026-01-15T12:36:00.000Z",
        "maghrib": "2026-01-15T14:55:00.000Z",
        "isha": "2026-01-15T16:16:00.000Z"
      }
    }
  ],
  "next": {
    "prayer": "dhuhr",
    "time": "12:47",
    "utc": "2026-01-15T09:47:00.000Z",
    "tomorrow": false,
    "countdown": "00:17:00",
    "minutes": 17
  }
}
```

**Example** — London monthly timetable, MWL, seventh-of-the-night rule

```json
{
  "latitude": 51.5074,
  "longitude": -0.1278,
  "timezone": "Europe/London",
  "month": "2026-03"
}
```

Result:

```json
{
  "latitude": 51.5074,
  "longitude": -0.1278,
  "timezone": "Europe/London",
  "method": {
    "id": "mwl",
    "name": "Muslim World League"
  },
  "asr": "shafi",
  "highLatitudeRule": "seventhofthenight",
  "days": [
    {
      "date": "2026-03-01",
      "weekday": "Sunday",
      "hijri": "Ramadan 12, 1447 AH",
      "hijriAr": "12 رمضان 1447 هـ",
      "times": {
        "fajr": "04:55",
        "sunrise": "06:46",
        "dhuhr": "12:14",
        "asr": "15:05",
        "maghrib": "17:41",
        "isha": "19:25"
      },
      "utc": {
        "fajr": "2026-03-01T04:55:00.000Z",
        "sunrise": "2026-03-01T06:46:00.000Z",
        "dhuhr": "2026-03-01T12:14:00.000Z",
        "asr": "2026-03-01T15:05:00.000Z",
        "maghrib": "2026-03-01T17:41:00.000Z",
        "isha": "2026-03-01T19:25:00.000Z"
      }
    },
    {
      "date": "2026-03-02",
      "weekday": "Monday",
      "hijri": "Ramadan 13, 1447 AH",
      "hijriAr": "13 رمضان 1447 هـ",
      "times": {
        "fajr": "04:53",
        "sunrise": "06:44",
        "dhuhr": "12:14",
        "asr": "15:06",
        "maghrib": "17:42",
        "isha": "19:27"
      },
      "utc": {
        "fajr": "2026-03-02T04:53:00.000Z",
        "sunrise": "2026-03-02T06:44:00.000Z",
        "dhuhr": "2026-03-02T12:14:00.000Z",
        "asr": "2026-03-02T15:06:00.000Z",
        "maghrib": "2026-03-02T17:42:00.000Z",
        "isha": "2026-03-02T19:27:00.000Z"
      }
    },
    {
      "date": "2026-03-03",
      "weekday": "Tuesday",
      "hijri": "Ramadan 14, 1447 AH",
      "hijriAr": "14 رمضان 1447 هـ",
      "times": {
        "fajr": "04:51",
        "sunrise": "06:41",
        "dhuhr": "12:13",
        "asr": "15:07",
        "maghrib": "17:44",
        "isha": "19:29"
      },
      "utc": {
        "fajr": "2026-03-03T04:51:00.714Z",
        "sunrise": "2026-03-03T06:41:00.000Z",
        "dhuhr": "2026-03-03T12:13:00.000Z",
        "asr": "2026-03-03T15:07:00.000Z",
        "maghrib": "2026-03-03T17:44:00.000Z",
        "isha": "2026-03-03T19:29:00.000Z"
      }
    },
    {
      "date": "2026-03-04",
      "weekday": "Wednesday",
      "hijri": "Ramadan 15, 1447 AH",
      "hijriAr": "15 رمضان 1447 هـ",
      "times": {
        "fajr": "04:49",
        "sunrise": "06:39",
        "dhuhr": "12:13",
        "asr": "15:09",
        "maghrib": "17:46",
        "isha": "19:31"
      },
      "utc": {
        "fajr": "2026-03-04T04:49:00.571Z",
        "sunrise": "2026-03-04T06:39:00.000Z",
        "dhuhr": "2026-03-04T12:13:00.000Z",
        "asr": "2026-03-04T15:09:00.000Z",
        "maghrib": "2026-03-04T17:46:00.000Z",
        "isha": "2026-03-04T19:31:00.000Z"
      }
    },
    {
      "date": "2026-03-05",
      "weekday": "Thursday",
      "hijri": "Ramadan 16, 1447 AH",
      "hijriAr": "16 رمضان 1447 هـ",
      "times": {
        "fajr": "04:48",
        "sunrise": "06:37",
        "dhuhr": "12:13",
        "asr": "15:10",
        "maghrib": "17:48",
        "isha": "19:32"
      },
      "utc": {
        "fajr": "2026-03-05T04:48:00.428Z",
        "sunrise": "2026-03-05T06:37:00.000Z",
        "dhuhr": "2026-03-05T12:13:00.000Z",
        "asr": "2026-03-05T15:10:00.000Z",
        "maghrib": "2026-03-05T17:48:00.000Z",
        "isha": "2026-03-05T19:32:00.000Z"
      }
    },
    {
      "date": "2026-03-06",
      "weekday": "Friday",
      "hijri": "Ramadan 17, 1447 AH",
      "hijriAr": "17 رمضان 1447 هـ",
      "times": {
        "fajr": "04:46",
        "sunrise": "06:35",
        "dhuhr": "12:13",
        "asr": "15:11",
        "maghrib": "17:50",
        "isha": "19:34"
      },
      "utc": {
        "fajr": "2026-03-06T04:46:00.285Z",
        "sunrise": "2026-03-06T06:35:00.000Z",
        "dhuhr": "2026-03-06T12:13:00.000Z",
        "asr": "2026-03-06T15:11:00.000Z",
        "maghrib": "2026-03-06T17:50:00.000Z",
        "isha": "2026-03-06T19:34:00.000Z"
      }
    },
    {
      "date": "2026-03-07",
      "weekday": "Saturday",
      "hijri": "Ramadan 18, 1447 AH",
      "hijriAr": "18 رمضان 1447 هـ",
      "times": {
        "fajr": "04:44",
        "sunrise": "06:33",
        "dhuhr": "12:13",
        "asr": "15:12",
        "maghrib": "17:51",
        "isha": "19:36"
      },
      "utc": {
        "fajr": "2026-03-07T04:44:00.285Z",
        "sunrise": "2026-03-07T06:33:00.000Z",
        "dhuhr": "2026-03-07T12:13:00.000Z",
        "asr": "2026-03-07T15:12:00.000Z",
        "maghrib": "2026-03-07T17:51:00.000Z",
        "isha": "2026-03-07T19:36:00.000Z"
      }
    },
    {
      "date": "2026-03-08",
      "weekday": "Sunday",
      "hijri": "Ramadan 19, 1447 AH",
      "hijriAr": "19 رمضان 1447 هـ",
      "times": {
        "fajr": "04:43",
        "sunrise": "06:30",
        "dhuhr": "12:12",
        "asr": "15:13",
        "maghrib": "17:53",
        "isha": "19:38"
      },
      "utc": {
        "fajr": "2026-03-08T04:43:00.142Z",
        "sunrise": "2026-03-08T06:30:00.000Z",
        "dhuhr": "2026-03-08T12:12:00.000Z",
        "asr": "2026-03-08T15:13:00.000Z",
        "maghrib": "2026-03-08T17:53:00.000Z",
        "isha": "2026-03-08T19:38:00.000Z"
      }
    },
    {
      "date": "2026-03-09",
      "weekday": "Monday",
      "hijri": "Ramadan 20, 1447 AH",
      "hijriAr": "20 رمضان 1447 هـ",
      "times": {
        "fajr": "04:41",
        "sunrise": "06:28",
        "dhuhr": "12:12",
        "asr": "15:15",
        "maghrib": "17:55",
        "isha": "19:40"
      },
      "utc": {
        "fajr": "2026-03-09T04:41:00.285Z",
        "sunrise": "2026-03-09T06:28:00.000Z",
        "dhuhr": "2026-03-09T12:12:00.000Z",
        "asr": "2026-03-09T15:15:00.000Z",
        "maghrib": "2026-03-09T17:55:00.000Z",
        "isha": "2026-03-09T19:40:00.000Z"
      }
    },
    {
      "date": "2026-03-10",
      "weekday": "Tuesday",
      "hijri": "Ramadan 21, 1447 AH",
      "hijriAr": "21 رمضان 1447 هـ",
      "times": {
        "fajr": "04:39",
        "sunrise": "06:26",
        "dhuhr": "12:12",
        "asr": "15:16",
        "maghrib": "17:56",
        "isha": "19:42"
      },
      "utc": {
        "fajr": "2026-03-10T04:39:00.142Z",
        "sunrise": "2026-03-10T06:26:00.000Z",
        "dhuhr": "2026-03-10T12:12:00.000Z",
        "asr": "2026-03-10T15:16:00.000Z",
        "maghrib": "2026-03-10T17:56:00.000Z",
        "isha": "2026-03-10T19:42:00.000Z"
      }
    },
    {
      "date": "2026-03-11",
      "weekday": "Wednesday",
      "hijri": "Ramadan 22, 1447 AH",
      "hijriAr": "22 رمضان 1447 هـ",
      "times": {
        "fajr": "04:38",
        "sunrise": "06:24",
        "dhuhr": "12:12",
        "asr": "15:17",
        "maghrib": "17:58",
        "isha": "19:43"
      },
      "utc": {
        "fajr": "2026-03-11T04:38:00.285Z",
        "sunrise": "2026-03-11T06:24:00.000Z",
        "dhuhr": "2026-03-11T12:12:00.000Z",
        "asr": "2026-03-11T15:17:00.000Z",
        "maghrib": "2026-03-11T17:58:00.000Z",
        "isha": "2026-03-11T19:43:00.000Z"
      }
    },
    {
      "date": "2026-03-12",
      "weekday": "Thursday",
      "hijri": "Ramadan 23, 1447 AH",
      "hijriAr": "23 رمضان 1447 هـ",
      "times": {
        "fajr": "04:36",
        "sunrise": "06:22",
        "dhuhr": "12:11",
        "asr": "15:18",
        "maghrib": "18:00",
        "isha": "19:45"
      },
      "utc": {
        "fajr": "2026-03-12T04:36:00.285Z",
        "sunrise": "2026-03-12T06:22:00.000Z",
        "dhuhr": "2026-03-12T12:11:00.000Z",
        "asr": "2026-03-12T15:18:00.000Z",
        "maghrib": "2026-03-12T18:00:00.000Z",
        "isha": "2026-03-12T19:45:00.000Z"
      }
    },
    {
      "date": "2026-03-13",
      "weekday": "Friday",
      "hijri": "Ramadan 24, 1447 AH",
      "hijriAr": "24 رمضان 1447 هـ",
      "times": {
        "fajr": "04:34",
        "sunrise": "06:19",
        "dhuhr": "12:11",
        "asr": "15:19",
        "maghrib": "18:02",
        "isha": "19:47"
      },
      "utc": {
        "fajr": "2026-03-13T04:34:00.428Z",
        "sunrise": "2026-03-13T06:19:00.000Z",
        "dhuhr": "2026-03-13T12:11:00.000Z",
        "asr": "2026-03-13T15:19:00.000Z",
        "maghrib": "2026-03-13T18:02:00.000Z",
        "isha": "2026-03-13T19:47:00.571Z"
      }
    },
    {
      "date": "2026-03-14",
      "weekday": "Saturday",
      "hijri": "Ramadan 25, 1447 AH",
      "hijriAr": "25 رمضان 1447 هـ",
      "times": {
        "fajr": "04:33",
        "sunrise": "06:17",
        "dhuhr": "12:11",
        "asr": "15:20",
        "maghrib": "18:03",
        "isha": "19:48"
      },
      "utc": {
        "fajr": "2026-03-14T04:33:00.571Z",
        "sunrise": "2026-03-14T06:17:00.000Z",
        "dhuhr": "2026-03-14T12:11:00.000Z",
        "asr": "2026-03-14T15:20:00.000Z",
        "maghrib": "2026-03-14T18:03:00.000Z",
        "isha": "2026-03-14T19:48:00.428Z"
      }
    },
    {
      "date": "2026-03-15",
      "weekday": "Sunday",
      "hijri": "Ramadan 26, 1447 AH",
      "hijriAr": "26 رمضان 1447 هـ",
      "times": {
        "fajr": "04:31",
        "sunrise": "06:15",
        "dhuhr": "12:10",
        "asr": "15:21",
        "maghrib": "18:05",
        "isha": "19:49"
      },
      "utc": {
        "fajr": "2026-03-15T04:31:00.714Z",
        "sunrise": "2026-03-15T06:15:00.000Z",
        "dhuhr": "2026-03-15T12:10:00.000Z",
        "asr": "2026-03-15T15:21:00.000Z",
        "maghrib": "2026-03-15T18:05:00.000Z",
        "isha": "2026-03-15T19:49:00.285Z"
      }
    },
    {
      "date": "2026-03-16",
      "weekday": "Monday",
      "hijri": "Ramadan 27, 1447 AH",
      "hijriAr": "27 رمضان 1447 هـ",
      "times": {
        "fajr": "04:29",
        "sunrise": "06:12",
        "dhuhr": "12:10",
        "asr": "15:22",
        "maghrib": "18:07",
        "isha": "19:50"
      },
      "utc": {
        "fajr": "2026-03-16T04:29:00.714Z",
        "sunrise": "2026-03-16T06:12:00.000Z",
        "dhuhr": "2026-03-16T12:10:00.000Z",
        "asr": "2026-03-16T15:22:00.000Z",
        "maghrib": "2026-03-16T18:07:00.000Z",
        "isha": "2026-03-16T19:50:00.285Z"
      }
    },
    {
      "date": "2026-03-17",
      "weekday": "Tuesday",
      "hijri": "Ramadan 28, 1447 AH",
      "hijriAr": "28 رمضان 1447 هـ",
      "times": {
        "fajr": "04:27",
        "sunrise": "06:10",
        "dhuhr": "12:10",
        "asr": "15:23",
        "maghrib": "18:08",
        "isha": "19:51"
      },
      "utc": {
        "fajr": "2026-03-17T04:27:00.714Z",
        "sunrise": "2026-03-17T06:10:00.000Z",
        "dhuhr": "2026-03-17T12:10:00.000Z",
        "asr": "2026-03-17T15:23:00.000Z",
        "maghrib": "2026-03-17T18:08:00.000Z",
        "isha": "2026-03-17T19:51:00.285Z"
      }
    },
    {
      "date": "2026-03-18",
      "weekday": "Wednesday",
      "hijri": "Ramadan 29, 1447 AH",
      "hijriAr": "29 رمضان 1447 هـ",
      "times": {
        "fajr": "04:26",
        "sunrise": "06:08",
        "dhuhr": "12:10",
        "asr": "15:25",
        "maghrib": "18:10",
        "isha": "19:52"
      },
      "utc": {
        "fajr": "2026-03-18T04:26:00.857Z",
        "sunrise": "2026-03-18T06:08:00.000Z",
        "dhuhr": "2026-03-18T12:10:00.000Z",
        "asr": "2026-03-18T15:25:00.000Z",
        "maghrib": "2026-03-18T18:10:00.000Z",
        "isha": "2026-03-18T19:52:00.142Z"
      }
    },
    {
      "date": "2026-03-19",
      "weekday": "Thursday",
      "hijri": "Ramadan 30, 1447 AH",
      "hijriAr": "30 رمضان 1447 هـ",
      "times": {
        "fajr": "04:24",
        "sunrise": "06:06",
        "dhuhr": "12:09",
        "asr": "15:26",
        "maghrib": "18:12",
        "isha": "19:53"
      },
      "utc": {
        "fajr": "2026-03-19T04:24:00.000Z",
        "sunrise": "2026-03-19T06:06:00.000Z",
        "dhuhr": "2026-03-19T12:09:00.000Z",
        "asr": "2026-03-19T15:26:00.000Z",
        "maghrib": "2026-03-19T18:12:00.000Z",
        "isha": "2026-03-19T19:53:00.000Z"
      }
    },
    {
      "date": "2026-03-20",
      "weekday": "Friday",
      "hijri": "Shawwal 1, 1447 AH",
      "hijriAr": "1 شوال 1447 هـ",
      "times": {
        "fajr": "04:22",
        "sunrise": "06:03",
        "dhuhr": "12:09",
        "asr": "15:27",
        "maghrib": "18:14",
        "isha": "19:55"
      },
      "utc": {
        "fajr": "2026-03-20T04:22:00.142Z",
        "sunrise": "2026-03-20T06:03:00.000Z",
        "dhuhr": "2026-03-20T12:09:00.000Z",
        "asr": "2026-03-20T15:27:00.000Z",
        "maghrib": "2026-03-20T18:14:00.000Z",
        "isha": "2026-03-20T19:55:00.857Z"
      }
    },
    {
      "date": "2026-03-21",
      "weekday": "Saturday",
      "hijri": "Shawwal 2, 1447 AH",
      "hijriAr": "2 شوال 1447 هـ",
      "times": {
        "fajr": "04:21",
        "sunrise": "06:01",
        "dhuhr": "12:09",
        "asr": "15:28",
        "maghrib": "18:15",
        "isha": "19:56"
      },
      "utc": {
        "fajr": "2026-03-21T04:21:00.285Z",
        "sunrise": "2026-03-21T06:01:00.000Z",
        "dhuhr": "2026-03-21T12:09:00.000Z",
        "asr": "2026-03-21T15:28:00.000Z",
        "maghrib": "2026-03-21T18:15:00.000Z",
        "isha": "2026-03-21T19:56:00.714Z"
      }
    },
    {
      "date": "2026-03-22",
      "weekday": "Sunday",
      "hijri": "Shawwal 3, 1447 AH",
      "hijriAr": "3 شوال 1447 هـ",
      "times": {
        "fajr": "04:19",
        "sunrise": "05:59",
        "dhuhr": "12:08",
        "asr": "15:29",
        "maghrib": "18:17",
        "isha": "19:57"
      },
      "utc": {
        "fajr": "2026-03-22T04:19:00.285Z",
        "sunrise": "2026-03-22T05:59:00.000Z",
        "dhuhr": "2026-03-22T12:08:00.000Z",
        "asr": "2026-03-22T15:29:00.000Z",
        "maghrib": "2026-03-22T18:17:00.000Z",
        "isha": "2026-03-22T19:57:00.714Z"
      }
    },
    {
      "date": "2026-03-23",
      "weekday": "Monday",
      "hijri": "Shawwal 4, 1447 AH",
      "hijriAr": "4 شوال 1447 هـ",
      "times": {
        "fajr": "04:17",
        "sunrise": "05:57",
        "dhuhr": "12:08",
        "asr": "15:30",
        "maghrib": "18:19",
        "isha": "19:58"
      },
      "utc": {
        "fajr": "2026-03-23T04:17:00.428Z",
        "sunrise": "2026-03-23T05:57:00.000Z",
        "dhuhr": "2026-03-23T12:08:00.000Z",
        "asr": "2026-03-23T15:30:00.000Z",
        "maghrib": "2026-03-23T18:19:00.000Z",
        "isha": "2026-03-23T19:58:00.571Z"
      }
    },
    {
      "date": "2026-03-24",
      "weekday": "Tuesday",
      "hijri": "Shawwal 5, 1447 AH",
      "hijriAr": "5 شوال 1447 هـ",
      "times": {
        "fajr": "04:15",
        "sunrise": "05:54",
        "dhuhr": "12:08",
        "asr": "15:31",
        "maghrib": "18:20",
        "isha": "19:59"
      },
      "utc": {
        "fajr": "2026-03-24T04:15:00.428Z",
        "sunrise": "2026-03-24T05:54:00.000Z",
        "dhuhr": "2026-03-24T12:08:00.000Z",
        "asr": "2026-03-24T15:31:00.000Z",
        "maghrib": "2026-03-24T18:20:00.000Z",
        "isha": "2026-03-24T19:59:00.571Z"
      }
    },
    {
      "date": "2026-03-25",
      "weekday": "Wednesday",
      "hijri": "Shawwal 6, 1447 AH",
      "hijriAr": "6 شوال 1447 هـ",
      "times": {
        "fajr": "04:14",
        "sunrise": "05:52",
        "dhuhr": "12:07",
        "asr": "15:32",
        "maghrib": "18:22",
        "isha": "20:00"
      },
      "utc": {
        "fajr": "2026-03-25T04:14:00.285Z",
        "sunrise": "2026-03-25T05:52:00.000Z",
        "dhuhr": "2026-03-25T12:07:00.000Z",
        "asr": "2026-03-25T15:32:00.000Z",
        "maghrib": "2026-03-25T18:22:00.000Z",
        "isha": "2026-03-25T20:00:00.714Z"
      }
    },
    {
      "date": "2026-03-26",
      "weekday": "Thursday",
      "hijri": "Shawwal 7, 1447 AH",
      "hijriAr": "7 شوال 1447 هـ",
      "times": {
        "fajr": "04:12",
        "sunrise": "05:50",
        "dhuhr": "12:07",
        "asr": "15:32",
        "maghrib": "18:24",
        "isha": "20:01"
      },
      "utc": {
        "fajr": "2026-03-26T04:12:00.285Z",
        "sunrise": "2026-03-26T05:50:00.000Z",
        "dhuhr": "2026-03-26T12:07:00.000Z",
        "asr": "2026-03-26T15:32:00.000Z",
        "maghrib": "2026-03-26T18:24:00.000Z",
        "isha": "2026-03-26T20:01:00.714Z"
      }
    },
    {
      "date": "2026-03-27",
      "weekday": "Friday",
      "hijri": "Shawwal 8, 1447 AH",
      "hijriAr": "8 شوال 1447 هـ",
      "times": {
        "fajr": "04:10",
        "sunrise": "05:47",
        "dhuhr": "12:07",
        "asr": "15:33",
        "maghrib": "18:25",
        "isha": "20:02"
      },
      "utc": {
        "fajr": "2026-03-27T04:10:00.285Z",
        "sunrise": "2026-03-27T05:47:00.000Z",
        "dhuhr": "2026-03-27T12:07:00.000Z",
        "asr": "2026-03-27T15:33:00.000Z",
        "maghrib": "2026-03-27T18:25:00.000Z",
        "isha": "2026-03-27T20:02:00.714Z"
      }
    },
    {
      "date": "2026-03-28",
      "weekday": "Saturday",
      "hijri": "Shawwal 9, 1447 AH",
      "hijriAr": "9 شوال 1447 هـ",
      "times": {
        "fajr": "04:09",
        "sunrise": "05:45",
        "dhuhr": "12:07",
        "asr": "15:34",
        "maghrib": "18:27",
        "isha": "20:04"
      },
      "utc": {
        "fajr": "2026-03-28T04:09:00.285Z",
        "sunrise": "2026-03-28T05:45:00.000Z",
        "dhuhr": "2026-03-28T12:07:00.000Z",
        "asr": "2026-03-28T15:34:00.000Z",
        "maghrib": "2026-03-28T18:27:00.000Z",
        "isha": "2026-03-28T20:04:00.714Z"
      }
    },
    {
      "date": "2026-03-29",
      "weekday": "Sunday",
      "hijri": "Shawwal 10, 1447 AH",
      "hijriAr": "10 شوال 1447 هـ",
      "times": {
        "fajr": "05:07",
        "sunrise": "06:43",
        "dhuhr": "13:06",
        "asr": "16:35",
        "maghrib": "19:29",
        "isha": "21:05"
      },
      "utc": {
        "fajr": "2026-03-29T04:07:00.000Z",
        "sunrise": "2026-03-29T05:43:00.000Z",
        "dhuhr": "2026-03-29T12:06:00.000Z",
        "asr": "2026-03-29T15:35:00.000Z",
        "maghrib": "2026-03-29T18:29:00.000Z",
        "isha": "2026-03-29T20:05:00.000Z"
      }
    },
    {
      "date": "2026-03-30",
      "weekday": "Monday",
      "hijri": "Shawwal 11, 1447 AH",
      "hijriAr": "11 شوال 1447 هـ",
      "times": {
        "fajr": "05:05",
        "sunrise": "06:41",
        "dhuhr": "13:06",
        "asr": "16:36",
        "maghrib": "19:30",
        "isha": "21:06"
      },
      "utc": {
        "fajr": "2026-03-30T04:05:00.857Z",
        "sunrise": "2026-03-30T05:41:00.000Z",
        "dhuhr": "2026-03-30T12:06:00.000Z",
        "asr": "2026-03-30T15:36:00.000Z",
        "maghrib": "2026-03-30T18:30:00.000Z",
        "isha": "2026-03-30T20:06:00.142Z"
      }
    },
    {
      "date": "2026-03-31",
      "weekday": "Tuesday",
      "hijri": "Shawwal 12, 1447 AH",
      "hijriAr": "12 شوال 1447 هـ",
      "times": {
        "fajr": "05:03",
        "sunrise": "06:38",
        "dhuhr": "13:06",
        "asr": "16:37",
        "maghrib": "19:32",
        "isha": "21:07"
      },
      "utc": {
        "fajr": "2026-03-31T04:03:00.714Z",
        "sunrise": "2026-03-31T05:38:00.000Z",
        "dhuhr": "2026-03-31T12:06:00.000Z",
        "asr": "2026-03-31T15:37:00.000Z",
        "maghrib": "2026-03-31T18:32:00.000Z",
        "isha": "2026-03-31T20:07:00.285Z"
      }
    }
  ],
  "next": {
    "prayer": "dhuhr",
    "time": "12:11",
    "utc": "2026-01-15T12:11:00.000Z",
    "tomorrow": false,
    "countdown": "02:41:00",
    "minutes": 161
  }
}
```

<a id="qibla_direction"></a>
### `qibla_direction` — Qibla direction

Qibla direction (great-circle bearing from true north to the Kaaba in Makkah) and distance from any coordinates or a known city, with the compass point, a "face north, then turn right/left N°" instruction, and (if you give a device heading) how far to turn. Bearings are from TRUE north; a magnetic compass can differ by a few degrees. Web version: https://s3eidat.github.io/qibla-finder/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `latitude` | number | no | Latitude in degrees (−90..90). |
| `longitude` | number | no | Longitude in degrees (−180..180). |
| `city` | string | no | Or a known city: amman, irbid, zarqa, aqaba, mecca, medina, riyadh, jeddah, jerusalem, gaza, damascus, beirut, baghdad, cairo, alexandria, dubai, abudhabi, doha, kuwait, manama, muscat, istanbul, casablanca, tunis, algiers, khartoum, karachi, lahore, dhaka, delhi, kualalumpur, jakarta, tehran, london, paris, berlin, newyork, chicago, toronto, losangeles, sydney. |
| `heading` | number | no | Optional: the direction you are facing now (degrees from north) to get a turn instruction. |

**Output**

| Field | Type | Description |
|---|---|---|
| `latitude` | number |  |
| `longitude` | number |  |
| `place` | string |  |
| `bearing` | number | Degrees clockwise from true north, 1 decimal. |
| `direction` | string | 16-point compass direction, e.g. SSE. |
| `directionAr` | string | 8-point compass direction in Arabic. |
| `distanceKm` | number | Great-circle distance to the Kaaba, rounded km. |
| `atKaaba` | boolean |  |
| `turnFromNorth` | object |  |
| `instruction` | string |  |
| `fromHeading` | object |  |

**Example** — Face north, then turn clockwise 160.7°

```json
{
  "city": "amman"
}
```

Result:

```json
{
  "bearing": 160.7,
  "direction": "SSE",
  "distanceKm": 1234
}
```

**Example** — London: about 119° (ESE)

```json
{
  "latitude": 51.5074,
  "longitude": -0.1278
}
```

Result:

```json
{
  "latitude": 51.5074,
  "longitude": -0.1278,
  "bearing": 119,
  "direction": "ESE",
  "directionAr": "جنوب شرق",
  "distanceKm": 4794,
  "atKaaba": false,
  "turnFromNorth": {
    "direction": "right",
    "degrees": 119
  },
  "instruction": "Face north, then turn clockwise (to your right) by 119°. You are then facing the Qibla (ESE)."
}
```

<a id="random_dice"></a>
### `random_dice` — Dice roller, coin flip, random numbers

Roll dice with standard notation (2d6+3, d20, d%, 4d6dl1 / "4d6 drop lowest", 2d20kh1 advantage, 2d20kl1, 5d10dh2; up to 100 dice per term, 2–1000 sides, 20 terms), flip coins (counts + streaks), or draw random whole numbers in a range (optionally unique and sorted, up to 10,000). Crypto random with rejection sampling (no modulo bias); optional seed for reproducible results. Web version: https://s3eidat.github.io/dice-roller/

Hints: readOnly

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "dice" \| "coin" \| "numbers" | no (default `"dice"`) | What to do. |
| `notation` | string | no (default `"2d6"`) | dice: the dice notation, e.g. "3d8+2d6+4". |
| `rolls` | integer | no (default `1`) | dice: how many times to roll the expression. |
| `flips` | integer | no (default `1`) | coin: how many coins to flip. |
| `min` | integer | no (default `1`) | numbers: smallest value (inclusive). |
| `max` | integer | no (default `100`) | numbers: largest value (inclusive). |
| `count` | integer | no (default `1`) | numbers: how many numbers. |
| `unique` | boolean | no (default `false`) | numbers: no repeats. |
| `sort` | boolean | no (default `false`) | numbers: sort ascending. |
| `seed` | string | no | Optional seed for a reproducible result (same seed + same input = same output). Omit for true crypto randomness. |

**Output**

| Field | Type | Description |
|---|---|---|
| `mode` | string |  |
| `notation` | string | dice: canonical form of the notation. |
| `results` | object[] |  |
| `coins` | object |  |
| `numbers` | number[] |  |

**Example** — e.g. total 13, breakdown "4d6dl1 [3, (1 dropped), 4, 6] = 13"

```json
{
  "notation": "4d6 drop lowest"
}
```

**Example**

```json
{
  "mode": "coin",
  "flips": 10
}
```

**Example** — Lottery-style draw

```json
{
  "mode": "numbers",
  "min": 1,
  "max": 49,
  "count": 6,
  "unique": true,
  "sort": true
}
```

<a id="random_pick"></a>
### `random_pick` — Wheel picker (random pick)

Pick a random winner (or several, without repeats, like spinning with "remove winner" on) from a list of names, or shuffle / sort the whole list. Fair: crypto random with rejection sampling. Entries are trimmed, cut to 100 characters, max 500; at least 2 needed. Optional seed for a reproducible draw. Web version: https://s3eidat.github.io/wheel-picker/

Hints: readOnly

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `items` | string[] | no | The entries, one per array item. |
| `text` | string | no | Or: the entries as text, one per line. |
| `mode` | "pick" \| "shuffle" \| "sort" | no (default `"pick"`) | "pick" winners, "shuffle" the list, or "sort" it A→Z. |
| `count` | integer | no (default `1`) | How many winners to pick (each winner is removed before the next spin). |
| `seed` | string | no | Optional seed for a reproducible result (same seed + same input = same output). Omit for true crypto randomness. |

**Output**

| Field | Type | Description |
|---|---|---|
| `entries` | number | Number of usable entries. |
| `winners` | object[] |  |
| `remaining` | string[] | Entries left on the wheel after removing the winners. |
| `list` | string[] | The shuffled or sorted list. |

**Example** — One random winner, e.g. { winners: [{ name: "Omar", index: 2 }] }

```json
{
  "items": [
    "Ali",
    "Sara",
    "Omar",
    "Lina"
  ]
}
```

**Example** — Three distinct winners, reproducible

```json
{
  "text": "Ali\nSara\nOmar\nLina\nHuda",
  "count": 3,
  "seed": "raffle-2026"
}
```

<a id="random_teams"></a>
### `random_teams` — Team maker

Split a list of people into fair random teams: choose the number of teams or people per team. Add a skill level ("Ali, 5" / "Sara: 3") and teams are balanced by total skill (people without one count as the average). Team names: numbers, letters, colours, animals or your own. Crypto-random shuffle; optional seed for a reproducible split. Max 2000 people, 100 teams. Web version: https://s3eidat.github.io/team-maker/

Hints: readOnly

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `people` | string[] | no | People, one per item, optionally "Name, skill" (skill 0–100). |
| `text` | string | no | Or: people as text, one per line. |
| `mode` | "teams" \| "per" | no (default `"teams"`) | "teams": make `num` teams; "per": `num` people per team. |
| `num` | integer | no (default `2`) | Number of teams (min 2) or people per team (min 1), see mode. |
| `balance` | boolean | no (default `true`) | Balance teams by skill when skills are given. |
| `naming` | "num" \| "letters" \| "colors" \| "animals" \| "custom" | no (default `"num"`) | Team names: Team 1…, Team A…, colours, animals, or customNames. |
| `customNames` | string[] | no | naming=custom: team names in order (missing ones fall back to "Team N"). |
| `seed` | string | no | Optional seed for a reproducible result (same seed + same input = same output). Omit for true crypto randomness. |

**Output**

| Field | Type | Description |
|---|---|---|
| `people` | number |  |
| `teams` | object[] |  |
| `text` | string | Plain-text list, as the website copies it. |

**Example** — Two balanced teams (Lions, Falcons)

```json
{
  "people": [
    "Ali, 5",
    "Sara, 4",
    "Omar, 3",
    "Lina, 5",
    "Yousef, 2",
    "Noor, 4"
  ],
  "num": 2,
  "naming": "animals"
}
```

<a id="secret_santa_draw"></a>
### `secret_santa_draw` — Secret Santa draw

Draw Secret Santa (gift exchange) pairs: everyone gives to exactly one other person and receives from one, never themselves. Exclusions (couples, housemates…) block a pair both ways. Checks exactly whether a draw is possible and explains why not (and which exclusion to drop). Crypto random and uniform; optional seed. Up to 200 people. Nothing is emailed: hand each giver only their own line. Optional budget, date and message are echoed for slips. Web version: https://s3eidat.github.io/secret-santa/

Hints: readOnly

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `names` | string[] | yes | Participants (an item may also hold several names separated by commas or new lines). |
| `exclusions` | any[][] | no (default `[]`) | Pairs of names who must not draw each other, e.g. [["Omar","Sara"]]. |
| `budget` | string | no | Optional gift budget, e.g. "20 JD". |
| `date` | string | no | Optional exchange date, YYYY-MM-DD. |
| `message` | string | no | Optional note for everyone. |
| `seed` | string | no | Optional seed for a reproducible result (same seed + same input = same output). Omit for true crypto randomness. |

**Output**

| Field | Type | Description |
|---|---|---|
| `people` | number |  |
| `assignments` | object[] |  |
| `budget` | string |  |
| `date` | string |  |
| `message` | string |  |
| `note` | string |  |

**Example** — Omar and Sara never draw each other

```json
{
  "names": [
    "Omar",
    "Sara",
    "Lina",
    "Yousef",
    "Huda"
  ],
  "exclusions": [
    [
      "Omar",
      "Sara"
    ]
  ],
  "budget": "20 JD"
}
```

<a id="time_world_clock"></a>
### `time_world_clock` — World clock & meeting planner

Current (or any given) time in a list of time zones or cities: local time, date, weekday, UTC offset, difference from a home zone, day/night and working-hours flags. Plus the website's meeting planner: the 24 hours of a day in the home zone, each zone's local hour and band (work 9–17, awake 7–22, night), and the hours when everyone is in working hours. Zones: IANA ids ("Asia/Tokyo") or city names ("Mecca", "New York", "Tokyo"). Uses Node's ICU time-zone data; no network. Web version: https://s3eidat.github.io/world-clock/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `zones` | string[] | yes | Time zones or cities, e.g. ["Asia/Amman", "London", "New York"]. The first is the home zone unless homeZone is set. |
| `homeZone` | string | no | Zone the differences and the planner day are measured from. Default: the first of zones. |
| `at` | string | no | The instant to show (ISO 8601, e.g. "2026-01-15T12:00:00Z"). Default: now. |
| `hour12` | boolean | no (default `false`) | 12-hour clock (AM/PM) instead of 24-hour. |
| `planner` | boolean | no (default `true`) | Include the 24-hour meeting planner. |
| `plannerDate` | string | no | Planner day (YYYY-MM-DD in the home zone). Default: the date of "at" there. |

**Output**

| Field | Type | Description |
|---|---|---|
| `at` | string |  |
| `homeZone` | string |  |
| `clocks` | object[] |  |
| `planner` | object |  |

**Example** — Working-hours overlap 14:00–16:00 UTC (hours 14, 15, 16)

```json
{
  "zones": [
    "Europe/London",
    "America/New_York"
  ],
  "at": "2026-01-15T12:00:00Z"
}
```

Result:

```json
{
  "at": "2026-01-15T12:00:00.000Z",
  "homeZone": "Europe/London",
  "clocks": [
    {
      "zone": "Europe/London",
      "city": "London",
      "region": "Europe",
      "time": "12:00:00",
      "date": "2026-01-15",
      "weekday": "Thu",
      "offset": "UTC",
      "offsetMinutes": 0,
      "diff": "home",
      "isDay": true,
      "band": "work",
      "workingHours": true
    },
    {
      "zone": "America/New_York",
      "city": "New York",
      "region": "America",
      "time": "07:00:00",
      "date": "2026-01-15",
      "weekday": "Thu",
      "offset": "UTC−5",
      "offsetMinutes": -300,
      "diff": "−5h",
      "isDay": true,
      "band": "awake",
      "workingHours": false
    }
  ],
  "planner": {
    "date": "2026-01-15",
    "startUtc": "2026-01-15T00:00:00.000Z",
    "overlapHours": [
      14,
      15,
      16
    ],
    "overlapUtc": [
      "2026-01-15T14:00:00.000Z",
      "2026-01-15T15:00:00.000Z",
      "2026-01-15T16:00:00.000Z"
    ],
    "summary": "Everyone is in working hours for 3 hours.",
    "rows": [
      {
        "zone": "Europe/London",
        "city": "London",
        "hours": [
          {
            "label": "0",
            "band": "night"
          },
          {
            "label": "1",
            "band": "night"
          },
          {
            "label": "2",
            "band": "night"
          },
          {
            "label": "3",
            "band": "night"
          },
          {
            "label": "4",
            "band": "night"
          },
          {
            "label": "5",
            "band": "night"
          },
          {
            "label": "6",
            "band": "night"
          },
          {
            "label": "7",
            "band": "awake"
          },
          {
            "label": "8",
            "band": "awake"
          },
          {
            "label": "9",
            "band": "work"
          },
          {
            "label": "10",
            "band": "work"
          },
          {
            "label": "11",
            "band": "work"
          },
          {
            "label": "12",
            "band": "work"
          },
          {
            "label": "13",
            "band": "work"
          },
          {
            "label": "14",
            "band": "work"
          },
          {
            "label": "15",
            "band": "work"
          },
          {
            "label": "16",
            "band": "work"
          },
          {
            "label": "17",
            "band": "awake"
          },
          {
            "label": "18",
            "band": "awake"
          },
          {
            "label": "19",
            "band": "awake"
          },
          {
            "label": "20",
            "band": "awake"
          },
          {
            "label": "21",
            "band": "awake"
          },
          {
            "label": "22",
            "band": "night"
          },
          {
            "label": "23",
            "band": "night"
          }
        ]
      },
      {
        "zone": "America/New_York",
        "city": "New York",
        "hours": [
          {
            "label": "19",
            "band": "awake"
          },
          {
            "label": "20",
            "band": "awake"
          },
          {
            "label": "21",
            "band": "awake"
          },
          {
            "label": "22",
            "band": "night"
          },
          {
            "label": "23",
            "band": "night"
          },
          {
            "label": "1/15",
            "band": "night"
          },
          {
            "label": "1",
            "band": "night"
          },
          {
            "label": "2",
            "band": "night"
          },
          {
            "label": "3",
            "band": "night"
          },
          {
            "label": "4",
            "band": "night"
          },
          {
            "label": "5",
            "band": "night"
          },
          {
            "label": "6",
            "band": "night"
          },
          {
            "label": "7",
            "band": "awake"
          },
          {
            "label": "8",
            "band": "awake"
          },
          {
            "label": "9",
            "band": "work"
          },
          {
            "label": "10",
            "band": "work"
          },
          {
            "label": "11",
            "band": "work"
          },
          {
            "label": "12",
            "band": "work"
          },
          {
            "label": "13",
            "band": "work"
          },
          {
            "label": "14",
            "band": "work"
          },
          {
            "label": "15",
            "band": "work"
          },
          {
            "label": "16",
            "band": "work"
          },
          {
            "label": "17",
            "band": "awake"
          },
          {
            "label": "18",
            "band": "awake"
          }
        ]
      }
    ]
  }
}
```

**Example**

```json
{
  "zones": [
    "Asia/Amman",
    "Mecca",
    "Tokyo",
    "Los Angeles"
  ],
  "hour12": true
}
```

Result:

```json
{
  "at": "2026-01-15T09:30:00.000Z",
  "homeZone": "Asia/Amman",
  "clocks": [
    {
      "zone": "Asia/Amman",
      "city": "Amman",
      "region": "Asia",
      "time": "12:30:00 PM",
      "date": "2026-01-15",
      "weekday": "Thu",
      "offset": "UTC+3",
      "offsetMinutes": 180,
      "diff": "home",
      "isDay": true,
      "band": "work",
      "workingHours": true
    },
    {
      "zone": "Asia/Riyadh",
      "city": "Riyadh",
      "region": "Asia",
      "time": "12:30:00 PM",
      "date": "2026-01-15",
      "weekday": "Thu",
      "offset": "UTC+3",
      "offsetMinutes": 180,
      "diff": "same time",
      "isDay": true,
      "band": "work",
      "workingHours": true
    },
    {
      "zone": "Asia/Tokyo",
      "city": "Tokyo",
      "region": "Asia",
      "time": "6:30:00 PM",
      "date": "2026-01-15",
      "weekday": "Thu",
      "offset": "UTC+9",
      "offsetMinutes": 540,
      "diff": "+6h",
      "isDay": false,
      "band": "awake",
      "workingHours": false
    },
    {
      "zone": "America/Los_Angeles",
      "city": "Los Angeles",
      "region": "America",
      "time": "1:30:00 AM",
      "date": "2026-01-15",
      "weekday": "Thu",
      "offset": "UTC−8",
      "offsetMinutes": -480,
      "diff": "−11h",
      "isDay": false,
      "band": "night",
      "workingHours": false
    }
  ],
  "planner": {
    "date": "2026-01-15",
    "startUtc": "2026-01-14T21:00:00.000Z",
    "overlapHours": [],
    "overlapUtc": [],
    "summary": "No hour where everyone is inside 9–17. Look for the 'awake' hours instead.",
    "rows": [
      {
        "zone": "Asia/Amman",
        "city": "Amman",
        "hours": [
          {
            "label": "12a",
            "band": "night"
          },
          {
            "label": "1a",
            "band": "night"
          },
          {
            "label": "2a",
            "band": "night"
          },
          {
            "label": "3a",
            "band": "night"
          },
          {
            "label": "4a",
            "band": "night"
          },
          {
            "label": "5a",
            "band": "night"
          },
          {
            "label": "6a",
            "band": "night"
          },
          {
            "label": "7a",
            "band": "awake"
          },
          {
            "label": "8a",
            "band": "awake"
          },
          {
            "label": "9a",
            "band": "work"
          },
          {
            "label": "10a",
            "band": "work"
          },
          {
            "label": "11a",
            "band": "work"
          },
          {
            "label": "12p",
            "band": "work"
          },
          {
            "label": "1p",
            "band": "work"
          },
          {
            "label": "2p",
            "band": "work"
          },
          {
            "label": "3p",
            "band": "work"
          },
          {
            "label": "4p",
            "band": "work"
          },
          {
            "label": "5p",
            "band": "awake"
          },
          {
            "label": "6p",
            "band": "awake"
          },
          {
            "label": "7p",
            "band": "awake"
          },
          {
            "label": "8p",
            "band": "awake"
          },
          {
            "label": "9p",
            "band": "awake"
          },
          {
            "label": "10p",
            "band": "night"
          },
          {
            "label": "11p",
            "band": "night"
          }
        ]
      },
      {
        "zone": "Asia/Riyadh",
        "city": "Riyadh",
        "hours": [
          {
            "label": "12a",
            "band": "night"
          },
          {
            "label": "1a",
            "band": "night"
          },
          {
            "label": "2a",
            "band": "night"
          },
          {
            "label": "3a",
            "band": "night"
          },
          {
            "label": "4a",
            "band": "night"
          },
          {
            "label": "5a",
            "band": "night"
          },
          {
            "label": "6a",
            "band": "night"
          },
          {
            "label": "7a",
            "band": "awake"
          },
          {
            "label": "8a",
            "band": "awake"
          },
          {
            "label": "9a",
            "band": "work"
          },
          {
            "label": "10a",
            "band": "work"
          },
          {
            "label": "11a",
            "band": "work"
          },
          {
            "label": "12p",
            "band": "work"
          },
          {
            "label": "1p",
            "band": "work"
          },
          {
            "label": "2p",
            "band": "work"
          },
          {
            "label": "3p",
            "band": "work"
          },
          {
            "label": "4p",
            "band": "work"
          },
          {
            "label": "5p",
            "band": "awake"
          },
          {
            "label": "6p",
            "band": "awake"
          },
          {
            "label": "7p",
            "band": "awake"
          },
          {
            "label": "8p",
            "band": "awake"
          },
          {
            "label": "9p",
            "band": "awake"
          },
          {
            "label": "10p",
            "band": "night"
          },
          {
            "label": "11p",
            "band": "night"
          }
        ]
      },
      {
        "zone": "Asia/Tokyo",
        "city": "Tokyo",
        "hours": [
          {
            "label": "6a",
            "band": "night"
          },
          {
            "label": "7a",
            "band": "awake"
          },
          {
            "label": "8a",
            "band": "awake"
          },
          {
            "label": "9a",
            "band": "work"
          },
          {
            "label": "10a",
            "band": "work"
          },
          {
            "label": "11a",
            "band": "work"
          },
          {
            "label": "12p",
            "band": "work"
          },
          {
            "label": "1p",
            "band": "work"
          },
          {
            "label": "2p",
            "band": "work"
          },
          {
            "label": "3p",
            "band": "work"
          },
          {
            "label": "4p",
            "band": "work"
          },
          {
            "label": "5p",
            "band": "awake"
          },
          {
            "label": "6p",
            "band": "awake"
          },
          {
            "label": "7p",
            "band": "awake"
          },
          {
            "label": "8p",
            "band": "awake"
          },
          {
            "label": "9p",
            "band": "awake"
          },
          {
            "label": "10p",
            "band": "night"
          },
          {
            "label": "11p",
            "band": "night"
          },
          {
            "label": "1/16",
            "band": "night"
          },
          {
            "label": "1a",
            "band": "night"
          },
          {
            "label": "2a",
            "band": "night"
          },
          {
            "label": "3a",
            "band": "night"
          },
          {
            "label": "4a",
            "band": "night"
          },
          {
            "label": "5a",
            "band": "night"
          }
        ]
      },
      {
        "zone": "America/Los_Angeles",
        "city": "Los Angeles",
        "hours": [
          {
            "label": "1p",
            "band": "work"
          },
          {
            "label": "2p",
            "band": "work"
          },
          {
            "label": "3p",
            "band": "work"
          },
          {
            "label": "4p",
            "band": "work"
          },
          {
            "label": "5p",
            "band": "awake"
          },
          {
            "label": "6p",
            "band": "awake"
          },
          {
            "label": "7p",
            "band": "awake"
          },
          {
            "label": "8p",
            "band": "awake"
          },
          {
            "label": "9p",
            "band": "awake"
          },
          {
            "label": "10p",
            "band": "night"
          },
          {
            "label": "11p",
            "band": "night"
          },
          {
            "label": "1/15",
            "band": "night"
          },
          {
            "label": "1a",
            "band": "night"
          },
          {
            "label": "2a",
            "band": "night"
          },
          {
            "label": "3a",
            "band": "night"
          },
          {
            "label": "4a",
            "band": "night"
          },
          {
            "label": "5a",
            "band": "night"
          },
          {
            "label": "6a",
            "band": "night"
          },
          {
            "label": "7a",
            "band": "awake"
          },
          {
            "label": "8a",
            "band": "awake"
          },
          {
            "label": "9a",
            "band": "work"
          },
          {
            "label": "10a",
            "band": "work"
          },
          {
            "label": "11a",
            "band": "work"
          },
          {
            "label": "12p",
            "band": "work"
          }
        ]
      }
    ]
  }
}
```

<a id="qr"></a>
## QR & barcodes

Make and read QR codes, Wi-Fi QR, barcodes.

| Tool | What it does |
|---|---|
| [`barcode_generate`](#barcode_generate) | Barcode generator |
| [`qr_generate`](#qr_generate) | QR code generator |
| [`qr_read`](#qr_read) | QR code reader |
| [`qr_wifi`](#qr_wifi) | Wi-Fi QR code and card |

<a id="barcode_generate"></a>
### `barcode_generate` — Barcode generator

Make 1D barcodes as SVG or PNG: Code 128 (any printable ASCII; digit runs packed with subset C automatically), EAN-13 (12 digits, check digit added; or 13, check digit verified), UPC-A (11 or 12 digits, same) and Code 39 (A-Z, 0-9, space, - . $ / + %, optional mod-43 check). Same encoders, quiet zones, guard bars and human-readable digits as the website. Give "value" for one barcode, or "values" (up to 1000) to write one file per value into a folder; invalid values are reported, not fatal. PNG text uses the system monospace font. Web version: https://s3eidat.github.io/barcode-gen/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `value` | string | no | The value to encode (one barcode). |
| `values` | string[] | no | Or: many values; each becomes its own file in the output folder. |
| `symbology` | "code128" \| "ean13" \| "upca" \| "code39" | no (default `"code128"`) | Barcode type (default code128). |
| `code39Check` | boolean | no (default `false`) | Code 39 only: append the mod-43 check character. |
| `format` | "svg" \| "png" | no (default `"svg"`) | Output format (default svg). |
| `barWidth` | integer | no (default `2`) | Narrow bar (module) width in px, 1-6 (default 2). |
| `height` | integer | no (default `80`) | Bar height in px, 30-220 (default 80). |
| `showText` | boolean | no (default `true`) | Print the human-readable text/digits under the bars. |
| `foreground` | string | no (default `"#000000"`) | Bar colour, hex. |
| `background` | string | no (default `"#ffffff"`) | Background colour, hex. |
| `scale` | integer | no (default `2`) | PNG only: pixel scale (1×, 2× default, 4×…). |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | The written file (single value). |
| `bytes` | number |  |
| `mimeType` | string |  |
| `base64` | string |  |
| `text` | string | Encoded text (with check digit / check character). |
| `note` | string | e.g. "Check digit 1 added". |
| `width` | number | SVG width in px (PNG is width × scale). |
| `height` | number |  |
| `modules` | number | Barcode width in modules (without quiet zones). |
| `folder` | string | Batch: folder with one file per value. |
| `files` | object[] |  |
| `errors` | object[] |  |

**Example**

```json
{
  "value": "400638133393",
  "symbology": "ean13"
}
```

Result:

```json
{
  "text": "4006381333931",
  "note": "Check digit 1 added"
}
```

**Example** — Code 128 SVG

```json
{
  "value": "S3EIDAT-2026"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-npITvj\\S3EIDAT-2026.svg",
  "bytes": 2214,
  "mimeType": "image/svg+xml",
  "text": "S3EIDAT-2026",
  "width": 352,
  "height": 123,
  "modules": 156
}
```

**Example** — Two files, one error (12345 is too short).

```json
{
  "values": [
    "590123412345",
    "400638133393",
    "12345"
  ],
  "symbology": "ean13",
  "format": "png"
}
```

Result:

```json
{
  "folder": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-dH0Pbu\\barcodes-ean13\\",
  "files": [
    {
      "value": "590123412345",
      "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-dH0Pbu\\barcodes-ean13\\5901234123457.png",
      "text": "5901234123457"
    },
    {
      "value": "400638133393",
      "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-dH0Pbu\\barcodes-ean13\\4006381333931.png",
      "text": "4006381333931"
    }
  ],
  "errors": [
    {
      "line": 3,
      "value": "12345",
      "error": "EAN-13 needs 12 or 13 digits (you have 5)"
    }
  ]
}
```

<a id="qr_generate"></a>
### `qr_generate` — QR code generator

Make a QR code as SVG or PNG for a link, plain text, Wi-Fi login, contact card (vCard 3.0), email, SMS, phone call or map location. Same encoder as the website (qrcode.js: versions 1-40, error correction L/M/Q/H, smallest version that fits, best mask), UTF-8 so Arabic/emoji work. Options: size in px, quiet-zone margin, colours, square/rounded/dots modules, and an optional centre logo (forces level H). Returns the file path, the exact encoded text, version, module count and contrast warnings. Max about 2.9 KB of data at level L. Web version: https://s3eidat.github.io/qr-studio/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `type` | "url" \| "text" \| "wifi" \| "vcard" \| "email" \| "sms" \| "tel" \| "geo" | no | What the code holds. Default: "url" if url is given, else "text". |
| `text` | string | no | text: any text (encoded exactly as given). |
| `url` | string | no | url: web address; "https://" is added when no scheme is given. |
| `ssid` | string | no | wifi: network name. |
| `password` | string | no | wifi: password (ignored for open networks). |
| `security` | "WPA" \| "WEP" \| "nopass" | no | wifi: WPA (WPA/WPA2/WPA3, default), WEP, or nopass (open). |
| `hidden` | boolean | no | wifi: the network is hidden. |
| `firstName` | string | no | vcard: first name. |
| `lastName` | string | no | vcard: last name. |
| `phone` | string | no | vcard/sms/tel: phone number. |
| `email` | string | no | vcard: email address. |
| `company` | string | no | vcard: company. |
| `jobTitle` | string | no | vcard: job title. |
| `website` | string | no | vcard: website. |
| `address` | string | no | vcard: address. |
| `to` | string | no | email: recipient address. |
| `subject` | string | no | email: subject. |
| `body` | string | no | email: message body. |
| `message` | string | no | sms: message text. |
| `latitude` | number \| string | no | geo: latitude, -90..90. |
| `longitude` | number \| string | no | geo: longitude, -180..180. |
| `label` | string | no | geo: optional place label (?q=). |
| `ecc` | "L" \| "M" \| "Q" \| "H" | no (default `"M"`) | Error correction: L 7%, M 15% (default), Q 25%, H 30%. A logo forces H. |
| `mode` | "byte" \| "auto" \| "numeric" \| "alphanumeric" | no (default `"byte"`) | Encoding mode. "byte" (UTF-8, what the website uses), "auto" (numeric/alphanumeric when the text allows, smaller codes), or force one. |
| `version` | integer | no (default `0`) | QR version 1-40 (size), or 0 = smallest that fits (default). |
| `format` | "svg" \| "png" | no (default `"png"`) | Output format. |
| `size` | integer | no (default `1024`) | Image width/height in px (default 1024). |
| `margin` | integer | no (default `4`) | Quiet zone in modules (default 4; under 2 may not scan). |
| `foreground` | string | no (default `"#0d0f12"`) | Module colour, hex (default #0d0f12). |
| `background` | string | no (default `"#ffffff"`) | Background colour, hex (default #ffffff). |
| `shape` | "square" \| "rounded" \| "dots" | no (default `"square"`) | Module shape. Rounded/dots also round the three finder squares. |
| `logo` | string | no | Optional centre logo image (path or base64, max 5 MB). Covers ~22% of the code; error correction becomes H. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `payload` | string | The exact text encoded in the QR code. |
| `version` | number |  |
| `modules` | number | Modules per side (version × 4 + 17). |
| `ecc` | string |  |
| `mode` | string |  |
| `mask` | number |  |
| `dataBytes` | number | Encoded length (UTF-8 bytes in byte mode). |
| `contrast` | number | Foreground/background contrast ratio (21 = black on white). |
| `warnings` | string[] |  |

**Example** — Encodes "https://example.com" as a 1024 px PNG.

```json
{
  "url": "example.com"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-Y92THC\\qr-url.png",
  "bytes": 17533,
  "mimeType": "image/png",
  "payload": "https://example.com",
  "version": 2,
  "modules": 25,
  "ecc": "M",
  "mode": "Byte",
  "mask": 5,
  "dataBytes": 19,
  "contrast": 19.19,
  "warnings": []
}
```

**Example** — payload WIFI:T:WPA;S:Home;P:12345678;;

```json
{
  "type": "wifi",
  "ssid": "Home",
  "password": "12345678",
  "format": "svg"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-SUmYUZ\\qr-wifi.svg",
  "bytes": 5788,
  "mimeType": "image/svg+xml",
  "payload": "WIFI:T:WPA;S:Home;P:12345678;;",
  "version": 3,
  "modules": 29,
  "ecc": "M",
  "mode": "Byte",
  "mask": 1,
  "dataBytes": 30,
  "contrast": 19.19,
  "warnings": []
}
```

**Example**

```json
{
  "type": "vcard",
  "firstName": "Sara",
  "lastName": "Haddad",
  "phone": "+962 7 9000 0000",
  "shape": "rounded"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-nWOSYW\\qr-vcard.png",
  "bytes": 138314,
  "mimeType": "image/png",
  "payload": "BEGIN:VCARD\r\nVERSION:3.0\r\nN:Haddad;Sara;;;\r\nFN:Sara Haddad\r\nTEL;TYPE=CELL:+962 7 9000 0000\r\nEND:VCARD",
  "version": 6,
  "modules": 41,
  "ecc": "M",
  "mode": "Byte",
  "mask": 4,
  "dataBytes": 101,
  "contrast": 19.19,
  "warnings": []
}
```

<a id="qr_read"></a>
### `qr_read` — QR code reader

Read (decode) a QR code from an image file: photo, screenshot or scan (PNG, JPEG, WebP, GIF, TIFF, AVIF, SVG). Uses jsQR like the website, trying several sizes and inverted (light-on-dark) codes. Returns the text plus what it is (Link, Wi-Fi, Contact, Email, SMS, Phone, Location, Event or Text) with parsed fields, and safety notes for links (http, hidden @ user part, punycode lookalikes, bare IPs, odd ports). Links are never opened. Reads one code per image. Web version (with camera): https://s3eidat.github.io/qr-scanner/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `image` | string | yes | The image containing a QR code: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |

**Output**

| Field | Type | Description |
|---|---|---|
| `text` | string | The decoded content. |
| `type` | string | Link, Wi-Fi, Contact, Email, SMS, Phone, Location, Event or Text. |
| `fields` | object[] | Parsed fields (e.g. Network, Password). |
| `url` | object | For links: the real host and safety warnings. |
| `version` | number | QR version (1-40). |
| `bytes` | number | Number of data bytes. |
| `location` | object | Corner and finder positions in image pixels. |

**Example** — e.g. { text: "WIFI:T:WPA;S:Home;P:secret;;", type: "Wi-Fi", fields: [{label:"Network",value:"Home"},…] }

```json
{
  "image": "/path/to/photo-of-qr.jpg"
}
```

<a id="qr_wifi"></a>
### `qr_wifi` — Wi-Fi QR code and card

Make a Wi-Fi QR code that phones join by scanning (WPA/WPA2/WPA3, WEP or open; hidden networks), as PNG or SVG, or a printable PDF card like the website's: coloured header with the Wi-Fi symbol, "Scan to join", the QR code, network name and (optionally) the password, on an A6 card, an A4 poster, or A4 sheets of 4 or 9 cards to cut. Special characters (; , : " \) are escaped. The PDF card uses English labels and Latin text only for now. Web version: https://s3eidat.github.io/wifi-card/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `ssid` | string | yes | Network name (SSID). |
| `password` | string | no (default `""`) | Wi-Fi password (ignored for open networks). |
| `security` | "WPA" \| "WEP" \| "nopass" | no (default `"WPA"`) | WPA (WPA/WPA2/WPA3, default), WEP, or nopass (open network). |
| `hidden` | boolean | no (default `false`) | The network is hidden (not broadcast). |
| `format` | "png" \| "svg" \| "pdf" | no (default `"pdf"`) | pdf = printable card sheet (default); png/svg = just the QR code. |
| `layout` | "a6" \| "a4" \| "a4x4" \| "a4x9" | no (default `"a4x4"`) | PDF: a6 = one A6 card; a4 = one A4 poster; a4x4 = A4 sheet of 4 cards (default); a4x9 = A4 sheet of 9 small cards. |
| `theme` | "teal" \| "ink" \| "coral" \| "violet" \| "saver" | no (default `"teal"`) | PDF: card colour; "saver" is an ink-saving outline. |
| `title` | string | no (default `""`) | PDF: heading on the card (default "Wi-Fi"). |
| `showPassword` | boolean | no (default `true`) | PDF: print the password under the code. |
| `size` | integer | no (default `1024`) | PNG/SVG: image size in px. |
| `margin` | integer | no (default `4`) | PNG/SVG: quiet zone in modules. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string |  |
| `bytes` | number |  |
| `mimeType` | string |  |
| `base64` | string |  |
| `pages` | number |  |
| `payload` | string | The exact text in the QR code. |
| `version` | number |  |
| `modules` | number |  |
| `warnings` | string[] |  |

**Example** — A4 sheet with 4 cards; payload WIFI:T:WPA;S:S3eidat Café;P:coffee-is-free;;

```json
{
  "ssid": "S3eidat Café",
  "password": "coffee-is-free"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-jx8uxA\\wifi-card.pdf",
  "bytes": 6360,
  "mimeType": "application/pdf",
  "pages": 1,
  "payload": "WIFI:T:WPA;S:S3eidat Café;P:coffee-is-free;;",
  "version": 4,
  "modules": 33,
  "warnings": []
}
```

**Example** — payload WIFI:T:nopass;S:Guest;;

```json
{
  "ssid": "Guest",
  "security": "nopass",
  "format": "png"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-REoI5U\\wifi-qr.png",
  "bytes": 17598,
  "mimeType": "image/png",
  "payload": "WIFI:T:nopass;S:Guest;;",
  "version": 2,
  "modules": 25,
  "warnings": []
}
```

<a id="docs"></a>
## Document makers

Invoices, receipts, business cards, labels, calendars and CVs as PDF.

| Tool | What it does |
|---|---|
| [`doc_attendance_sheet`](#doc_attendance_sheet) | Attendance sheet PDF |
| [`doc_business_cards`](#doc_business_cards) | Business cards PDF |
| [`doc_calendar`](#doc_calendar) | Calendar PDF |
| [`doc_invoice`](#doc_invoice) | Invoice PDF |
| [`doc_labels`](#doc_labels) | Label sheets PDF |
| [`doc_receipt`](#doc_receipt) | Receipt PDF |
| [`doc_resume`](#doc_resume) | Resume / CV PDF |

<a id="doc_attendance_sheet"></a>
### `doc_attendance_sheet` — Attendance sheet PDF

Make a printable attendance / sign-in sheet PDF: names down the side, one column per date for a month (skipping chosen weekdays, e.g. Fri+Sat or Sat+Sun), a run of N sessions from a start date, or N blank columns; tick boxes or wide signature cells; row numbers, a total column, extra empty rows, title, group and teacher. Same page maths as the website: rows that don't fit continue on more pages, and too many date columns are split across pages with the same names. Also returns a blank register CSV. English labels, Latin text only for now. Web version: https://s3eidat.github.io/attendance-sheet/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `names` | string \| string[] | yes | Names, one per line (leading "1." numbering and anything after a tab are removed), or a list. |
| `title` | string | no (default `""`) | Title (default "Attendance"). |
| `group` | string | no (default `""`) | Class / group, shown under the title. |
| `teacher` | string | no (default `""`) | Teacher, shown under the title. |
| `mode` | "month" \| "sessions" \| "blank" | no (default `"month"`) | month = every day of a month; sessions = N dates from a start date; blank = N unnamed columns. |
| `month` | string | no | month mode: YYYY-MM (default this month). |
| `start` | string | no | sessions mode: first date YYYY-MM-DD (default today). |
| `count` | integer | no (default `12`) | sessions/blank: number of columns. |
| `skip` | integer[] | no (default `[0,6]`) | Weekdays to skip, 0 = Sunday … 6 = Saturday (default [0, 6] = weekends Sat+Sun; use [5, 6] for Fri+Sat). |
| `cell` | "box" \| "sign" | no (default `"box"`) | box = tick boxes, sign = wide cells for signatures. |
| `paper` | "A4" \| "Letter" | no (default `"A4"`) |  |
| `orientation` | "landscape" \| "portrait" | no (default `"landscape"`) |  |
| `rowHeight` | number | no | Row height in mm (default 8, or 13 for signature cells). |
| `extraRows` | integer | no (default `2`) | Empty rows after the names. |
| `numbers` | boolean | no (default `true`) | Row number column. |
| `total` | boolean | no (default `true`) | Total column. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size in bytes. |
| `pages` | number | Number of pages. |
| `mimeType` | string |  |
| `base64` | string |  |
| `names` | number |  |
| `columns` | number |  |
| `rowsPerPage` | number |  |
| `columnChunks` | number | Pages the date columns are split over. |
| `dates` | string[] | Column keys (YYYY-MM-DD, or c1… in blank mode). |
| `csv` | string | A blank register as CSV (Name + one column per date). |

**Example** — 22 date columns, 1 page.

```json
{
  "names": "Ahmad Khalil\nLina Haddad\nOmar Saleh",
  "group": "Grade 7 B · Science",
  "teacher": "Ms. Rana",
  "month": "2026-11",
  "skip": [
    5,
    6
  ]
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-lzAwwv\\attendance.pdf",
  "bytes": 11902,
  "mimeType": "application/pdf",
  "pages": 1,
  "names": 3,
  "columns": 22,
  "rowsPerPage": 19,
  "columnChunks": 1,
  "dates": [
    "2026-11-01",
    "2026-11-02",
    "2026-11-03",
    "2026-11-04",
    "2026-11-05",
    "2026-11-08",
    "2026-11-09",
    "2026-11-10",
    "2026-11-11",
    "2026-11-12",
    "2026-11-15",
    "2026-11-16",
    "2026-11-17",
    "2026-11-18",
    "2026-11-19",
    "2026-11-22",
    "2026-11-23",
    "2026-11-24",
    "2026-11-25",
    "2026-11-26",
    "2026-11-29",
    "2026-11-30"
  ],
  "csv": "Name,2026-11-01,2026-11-02,2026-11-03,2026-11-04,2026-11-05,2026-11-08,2026-11-09,2026-11-10,2026-11-11,2026-11-12,2026-11-15,2026-11-16,2026-11-17,2026-11-18,2026-11-19,2026-11-22,2026-11-23,2026-11-24,2026-11-25,2026-11-26,2026-11-29,2026-11-30\r\nAhmad Khalil,,,,,,,,,,,,,,,,,,,,,,\r\nLina Haddad,,,,,,,,,,,,,,,,,,,,,,\r\nOmar Saleh,,,,,,,,,,,,,,,,,,,,,,\r\n"
}
```

<a id="doc_business_cards"></a>
### `doc_business_cards` — Business cards PDF

Design business cards and get a print-ready PDF sheet: cards centred on A4 or Letter with crop marks (10 per sheet for 85 × 55 mm EU or 3.5 × 2 in US cards), plus a back side page mirrored for long-edge duplex printing. Four front templates (classic, band, centered, minimal), backs with your logo, a "scan to save my contact" vCard QR code, or blank; six colour themes or your own accent/paper/ink colours; optional logo and a vCard QR on the front. Also returns the vCard text. Print at 100% / actual size. Latin text only for now (the website also does an Arabic side). Web version: https://s3eidat.github.io/business-card/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Your name. |
| `title` | string | no (default `""`) | Job title. |
| `company` | string | no (default `""`) | Company. |
| `phone` | string | no (default `""`) |  |
| `email` | string | no (default `""`) |  |
| `website` | string | no (default `""`) |  |
| `address` | string | no (default `""`) |  |
| `tagline` | string | no (default `""`) | Short line for the back. |
| `template` | "classic" \| "band" \| "centered" \| "minimal" | no (default `"classic"`) | Front layout. |
| `back` | "logo" \| "qr" \| "none" | no (default `"logo"`) | Back side: logo/company on the accent colour, a contact QR code, or none. |
| `qrFront` | boolean | no (default `false`) | Also put the contact QR code on the front. |
| `theme` | "teal" \| "navy" \| "charcoal" \| "forest" \| "violet" \| "mono" | no (default `"teal"`) | Colour preset (navy = navy & gold). |
| `accent` | string | no | Override the accent colour. |
| `paperColor` | string | no | Override the card background colour. |
| `ink` | string | no | Override the text colour. |
| `size` | "eu" \| "us" | no (default `"eu"`) | eu = 85 × 55 mm, us = 3.5 × 2 in. |
| `sheet` | "A4" \| "Letter" | no (default `"A4"`) | Paper for the print sheet. |
| `gap` | number | no (default `0`) | Gap between cards in mm (0 = shared cut lines). |
| `sides` | "both" \| "front" \| "back" | no (default `"both"`) | Which pages to include (both = front page then mirrored back page). |
| `logo` | string | no | Optional logo image: a PNG/JPEG/WebP/GIF/SVG file path or base64. Drawn as-is (keep it small). |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size in bytes. |
| `pages` | number | Number of pages. |
| `mimeType` | string |  |
| `base64` | string |  |
| `cardsPerSheet` | number |  |
| `columns` | number |  |
| `rows` | number |  |
| `vcard` | string | The vCard 3.0 text (what the QR code holds). |
| `qrVersion` | number \| null |  |

**Example** — A4 sheet of 10 fronts + mirrored sheet of 10 QR backs.

```json
{
  "name": "Lina Haddad",
  "title": "Optometrist",
  "company": "Lumen Eye Clinic",
  "phone": "+962 79 000 0000",
  "email": "lina@lumen.example",
  "website": "lumen.example",
  "address": "Amman, Jordan",
  "back": "qr"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-eTOUAz\\lina-haddad-cards.pdf",
  "bytes": 18458,
  "mimeType": "application/pdf",
  "pages": 2,
  "cardsPerSheet": 10,
  "columns": 2,
  "rows": 5,
  "vcard": "BEGIN:VCARD\r\nVERSION:3.0\r\nN:Haddad;Lina;;;\r\nFN:Lina Haddad\r\nORG:Lumen Eye Clinic\r\nTITLE:Optometrist\r\nTEL;TYPE=CELL:+962 79 000 0000\r\nEMAIL;TYPE=INTERNET:lina@lumen.example\r\nURL:lumen.example\r\nADR;TYPE=WORK:;;Amman\\, Jordan;;;;\r\nEND:VCARD",
  "qrVersion": 11
}
```

<a id="doc_calendar"></a>
### `doc_calendar` — Calendar PDF

Make a printable calendar PDF: a month grid (one page per month, several months in a row if you like) or a whole year on one page, A4 or Letter, landscape or portrait. Week starts on Saturday (default, like the website), Sunday or Monday; optional Hijri (Umm al-Qura) dates; events (one-off or yearly, 7 colours) shown in the day cells, or as coloured days plus a legend in the year view; optional title. English names by default; other Latin-script locales (fr, de, es, tr…) use their own month/day names. Web version: https://s3eidat.github.io/calendar-maker/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `view` | "month" \| "year" | no (default `"month"`) | month = month grid pages, year = 12 months on one page. |
| `year` | integer | no | Year (default: this year). |
| `month` | integer | no | Month 1-12 (month view; default: this month). |
| `months` | integer | no (default `1`) | Month view: how many consecutive months (one page each). |
| `weekStart` | 0 \| 1 \| 6 | no (default `6`) | First day of the week: 6 = Saturday (default), 0 = Sunday, 1 = Monday. |
| `hijri` | boolean | no (default `false`) | Show Hijri (Umm al-Qura) dates. |
| `paper` | "A4" \| "Letter" | no (default `"A4"`) |  |
| `orientation` | "landscape" \| "portrait" | no (default `"landscape"`) |  |
| `title` | string | no (default `""`) | Optional title above the month name, e.g. "Family calendar". |
| `locale` | string | no (default `"en"`) | Language for month/day names (default en). Latin-script locales only. |
| `events` | object[] | no (default `[]`) | Events to show. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size in bytes. |
| `pages` | number | Number of pages. |
| `mimeType` | string |  |
| `base64` | string |  |
| `from` | string |  |
| `to` | string | Last month covered, YYYY-MM. |

**Example**

```json
{
  "year": 2026,
  "month": 12,
  "title": "Family calendar",
  "hijri": true,
  "events": [
    {
      "date": "2026-12-03",
      "text": "Sara's birthday",
      "color": "purple",
      "yearly": true
    }
  ]
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-MDrckN\\calendar-2026-12.pdf",
  "bytes": 4028,
  "mimeType": "application/pdf",
  "pages": 1,
  "from": "2026-12",
  "to": "2026-12"
}
```

**Example**

```json
{
  "view": "year",
  "year": 2027,
  "weekStart": 1,
  "orientation": "portrait"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-i8ED9t\\calendar-2027.pdf",
  "bytes": 11458,
  "mimeType": "application/pdf",
  "pages": 1,
  "from": "2027-01",
  "to": "2027-12"
}
```

<a id="doc_invoice"></a>
### `doc_invoice` — Invoice PDF

Make a clean A4 invoice PDF: your business (logo, name, address, email, phone, tax ID), client, invoice number and dates, line items with quantity, unit price, per-line discount % and tax %, subtotal, discount, tax per rate and total, notes, payment terms, and an optional PAID stamp. Same maths as the website: every line is rounded to the currency's minor unit (2 decimals, 3 for JOD/KWD/BHD/OMR/TND…, 0 for JPY…) with half-up rounding; amounts formatted en-US style ($1,234.50, JOD 10.124). Long item lists continue on more pages. Returns the PDF path and the computed totals. Latin text only for now. Web version: https://s3eidat.github.io/invoice-maker/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `number` | string | no (default `"INV-0001"`) | Invoice number. |
| `date` | string | no | Issue date, YYYY-MM-DD (default today). |
| `due` | string | no | Due date, YYYY-MM-DD (optional). |
| `currency` | "USD" \| "EUR" \| "GBP" \| "JOD" \| "SAR" \| "AED" \| "EGP" \| "KWD" \| "QAR" \| "BHD" \| "OMR" \| "IQD" \| "LBP" \| "MAD" \| "TND" \| "DZD" \| "TRY" \| "INR" \| "PKR" \| "CAD" \| "AUD" \| "NZD" \| "JPY" \| "CNY" \| "CHF" \| "SEK" \| "NOK" \| "DKK" \| "PLN" \| "ZAR" \| "BRL" \| "MXN" \| "SGD" | no (default `"USD"`) | Currency code. |
| `accent` | string | no (default `"#0e9f9a"`) | Accent colour (hex). |
| `paid` | boolean | no (default `false`) | Mark as paid (shows "Amount paid" and a PAID stamp). |
| `from` | object | no (default `{"name":"","email":"","phone":"","address":"","taxId":""}`) | Your business: name, email, phone, address, taxId. |
| `to` | object | no (default `{"name":"","email":"","address":""}`) | The client (bill to): name, email, address. |
| `items` | object[] | yes | Line items: desc, qty, price, disc %, tax %. |
| `notes` | string | no (default `""`) | Notes shown at the bottom. |
| `terms` | string | no (default `""`) | Payment terms shown at the bottom. |
| `logo` | string | no | Optional logo image: a PNG/JPEG/WebP/GIF/SVG file path or base64. Drawn as-is (keep it small). |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size in bytes. |
| `pages` | number | Number of pages. |
| `mimeType` | string |  |
| `base64` | string |  |
| `subtotal` | number |  |
| `discount` | number |  |
| `tax` | number |  |
| `total` | number |  |
| `taxByRate` | object | Tax amount per rate (%). |
| `lineAmounts` | number[] | Net amount of each line (after discount, before tax). |
| `currency` | string |  |
| `decimals` | number |  |
| `formattedTotal` | string |  |

**Example** — Subtotal $1,540.00, discount $34.00, tax 8% $120.48, total $1,626.48.

```json
{
  "number": "INV-0042",
  "currency": "USD",
  "from": {
    "name": "Northwind Studio",
    "email": "hello@northwind.studio"
  },
  "to": {
    "name": "Blue Fern Café"
  },
  "items": [
    {
      "desc": "Brand identity design",
      "qty": 1,
      "price": 1200,
      "tax": 8
    },
    {
      "desc": "Menu layout (per page)",
      "qty": 4,
      "price": 85,
      "disc": 10,
      "tax": 8
    }
  ],
  "terms": "Bank transfer within 14 days."
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-62gT1b\\invoice-INV-0042.pdf",
  "bytes": 3109,
  "mimeType": "application/pdf",
  "pages": 1,
  "subtotal": 1540,
  "discount": 34,
  "tax": 120.48,
  "total": 1626.48,
  "taxByRate": {
    "8": 120.48
  },
  "lineAmounts": [
    1200,
    306
  ],
  "currency": "USD",
  "decimals": 2,
  "formattedTotal": "$1,626.48"
}
```

<a id="doc_labels"></a>
### `doc_labels` — Label sheets PDF

Print address or product labels on standard sticker sheets as PDF: A4 21/24/14/40/65-up (L7160, L7159, L7163, L7654, L7651 sizes) or Letter 30/10/80-up (5160, 5163, 5167 sizes), or custom geometry in mm. Text modes: "list" (one label per block, blank line between labels), "csv" (rows filled into a template like "{Name}\n{Street}\n{City} {Postcode}", header row or {1},{2}…), or "same" (one text × count). Copies per label, start at a later position to reuse a part-used sheet, alignment, font, size, bold first line, border, shrink-to-fit and printer offset nudges. Print at 100% / actual size. Up to 3000 labels. Latin text only for now. Web version: https://s3eidat.github.io/label-maker/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | "list" \| "csv" \| "same" | no (default `"list"`) | list = blocks separated by a blank line; csv = rows + template; same = one text repeated. |
| `text` | string | no | The labels text (list blocks, CSV/TSV, or the single text). |
| `path` | string | no | Or: a text/CSV file path to read it from. |
| `template` | string | no (default `""`) | csv: template with {Column name} or {1}-style placeholders (empty = all columns, one per line). |
| `header` | boolean | no (default `true`) | csv: the first row holds column names. |
| `count` | integer | no (default `21`) | same: how many labels. |
| `copies` | integer | no (default `1`) | list/csv: copies of each label. |
| `preset` | "a4-21" \| "a4-24" \| "a4-14" \| "a4-40" \| "a4-65" \| "lt-30" \| "lt-10" \| "lt-80" \| "custom" | no (default `"a4-21"`) | Sheet: a4-21 (63.5×38.1), a4-24 (63.5×33.9), a4-14 (99.1×38.1), a4-40 (45.7×25.4), a4-65 (38.1×21.2), lt-30 (2⅝×1 in), lt-10 (4×2 in), lt-80 (1¾×½ in), or custom. |
| `custom` | object | no | preset "custom": paper, cols, rows, w, h, top, left, gx, gy (mm). |
| `startAt` | integer | no (default `1`) | Start at this label position on the first sheet (skips used labels). |
| `fontSize` | number | no (default `11`) | Font size in pt (default 11). |
| `font` | "sans" \| "serif" \| "mono" | no (default `"sans"`) |  |
| `align` | "left" \| "center" \| "right" | no (default `"center"`) |  |
| `valign` | "top" \| "middle" | no (default `"middle"`) |  |
| `bold` | boolean | no (default `true`) | Bold first line (e.g. the name). |
| `fit` | boolean | no (default `true`) | Shrink text (in 0.5 pt steps, min 5 pt) until it fits the label. |
| `border` | boolean | no (default `false`) | Draw a thin rounded border around each label. |
| `padding` | number | no (default `3`) | Inner padding in mm. |
| `offsetX` | number | no (default `0`) | Printer nudge right (mm, negative = left). |
| `offsetY` | number | no (default `0`) | Printer nudge down (mm, negative = up). |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size in bytes. |
| `pages` | number | Number of pages. |
| `mimeType` | string |  |
| `base64` | string |  |
| `labels` | number | Labels printed. |
| `perSheet` | number |  |
| `shrunk` | number | Labels whose text was shrunk to fit. |
| `truncated` | number | Labels whose text still did not fit (cut at the bottom). |

**Example** — 2 address labels on an A4 21-up sheet.

```json
{
  "text": "Lina Haddad\n12 Rainbow Street\nAmman 11181\n\nOmar Saleh\n4 Garden Lane\nIrbid 21110",
  "border": true
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-ngqEIy\\labels.pdf",
  "bytes": 1545,
  "mimeType": "application/pdf",
  "pages": 1,
  "labels": 2,
  "perSheet": 21,
  "shrunk": 0,
  "truncated": 0
}
```

**Example**

```json
{
  "mode": "csv",
  "text": "Name,City\nLina,Amman\nOmar,Irbid",
  "template": "{Name}\n{City}",
  "preset": "lt-30"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-VE8GV4\\labels.pdf",
  "bytes": 1091,
  "mimeType": "application/pdf",
  "pages": 1,
  "labels": 2,
  "perSheet": 30,
  "shrunk": 0,
  "truncated": 0
}
```

**Example**

```json
{
  "mode": "same",
  "text": "Homemade Fig Jam\n250 g",
  "count": 24,
  "preset": "a4-24"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-cNF3mK\\labels.pdf",
  "bytes": 2089,
  "mimeType": "application/pdf",
  "pages": 1,
  "labels": 24,
  "perSheet": 24,
  "shrunk": 0,
  "truncated": 0
}
```

<a id="doc_receipt"></a>
### `doc_receipt` — Receipt PDF

Make a simple sales receipt as PDF: an 80 mm thermal-roll receipt (page height fits the content, monospace) or an A5 page. Shop header (name, address, phone, tax number), receipt number with prefix, date/time, customer, items (name × qty × price), discount (% or fixed amount), tax % after discount, total, payment method, amount paid and change, note and footer. Same maths as the website: amounts rounded half-up to 0, 2 or 3 decimals. Also returns the totals and a 32-column plain-text version. English labels, Latin text only for now. Web version: https://s3eidat.github.io/receipt-maker/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `shop` | object | no (default `{"name":"","address":"","phone":"","taxId":"","currency":"JD","decimals":2,"prefix":"R-","footer":"Thank you!"}`) | Shop details: name, address, phone, taxId, currency, decimals, prefix, footer. |
| `no` | integer | no (default `1`) | Receipt number. |
| `when` | string | no | Date and time, YYYY-MM-DDTHH:MM (default now). |
| `customer` | string | no (default `""`) | Customer name or table (optional). |
| `pay` | "cash" \| "card" \| "transfer" \| "wallet" \| "other" | no (default `"cash"`) | Payment method. |
| `items` | object[] | yes | Items: name, qty, price. |
| `disc` | number | no (default `0`) | Discount: a percentage, or an amount when discType is "amt". |
| `discType` | "pct" \| "amt" | no (default `"pct"`) | pct = percent of subtotal (default), amt = fixed amount. |
| `tax` | number | no (default `0`) | Tax rate % applied after the discount. |
| `paid` | number | no (default `0`) | Amount the customer paid (shows change). |
| `note` | string | no (default `""`) | Note line above the footer. |
| `format` | "thermal" \| "a5" | no (default `"thermal"`) | thermal = 80 mm roll (default), a5 = A5 page. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size in bytes. |
| `pages` | number | Number of pages. |
| `mimeType` | string |  |
| `base64` | string |  |
| `subtotal` | number |  |
| `discount` | number |  |
| `tax` | number |  |
| `total` | number |  |
| `paid` | number |  |
| `change` | number |  |
| `text` | string | Plain-text receipt (32 columns). |

**Example** — Subtotal 8.000, discount 0.800, tax 1.152, total 8.352 JD, change 1.648.

```json
{
  "shop": {
    "name": "Zaytoun Corner Café",
    "address": "Rainbow St. 12, Amman",
    "currency": "JD",
    "decimals": 3
  },
  "no": 1043,
  "customer": "Table 4",
  "paid": 10,
  "disc": 10,
  "tax": 16,
  "items": [
    {
      "name": "Cappuccino",
      "qty": 2,
      "price": 2.25
    },
    {
      "name": "Za'atar manakeesh",
      "qty": 1,
      "price": 1.5
    },
    {
      "name": "Fresh orange juice",
      "qty": 1,
      "price": 2
    }
  ]
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-y912kN\\receipt-R-1043.pdf",
  "bytes": 2399,
  "mimeType": "application/pdf",
  "pages": 1,
  "subtotal": 8,
  "discount": 0.8,
  "tax": 1.152,
  "total": 8.352,
  "paid": 10,
  "change": 1.648,
  "text": "      Zaytoun Corner Café\n     Rainbow St. 12, Amman\n--------------------------------\n            Receipt\nNo.                       R-1043\nDate           15/01/2026  12:30\nCustomer                 Table 4\n--------------------------------\nCappuccino\n  2 × 2.250 JD          4.500 JD\nZa'atar manakeesh\n  1 × 1.500 JD          1.500 JD\nFresh orange juice\n  1 × 2.000 JD          2.000 JD\n--------------------------------\nSubtotal                8.000 JD\nDiscount 10%           −0.800 JD\nTax 16%                 1.152 JD\nTOTAL                   8.352 JD\nPayment                     Cash\nPaid             …"
}
```

<a id="doc_resume"></a>
### `doc_resume` — Resume / CV PDF

Turn structured CV fields into a clean A4 resume PDF: contact header, summary, experience, education, skills, projects and languages, in any section order, with three templates like the website: "classic" (centred header and accent rule), "modern" (coloured sidebar with contact, skills and languages) and "compact" (denser, headings in a left column). Description lines starting with "-", "•", "*" or "–" become bullet points; skills are comma-separated. Aims for one page; longer CVs continue on a second page. English headings, Latin text only for now. Web version: https://s3eidat.github.io/resume-builder/

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `template` | "classic" \| "modern" \| "compact" | no (default `"classic"`) |  |
| `accent` | string | no (default `"#0e7c86"`) | Accent colour (hex). |
| `order` | ("summary" \| "experience" \| "education" \| "skills" \| "projects" \| "languages")[] | no | Section order; missing sections are appended in the default order. |
| `contact` | object | no (default `{"name":"","title":"","email":"","phone":"","location":"","website":""}`) |  |
| `summary` | string | no (default `""`) | Two or three lines about you. |
| `experience` | object[] | no (default `[]`) |  |
| `education` | object[] | no (default `[]`) |  |
| `skills` | string \| string[] | no (default `""`) | Skills: comma-separated text or a list. |
| `projects` | object[] | no (default `[]`) |  |
| `languages` | object[] | no (default `[]`) |  |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size in bytes. |
| `pages` | number | Number of pages. |
| `mimeType` | string |  |
| `base64` | string |  |

**Example**

```json
{
  "template": "modern",
  "contact": {
    "name": "Layla Haddad",
    "title": "Senior Product Designer",
    "email": "layla@example.com",
    "location": "Amman, Jordan"
  },
  "summary": "Product designer with 8 years of experience.",
  "experience": [
    {
      "role": "Senior Product Designer",
      "org": "Mosaic Health",
      "start": "2022",
      "end": "Present",
      "desc": "- Redesigned patient booking; completion up 31%\n- Built a design system"
    }
  ],
  "skills": "Figma, Prototyping, User research",
  "languages": [
    {
      "name": "Arabic",
      "level": "Native"
    },
    {
      "name": "English",
      "level": "Fluent"
    }
  ]
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-6FSQmm\\Layla-Haddad.pdf",
  "bytes": 2360,
  "mimeType": "application/pdf",
  "pages": 1
}
```

<a id="ai"></a>
## For AI agents

Extra tools made for agents: documents to Markdown, OCR, chunking, SQL over CSV, charts and more.

| Tool | What it does |
|---|---|
| [`ai_chart`](#ai_chart) | Chart to SVG/PNG |
| [`ai_chunk_text`](#ai_chunk_text) | Chunk text for RAG |
| [`ai_create_pptx`](#ai_create_pptx) | Create a PowerPoint deck |
| [`ai_create_xlsx`](#ai_create_xlsx) | Create an Excel workbook (.xlsx) |
| [`ai_csv_profile`](#ai_csv_profile) | Profile a table (CSV, JSON, XLSX) |
| [`ai_detect_language`](#ai_detect_language) | Detect language |
| [`ai_diagram`](#ai_diagram) | Diagram to SVG/PNG |
| [`ai_docx_to_markdown`](#ai_docx_to_markdown) | Word (.docx) to Markdown |
| [`ai_extract_entities`](#ai_extract_entities) | Extract entities |
| [`ai_file_identify`](#ai_file_identify) | Identify a file type |
| [`ai_html_to_markdown`](#ai_html_to_markdown) | HTML to Markdown |
| [`ai_image_diff`](#ai_image_diff) | Compare two images |
| [`ai_json_schema_infer`](#ai_json_schema_infer) | Infer a JSON Schema from samples |
| [`ai_json_schema_validate`](#ai_json_schema_validate) | Validate JSON against a JSON Schema |
| [`ai_markdown_to_docx`](#ai_markdown_to_docx) | Markdown to Word (.docx) |
| [`ai_markdown_to_pdf`](#ai_markdown_to_pdf) | Markdown to PDF |
| [`ai_media_info`](#ai_media_info) | Audio/video metadata |
| [`ai_ocr`](#ai_ocr) | OCR (image or scanned PDF to text) |
| [`ai_pdf_to_markdown`](#ai_pdf_to_markdown) | PDF to Markdown |
| [`ai_pptx_to_markdown`](#ai_pptx_to_markdown) | PowerPoint (.pptx) to Markdown |
| [`ai_sql_query`](#ai_sql_query) | SQL query over tables |
| [`ai_text_similarity`](#ai_text_similarity) | Text similarity and near-duplicates |
| [`ai_token_count`](#ai_token_count) | Count tokens |
| [`ai_web_fetch`](#ai_web_fetch) | Fetch a web page as Markdown |
| [`ai_xlsx_to_markdown`](#ai_xlsx_to_markdown) | Excel (.xlsx/.csv) to Markdown tables |
| [`ai_zip`](#ai_zip) | ZIP create / extract / list |

<a id="ai_chart"></a>
### `ai_chart` — Chart to SVG/PNG

Render a chart to an SVG or PNG file, offline. Either give a simple description ({ type: bar|line|area|pie|scatter, data: rows, x, y, color?, title? }; y may be a list of fields for multi-series line/bar charts), or a full Vega-Lite (or Vega) spec in "spec" with the rows inline in data.values. Field types are inferred (numbers = quantitative, ISO dates = temporal, other = categories). Loading data or images from URLs or files is refused. Up to 50,000 rows. PNG is rasterised from the SVG at "scale" (default 2×).

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `spec` | object | no | A full Vega-Lite spec (or Vega, if its $schema says so). Data must be inline (data.values). Use this or "type". |
| `type` | "bar" \| "line" \| "area" \| "pie" \| "scatter" | no | Simple chart type. Use with data, x and y. |
| `data` | object[] | no | Rows for the simple chart, e.g. [{"month":"Jan","sales":12}]. |
| `x` | string | no | Field for the x axis (the category / slice label for pie). |
| `y` | string \| string[] | no | Numeric field for the y axis (slice size for pie). A list of fields draws one series per field. |
| `color` | string | no | Optional field that splits the data into coloured series. |
| `title` | string | no |  |
| `width` | integer | no | Plot width in px (default 600; pie 360). |
| `height` | integer | no | Plot height in px (default 360). |
| `format` | "svg" \| "png" | no (default `"svg"`) | Output format: svg (default) or png. |
| `scale` | number | no (default `2`) | PNG only: pixel density multiplier (2 = twice the SVG size, sharp on high-DPI screens). Default 2. |
| `background` | string | no (default `"#ffffff"`) | Background colour. Default white; "transparent" for none. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string |  |
| `bytes` | number |  |
| `mimeType` | string |  |
| `base64` | string |  |
| `format` | string |  |
| `width` | number | Image width in px. |
| `height` | number | Image height in px. |

**Example**

```json
{
  "type": "bar",
  "data": [
    {
      "month": "Jan",
      "sales": 12
    },
    {
      "month": "Feb",
      "sales": 19
    },
    {
      "month": "Mar",
      "sales": 7
    }
  ],
  "x": "month",
  "y": "sales",
  "title": "Sales"
}
```

Result:

```json
{
  "format": "svg"
}
```

**Example**

```json
{
  "type": "pie",
  "data": [
    {
      "k": "A",
      "v": 3
    },
    {
      "k": "B",
      "v": 1
    }
  ],
  "x": "k",
  "y": "v",
  "format": "png"
}
```

Result:

```json
{
  "format": "png",
  "mimeType": "image/png"
}
```

<a id="ai_chunk_text"></a>
### `ai_chunk_text` — Chunk text for RAG

Split text, markdown or a text file into token-sized chunks for retrieval (RAG) and embeddings. Splits at markdown headings (ATX "#" headings, never inside code fences) and paragraphs first, then sentences (lines inside code blocks), and only cuts mid-sentence when a single sentence is longer than maxTokens. Consecutive chunks of the same section share about overlapTokens of text. Each chunk returns index, text, tokens, startChar/endChar (JavaScript string offsets, end exclusive, so text === source.slice(startChar, endChar) once CRLF line endings are normalised to \n) and the heading path it sits under. Tokens are counted with tiktoken (o200k_base by default; for Claude treat counts as estimates). Give "text" or "path" (file path or base64); give "output" to also write the chunks as JSONL (one JSON object per line).

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The text (inline). |
| `path` | string | no | Or: a UTF-8 text/markdown file, as a local path or base64 (raw or a data: URI). |
| `maxTokens` | integer | no (default `500`) | Largest chunk, in tokens. Default 500. |
| `overlapTokens` | integer | no (default `50`) | Tokens repeated from the end of the previous chunk (same section only). Default 50. Must be below maxTokens. |
| `markdown` | boolean | no (default `true`) | Treat "#" lines as section headings (default true). Set false for plain text. |
| `encoding` | "o200k_base" \| "cl100k_base" | no (default `"o200k_base"`) | Tokenizer: "o200k_base" (GPT-4o / o-series, default) or "cl100k_base" (GPT-4 / GPT-3.5 / text-embedding-3). |
| `output` | string | no | Also write the chunks as JSONL to this file (or folder ending in "/"). |
| `includeText` | boolean | no | Return each chunk's text inline. Default: true, or false when "output" is given. |
| `maxChunks` | integer | no (default `2000`) | Most chunks to return inline (the JSONL file always has all). Default 2000. |

**Output**

| Field | Type | Description |
|---|---|---|
| `chunkCount` | number |  |
| `totalTokens` | number | Tokens in the whole source text. |
| `encoding` | string |  |
| `maxTokens` | number |  |
| `overlapTokens` | number |  |
| `chunks` | object[] |  |
| `truncated` | boolean |  |
| `path` | string |  |
| `bytes` | number |  |

**Example** — Two chunks, headings ["Guide","Install"] and ["Guide","Use"].

```json
{
  "text": "# Guide\n\n## Install\n\nRun npm install.\n\n## Use\n\nCall the tool.",
  "maxTokens": 100,
  "overlapTokens": 0
}
```

Result:

```json
{
  "chunkCount": 2,
  "totalTokens": 17,
  "encoding": "o200k_base",
  "maxTokens": 100,
  "overlapTokens": 0,
  "chunks": [
    {
      "index": 0,
      "tokens": 10,
      "startChar": 0,
      "endChar": 37,
      "headings": [
        "Guide",
        "Install"
      ],
      "text": "# Guide\n\n## Install\n\nRun npm install."
    },
    {
      "index": 1,
      "tokens": 7,
      "startChar": 39,
      "endChar": 61,
      "headings": [
        "Guide",
        "Use"
      ],
      "text": "## Use\n\nCall the tool."
    }
  ]
}
```

**Example**

```json
{
  "path": "handbook.md",
  "maxTokens": 400,
  "overlapTokens": 40,
  "output": "chunks.jsonl"
}
```

<a id="ai_create_pptx"></a>
### `ai_create_pptx` — Create a PowerPoint deck

Build a 16:9 PowerPoint (.pptx) deck from a simple outline: an optional title slide, then slides with a title plus any of bullets (nested), a paragraph, an image (file path or base64), a table (first row = header; long tables continue on extra slides), a native editable chart (bar, line, pie) and speaker notes. Light or dark theme with clean, readable defaults; text plus a visual are laid out side by side. Arabic/Hebrew text is set right-to-left. Up to 300 slides. Opens in PowerPoint, Keynote, Google Slides and LibreOffice.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `title` | string | no | Deck title: adds a title slide and sets the file title. |
| `subtitle` | string | no | Subtitle on the title slide. |
| `author` | string | no |  |
| `theme` | "light" \| "dark" | no (default `"light"`) |  |
| `slides` | object[] | yes |  |
| `slideNumbers` | boolean | no (default `true`) | Show slide numbers on content slides. Default true. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string |  |
| `bytes` | number |  |
| `mimeType` | string |  |
| `base64` | string |  |
| `slides` | number | Slides in the deck (title slide and table continuation slides included). |

**Example**

```json
{
  "title": "Q3 Review",
  "subtitle": "Sales team",
  "theme": "light",
  "slides": [
    {
      "title": "Highlights",
      "bullets": [
        "Revenue up 12%",
        [
          "EMEA +20%",
          "APAC +8%"
        ],
        "Two new clients"
      ],
      "notes": "Start with the good news."
    },
    {
      "title": "Revenue by month",
      "chart": {
        "type": "bar",
        "labels": [
          "Jul",
          "Aug",
          "Sep"
        ],
        "series": [
          {
            "name": "Revenue",
            "values": [
              120,
              135,
              150
            ]
          }
        ]
      }
    },
    {
      "title": "Team",
      "table": [
        [
          "Name",
          "Deals"
        ],
        [
          "Sara",
          12
        ],
        [
          "Omar",
          9
        ]
      ]
    }
  ]
}
```

Result:

```json
{
  "slides": 4
}
```

<a id="ai_create_xlsx"></a>
### `ai_create_xlsx` — Create an Excel workbook (.xlsx)

Build a real Excel .xlsx file from data: one or more sheets, each with optional column definitions (header, key, width, numFmt) and rows given as objects or arrays. Bold shaded header, frozen header row and filter buttons (both on by default), column widths fitted to the content, Excel number formats (raw codes like "#,##0.00" or aliases: integer, number, percent, currency, usd, eur, gbp, date, datetime, time, text), strings starting with "=" written as live formulas (=SUM(B2:B9), =B2*C2, cross-sheet refs), ISO date strings written as real dates, and an optional totals row (SUM/AVERAGE/COUNT/MIN/MAX formulas). Up to 50 sheets and 1,000,000 rows per sheet. Formulas are calculated by Excel when the file is opened. Writes to "output" (default "workbook.xlsx" in the output folder) and returns the path and a summary of each sheet.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `sheets` | object[] | yes |  |
| `parseDates` | boolean | no (default `true`) | Write ISO date strings as Excel dates (default true). |
| `title` | string | no | Workbook title (file properties, and the default file name). |
| `author` | string | no | Author in the file properties. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `sheets` | object[] |  |

**Example**

```json
{
  "sheets": [
    {
      "name": "Orders",
      "columns": [
        {
          "header": "Item"
        },
        {
          "header": "Qty",
          "numFmt": "integer"
        },
        {
          "header": "Price",
          "numFmt": "usd"
        },
        {
          "header": "Total",
          "numFmt": "usd"
        }
      ],
      "rows": [
        [
          "Pen",
          3,
          1.5,
          "=B2*C2"
        ],
        [
          "Book",
          1,
          12,
          "=B3*C3"
        ]
      ],
      "totalsRow": {
        "Qty": "sum",
        "Total": "sum"
      }
    }
  ]
}
```

Result:

```json
{
  "sheets": [
    {
      "name": "Orders",
      "rows": 2,
      "columns": [
        "Item",
        "Qty",
        "Price",
        "Total"
      ],
      "range": "A1:D4",
      "formulas": 4
    }
  ]
}
```

<a id="ai_csv_profile"></a>
### `ai_csv_profile` — Profile a table (CSV, JSON, XLSX)

Profile a table to understand and clean it before analysis: row and column counts, and per column the inferred type (integer, number, boolean, date, datetime, string), non-null and null counts, unique count, top 5 values, sample values, min/max/mean/median/stddev for numbers, min/max for ISO dates, min/max length for text, plus data-quality warnings (mixed types, leading/trailing spaces, mostly missing, constant columns, duplicate rows, ragged headers). Input: "file" (local path or base64 of CSV, TSV, JSON array, JSONL or .xlsx, first sheet or "sheet") or inline "csv" / "json". Empty cells, "null" and "NULL" count as missing. Runs locally; nothing is uploaded.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | no | A table file: local path (absolute, or relative to --root) or base64. CSV, TSV, JSON array, JSONL or .xlsx. |
| `csv` | string | no | Or: CSV/TSV text inline (delimiter detected; first row is the header). |
| `json` | string \| object[] | no | Or: JSON inline: an array of objects (or of arrays, first one the header), as a value or as JSON/JSONL text. |
| `sheet` | string | no | For .xlsx: the sheet name to read (default: the first sheet). |
| `format` | "auto" \| "csv" \| "tsv" \| "json" \| "jsonl" \| "xlsx" | no (default `"auto"`) | Force the file format (default "auto": by extension, then by content). |
| `columns` | string[] | no | Only profile these columns (default: all). |

**Output**

| Field | Type | Description |
|---|---|---|
| `rows` | number |  |
| `columns` | number |  |
| `format` | string |  |
| `sheet` | string |  |
| `duplicateRows` | number | Rows that exactly repeat an earlier row. |
| `profile` | object[] |  |
| `warnings` | string[] | Table-level warnings. |

**Example**

```json
{
  "csv": "name,age\nAda,36\nBob ,41\nAda,36\n,"
}
```

Result:

```json
{
  "rows": 4,
  "columns": 2,
  "duplicateRows": 1
}
```

<a id="ai_detect_language"></a>
### `ai_detect_language` — Detect language

Detect the language of a text with franc-min (82 languages with 8M+ speakers, ISO 639-3 codes) and the writing systems it uses (Latin, Arabic, Cyrillic, Han/CJK, Hangul, Devanagari, Greek, Hebrew, Thai…). Returns the top 3 candidates with scores (1 = best match, relative to the others), English names and ISO 639-1 codes where they exist, plus a share per script. Texts under 20 characters are "too short": only the script is reported. Works best on a sentence or more of one language; for mixed text the dominant script wins; long texts are judged on their first 100,000 characters. Hebrew is not among the languages (script only). Give "text" or "path" (file path or base64).

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The text (inline). |
| `path` | string | no | Or: a UTF-8 text/markdown file, as a local path or base64 (raw or a data: URI). |
| `only` | string[] | no | Restrict to these ISO 639-3 codes (e.g. ["arb","eng","fra"]). |

**Output**

| Field | Type | Description |
|---|---|---|
| `language` | string \| null | Best ISO 639-3 code, or null when too short/undetermined. |
| `name` | string \| null |  |
| `iso6391` | string \| null |  |
| `confidence` | "high" \| "medium" \| "low" \| "none" |  |
| `tooShort` | boolean |  |
| `note` | string |  |
| `candidates` | object[] |  |
| `script` | string \| null | Dominant writing system. |
| `isCJK` | boolean |  |
| `scripts` | object[] |  |
| `characters` | number |  |

**Example**

```json
{
  "text": "The quick brown fox jumps over the lazy dog near the river bank."
}
```

Result:

```json
{
  "language": "eng",
  "iso6391": "en",
  "script": "Latin"
}
```

**Example**

```json
{
  "text": "مرحبا بكم في موقعنا، نتمنى لكم يوما سعيدا"
}
```

Result:

```json
{
  "language": "arb",
  "iso6391": "ar",
  "script": "Arabic"
}
```

**Example**

```json
{
  "text": "Hi there"
}
```

Result:

```json
{
  "tooShort": true,
  "script": "Latin"
}
```

<a id="ai_diagram"></a>
### `ai_diagram` — Diagram to SVG/PNG

Draw a flowchart / graph diagram from a tiny text language and save it as SVG or PNG, laid out automatically (dagre). One statement per line: `direction: LR` (TB, LR, BT, RL), `title: My flow`, `A [shape=diamond, label="Ready?", color=#fde68a]` (shapes: box, round, diamond, circle, ellipse), edges `A -> B: label`, chains `A -> B -> C`, `-->` dashed, `==>` thick, `<->` both ends, `--` no arrow; quote names with spaces ("Start here" -> A); `\n` in labels breaks lines; # starts a comment. Or pass JSON { nodes: [{id, label, shape, color}], edges: [{from, to, label, style, arrow}] } as "graph". Up to 500 nodes and 2000 edges. Labels use Helvetica/Arial metrics (Latin text is measured best).

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `source` | string | no | Diagram text in the DSL (or a JSON graph as text). |
| `graph` | object | no | The diagram as JSON instead of "source". |
| `direction` | "TB" \| "LR" \| "BT" \| "RL" | no | Override the layout direction: TB (top-bottom, default), LR, BT, RL. |
| `theme` | "light" \| "dark" | no (default `"light"`) |  |
| `format` | "svg" \| "png" | no (default `"svg"`) | Output format: svg (default) or png. |
| `scale` | number | no (default `2`) | PNG only: pixel density multiplier (2 = twice the SVG size, sharp on high-DPI screens). Default 2. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string |  |
| `bytes` | number |  |
| `mimeType` | string |  |
| `base64` | string |  |
| `format` | string |  |
| `width` | number |  |
| `height` | number |  |
| `nodes` | number |  |
| `edges` | number |  |

**Example**

```json
{
  "source": "direction: LR\nStart [shape=circle]\nCheck [shape=diamond, label=\"Valid?\"]\nStart -> Check\nCheck -> Done: yes\nCheck -> Fix: no\nFix --> Check"
}
```

Result:

```json
{
  "format": "svg",
  "nodes": 4,
  "edges": 4
}
```

**Example**

```json
{
  "graph": {
    "nodes": [
      {
        "id": "a",
        "label": "Client"
      },
      {
        "id": "b",
        "label": "Server",
        "shape": "box"
      }
    ],
    "edges": [
      {
        "from": "a",
        "to": "b",
        "label": "HTTPS",
        "arrow": "both"
      }
    ]
  },
  "format": "png"
}
```

Result:

```json
{
  "format": "png",
  "nodes": 2,
  "edges": 1
}
```

<a id="ai_docx_to_markdown"></a>
### `ai_docx_to_markdown` — Word (.docx) to Markdown

Convert a Word .docx document to Markdown: headings (from Heading styles), bold/italic, lists, links, tables, footnotes. Images become placeholders like ![](image-1.png) unless embedImages is true (then data: URIs, which can be very large). Returns the number of conversion warnings (unrecognised styles and the like) and the first few messages. Old binary .doc files are not supported. Accepts a local path or base64. Inline Markdown is cut at maxChars (default 200k); "output" saves it to a file. Runs locally; nothing is uploaded.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The .docx document: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `embedImages` | boolean | no (default `false`) | Embed images as base64 data: URIs instead of placeholders. Default false. |
| `maxChars` | integer | no (default `200000`) | Cut the inline Markdown after this many characters, with a note (a file written with "output" is never cut). Default 200000. |
| `output` | string | no | Write the Markdown to this file (or folder ending in "/") and return its path instead of the inline text. |

**Output**

| Field | Type | Description |
|---|---|---|
| `images` | number | Images found in the document. |
| `warnings` | number | Conversion warnings and errors reported by the converter. |
| `messages` | string[] | Up to 10 of those messages. |
| `markdown` | string | The Markdown (absent when written to "output"). |
| `truncated` | boolean | True when the inline Markdown was cut at maxChars. |
| `characters` | number | Length of the full Markdown in characters. |
| `words` | number | Words in the text (link URLs not counted). |
| `path` | string | Written file, when "output" was given. |
| `bytes` | number |  |

**Example**

```json
{
  "file": "report.docx"
}
```

**Example**

```json
{
  "file": "thesis.docx",
  "output": "thesis.md"
}
```

<a id="ai_extract_entities"></a>
### `ai_extract_entities` — Extract entities

Pull structured items out of text or a text file, deduplicated with counts: emails, URLs, phone numbers (libphonenumber, validated, E.164 plus type/country; local numbers read with defaultRegion, default JO), dates (ISO, D/M/Y or M/D/Y, Y/M/D, English month names; normalised to YYYY-MM-DD; a date like 03/04/2024 is flagged ambiguous with the other reading), money ($, €, £, JOD/JD/د.أ, SAR/ر.س, AED/د.إ, KWD, EGP… and Arabic currency words, with amount and ISO currency), percentages, #hashtags, @mentions, IPv4/IPv6, IBANs (country length and mod-97 checked) and card-like numbers (only Luhn-valid ones with a known brand prefix, always masked: only the last 4 digits are shown, the full number is never returned). Arabic-Indic digits are understood. Regex-based, not NLP: no names, places or organisations. Give "text" or "path" (file path or base64).

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The text (inline). |
| `path` | string | no | Or: a UTF-8 text/markdown file, as a local path or base64 (raw or a data: URI). |
| `types` | ("emails" \| "urls" \| "phones" \| "dates" \| "money" \| "percentages" \| "hashtags" \| "mentions" \| "ipv4" \| "ipv6" \| "ibans" \| "cards")[] | no | Only these types (default all): emails, urls, phones, dates, money, percentages, hashtags, mentions, ipv4, ipv6, ibans, cards. |
| `defaultRegion` | string | no (default `"JO"`) | Two-letter country for local phone numbers, regional currencies (dinar, riyal…) and D/M/Y vs M/D/Y. Default JO. |
| `maxPerType` | integer | no (default `500`) | Most unique items to return per type. Default 500. |

**Output**

| Field | Type | Description |
|---|---|---|
| `counts` | object | Unique items per type. |
| `occurrences` | object | All occurrences per type (before dedupe). |
| `total` | number | Unique items over all types. |
| `entities` | object | Per type, in order of first appearance. "index" is the first offset in the text. |
| `truncated` | string[] |  |

**Example**

```json
{
  "text": "Mail omar@example.com or call 079 123 4567 before 15/03/2025. Fee: 25 JOD (10% off). #sale @s3eidat"
}
```

Result:

```json
{
  "counts": {
    "emails": 1,
    "phones": 1,
    "dates": 1,
    "money": 1,
    "percentages": 1,
    "hashtags": 1,
    "mentions": 1
  }
}
```

<a id="ai_file_identify"></a>
### `ai_file_identify` — Identify a file type

Find out what a file really is from its content (magic bytes, via file-type: images, video, audio, archives, Office, PDF, fonts, executables… ~150 formats), not its name. Reports the detected type and MIME, whether the extension disagrees with the content, size, SHA-256, and for text files an encoding guess (UTF-8, UTF-8/16/32 with BOM, UTF-16 without BOM, ASCII, 8-bit legacy) plus a text kind (JSON, XML, SVG, HTML…). Accepts a file path or base64. Nothing is executed.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The file to identify: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `name` | string | no | File name to compare the extension against (handy for base64 input). Default: the path's own name. |

**Output**

| Field | Type | Description |
|---|---|---|
| `name` | string |  |
| `size` | number | Size in bytes. |
| `sha256` | string |  |
| `extension` | string \| null | Extension of the file name (lower case, no dot), or null. |
| `detected` | object \| null | What the content is; null when unknown binary. |
| `isText` | boolean |  |
| `encoding` | string \| null | Text encoding guess, or null for binary formats. |
| `mismatch` | boolean | True when the extension does not match the detected content. |
| `note` | string |  |

**Example**

```json
{
  "file": "base64:iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
  "name": "photo.jpg"
}
```

Result:

```json
{
  "detected": {
    "ext": "png",
    "mime": "image/png"
  },
  "mismatch": true
}
```

<a id="ai_html_to_markdown"></a>
### `ai_html_to_markdown` — HTML to Markdown

Convert HTML to clean Markdown for reading or feeding to a model: headings, lists, links, images, code blocks, GitHub-style tables and strikethrough. Give the HTML inline ("html") or as a file ("path": local path or base64). Optional "readable" keeps only the main article (Mozilla Readability, falls back to the whole body), "selector" converts only matching elements, "baseUrl" makes relative links absolute, "includeLinks": false drops link URLs and images. Scripts, styles and forms are removed. Inline Markdown is cut at maxChars (default 200k) with a note; give "output" to save the whole thing to a .md file. Runs locally; nothing is uploaded.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `html` | string | no | The HTML text. Give this or "path". |
| `path` | string | no | An .html file instead of "html": local path or base64. |
| `readable` | boolean | no (default `false`) | Keep only the main content (article text), dropping navigation, ads and footers. |
| `selector` | string | no | CSS selector: convert only the matching elements (e.g. "main", "article .post"). |
| `baseUrl` | string | no | URL the HTML came from, to make relative links and images absolute. |
| `includeLinks` | boolean | no (default `true`) | Keep links and images. False turns links into plain text and drops images. |
| `maxChars` | integer | no (default `200000`) | Cut the inline Markdown after this many characters, with a note (a file written with "output" is never cut). Default 200000. |
| `output` | string | no | Write the Markdown to this file (or folder ending in "/") and return its path instead of the inline text. |

**Output**

| Field | Type | Description |
|---|---|---|
| `title` | string |  |
| `byline` | string |  |
| `source` | "selector" \| "readability" \| "body" | What was converted: the selector matches, the Readability main content, or the whole body. |
| `markdown` | string | The Markdown (absent when written to "output"). |
| `truncated` | boolean | True when the inline Markdown was cut at maxChars. |
| `characters` | number | Length of the full Markdown in characters. |
| `words` | number | Words in the text (link URLs not counted). |
| `path` | string | Written file, when "output" was given. |
| `bytes` | number |  |

**Example**

```json
{
  "html": "<h1>Notes</h1><p>Hello <b>world</b>, see <a href=\"/docs\">docs</a>.</p><ul><li>one</li><li>two</li></ul>",
  "baseUrl": "https://example.com/"
}
```

Result:

```json
{
  "markdown": "# Notes\n\nHello **world**, see [docs](https://example.com/docs).\n\n- one\n- two\n"
}
```

**Example**

```json
{
  "path": "page.html",
  "readable": true,
  "output": "page.md"
}
```

<a id="ai_image_diff"></a>
### `ai_image_diff` — Compare two images

Compare two images pixel by pixel (pixelmatch, anti-aliasing aware) and write a diff PNG with changed pixels in red over a faded copy of the first image. Returns the number and percentage of different pixels. Images must be the same size unless resize is true (the second is stretched to the first's size). Reads PNG, JPEG, WebP, AVIF, GIF (first frame), TIFF and SVG; file path or base64; up to 50 MP. Good for visual regression checks.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `a` | string | yes | The first (reference) image: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `b` | string | yes | The second image: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `threshold` | number | no (default `0.1`) | Colour difference tolerance 0..1; smaller is stricter. Default 0.1. |
| `includeAA` | boolean | no (default `false`) | Count anti-aliased edge pixels as differences too. Default false. |
| `resize` | boolean | no (default `false`) | Resize the second image to the first one's size when they differ (otherwise that is an error). |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | The diff PNG. |
| `bytes` | number |  |
| `mimeType` | string |  |
| `base64` | string |  |
| `width` | number |  |
| `height` | number |  |
| `diffPixels` | number |  |
| `totalPixels` | number |  |
| `percent` | number | Share of different pixels, 0..100 (4 decimals). |
| `identical` | boolean |  |
| `resized` | boolean |  |

**Example** — Writes before-diff.png and returns the changed pixel count.

```json
{
  "a": "before.png",
  "b": "after.png",
  "threshold": 0.1
}
```

<a id="ai_json_schema_infer"></a>
### `ai_json_schema_infer` — Infer a JSON Schema from samples

Generate a JSON Schema (draft 2020-12) from one or more sample JSON values: types (integer vs number, mixed types as a type list), "null" added where a value can be null, required = keys present in every sample object, array items merged across all elements, string formats guessed when every sample matches (date-time, date, email, uri, uuid), and optional enums for small repeated string sets. Give "samples" (an array of sample values), or "text" (JSON, or JSONL = one sample per line), or "file" (path or base64 of a .json/.jsonl file). An array sample describes an array; to treat its elements as separate samples set eachItem. Pure local code; optionally writes the schema to "output".

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `samples` | object[] | no | Sample values; each element is one sample. |
| `text` | string | no | Or: JSON text (one sample) or JSONL (one sample per line). |
| `file` | string | no | Or: a .json/.jsonl file (local path or base64). |
| `eachItem` | boolean | no (default `false`) | If the only sample is an array, use its elements as the samples (schema of one record). Default false. |
| `title` | string | no | Optional "title" for the schema. |
| `enums` | boolean | no (default `false`) | Emit "enum" for string fields with few distinct values that repeat. Default false. |
| `maxEnumValues` | integer | no (default `8`) | Largest string set turned into an enum (default 8). |
| `formats` | boolean | no (default `true`) | Guess string formats (default true). |
| `required` | boolean | no (default `true`) | List keys present in every sample as "required" (default true). |
| `additionalProperties` | boolean | no (default `true`) | false adds additionalProperties: false to every object (strict). Default true (left open). |
| `output` | string | no | Optional: also write the schema as a .json file here. |

**Output**

| Field | Type | Description |
|---|---|---|
| `schema` | object |  |
| `samples` | number | How many samples were used. |
| `path` | string |  |
| `bytes` | number |  |

**Example**

```json
{
  "samples": [
    {
      "id": 1,
      "email": "a@b.co",
      "tags": [
        "x"
      ]
    },
    {
      "id": 2,
      "email": "c@d.org",
      "tags": [],
      "note": null
    }
  ]
}
```

Result:

```json
{
  "samples": 2,
  "schema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
      "id": {
        "type": "integer"
      },
      "email": {
        "type": "string",
        "format": "email"
      },
      "tags": {
        "type": "array",
        "items": {
          "type": "string"
        }
      },
      "note": {
        "type": "null"
      }
    },
    "required": [
      "id",
      "email",
      "tags"
    ]
  }
}
```

<a id="ai_json_schema_validate"></a>
### `ai_json_schema_validate` — Validate JSON against a JSON Schema

Validate JSON data against a JSON Schema with Ajv, reporting every error (allErrors) with its instancePath (JSON Pointer), schemaPath, keyword, message and params. Draft 2020-12 is used when the schema's $schema says 2020-12 (2019-09 likewise), otherwise draft-07 (draft-04/06 schemas are checked as draft-07). Formats checked: date, time, date-time, email, hostname, ipv4, ipv6, uri, uri-reference, uuid, regex, json-pointer (other formats are ignored and listed). Data: inline "data" (any JSON value), "dataText" (JSON text) or "dataFile" (path or base64). Schema: inline "schema" (object or JSON text) or "schemaFile". Remote $ref URLs are never fetched. Runs locally.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | object | no | The JSON value to validate, inline (a string here is validated as a string; use dataText for JSON text). |
| `dataText` | string | no | Or: the data as JSON text. |
| `dataFile` | string | no | Or: a JSON file (local path or base64). |
| `schema` | object \| boolean \| string | no | The JSON Schema inline: an object (or JSON text). |
| `schemaFile` | string | no | Or: a JSON Schema file (local path or base64). |
| `maxErrors` | integer | no (default `100`) | Most errors to return (default 100). |

**Output**

| Field | Type | Description |
|---|---|---|
| `valid` | boolean |  |
| `errorCount` | number |  |
| `errors` | object[] |  |
| `draft` | string | The JSON Schema draft used. |
| `ignoredFormats` | string[] |  |

**Example**

```json
{
  "data": {
    "name": "Omar",
    "age": -1
  },
  "schema": {
    "type": "object",
    "required": [
      "name",
      "email"
    ],
    "properties": {
      "age": {
        "type": "integer",
        "minimum": 0
      }
    }
  }
}
```

Result:

```json
{
  "valid": false,
  "errorCount": 2,
  "draft": "draft-07"
}
```

<a id="ai_markdown_to_docx"></a>
### `ai_markdown_to_docx` — Markdown to Word (.docx)

Convert Markdown (GitHub flavoured) to a real Word .docx: headings H1-H6 (Word heading styles, so the navigation pane and table of contents work), paragraphs with bold, italic, strikethrough, inline code and clickable links, bullet and numbered lists (nested, numbering restarts per list, task-list checkboxes), tables (header row shaded, column alignment kept), blockquotes, fenced code blocks in a monospace font, horizontal rules, and a page break wherever a line says \newpage. Arabic/Hebrew paragraphs are laid out right-to-left automatically (rtl: "auto"). Images are not embedded (their alt text is shown); raw HTML tags are dropped. Options: title and author (document properties), font (default Calibri), fontSize. Give "markdown" inline or "path" to a .md file. Writes the .docx to "output" (default name from the title or first heading).

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `markdown` | string | no | The Markdown text inline. |
| `path` | string | no | Or: a Markdown file (local path or base64). |
| `title` | string | no | Document title (file properties, and the default file name). |
| `author` | string | no | Author shown in the file properties. |
| `font` | string | no (default `"Calibri"`) | Body font (default Calibri). |
| `fontSize` | number | no (default `11`) | Body font size in points (default 11). |
| `rtl` | "auto" \| "on" \| "off" | no (default `"auto"`) | Right-to-left paragraphs: "auto" (detect Arabic/Hebrew per paragraph), "on" or "off". |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written file. |
| `bytes` | number | Size of the written file in bytes. |
| `mimeType` | string |  |
| `base64` | string |  |
| `paragraphs` | number | Paragraphs and tables written. |

**Example**

```json
{
  "markdown": "# Report\n\nHello **world**.\n\n- one\n- two",
  "title": "Report"
}
```

Result:

```json
{
  "path": "C:\\Users\\User\\AppData\\Local\\Temp\\s3eidat-docs-7XnoQD\\Report.docx",
  "bytes": 9792,
  "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  "paragraphs": 4
}
```

<a id="ai_markdown_to_pdf"></a>
### `ai_markdown_to_pdf` — Markdown to PDF

Turn Markdown (GitHub-flavoured) into a clean, paginated A4/Letter/A5 PDF: headings, paragraphs with word wrap, bold/italic/strikethrough, inline code, links (clickable), bullet / numbered / task lists (nested), tables (wrapped cells, header repeated on new pages), blockquotes, code blocks, horizontal rules, local images (relative to the Markdown file; remote images are never fetched and show as alt text), page breaks (\pagebreak, \newpage or <!-- pagebreak -->), page numbers, margins and title metadata. Text uses the built-in PDF fonts: Latin text only (English and Western European). Arabic, CJK and other scripts are not supported yet: such characters are replaced with "?" (or rejected with onUnsupportedChar: "error"); emoji are dropped; arrows and common symbols are transliterated. Give "markdown" inline or "path" to a .md file.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `markdown` | string | no | Markdown text. Use this or "path". |
| `path` | string | no | A .md file: local path or base64. Relative image links resolve against its folder. |
| `title` | string | no | PDF title metadata. Default: the first # / ## heading. |
| `author` | string | no |  |
| `paper` | "A4" \| "Letter" \| "A5" | no (default `"A4"`) |  |
| `landscape` | boolean | no (default `false`) |  |
| `margin` | number | no (default `20`) | Page margin in mm. Default 20. |
| `fontSize` | number | no (default `10.5`) | Body font size in pt. Default 10.5. |
| `font` | "sans" \| "serif" | no (default `"sans"`) | Body font: sans (Helvetica) or serif (Times). Code is always Courier. |
| `pageNumbers` | boolean | no (default `true`) | Print "n / total" at the bottom of each page. Default true. |
| `onUnsupportedChar` | "replace" \| "error" | no (default `"replace"`) | What to do with characters the PDF fonts can't show: replace with "?" (default) or fail. |
| `output` | string | no | Where to write the result: a file path, or a folder ending in "/". Default: the server output folder (a temp folder unless --out-dir is set). |
| `returnBase64` | boolean | no (default `false`) | Also return the output as base64 in the result (only for small files). Default false. |

**Output**

| Field | Type | Description |
|---|---|---|
| `path` | string | Absolute path of the written PDF. |
| `bytes` | number | Size in bytes. |
| `pages` | number | Number of pages. |
| `mimeType` | string |  |
| `base64` | string |  |
| `title` | string |  |
| `unsupportedChars` | string[] | Characters that could not be shown (replaced with "?" or dropped). |

**Example**

```json
{
  "markdown": "# Report\n\nHello **world**.\n\n- one\n- two\n\n| a | b |\n|---|---|\n| 1 | 2 |",
  "paper": "A4"
}
```

Result:

```json
{
  "pages": 1,
  "title": "Report"
}
```

**Example** — Converts a Markdown file; images next to it are embedded.

```json
{
  "path": "notes.md",
  "paper": "Letter",
  "margin": 15
}
```

<a id="ai_media_info"></a>
### `ai_media_info` — Audio/video metadata

Read audio and video file metadata offline (music-metadata): container, codec, duration, bitrate, sample rate, bit depth, channels, lossless or not, tags (title, artist, album, year, genre, track…), embedded cover art, and per-track info (including video width/height for MP4/MKV/WebM). Formats: MP3, AAC/M4A/MP4/MOV, FLAC, WAV, AIFF, OGG/Opus/Vorbis, WebM/Matroska, WMA/ASF, APE, WavPack, DSD and more. File path (streamed, any size) or base64. Duration of VBR MP3s without a header is computed by scanning the file.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The audio or video file: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |

**Output**

| Field | Type | Description |
|---|---|---|
| `name` | string |  |
| `fileSize` | number |  |
| `container` | string \| null |  |
| `codec` | string \| null |  |
| `codecProfile` | string |  |
| `lossless` | boolean \| null |  |
| `durationSeconds` | number \| null |  |
| `duration` | string \| null | Duration as m:ss or h:mm:ss. |
| `bitrate` | number \| null | Bits per second. |
| `sampleRate` | number \| null |  |
| `bitsPerSample` | number \| null |  |
| `channels` | number \| null |  |
| `numberOfSamples` | number |  |
| `tool` | string |  |
| `tagTypes` | string[] |  |
| `tags` | object |  |
| `hasCoverArt` | boolean |  |
| `coverArt` | object[] |  |
| `tracks` | object[] |  |

**Example** — Duration, bitrate, codec, ID3 tags and cover art presence.

```json
{
  "file": "song.mp3"
}
```

<a id="ai_ocr"></a>
### `ai_ocr` — OCR (image or scanned PDF to text)

Read the text in an image (PNG, JPEG, WebP, TIFF incl. multi-page, GIF, AVIF) or a scanned PDF with Tesseract (tesseract.js), fully offline: the English and Arabic models ship with the server and nothing is downloaded or uploaded. Languages: "eng", "ara" or "eng+ara" (default). PDF pages are rendered at 200 dpi by default. Returns the text per page and the mean word confidence (0-100); give "output" to also write a .txt file. Limits: up to 200 pages per call, roughly 1-5 s per page; printed text works well, handwriting and very small or skewed text poorly. For PDFs that already have a text layer, pdf_to_text is faster and exact. Give "file" as a local path or base64.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The image or PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `languages` | "eng" \| "ara" \| "eng+ara" | no (default `"eng+ara"`) | Languages to read. Default "eng+ara". |
| `pages` | string | no | PDF pages / TIFF frames, e.g. "1-3, 5" (default all). |
| `dpi` | integer | no (default `200`) | Render resolution for PDF pages. Default 200. |
| `psm` | "auto" \| "block" \| "line" \| "word" \| "sparse" | no (default `"auto"`) | Page layout: "auto" (default), "block" (one uniform block), "line", "word", or "sparse" (scattered text). |
| `perPage` | boolean | no (default `false`) | Also return the text of each page separately in "pages". Default false (the joined "text" has page separators). |
| `output` | string | no | Also write the text to this .txt file (or folder ending in "/"). |
| `maxChars` | integer | no (default `200000`) | Cut the inline text after this many characters (the file is never cut). |

**Output**

| Field | Type | Description |
|---|---|---|
| `kind` | "pdf" \| "image" |  |
| `languages` | string |  |
| `pageCount` | number | Pages/frames in the input. |
| `pages` | object[] |  |
| `text` | string |  |
| `meanConfidence` | number | Mean of the page confidences (0-100), pages with no text excluded. |
| `truncated` | boolean |  |
| `path` | string |  |
| `bytes` | number |  |

**Example**

```json
{
  "file": "scan.png",
  "languages": "eng"
}
```

**Example**

```json
{
  "file": "scanned-contract.pdf",
  "pages": "1-3",
  "output": "contract.txt"
}
```

<a id="ai_pdf_to_markdown"></a>
### `ai_pdf_to_markdown` — PDF to Markdown

Convert a text PDF to structured Markdown: headings guessed from font size (text clearly larger than the body becomes #, ##, ###), paragraphs rebuilt from line spacing with hyphenated words mended, bullet and numbered lists kept, right-to-left lines in reading order. Optional page range ("1-5, 8") and page markers. Scanned PDFs without a text layer give empty pages (listed in pagesWithoutText; run OCR first). Tables and columns are read line by line. Accepts a local path or base64. Inline Markdown is cut at maxChars (default 200k); "output" saves it to a file.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The PDF: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `pages` | string | no | Pages to convert, e.g. "1-3, 7" or "5-" (1-based). Default: all. |
| `password` | string | no | Password, if the PDF is protected. |
| `pageMarkers` | boolean | no (default `false`) | Put "<!-- Page N -->" before each page and "---" between pages. Default false. |
| `maxChars` | integer | no (default `200000`) | Cut the inline Markdown after this many characters, with a note (a file written with "output" is never cut). Default 200000. |
| `output` | string | no | Write the Markdown to this file (or folder ending in "/") and return its path instead of the inline text. |

**Output**

| Field | Type | Description |
|---|---|---|
| `pageCount` | number | Pages in the PDF. |
| `pagesConverted` | number |  |
| `pagesWithoutText` | number[] | 1-based pages with no text layer (probably scanned). |
| `headings` | number | Headings detected. |
| `bodyFontSize` | number | Font size (pt) taken as body text. |
| `markdown` | string | The Markdown (absent when written to "output"). |
| `truncated` | boolean | True when the inline Markdown was cut at maxChars. |
| `characters` | number | Length of the full Markdown in characters. |
| `words` | number | Words in the text (link URLs not counted). |
| `path` | string | Written file, when "output" was given. |
| `bytes` | number |  |

**Example**

```json
{
  "file": "paper.pdf"
}
```

**Example**

```json
{
  "file": "book.pdf",
  "pages": "1-20",
  "pageMarkers": true,
  "output": "book.md"
}
```

<a id="ai_pptx_to_markdown"></a>
### `ai_pptx_to_markdown` — PowerPoint (.pptx) to Markdown

Extract the text of a PowerPoint .pptx deck as Markdown: one "## Slide N: title" section per slide in presentation order, text boxes as paragraphs and bullet lists (with indent levels), tables as Markdown tables, and speaker notes as a quote under each slide. Images, charts and SmartArt text are not extracted. Old binary .ppt is not supported. Optional "slides" range (e.g. "1-5, 8"). Accepts a local path or base64. Inline Markdown is cut at maxChars (default 200k); "output" saves it to a file. Runs locally; nothing is uploaded.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The .pptx presentation: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `slides` | string | no | Slides to include, e.g. "1-3, 7" or "5-" (1-based). Default: all. |
| `notes` | boolean | no (default `true`) | Include speaker notes. Default true. |
| `maxChars` | integer | no (default `200000`) | Cut the inline Markdown after this many characters, with a note (a file written with "output" is never cut). Default 200000. |
| `output` | string | no | Write the Markdown to this file (or folder ending in "/") and return its path instead of the inline text. |

**Output**

| Field | Type | Description |
|---|---|---|
| `slideCount` | number | Slides in the deck. |
| `slides` | object[] |  |
| `markdown` | string | The Markdown (absent when written to "output"). |
| `truncated` | boolean | True when the inline Markdown was cut at maxChars. |
| `characters` | number | Length of the full Markdown in characters. |
| `words` | number | Words in the text (link URLs not counted). |
| `path` | string | Written file, when "output" was given. |
| `bytes` | number |  |

**Example**

```json
{
  "file": "pitch.pptx"
}
```

**Example**

```json
{
  "file": "lecture.pptx",
  "slides": "1-10",
  "notes": false
}
```

<a id="ai_sql_query"></a>
### `ai_sql_query` — SQL query over tables

Run one read-only SQL SELECT (alasql dialect: JOIN, GROUP BY, HAVING, ORDER BY, LIMIT, UNION, WITH, subqueries, CASE, SUM/COUNT/AVG/MIN/MAX/MEDIAN/STDEV, string and date functions) over one or more tables. Each table is { name, file | csv | json }: a local path or base64 of a CSV, TSV, JSON array, JSONL or .xlsx (first sheet, or "sheet"), or inline CSV text / JSON rows. CSV cells are typed (numbers, true/false, empty → null; ISO dates stay ISO text, so they sort and compare correctly; YEAR()/MONTH() work on them). Only SELECT is allowed: INSERT/UPDATE/DELETE/DROP/CREATE/ALTER/ATTACH, SELECT INTO, file readers like FROM CSV(...), inline JavaScript and the -> operator are refused. Table names are case-sensitive; quote odd column names and reserved words (total, value, count, date…) as [My Column]. Returns columns and up to maxRows rows (default 500) with the total count; set "output" (.csv, .tsv, .json, .jsonl or .xlsx) to save every row to a file. Runs locally; nothing is uploaded.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `query` | string | yes | One SELECT statement, e.g. SELECT region, SUM(amount) AS revenue FROM sales GROUP BY region ORDER BY revenue DESC |
| `tables` | object[] | yes | The tables the query can read (1-20). |
| `types` | boolean | no (default `true`) | Type CSV cells (numbers, booleans, null). Default true; false keeps every CSV cell as text. |
| `maxRows` | integer | no (default `500`) | Rows to return inline (default 500). "total" always counts all rows. |
| `output` | string | no | Optional: save ALL result rows to this file; the type comes from the extension (.csv, .tsv, .json, .jsonl, .xlsx; default .csv). |

**Output**

| Field | Type | Description |
|---|---|---|
| `columns` | string[] |  |
| `rows` | object[] |  |
| `total` | number | Number of rows the query produced. |
| `truncated` | boolean | True when only the first maxRows rows are returned inline. |
| `tables` | object[] |  |
| `path` | string |  |
| `bytes` | number |  |

**Example**

```json
{
  "query": "SELECT region, SUM(amount) AS revenue FROM sales GROUP BY region ORDER BY revenue DESC",
  "tables": [
    {
      "name": "sales",
      "csv": "region,amount\nNorth,10\nSouth,5\nNorth,7.5"
    }
  ]
}
```

Result:

```json
{
  "columns": [
    "region",
    "revenue"
  ],
  "rows": [
    {
      "region": "North",
      "revenue": 17.5
    },
    {
      "region": "South",
      "revenue": 5
    }
  ],
  "total": 2,
  "truncated": false
}
```

<a id="ai_text_similarity"></a>
### `ai_text_similarity` — Text similarity and near-duplicates

Compare two or more texts (or text files) for overlap. For every pair returns: Jaccard similarity of word shingles (k=3 words by default), a MinHash estimate of that Jaccard (128 hashes), and cosine similarity of word frequencies. Words are lower-cased, punctuation and Arabic diacritics ignored. Measures wording overlap, not meaning (no embeddings): paraphrases score low. Mode "pairs" (default) lists pairs sorted by the chosen metric; mode "dedupe" groups near-duplicates whose metric is at least "threshold" (default 0.8; groups are connected, so A~B and B~C put A, B, C together). Give "texts" (inline strings) and/or "paths" (file paths or base64). Up to 1000 texts.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `texts` | string[] | no | Texts to compare (inline). |
| `paths` | string[] | no | Or/also: UTF-8 text files (local paths or base64). |
| `labels` | string[] | no | Optional names for the texts, in order (texts first, then paths). Default: "#1", "#2"… or the file name. |
| `mode` | "pairs" \| "dedupe" | no (default `"pairs"`) | "pairs" (similarity of each pair) or "dedupe" (groups of near-duplicates). |
| `metric` | "jaccard" \| "minhash" \| "cosine" | no (default `"jaccard"`) | Metric used to sort pairs and to group duplicates. Default jaccard. |
| `threshold` | number | no (default `0.8`) | Dedupe: pairs at or above this are duplicates. Default 0.8. |
| `shingleSize` | integer | no (default `3`) | Words per shingle (k). Default 3. |
| `numHashes` | integer | no (default `128`) | MinHash signature length. Default 128 (error about ±0.09). |
| `maxPairs` | integer | no (default `200`) | Most pairs to return (highest first). Default 200. |

**Output**

| Field | Type | Description |
|---|---|---|
| `count` | number | Texts compared. |
| `texts` | object[] |  |
| `pairs` | object[] |  |
| `pairCount` | number | All pairs compared. |
| `truncated` | boolean |  |
| `groups` | object[] | Dedupe: groups of 2+ near-duplicates; "keep" is the first member. |
| `uniqueCount` | number | Dedupe: texts left after keeping one per group. |
| `duplicateCount` | number |  |

**Example** — Pair #1/#2 scores 1 on every metric.

```json
{
  "texts": [
    "the cat sat on the mat",
    "the cat sat on the mat",
    "a completely different sentence here"
  ]
}
```

Result:

```json
{
  "count": 3,
  "texts": [
    {
      "index": 0,
      "label": "#1",
      "words": 6,
      "shingles": 4
    },
    {
      "index": 1,
      "label": "#2",
      "words": 6,
      "shingles": 4
    },
    {
      "index": 2,
      "label": "#3",
      "words": 5,
      "shingles": 3
    }
  ],
  "pairCount": 3,
  "pairs": [
    {
      "a": 0,
      "b": 1,
      "jaccard": 1,
      "minhash": 1,
      "cosine": 1,
      "labelA": "#1",
      "labelB": "#2"
    },
    {
      "a": 0,
      "b": 2,
      "jaccard": 0,
      "minhash": 0,
      "cosine": 0,
      "labelA": "#1",
      "labelB": "#3"
    },
    {
      "a": 1,
      "b": 2,
      "jaccard": 0,
      "minhash": 0,
      "cosine": 0,
      "labelA": "#2",
      "labelB": "#3"
    }
  ]
}
```

**Example** — One group: #1 and #2.

```json
{
  "texts": [
    "Hello world, this is a test.",
    "hello world this is a test",
    "Something else entirely."
  ],
  "mode": "dedupe"
}
```

Result:

```json
{
  "count": 3,
  "texts": [
    {
      "index": 0,
      "label": "#1",
      "words": 6,
      "shingles": 4
    },
    {
      "index": 1,
      "label": "#2",
      "words": 6,
      "shingles": 4
    },
    {
      "index": 2,
      "label": "#3",
      "words": 3,
      "shingles": 1
    }
  ],
  "pairCount": 3,
  "groups": [
    {
      "members": [
        0,
        1
      ],
      "labels": [
        "#1",
        "#2"
      ],
      "keep": 0,
      "minSimilarity": 1
    }
  ],
  "duplicateCount": 1,
  "uniqueCount": 2,
  "pairs": [
    {
      "a": 0,
      "b": 1,
      "jaccard": 1,
      "minhash": 1,
      "cosine": 1,
      "labelA": "#1",
      "labelB": "#2"
    }
  ]
}
```

<a id="ai_token_count"></a>
### `ai_token_count` — Count tokens

Count LLM tokens in a text or text file with the OpenAI tiktoken tokenizers ("o200k_base", default, used by GPT-4o and the o-series; or "cl100k_base", used by GPT-4/3.5 and text-embedding-3). Also returns characters and words, and whether the text fits in common context windows (8k, 32k, 128k, 200k, 1M: e.g. "fits in 200k: yes"). Claude's tokenizer is not public, so for Claude the count is an estimate (typically within ~10-20%; Claude usually needs a little more), returned as a rough range. Runs locally, no API calls. Give "text" or "path" (file path or base64). Large texts (many MB) take a few seconds.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | string | no | The text (inline). |
| `path` | string | no | Or: a UTF-8 text/markdown file, as a local path or base64 (raw or a data: URI). |
| `encoding` | "o200k_base" \| "cl100k_base" | no (default `"o200k_base"`) | Tokenizer: "o200k_base" (GPT-4o / o-series, default) or "cl100k_base" (GPT-4 / GPT-3.5 / text-embedding-3). |

**Output**

| Field | Type | Description |
|---|---|---|
| `tokens` | number | Exact token count for the chosen tiktoken encoding. |
| `encoding` | string |  |
| `characters` | number | Characters (grapheme clusters, so an emoji counts once). |
| `words` | number |  |
| `charsPerToken` | number |  |
| `claudeEstimate` | object |  |
| `contextFit` | object[] |  |
| `summary` | string |  |

**Example**

```json
{
  "text": "hello world"
}
```

Result:

```json
{
  "tokens": 2,
  "words": 2,
  "characters": 11
}
```

**Example**

```json
{
  "path": "notes.md",
  "encoding": "cl100k_base"
}
```

<a id="ai_web_fetch"></a>
### `ai_web_fetch` — Fetch a web page as Markdown

Fetch a web page (http/https) and return its readable content as clean Markdown, with title, byline, final URL after redirects and a word count. Uses Mozilla Readability to keep the main article (falls back to the whole page), or a CSS "selector" you give. Respects robots.txt (refuses pages disallowed for the s3eidat-tools-mcp user agent), only reads HTML and text responses, stops at 5 MB and 20 seconds, follows up to 5 redirects, and refuses private, local and loopback addresses unless allowPrivate is true. Does not run JavaScript, so pages that build their content in the browser may come back nearly empty. Inline Markdown is cut at maxChars (default 200k); "output" saves it to a file.

Hints: readOnly, idempotent, openWorld

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `url` | string | yes | The page to fetch (http:// or https://). |
| `selector` | string | no | CSS selector: convert only the matching elements instead of the Readability main content. |
| `readable` | boolean | no (default `true`) | Keep only the main content (default true). False converts the whole page body. |
| `includeLinks` | boolean | no (default `true`) | Keep links (made absolute) and images. False turns links into plain text and drops images. |
| `allowPrivate` | boolean | no (default `false`) | Allow private, local and loopback hosts (e.g. a dev server on localhost). Default false. |
| `maxChars` | integer | no (default `200000`) | Cut the inline Markdown after this many characters, with a note (a file written with "output" is never cut). Default 200000. |
| `output` | string | no | Write the Markdown to this file (or folder ending in "/") and return its path instead of the inline text. |

**Output**

| Field | Type | Description |
|---|---|---|
| `url` | string | The URL asked for. |
| `finalUrl` | string | The URL after redirects. |
| `status` | number |  |
| `contentType` | string |  |
| `title` | string |  |
| `byline` | string |  |
| `excerpt` | string |  |
| `siteName` | string |  |
| `lang` | string |  |
| `publishedTime` | string |  |
| `source` | "selector" \| "readability" \| "body" \| "text" | What was converted. |
| `bodyTruncated` | boolean | True when the page was over 5 MB and only the first 5 MB were read. |
| `markdown` | string | The Markdown (absent when written to "output"). |
| `truncated` | boolean | True when the inline Markdown was cut at maxChars. |
| `characters` | number | Length of the full Markdown in characters. |
| `words` | number | Words in the text (link URLs not counted). |
| `path` | string | Written file, when "output" was given. |
| `bytes` | number |  |

**Example**

```json
{
  "url": "https://en.wikipedia.org/wiki/Markdown"
}
```

**Example**

```json
{
  "url": "https://example.com/docs/page",
  "selector": "main",
  "includeLinks": false
}
```

<a id="ai_xlsx_to_markdown"></a>
### `ai_xlsx_to_markdown` — Excel (.xlsx/.csv) to Markdown tables

Turn an Excel .xlsx workbook (or a .csv/.tsv file) into Markdown tables, one "## sheet" section per sheet, or only the sheet you name. The first used row is the table header; empty rows and columns around the data are dropped. Formulas show their cached result (as last saved by Excel), dates are ISO (2026-10-30, or 2026-10-30T14:05:00), merged cells show their value once, hyperlinks become Markdown links. Each sheet shows at most maxRows data rows (default 200) and says how many were left out. Old binary .xls is not supported. Accepts a local path or base64. Runs locally.

Hints: readOnly, idempotent

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | string | yes | The .xlsx or .csv file: a local file path (absolute, or relative to the server's --root) or base64 data (raw or a data: URI). |
| `sheet` | string | no | Only this sheet: its name, or its 1-based position (e.g. "2"). Default: every sheet. |
| `maxRows` | integer | no (default `200`) | Data rows to show per sheet (after the header). Default 200. |
| `format` | "auto" \| "xlsx" \| "csv" | no (default `"auto"`) | Input format. "auto" tells .xlsx (a ZIP) from CSV by its bytes. |
| `maxChars` | integer | no (default `200000`) | Cut the inline Markdown after this many characters, with a note (a file written with "output" is never cut). Default 200000. |
| `output` | string | no | Write the Markdown to this file (or folder ending in "/") and return its path instead of the inline text. |

**Output**

| Field | Type | Description |
|---|---|---|
| `sheets` | object[] |  |
| `sheetNames` | string[] | Every sheet in the workbook, in order. |
| `markdown` | string | The Markdown (absent when written to "output"). |
| `truncated` | boolean | True when the inline Markdown was cut at maxChars. |
| `characters` | number | Length of the full Markdown in characters. |
| `words` | number | Words in the text (link URLs not counted). |
| `path` | string | Written file, when "output" was given. |
| `bytes` | number |  |

**Example**

```json
{
  "file": "budget.xlsx"
}
```

**Example**

```json
{
  "file": "sales.xlsx",
  "sheet": "Q3",
  "maxRows": 50
}
```

**Example**

```json
{
  "file": "contacts.csv"
}
```

<a id="ai_zip"></a>
### `ai_zip` — ZIP create / extract / list

Work with ZIP archives locally. action "create": zip files and folders (given as paths; folders keep their own name as the top folder, symlinks are skipped) into one .zip. action "extract": unpack a .zip (path or base64) into the "output" folder; entries with "..", absolute paths or drive letters are refused (zip-slip protection), existing files are kept unless overwrite is true. action "list": entry names with sizes, no extraction. Limits: total uncompressed size 500 MB and 10000 files by default (maxTotalMB / maxFiles). Encrypted ZIPs are not supported.

**Inputs**

| Name | Type | Required | Description |
|---|---|---|---|
| `action` | "create" \| "extract" \| "list" | yes |  |
| `files` | string[] | no | create: files and folders to add (local paths). |
| `zip` | string | no | extract / list: the ZIP archive, a local path or base64. |
| `output` | string | no | create: the .zip path (or a folder ending in "/"). extract: the folder to extract into. Default: the server output folder. |
| `level` | integer | no (default `6`) | create: compression level 0 (store) to 9 (smallest). Default 6. |
| `overwrite` | boolean | no (default `false`) | extract: replace files that already exist in the output folder. Default false (they are skipped). |
| `maxTotalMB` | number | no (default `500`) | Cap on the total uncompressed size, MB. Default 500. |
| `maxFiles` | integer | no (default `10000`) | Cap on the number of entries. Default 10000. |

**Output**

| Field | Type | Description |
|---|---|---|
| `action` | string |  |
| `path` | string | create: the written .zip. extract: the output folder. |
| `bytes` | number | create: size of the .zip. |
| `files` | number | Number of file entries (created, extracted, or listed). |
| `totalSize` | number | Total uncompressed size of the files, bytes. |
| `compressedSize` | number |  |
| `entries` | object[] |  |
| `truncated` | boolean | list: true when only the first 5000 entries are shown. |
| `skipped` | object[] |  |

**Example**

```json
{
  "action": "create",
  "files": [
    "/projects/site",
    "/notes/readme.txt"
  ],
  "output": "/backups/site.zip"
}
```

**Example**

```json
{
  "action": "list",
  "zip": "/downloads/archive.zip"
}
```

**Example**

```json
{
  "action": "extract",
  "zip": "/downloads/archive.zip",
  "output": "/downloads/archive/"
}
```

