יש לי 51 כלים שורת פקודה על מכשיר זה. לא תכננתי את המספר הזה. זה קרה כי CLI הוא המרחק הקצר ביותר בין רעיון למשהו שאני יכול להריץ בפועל, ולפיכך במשך השנים האחרונות קיבלתי עזרה בכתיבתם מהר יותר ממה שהייתי יכול לבד.
העזרה הזו באה עם הרגל שראיתי מוקדם, כשה-GPT-3.5 והמודלים הראשונים של Claude היו אלה שעבדתי איתם. שאל 3 מודלים שונים לבנות CLI ותקבל 3 דעות שונות על מה זה CLI. אחד ניגש לCommander. אחד ניגש לInquirer עבור ההנחיה. אחד ניגש לChalk כי הפלט צריך להיות ממולץ. כל תשובה ניתנת להגנה. יחד הם מסלול, כי כעת אני מחזיק 3 בסיסי קוד שמחמיאים על ניתוח ארגומנטים, על איך נראית כישלון ועל איזה מהספריות האלה אני כעת אחראי לצפות.

כל מודל ניגש למערך ספריות שונה, ואני היה זה שחייב לחיות עם כולם

המסה איננה הספריות. זה שהשיפורים מפסיקים לנסוע.
כאשר 3 CLIs מתנגדים על איך פקודה מדווחת כישלון, תיקון באחד הוא תיקון באחד. אין שום דבר להעלות אותו למעלה. העבודה אינה מתכנסת, ואחרי הכלי העשירי אינך בונה leverage, אתה שומר תיקיית קרבות קרובים.
כבר כתבתי על הרצון להפוך את זה למנוגד. הטיעון המלא ב-How to Turn AI Gains Into Compounding Infrastructure הוא שמועלה הופך ליציב כאשר כל פרויקט תלוי יורש אותו. משטח יכולות משותף. כלל קידום. מקום אחד שבו שיפור נופל ומתרחב.
בנו את השכבה הזו ליכולת AI, לעבודה, לפעולות. לא בנו אותה למשהו שאני בעצם יוצר הכי הרבה פעמים.

אז בניתי בסיס אחד ועשיתי שכל CLI פרויקט משדר את השיפורים שלו אליו

הכלל היה פשוט והוא היה שלי להטמיע: כאשר CLI במקורות שלי צריך משהו טוב יותר — דרך נקייה יותר לרשום שירותים, נתיב שגיאה טוב יותר, עוזר בדיקות שהופך את הסוויטה לקריאה — השיפור לא נשאר בפרויקט. הוא נכנס לבסיס, והבסיס יצא לשאר.
זהו העיצוב כולו. הבסיס קטן במכוון. אין לו דעה על מה שהכלי שלך עושה. יש לו דעה חזקה על מה פקודה היא: משהו שמקבל ארגומנטים, מבצע עבודה, מדווח על מה קרה, ונותן.
התיקייה נוצרה ב-6 ביולי 2025, ו2 מהכלים שלי התלויות בגרסה 1.0.0 של זה באותו יום. זהו הסיפור: לא נבנה באופן ספקולטיבי ולאחר מכן נלקח. הוא נלקח מעבודה שכבר קיימת, בנקודה שבה העתקת אותו scaffolding בין פרויקטים הפסיקה להיות סבירה.
הוא התפשט במהירות, כי התפשטות הייתה הרעיון כולו. 8 מאגרי הקוד היו עליו תוך 25 ימים. 10 תוך 11 שבועות.
Repositories Adopting the Base in 2025
Chart data
repositories
Jul 62
Jul 84
Jul 175
Jul 237
Jul 308
Sep 2010
Git הגיע מאוחר יותר מכל זה. המאגר הושלם ב-12 בנובמבר 2025, 4 חודשים, ונפרסם ביום שלאחר — ולכן היסטוריית הגרסאות וההיסטוריה האמיתית מתנגשות, ולמה בדקתי את מערכת הקבצים במקום לסמוך על לוג הקומיט כשישבתי לכתוב את זה.
הכלים האלה 10 מנהלים את Cloudflare. ניהול DNS מקומי ו-nginx. פריסה נגד Coolify. אוטומציה בדפדפן. דיווח על עלויות בין ספקי מודלים. רוב הם פרטיים, ולכן אני מתאר אותם לפי מה שהם עושים ולא לפי שם. האלו הציבוריים הם aia, שמייעץ במספר מודלים במקביל, והבסיס עצמו. vssh, הכלי שלי לביצוע מרחוק מוגן, הוא גם ציבורי ויצא מההסתכלות ההיא — לבנות את משטח המפעיל פעם אחת, נכון, ולפסיק לבנות אותו מחדש.
הדיבידנדים היו אמיתיים והם משעממים, שזה הצורה הנכונה לדיבידנדים של תשתית. חיזוק בכלי אחד הופיע בכליהם. כשגיליתי שאפשר להדפיס הודעת שגיאה אדומה ולצאת 0 — להגיד לאדם שזה נכשל ולספר ללשונית שזה עבד — התיקון לא נכנס ל-16 המקומות בכלי אחד שבהם זה קרה. הוא נכנס לבסיס, וכל כלי יורש אותו.

