> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wolffi.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# إنشاء القدرات

> دليل خطوة بخطوة لبناء قدرة وولف فيش جديدة

# ما الذي ستبنيه

يوضّح هذا الدليل عملية إنشاء قدرة جديدة من الصفر. ستحصل في نهايته على قدرة عاملة يكتشفها وولف فيش ويحمّلها ويتيحها لنموذج اللغة.

<Note>
  **يستطيع وولف فيش فعل هذا كلّه بنفسه.** تتيح له قدرة [`skills`](/ar/capabilities/built-in-capabilities) المدمجة أن يسرد قدراته ويبحث فيها ويفعّلها/يعطّلها ويحذفها و**يؤلّفها** أثناء التشغيل — فيتحوّل «افعل هذا في كل مرة» إلى مهارة قابلة لإعادة الاستخدام دون أن تلمس نظام الملفات. وهذا الدليل لحين تفضّل بناء واحدة بيدك.
</Note>

## تثبيت قدرة

لست مضطرًا للتعامل مع نظام الملفات. أسرع طريقة لإضافة قدرة هي **إفلاتها على الإعدادات ← المُخيخ** — أو النقر على حقل الاستيراد للتصفّح. تُقبل ثلاثة أشكال:

<CardGroup cols={1}>
  <Card title="ملف SKILL.md واحد" icon="file-lines">
    إجراء Markdown مع واجهة YAML (الاسم، الوصف، والمحفّزات الاختيارية). يُستورد كـ **مهارة نقية** — دون أدوات للتثبيت.
  </Card>

  <Card title="مجلد قدرة" icon="folder">
    مجلد يحتوي على `SKILL.md` بالإضافة إلى `plugin/` اختياري (`index.mjs`) وملف `package.json`. القدرة **الكاملة** — بأدواتها.
  </Card>

  <Card title="أرشيف ‎.zip" icon="file-zipper">
    المجلد نفسه مضغوطًا (مثل *ضغط* في ماك). يُفكّ ضغطه إلى مجلد مؤقّت، ويُفحص، ثم يُضاف.
  </Card>
</CardGroup>

يُتحقَّق من كل عنصر **قبل كتابة أي شيء على القرص**، لذا لا يمكن لاستيراد فاشل أن يُثبّت جزئيًا أو يطمس قدرة موجودة. عند النجاح تُحدَّث القائمة وتظهر قدرتك فورًا — موسومة بـ **غير معروف** بدلًا من **رسمي**، لأنها قدرتك وليست مدمجة. وإذا فشل التحقق، تعرض اللوحة بالضبط ما حدث من خطأ (اسم مفقود، أو YAML غير صالح، أو مجلد `plugin/` بلا ملف دخول، أو اسم مكرر…) في كتلة شيفرة، فتُصلحه وتعيد الإفلات.

لإزالة قدرة مستوردة، انقر **أيقونة سلة المهملات** بجوارها وأكّد — فيُحذف مجلدها نظيفًا من `brain/cerebellum/`. القدرات الرسمية والمدمجة لا يوجد لها زر حذف ولا يمكن إزالتها بهذه الطريقة.

