img-subir 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +147 -0
- package/dist/chunk-CWZBNFPK.js +1004 -0
- package/dist/chunk-CWZBNFPK.js.map +1 -0
- package/dist/cli.js +295 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +215 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
- package/skills/img-subir/SKILL.md +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zuriel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# img-subir
|
|
2
|
+
|
|
3
|
+
Agent-friendly CLI that makes images small enough for websites, READMEs and [Subir](https://www.npmjs.com/package/subir) pages. Point it at a folder of screenshots and it does the right thing: downsizes Retina captures, strips metadata, tries WebP / AVIF / PNG / JPEG, keeps the smallest one that still looks the same, and never writes a file that is bigger than the original.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx img-subir ./assets
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
img-subir v0.1.0 · preset: web
|
|
11
|
+
|
|
12
|
+
✔ dashboard@2x.png 2880×1800 PNG 3.40 MB → dashboard@2x.webp 1920×1200 q82 148 KB −95.7%
|
|
13
|
+
✔ settings.png 1440×900 PNG 1.10 MB → settings.webp 1440×900 q82 92 KB −91.8%
|
|
14
|
+
✔ logo.svg SVG 41 KB → logo.svg (svgo) 12 KB −70.7%
|
|
15
|
+
· already.webp 320×200 WEBP 200 B kept: no candidate smaller than original
|
|
16
|
+
|
|
17
|
+
Saved 4.54 MB → 252 KB (−94.6%) in 1.8s · 3 optimized, 1 kept
|
|
18
|
+
Originals kept in assets/.img-subir-originals — delete or ignore before upload/commit (or use --no-backup).
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Why
|
|
22
|
+
|
|
23
|
+
Screenshots straight out of macOS are 3–5 MB each. Put a few in a plan page, a README or a marketing site and the page is slow, the repo bloats, and storage fills up. `img-subir` is the one command an agent (or you) runs right before uploading or committing.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx img-subir <paths> # no install
|
|
29
|
+
npm i -g img-subir # or global
|
|
30
|
+
pnpm add -D img-subir # or per project
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Node ≥ 20. Uses [sharp](https://sharp.pixelplumbing.com/) (prebuilt libvips, no system deps) and [svgo](https://svgo.dev/).
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
img-subir <paths...> [options] files, directories (recursive) or globs
|
|
39
|
+
|
|
40
|
+
-p, --preset <name> web | github | screenshot | og | thumb | lossless (default: web)
|
|
41
|
+
-f, --format <fmt> auto | keep | webp | avif | png | jpeg (default: auto)
|
|
42
|
+
--kind <kind> auto | ui | photo | graphic (default: auto)
|
|
43
|
+
--max-width <px> cap width (overrides preset)
|
|
44
|
+
--max-height <px> cap height (overrides preset)
|
|
45
|
+
-q, --quality <1-100> starting quality for lossy encoders (overrides preset)
|
|
46
|
+
--target-kb <n> binary-search quality until each file is ≤ n KB
|
|
47
|
+
-o, --out <dir> write to dir (mirrors structure; kept files are copied too) instead of in place
|
|
48
|
+
--suffix <str> insert before the extension, e.g. ".min"
|
|
49
|
+
--no-backup don't keep originals in .img-subir-originals/
|
|
50
|
+
--no-rename keep the original filename even if the format changes
|
|
51
|
+
--rewrite-refs update src/href/url() in html/md/tsx/css that point at renamed files
|
|
52
|
+
-n, --dry-run report, write nothing
|
|
53
|
+
--json JSON report on stdout, human log on stderr
|
|
54
|
+
--fast skip SSIM checks (~3× faster)
|
|
55
|
+
-c, --concurrency <n> files in flight (default: cpus − 1)
|
|
56
|
+
--quiet / --verbose
|
|
57
|
+
|
|
58
|
+
img-subir inspect <paths...> metadata, detected kind, and what optimize would do
|
|
59
|
+
img-subir presets list presets
|
|
60
|
+
img-subir skill print the SKILL.md for agents
|
|
61
|
+
img-subir doctor verify sharp and every encoder on this machine
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Presets
|
|
65
|
+
|
|
66
|
+
| Preset | For | Max width | Tries | Notes |
|
|
67
|
+
| ------------ | ------------------------------------- | --------- | ----------------------- | ----------------------------------------------------------- |
|
|
68
|
+
| `web` | websites, Subir pages, docs | 1920 | webp, avif, png, jpeg | default. AVIF kept only when ≥15% smaller than WebP |
|
|
69
|
+
| `github` | README images, PR screenshots | 1600 | webp, png, jpeg | never AVIF |
|
|
70
|
+
| `screenshot` | crisp UI captures | 1600 | webp, webp-lossless, png | forces `kind=ui`, higher quality floor |
|
|
71
|
+
| `og` | Open Graph / social cards | 1200×630 | jpeg, png | many crawlers dislike WebP |
|
|
72
|
+
| `thumb` | thumbnails, avatars | 480 | webp, avif | aggressive; pair with `--target-kb` |
|
|
73
|
+
| `lossless` | pixel-exact | none | png, webp-lossless | no resize, no lossy |
|
|
74
|
+
|
|
75
|
+
Unknown flags, missing values and malformed config files are errors (exit 2), never silent fallbacks, so an agent typo like `--dryrun` cannot cause real writes.
|
|
76
|
+
|
|
77
|
+
Flags beat config beat preset. Config lives in `img-subir.config.json`, `.img-subirrc.json` or a `"img-subir"` key in `package.json`, found by walking up from the current directory:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{ "preset": "github", "maxWidth": 1400, "rewriteRefs": true }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## How it decides
|
|
84
|
+
|
|
85
|
+
1. **Probe** — format, size, alpha, animation. A PNG whose alpha channel is entirely opaque is treated as opaque (this unlocks JPEG and smaller WebP for most macOS screenshots).
|
|
86
|
+
2. **Classify** — `ui` / `photo` / `graphic` from cheap pixel statistics (flat runs, distinct colors) plus filename and device-size hints. Override with `--kind`.
|
|
87
|
+
3. **Resize** — fit inside the preset's max width/height, never enlarge, Lanczos3. EXIF orientation is applied, colors converted to sRGB, metadata dropped.
|
|
88
|
+
4. **Encode candidates** in parallel: lossy WebP, AVIF (skipped above 4 MP), palette PNG, mozjpeg JPEG, plus lossless PNG/WebP for graphics.
|
|
89
|
+
5. **Quality floor** — each lossy candidate is compared to the resized source with SSIM (0.985 for UI, 0.96 for photos by default). Candidates below the floor get +6 quality and another try, up to three times. `--fast` skips this.
|
|
90
|
+
6. **Pick** the smallest passing candidate. AVIF must beat the best non-AVIF by 15%. If nothing is smaller than the input (or the gain is under 5%, or under 10% when re-encoding an already-lossy file), the file is **kept** untouched.
|
|
91
|
+
7. **Write atomically** — temp file, move the original to `.img-subir-originals/`, rename. A crash never leaves a half-written image.
|
|
92
|
+
|
|
93
|
+
SVGs go through svgo (`viewBox`, `width`/`height` and ids preserved). Animated GIF/APNG/WebP become animated WebP (lossless under the `lossless` preset).
|
|
94
|
+
|
|
95
|
+
Safety rules: an existing file at the output path is never overwritten (the input is kept with a reason), `--out` may not be an input directory, and when the extension changes the new file is published before the original is moved or deleted.
|
|
96
|
+
|
|
97
|
+
## For agents
|
|
98
|
+
|
|
99
|
+
`img-subir` is built to be run blind by a coding agent:
|
|
100
|
+
|
|
101
|
+
- Non-interactive, no prompts, no colors when stdout isn't a TTY.
|
|
102
|
+
- `--json` emits a versioned report (`schemaVersion: 1`). `renamed[]` lists every `a.png → a.webp` so the agent can fix references, or pass `--rewrite-refs` and read `rewrittenRefs[]`.
|
|
103
|
+
- Every kept file has a `reason`; every written file has an `actions[]` trail.
|
|
104
|
+
- Exit codes: `0` ok · `1` a file failed (others still written) · `2` bad invocation · `3` `--target-kb` could not be met within the quality floor (best effort written).
|
|
105
|
+
- `img-subir skill` prints a ready-to-save [SKILL.md](skills/img-subir/SKILL.md) for Claude Code, Codex and friends. Drop it in `~/.claude/skills/img-subir/`.
|
|
106
|
+
|
|
107
|
+
```jsonc
|
|
108
|
+
// npx img-subir ./assets --json
|
|
109
|
+
{
|
|
110
|
+
"schemaVersion": 1,
|
|
111
|
+
"preset": "web",
|
|
112
|
+
"files": [
|
|
113
|
+
{
|
|
114
|
+
"input": { "path": "…/dashboard@2x.png", "format": "png", "width": 2880, "height": 1800, "bytes": 3565158, "hasAlpha": true, "animated": false },
|
|
115
|
+
"output": { "path": "…/dashboard@2x.webp", "format": "webp", "width": 1920, "height": 1200, "bytes": 151552, "quality": 82 },
|
|
116
|
+
"kind": "ui", "status": "optimized", "savedBytes": 3413606, "savedPercent": 95.7,
|
|
117
|
+
"actions": ["drop-unused-alpha", "resize:1920x1200", "strip-metadata", "encode:webp@82"],
|
|
118
|
+
"candidates": [{ "format": "webp", "bytes": 151552, "quality": 82, "ssim": 0.9931, "passed": true }, …]
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"totals": { "files": 4, "inputBytes": 4761600, "outputBytes": 258048, "savedPercent": 94.6, "optimized": 3, "kept": 1, "failed": 0, "budgetMissed": 0 },
|
|
122
|
+
"renamed": [{ "from": "…/dashboard@2x.png", "to": "…/dashboard@2x.webp" }],
|
|
123
|
+
"rewrittenRefs": [],
|
|
124
|
+
"backupDirs": ["…/assets/.img-subir-originals"]
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### With Subir
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npx img-subir ./site --rewrite-refs # shrink assets, fix <img src> in the HTML
|
|
132
|
+
rm -rf ./site/.img-subir-originals # once you've eyeballed the page
|
|
133
|
+
subir upload ./site
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Programmatic API
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { optimize, inspect, type Report } from "img-subir";
|
|
140
|
+
|
|
141
|
+
const report: Report = await optimize({ paths: ["./assets"], preset: "github", rewriteRefs: true });
|
|
142
|
+
console.log(report.totals.savedPercent);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|