אחרי 13 חודשים רציתי שהוא יוקם מחדש, לא מתוקן

באוגוסט 2026 הבסיס פועל ואני עדיין רוצה שהוא ייעלם.
לא כי הוא שבור. כי הוא מצטבר. כי כלל קוד יציאה שהייתי גאה בו הותאם מחדש במקום לעצב אותו מלכתחילה. כי העולם אליו נכתב השתנה מתחתיו: רוב הקריאות ל-CLIs שלי כבר לא נכתבות על ידי אני. הן ניתנות על ידי סוכנים, קוראים stdout, stderr, ו-$? כיחידת החושים היחידה שלהם.
לכן במקום לתקן, קבעתי את התנאים אחרת. נתתי ל-Claude Fable הוראה אחת, והפכתי אותה למ deliberately גדולה:
אם זה היה המסגרת האחרונה CLI שהאדם בנה — זו שנשארת בשירות בעוד עשור — כעת יש לך את ההזדמנות להפוך אותה ל.
עיצוב ממנה. לא ציפיתי למסמך חוזר. ציפיתי לתוכנית.

פבל חזר עם אמנה, וההגבלה הייתה שההבטחות חייבות להיות מעט

מה שהגיע לא היה רשימת תכונות. זה היה מאורגן כאמנה, מחולק באמצע על ידי קיר קשה.
חצי היה חוזה: מה שכל CLI בונה על בסיס זה מבטיח לכל צופה, כתוב כפרקים ממוספרים בשפה RFC-2119 — חייב, אינו חייב, מומלץ, אפשר. שניים עשר משפחות מהם. קודי יציאה. משמעת זרימה. פלט מכונה. תיאור עצמי. דקדוק. סביבה. ביטול. דטרמיניזם. תקציבי ביצועים. תאימות.
החצי השני היה משטח הכתיבה, שהותר לגדול, והקיים רק כדי להפוך את מילוי החוזה לדרך עם ההתנגדות הקטנה ביותר.
ההסבר שמתחתיו היה החלק שמצאתי משכנע. עיצוב שנועד להחזיק עשור לא יכול להסתמך על אופנה, כי אופנה היא מה שמסתיים. הוא לא יכול להסתמך על חוכמה, כי חוכמה היא מה שלא ניתן לחזות בשנת 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"]

אופוס 5 ומצאתי שהספציפיקציה הייתה נכונה לגבי התזה ושגויה לגבי דברים 3

