洪 民憙 (Hong Minhee) :nonbinary:'s avatar

洪 民憙 (Hong Minhee) :nonbinary:

@hongminhee@hollo.social

1,107 following1,897 followers

An intersectionalist, feminist, and socialist living in Seoul (UTC+09:00). @tokolovesme's spouse. Who's behind @fedify, @hollo, and @botkit. Write some free software in , , , & . They/them.

서울에 사는 交叉女性主義者이자 社會主義者. 金剛兔(@tokolovesme)의 配偶者. @fedify, @hollo, @botkit 메인테이너. , , , 等으로 自由 소프트웨어 만듦.

()

Pinned

@hongminhee@hollo.social

Hello! I'm Hong Minhee (洪 民憙), an open source software engineer in my late 30s, living in Seoul, Korea. I'm bisexual and non-binary (they/them), and an enthusiastic advocate of free/open source software and the fediverse.

I work full-time on @fedify, an ActivityPub server framework in TypeScript, funded by @sovtechfund. I'm also the creator of @hollo, a single-user ActivityPub microblog; @botkit, an ActivityPub bot framework; Hackers' Pub, a fediverse platform for software developers; and LogTape, a logging library for JavaScript and TypeScript.

I have a long interest in East Asian languages (CJK) and Unicode. I post mostly in English here, though occasionally in Japanese or in mixed-script Korean (國漢文混用體), a traditional writing style that interleaves Chinese characters with the native Korean alphabet. Wanting to write in that style was actually one of the reasons I joined the fediverse. Feel free to talk to me in English, Korean, Japanese, or even Literary Chinese!

en.wikipedia.org

Korean mixed script - Wikipedia

Pinned

はじめまして!ソウル在住の30代後半のオープンソースソフトウェアエンジニア、洪 民憙ホン・ミンヒと申します。バイセクシュアル(bisexual)・ノンバイナリー(non-binary)で、自由・オープンソースソフトウェア(F/OSS)とフェディバース(fediverse)の熱烈な支持者です。

STF(@sovtechfund)の支援を受け、TypeScript用ActivityPubサーバーフレームワーク「@fedify」の開発に専念しています。他にも、おひとり様向けのActivityPubマイクロブログ「@hollo」、ActivityPubボットフレームワーク「@botkit」、ソフトウェア開発者向けフェディバースプラットフォームHackers' Pub、JavaScript・TypeScript用ロギングライブラリLogTapeなどの制作者でもあります。

東アジア言語(いわゆるCJK)とUnicodeにも興味があります。このアカウントでは主に英語で投稿していますが、時々日本語や国漢文混用体(漢字ハングル混じり文)の韓国語でも書いています。実はこの文体で書きたくてフェディバースを始めた、という経緯もあります。日本語、英語、韓国語、漢文でも気軽に話しかけてください!

speakerdeck.com

国漢文混用体からHolloまで

本発表では、韓国語の「国漢文混用体」(漢字ハングル混じり文)を自分のフェディバース投稿に実装したいという小さな目標から始まった旅路を共有します。 この目標を達成するために、ActivityPubのJSON-LDの複雑さやHTTP Signatures、WebFingerなどの仕様を理解する必要性に…

Pinned

安寧(안녕)하세요! 저는 서울에 살고 있는 30() 後半(후반)의 오픈 소스 소프트웨어 엔지니어 洪民憙(홍민희)입니다. 兩性愛者(양성애자)(bisexual)이자 논바이너리(non-binary)이며, 自由(자유)·오픈 소스 소프트웨어(F/OSS)와 聯合宇宙(연합우주)(fediverse)의 熱烈(열렬)支持者(지지자)이기도 합니다.

STF(@sovtechfund)의 支援(지원)을 받아 TypeScript() ActivityPub 서버 프레임워크 @fedify 開發(개발)專業(전업)으로 ()하고 있습니다. 그 ()에도 싱글 유저() ActivityPub 마이크로블로그 @hollo, ActivityPub 봇 프레임워크 @botkit, 소프트웨어 開發者(개발자)를 위한 聯合宇宙(연합우주) 플랫폼 Hackers' Pub, JavaScript·TypeScript() 로깅 라이브러리 LogTape ()製作者(제작자)이기도 합니다.

