تخطَّ إلى المحتوى

واجهة سطر الأوامر في Python

واجهة سطر الأوامر في ⁨Python⁩

قسم بعنوان «واجهة سطر الأوامر في ⁨Python⁩»

استخدم الأمر nextpdf لاستخراج المحتوى من ملفات تنسيق المستندات المحمولة (⁨PDF⁩) عبر الطرفية. وجِّهه إلى نقطة نهاية ⁨NextPDF Connect⁩، ومرِّر ملف ⁨PDF⁩، وتلقَّ مخرجات مُنظَّمة: نصًّا موثَّقًا بالاستشهادات، أو جداول، أو شجرة البنية النحوية المجردة (⁨AST⁩) الدلالية الكاملة، أو ملخص بيانات وصفية، وذلك على الإخراج القياسي (⁨stdout⁩) أو في ملف.

يمثّل الأمر nextpdf مجموعة أوامر في ⁨Click.⁩ تنتمي خيارات الاتصال والجلسة — --base-url، و--api-key، و--log-level، و--output/-o، و--strict — إلى المجموعة، لذا ضعها قبل الأمر الفرعي. ثم ضع الأمر الفرعي وخياراته، مثل --format أو --page، بعده:

nextpdf [GROUP OPTIONS] COMMAND [SUBCOMMAND] PDF_PATH [COMMAND OPTIONS]

إذا وضعت خيار مجموعة بعد الأمر الفرعي، فسيفشل الأمر. على سبيل المثال، يعرض nextpdf info document.pdf --base-url ... الرسالة Error: No such option: --base-url ويخرج برمز الحالة 2، لأن ⁨Click⁩ يكون قد بدأ بالفعل في تحليل الأمر الفرعي info عند رؤية --base-url، بينما لا يعرِّف info ذلك الخيار.

لتجنُّب مشكلة الترتيب، زوِّد بيانات الاعتماد عبر متغيرات البيئة (انظر التهيئة مرة واحدة لكل صَدَفة). تُظهِر الأمثلة أدناه صيغة الأعلام الصريحة أولًا حتى يكون الترتيب الصحيح واضحًا.

استخراج النص بصيغة تدوين كائنات ⁨JavaScript⁩ (⁨JSON⁩):

Terminal window
nextpdf --base-url http://localhost:8080 --api-key "$NEXTPDF_API_KEY" extract text document.pdf

استخراج الجداول بصيغة القيم المفصولة بفواصل (⁨CSV⁩):

Terminal window
nextpdf --base-url http://localhost:8080 --api-key "$NEXTPDF_API_KEY" extract tables invoice.pdf --format csv

فحص البيانات الوصفية للمستند وبنيته:

Terminal window
nextpdf --base-url http://localhost:8080 --api-key "$NEXTPDF_API_KEY" info document.pdf

الحصول على شجرة البنية النحوية المجردة (⁨AST⁩) الدلالية الكاملة:

Terminal window
nextpdf --base-url http://localhost:8080 --api-key "$NEXTPDF_API_KEY" ast document.pdf

طباعة إصدار مجموعة تطوير البرمجيات (⁨SDK⁩) المُثبَّتة دون الاتصال بخادم:

Terminal window
nextpdf version

الأمر version هو الأمر الوحيد الذي لا يحتاج إلى --base-url ولا إلى --api-key. أما كل أمر آخر فيتصل بالخادم ويتطلب كليهما.

يقرأ كل مثال مفتاح واجهة برمجة التطبيقات (⁨API⁩) من متغير البيئة NEXTPDF_API_KEY بدلًا من تضمينه في سطر الأوامر. تعامَل مع المفتاح بوصفه سرًّا. يظهر المفتاح الحرفي على سطر الأوامر في سِجِل الصَّدَفة الخاص بك وفي قائمة العمليات (ps) لدى مستخدمين آخرين على المُضيف.

ضع هذه الخيارات قبل الأمر الفرعي. يقرأ كل خيار اتصال أيضًا من متغير بيئة، لذا يمكنك حذف العَلَم عند تعيين المتغير.