זהו המקום שבו העבודה הפכה לכולנו ולא שלי.
הבאת את הספציפיקציה לאופוס 5 ובנו אותה ביום אחד. לא היה יום נקי. החלקים המועילים הם המקומות שבהם המסמך פגש את הנכס ונפגע.
הספציפיקציה רצתה ctx.args להפוך לרשומה של ארגומנטים שמוזנים. זהו העיצוב הטוב יותר בידוד. זה היה גם שבר את כל הפקודה בכל אחד של 10 הכלים, כי כולם קוראים ל-ctx.args כערך מערך. שמרנו על המערך והצבנו את הארגומנטים המוגדרים על ctx.namedArgs לידו. הכלל שהחליט כבר נכתב בחוזה, סעיף אחד מעל: אל תפרוץ את הצרכן עולה על כל ערך אחר במאגר, כולל שלמות החוזה שלו.
הספציפיקציה רצתה קבוצת פקודות ללא פועל להיות שגיאת שימוש. הרצת פקודת אב עם פקודה משנה לא תגרום ליציאה 2. ניתן להגן, והוא היה משנה את ההתנהגות של כל סקריפט שמריץ פקודת קבוצה ריקה כדי לראות את העזרה שלו. שמנו להדפיס עזרה ולצאת 0.
הספציפיקציה מניחה שהזרימה והמסמך היחיד JSON הם אותו תכונה. הם אינם. זרימת מיליון פריטים בזיכרון קבוע היא נקודת אחת ואפשר לא אחרת, כי קורא ש… חלקנו את ההתנהגות וכתבנו. הפרשנו את ההתנהגות וכתבנו מצאנו גם דברים שהספציפיקציה לא יכלה לדעת, כי הם נראו רק מהאוצר.
מצאנו גם דברים שהספציפיקציה לא יכלה לדעת, כי הם נראו רק מהארטיפקט. קובץ בדיקה שהריץ 0 בדיקות והדווח על הצלחה, כאשר הרוג את הרץ באמצע. טיפול אותות שיצא 0 ב‑Ctrl‑C — פקודה שנקטעה מדווחת שהצליחה. עזר צבע ש, לאחר שהחלפנו את התלות שהוא עטף, צמצם בעדינות את חתימת הסוג שלו ופרץ קוד שלא שינה תו אחד. האחרון הזה שווה לשבת איתו.
הוא לא נתפס על ידי שום בדיקה שאחת מאיתנו כתבה. הוא הופיע ב-typecheck של צרכן במהלך הגירה, שזה המקום היחיד שבו יכול היה. הוא הופיע ב- consumer's typecheck במהלך ההעברה, שזה המקום היחיד שבו ייתכן שהיה.

החוזה נחשב רק כי הבנייה נכשלה כאשר סעיף אין לו בדיקה

הבטחה שאין בדיקה היא הערה.
לכן חבילת ההתאמה מפרקת את קובץ החוזה, מוצאת כל סעיף שמכיל את המילה MUST, ונכשלת בבנייה אם אחד מהם אין לו בדיקה רשומה. אינך יכול להוסיף הבטחה לפרויקט זה ללא הוספת הדבר שמוכיח אותו, באותו הקומיט.
Conformance Tests by Contract Family
Chart data
Value
Grammar20
Exit codes (truth)12
Machine output11
Self-description10
Environment8
Prompt safety6
Streams5
Cancellation5
Determinism5
46 סעיפים נורמטיביים. 92 בדיקות ממפות אליהם. 184 בדיקות בסך הכל.
ואף אחת מהבדיקות ההתאמה הללו לא רצה נגד המקור. הם בונים את החבילה עם סקריפט הבנייה שלו, מריצים npm pack, מפורקים את הטארבל, כותבים CLI תבניות שמייבאים את נקודת הכניסה המפורקת, ומפעילים אותם תחת Node, Bun ו-Deno — מאשרים על מצב יציאה והבייטים בדיוק כפי שהש shell רואה אותם.
צורתה לא הייתה בחירה אסתטית. חבילת זו שיחקה פעם את 65 stub של KB. דגל "sideEffects": false יחיד אפשר את bundler tree-shake את ה-router ואת מודול קוד היציאה מהאוצר בעוד שמותיהם נשארו ברשימת הייצוא. הבנייה יצאה 0. הסוויטה המקורית נשארה ירוקה לאורך כל הזמן. רק האביזר היה הוכחה, ולא היה שום דבר שמסתכל על האביזר.

העברת 7 כלים מצאה 3 שערים שלא ידעו שקיימים

העברנו 7 של 10 CLI באותו היום, וההעברה היא המקום שבו העיצוב קיבל את דרגתו האמיתית.
הדיבידנד נחת מיד והעלה כלום: כי הפקודות בגרסה הישנה כבר החזירו ערכים — המסגרת השתמשה בהם רק כדי להסיק קוד יציאה, ואז דילגה עליהם — כל אחד מהערכים החוזרים הפך לJSON payload ביום השדרוג. 7 כלים קיבלו פלט קריא מכונה ללא פקודה אחת שנכתבה מחדש.
מה שלא ציפינו היה אותו תקלת 3 כלים שונים, אף אחד מהם לא ידע על האחרים. לכל אחד מהם היה שער לפני הרוטר: רשימה שמו ידנית של שמות פקודות תקפים, או שלב הפעלה שמחייב פרטי זהות לפני כל דבר אחר רץ. בכל מקרה הפקודה החדשה manifest — זו שמתארת את כל פני הכלי בשיחה אחת, כך שהסוכן יכול ללמוד אותה ללא קריאת המקור — הגיבה ב"פקודה לא ידועה" או "טוקן חסר".
אחד מהם שמר עותק שני של רשימת הפקודות שלו ומסך עזרה כתוב ידנית, שניהם התרחבו מה שהכלי באמת עשה. מחיקת שניהם לקחה את החבילה שלו מ52 שעובדת עם 3 כשל ל57 שעובדת עם 0. הכלי הגדול ביותר במערך יש 364 בדיקות, והן עברו לפני ואחרי השדרוג ללא שינוי מקור.
הדפוס התרחב מספיק כדי להפוך לפרוצדורה כתובה, שנשלחה בתוך החבילה עצמה. זה הוא 9 שלבים, והשלבים 2 שצריכים את הזמן הם 2 שלא מצפים.