()아시아 言語(언어)(이른바 CJK)와 Unicode에도 關心(관심)이 많습니다. 이 計定(계정)에서는 ()英語(영어)로 포스팅하지만, 때때로 日本語(일본어)國漢文混用體(국한문 혼용체) 韓國語(한국어)로도 씁니다. 聯合宇宙(연합우주)에 오게 된 動機(동기) () 하나가 바로 國漢文混用體(국한문 혼용체)로 글을 쓰고 싶었기 때문이기도 하고요. 韓國語(한국어), 英語(영어), 日本語(일본어), 아니면 漢文(한문)으로도 말을 걸어주세요!

logtape.org

LogTape

Unobtrusive logging library with zero dependencies—library-first design for Deno, Node.js, Bun, browsers, and edge functions

@jimmac@mastodon.social

Kinda dig the cheesy CRT filters in . Drawn in though.

Pixelart variants of the GNOME Circle apps.
ALT text

Pixelart variants of the GNOME Circle apps.

More pixelart variants of the GNOME Circle apps.
ALT text

More pixelart variants of the GNOME Circle apps.

Pixelart variants of the GNOME Core apps. Oops, and some extras.
ALT text

Pixelart variants of the GNOME Core apps. Oops, and some extras.

@deno_land@fosstodon.org
@hongminhee@hackers.pub

타입 검사는 해결책이 아니라 증상이다〉(Type Checking is a Symptom, Not a Solution).

난 이 글에 동의하지 않는데, 여러 측면에서 그렇지만, 한 측면에만 집중해서 얘기해 보자면: 좋은 아키텍처는 훌륭한 프로그래머를 요구하지만 타입 시스템은 훌륭한 프로그래머를 요구하지 않기 때문이다.

누구나 훌륭한 프로그래머가 되어야만 하는가? 혹은 될 수 있는가? 좋은 아키텍처를 그릴 수 있는 훌륭한 프로그래머가 아니라면 소프트웨어 개발을 해서는 안 될까? 좋은 아키텍처에만 의존하는 것은 잠재적으로 엘리트주의를 끌어들이기 쉽다: 「어떤 시스템이 오작동하는 것은 아키텍처가 나쁘기 때문이다. 아키텍처가 나쁜 이유는 그걸 설계한 프로그래머가 수준 미달이기 때문이다」와 같이.

반면 타입 시스템은 일단 도입만 하면 누구나 그 덕을 볼 수 있다. 팀 내의 프로그래머들의 역량이 뛰어나든 뛰어나지 않든. 훨씬 평범한 보통 사람에게 유리하다. 타입 시스템이 미봉책일 수는 있지만, 그 미봉책이 더 많은 사람들을 프로젝트에 참여할 수 있게 해준다고 생각한다.

programmingsimplicity.substack.com

Type Checking is a Symptom, Not a Solution

2025-08-31

@kodingwarrior@hackers.pub

벌써 이번주 일요일이 해커스펍 오프라인 모임이라니... 시간 진짜 빠르다.....

hackers.pub

✨ Hackers' Public 첫 오프라인 모임! ✨ Hackers' Pub 사용자들의 자발적인 모임, Hackers' Public이📅 9월 14일(일) 오후 3시 ~ 6시 열립니다. 이번 모임에서는 많은 분들이 흥미로워할 두 가지 발표가 준비되어 있습니다: 🎨 Code As a Canvas: 코드에서 예술작품이 되기까지 ✍️ 폰트는 어떻게 만들어지는가 – Neo둥근모 개발 후일담 또한 자유롭게 교류할 수 있는 네트워킹 시간도 마련되어 있으니 많은 관심 부탁드립니다 🙌 현재는 2차 모집 단계이며,👉 신청은 포스터의 QR코드 또는 http://public.hackers.pub에서 가능합니다. (두 경로 모두 동일한 이벤트 페이지로 연결됩니다) 2차 모집 기간은 9월 7일까지이며, 완판이 되었더라도 참가자 신청 대기하신 분 중에서 두분 정도 선정할 예정입니다!