الخيارمتغير البيئةالافتراضيالغرض
--base-urlNEXTPDF_BASE_URL(مطلوب)محدد موارد موحَّد (⁨URL⁩) لخادم ⁨NextPDF Connect.⁩
--api-keyNEXTPDF_API_KEY(مطلوب)مفتاح ⁨API⁩ للمصادقة باستخدام حامل الرمز.
--log-levelwarningإسهاب التسجيل: debug، أو info، أو warning، أو error. تذهب السجلات إلى الخطأ القياسي (⁨stderr⁩).
--output, -o⁨stdout⁩كتابة مخرجات الأمر إلى ملف بدلًا من ⁨stdout.⁩
--strictمُعطَّلمحجوز للاستخدام المستقبلي. يُحلَّل العَلَم حاليًا لكنه لا يُغيِّر السلوك.
--help, -hعرض التعليمات والخروج.

القيمتان --base-url و--api-key مطلوبتان لكل أمر باستثناء version. إذا كانت إحدى القيمتين مفقودة — لا عَلَم ولا متغير بيئة — فإن الأمر يطبع خطأً ويخرج بالحالة 1.

استخراج كتل النص الموثَّقة بالاستشهادات. تتضمن كل كتلة مِرساة استشهاد مع مُعرِّف عُقدة، وفهرس صفحة، ومربع إحاطة، ودرجة ثقة.

nextpdf [GROUP OPTIONS] extract text PDF_PATH [--format FORMAT] [--page N] [--headings-only]
الخيارالقيمالافتراضيالغرض
--formatjson, markdown, plainjsonصيغة المخرجات.
--pageعدد صحيحكل الصفحاتاستخراج فهرس هذه الصفحة فقط، المُرقَّم ابتداءً من 0.
--headings-onlyعَلَممُعطَّلاستخراج عُقد العناوين فقط.

PDF_PATH هو مسار ملف، أو - لقراءة بايتات ⁨PDF⁩ من المُدخَل القياسي (⁨stdin⁩).

استخراج كل جدول مع مراسي استشهاد وبنية على مستوى الخلايا.

nextpdf [GROUP OPTIONS] extract tables PDF_PATH [--format FORMAT] [--page-start N] [--page-end N]
الخيارالقيمالافتراضيالغرض
--formatjson, csvjsonصيغة المخرجات.
--page-startعدد صحيحالصفحة الأولىفهرس صفحة البدء (مُرقَّم ابتداءً من 0).
--page-endعدد صحيحالصفحة الأخيرةفهرس صفحة النهاية (مُرقَّم ابتداءً من 0).

PDF_PATH هو مسار ملف، أو - للقراءة من ⁨stdin.⁩

إرجاع شجرة البنية النحوية المجردة (⁨AST⁩) الدلالية الكاملة بصيغة ⁨JSON⁩: شجرة هرمية من العُقد، تتضمن العناوين والفقرات والجداول والقوائم والأشكال، مع مربعات الإحاطة والمحتوى النصي.

nextpdf [GROUP OPTIONS] ast PDF_PATH [--page-start N] [--page-end N] [--token-budget N]
الخيارالقيمالافتراضيالغرض
--page-startعدد صحيحالصفحة الأولىفهرس صفحة البدء (مُرقَّم ابتداءً من 0).
--page-endعدد صحيحالصفحة الأخيرةفهرس صفحة النهاية (مُرقَّم ابتداءً من 0).
--token-budgetعدد صحيحغير محدودحد تقريبي لعدد الرموز في شجرة ⁨AST⁩ المُعادة.

PDF_PATH هو مسار ملف، أو - للقراءة من ⁨stdin.⁩ يُنتِج الأمر ast شجرة مستند واحدة؛ وهو لا يقارن بين ملفي ⁨PDF.⁩ لإجراء المقارنة البنيوية، انظر وصفة: مقارنة مراجعتين من ⁨PDF⁩.

طباعة ملخص ⁨JSON⁩ مُوجَز لمستند واحد: إصدار المخطط، وتجزئة المصدر، وعدد الصفحات، وعدد الرموز المُقدَّر، ونوع العُقدة الجذرية، وعدد العناصر الفرعية للجذر.

nextpdf [GROUP OPTIONS] info PDF_PATH

PDF_PATH هو مسار ملف، أو - للقراءة من ⁨stdin.⁩

طباعة إصدار ⁨SDK⁩ المُثبَّتة، مثل nextpdf 1.1.0، ثم الخروج. لا يتصل هذا الأمر بأي خادم ولا يحتاج إلى بيانات اعتماد.

nextpdf version