<Tip>
  تفضّل الطرفية؟ أنشئ المجلد يدويًا بدلًا من ذلك — تُظهر الخطوات أدناه ذلك تمامًا. بعد تعديل الملفات يدويًا، انقر **إعادة المزامنة** في لوحة المُخيخ (أو أعد تشغيل وولف فيش) لالتقاط التغييرات. تستحق القواعد في [التغليف للاستيراد والمشاركة](#التغليف-للاستيراد-والمشاركة) الاتّباع في الحالتين — فهي ما يُبقي القدرة نظيفة وقابلة للنقل.
</Tip>

## جرّبها الآن: قدرات نموذجية

تريد رؤية تدفّق الاستيراد قبل بناء قدرتك الخاصة؟ نزّل هذه الحزمة المكوّنة من ثلاث قدرات صغيرة جاهزة للتشغيل، ثم **أفلت ملف `.zip` على الإعدادات ← المُخيخ**. تُستورد الثلاث دفعة واحدة — يُتحقَّق منها وتُحمَّل في الحال، دون طرفية ودون كتابة أي شيفرة.

<Card title="نزّل القدرات النموذجية (.zip)" icon="download" href="https://cdn.wolffi.sh/general/wolffish-extensibility-examples.zip">
  `coin-flip` و`dice-roller` و`color-mixer` — ثلاث قدرات مكتفية بذاتها تستوردها بإفلاتٍ واحد وتراها حيّةً قيد العمل.
</Card>

كل واحدة قدرة كاملة (SKILL.md + إضافة)، فبمجرد استيرادها يمكنك تشغيلها مباشرةً من المحادثة:

| القدرة        | ماذا تفعل                    | جرّب أن تقول               |
| ------------- | ---------------------------- | -------------------------- |
| `coin-flip`   | تقذف عملة عادلة              | *«اقذف عملة»*              |
| `dice-roller` | ترمي نردًا بأي عدد من الأوجه | *«ارمِ 2d20»*              |
| `color-mixer` | تحوّل الألوان بين الصيغ      | *«حوّل ‎#ff5733‎ إلى RGB»* |

تظهر موسومةً بـ **غير معروف** (فهي قدراتك لا قدرات مدمجة) ويمكن إزالتها في أي وقت بأيقونة سلة المهملات. افتحها في `brain/cerebellum/` لترى بالضبط كيف تُبنى قدرة صغيرة — فهي بالشكل نفسه الذي عليه [مثال الطقس أدناه](#مثال-حقيقي-وعامل-قدرة-الطقس).

## الخطوة 1: أنشئ المجلد

```bash theme={null}
mkdir -p ~/.wolffish/workspace/brain/cerebellum/my-capability
```

## الخطوة 2: اكتب SKILL.md

أنشئ `SKILL.md` بواجهة YAML وتعليمات Markdown:

```yaml theme={null}
---
name: my-capability
description: What this capability does in one sentence
triggers:
  - keyword1
  - keyword2
tools:
  - name: my_tool
    description: What this tool does
    parameters:
      type: object
      properties:
        input:
          type: string
          description: The input to process
      required:
        - input
---

# My Capability

Instructions for the LLM on how to use this capability.

When the user asks about [topic], use the `my_tool` tool with the relevant input.
Always [specific behavior guideline].
Never [specific constraint].
```

## الخطوة 3: اكتب الإضافة (اختياري)

إذا احتاجت قدرتك إلى كود مخصص، أنشئ `plugin/index.mjs`:

```javascript theme={null}
export default {
  name: 'my-capability',
  tools: ['my_tool'],

  async init(context) {
    // context.pluginDir — path to this plugin folder
    // context.workspaceRoot — path to ~/.wolffish/workspace/
  },

  async execute(toolName, args) {
    if (toolName === 'my_tool') {
      // Your logic here
      const result = doSomething(args.input)
      return { success: true, output: result }
    }
    return { success: false, output: '', error: `Unknown tool: ${toolName}` }
  },

  async destroy() {
    // Cleanup on shutdown
  }
}
```

## الخطوة 4: أضف أنماط أمان (إذا لزم الأمر)

إذا كانت أداتك قادرة على إجراء عمليات خطرة، أضف أنماطًا إلى واجهة YAML لـ SKILL.md:

```yaml theme={null}
danger_patterns:
  - "dangerous_regex_here"
confirm_patterns:
  - "risky_but_allowed_regex"
```

## الخطوة 5: اختبرها

أرسل رسالة تطابق مشغّلاتك. تحقق من:

1. **ملف تصحيح السياق** (`brain/prefrontal/.debug/`) — هل أُدرج ملف SKILL.md الخاص بك في السياق؟
2. **سجل الأحداث** (`brain/corpus/`) — هل أُطلق `tool.called` باسم أداتك؟
3. **سجل المهام** (`brain/motor/tasks/`) — هل يُظهر ملف المهمة تنفيذًا ناجحًا؟

## مثال حقيقي وعامل: قدرة الطقس

استخدم الشرح أعلاه عناصر نائبة. إليك **قدرة كاملة يمكنك نسخها ولصقها اليوم** وتعمل فورًا — تستدعي واجهة [Open-Meteo](https://open-meteo.com) المجانية التي **لا تحتاج إلى مفتاح API ولا إلى تسجيل**. تُظهر النمط كاملًا من البداية إلى النهاية: مخطط أداة حقيقي، ونداءان شبكيان متسلسلان (تحويل اسم المكان إلى إحداثيات ← جلب طقسه الحالي)، والتحقق من المدخلات، ومعالجة الأخطاء بأناقة، ونتيجة بلغة طبيعية نظيفة.

### بنية المجلد

```
~/.wolffish/workspace/brain/cerebellum/weather/
├── SKILL.md
└── plugin/
    └── index.mjs
```

<Note>
  القدرات المدمجة **مسبوقة بنقطة** (`.shell`، `.web-search`) لتبقى مخفية عن `ls` العادي. قدراتك الخاصة لا تحتاج إلى النقطة — `weather` كافٍ وأسهل في الإيجاد. يُكتشف الاثنان بالطريقة نفسها.
</Note>

### SKILL.md

```yaml theme={null}
---
name: weather
description: Get the current weather for any city or place
triggers:
  - weather
  - temperature
  - forecast
  - how hot
  - how cold
  - is it raining
tools:
  - name: weather_get
    description: Get current weather conditions for a city, town, or place name.
    parameters:
      type: object
      properties:
        location:
          type: string
          description: City or place name, e.g. "Tokyo" or "Riyadh, Saudi Arabia"
        units:
          type: string
          enum: [celsius, fahrenheit]
          description: Temperature units. Defaults to celsius.
      required:
        - location
---

# Weather

When the user asks about the weather, temperature, or forecast for a place,
call `weather_get` with the location they named.

- If they don't specify units, use celsius (the API default).
- If they don't name a location, ask which city before calling the tool — never guess.
- Data comes from Open-Meteo (no API key). Report sky, temperature, humidity, and
  wind in one natural sentence.
```

### plugin/index.mjs

```javascript theme={null}
// Real, working plugin — no API key required.
// Open-Meteo docs: https://open-meteo.com/en/docs

const GEOCODE = 'https://geocoding-api.open-meteo.com/v1/search'
const FORECAST = 'https://api.open-meteo.com/v1/forecast'

// WMO weather-interpretation codes → human text
const SKY = {
  0: 'clear sky', 1: 'mainly clear', 2: 'partly cloudy', 3: 'overcast',
  45: 'fog', 48: 'rime fog',
  51: 'light drizzle', 53: 'drizzle', 55: 'dense drizzle',
  61: 'light rain', 63: 'rain', 65: 'heavy rain',
  71: 'light snow', 73: 'snow', 75: 'heavy snow',
  80: 'rain showers', 81: 'rain showers', 82: 'violent rain showers',
  95: 'thunderstorm', 96: 'thunderstorm with hail', 99: 'severe thunderstorm'
}

export default {
  name: 'weather',
  tools: ['weather_get'],

  async execute(toolName, args) {
    if (toolName !== 'weather_get') {
      return { success: false, output: '', error: `Unknown tool: ${toolName}` }
    }

    const location = String(args.location ?? '').trim()
    if (!location) {
      return { success: false, output: '', error: 'Missing required "location" argument' }
    }
    const units = args.units === 'fahrenheit' ? 'fahrenheit' : 'celsius'

    try {
      // 1) Resolve the place name to coordinates
      const geoRes = await fetch(
        `${GEOCODE}?name=${encodeURIComponent(location)}&count=1&language=en&format=json`
      )
      if (!geoRes.ok) {
        return { success: false, output: '', error: `Geocoding failed (HTTP ${geoRes.status})` }
      }
      const place = (await geoRes.json()).results?.[0]
      if (!place) {
        return { success: false, output: '', error: `No location found for "${location}"` }
      }

      // 2) Fetch current conditions for those coordinates
      const res = await fetch(
        `${FORECAST}?latitude=${place.latitude}&longitude=${place.longitude}` +
        `&current=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code` +
        `&temperature_unit=${units}`
      )
      if (!res.ok) {
        return { success: false, output: '', error: `Forecast failed (HTTP ${res.status})` }
      }

      const data = await res.json()
      const c = data.current
      const u = data.current_units
      const sky = SKY[c.weather_code] ?? `code ${c.weather_code}`
      const where = [place.name, place.admin1, place.country].filter(Boolean).join(', ')

      return {
        success: true,
        output:
          `Weather in ${where}: ${sky}, ${c.temperature_2m}${u.temperature_2m}, ` +
          `humidity ${c.relative_humidity_2m}${u.relative_humidity_2m}, ` +
          `wind ${c.wind_speed_10m} ${u.wind_speed_10m}.`
      }
    } catch (err) {
      return { success: false, output: '', error: `Weather lookup failed: ${err.message}` }
    }
  }
}
```

### لماذا لا توجد `danger_patterns`

تقرأ `weather_get` بيانات عامة عبر HTTPS فقط — لا يمكنها حذف أو إنفاق أو الكتابة فوق أي شيء. الأدوات للقراءة فقط مثل هذه لا تحتاج إلى **أي** أنماط أمان. احتفظ بأنماط `danger_patterns` / `confirm_patterns` للأدوات التي قد تسبب ضررًا فعليًا (انظر [أنماط الأمان](/ar/extending/safety-patterns)).

### جرّبها

أعد تشغيل وولف فيش ليحمّل المخيخ المجلد الجديد، ثم اسأل:

> *"ما حالة الطقس في طوكيو؟"*

سترى الوكيل يستدعي `weather_get` ويردّ بشيء مثل *"Weather in Tokyo, Tokyo, Japan: partly cloudy, 22.4°C, humidity 61%, wind 9.3 km/h."* تتبّع النداء في `brain/corpus/<today>.log.md` للتأكد من الوسائط والمخرجات.

<Tip>
  هذا هو القالب لأي تكامل للقراءة فقط تقريبًا. استبدل نداءَي `fetch` بواجهة أخرى — Hacker News، أو أسعار العملات، أو خادمك الخاص — مع الحفاظ على شكل تحقّق ← نداء ← تنسيق ← إرجاع، وستحصل على قدرة جديدة في دقائق.
</Tip>

## مهارة نقية مقابل إضافة: متى تستخدم أيًا منهما

استخدم **مهارة نقية** عندما يمكن إنجاز قدرتك باستخدام أدوات موجودة (مثل `shell_exec` أو `file_read`)، أو عندما تريد تشكيل سلوك الوكيل بدون إضافة أدوات جديدة. تُحقن تعليمات SKILL.md في قسم `<skills>` في الموجّه عند تفعيلها.

استخدم **إضافة** عندما تحتاج إلى منطق مخصص لا يمكن التعبير عنه بأوامر شل أو عمليات ملفات — استدعاءات واجهات برمجية، ومعالجة بيانات، وبروتوكولات مخصصة، إلخ.

<Tip>
  ابدأ بمهارة نقية. إذا وجدت أن نموذج اللغة يكافح مع سلاسل أدوات معقدة، رقّ إلى إضافة. المهارات النقية أبسط في الكتابة والتصحيح والمشاركة.
</Tip>

### المهارات النقية الدائمة

بعض المهارات النقية يجب أن تنطبق على كل رسالة — انضباط التخطيط، تنسيق المخرجات، قيود الأمان. استخدم المشغّل الشامل `"*"`:

```yaml theme={null}
---
name: planning
description: Think before acting.
triggers:
  - "*"
tools: []
---

# Planning

Before executing any multi-step task, state your plan first...
```

تُحقن المهارات الدائمة قبل المهارات المطابقة بالكلمات المفتاحية و**لا تُحتسب ضمن حدّ الـ 3**. يمكن لرسالة واحدة تفعيل حتى 3 مهارات مطابقة بالكلمات المفتاحية بالإضافة إلى أي عدد من المهارات الدائمة.

## قائمة فحص القدرة

قبل شحن قدرتك:

* [ ] يحتوي SKILL.md على مشغّلات دقيقة (اختبر بصيغ مختلفة)
* [ ] أوصاف معاملات الأدوات واضحة (يقرؤها نموذج اللغة)
* [ ] أنماط الخطر تغطي كل العمليات المدمرة
* [ ] أنماط التأكيد تغطي العمليات الخطرة لكنها مشروعة
* [ ] تتعامل الإضافة مع الأخطاء بأناقة (تُرجع `ToolResult` بقيمة `success: false`)
* [ ] متن Markdown يحتوي على تعليمات واضحة ومحددة
* [ ] تم الاختبار مع نماذج سحابية ومحلية على حد سواء

## التغليف للاستيراد والمشاركة

سواء أضاف أحدهم قدرتك كمجلد أو كملف `.zip`، يُجري المستورد الفحوص نفسها. واستيفاؤها يُبقي قدرتك نظيفة وقابلة للنقل وآمنة للمشاركة:

* **ملف `SKILL.md` واحد فقط.** يجده المستورد في الجذر أو حتى ثلاثة مجلدات في العمق، لذا يعمل ضغط مجلد (`my-skill/SKILL.md`) مباشرةً. ووجود صفر أو أكثر من ملف `SKILL.md` يُرفض.
* **واجهة صالحة.** يجب أن تبدأ بكتلة `---` تُحلَّل كـ YAML ولها `name` غير فارغ. والحقول الاختيارية تُفحص بنيتها عند وجودها: يجب أن يكون `triggers` و`requires` قوائم نصوص، وكل عنصر في `tools` يحتاج إلى `name`. انظر [مرجع SKILL.md](/ar/capabilities/skill-md-reference).
* **شيء لتحميله.** ملف SKILL.md بمتن فارغ *وبلا* أدوات لا يفعل شيئًا، ويُرفض — امنحه متنًا أو أدوات أو كليهما.
* **الإضافة تحتاج ملف دخول.** إذا شحنت مجلد `plugin/`، فيجب أن يحتوي على `index.mjs` أو `index.js` أو `index.cjs`. وإعلان `tools` في مجلد/zip بلا `plugin/` يُرفض — إذ لن يكون للأدوات ما يشغّلها.
* **اسم فريد.** لا يمكن أن يتعارض `name` مع قدرة لديك بالفعل (مدمجة أو مستوردة). ويُشتق اسم المجلد على القرص من `name` بتحويله إلى صيغة سهلة (`My Cool Skill` ← `my-cool-skill`).
* **تُزال المخلفات تلقائيًا.** تُتخطّى `node_modules/` و`.git/` و`.DS_Store` و`__MACOSX/` عند الاستيراد — لا داعي لإزالتها أولًا، واترك `node_modules/` خارج ملف zip لإبقائه صغيرًا.
* **حدود الحجم.** `SKILL.md` ≤ 1 ميغابايت، والحمولة الكلية ≤ 50 ميغابايت، و≤ 5000 ملف. وتُستخرج ملفات zip مع حماية من اجتياز المسارات.
* **التبعيات تُثبَّت عند الحاجة.** أي شيء في `requires` (أدوات النظام) أو في `package.json` (حزم npm) يُثبَّت **أول مرة تعمل فيها القدرة**، لا عند الاستيراد — فيبقى الاستيراد سريعًا وآمنًا دون اتصال.

<Tip>
  أبسط طريقة لتسليم قدرة لأحدهم هي ضغط المجلد وإرساله — يُفلت ملف `.zip` على لوحة المُخيخ لديه فتصبح فعّالة. وللتوزيع المستمر، ادفعها إلى git بدلًا من ذلك (انظر [قدرات المجتمع](/ar/capabilities/community-capabilities)).
</Tip>