✨ Hackers' Public 첫 오프라인 모임! ✨ Hackers' Pub 사용자들의 자발적인 모임, Hackers' Public이📅 9월 14일(일) 오후 3시 ~ 6시 열립니다. 이번 모임에서는 많은 분들이 흥미로워할 두 가지 발표가 준비되어 있습니다: 🎨 Code As a Canvas: 코드에서 예술작품이 되기까지 ✍️ 폰트는 어떻게 만들어지는가 – Neo둥근모 개발 후일담 또한 자유롭게 교류할 수 있는 네트워킹 시간도 마련되어 있으니 많은 관심 부탁드립니다 🙌 현재는 2차 모집 단계이며,👉 신청은 포스터의 QR코드 또는 http://public.hackers.pub에서 가능합니다. (두 경로 모두 동일한 이벤트 페이지로 연결됩니다) 2차 모집 기간은 9월 7일까지이며, 완판이 되었더라도 참가자 신청 대기하신 분 중에서 두분 정도 선정할 예정입니다!

@kodingwarrior@hackers.pub

✨ Hackers' Public 첫 오프라인 모임! ✨

Hackers' Pub 사용자들의 자발적인 모임, Hackers' Public이 📅 9월 14일(일) 오후 3시 ~ 6시 열립니다.

이번 모임에서는 많은 분들이 흥미로워할 두 가지 발표가 준비되어 있습니다:

  • 🎨 Code As a Canvas: 코드에서 예술작품이 되기까지
  • ✍️ 폰트는 어떻게 만들어지는가 – Neo둥근모 개발 후일담

또한 자유롭게 교류할 수 있는 네트워킹 시간도 마련되어 있으니 많은 관심 부탁드립니다 🙌

현재는 2차 모집 단계이며, 👉 신청은 포스터의 QR코드 또는 http://public.hackers.pub 에서 가능합니다. (두 경로 모두 동일한 이벤트 페이지로 연결됩니다)

2차 모집 기간은 9월 7일까지이며, 완판이 되었더라도 참가자 신청 대기하신 분 중에서 두분 정도 선정할 예정입니다!

Hackers Public 1회차 모임 포스터
ALT text

Hackers Public 1회차 모임 포스터

We're excited to share an update on 's development! While we're actively working on Fedify 1.9 in the main branch, we've also begun preparations for Fedify 2.0 in the next branch.

Before you get too excited about revolutionary new features, we want to set clear expectations: Fedify 2.0 will primarily focus on cleaning up technical debt that we couldn't address due to backward compatibility constraints. This means removing deprecated APIs and making breaking changes that will ultimately result in a cleaner, more maintainable codebase. Think of it as a major housekeeping release—necessary work that will make Fedify better in the long run.

Some of the planned improvements include adding readonly modifiers throughout our types and interfaces to better enforce our immutability-by-default principle, implementing our own RFC 6570 URI Template library for symmetric expansion and pattern matching, and various CLI tool migrations to our new Optique framework for better cross-runtime support. While the majority of changes will be about refinement rather than revolution, these updates will strengthen Fedify's foundation and improve interoperability across the . You can track all planned changes in detail by checking out the Fedify 2.0 milestone on our GitHub repository.

github.com

fedify-dev/fedify

ActivityPub server framework in TypeScript. Contribute to fedify-dev/fedify development by creating an account on GitHub.

@kodingwarrior@silicon.moe

여러분(0명) 트위터 요즘 꼬라지가 말이 아닌데, 원래 있던 트친들 연합우주로 데려오기 좋은 명분이 생겼읍니다,, 힘을 보태주세요잇!!

@cheeaun@mastodon.social · Reply to Renaud Chaput

@renchap @chaosexanima I'm still a huge proponent of using native OS emoji pickers 😬

I respect all the hard work, but this is an issue for *every* web site and browser/standards folks seem like not prioritizing this with <input type="emoji"> or some JS method to invoke OS emoji pickers 🤷‍♂️