التهيئة مرة واحدة لكل صَدَفة

قسم بعنوان «التهيئة مرة واحدة لكل صَدَفة»

عيِّن خيارات الاتصال مرة واحدة كمتغيرات بيئة، واحذف الأعلام المتكررة. تتجنب هذه الصيغة أيضًا مشكلة ترتيب الخيارات بالكامل، لأن بيانات الاعتماد لا تظهر أبدًا على سطر الأوامر.

Terminal window
export NEXTPDF_BASE_URL=http://localhost:8080
export NEXTPDF_API_KEY=your-key
nextpdf extract text document.pdf

في ⁨Windows PowerShell⁩:

Terminal window
$env:NEXTPDF_BASE_URL = "http://localhost:8080"
$env:NEXTPDF_API_KEY = "your-key"
nextpdf extract text document.pdf

يُفضَّل تحميل المفتاح من مخزن أسرار أو من ملف .env تُبقيه خارج التحكم في الإصدارات. لا تلصق مفتاح إنتاج في جلسة طرفية مشتركة أو في سكربت تدرجه في التحكم بالإصدار.

اختر صيغة المخرجات لكل أمر باستخدام --format. يدعم أمرا النص والجداول أكثر من صيغة واحدة؛ أما ast وinfo فيُخرِجان ⁨JSON⁩ دائمًا.

الأمرالصيغالافتراضي
extract textjson, markdown, plainjson
extract tablesjson, csvjson
astjsonjson
infojsonjson

اختر ⁨JSON⁩ عندما يحتاج برنامج لاحق إلى فهارس الصفحات أو درجات الثقة أو مُعرِّفات العُقد. اختر ⁨CSV⁩ عندما تكون الجداول مدخلات لجدول بيانات أو لمسار معالجة جدولي. اختر نص plain أو markdown عندما يقرأ النتيجة شخص أو أداة نصية فقط.

يُخرِج أمر النص مصفوفة ⁨JSON⁩ من الكتل الموثَّقة بالاستشهادات. تتضمن كل كتلة text، وكائن citation (node_id، وpage_index، وbbox، وconfidence)، وحقل node_type اختياري. أرسِل المخرجات إلى ملف باستخدام --output، أو أعِد توجيه ⁨stdout⁩، ثم حلِّلها.

يستخدم مثال الصَّدَفة هذا jq للإبقاء على العناوين فقط في الصفحة 0:

Terminal window
nextpdf --output blocks.json extract text report.pdf --format json
jq '[.[] | select(.citation.page_index == 0 and .node_type == "heading") | .text]' blocks.json

تُحلَّل البيانات نفسها بسلاسة في ⁨Python.⁩ تكتب واجهة سطر الأوامر (⁨CLI⁩) مصفوفة ⁨JSON⁩، لذا حمِّلها باستخدام المكتبة القياسية واقرأ الحقول المُنمَّطة:

"""Parse cited text blocks emitted by `nextpdf extract text --format json`."""
import json
from pathlib import Path
def load_headings(blocks_path: Path) -> list[str]:
"""Return the text of every heading block on page 0.
Args:
blocks_path: Path to the JSON file written by `nextpdf extract text`.
Returns:
The text of each heading-type block whose citation is on page 0.
"""
raw = blocks_path.read_text(encoding="utf-8")
blocks: list[dict[str, object]] = json.loads(raw)
headings: list[str] = []
for block in blocks:
citation = block["citation"]
if block.get("node_type") == "heading" and citation["page_index"] == 0:
headings.append(str(block["text"]))
return headings
if __name__ == "__main__":
for heading in load_headings(Path("blocks.json")):
print(heading)

عندما تحتاج إلى نماذج مُنمَّطة ومُتحقَّق منها بدلًا من القواميس الخام، استدعِ ⁨SDK⁩ مباشرةً بدلًا من تحليل مخرجات ⁨CLI.⁩ انظر نظرة عامة على ⁨Python⁩ للاطلاع على عميل NextPDF ونوع الإرجاع CitedTextBlock الخاص به.

