{"_id":"@ariaskit/optipix","name":"@ariaskit/optipix","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@ariaskit/optipix","version":"1.0.0","description":"A fast, colorful CLI that scans a folder and optimizes every image with sharp — pick the output format, dimensions and quality.","type":"module","main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.mts","bin":{"optipix":"dist/cli.mjs","opx":"dist/cli.mjs"},"exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"sideEffects":false,"engines":{"node":">=20.0.0"},"scripts":{"build":"tsdown","dev":"tsdown --watch","typecheck":"tsc --noEmit","lint":"eslint .","lint:fix":"eslint . --fix","link":"pnpm link --global","unlink":"pnpm unlink --global","publish:check":"pnpm pack --dry-run","sample":"pnpm --filter samples start","prepublishOnly":"pnpm run build"},"keywords":["cli","sharp","image","images","optimize","optimizer","compress","resize","convert","webp","avif","jpeg","png"],"author":{"name":"JorgeRosbel"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/JorgeRosbel/optipix.git"},"bugs":{"url":"https://github.com/JorgeRosbel/optipix/issues"},"homepage":"https://github.com/JorgeRosbel/optipix#readme","packageManager":"pnpm@10.33.2","dependencies":{"@clack/prompts":"1.7.0","commander":"15.0.0","picocolors":"1.1.1","sharp":"0.35.3"},"devDependencies":{"@eslint/js":"10.0.1","@types/node":"25.9.1","eslint":"10.4.0","tsdown":"0.22.0","typescript":"5.9.3","typescript-eslint":"8.60.0","unrun":"0.3.0"},"gitHead":"b9de753f69e4c866c1603a656dd56f7ff74529dc","_id":"@ariaskit/optipix@1.0.0","_nodeVersion":"22.22.1","_npmVersion":"11.13.0","dist":{"integrity":"sha512-T8UzjDtBO45IzE5q8wRxU6DGC24DHDGeWFaH85BIDc6lSgFWUGDi6PoyYNVyvxpV22vMs1tpxFEhN5eUYpQDFg==","shasum":"f6e91efc930d2efad2e542320f247cdf06f5dd2a","tarball":"https://registry.npmjs.org/@ariaskit/optipix/-/optipix-1.0.0.tgz","fileCount":17,"unpackedSize":288110,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIB3eM+ZbdcPGyFBZF+lUk5kHf1gfm8c8894OaqLcpHFRAiAj1Dx6njo6KkMiWBs6dCu8cDCOeLffniyInqYMGzPUhw=="}]},"_npmUser":{"name":"jorgerosbel","email":"jorgerosbelcruzarias@gmail.com"},"directories":{},"maintainers":[{"name":"jorgerosbel","email":"jorgerosbelcruzarias@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/optipix_1.0.0_1785328033074_0.7656768500734736"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-29T12:27:12.884Z","1.0.0":"2026-07-29T12:27:13.229Z","modified":"2026-07-29T12:27:13.477Z"},"maintainers":[{"name":"jorgerosbel","email":"jorgerosbelcruzarias@gmail.com"}],"description":"A fast, colorful CLI that scans a folder and optimizes every image with sharp — pick the output format, dimensions and quality.","homepage":"https://github.com/JorgeRosbel/optipix#readme","keywords":["cli","sharp","image","images","optimize","optimizer","compress","resize","convert","webp","avif","jpeg","png"],"repository":{"type":"git","url":"git+https://github.com/JorgeRosbel/optipix.git"},"author":{"name":"JorgeRosbel"},"bugs":{"url":"https://github.com/JorgeRosbel/optipix/issues"},"license":"MIT","readme":"<div align=\"center\">\n\n# ✦ optipix\n\n**A fast, colorful CLI that optimizes every image in a folder — powered by [sharp](https://sharp.pixelplumbing.com).**\n\nPick the output format, the dimensions and the quality. optipix scans the\ndirectory, converts everything in parallel and drops the results into an\n`output/` folder, with a full before/after report in your terminal.\n\n[![npm](https://img.shields.io/npm/v/%40ariaskit%2Foptipix?color=cyan)](https://www.npmjs.com/package/@ariaskit/optipix)\n[![license](https://img.shields.io/npm/l/%40ariaskit%2Foptipix?color=green)](./LICENSE)\n\n</div>\n\n---\n\n## Table of contents\n\n- [Why optipix](#why-optipix)\n- [Install](#install)\n- [Quick start](#quick-start)\n- [Interactive mode](#interactive-mode)\n- [Command reference](#command-reference)\n- [Output formats](#output-formats)\n- [Resizing and fit modes](#resizing-and-fit-modes)\n- [Reading the report](#reading-the-report)\n- [How files are written](#how-files-are-written)\n- [Recipes](#recipes)\n- [Programmatic API](#programmatic-api)\n- [Behaviour and guarantees](#behaviour-and-guarantees)\n- [Requirements](#requirements)\n- [Development](#development)\n- [Releasing](#releasing)\n- [License](#license)\n\n---\n\n## Why optipix\n\n- **Zero configuration.** `cd` into a folder, run `optipix`, answer three\n  questions. No config file, no glob patterns to remember.\n- **Non-destructive.** Your originals are never touched. Everything is written\n  to a separate `output/` directory.\n- **You can see what happened.** Every image gets a row with its size before and\n  after, the percentage saved, the dimensions and the format conversion.\n- **Fast.** Images are encoded in parallel with a worker pool, and sharp does\n  the heavy lifting in native code.\n- **Scriptable.** Every prompt has a flag, so the same tool works in CI.\n\n---\n\n## Install\n\nRun it without installing anything:\n\n```bash\nnpx @ariaskit/optipix\n```\n\nInstall it globally, so `optipix` is available everywhere:\n\n```bash\nnpm  install -g @ariaskit/optipix\npnpm add     -g @ariaskit/optipix\n```\n\nOr add it to a project:\n\n```bash\npnpm add -D @ariaskit/optipix\n```\n\nThe package installs two identical binaries: **`optipix`** and the shorter\n**`opx`**.\n\n---\n\n## Quick start\n\n```bash\ncd ./assets/images\noptipix\n```\n\nThat starts the guided setup. To skip every question and go straight to work:\n\n```bash\noptipix -y -f webp -q 80 -w 1600\n```\n\nWhat you get:\n\n```text\n╭────────────────────────────────────────────────────────╮\n│ ✦ optipix  ·  image optimizer powered by sharp  v1.0.0 │\n╰────────────────────────────────────────────────────────╯\n\n╭─ Run configuration ──────────────────╮\n│ Input         /home/me/assets/images │\n│ Output        /home/me/assets/images/output │\n│ Images found  5                      │\n│ Format        webp                   │\n│ Dimensions    1600 × auto            │\n│ Quality       80                     │\n│ Fit           inside                 │\n│ Recursive     yes                    │\n│ Metadata      stripped               │\n│ Concurrency   4                      │\n╰──────────────────────────────────────╯\n\nOptimizing\n\n  ✔ icon-set.png  4.40 KB → 554 B  -87.7%  [png → webp]\n  ✔ avatar.jpg  3.92 KB → 1.19 KB  -69.6%  [jpeg → webp]\n  ✔ hero-banner.png  30.6 KB → 1.67 KB  -94.5%  [png → webp]\n\n╭─────────────────────────┬─────────┬─────────┬────────┬──────────────────────┬─────────────┬──────╮\n│ FILE                    │  BEFORE │   AFTER │  SAVED │ DIMENSIONS           │ FORMAT      │ TIME │\n├─────────────────────────┼─────────┼─────────┼────────┼──────────────────────┼─────────────┼──────┤\n│ ✔ avatar.jpg            │ 3.92 KB │ 1.19 KB │ -69.6% │ 800×800              │ jpeg → webp │ 31ms │\n│ ✔ hero-banner.png       │ 30.6 KB │ 1.67 KB │ -94.5% │ 1920×1080 → 1600×900 │ png → webp  │ 49ms │\n│ ✔ icon-set.png          │ 4.40 KB │   554 B │ -87.7% │ 512×512              │ png → webp  │ 20ms │\n╰─────────────────────────┴─────────┴─────────┴────────┴──────────────────────┴─────────────┴──────╯\n\n╭─ Done ─────────────────────────────────────────╮\n│ Optimized     3                                │\n│ Total before  38.9 KB                          │\n│ Total after   3.39 KB                          │\n│ Saved         35.5 KB (-91.3%)                 │\n│ Elapsed       76ms                             │\n│ Output dir    ./output                         │\n│                                                │\n│ ████████████████████████████  kept █ / saved █ │\n╰────────────────────────────────────────────────╯\n```\n\n---\n\n## Interactive mode\n\nRunning `optipix` with no flags (in a real terminal) starts a guided setup:\n\n1. **Output format** — every option comes with a hint explaining when to use it.\n2. **Dimensions** — keep the original size, limit the width, or set an explicit\n   width × height box.\n3. **Fit mode** — only asked when you gave both a width and a height.\n4. **Quality** — four presets from *near lossless* to *aggressive*.\n5. **Recursive** — whether sub-directories should be scanned too.\n\nThen optipix prints the run configuration, tells you how many images it found\nand asks for a final confirmation before writing anything.\n\n> Flags and prompts mix freely: `optipix -f avif` asks about everything **except**\n> the format. Pass `-y` (or run in a non-TTY, like CI) to skip all prompts and\n> fall back to the defaults.\n\nPress <kbd>Ctrl</kbd>+<kbd>C</kbd> at any prompt to abort — nothing is written.\n\n---\n\n## Command reference\n\n```text\noptipix [options]\nopx     [options]\n```\n\n| Flag | Type | Default | What it does |\n|------|------|---------|--------------|\n| `-f, --format <format>` | `original` \\| `webp` \\| `avif` \\| `jpeg` \\| `png` \\| `tiff` \\| `gif` | `webp` | Format every image is encoded to. `original` keeps each file's own format. |\n| `-w, --width <px>` | integer > 0 | source width | Target width. Used alone, the height follows the aspect ratio. |\n| `-H, --height <px>` | integer > 0 | source height | Target height. Used alone, the width follows the aspect ratio. |\n| `-q, --quality <1-100>` | integer 1–100 | `80` | Encoder quality. Lower means smaller files and more artifacts. |\n| `--fit <mode>` | `cover` \\| `contain` \\| `fill` \\| `inside` \\| `outside` | `inside` | How the image is fitted into the width × height box. |\n| `-i, --input <dir>` | path | current directory | Directory that gets scanned. |\n| `-o, --output <dir>` | path | `./output` | Where optimized images are written. Relative paths resolve against the input directory. |\n| `-r, --recursive` | flag | off | Also scan sub-directories, mirroring the folder tree in the output. |\n| `--enlarge` | flag | off | Allow upscaling past the source dimensions. By default images are never enlarged. |\n| `-m, --keep-metadata` | flag | off | Keep EXIF/ICC metadata. By default it is stripped, which saves bytes and removes GPS data. |\n| `--no-overwrite` | flag | overwrite on | Skip images that already exist in the output directory instead of replacing them. |\n| `-c, --concurrency <n>` | integer 1–32 | `4` | How many images are encoded in parallel. |\n| `-d, --dry-run` | flag | off | Encode in memory and print the full report **without writing any file**. |\n| `-y, --yes` | flag | off | Skip every prompt and use flags plus defaults. |\n| `--no-color` | flag | colors on | Disable colored output. `NO_COLOR=1` works too. |\n| `-v, --version` | flag | — | Print the version and exit. |\n| `-h, --help` | flag | — | Print the colored help screen and exit. |\n\n**Exit codes:** `0` success (or nothing to do), `1` at least one image failed or\nthe arguments were invalid, `130` cancelled with <kbd>Ctrl</kbd>+<kbd>C</kbd>.\n\n> **Note on `-H`:** `-h` is reserved for help, so the short flag for height is an\n> uppercase `-H`.\n\n---\n\n## Output formats\n\n| Format | Extension | Lossy | Transparency | Animation | Best for |\n|--------|-----------|:-----:|:------------:|:---------:|----------|\n| `webp` | `.webp` | yes | yes | yes | The default. Great ratio, supported by every modern browser. |\n| `avif` | `.avif` | yes | yes | no | Smallest files by a wide margin — but slower to encode. |\n| `jpeg` | `.jpg` | yes | no | no | Photographs, maximum compatibility. Encoded with mozjpeg + progressive. |\n| `png` | `.png` | no\\* | yes | no | Flat graphics, logos, screenshots. |\n| `tiff` | `.tiff` | yes | yes | no | Print and archival workflows. |\n| `gif` | `.gif` | no | yes | yes | Short animations. **Ignores `--quality`.** |\n| `original` | source ext. | — | — | — | Keep each file in its own format and only re-encode/resize it. |\n\n\\* PNG is lossless, but any `--quality` below 100 lets sharp quantise the palette,\nwhich shrinks the file a lot at a small visual cost.\n\nAccepted **input** extensions: `.jpg` `.jpeg` `.png` `.webp` `.avif` `.tif`\n`.tiff` `.gif` `.svg`.\n\nSVG files are rasterized at 300 DPI. With `--format original` they become PNG,\nbecause sharp can read vectors but not write them.\n\n---\n\n## Resizing and fit modes\n\nResizing only kicks in when you pass `--width`, `--height`, or both.\n\n- **Only `--width`** (or only `--height`): the other side is computed from the\n  aspect ratio. This is the common case for \"make everything at most 1600px wide\".\n- **Both**: the image is fitted into that box according to `--fit`.\n\n| Fit | What happens |\n|-----|--------------|\n| `inside` *(default)* | Scales down until the image fits inside the box. Aspect ratio kept, nothing cropped, the result may be smaller than the box. |\n| `cover` | Fills the whole box and crops whatever overflows. This is what you want for square thumbnails. |\n| `contain` | Fits inside the box and pads the remaining space. |\n| `fill` | Stretches to exactly the box, ignoring the aspect ratio. |\n| `outside` | Scales until the box is covered on both axes; one side may exceed it. |\n\nImages are **never upscaled** unless you pass `--enlarge`, so a 300px source with\n`--width 1600` stays at 300px instead of turning into a blurry mess.\n\n---\n\n## Reading the report\n\nEach row of the results table describes one image:\n\n| Column | Meaning |\n|--------|---------|\n| `FILE` | Path relative to the input directory, prefixed with a status icon: `✔` optimized, `◦` skipped, `✖` failed. |\n| `BEFORE` | Size of the source file on disk. |\n| `AFTER` | Size of the file that was written. |\n| `SAVED` | Change in size. Green means smaller, red means the output grew, gray means it barely moved. |\n| `DIMENSIONS` | The source size, plus `→ new size` when the image was resized. |\n| `FORMAT` | The source format, plus `→ target` when it was converted. Each format has its own color. |\n| `TIME` | Wall-clock time spent on that image. |\n\nThe closing panel totals it all up: how many images were optimized, skipped and\nfailed, the combined size before and after, the bytes saved, the elapsed time\nand where the results landed. The bar at the bottom shows the proportion that\nwas **kept** against the proportion that was **saved**.\n\nFailed images get their own section underneath with the exact error sharp\nreported, so a single corrupt file never hides the rest of the run.\n\n---\n\n## How files are written\n\nGiven this input directory:\n\n```text\nassets/\n├─ hero-banner.png\n├─ avatar.jpg\n└─ icons/\n   └─ logo.svg\n```\n\n`optipix -y -f webp -r` produces:\n\n```text\nassets/\n├─ hero-banner.png          ← untouched\n├─ avatar.jpg               ← untouched\n├─ icons/\n│  └─ logo.svg              ← untouched\n└─ output/\n   ├─ hero-banner.webp\n   ├─ avatar.webp\n   └─ icons/\n      └─ logo.webp\n```\n\n- The folder structure is mirrored when `--recursive` is on.\n- File names keep their base name and get the extension of the target format.\n- If two sources would collide (`photo.png` and `photo.jpg` both becoming\n  `photo.webp`), the second one is written as `photo-1.webp` instead of being\n  silently overwritten.\n\n---\n\n## Recipes\n\n**Convert a whole asset folder to WebP for the web**\n\n```bash\noptipix -y -f webp -q 80 -w 1920 -r\n```\n\n**Generate square avatars**\n\n```bash\noptipix -y -f webp -w 256 -H 256 --fit cover -o ./avatars\n```\n\n**Squeeze photos without changing their format**\n\n```bash\noptipix -y -f original -q 65\n```\n\n**Preview the savings before committing to a quality level**\n\n```bash\noptipix -y -f avif -q 50 --dry-run\n```\n\n**Optimize a different folder from where you are**\n\n```bash\noptipix -y -i ./public/images -o ./public/images/min -f webp\n```\n\n**Only process images you have not optimized yet**\n\n```bash\noptipix -y -f webp --no-overwrite\n```\n\n**Keep camera metadata (EXIF, ICC profile)**\n\n```bash\noptipix -y -f jpeg -q 90 --keep-metadata\n```\n\n**Run it in CI** (no TTY, so prompts are skipped automatically)\n\n```bash\nnpx @ariaskit/optipix -f webp -q 80 -r -i ./src/assets -o ./dist/assets\n```\n\n---\n\n## Programmatic API\n\nEverything the CLI does is available from code.\n\n```ts\nimport { optimizeDirectory } from \"@ariaskit/optipix\";\n\nconst summary = await optimizeDirectory(\n  {\n    inputDir: `${process.cwd()}/assets`,\n    outputDir: `${process.cwd()}/assets/output`,\n    format: \"webp\",\n    width: 1280,\n    quality: 80,\n    fit: \"inside\",\n    recursive: true,\n    enlarge: false,\n    keepMetadata: false,\n    overwrite: true,\n    concurrency: 4,\n    dryRun: false,\n  },\n  {\n    onFileDone: (result) => {\n      console.log(`${result.file} → ${result.outputFile}`);\n    },\n  },\n);\n\nconsole.log(`Saved ${summary.savedBytes} bytes across ${summary.done} images`);\n```\n\n### Exports\n\n| Export | Description |\n|--------|-------------|\n| `optimizeDirectory(options, hooks?)` | Scans a directory and optimizes everything it finds. Returns an `OptimizeSummary`. |\n| `scanImages(options)` | Just the scan: returns the `ImageTask[]` that would be processed. |\n| `optimizeTasks(tasks, options, hooks?)` | Optimizes an already-scanned list — useful when you want to confirm or filter first. |\n| `optimizeImage(task, options)` | Processes a single image and returns its `OptimizeResult`. |\n| `OUTPUT_FORMATS`, `SUPPORTED_INPUT_EXTENSIONS` | The format and extension lists the CLI uses. |\n| `isOutputFormat`, `isSupportedInput`, `normalizeFormat`, `resolveTargetFormat`, `extensionFor` | Format helpers. |\n| `VERSION` | The package version. |\n\nTypes `OptimizeOptions`, `OptimizeResult`, `OptimizeSummary`, `OptimizeHooks`,\n`ImageTask`, `OutputFormat` and `FitMode` are exported too.\n\n### Hooks\n\n```ts\ninterface OptimizeHooks {\n  onStart?: (tasks: ImageTask[]) => void;         // after the scan\n  onFileStart?: (task: ImageTask, i: number) => void;\n  onFileDone?: (result: OptimizeResult, i: number) => void;\n}\n```\n\nThe package ships **ESM and CJS** builds with per-format `.d.ts`, so `import` and\n`require` both work.\n\n---\n\n## Behaviour and guarantees\n\n- **Originals are never modified.** optipix only ever writes inside the output\n  directory.\n- **Re-runs are safe.** The output directory is excluded from the scan, so a\n  second run does not re-optimize its own results.\n- **Noisy folders are skipped.** `node_modules`, `dist`, `build`, `coverage`,\n  `.git` and other dot-directories are ignored while walking.\n- **One bad file does not stop the run.** Failures are collected, reported at\n  the end, and the process exits with code `1`.\n- **EXIF orientation is applied** before metadata is stripped, so photos never\n  come out rotated.\n- **Animated GIF and WebP keep their frames** when the target format supports\n  animation.\n- **`--dry-run` is honest.** Images are really encoded, in memory, so the sizes\n  it reports are the sizes you would get.\n\n---\n\n## Requirements\n\n- **Node.js 20** or newer\n- No system libraries to install — sharp ships prebuilt binaries for the common\n  platforms\n\n---\n\n## Development\n\n```bash\npnpm install      # install the workspace\npnpm build        # bundle src/ into dist/ (ESM + CJS + types)\npnpm dev          # tsdown in watch mode\npnpm typecheck    # tsc --noEmit\npnpm lint         # eslint\npnpm lint:fix     # eslint --fix\npnpm sample       # run samples/index.ts against the library\npnpm publish:check # dry-run of the published tarball\n```\n\nTry the built CLI locally without publishing:\n\n```bash\npnpm build\npnpm link --global\noptipix --help\n```\n\n### Repository layout\n\n```text\nsrc/\n├─ cli.ts            # the binary: argument parsing and the whole run flow\n├─ index.ts          # public programmatic API\n├─ prompts.ts        # guided mode (@clack/prompts)\n├─ types.ts          # shared types\n├─ core/\n│  ├─ formats.ts     # format tables, extensions, resolution rules\n│  ├─ scan.ts        # directory walking and task building\n│  └─ optimize.ts    # the sharp pipeline and the worker pool\n└─ ui/\n   ├─ theme.ts       # the single source of truth for colors and icons\n   ├─ format.ts      # bytes, durations, percentages, ANSI-aware widths\n   ├─ table.ts       # box-drawing tables and panels\n   ├─ progress.ts    # live progress bar with a pinned bottom line\n   ├─ banner.ts      # the wordmark\n   ├─ help.ts        # the colored --help screen\n   └─ report.ts      # config panel, results table, summary\n```\n\n### Supply-chain safety\n\nThe pnpm workspace sets `minimumReleaseAge: 10080` (7 days), so a dependency\nversion has to have been on the registry for a full week before it can be\ninstalled here. That is a cheap defense against freshly published, compromised\nor simply broken releases.\n\n---\n\n## Releasing\n\nCI runs lint, typecheck and build on every push and pull request.\n\nPublishing is automatic: pushing to `main` triggers the **Release** workflow,\nwhich verifies the package, reads the version from `package.json` and publishes\nit to npm with [provenance](https://docs.npmjs.com/generating-provenance-statements)\nvia OIDC trusted publishing. If that version already exists on npm, the publish\nstep is skipped, so releasing is just a version bump:\n\n```bash\npnpm version patch   # or minor / major\ngit push\n```\n\n---\n\n## License\n\n[MIT](./LICENSE) © JorgeRosbel\n","readmeFilename":"README.md","_rev":"1-985e38207a48fd0ae70ddefb6c6f7ffa"}