@kodingwarrior@hackers.pub

트잇타 사담계도 터지고, 뭔 별의별 되도않는 싸불이 너무 당연하다는듯이 일어나버리니까 이렇게 된 이상 연합우주로 트친들을 전부 데려와야겠다는 사명감이 생겼다(?)

@cloudflare@noc.social

We've implemented the node:http client and server APIs in Cloudflare Workers, allowing developers to migrate existing Node.js applications with minimal code changes. This post explains how we built a bridge between the Workers serverless environment and Node.js's traditional HTTP model, complete with examples for running frameworks like Express.js at the edge. blog.cloudflare.com/bringing-n

blog.cloudflare.com

Bringing Node.js HTTP servers to Cloudflare Workers

We've implemented the node:http client and server APIs in Cloudflare Workers, allowing developers to migrate existing Node.js applications with minimal code changes. This post explains how we built a bridge between the Workers serverless environment and Node.js's traditional HTTP model, complete with examples for running frameworks like Express.js at the edge.

@tertle950@app.wafrn.net
We assume that you have experience in creating web applications using HTML and HTTP, and that you understand command-line interfaces, SQL, JSON, and basic JavaScript. However, you don't need to know TypeScript, JSX, ActivityPub, or Fedify—we'll teach you what you need to know about these as we go along.
(That last sentence is highlighted for importance.)
ALT text

We assume that you have experience in creating web applications using HTML and HTTP, and that you understand command-line interfaces, SQL, JSON, and basic JavaScript. However, you don't need to know TypeScript, JSX, ActivityPub, or Fedify—we'll teach you what you need to know about these as we go along. (That last sentence is highlighted for importance.)

@box464@mastodon.social

Finished the basic tutorial for - I can now Follow and Unfollow the "me" account. Lots of useful debugging and dev tools built in, too.

I think what's most interesting about this framework is that there are quite a few AP vocabulary activitites available to you above and beyond the Mastodon mainstream.

Would love to tinker around with Offer, Reject, Listen, Question, Read

fedify.dev/tutorial/basics

A screenshot of some basic code from a tutorial that was following to create a basic AP server.
ALT text

A screenshot of some basic code from a tutorial that was following to create a basic AP server.

@robin_maki@planet.moe

연합우주라 우주 관련된 단어가 많이 쓰이는게 좋다

@hongminhee@hollo.social

最近(최근) 한창 開發中(개발중)인 Fedify 基盤(기반) ActivityPub 서비스 2():

  • Kosmo: @robin_maki 님이 만들고 계시는 次世代(차세대) ActivityPub 基盤(기반) SNS
  • Cosmoslide: @kodingwarrior 님이 만들고 계시는 ActivityPub 基盤(기반) 슬라이드 共有(공유) 서비스

完全(완전) 期待中(기대중)!!

github.com

GitHub - cosmoslide/cosmoslide

Contribute to cosmoslide/cosmoslide development by creating an account on GitHub.

@hongminhee@hollo.social

袁枚(원매)의 《隨園食單(수원식단)》을 ()硏究(연구)申桂淑(신계숙) 敎授(교수)直接(직접) 運營(운영)하는 淸代(청대) 中華料理(중화요리) 專門店(전문점) 桂香閣(계향각)에 다녀왔다. (寫眞(사진)肉食(육식) 包含(포함).)

辣子雞
ALT text

辣子雞

開陽白菜
ALT text

開陽白菜

麻辣黃瓜
ALT text

麻辣黃瓜

東坡肉
ALT text

東坡肉

三鮮鍋巴
ALT text

三鮮鍋巴

紅燒海蔘肘子
ALT text

紅燒海蔘肘子

涼拌藕片
ALT text

涼拌藕片

蒜蓉粉絲蒸蝦
ALT text

蒜蓉粉絲蒸蝦

@kodingwarrior@hackers.pub · Reply to Jaeyeol Lee