باستخدام --format csv، يكتب أمر الجداول كتلة ⁨CSV⁩ واحدة لكل جدول. يسبق كلَّ جدول صفُّ تعليق، # Table N (pM)، يذكر رقم الجدول المُرقَّم ابتداءً من 1 وفهرس الصفحة المُرقَّم ابتداءً من 0. يفصل سطرٌ فارغ بين الجداول المتتالية. تُحيط ⁨CLI⁩ قيم الخلايا بعلامات اقتباس وتُهرِّبها باستخدام وحدة csv في ⁨Python⁩، حتى تبقى القيم التي تحتوي على فواصل أو علامات اقتباس أو أسطر جديدة آمنة عند القراءة والكتابة.

Terminal window
nextpdf --output tables.csv extract tables statement.pdf --format csv

لأن الملف يحتوي على كتل ⁨CSV⁩ متعددة، قسِّمه عند صفوف التعليقات قبل تحليل كل كتلة كجدول مستقل:

"""Split multi-table CSV output from `nextpdf extract tables --format csv`."""
import csv
import io
from pathlib import Path
def read_tables(csv_path: Path) -> list[list[list[str]]]:
"""Parse the multi-block CSV file into a list of tables.
Each table is a list of rows; each row is a list of cell strings.
The leading `# Table N (pM)` comment row is dropped from every table.
Args:
csv_path: Path to the file written by `nextpdf extract tables`.
Returns:
One parsed table per `# Table` block in the file.
"""
text = csv_path.read_text(encoding="utf-8")
tables: list[list[list[str]]] = []
current: list[str] = []
for line in text.splitlines(keepends=True):
if line.startswith("# Table ") and current:
tables.append(_parse_block(current))
current = []
current.append(line)
if current:
tables.append(_parse_block(current))
return tables
def _parse_block(lines: list[str]) -> list[list[str]]:
"""Parse one CSV block, discarding its leading comment row."""
reader = csv.reader(io.StringIO("".join(lines)))
rows = [row for row in reader if row]
return rows[1:] if rows and rows[0] and rows[0][0].startswith("# Table ") else rows
if __name__ == "__main__":
for index, table in enumerate(read_tables(Path("tables.csv")), start=1):
print(f"table {index}: {len(table)} rows")

رموز الخروج واكتشاف الأخطاء

قسم بعنوان «رموز الخروج واكتشاف الأخطاء»

تستخدم ⁨CLI⁩ ثلاثة رموز خروج. تحقَّق من $? في سكربتات الصَّدَفة، أو من $LASTEXITCODE في ⁨PowerShell⁩، للتفرُّع بناءً على النجاح أو الفشل. اقرأ رسائل التشخيص من ⁨stderr⁩، الذي يبقى منفصلًا عن البيانات الموجودة في ⁨stdout.⁩

رمز الخروجالمعنىأمثلة
0نجاح.اكتمل أمرٌ ما؛ طُبِع version.
1خطأ وقت التشغيل. تطبع ⁨CLI⁩ Error: <message> إلى ⁨stderr.⁩ملف الإدخال غير موجود أو ليس ملفًا عاديًّا، أو ⁨stdin⁩ فارغ، أو --base-url/--api-key مفقود أو غير صالح، أو أي خطأ من جانب الخادم (الترخيص مطلوب، أو تجاوز الحصة، أو ملف ⁨PDF⁩ غير موسوم، أو انتهاء مهلة البناء، أو فشل آخر في ⁨API⁩).
2خطأ استخدام، تُبلِغ عنه ⁨Click.⁩أمر أو خيار غير معروف (بما في ذلك وضع خيار مجموعة بعد الأمر الفرعي)، أو وسيط مطلوب مفقود مثل PDF_PATH.

يُرجِع كل فشل من جانب الخادم رمز الخروج 1 مع رسالة قابلة للقراءة البشرية على ⁨stderr.⁩ يُطلِق ⁨SDK⁩ استثناءً مُنمَّطًا — NextPDFLicenseError (بروتوكول نقل النص التشعبي (⁨HTTP⁩) 402)، أو QuotaExceededError (⁨HTTP 429⁩)، أو AstNoStructTreeError (⁨HTTP 422⁩، ملف ⁨PDF⁩ غير موسوم)، أو AstBuildTimeoutError (⁨HTTP 504⁩)، أو القاعدة NextPDFAPIError. تلتقط ⁨CLI⁩ هذه الاستثناءات كلها عبر القاعدة المشتركة NextPDFError، وتطبع الرسالة، وتخرج بالرمز 1. لا تعرض ⁨CLI⁩ رموز خروج مميزة لكل نوع فشل. للتمييز، على سبيل المثال، بين خطأ حصة وخطأ ترخيص في سكربت، افحص نص الرسالة على ⁨stderr⁩ أو استدعِ ⁨SDK⁩ مباشرةً (انظر نظرة عامة على ⁨Python⁩ للاطلاع على فئات الاستثناء).

