모든 모델은 다른 라이브러리 세트를 추구했다, 그래서 하나의 베이스를 만들고 모든 프로젝트를 그 안으로 업스트림으로 만들었다. 그때 나는 클로드 파블에게 CLI 인간이 십년 뒤에도 여전히 사용할 수 있을 것이라고 물었고, 오푸스 5와 나는 돌아온 것을 만들었다 — 46개의 조항, 92개의 테스트, zero 의존성.
Developed by Robert E. Beckner III (Merlin) | rbeckner.com
나는 이 머신에 51 명령줄 도구가 있다. 나는 그 숫자를 계획하지 않았다. 그것은 CLI가 아이디어와 내가 실제로 실행할 수 있는 것 사이의 가장 짧은 거리가 되기 때문에 발생했고, 지난 몇 년 동안 나는 혼자보다 더 빨리 쓰는 데 도움을 받았기 때문이다.
그 도움은 내가 일찍부터 관찰한 습관과 함께 왔으며, GPT-3.5와 첫 번째 Claude 모델이 내가 작업한 모델이었을 때였다. 3개의 다른 모델에게 CLI를 스캐폴드하도록 요청하면, CLI가 무엇인지에 대한 3가지 다른 의견을 얻는다. 하나는 Commander를 선택한다. 하나는 프롬프트를 위해 Inquirer를 선택한다. 하나는 출력이 색칠되어야 하기 때문에 Chalk를 선택한다. 각 답변은 방어 가능하다. 함께 그들은 세금이 된다, 왜냐하면 이제 나는 3 코드베이스를 소유하고 있어, 인수 파싱, 실패가 어떻게 보이는지, 그리고 내가 지금 감시할 책임이 있는 라이브러리 중 어느 것인지에 대해 서로 다르기 때문이다.
각 모델은 다른 라이브러리 세트를 선택했고, 나는 그 모든 것을 살아야 했던 사람이었습니다#
세금은 라이브러리가 아닙니다. 개선이 이동을 멈추는 것입니다.
3 CLI가 명령이 실패를 보고하는 방식에 대해 의견이 다르면, 하나의 수정은 하나의 수정입니다. 그것을 업스트림에 넣을 것이 없습니다. 작업은 복리되지 않으며, 열 번째 도구 이후에는 레버리지를 구축하는 것이 아니라 근접 실패 포트폴리오를 유지하는 것입니다.
나는 AI 기능, 워크플로우, 운영을 위해 그 층을 만들었습니다. 내가 실제로 가장 자주 만드는 것에 대해 만들지는 않았습니다.
그래서 나는 하나의 베이스를 만들고 모든 CLI 프로젝트를 그 베이스에 개선 사항을 업스트림으로 만들었습니다#
규칙은 단순했고 내가 강제할 수 있는 것이었습니다: 내 재산에 있는 CLI이 더 나은 것이 필요할 때 — 서비스 등록을 더 깔끔하게 하는 방법, 더 나은 오류 경로, 테스트를 읽기 쉽게 만드는 헬퍼 — 그 개선은 프로젝트에 남지 않았습니다. 그것은 베이스에 들어갔고, 베이스는 다른 사람들에게 나갔습니다.
이것이 전체 설계입니다. 베이스는 의도적으로 작습니다. 당신의 도구가 무엇을 하는지에 대해 의견이 없습니다. 명령이 무엇인지에 대해 강한 의견을 가지고 있습니다: 인수를 받아들이고, 작업을 수행하며, 발생한 일을 보고하고, 끝납니다.
폴더는 2025년 7월 6일에 생성되었으며, 내 도구의 2는 버전 1.0에 의존했습니다. 0 같은 날. 이것이 증거입니다: 그것은 투기적으로 구축되지 않았고, 그 다음에 채택되었습니다. 이미 존재하던 작업에서 추출되었으며, 프로젝트 간에 같은 스캐폴딩을 복사하는 것이 합리적이지 않게 된 시점에서였습니다.
빠르게 퍼졌습니다, 왜냐하면 퍼지는 것이 전체 아이디어였기 때문입니다. 8 저장소는 25 일 이내에 그것에 있었습니다. 10는 11 주 이내에 있었습니다.
Repositories Adopting the Base in 2025Chart data
repositories
Jul 6
2
Jul 8
4
Jul 17
5
Jul 23
7
Jul 30
8
Sep 20
10
Git은 그 모든 것보다 나중에 등장했습니다. 저장소는 2025년 11월 12일에 초기화되었고, 4 개월이 지나고 다음 날에 게시되었습니다 — 그래서 버전 기록과 실제 기록이 불일치하고, 내가 이것을 쓰기 위해 앉았을 때 커밋 로그를 신뢰하기보다 파일 시스템을 확인한 이유입니다.
그 10 도구들은 Cloudflare 관리를 수행합니다. 로컬 DNS 및 nginx 관리. Coolify에 대한 배포. 브라우저 자동화. 모델 제공업체 전반에 걸친 비용 보고. 대부분은 비공개이므로 이름이 아니라 그들이 하는 일을 설명합니다. 공개된 것들은 aia, 여러 모델을 병렬로 조회하고 기본 자체입니다. vssh, 나의 보호된 원격 실행 도구는 공개적이며 같은 본능에서 나왔습니다 — 운영자 표면을 한 번, 제대로 구축하고 재구축을 멈춥니다.
배당금은 실제였고 지루했으며, 이는 인프라 배당금의 올바른 형태입니다. 한 도구에서의 강화가 모두에 나타났습니다. 명령이 빨간 오류 메시지를 출력하고 0로 종료될 수 있다는 것을 발견했을 때 — 인간에게는 실패를 알리고 셸에게는 성공을 알리는 — 수정은 한 도구에서 발생한 16곳에 적용되지 않았습니다. 그것은 기본에 적용되었고 모든 도구가 상속받았습니다.
도착한 것은 기능 목록이 아니었다. 그것은 조약으로 구조화되었으며, 한쪽은 단단한 벽으로 나뉘었다.
하나의 절반은 계약이었습니다: 이 기반 위에 구축된 모든 CLI가 모든 관찰자에게 보장하는 것, RFC-2119 언어로 번호가 매겨진 조항으로 작성되었습니다 — 반드시, 반드시 안 함, 할 수 있음, 가능. 그들 중 열두 개의 가문이 있습니다. 종료 코드. 스트림 규율. 기계 출력. Self-description. Grammar. Environment. Cancellation. Determinism. 성능 예산. Compatibility.
다른 절반은 작성 표면이었으며, 이는 성장하도록 허용되었고, 계약을 만족시키는 것이 가장 저항이 적은 경로가 되도록 존재했습니다.
그 아래의 추론은 내가 설득력 있다고 느낀 부분이었습니다. 십년을 지속하도록 설계된 디자인은 패션에 베팅할 수 없습니다. 왜냐하면 패션은 소멸되는 것입니다. 그것은 영리함에 베팅할 수 없습니다, 왜냐하면 영리함은 당신이 예측할 수 없는 것입니다, 8년. 그것은 오직 1970가 이동하지 않은 인터페이스에만 베팅할 수 있습니다: 인수 벡터, 3 스트림, 8-비트 종료 코드, 환경 변수. 그리고 그것은 진정으로 새로운 사실을 언급했습니다 — 그 인터페이스의 대부분 독자는 이제 후속 질문을 할 수 없는 기계라는 점.
모든 다른 것을 정리한 조항은 그가 열었던 조항이었습니다:
하나의 결과, 여러 렌더링. 명령은 단일 결과를 계산합니다. 종료
코드, 인간 텍스트, JSON 문서 및 스트리밍 라인은 모두
그 하나의 값의 투영입니다. 그들은 서로 모순될 수 없습니다, 왜냐하면
소스가 하나뿐이기 때문입니다.
그것이 전체 재구성을 끌어당기는 문장입니다.
Diagram source
graph LR
A["execute()는
하나의 값을 반환합니다"] --> B["종료 코드"]
A --> C["렌더링된 텍스트
stdout"]
A --> D["JSON 봉투
--json"]
A --> E["NDJSON 스트림
--ndjson"]
F["logger.error()
ctx.emit()"] -.-> B
F -.-> G["이벤트
stderr"]
나는 사양을 Opus 5에 가져가서 하루 만에 구축했다. 깨끗한 날은 아니었다. 유용한 부분은 문서가 부동산에 부딪혀 잃어버린 곳이다.
스펙은 ctx.args를 명명된 인수의 기록으로 만들고 싶었습니다. 그것은 the
더 나은 설계는 고립된 상태에서. 우리는 배열을 유지했다
10개의 도구 중에서, 그들은 모두 ctx.args를 배열로 읽습니다. 결정한 규칙은
그리고 입력된 인수를 ctx.namedArgs 옆에 넣으세요. 결정한 규칙
이미 계약에 작성되어 있었으며, 한 조항 위에: 절대로 깨지 말아야 한다
소비자는 저장소의 다른 모든 가치보다 우선하며, 계약의
자체 완전성.
사양은 동사 없이 명령 그룹을 사용 오류로 간주했다. 실행 시
하위 명령이 없는 상위 명령은 2 종료된다. 방어 가능하며,
단독 그룹 명령을 실행하는 모든 스크립트의 동작을 바꾸었을 것이다
그것의 도움. 우리는 계속해서 도움말을 출력하고 종료했습니다 0.
일정 메모리로 백만 항목을 스트리밍하는 것은
하나의 포인트이며 다른 경우에는 불가능하다, 왜냐하면. 일정한 메모리에서 백만 개의 항목을 스트리밍하는 것은
우리는 동작을 분리하고
어느 조항이 어느 것을 관리하는지 적었다. 또한 사양이 알 수 없었던 것들을 발견했다, 왜냐하면
그것들은 아티팩트에서만 보였기 때문이다.
우리는 또한 사양이 알 수 없었던 것들을 발견했습니다. 그들은 오직 아티팩트에서만 보였기 때문입니다. 테스트 파일은 0 테스트를 실행하고 성공을 보고했으며, 런너를 중간에 종료했습니다. Ctrl-C에서 종료된 0 신호 처리 — 성공했다고 보고된 중단된 명령. 한 번 의존성을 교체한 후 조용히 자신의 타입 서명을 좁히고, 한 글자도 바뀌지 않은 코드를 깨뜨린 색상 헬퍼였다. 그 마지막 것은 함께 앉아야 할 가치가 있다.
우리 둘 다 작성한 테스트에서도 잡히지 않았다. 마이그레이션 중에 소비자의 타입체크에서 나타났으며, 그곳이 유일한 장소였다. 마이그레이션 중에 소비자의 타입체크에서 나타났으며, 그것이 있을 수 있는 유일한 장소입니다.
따라서 호환성 스위트는 계약 파일을 파싱하고 MUST라는 단어를 포함하는 모든 조항을 찾아 그 중 하나라도 등록된 테스트가 없으면 빌드를 실패시킨다. 이 프로젝트에 약속을 추가하려면 같은 커밋에 그것을 증명하는 것을 추가해야 한다.
Conformance Tests by Contract FamilyChart data
Value
Grammar
20
Exit codes (truth)
12
Machine output
11
Self-description
10
Environment
8
Prompt safety
6
Streams
5
Cancellation
5
Determinism
5
46 규범 조항. 92 그에 매핑된 테스트. 184 총 테스트 수.
그리고 그 중 어떤 호환성 테스트도 소스에 대해 실행되지 않는다. 그들은 자체 빌드 스크립트로 패키지를 빌드하고 npm pack를 실행하며 tarball을 풀고, 풀린 진입점을 가져오는 fixture CLI를 작성하고, Node, Bun, Deno 아래에서 그들을 생성한다 — 종료 상태와 바이트를 셸이 보는 그대로 단정한다.
그 모양은 미학적 선택이 아니었다. 이 패키지는 한 번 65 KB 스텁을 배송했다. 단일 "sideEffects": false 플래그가 번들러에게 라우터와 종료 코드 모듈을 아티팩트에서 트리쉐이크하도록 허용했고, 그들의 이름은 내보내기 목록에 남아 있었다. 빌드는 0으로 종료되었다. 소스 스위트는 전체 시간 동안 초록색으로 유지되었습니다. 오직 유물만이 증거였으며, 아무도 유물을 바라보지 않았습니다.
우리는 같은 날 7의 10 CLI를 마이그레이션했고, 마이그레이션은 설계가 실제 등급을 얻은 지점입니다.
배당금은 즉시 도착했고 비용이 없었습니다: 오래된 버전의 명령이 이미 값을 반환했기 때문입니다 — 프레임워크는 이를 단지 종료 코드를 도출하는 데만 사용했고, 그 뒤에 버렸습니다 — 그 반환값 중 하나하나가 업그레이드 날에 JSON 페이로드가 되었습니다. 7 도구는 한 번도 명령을 다시 작성하지 않고 기계가 읽을 수 있는 출력을 얻었습니다.
우리가 예상하지 못한 것은 3 다른 도구에서도 같은 결함이 있었고, 그들 중 아무도 서로를 알지 못했다는 점이었습니다. 각 도구는 라우터 앞에 게이트를 두었습니다: 유효한 명령 이름의 수동 목록이거나, 시작 단계에서 자격 증명을 요구하는 단계가 있었습니다. 모든 경우에 새로운 manifest 명령 — 도구의 전체 표면을 한 번에 설명하는 명령이어서, 에이전트가 소스를 읽지 않고도 배울 수 있도록 하는 — 은 "알 수 없는 명령" 또는 "토큰 누락"이라고 응답했습니다.
그 중 하나는 명령 목록의 두 번째 사본과 수기로 작성된 도움말 화면을 보관했으며, 둘 다 도구가 실제로 수행하는 것과 일치하지 않았습니다. 두 개를 삭제하면 그 스위트는 52에서 통과에서 3 실패로, 57 통과로, 0로 바뀝니다. 세트에서 가장 큰 도구는 364 테스트를 가지고 있으며, 업그레이드 전후에 소스 변경 없이 통과했습니다.
이 패턴은 충분히 일반화되어, 패키지 자체에 포함된 서면 절차가 되었습니다. 이는 9 단계이며, 시간을 소비하는 2 단계는 2 아무도 예상하지 못합니다.
이는 부분적으로 미학적이고 대부분 산술적이었습니다. 2025년 9월 8일, 공격자는 가짜 도메인과 실시간 일회용 코드를 사용해 JavaScript에서 가장 많이 의존되는 패키지 중 일부의 유지보수자인 Josh Junon의 npm 계정을 피싱했습니다. 18개의 패키지가 악성 버전이 포함된 상태로 발행되었습니다, chalk 및 debug을 포함하여 — 그들 사이에서 주당 2.6억 다운로드를 기록하는 패키지들. 페이로드는 암호 클리퍼였습니다. 유지보수자들은 이를 포착해 약 2 시간 내에 되돌렸고, 손상된 버전은 그 창에서 약 2.6 백만 번 다운로드되었습니다.
Chalk는 내가 CLI를 요청했을 때 모델이 계속 찾던 3개의 라이브러리 중 하나입니다.
기본은 영향을 받지 않았습니다 — Chalk에 의존하지 않았고, 나는 그 사건 이후 2개월에 만들어졌기 때문에 그 점을 극적으로 말하기보다 정확히 말하고 싶습니다. 관련성은 우리가 뭔가를 피했다는 것이 아닙니다. 이 사건이 위험의 종류를 정확히 설명한다는 사실이 바로 그것이다: 모든 종속성은 다른 사람의 출시 결정에 대한 십년이며, 당신은 통제하지 않는 계정을 신뢰하고 있다. 한 종속성을 대체한 색상 처리 기능은 약 60줄입니다. 다른 것을 대체한 프롬프트는 약 120. 0은 모니터링이 필요 없는 유일한 숫자입니다.
배포된 버전은 85 KB이며, 미니파이되지 않았고 런타임 종속성이 없으며 Node, Bun 및 Deno에서 실행됩니다. 그 위에 구축된 모든 명령은, 명령당 코드 없이, 다음을 얻습니다:
보장
실제로 의미하는 바
정직한 종료 코드
사람이 보고한 오류는 셸에 보고됩니다
--json 및 --ndjson
명령이 반환하는 값은 기계가 파싱할 수 있는 형태입니다
manifest
1 결정론적 호출에서 설명된 전체 도구는 아무것도 로드하지 않습니다
스트림 규율
stdout은 페이로드이며, 모든 로그 라인은 stderr에 있습니다
사용 오류
"당신이 나를 잘못 호출했다"에 대해 2를 종료하고, "시도했지만 실패했다"에 대해 1를 종료합니다
프롬프트 안전성
터미널이 없는 프롬프트는 영원히 걸리지 않고 밀리초 안에 실패합니다
취소
Ctrl-C는 명령의 신호를 중단한 뒤 130를 종료합니다
내가 계속 돌아오는 것은 그 목록의 어떤 단일 항목도 아닙니다. 그 목록이 이제 검증 가능하다는 것입니다. README 자체 예시는 게시된 tarball에 대한 테스트로 실행되며, 그 서사에서 인용된 숫자는 스위트가 생성한 숫자와 일치하도록 유지됩니다 — 이는 작성된 지 1분 이내에 첫 번째 오류를 포착한 규칙으로, 페이지가 87 KB라고 표시했으나 아티팩트는 85였습니다.