آخر CLI: إعادة بناء القاعدة 51 من أدواتي التي تم بناؤها ضدها
كل نموذج سعى لمجموعة مختلفة من المكتبات، لذا بنيت قاعدة واحدة وجعلت كل مشروع يتجه إليها. ثم سألت Claude Fable عن CLI التي لا تزال الإنسانية قد تستخدمها في عقد، وOpus 5 وأنا بنينا ما عاد — 46 بندًا، 92 اختبارًا، صفر تبعيات.
Developed by Robert E. Beckner III (Merlin) | rbeckner.com
لدي 51 أدوات سطر أوامر على هذه الآلة. لم أخطط لذلك الرقم. حدث ذلك لأن CLI هو أقصر مسافة بين فكرة وشيء يمكنني تشغيله فعليًا، ولأنني منذ عدة سنوات حصلت على مساعدة في كتابتها أسرع مما كنت أستطيع بمفردي.
جاءت تلك المساعدة مع عادة لاحظتها مبكرًا، عندما كانت GPT-3.5 والنماذج Claude الأولى هي التي عملت معها. اطلب من 3 نماذج مختلفة إنشاء إطار CLI وستحصل على 3 آراء مختلفة حول ما هو CLI. واحد يتجه إلى Commander. واحد يتجه إلى Inquirer للتوجيه. واحد يتجه إلى Chalk لأن الإخراج يجب أن يكون ملونًا. كل إجابة قابلة للدفاع. معًا هم ضريبة، لأنني الآن أملك 3 قواعد كود تتعارض حول تحليل الحجج، حول ما يبدو عليه الفشل، وأي من تلك المكتبات أنا الآن مسؤول عنها للمراقبة.
كل نموذج اقترب من مجموعة مختلفة من المكتبات، وكنت أنا من اضطررت للعيش مع جميعها#
الضرائب ليست المكتبات. إنها أن التحسينات تتوقف عن السفر.
عندما يختلف 3 CLIs حول كيفية إبلاغ أمر بالفشل، فإن إصلاحًا في واحد هو إصلاح في واحد. لا يوجد شيء لتوجيهه إلى الأعلى. العمل لا يتراكم، وبعد الأداة العاشرة أنت لا تبني حافزًا، بل تحافظ على محفظة من القرب من الفشل.
كنت قد كتبت بالفعل عن رغبة في العكس من ذلك. الحجة الكاملة في How to Turn AI Gains Into Compounding Infrastructure هي أن الربح يصبح دائمًا عندما يرثه كل مشروع تابع. سطح قدرة مشترك. قاعدة ترويج. مكان واحد حيث يهبط التحسين وينشر.
لقد بنيت ذلك الطبقة لقدرة الذكاء الاصطناعي، للعملية، للعمليات. لم أقم ببنائها للشيء الذي أصنعه فعليًا أكثر من أي شيء آخر.
لذا بنيت قاعدة واحدة وجعلت كل مشروع CLI يرفع تحسيناته إلى ذلك#
القاعدة كانت بسيطة وكانت لي أن أفرضها: عندما يحتاج CLI في ممتلكاتي إلى شيء أفضل — طريقة أنظف لتسجيل الخدمات، مسار خطأ أفضل، مساعدة اختبار تجعل المجموعة قابلة للقراءة — لم يبقَ ذلك التحسين في المشروع. انتقل إلى القاعدة، وخرجت القاعدة إلى الآخرين.
هذا هو التصميم الكامل. القاعدة صغيرة بالنية. لا تحمل رأيًا حول ما تفعله أداةك. لديه رأي قوي حول ما هو أمر is: شيء يأخذ وسائط، يقوم بالعمل، يبلغ ما حدث، ويترك.
تم إنشاء المجلد في 6 يوليو 2025، وكانت 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 جاء لاحقًا من أي من ذلك. تم تهيئة المستودع في 12 نوفمبر 2025، بعد 4 شهرًا، ونُشر اليوم التالي — وهذا هو السبب في أن تاريخ الإصدار وتاريخ الفعل الحقيقي لا يتفقان، ولذلك قمت بفحص نظام الملفات بدلاً من الثقة في سجل الالتزام عندما جلست لكتابة هذا.
تلك الأدوات 10 تدير Cloudflare. الإدارة المحلية DNS و nginx. النشر ضد Coolify. أتمتة المتصفح. تقارير التكاليف عبر موفري النماذج. معظمها خاص، ولهذا أصفها بما تقوم به بدلاً من الاسم. العامة هي aia، التي تستشير عدة نماذج بالتوازي، والقاعدة نفسها. vssh، أداتك الآمنة لتنفيذ البعيد، عامة أيضاً وتخرج من نفس الغريزة — بناء سطح المشغل مرة واحدة، بشكل صحيح، وتوقف إعادة بنائه.
كانت الأرباح حقيقية وكانت مملة، وهو الشكل الصحيح لأرباح البنية التحتية. تقوية في أداة واحدة ظهرت في جميعها. عندما اكتشفت أن أمرًا يمكنه طباعة رسالة خطأ حمراء وما زال يخرج 0 — يخبر الإنسان أنه فشل ويخبر الصدفة أنه نجح — لم يتم تطبيق الإصلاح في الـ 16 مكانًا في الأداة حيث حدث ذلك. انتقل إلى القاعدة، وكل أداة ورثته.
بحلول أغسطس 2026 كانت القاعدة تعمل وما زلت أريد إزالتها.
ليس لأنها كانت معطلة. لأنها تراكمت. لأن قاعدة exit-code التي كنت أفتخر بها تم تعديلها بدلاً من تصميمها من البداية. لأن العالم الذي كتبت من أجله قد تغير تحتها: معظم استدعاءات CLIs الخاصة بي لم تعد تُكتب من قبلي. يتم إصدارها من قبل وكلاء، يقرؤون stdout، stderr، و$? كحواسهم الوحيدة.
لذا بدلاً من التصحيح، عيّنت الشروط بشكل مختلف. أعطيت Claude Fable تعليمًا واحدًا، وجعلته كبيرًا عمدًا:
إذا كان هذا هو آخر إطار عمل CLI الذي بنته البشرية — الذي لا يزال في الخدمة
في عقدة — لديك الآن الفرصة لجعله ذلك.
صممه من هناك. لم أتوقع وثيقة عائدة. توقعت خطة.
عاد Fable مع معاهدة، وكانت القيد هو أن الوعود يجب أن تكون قليلة#
ما وصل لم يكن قائمة ميزات. كان منظمًا كمعاهدة، مقسمًا إلى نصفين بواسطة جدار صلب.
نصف كان عقدًا: ما يضمنه كل CLI مبني على هذا الأساس لكل مراقب، مكتوب كحُكم مرقّمة باللغة RFC-2119 — يجب، يجب ألا، يجب أن، قد. اثني عشر عائلات منهم. رموز الخروج. انضباط التدفق. إخراج الآلة. الوصف الذاتي. القواعد النحوية. البيئة. الإلغاء. التحديد. ميزانيات الأداء. التوافق.
النصف الآخر كان سطح التدوين، الذي يُسمح له بالنمو، وكان موجودًا فقط لجعل تحقيق العقد هو مسار أقل مقاومة.
كان التفكير أدناه هو الجزء الذي وجدته مقنعًا. لا يمكن لتصميم يهدف إلى البقاء لعقد أن يراهن على الموضة، لأن الموضة هي ما ينتهي. لا يمكنه الرهان على الذكاء، لأن الذكاء هو ما لا يمكنك التنبؤ به في السنة 8. يمكنه فقط الرهان على الواجهات التي لم تتحرك منذ 1970s: متجهات الحجة، 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 — أمر مقطوع يبلغ أنه نجح. مساعدان إخراجان جمعوا أسطرهما بمسافة backslash-n حرفية، بحيث عادت كل جدول في سطر واحد. مساعد ألوان، عندما استبدلنا الاعتماد الذي كان يلفه، ضيق توقيعه النوعي بهدوء وكسر الكود الذي لم يغيّر حرفًا.
هذا الأخير يستحق الجلوس معه. لم يتم اكتشافه بواسطة أي اختبار كتبته أي منا. ظهر في فحص النوع لـ consumer's أثناء الهجرة، وهو المكان الوحيد الذي يمكن أن يكون فيه.
يُعد العقد فقط لأنه يفشل البناء عندما لا يحتوي بند على اختبار#
وعد لا يشيك شيء هو تعليق.
لذلك تقوم مجموعة الامتثال بتحليل ملف العقد، وتجد كل بند يحتوي على كلمة 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، يفكّون الحزمة، يكتبون CLIs التجريبية التي تستورد نقطة الدخول المفكوكة، ويطلقونها تحت Node، Bun و Deno — يثبتون حالة الخروج والبايتات بالضبط كما يراها الصدفة.
لم يكن ذلك الشكل خيارًا جماليًا. شغلت هذه الحزمة مرة واحدة 65 stub KB. علم "sideEffects": false واحد سمح للـ bundler tree-shake للموجه ووحدة exit-code خارج القطعة بينما بقيت أسماؤهم في قائمة التصدير. خرج البناء 0. ظلّ مجموعة المصادر خضراء طوال الوقت. كان القطعة الوحيدة الدليل، ولم يلاحظ أحد القطعة.
يجري هجرة 7 أدوات وجد 3 بوابات لم يعرف أحد وجودها#
قمنا بنقل 7 من أدوات 10 CLIs في نفس اليوم، وكانت عملية الترحيل هي المكان الذي حصل فيه التصميم على درجته الفعلية.
وصلت الأرباح فورًا ولم تكلف شيئًا: لأن الأوامر في الإصدار القديم كانت تُعيد القيم بالفعل — استخدم الإطار هذه القيم فقط لاستنتاج رمز خروج، ثم تجاهلها — أصبحت كل واحدة من تلك القيم العائدة حمولة JSON في يوم الترقية. اكتسبت أدوات 7 إخراجًا قابلًا للقراءة الآلية دون إعادة كتابة أي أمر واحد.
ما لم نتوقعه هو نفس العيب في أدوات 3 مختلفة، لا أحد منها كان يعرف عن الآخر. كان لكل منها بوابة أمام الموجه: قائمة يدوية يدويًا بأسماء الأوامر الصالحة، أو خطوة بدء تشغيل تطلب بيانات الاعتماد قبل تشغيل أي شيء آخر. في كل حالة كان الأمر الجديد manifest — الذي يصف سطح الأداة بأكمله في مكالمة واحدة، بحيث يمكن للوكيل تعلمه دون قراءة المصدر — يجيب بـ "أمر غير معروف" أو "رمز مفقود".
أحدهم احتفظ بنسخة ثانية من قائمة أوامره وشاشة مساعدة مكتوبة يدويًا، وكلاهما قد ابتعد عن ما فعله الأداة فعليًا. حذف الاثنين أخذ مجموعته من 52 التي كانت ناجحة مع 3 فاشلة إلى 57 ناجحة مع 0. أداة المجموعة الأكبر لديها 364 اختبارات، ونجحت قبل وبعد الترقية دون تغيير المصدر.
أصبح النمط عاملاً بما فيه الكفاية ليصبح إجراءً مكتوبًا، يُشحن داخل الحزمة نفسها. هو 9 خطوات، والخطوات 2 التي تستهلك الوقت هي 2 لا يتوقعها أحد.
لا توجد تبعيات هو الرقم الوحيد الذي لا يحتاج إلى مراقبة#
كان الأساس لديه تبعيات تشغيلية 2. الآن لا يوجد أي منها.
كان ذلك جزئياً جماليًا ومعظمًا حسابيًا. في 8 سبتمبر 2025، قام مهاجم بالاحتيال على حساب npm لـ Josh Junon، المسؤول عن بعض الحزم الأكثر اعتمادًا في JavaScript، باستخدام نطاق مزيف ورمز مرة واحدة حقيقي. تم نشر 18 حزمةً بإصدارات خبيثة، بما في ذلك chalk و debug — حزم تحمل ما يقارب 2.6 مليار تحميل أسبوعيًا بينهما. كانت الحمولة عبارة عن متلاعب بالعملات الرقمية. أمسك المسؤولون بها وعادوا إليها في غضون تقريبًا 2 ساعات، وكانت الإصدارات المخترقة لا تزال تُحمَّل حوالي 2.6 مليون مرة في تلك الفترة.
Chalk هو واحد من 3 مكتبات كان النماذج تستمر في اللجوء إليها عندما طلبت منهم CLI.
لم يتأثر الأساس — لم يعتمد أبدًا على chalk — وأريد أن أكون دقيقًا بدلاً من درامي حول ذلك، لأنه تم إنشاؤه 2 أشهر بعد الحادثة. الأهمية ليست أننا تجنبنا شيئًا. إنها أن الحادثة تصف فئة المخاطر بدقة: كل تبعية هي عقدة من قرارات إصدار شخص آخر، وأنت تثق بحساب لا تملكه. معالجة الألوان التي استبدلت تبعية واحدة تتعلق بـ 60 أسطر. التوجيه الذي استبدل الآخر يتعلق بـ 120. لا يوجد هو الرقم الوحيد الذي لا يحتاج إلى مراقبة.
الإصدار الذي تم شحنه هو 85 كيلوبايت، غير مصغر، بدون تبعيات وقت التشغيل، يعمل على Node، Bun و Deno. كل أمر مبني عليه يحصل، بدون كود لكل أمر:
ضمان
ما يعنيه ذلك عمليًا
رموز خروج صادقة
يُبلغ الخطأ للإنسان يُبلغ للشل
--json و --ndjson
القيمة التي يعيدها أمرك، في شكل يمكن للآلة تحليله
manifest
الأداة الكاملة الموصوفة في 1 استدعاء محدد، لا يحمل أي شيء
انضباط التدفق
stdout هو الحمولة؛ كل سطر سجل على stderr
أخطاء الاستخدام
خروج 2 لـ "قمت بالاتصال بي خطأ"، متميزًا عن 1 لـ "حاولت وفشلت"
أمان الموجه
موجه بدون طرفة ينتهي في مللي ثانية بدلاً من التوقف إلى الأبد
إلغاء
Ctrl-C يقطع إشارة الأمر، ثم يخرج 130
الشيء الذي أعود إليه هو ليس أي عنصر واحد في تلك القائمة. هو أن القائمة أصبحت قابلة للتفقد. مثال README الخاص يعمل كاختبار ضد الحزمة المنشورة، والأرقام المذكورة في نصه تُحافظ على الأرقام التي ينتجها المجموعة — قاعدة أدت إلى أول خطأ لها خلال دقيقة من كتابتها، حيث قالت الصفحة 87 كيلوبايت وكان العمل 85.
قبل أربع سنوات كان المشكلة أن كل نموذج كان لديه رأي مختلف حول ما يجب أن يكونه CLI. الجواب لم يكن أبداً الجدال مع الآراء. كان هو امتلاك القاعدة التي يبني عليها الجميع، وكتابة الوعد في مكان يمكن أن يفشل البناء فيه.