contrast_check | Computes the WCAG 2.1 contrast ratio between a foreground and background color and returns AA/AAA pass/fail (normal text, large text, UI components) | fg, bg (hex, e.g. #333333 / 333 / fff) |
count_chars | Counts Japanese text by grapheme, breaks it down into zenkaku/hankaku, counts lines, and computes the X (Twitter) post weight (zenkaku=2, hankaku=1, URL=23 flat, limit 280) | text |
webp_convert | Converts PNG/JPEG files (absolute paths) to WebP using the same libwebp WASM encoder (default quality 80) as the browser tool at tools.first-ch.com/webp/. Output defaults to the same directory as each input with a .webp extension | paths[] (absolute paths), quality? (1-100), outputDir? |
jsonld_generate | Generates schema.org JSON-LD for organization / faqpage / service / breadcrumb. Empty fields are omitted automatically. Returns both a json object and a ready-to-embed <script> snippet | type, plus the matching organization / faq / service / breadcrumb object |
llmstxt_generate | Generates an llms.txt file (per the llmstxt.org proposed format) summarizing a site for AI crawlers/agents | siteName, summary?, notes?, sections? |
encoding_convert | Detects the character encoding (UTF-8 / Shift_JIS), BOM and line endings (CRLF / LF / CR) of a file or text and converts it to UTF-8. Useful for diagnosing garbled Japanese CSVs and for normalising line endings. Output is UTF-8 only — encoding to Shift_JIS is not supported (no standard API, and a mapping table would be required). | base64 or text, mode (analyze | convert), encoding, newline, bom |
marp_render | Renders Marp Markdown to slides. Emits a self-contained HTML file (theme CSS inlined; opens in a browser and prints one-slide-per-page) and optionally PDF. Ships a bundled Japanese theme firstch (paper/ink/vermilion; uses IBM Plex Sans JP if installed) used as the default theme. Marp front-matter in the Markdown (theme: / paginate: / size: / ``) is honored. | markdown or inputPath, theme?, formats? (html | pdf), outputPath?, title? |
testdata_generate | Generates dummy data for form / CSV-import testing. mode=records returns names, kana readings, addresses, postal codes, emails and phone numbers as CSV/TSV/JSON/XLSX with a choice of encoding (UTF-8 / Shift_JIS), BOM and line endings. format=xlsx returns a real Excel workbook as base64 (or writes it to outputPath) — the ZIP/OOXML parts are assembled directly, with no spreadsheet dependency, and postal codes and phone numbers are written as text cells so leading zeros survive; mode=text returns strings of exactly n-1 / n / n+1 characters for maxlength boundary tests. All output is fictional (emails use the RFC 2606 example.com family). Passing a seed makes the output reproducible | mode?, rows?, fields?, format?, locale?, encoding?, newline?, bom?, header?, seed?, preset?, length?, outputPath? |
diff_check | Compares two texts (or two files) and returns the added/removed/changed line counts plus a unified diff (.patch) — the same logic as the browser tool at tools.first-ch.com/diff/. Lines are matched with patience diff (lines occurring exactly once in both sides become anchors), falling back to Myers only inside anchor-less ranges. With format=blocks / both, paired changed lines are also compared at token level (runs of letters/digits are one token, CJK characters one each) and returned as changed_parts, so you can see which words changed. CRLF/CR/LF all count as the same line break; trailing-whitespace-only differences are ignored by default | a/b (text) or pathA/pathB (absolute paths), format? (unified | blocks | both), context?, ignoreWhitespace?, ignoreCase?, words? |
cron_explain | Explains a cron expression in plain language and returns the upcoming run times — the same logic as the browser tool at tools.first-ch.com/cron/. Parsing follows Vixie cron (crontab(5)): ranges, steps, lists, JAN-DEC / SUN-SAT names and the @daily-style shorthands; six fields means the first one is seconds (node-cron / Spring style). warnings call out the traps — day-of-month and day-of-week are OR'd (not AND'd) when both are restricted, */n that does not divide its range evenly is not a uniform interval, and impossible dates such as February 30th never fire. Run times are found on the wall clock of the target time zone and converted back to real instants, so they stay correct across daylight-saving transitions | expression, timeZone? (IANA, default UTC), count? (default 5, max 100), from? (ISO 8601) |
base64_encode | Encodes text or a file as Base64 and a data: URI, and returns ready-to-paste HTML <img> / CSS background-image snippets — the same logic as the browser tool at tools.first-ch.com/base64/. With mode="decode" it turns Base64 or a data URI back into bytes, writing them to outputPath when given. For SVG it returns both encodings and defaults to the shorter one (percent-encoding beats Base64, which always inflates by ~33%), always escaping & " < > # %, whitespace and non-ASCII so the URI drops straight into an HTML attribute or a CSS url("…"). Decoding accepts standard or URL-safe Base64, tolerates whitespace and newlines, does not require padding, and trusts the actual magic numbers (PNG/JPEG/GIF/WebP/ico/PDF/zip/woff/woff2/SVG) over the MIME type a data URI claims | mode? (encode default / decode), text? or path?, base64?, outputPath?, urlSafe?, wrap? (76 for MIME), dataUri?, mimeType?, snippets? |
url_params | Breaks a URL's query string into keys and values, edits it (set / remove / utm / removeTracking / sort) and rebuilds it — the same logic as the browser tool at tools.first-ch.com/url/. Values come back decoded (%XX to characters, + to a space), and parameters you did not touch are written back byte-for-byte, so calling it with nothing but a url returns exactly what you passed in — signed URLs survive a round trip. reencode normalises the whole query with encodeURIComponent rules instead. warnings flag duplicate keys, unencoded spaces and non-ASCII, broken %XX, + being read as a space, mixed-case UTM values, a missing utm_source/utm_medium, passwords in the URL, credential-looking keys and URLs over 2,000 characters. Relative paths and broken percent-encoding are parsed as far as they can be read instead of throwing. mode="encode" / "decode" converts a bare string instead (scheme: component / uri / form). No network access — the URL is never fetched | url?, mode? (parse default / encode / decode), text?, scheme?, set?, remove?, utm?, removeTracking?, sort?, reencode?, spaceAsPlus? |
html_escape | Escapes < > & " ' into HTML entities, or with mode="unescape" turns entities such as &, ' and あ back into characters — the same logic as the browser tool at tools.first-ch.com/html-escape/. Escaping handles & first, so a second pass never double-escapes what the first pass produced; pick named or numeric references (numeric), decide whether quotes are escaped (quotes, mandatory inside an attribute value) and whether ' is written as ' or ' (apos — ' does not exist in HTML 4.01), and encode non-ASCII characters as references (nonAscii) when the charset may not survive the pipeline. Unescaping covers all 252 named references from HTML 4.01 plus decimal and hexadecimal ones, maps C1-range references such as € to their Windows-1252 characters as the spec requires, and leaves unknown names, out-of-range numbers and semicolon-less references untouched rather than guessing. notes reports already-escaped input, bare &, missing semicolons, unknown entity names and no-break spaces (U+00A0) | mode? (escape default / unescape), text? or path?, outputPath?, quotes?, apos?, numeric?, nonAscii? (none / named / decimal / hex) |
json_to_yaml | Converts JSON to formatted YAML, and yaml_to_json converts back — the same logic as the browser tool at tools.first-ch.com/json-yaml/, with no YAML dependency (the parser and writer are implemented in this package). Choose the indent width, the quoting style (quote), how null is written (nullStyle), whether multi-line strings become ` | blocks (block), whether keys are sorted (sortKeys) and whether the output starts with --- (docStart). Strings another parser could read as a different type — yes/no/on/off, 0755, 12:30, 2026-08-12, anything numeric-looking — plus strings with surrounding whitespace or a leading -/*/#are quoted automatically, so the output means the same thing to a YAML 1.1 parser such as PyYAML. JSON containing comments, trailing commas, single quotes or unquoted keys (as intsconfig.json) is read and converted, with notes saying it is not valid JSON (relaxed=false` rejects it strictly instead) |
yaml_to_json | Converts YAML to JSON — useful for turning docker-compose, GitHub Actions, Kubernetes and CI config into something a program can handle, and for checking that a file parses at all. A syntax error is returned as an error naming the line, the column, the cause and the fix, with the two surrounding lines quoted and a ^ under the column. Multiple documents (---) become a single JSON array, and anchors (&name), aliases (*name) and merge keys (<<) are expanded because JSON has no references (notes says when that happened). Scalars follow the YAML 1.2 core schema, so yes / no / on / off / NO stay strings — but notes flags them, along with 0755 (decimal 755, not octal), 12:30 (750 in YAML 1.1's base 60), date-like values, duplicate keys and integers beyond 2^53. Supports block mappings and sequences, flow style, quoted scalars (multi-line, with escapes), block scalars (` | > with chomping and an explicit indent), tags (!!str !!int !!float !!bool !!null !!binary) and comments; only the explicit ? key` notation is unsupported |
px_rem_convert | Converts CSS lengths between px, rem, em and pt — the same logic as the browser tool at tools.first-ch.com/px-rem/. Pass value for a single conversion and you get px/rem/em/pt, a ready-to-paste font-size line and the scale of common font sizes (12–64px, one row per size with its typical use); pass css or path and the whole stylesheet is rewritten (direction: px2rem default / px2em / rem2px / em2px). The bulk pass never touches comments, strings (content: "10px"), the contents of url() or digits inside identifiers such as --size-16px, and by default keeps hairlines in px (minPx=2, because a 1px border in rem varies in thickness between devices) and leaves the conditions of @media and other at-rules alone (skipMedia), which is where breakpoints live. ignoreProps excludes properties by prefix, zeroUnitless writes a bare 0. The root can be given as a percentage (root="62.5%" → 10px, read against the 16px browser default), and notes reports rounding, skipped values and the accessibility cost of the 62.5% trick | value? (24 / "1.5rem") or css? / path?, unit?, outputPath?, direction?, root?, parent?, precision? (auto or 0-6), minPx?, zeroUnitless?, skipMedia?, ignoreProps?, scale? |
color_convert | Converts a colour between HEX, RGB, HSL and OKLCH and builds the rgba() / hsla() / 8-digit HEX code at any alpha — the same logic as the browser tool at tools.first-ch.com/color/. Input can be HEX (3/4/6/8 digits), rgb(), hsl(), hwb(), oklch(), oklab(), one of the 148 CSS named colours or transparent, in either the legacy comma form or the modern slash form, with angles in deg / grad / rad / turn. Pass a background and flattened returns the colour that actually reaches the screen once the transparency is composited over it (foreground × α + background × (1−α)) — what you need to turn a semi-transparent layer from a mockup into a solid HEX. It also returns the WCAG 2.1 contrast against white and black, an alpha_table stepped by step%, and an 11-step lightness palette (50–950) that keeps the hue and chroma. An OKLCH value outside sRGB has its chroma lowered by binary search until it fits, keeping the lightness and hue, because clipping the RGB channels would shift the hue; notes says when that happened, when the colour matches a named colour exactly, and when it is a neutral grey | color, alpha? (0-1, 0-100 or "50%"), background? (default #ffffff), syntax? (modern default / legacy), uppercase?, alphaPercent?, step? (1-50, default 10), alphaTable?, palette? |
hash_generate | Computes the MD5, SHA-1, SHA-256, SHA-384 and SHA-512 digests of a string or a file in one call — the same logic as the browser tool at tools.first-ch.com/hash/. Pass expected and the digest is verified against it: the algorithm is inferred from the length, and verification says whether it matched. expected takes command output as-is — the <digest>␣␣<filename> form from sha256sum, the output of shasum -a 256, the SHA256 (file) = … form, a sha256: prefix, colon-separated hex, Base64 and base64url are all understood. Output is lower-case hex by default, or HEX / base64 / base64url. For text, newline (lf / crlf) and bom control the exact bytes that get hashed, so a digest produced from a Windows file can be reproduced (the encoding is always UTF-8); neither applies to path, which is hashed byte for byte and streamed in a single pass when large. notes warns that MD5 and SHA-1 are broken for collision resistance and that a plain digest is not password storage | text? or path?, algorithms? (default ["md5","sha1","sha256","sha512"]), format? (hex default / HEX / base64 / base64url), newline?, bom?, expected? |
jwt_decode | Decodes a JWT (JSON Web Token) into its header and payload and reports on the expiry — the same logic as the browser tool at tools.first-ch.com/jwt/. token can be a whole Authorization: Bearer <token> line: the header name, the Bearer prefix, surrounding quotes, trailing commas and line breaks inside the token are all stripped. expiry gives the status (valid / expired / not_yet / no_exp), the ISO timestamps of exp / nbf / iat, the seconds remaining and how far through its lifetime the token is; clockTolerance allows for clock skew, and now pins the current time so a check is reproducible. Pass key and the signature is verified with Web Crypto: HS256/384/512 take the shared secret as a string (keyEncoding accepts base64url / hex), while RS / PS / ES / EdDSA take an SPKI PEM public key or a JWK / JWKS document (a JWKS is matched against the header's kid). A private key, a certificate or a PKCS#1 key is refused with the command that converts it. warnings cover alg: none, a missing signature, an expired or not-yet-valid token, exp given in milliseconds (Date.now() without dividing by 1000), a lifetime that is too long for an access token, and secrets or personal data left in the payload — which anyone holding the token can read, because base64url is encoding, not encryption. A five-segment JWE returns its header only (it is not decrypted), and nothing is ever fetched: JWKS documents must be passed in | token, key?, keyEncoding? (utf8 default / base64url / base64 / hex), clockTolerance? (seconds), now? (Unix seconds) |
user_agent_parse | Parses a User-Agent string into browser name and version, rendering engine, OS and version, device type (desktop / mobile / tablet / tv / console / wearable / bot), vendor and model, and CPU architecture — the same logic as the browser tool at tools.first-ch.com/user-agent/. ua can be a raw access-log line: a leading User-Agent:, surrounding quotes and a trailing comma are stripped. Detection tries the more specific token first (Edg/ → OPR/ → Chrome/ → Safari/), because Chrome, Edge and Opera all claim both Chrome/ and Safari/ and a substring match misclassifies them. tokens breaks the string down token by token and explains the fossils (Mozilla/5.0, KHTML, like Gecko, Mobile/15E148). notes reports what the UA cannot tell you: Chrome's reduced UA (minor version frozen to 0.0.0, Android model frozen to K), macOS always reporting 10_15_7, Windows 10 being indistinguishable from 11, an iPad sending the same UA as a Mac, in-app browsers (LINE / Instagram / Facebook / Android WebView), and the fact that a UA is self-declared and must never be used for access control. Crawlers (Googlebot, bingbot, GPTBot, ClaudeBot, PerplexityBot) and HTTP clients (curl, python-requests, axios) are identified as well. Pass uas to parse many at once and get a summary breakdown by browser, OS and device type | ua? or uas? (array), includeTokens? (default: on for 20 strings or fewer) |
uuid_generate | Generates 1-100 UUID v4 or ULID values at once — the same logic as the browser tool at tools.first-ch.com/uuid/. A UUID v4 is 128 bits of which 122 are random (the other 6 carry the version and variant); a ULID is 26 characters where the first 10 encode the creation time (Unix milliseconds, 48 bits) and the last 16 are random (80 bits), written in Crockford's Base32. Because a ULID leads with the time, sorting the strings lexicographically sorts them chronologically, and timestamp (ISO 8601, Unix seconds or Unix milliseconds) pins that time for reproducible output. Values minted inside the same millisecond follow the monotonic rule from the ULID spec — the random part is incremented by one — so a batch of 100 still comes out in creation order. Randomness comes from node:crypto randomBytes (CSPRNG); Math.random is never used. format shapes text (one per line / JSON array / comma separated / quoted for a SQL INSERT), and uppercase / hyphens / braces control the spelling (UUID defaults to lower case per RFC 9562, ULID to upper case; hyphens: false gives the 32-character form for MySQL BINARY(16), braces: true the Windows GUID form) | type? (uuid | ulid), count? (1-100), format?, uppercase?, hyphens?, braces?, timestamp? (ULID only) |
aspect_ratio_calc | Calculates between an aspect ratio and a size — the same logic as the browser tool at tools.first-ch.com/aspect-ratio/. Pass ratio (16:9, 16/9, 16x9 or a decimal such as 1.85) with either width or height and the other side comes back, along with the simplified ratio, the decimal, the padding-top percentage and an aspect-ratio declaration. Pass width and height without a ratio and it works backwards: the ratio simplified by the greatest common divisor, the orientation, the megapixel count, the closest common ratio (16:9, 4:3, 3:2, 1:1, 4:5, 9:16, 21:9, 1.85:1, 2.39:1, 1.91:1 for OGP, the golden ratio, A-series paper) with the percentage it is off by, and the name of the size where there is one (1920×1080 is FHD). round takes the result to the nearest whole pixel, an even number, down or up, and notes then reports the real ratio that rounding produced and how far it drifts — H.264 / H.265 store colour at half resolution (YUV 4:2:0), so round: "even" is what video needs. box fits the size into a frame with contain or cover and returns the rendered size, the scale, the letterbox / pillarbox bars, how many pixels are cropped and how much stays visible. widths (or table: true) returns the height at every breakpoint, and snippet: true returns the CSS — with an optional @supports padding-top fallback — plus HTML carrying the width / height attributes that prevent layout shift | ratio? and/or width? / height?, round? (none default / round / floor / ceil / even), widths?, table?, box? ("1280x400"), fit? (cover default / contain), snippet?, selector?, target? (img / video / iframe / background), objectFit?, fallback? |
markdown_table | Turns TSV or CSV copied from a spreadsheet into a Markdown table, and converts a Markdown table back to CSV, TSV, HTML or JSON — the same logic as the browser tool at tools.first-ch.com/markdown-table/. Copying a range in Excel, Google Sheets or Numbers puts tab-separated text on the clipboard, not comma-separated, so the delimiter is detected by parsing with tabs, commas and semicolons in turn and keeping whichever gives the most consistent column count — thousands separators (1,200) never split a column. CSV input follows RFC 4180 quoting (commas, newlines and doubled quotes inside a quoted field). When padding cells, full-width characters are counted as two columns following the Unicode East Asian Width property, so the pipes line up in a monospaced editor. Columns whose non-empty cells are all numeric (thousands separators, decimals, currency symbols, percent signs, units) are right-aligned with ---:, and aligns overrides any column. Pipes inside cells become |, line breaks become <br> (or a space), short rows are padded with empty cells, and every fix is reported in notes rather than applied silently. Reading a Markdown table picks up the alignment colons and turns <br> back into a real newline, so a CSV export reopens cleanly in a spreadsheet | text or path, outputPath?, from? (auto default / tsv / csv / ssv / markdown), to? (markdown default / csv / tsv / ssv / html / json), header? (first default / auto / none), align?, aligns?, pad?, eastAsian?, autoNumber?, trim?, skipEmpty?, transpose?, multiline? (br default / space), eol? |
sql_format | Formats a SQL query that has been squashed onto a single line — uppercase keywords, a line break per clause and real indentation — the same logic as the browser tool at tools.first-ch.com/sql-format/. SELECT / FROM / WHERE / GROUP BY / HAVING / ORDER BY / LIMIT / INSERT INTO / VALUES / UPDATE / SET / DELETE FROM / WITH / UNION start a line and their contents are indented one level; joins start a line with ON indented below them; AND / OR break per condition, except the AND of BETWEEN a AND b. A parenthesis becomes an indented subquery only when SELECT, WITH or VALUES follows it, so function calls (SUM(…), IN (1, 2, 3), OVER (PARTITION BY … ORDER BY …)) stay on one line, and CASE gets WHEN / ELSE / END stacked. Only keywords and type names are re-cased — table, column and alias spellings are left exactly as typed, because identifier folding differs per database. Strings, quoted identifiers ("…" / `…` / […]), comments and placeholders (? / :name / $1 / @var) pass through verbatim. notes reports UPDATE or DELETE without a WHERE, unbalanced parentheses and quotes, SELECT *, implicit comma joins and placeholder usage. compact collapses everything back onto one line. It is a lexical formatter rather than a parser for one database, so MySQL, PostgreSQL, SQL Server, SQLite and Oracle dialects survive as written (syntax errors are not detected) | text or path, outputPath?, keywordCase? (upper default / lower / preserve), functionCase?, indent? (2 / 4 default / 8 / tab), commaStyle? (trailing default / leading), logicStyle? (leading default / trailing), breakColumns?, breakLogic?, breakOn?, breakCase?, breakSubquery?, expandClauses?, compact?, eol? |
qr_generate | Turns a URL or any text into a QR code and returns it as SVG (vector), PNG or terminal-friendly text art — the same logic as the browser tool at tools.first-ch.com/qr/. Encoding (numeric, alphanumeric or UTF-8 byte mode, picked automatically from the input), Reed–Solomon error correction, the layout of finder, timing and alignment patterns, version information and the choice between the eight mask patterns are all implemented here (JIS X 0510 / ISO 18004, versions 1–40); there is no external API or image service. ecLevel trades capacity for damage tolerance (L about 7%, M about 15%, Q about 25%, H about 30% — use Q or H for print), size is the edge length in pixels, and margin is the quiet zone in modules (4 is the recommended default; 0 can break scanning over a patterned background). Wi-Fi (WIFI:T:WPA;S:…;P:…;;), mailto:, tel:, SMSTO: and geo: strings just go in as text. outputPath writes the file instead of returning the body; format="png" without it returns a data URI. Capacity tops out at 7,089 digits, 4,296 alphanumeric characters or 2,953 bytes (all at level L) | text, ecLevel? (L / M default / Q / H), size? (64–4096, default 320), margin? (0–32, default 4), format? (svg default / png / text), mode? (auto default / numeric / alnum / byte), mask? (0–7), minVersion?, outputPath? |
unixtime_convert | Converts Unix seconds, milliseconds, microseconds and nanoseconds to and from ISO 8601 and ordinary date strings — the same logic as the browser tool at tools.first-ch.com/unixtime/. The unit of a plain number is detected from its digit count (up to 10 digits seconds, up to 13 milliseconds, up to 16 microseconds, longer nanoseconds); unit states it outright instead. Digits below the millisecond are dropped and reported in notes. On the date side it accepts 2026-08-24T09:30:00Z (ISO 8601), 2026-08-24 18:30, 2026/8/24 9:05, the Japanese 2026年8月24日 18時30分, Sun, 24 Aug 2026 03:00:00 GMT (HTTP-date) and now, stripping the quotes, brackets and trailing commas that come with a paste from CSV or JSON. input takes one value per line, up to 500 lines at a time, and every row comes back with Unix seconds, milliseconds, ISO 8601 in UTC, local time in timeZone (with weekday and UTC offset) and a relative reading ("30分後" / "in 30 minutes"); a line that cannot be read fails on its own without taking the others down. A date with no offset is read as a wall-clock time in timeZone and always says so in notes; an explicit +09:00 or Z in the input wins over timeZone. A wall-clock time that daylight saving skips is moved forward to the moment after the change, also noted. An 8-digit number such as 20260824 is Unix seconds here, so it is flagged in case a date was meant. Negative values (before 1970) and fractional seconds work | input, timeZone? (IANA name, default UTC), unit? (auto default / s / ms / us / ns), now? (Unix milliseconds or a date string, default the current time), lang? (ja default / en) |
robotstxt_generate | Assembles a robots.txt from crawl rules, sitemap declarations and a preset for AI crawlers — the same logic as the browser tool at tools.first-ch.com/robots-txt/. ai.preset decides how 24 known AI crawlers are treated in one move: training (the default: refuse the ones that collect training data — GPTBot, ClaudeBot, Google-Extended, Bytespider, CCBot and the rest — while allowing AI search and on-demand fetchers such as OAI-SearchBot, Claude-SearchBot, PerplexityBot and ChatGPT-User), allow, block, none (leave AI crawlers out) or custom with ai.overrides naming them one by one. A crawler obeys exactly one matching group, so writing a group for GPTBot stops the User-agent: * rules from ever reaching it; by default the shared disallow paths are copied into the AI groups to close that gap (ai.inherit: false turns it off). Paths get a leading slash added and full URLs reduced to their path, and warnings reports spaces inside a path, non-ASCII paths that need percent-encoding, wildcards that not every crawler supports, a Crawl-delay Googlebot will ignore, a missing sitemap, a User-agent listed twice and a site blocked in full. listCrawlers: true returns just the crawler list (name, vendor, purpose) without generating anything | siteUrl?, disallow?, allow?, userAgents? (default *), crawlDelay?, blockCrawlers?, sitemaps?, ai? (preset?, overrides?, inherit?), groups?, comments? (default true), allowStyle? (disallow-empty default / allow-slash), listCrawlers?, lang? (ja default / en), outputPath? |
case_convert | Converts identifiers between eleven cases — camelCase, PascalCase, snake_case, CONSTANT_CASE, kebab-case, Train-Case, dot.case, Title Case, Sentence case, lower case and UPPER CASE — the same logic as the browser tool at tools.first-ch.com/case/. Built for the mechanical jobs at the boundary between layers: renaming variables in bulk, moving between snake_case database columns and camelCase JSON keys, tidying CSV headers. scope sets the unit of work: lines (the default, one item per line), items (every comma or tab separated field of a line, for CSV header rows) or whole. Leading and trailing spaces, indentation, line endings (LF/CRLF) and the separators are preserved, so only the names change. A run of capitals is broken only before a capital followed by a lower-case letter (XMLHttpRequest → XML / Http / Request), and digits stay attached to the word before them (sha256Hash → sha256 / Hash; splitDigits splits them). Acronyms fold to parseXmlData by default and stay capitalised with keepAcronyms, except the first word of a camelCase name, which is always lower-cased (URLParser → urlParser). Characters with no letter case (Japanese, for example) pass through rather than being transliterated. notes reports names that collide after conversion (first name and first_name both become first_name, which is how a column silently disappears on import), results starting with a digit that cannot be identifiers, and items containing acronyms; items[].detected names the case each input was written in. allFormats expands every item into all eleven cases, listFormats returns just the list of cases with what each is used for | text? or path?, outputPath?, format? (camel default), scope? (lines default / items / whole), splitDigits?, keepAcronyms?, allFormats?, listFormats?, lang? (ja default / en) |
csv_convert | Converts CSV/TSV into a JSON array and back — the same logic as the browser tool at tools.first-ch.com/csv-json/. For moving data you already have between a spreadsheet and an API: turning an exported table into JSON a request body can carry, or an API response into something Excel opens. With direction omitted it guesses json2csv when the input starts with [ or { and csv2json otherwise. The delimiter is detected by looking at the first 20 lines for the character that appears the same number of times on every line, and can be pinned with delimiter. Column names such as stock.qty are read and written as nested objects, and tags.0 / tags.1 (tags[0] means the same) become an array — the reverse direction flattens by the same rule, so a round trip through CSV restores the original structure (nest: false writes nested values into a single cell as JSON text). Type inference is decided by round trip — a value becomes a number only when converting it back to text is character-for-character identical — so 0123, +1, 1.50 and integers beyond 2^53 (12345678901234567890) stay strings and postcodes, phone numbers, invoice numbers and version strings survive. Reading follows RFC 4180: quoted cells, "" escapes, line breaks inside cells, CRLF and a BOM are all handled, and the output side offers bom and newline for Excel. On the JSON side it accepts an array, a single object, an object wrapping an array such as {"data":[…]}, and JSON Lines. A JSON syntax error comes back as an ok: false result with a line, a column and an excerpt rather than an exception, so the reply itself says where to fix it. notes also reports rows whose column count does not match the header, duplicate header names (kept as name_2), empty headers (named column4), values that would lose digits, cells starting with =, +, - or @ that a spreadsheet may execute as a formula (CSV injection), and objects with a different set of keys | text? or path?, outputPath?, direction? (auto default / csv2json / json2csv), delimiter? (auto default / comma / tab / semicolon / pipe), header?, nest?, types?, trim?, emptyNull?, indent? (2 default / 0–8 / tab), newline? (lf default / crlf), quoteAll?, bom?, lang? (ja default / en) |
zenkaku_convert | Converts letters and digits, katakana, symbols and spaces between their full-width and half-width forms with a direction chosen per kind, and cleans up the whitespace in the same pass — the same logic as the browser tool at tools.first-ch.com/zenkaku/. For normalising Japanese copy supplied by a client, cleaning a file before an import, and tidying form input. Each of alnum / kana / symbol / space takes keep, han (half-width) or zen (full-width); the defaults are the form most Japanese copy is set in — letters, digits, symbols and spaces half-width, katakana full-width (the same as preset: 'ja'). preset offers ja, csv (ja plus the whitespace and invisible-character cleanup), han and zen, and any per-kind option overrides it. Half-width katakana is not one character per glyph, so converting to full-width composes a following voiced mark (ガ → ガ, パ → パ, ヴ → ヴ) and converting the other way splits it into two. Japanese punctuation, brackets, the middle dot and the prolonged sound mark (。 、 「 」 ・ ー) live inside the half-width katakana block (U+FF61–U+FF9F) and are treated as katakana (kana: 'han' turns 「テスト」。 into 「テスト」。; use keep to leave the punctuation full-width). Spaces cover U+3000 ⇄ U+0020 and fold NBSP and the Latin space family to the half-width side. The cleanup is collapseSpaces (a run of spaces becomes its first character), trimLines, blankLines (keep / collapse / remove), removeInvisible (zero-width, BOM, control codes) and composeMarks (NFC, for copy that writes ガ as カ plus a combining U+3099). Line endings (LF/CRLF), tabs, hiragana and kanji are never rewritten. notes reports what is left — half-width katakana, ideographic spaces, invisible characters, combining marks — plus the wave dash 〜 (U+301C) versus the fullwidth tilde ~ (U+FF5E) and platform-dependent characters (① ㈱ Ⅲ), which are reported and never rewritten because which way they should go depends on the destination. This is deliberately not NFKC: ① never becomes 1, and only the kinds you selected are touched. inspect: true converts nothing and returns just the audit | text? or path?, outputPath?, preset? (ja / csv / han / zen), alnum?, kana?, symbol?, space? (each keep / han / zen), collapseSpaces?, trimLines?, blankLines? (keep default / collapse / remove), removeInvisible?, composeMarks?, inspect?, lang? (ja default / en) |
cidr_calc | Turns CIDR notation, a subnet mask, an ACL wildcard mask or a bare address into the network address, broadcast address, usable host range, address and host counts, reverse DNS zone and a binary breakdown — the same logic as the browser tool at tools.first-ch.com/cidr/. For subnet maths while writing server configs, firewall rules and Basic-auth allow lists. IPv4 and IPv6 both work: :: compression, a trailing IPv4 form (::ffff:192.168.0.1), [...] brackets and a %zone suffix are parsed, and output follows RFC 5952. 192.168.1.5 255.255.255.0 and 192.168.1.5/0.0.0.255 (the ACL convention) are accepted in the same field. The exceptions in host counting are built in: a /31 has two usable addresses for point-to-point links (RFC 3021), a /32 is one host, and IPv6 has no broadcast address. When host bits are set in the address you pass, notes returns the block it actually belongs to — the form that belongs in a config file. Octets with a leading zero (192.168.001.1) are refused, because some libraries read them as octal and the same text then means a different address. The address type is matched against the main IANA special-purpose blocks (private, CGNAT, loopback, link-local, documentation, multicast, ULA and more) and a network that extends beyond its reserved range is flagged. split lists the equal subnets of a new prefix length, contains tests addresses for membership, and range reduces ranges such as 10.0.0.5 - 10.0.0.200 (mixed with CIDR blocks and single addresses, one per line or ;-separated) to the fewest blocks that cover them exactly, merging overlaps and adjacencies first. All arithmetic is BigInt | cidr? and/or range?, split? (prefix length), contains? (string[]), limit? (1-4096, default 256), lang? (ja default / en) |
date_calc | Counts the days and business days between two dates, works out the due date N days or N business days from a start date, and turns a closing day plus payment terms into a payment date — the same logic as the browser tool at tools.first-ch.com/date-calc/. For delivery dates, acceptance deadlines and payment schedules. A business day is any day that is not a weekend, a Japanese public holiday or a closure you list; the holiday table shipped with the package is the one published by Japan's Cabinet Office (1970–2027), so the "Happy Monday" holidays that move each year, substitute holidays after a Sunday holiday, citizens' holidays squeezed between two holidays (2026-09-22) and the shifting equinox days are all counted. mode="between" returns calendar days, business days, the breakdown of days off and every holiday in the range. mode="add" moves n (negative counts backwards) units from base and also lists the days it skipped; "within 10 business days of receipt" differs by one depending on whether day zero counts, so counting starts the day after by default (the Japanese civil-code rule) and include_start=true makes the start date day 1. mode="payment" resolves invoice + closing + pay_months + pay_day, moving a payment that lands on a day off by pay_adjust (default prev, the usual Japanese practice). The weekend, the New Year break, your own closures and your working weekends can all be set. Dates are held as days since 1970-01-01, so the server's time zone cannot shift the answer | mode? (between / add / payment), start?, end?, base?, n?, unit? (business default / days / weeks / months / years), include_start?, adjust?, invoice?, closing? (eom default / 1-31 / none), pay_months?, pay_day?, pay_adjust?, weekend?, holidays?, year_end?, closed_dates?, work_dates?, lang? |
html_to_markdown | Converts an HTML fragment into Markdown — the same logic as the browser tool at tools.first-ch.com/html-md/. For turning a fetched page into something an LLM can read cheaply, and for moving existing web copy into Markdown. Headings (h1-h6), paragraphs, line breaks, emphasis, strikethrough, lists (nested, the start attribute, checkboxes), tables (thead / caption, with column alignment taken from align and text-align), links, images, code blocks (the language comes from class="language-*"), block quotes, horizontal rules, definition lists, figure / figcaption and details / summary are all handled, while script, style, anything marked hidden or display:none and presentational div / span wrappers are removed. The HTML is not parsed with the browser DOM: the tokeniser and tree builder are written by hand, so this server and the web page agree exactly, and unclosed elements are closed at the end while stray closing tags are dropped — the counts always come back in notes. An attribute quote that is never closed stops the tag there, because carrying on would swallow the rest of the document as one attribute value. Whatever Markdown cannot express is reported rather than dropped in silence: merged cells (a colspan is padded with empty cells, a rowspan keeps its value on the first row only), a cell holding a list or a code block (that table is left as HTML), definition lists (written in the Markdown Extra : form) and sup / sub / mark / iframe / svg (unknown chooses between keeping them as HTML, reducing them to text and removing them). Tables, task lists and strikethrough are GFM extensions, so gfm=false switches them off. base_url rewrites relative links and images as absolute URLs — the thing that breaks most often in a migration — and main_only keeps only main / article, dropping header, nav, footer, aside and form. It never touches the network; fetching the page is the caller's job | html? or path?, outputPath?, main_only?, base_url?, gfm?, escape?, pad_tables?, headings? (atx default / setext), bullet? (- / * / +), code_block? (fenced / indented), links? (inline / reference / strip), images? (keep / alt / drop), tables? (gfm / html), unknown? (keep / text / drop), br? (spaces / backslash / html / space), lang? (ja default / en) |
wareki_convert | Converts Japanese era dates (Reiwa, Heisei, Showa, Taisho, Meiji) and Western dates both ways and returns the weekday and zodiac — the same logic as the browser tool at tools.first-ch.com/wareki/. For converting and checking the dates in contracts, invoices and official forms. mode="convert" takes date or dates (up to 1000) and detects which calendar each one is in: 2026-10-01, 2026/10/1, 20261001, 2026年10月1日, 令和8年10月1日, R8.10.1, 令和八年十月一日, ㋿8.10.1, Reiwa 8 and so on. A year alone (1989, 昭和64年) or a year and month (2019-05) also works, and both eras are returned when the period spans an era change. formats gives the era date (gannen, with weekday, in kanji numerals), the short forms (R8.10.1 / R08.10.01), Western, ISO 8601 and English. Eras begin on 1912-07-30 (Taisho), 1926-12-25 (Showa), 1989-01-08 (Heisei) and 2019-05-01 (Reiwa). Era dates that never existed (Heisei 32, Heisei 31 May 1, Reiwa 1 April 30) are still converted, and notes gives the correct era date; a written weekday that does not match comes back as WEEKDAY_MISMATCH. Before 1873 (Meiji 5 and earlier) only the year is converted, because dates were lunisolar (LUNAR_CALENDAR). mode="age" takes birth (era dates accepted) and reference (today in Japan time if omitted) and returns the exact age (it goes up on the birthday; people born on February 29 turn older on March 1 in common years), years/months/days, the kazoe age, the next birthday and the entry and graduation years from elementary school to university. mode="eras" returns the era list, plus a year chart of era, zodiac and age when from_year / to_year are given (up to 500 years). Badly written dates come back as ok: false with error.code | mode? (convert / age / eras), date?, dates?, birth?, reference?, from_year?, to_year?, lang? (ja default / en) |
slug_generate | Builds a URL slug for WordPress and other CMSs from a Japanese title — the same logic as the browser tool at tools.first-ch.com/slug/. Kana are written in Hepburn romaji (し→shi, ち→chi, つ→tsu, ふ→fu, じ→ji, しゃ→sha, a small っ doubles the consonant as in kitte / matcha, ん is always n, ファ→fa, ティ→ti, ヴァ→va), symbols are removed and the words are joined with hyphens (or underscores with separator="snake") and lowercased, so the result is only a-z, 0-9 and the separator. Kanji readings are not guessed: pass the reading in kana as reading and the slug is built from it instead of title (spaces in reading become separators: とうきょう で たべた ラーメン 10せん → tokyo-de-tabeta-ramen-10-sen). Kanji left without a reading are skipped and reported in skipped and a KANJI note. Words also split at symbols, kanji, kana ⇄ Latin/digit changes and (with splitKatakana, the default) hiragana ⇄ katakana changes. longVowel="omit" (default) collapses ou/oo/uu and ー (tokyo, ramen; ei and ii stay), "keep" spells them out (toukyou, raamen). The particles は・へ・を come out as spelled (ha, he, o). maxLength cuts at a word boundary. Digits-only slugs (which can clash with date archives so WordPress may append -2) and slugs over 60 characters are flagged. variants gives all four separator × long-vowel spellings, and items builds up to 1000 slugs at once with a DUPLICATE note for collisions | title?, reading?, items? (strings or {title, reading}), separator? (kebab default / snake), longVowel? (omit default / keep), maxLength? (0–200, 0 = no limit), splitKatakana?, lang? (ja default / en) |
htpasswd_generate | Builds htpasswd lines for HTTP Basic authentication — the same logic as the browser tool at tools.first-ch.com/htpasswd/ — for putting a staging site or a pre-delivery preview behind a password. Hashes are bcrypt ($2y$, as Apache's htpasswd -B, cost 5 by default) or apr1-md5 ($apr1$, for Apache 2.2 and other servers that cannot read bcrypt). Pass one user and password, or many as users ("user:pass" strings or {user, password}) or bulk (one user:password per line, # lines and blank lines skipped), up to 200. A user with an empty password gets a random 16-character password, returned in generated (CSPRNG, without look-alike characters such as 0 O o 1 l I); passwords you pass in are not echoed back. User names that contain :, are empty or exceed 255 bytes are skipped, and duplicates, passwords under 12 characters, equal to the user name, with leading/trailing spaces, non-ASCII or over bcrypt's 72-byte limit are flagged in notes. With server (apache / nginx) it also returns the .htaccess or nginx snippet, with file, realm and ips (IPv4 / IPv6 / CIDR let through without a password; paths with spaces are quoted, unreadable addresses are left out and flagged). mode="verify" (or just hash) checks a password against an existing htpasswd line or hash ($2y$ / $2b$ / $2a$, $apr1$, $1$, {SHA}; bcrypt above cost 14 is not computed). outputPath writes the file instead of returning it. Passwords are never logged | user + password, or users[] / bulk; algo? (bcrypt default / apr1), cost? (4–14, default 5), server? (apache / nginx), file?, realm?, ips?, mode? (generate / verify), hash?, outputPath?, lang? (ja default / en) |
openapi_doc_generate | Turns an OpenAPI 3.x / Swagger 2.0 specification (YAML or JSON) into a Markdown API reference in Japanese (or English with lang: "en") and TypeScript type definitions — the same logic as the browser tool at tools.first-ch.com/openapi-doc-gen/. The Markdown lists base URLs, an endpoint index, and per-tag tables of parameters, request bodies and responses plus the schemas, generating example responses from the schema when none are written (examples: false turns this off). The TypeScript output turns components.schemas (Swagger: definitions) into interfaces/types and, with operations (default true), adds Params / RequestBody / Response types per endpoint. Handles $ref, allOf / oneOf / anyOf, enum, nullable and additionalProperties; names that clash with TypeScript keywords or each other are renamed and reported in notes. $refs to other files or URLs are never fetched (typed unknown, flagged OA_EXTERNAL_REF) — pass a bundled single-file spec. Uses the same hand-written YAML/JSON parser as json_to_yaml, except that a folded line starting with - is read as part of the description. Syntax errors come back as ok: false with the code, line, column and an excerpt. outputDir writes <title>.md and <title>.ts instead of returning them. Up to 20 MB | spec or path (absolute), lang? (ja | en), examples?, operations?, output? (both | markdown | typescript), outputDir? |
faq_jsonld_generate | Builds schema.org FAQPage JSON-LD, the same Q&A as Markdown, and paste-ready <details>/<summary> HTML from question/answer pairs (same logic as tools.first-ch.com/faq-jsonld/). Unlike jsonld_generate (type=faqpage), answers are read as light Markdown — blank lines make paragraphs, - / 1. make lists, [text](URL) and bare URLs become links — and a pasted "Q: … / A: …" or "## heading" block is split into pairs. Flags duplicate questions, unsafe (javascript:) and relative links; escapes </script so the JSON-LD block cannot be broken out of | items[] ({q, a}) and/or bulk, title?, level? (2/3), answer_format? (html / text), wrap_script?, exclusive?, lang? |
lines_process | Tidies one-item-per-line text — the same logic as the browser tool at tools.first-ch.com/lines/ — for de-duplicating and sorting lists of keywords, URLs, e-mail addresses or product names, or turning them into a bullet list. The steps always run in this order, once each: trim (leading/trailing half- and full-width spaces, tabs, NBSP) → removeBlank (empty and whitespace-only lines) → dedupe (dedupeMode: first keeps the first copy / unique drops every line that repeats / dupes keeps only lines that repeat, once each; ignoreCase folds case) → sort (asc / desc / reverse / shuffle; asc and desc read numbers as numbers with numeric, default true — item2 < item10, -3 < 1.25 < 10, numeric lines before text — using the Japanese Intl.Collator) → prefix / suffix → number (start, sep: dot / paren / colon / bracket / tab / space, pad for zero padding; outside the prefix). preset (uniqsort = trim + drop blanks + dedupe + ascending, clean, numbered, mdlist = - bullets, shuffle) starts from the same settings as the web version's buttons; individual options override it. shuffle with a seed (0–4294967295) reproduces the same order; without one it uses cryptographic randomness. The line ending of the first line break is kept for every line, and so is a trailing newline. Returns which lines repeat and how often (duplicates), and notes on groups that differ only in surrounding spaces, case or full/half width. Pass text or path; outputPath writes the result to a file | text or path, outputPath?, preset?, trim?, removeBlank?, dedupe?, dedupeMode?, ignoreCase?, sort?, numeric?, seed?, prefix?, suffix?, number?, start?, sep?, pad?, lang? (ja default / en) |
dpi_calc | Print size ⇄ pixels for print-ready artwork (same logic as tools.first-ch.com/dpi/). size: pixels needed for a paper (A0-A6, JIS B1-B6, Japanese business card, postcard, photo L, US Letter… 28 presets, or custom mm) at a dpi (default 350) with bleed (default 3 mm), rounded up, plus uncompressed RGB/CMYK size, an artwork spec memo and notes (too low / no bleed / safe area / PSD 30,000 px limit). reverse: print size and effective dpi of an image on a paper (picks the better orientation) with a verdict and the pixels needed for 350 dpi. table: quick-reference table as text / Markdown / CSV. units: mm / cm / inch / px / pt / Q conversion | mode?, paper? / widthMm+heightMm, dpi?, bleedMm?, width+height (px), format?, value+from?, lang? |
serp_check | Checks whether a page's <title>, meta description and URL fit in a Google search result by pixel width, not character count (same judgement logic as tools.first-ch.com/serp/, without the visual preview). Desktop = 600px column, title 20px × 1 line, description 14px × 2 lines; mobile = 380px, 2 / 3 lines. Wraps CJK per character and Latin per word with simple kinsoku, returns the visible lines, the cut-off part, width vs. limit, grapheme counts, a state (ok / cut / short / empty), checks (empty, truncated, too short, no site-name separator, not https, deep path, percent-encoding), the breadcrumb and paste-ready tags. Widths are an approximation (Arial advance widths + 1em for CJK, no kerning), so they can run a few px wider than a browser measurement | title?, description?, url? or items[] (up to 500), device? (desktop / mobile / both), lang? |
css_layout_generate | Builds copy-ready CSS and HTML for a Flexbox or CSS Grid layout (same logic as tools.first-ch.com/css-layout/). Only non-default declarations are written, repeated tracks fold into repeat(). Validates grid-template-areas (equal cell counts per row, rectangular names, valid names, track-count mismatch) and emits grid-area rules. Notes flag combinations that do nothing (flex-grow vs justify-content, align-content with nowrap, 1fr tracks leaving no free space) and common pitfalls (min-width: auto overflow, 1fr minimum, reverse/dense reading order). Eight presets (holy grail, auto-fit grid, sidebar, header…) | mode? (flex / grid / presets), preset?, flex: direction?, wrap?, justifyContent?, alignItems?…; grid: columns?, rows?, areas?, autoFit?, flow?…; gap?, count?, items?, lang? |
wiki_doc_lint | Audits an exported internal wiki — Markdown, HTML or text exported from Notion, Backlog Wiki, Confluence and the like (same logic as tools.first-ch.com/wiki-doc-lint/). Returns near-duplicate page pairs (duplicates: Jaccard index of four-character fragments of the text with symbols and spaces removed, at or above threshold, with the lines only one side has and a fix that keeps the page with more incoming links, or the longer one on a tie), orphan pages that no other page links to (orphans, with addLine — the link line to add to a Notion-style parent or a contents page; index/README/home pages are not counted unless entryPages=false), inconsistent and outdated terms (terms: a "preferred ← variant" dictionary — your dictionary is added to the default one — plus Japanese katakana words found both with and without a final long-vowel mark, suggesting the majority form; every hit has its line and the line before/after the fix), broken links (broken: relative links with a document extension and [[Page name]] links whose target is not among the files) and a Markdown report of suggested fixes. Links are read from [text](path), [[Page name]] and <a href>, matched by relative path, title, Notion file names with their IDs and the page name at the end of Notion/Confluence URLs. Code blocks, inline code, URLs and tags are skipped for terms, and matches inside a longer word (ユーザ in ユーザビリティ, Javascript in Javascripts) are ignored. Pass paths (absolute files or folders, read recursively; hidden folders and node_modules are skipped) or files. UTF-8, Shift_JIS and UTF-16 (BOM) are detected; up to 3,000 files / 60 MB. Read-only — it never edits the files | paths? or files? ([{ path, text }]), threshold? (0.5–1, default 0.8), dictionary?, useDefaultDictionary?, autoChoon?, entryPages?, format? (both default / json / markdown), lang? (ja default / en) |