استخدم نمط البرمجة النصية هذا لفصل البيانات عن التشخيصات:

#!/usr/bin/env bash
set -euo pipefail
# Credentials come from the environment, not the command line.
: "${NEXTPDF_BASE_URL:?set NEXTPDF_BASE_URL}"
: "${NEXTPDF_API_KEY:?set NEXTPDF_API_KEY}"
if nextpdf --output contract.ast.json ast contract.pdf; then
echo "AST written to contract.ast.json"
else
status=$?
echo "nextpdf failed with exit code ${status}" >&2
exit "${status}"
fi

باستخدام --output، تكتب ⁨CLI⁩ البيانات إلى الملف المُسمَّى وتطبع سطر التأكيد Written to <path> فقط إلى ⁨stderr⁩، فيبقى ⁨stdout⁩ فارغًا. بدون --output، تذهب البيانات إلى ⁨stdout⁩، ويمكنك إعادة توجيهها.

تستخدم كل وصفة أدناه أوامر وأعلامًا تم التحقق منها فقط. في كل حالة، تأتي بيانات الاعتماد من البيئة.

وصفة: استخراج جداول الفواتير إلى ⁨CSV⁩

قسم بعنوان «وصفة: استخراج جداول الفواتير إلى ⁨CSV⁩»

حوِّل مجلدًا من الفواتير إلى ملف ⁨CSV⁩ واحد لكل مستند من أجل جدول بيانات أو مسار معالجة محاسبي:

