Repository navigation
Releases
v0.1.0
First release of ichava/icon-sets-sync as a single published commit. The 0.1.x tags cut between 2026-10-23 and 2026-10-26 (v0.1.0 to v0.1.2, with their src/ module aliases) are folded into this release, and their notes are kept below under Archived history.
The Go module proxy and checksum database already hold github.com/ichava/icon-sets-sync/src at v0.1.0, v0.1.1 and v0.1.2, fetched from commits before the single-commit publication, and neither can be cleared. So go install github.com/ichava/icon-sets-sync/src/...@v0.1.0 serves that older code, and @latest the older v0.1.2. The supported installs are the signed release binaries and the Composer package; go install is not offered.
Added
-
Integrations: a REST API, an event bus with an event stream, webhooks and an MCP server, every one off by default (owner, 2026-11-12, one pull request). A fresh install behaves exactly as before; each surface is a setting in a new Integrations card on the Settings page, and
doctorsays which are on and warns about each risky combination. See docs/tools/api.md, events.md, webhooks.md and mcp.md.- Events.
internal/eventsis an in-process bus of CloudEvents 1.0 envelopes (JSON format, ULID ids) with eleven v1 types:job.started,job.progress(stream only),job.finished,pack.synced,pack.behind_upstream,upstream.checked,config.changed(names the key, never the value),server.started,server.stopping,webhook.disabledandwebhook.test. Each type'sdatahas a JSON Schema embedded in the binary.TestEveryTypeHasASchemafails on a type without a schema or a schema without a type,TestEveryTypeHasAPublisheron a type no production code publishes, andTestRunEventsMatchTheirSchemasvalidates every event a run emits. The job registry gains publishing hooks; the run page's per-job stream is unchanged. - REST API under
/api/v1on theservelistener (api.enabled; beyond loopback alsoapi.allow_remote), 22 endpoints with an OpenAPI 3.1 document thatTestOpenAPIMatchesTheRoutesholds to the routes declared inapiRoutes, both ways and scope by scope. Bearer tokens only, with scopesread,sync,config:writeandadmin:icon-sets-sync api token create|list|revokeshows a token once and keeps only its SHA-256 inapi-tokens.jsonin the state directory (mode0600), compared in constant time, with expiry, revocation and a recorded last use. The session cookie does not open/api/and a bearer token opens no page. RFC 9457 problem documents, a 1 MiB body cap, a per-token rate limit (api.rate_limit, 60 a minute),Idempotency-Keyon the sync endpoints, an audit line per write inapi-audit.jsonl, anddry_rundefaulting to true.--read-onlyregisters no write endpoint.GET /api/v1/eventsstreams the bus as Server-Sent Events whenevents.streamis on. The UI and the API now start runs through oneweb.StartSync, and save settings through oneweb.SaveGlobalSettingwith the Settings page's checks (web.ParseSettingValue). - Webhooks (
webhooks.enabled, endpoints inwebhooks.endpoints, each with a required event filter).icon-sets-sync webhooks add|list|remove|rotate|test; secrets are generated (whsec_and 32 random bytes), shown once and kept inwebhook-secrets.json(mode0600), with an optionalsecret_envoverride, and a rotation keeps the old secret signing for 24 hours, so both signatures are sent. Standard Webhooks headers and HMAC-SHA256 signing (checked against the specification's test vector). Every address is checked when it is dialled (RFC 1918, loopback, link-local and metadata, CGNAT, ULA, unspecified, and the IPv6 prefixes that embed IPv4) unlesswebhooks.allow_private; https only (plain http only to localhost withwebhooks.allow_insecure_localhost), no proxy, no redirects, a 10 s timeout and a 256 KiB cap. A bounded queue (1,000), retries after 1 m, 5 m, 30 m, 2 h and 6 h, a rotatedwebhook-deliveries.jsonl, and auto-disable after 50 consecutive failures with awebhook.disabledevent. - MCP.
icon-sets-sync mcpserves the Model Context Protocol on stdio with the official Go SDK,github.com/modelcontextprotocol/go-sdkv1.8.0 (mcp.enabledor--enable). Eight read tools by default;sync_pack,cancel_jobandset_settingonly withmcp.allow_writesand never under--read-only, and a real sync needsconfirmequal to the slug and is refused while aserveruns on the machine. - Security guards, each proven red before it was kept: a token without the scope is refused; an expired or revoked token is refused; a webhook to a private address is refused at connect time; a bad signature does not verify; the write tools are absent under
--read-onlyand withoutmcp.allow_writes; every integration is off on a fresh config; a non-loopback bind defaults to TLS.
- Events.
-
A Windows launcher that opens the web UI with no console window.
make crossbuildsicon-sets-sync-launcher_windows_<arch>.exe(amd64, arm64, 386) beside each Windows binary. It is a separate program (src/cmd/icon-sets-sync-launcher) linked for the GUI subsystem (-H=windowsgui), so Windows opens no console for it: it finds the CLI beside itself, starts itsservedetached withICHAVA_DESKTOP_LAUNCH=1, and exits, and if the CLI is missing it shows an error box naming the paths it tried. A double-clicked CLI still flashes a console for under a second; the CLI itself is unchanged, so it behaves normally incmdand PowerShell (owner, option A, 2026-11-11). Like the macOS app and the Linux.desktopentry, it is not a GoReleaser build or a release asset.TestWindowsLauncherIsAGUIProgramAndTheCLIIsNotreads both PE headers (proven red both ways); the build-layout guard names the three launcher files;TestOnlyOneEntrypointallows exactly this secondsrc/cmddirectory. -
Settings are edited in place on the Settings page. Each effective setting has the control its type calls for (a menu, a switch, a bounded number, a list, a path or a multi-line field), a Save that writes it to the global config and a Reset that removes it from there, and says beside it when an
ICHAVA_*variable or a flag overrides the saved value. Values are checked as they are typed by the same code as the save (POST /settings/value/<name>/check), and the save adds checks the loader does not make: directories and the npm path must exist,allowed_hostsmust be bare host names, branch names must be ones git accepts, and lists may not repeat an entry. A refused save keeps what was typed and says why beside the field. -
Pagination on every list, with a
page_sizesetting. Packs, runs, audit results and a run's log showpage_sizerows (default 25, 5 to 500,ICHAVA_PAGE_SIZE) with a Per page menu and page links that keep any filter in the address. Settings gain aMaxbound, used bypage_sizeand carried intoglobal.schema.json. -
The global config starts initialised, and can be reinitialised.
serveand the newconfig initwrite it from what the machine has:$schema,packages_dirs(the directory of pack checkouts this tool sits in) andprivate_config_dir(when exactly one pack carries one). A missing,$schema-only or still-generated file is (re)written; one a person edited is left alone, told apart by a record of what was last written (global-init.jsonin the state directory). The global config page says which it is and has Reinitialise (a confirmed action;config init --forcein the CLI), which keeps the old copy in the history. Read-only servers and CI runs write nothing. -
Recipes for the 34 v0.1.0 packs and sets.
config/gains Lucide, Heroicons, Bootstrap Icons, Simple Icons, Material Design Icons, Phosphor, Font Awesome Free, Remix Icon (pinned to v4.8.0, the last Apache-2.0 release), Material Symbols, Fluent, Ionicons, Carbon, Octicons and Iconoir, and one config per set oficon-sets-extras(20, each recording its version atupstream.sets.<set>.current_versionin the pack). All 38 registered configs validate, and a dry run of each new one at its pinned version against the local pack checkout reproduced the committed SVGs byte for byte (0 added, removed or changed, 2026-11-04).TestEveryShippedRecipeValidatesloads and builds every registered config and fails below 38, or on a config filepacks.jsondoes not register. -
split-variantstransform. Sorts upstream files into one directory per variant by the directory they come from (from), the suffix they carry (suffix, stripped), or both; unclaimed files are dropped and counted, which is how a pack takes a subset, andflatten_prefixkeeps sub-folders as a name prefix. It covers Heroicons, Phosphor, Material Symbols, Fluent, Ionicons, Carbon and Octicons. A variant that claims nothing, or two names reducing to one, fail the run. -
excludetransform. Drops icons by stem (names, Octicons' GitHub marks), by directory (directories, Game Icons' unlicensedbadges/) or from an upstream data file (metadata, Simple Icons' non-CC0 logos fromdata/simple-icons.json, so the list follows each release). An excluded item that cannot be named fails the run. -
More transform options.
slugifytakesdirs(slug directory names too: Remix'sGame & Sports) andhyphenate_dots(lucene.net->lucene-net), and now fails when two names slug to one instead of renaming one over the other.flatten-variantstakesslug, which slugs each stem before the affix (Health Icons'!.svgis committed as-filled.svg).sanitisetakesdrop_unparsable, which removes a file the policy cannot parse instead of leaving it unchecked (Noto Emoji's 170 SVGs with an Illustrator DTD). -
Licence guards for every licence the packs carry.
licencenow admitsApache-2.0,CC-BY-3.0,CC0-1.0,ISCandMITbeside the two CC-BY ids, each checked against its opening lines (a Markdown#is ignored). The new `licence_sha256...