אפס תלויות הוא המספר היחיד שלא צריך מעקב

הבסיס היה עם 2 תלויות ריצה. כעת אין לו כל אחת.
זה היה חלקית אסתטי ובדרך כלל מתמטי. ב־8 בספטמבר 2025, תוקף פישינג את חשבון npm של Josh Junon, מנהל של חלק מהחבילות התלויות ביותר ב־JavaScript, באמצעות דומיין מזויף וקוד חד‑פעמי חי. 18 חבילות פורסמו עם גרסאות זדוניות, כולל chalk ו־debug — חבילות שמחזיקות בסביבות 2.6 מיליארד הורדות בשבוע יחד. הpayload היה crypto‑clipper. המנהלים חטפו אותו והחזירו תוך כ־2 שעות, והגרסאות המושחתות עדיין הורדו כ־2.6 מיליון פעמים באותו חלון.
Chalk הוא אחד מ-3 הספריות שהמודלים המשיכו להגיע אליהן כאשר ביקשתי מהם CLI
הבסיס לא נפגע — הוא מעולם לא התלוי על chalk — ואני רוצה להיות מדויק ולא דרמטי לגבי זה, מכיוון שיצרתי אותו 2 חודשים אחרי האירוע. הרלוונטיות אינה בכך שהצלחנו להימנע מ־. אלא בכך שהאירוע מתאר את סוג הסיכון בדיוק: כל תלות היא עשור של החלטות שחרור של מישהו אחר, ואתה סומך על חשבון שאינך בוחרת. הטיפול בצבע שהחליף תלות אחת הוא על כ־60 שורות. ההנחיה שהחליפה את האחר היא על כ־120. אפס הוא המספר היחיד שלא צריך מעקב.

מה שהבסיס מחזיר כעת

הגרסה ששודרה היא 85 KB, לא ממזער, ללא תלויות ריצה, רצה על Node, Bun ו-Deno. כל פקודה שנבנית על זה מקבלת, ללא קוד לכל פקודה:
הבטחהמה זה אומר בפועל
קודי יציאה כניםשגיאה שמדווחת לאדם מדווחת ללשונית
--json ו---ndjsonהערך שהפקודה שלך מחזירה, בצורה שהמכונה יכולה לנתח
manifestהכלי המלא המתואר ב-1 קריאה דטרמיניסטית, לא טוען כלום
משמעת זרימהstdout הוא payload; כל שורת לוג היא ב-stderr
שגיאות שימושיציאה 2 עבור "התקשרת אותי לא נכון", שונה מ-1 עבור "ניסיתי ונכשלתי"
בטיחות בקשתבקשה ללא מסוף נכשלת במילישניות במקום להיתקע לנצח
ביטולCtrl-C מבטל את אות הפקודה, ואז יוצא 130
הדבר שאני חוזר אליו אינו פריט יחיד ברשימה הזו. זה שהרשימה כעת ניתנת לבדיקה. דוגמת README הרצה כבדיקה נגד האריזה המפורסמת, והמספרים המוזכרים בפרוזה שלו נשמרים למספרים שהקבוצה מייצרת — כלל שהציל את השגיאה הראשונה שלו בתוך דקה מהכתיבה, כאשר הדף ציין 87 KB והאוצר היה 85.
הבסיס הוא קוד פתוח ב-github.com/light-merlin-dark/merlin-cli, והחוזה הוא קובץ במאגר במקום טענה באתר.
לפני ארבע שנים הבעיה הייתה שכל מודל היה בעל דעה שונה לגבי מה צריך להיות CLI. התשובה לא הייתה לעסוק בדעות. זה היה להחזיק את הבסיס שכל אחד בונה עליו, ולכתוב את ההבטחות למקום שבו בנייה יכולה להיכשל.