#!/usr/bin/env bash
set -euo pipefail
: "${NEXTPDF_BASE_URL:?set NEXTPDF_BASE_URL}"
: "${NEXTPDF_API_KEY:?set NEXTPDF_API_KEY}"
mkdir -p out
for pdf in invoices/*.pdf; do
name="$(basename "${pdf}" .pdf)"
nextpdf --output "out/${name}.csv" extract tables "${pdf}" --format csv
done

يحتوي كل out/<name>.csv على كتلة ⁨CSV⁩ واحدة لكل جدول مُكتشَف، مع ترويسة # Table N (pM) قبل كل كتلة. حلِّل الكتل باستخدام قارئ ⁨CSV⁩ الموضَّح أعلاه.

وصفة: بناء مخطط تفصيلي للمستند

قسم بعنوان «وصفة: بناء مخطط تفصيلي للمستند»

اجمع بين --headings-only وصيغة markdown لإنتاج مخطط تفصيلي سريع يمكنك قراءته أو لصقه في الملاحظات:

Terminal window
nextpdf --output outline.md extract text whitepaper.pdf --headings-only --format markdown

وصفة: مقارنة مراجعتين من ⁨PDF⁩

قسم بعنوان «وصفة: مقارنة مراجعتين من ⁨PDF⁩»

يُرجِع أمر ast في ⁨CLI⁩ شجرة مستند واحد؛ وليس له أمر فرعي للمقارنة. تتوفر المقارنة البنيوية في ⁨SDK⁩ باسم client.ast.get_ast_diff(...) وفي خادم بروتوكول سياق النموذج (⁨MCP⁩) بوصفها أداة nextpdf_diff. شغِّل المقارنة عبر ⁨SDK⁩:

"""Compare two PDF revisions structurally with the NextPDF SDK.
The API key is read from the environment, never hard-coded.
"""
import os
from pathlib import Path
from nextpdf import NextPDF
def diff_revisions(original: Path, modified: Path) -> None:
"""Print a structural diff summary between two PDF revisions.
Args:
original: Path to the earlier PDF revision.
modified: Path to the later PDF revision.
"""
base_url = os.environ["NEXTPDF_BASE_URL"]
api_key = os.environ["NEXTPDF_API_KEY"]
client = NextPDF(base_url=base_url, api_key=api_key)
result = client.ast.get_ast_diff(
original.read_bytes(),
modified.read_bytes(),
)
summary = result.summary
print(f"added: {summary.added_node_count}")
print(f"removed: {summary.removed_node_count}")
print(f"changed: {summary.changed_node_count}")
for entry in result.diff:
preview = entry.text_preview or ""
print(f" {entry.type:<8} {entry.node_type:<12} p{entry.page_index} {preview}")
if __name__ == "__main__":
diff_revisions(Path("contract-v1.pdf"), Path("contract-v2.pdf"))

لتشغيل المقارنة نفسها من عميل ذكاء اصطناعي (⁨AI⁩) بدلًا من سكربت، سجِّل خادم ⁨MCP⁩ واستدعِ أداة nextpdf_diff. انظر صفحة خادم ⁨MCP⁩ للغة ⁨Python⁩.

وصفة: تدفُّق ملف ⁨PDF⁩ من أداة أخرى

قسم بعنوان «وصفة: تدفُّق ملف ⁨PDF⁩ من أداة أخرى»

اقرأ بايتات ⁨PDF⁩ من ⁨stdin⁩ باستخدام - لربط nextpdf بعد أداة تُخرِج ملف ⁨PDF⁩ إلى ⁨stdout⁩ الخاص بها:

Terminal window
curl --silent https://example.com/report.pdf | nextpdf info -

يُخبِر الوسيط - الأمرَ بقراءة المستند من ⁨stdin.⁩ إذا لم تصل أي بايتات، فإن الأمر يُبلِّغ عن خطأ ويخرج بالرمز 1.

تبني ⁨CLI⁩ كل استجابة في الذاكرة وتكتبها مرة واحدة. إعادة التوجيه أو تمرير المخرجات عبر pipe أمر بسيط، لكن المخرجات لا تُنتَج تدريجيًّا. تُخزَّن شجرة ⁨AST⁩ كبيرة أو مجموعة جداول كبيرة بالكامل في المخزن المؤقت قبل أن يصل أول بايت إلى ⁨stdout⁩ أو إلى ملف --output. خطِّط للذاكرة والكُمون لاستجابات المستند الكامل، لا للتدفُّق.

يُنشئ كل استدعاء لـ nextpdf عميلًا واتصال ⁨HTTP⁩ جديدين، لذا فإن حلقة على ملفات كثيرة تفتح وتغلق اتصالًا لكل ملف. تكون تكلفة الاتصال عادةً صغيرة مقارنةً بوقت الاستخراج من جانب الخادم، لكنها تصبح عبئًا حقيقيًّا على نطاق واسع.

  • أعِد استخدام نقطة نهاية واحدة. وجِّه كل استدعاء إلى نشر ⁨NextPDF Connect⁩ نفسه حتى يتمكن الخادم من إعادة استخدام الذواكر المؤقتة المُحمَّاة ومجمَّعات الاتصال. تجنَّب توزيع دفعة عبر نقاط نهاية متعددة ما لم تكن توازن الأحمال عمدًا.
  • قيِّد العمل لكل استدعاء. استخدم --page، أو --page-start/--page-end، أو --token-budget لمعالجة الصفحات التي تحتاجها فقط. تُقلِّل نطاقات الصفحات الأصغر من وقت الخادم وحجم الاستجابة معًا؛ ويُحدِّد --token-budget سقف شجرة ⁨AST⁩ التي يتعين على عميلك قراءتها.
  • جمِّع الدُّفعات في عملية واحدة للمهام الكبيرة. بالنسبة للدُّفعات عالية الحجم، يُفضَّل استخدام ⁨Python SDK⁩ بدلًا من استدعاءات ⁨CLI⁩ المتكررة. يعيد عميل NextPDF أو AsyncNextPDF واحد طويل العمر استخدام اتصال ⁨HTTP⁩ مُجمَّع واحد عبر كل مستند، مما يُزيل بدء التشغيل لكل عملية وإعداد الاتصال الذي تدفعه حلقة ⁨CLI⁩ في كل مرة. تُظهِر نظرة عامة على ⁨Python⁩ دورة حياة العميل، ويدعم AsyncNextPDF الاستخراج المتزامن عبر ملفات ⁨PDF⁩ كثيرة.
  • أبقِ السجلات خارج مسار البيانات. اترك --log-level على قيمته الافتراضية لعمليات الدُّفعات. تذهب سجلات التشخيص إلى ⁨stderr⁩ ولا تُفسِد بيانات ⁨stdout⁩، لكن رفع المستوى إلى debug يضيف ضوضاء وعبئًا طفيفًا.