N줄 요약

  • 연합우주 소프트웨어 개밥먹기하면서 개발중
  • 로컬호스트에서 돌리고 있고, tailscale로 터널링 중이며 몇몇 인스턴스 계정에 팔로워로 들어가있음.
  • 몇몇 인스턴스에서는 아마 위의 사유로 계속 retry 되고 있는 Activity가 있을 것인데, 해당 인스턴스 모더레이터분들 괜찮으신지?
@kodingwarrior@hackers.pub

요즘 (https://github.com/cosmoslide/cosmoslide) 개발하면서 들고 있는 생각....


대부분의 액티비티펍 소프트웨어 인스턴스는 멀쩡하게 365일 24시간 동일한 위치에서 운영이 되고 있다고 가정이 된다. 내가 글을 올리면, 나를 팔로우 중인 모든 사람들의 inbox에 내가 글을 올렸다(Create(Note))는 Activity가 전달이 되는데, 각자가 운영되고 있는 서버 인스턴스가 멀쩡히 살아있다면.... 딱히 문제가 되지는 않는다.

문제는, 게시글을 작성하는 시점에 팔로워 중 누군가의 인스턴스가 죽어있을때도 있다는 점이다. 그런 경우를 대비해서 exponential backoff를 쓰든 아무튼 fallback 알고리즘이 동작하긴 하는데, 서버가 살아나면 당연히 전달이야 잘 되긴 한다. 그런데, Activity 전달이 실패하는 일이 잦으면 어떤 액티비티펍 소프트웨어를 쓰던간에 retry를 하기 위해서 계속해서 Queue에 쌓이고, 최종적으로는 Queue에 쌓인 것 때문에 적지 않은 오버헤드가 있을 것 같은데 모더레이터의 입장에선 어느 정도까지 감안할 수 있는가? 라는 생각이 문득 들었다.

사실 내가 왜 이런 글을 쓰고 있냐면, 위에서도 언급했다시피, 로컬호스트에서 실제로 서비스를 (맥북이 켜져있을때만) 서빙하고 있고 그걸 Tailscale로 연결해서 터널링을 하고 있다. 즉, 맥북을 켜놓고 있으면 Create(Note) Activity가 정상적으로 잘 전달되고, 맥북이 꺼져있으면 Activity 전달이 안되고 있다. 실제로, 이런 맥락에서 지금 테스트 중인 두 개의 인스턴스가 있다. 이런 실험적인 시도를 하면서 이래도 되는게 맞나 싶은 생각도 들고는 있다. 맥북을 켜놓으면, retry되고 있는 것도 다 consume되긴 하겠지만.... 찝찝하긴 찝찝하다.

개발하는 입장이라고 선해를 할 수는 있어도, 비뚤어진 관점에서 해석하면 누군가는 어뷰징의 관점으로 해석할 수 있는 가능성이 적지는 않다고 생각하고 있다. 이런 경우엔 모더레이터되는 분들한테, 내가 이런 tailscale 도메인으로 서빙하고 있다고 통지라도 하는게 나으려나... 아니면, 내가 구매해놓은 도메인을 tailscale 도메인으로 CNAME 걸어놓고 "이런 도메인으로 서비스 걸어놓을 예정이니까 이 도메인만은 제발 차단하지 말아주십쇼 헤헤" 라고 해야하나... 아예 서버를 만드는거다보니까 이런 고려사항이 생기는 것 같다.


근데, 한 편으로는 이런 생각이 든다. 물리적인 서버의 위치를 옮길 가능성이 많은 환경(예를 들면, 전시 상황)이면 어떡하지? ActivityPub이 사실은 분산된 웹 환경을 위해 나온 프로토콜이긴 하지만, 분산된 웹 환경이라는게 물리적으로 각자 다른 위치에 오랫동안 배치가 되어 있는 서버 뿐만이 아니라 위치가 자주 바뀔 수 있는 서버도 연합의 대상으로 포함이 될 수 있다면? 어떤 공상과학 영화(ex. 터미네이터4)들을 보면, 저항군이 독자적인 라디오 기지국 같은거 만들고 위치도 매번 다른 곳으로 옮기고 주파수를 매번 다르게 설정하면서 소식전달하는 모습을 볼 수 있는데, 액티비티펍도 어떻게 보면 그걸 고려한 설계도 포함될 수 있지 않나... 그런 생각도 든다..

github.com

GitHub - cosmoslide/cosmoslide

Contribute to cosmoslide/cosmoslide development by creating an account on GitHub.

@robin_maki@planet.moe

클러스터화는 단점이기도 하지만 장점이기도 한게 제가 생각하기에 다른 커뮤니티 서비스랑 트위터류의 가장 큰 차이점은 적당한? 느슨한? 클러스터화라고 생각하거든요 트위터만 봐도 인플루언서-like하게 사용하는 사람도 자기들끼리 노는 사람도 있는데 되게 그런 사람들간에 사용 형태와 목적이 판이하게 다를탠데 동시에 같은 트위터에 존재하면서도 디스코드같은 아예 클러스터가 완전히 분리된 동네하고는 다르게 그 클러스터들 사이에도 느슨한 연결고리가 존재한다는게 매력이라고 생각해요

@robin_maki@planet.moe

초보자에게 서버를 골라야 하는 큰 장벽이 없으면서 서버는 기술적 지식 없이 만들 수 있으면 좋겠다...

@lobsters@mastodon.social
@hongminhee@hollo.social

Every CLI tool has the same validation code hidden somewhere:

  • “option A requires option B”
  • “can't use X and Y together”
  • “this only works in production mode”

I got tired of writing it. So I built something that makes it unnecessary.

https://hackers.pub/@hongminhee/2025/stop-writing-cli-validation-parse-it-right-the-first-time

hackers.pub

Stop writing CLI validation. Parse it right the first time.

This post introduces Optique, a new library created to address the pervasive problem of repetitive and often messy validation code in CLI tools. The author was motivated by the observation that nearly every CLI tool reinvents the wheel with similar validation patterns for dependent options, mutually exclusive options, and environment-specific requirements. Optique leverages parser combinators and TypeScript's type inference to ensure that CLI arguments are parsed directly into valid configurations, eliminating the need for manual validation. By describing the desired CLI configuration with Optique, TypeScript automatically infers the types and constraints, catching potential bugs at compile time. The author shares their experience of deleting large chunks of validation code and simplifying refactoring tasks. Optique aims to provide a more robust and maintainable approach to CLI argument parsing, potentially saving developers from writing the same validation logic repeatedly.

@hongminhee@hackers.pub

I have this bad habit. When something annoys me enough times, I end up building a library for it. This time, it was CLI validation code.

See, I spend a lot of time reading other people's code. Open source projects, work stuff, random GitHub repos I stumble upon at 2 AM. And I kept noticing this thing: every CLI tool has the same ugly validation code tucked away somewhere. You know the kind:

if (!opts.server && opts.port) {
  throw new Error("--port requires --server flag");
}

if (opts.server && !opts.port) {
  opts.port = 3000; // default port
}

// wait, what if they pass --port without a value?
// what if the port is out of range?
// what if...

It's not even that this code is hard to write. It's that it's everywhere. Every project. Every CLI tool. The same patterns, slightly different flavors. Options that depend on other options. Flags that can't be used together. Arguments that only make sense in certain modes.

And here's what really got me: we solved this problem years ago for other types of data. Just… not for CLIs.

The problem with validation

There's this blog post that completely changed how I think about parsing. It's called Parse, don't validate by Alexis King. The gist? Don't parse data into a loose type and then check if it's valid. Parse it directly into a type that can only be valid.

Think about it. When you get JSON from an API, you don't just parse it as any and then write a bunch of if-statements. You use something like Zod to parse it directly into the shape you want. Invalid data? The parser rejects it. Done.

But with CLIs? We parse arguments into some bag of properties and then spend the next 100 lines checking if that bag makes sense. It's backwards.

So yeah, I built Optique. Not because the world desperately needed another CLI parser (it didn't), but because I was tired of seeing—and writing—the same validation code everywhere.

Three patterns I was sick of validating

Dependent options

This one's everywhere. You have an option that only makes sense when another option is enabled.

The old way? Parse everything, then check:

const opts = parseArgs(process.argv);
if (!opts.server && opts.port) {
  throw new Error("--port requires --server");
}
if (opts.server && !opts.port) {
  opts.port = 3000;
}
// More validation probably lurking elsewhere...

With Optique, you just describe what you want:

const config = withDefault(
  object({
    server: flag("--server"),
    port: option("--port", integer()),
    workers: option("--workers", integer())
  }),
  { server: false }
);

Here's what TypeScript infers for config's type:

type Config = 
  | { readonly server: false }
  | { readonly server: true; readonly port: number; readonly workers: number }

The type system now understands that when server is false, port literally doesn't exist. Not undefined, not null—it's not there. Try to access it and TypeScript yells at you. No runtime validation needed.

Mutually exclusive options

Another classic. Pick one output format: JSON, YAML, or XML. But definitely not two.

I used to write this mess:

if ((opts.json ? 1 : 0) + (opts.yaml ? 1 : 0) + (opts.xml ? 1 : 0) > 1) {
  throw new Error('Choose only one output format');
}

(Don't judge me, you've written something similar.)

Now?

const format = or(
  map(option("--json"), () => "json" as const),
  map(option("--yaml"), () => "yaml" as const),
  map(option("--xml"), () => "xml" as const)
);

The or() combinator means exactly one succeeds. The result is just "json" | "yaml" | "xml". A single string. Not three booleans to juggle.

Environment-specific requirements

Production needs auth. Development needs debug flags. Docker needs different options than local. You know the drill.

Instead of a validation maze, you just describe each environment:

const envConfig = or(
  object({
    env: constant("prod"),
    auth: option("--auth", string()),      // Required in prod
    ssl: option("--ssl"),
    monitoring: option("--monitoring", url())
  }),
  object({
    env: constant("dev"),
    debug: optional(option("--debug")),    // Optional in dev
    verbose: option("--verbose")
  })
);

No auth in production? Parser fails immediately. Trying to access --auth in dev mode? TypeScript won't let you—the field doesn't exist on that type.

“But parser combinators though…”

I know, I know. “Parser combinators” sounds like something you'd need a CS degree to understand.

Here's the thing: I don't have a CS degree. Actually, I don't have any degree. But I've been using parser combinators for years because they're actually… not that hard? It's just that the name makes them sound way scarier than they are.

I'd been using them for other stuff—parsing config files, DSLs, whatever. But somehow it never clicked that you could use them for CLI parsing until I saw Haskell's optparse-applicative. That was a real “wait, of course” moment. Like, why are we doing this any other way?

Turns out it's stupidly simple. A parser is just a function. Combinators are just functions that take parsers and return new parsers. That's it.

// This is a parser
const port = option("--port", integer());

// This is also a parser (made from smaller parsers)
const server = object({
  port: port,
  host: option("--host", string())
});

// Still a parser (parsers all the way down)
const config = or(server, client);

No monads. No category theory. Just functions. Boring, beautiful functions.

TypeScript does the heavy lifting

Here's the thing that still feels like cheating: I don't write types for my CLI configs anymore. TypeScript just… figures it out.

const cli = or(
  command("deploy", object({
    action: constant("deploy"),
    environment: argument(string()),
    replicas: option("--replicas", integer())
  })),
  command("rollback", object({
    action: constant("rollback"),
    version: argument(string()),
    force: option("--force")
  }))
);

// TypeScript infers this type automatically:
type Cli = 
  | { 
      readonly action: "deploy"
      readonly environment: string
      readonly replicas: number
    }
  | { 
      readonly action: "rollback"
      readonly version: string
      readonly force: boolean
    }

TypeScript knows that if action is "deploy", then environment exists but version doesn't. It knows replicas is a number. It knows force is a boolean. I didn't tell it any of this.

This isn't just about nice autocomplete (though yeah, the autocomplete is great). It's about catching bugs before they happen. Forget to handle a new option somewhere? Code won't compile.

What actually changed for me

I've been dogfooding this for a few weeks. Some real talk:

I delete code now. Not refactor. Delete. That validation logic that used to be 30% of my CLI code? Gone. It feels weird every time.

Refactoring isn't scary. Want to know something that usually terrifies me? Changing how a CLI takes its arguments. Like going from --input file.txt to just file.txt as a positional argument. With traditional parsers, you're hunting down validation logic everywhere. With this? You change the parser definition, TypeScript immediately shows you every place that breaks, you fix them, done. What used to be an hour of “did I catch everything?” is now “fix the red squiggles and move on.”

My CLIs got fancier. When adding complex option relationships doesn't mean writing complex validation, you just… add them. Mutually exclusive groups? Sure. Context-dependent options? Why not. The parser handles it.

The reusability is real too:

const networkOptions = object({
  host: option("--host", string()),
  port: option("--port", integer())
});

// Reuse everywhere, compose differently
const devServer = merge(networkOptions, debugOptions);
const prodServer = merge(networkOptions, authOptions);
const testServer = merge(networkOptions, mockOptions);

But honestly? The biggest change is trust. If it compiles, the CLI logic works. Not “probably works” or “works unless someone passes weird arguments.” It just works.

Should you care?

If you're writing a 10-line script that takes one argument, you don't need this. process.argv[2] and call it a day.

But if you've ever:

  • Had validation logic get out of sync with your actual options
  • Discovered in production that certain option combinations explode
  • Spent an afternoon tracking down why --verbose breaks when used with --json
  • Written the same “option A requires option B” check for the fifth time

Then yeah, maybe you're tired of this stuff too.

Fair warning: Optique is young. I'm still figuring things out, the API might shift a bit. But the core idea—parse, don't validate—that's solid. And I haven't written validation code in months.

Still feels weird. Good weird.

Try it or don't

If this resonates:

I'm not saying Optique is the answer to all CLI problems. I'm just saying I was tired of writing the same validation code everywhere, so I built something that makes it unnecessary.

Take it or leave it. But that validation code you're about to write? You probably don't need it.

github.com

GitHub - dahlia/optique: Type-safe combinatorial CLI parser for TypeScript

Type-safe combinatorial CLI parser for TypeScript. Contribute to dahlia/optique development by creating an account on GitHub.

@faithisleaping@anarres.family

The concept of the "male socialization" of trans women is deeply problematic. Most of the time when people use that phrase, it's to degender and degrade trans women and make them out to be less women than their cis counterparts. When people go down that path of reasoning, it's usually toxic and leads to a lot of wrong conclusions.

We often never got the kind of male privilege that people assume we did because we looked like dudes. The reality is that bullies often pick up on our gender differences even if we don't and it can get dark. We also didn't have boyhoods. Just because our parents dressed us in blue and bought us baseball hats doesn't mean we have the same relationship to those things that cis guys do. Often, it's some weird, fucked up mix.

But...

We also didn't have "normal" girlhoods, either, and this can leave a huge hole.

One of the things I'm constantly self-conscious about when trying to relate to cis women is that there's this vast swath of girlhood experiences that they just take for granted and assume every woman has.

I don't.

Now, yes, those assumptions are generally rubbish because they're heavily dependant on culture, race, and class. But even then, there are still common threads. I'm missing all the threads.

When talking to other women, I rely a lot on the experiences I've had over just the last 3 years as well as things I've read and things I watched my sisters go through. I have a few distinctly girl childhood experiences I can draw on as well but not many. The whole time I worry that I'm going to slip up and say the wrong thing. Or maybe they'll say something that assumes a shared experience and I'll just sit there dumbfounded because I know that's a common girl experience but I've got no clue.

Sometimes those interactions can be weirdly affirming because you know they see you as a girl subconsciously when they start making those assumptions rather than assuming "male socialization" (🤮) but it can still leave you dumbfounded and speechless.

The only thing I can say is that it does seem to be getting better with time, both as I form new authentically feminine experiences as an adult and as I grow in confidence. But still it's hard and I feel that hole in basically every relationship I have with a cis woman. 😢