خط محتوى الوكيل من البداية إلى النهاية: من الموجز إلى منشور لكل حساب ثم النقرات، مع كل استدعاء في Shager بالترتيب

خط عمل كامل لوكيل على Shager بلغة TypeScript: الموجز، والوسائط، ونص لكل حساب، والمسودة، وموافقة العميل، وجدولة متدرّجة، والتحقق من التسليم، والنقرات.

يتكون خط المحتوى الكامل لوكيل على Shager من ثماني مراحل: قراءة الموجز، وتجهيز الوسائط على رابط عام، وتحديد الحسابات المستهدفة، وكتابة نص لكل حساب، وإنشاء مسودة برابط مُتتبَّع، والحصول على الموافقة، والجدولة مع إرسال متدرّج، ثم التحقق من التسليم وجلب النقرات لكل حساب. يتولى الوكيل المراحل المتكررة، ويمسك الإنسان بالموجز والموافقة. وكل خطوة في Shager هي استدعاء REST واحد أو أداة MCP واحدة تحمل الاسم نفسه.

فيما يلي السلسلة كاملة في برنامج TypeScript واحد يعمل مع REST API، مع شرح منطق كل مرحلة. وإن كنت تفضّل تشغيلها من عميل وكيل، فاستدعاءات الأدوات هي نفسها.

ما المراحل، ومن المسؤول عن كل منها؟

المرحلة المسؤول الاستدعاء في Shager
1. الموجز إنسان لا يوجد
2. الوسائط الوكيل أو خط إنتاج المواد لديك upload_media_from_url
3. الأهداف الوكيل list_channels، list_groups
4. النصوص الوكيل (نموذجك اللغوي) لا يوجد
5. المسودة الوكيل create_post
6. الموافقة العميل request_approval
7. الجدولة وكيل يملك publish، أو إنسان schedule_post
8. التحقق والقياس الوكيل get_post، get_analytics

المرحلة الرابعة هي الوحيدة التي تحتاج إلى نموذج لغوي. كل ما عداها توصيلات حتمية، ويجب أن تُكتب برمجيًا بدل أن تُترك لارتجال النموذج. هذا الفصل هو أهم درس في موثوقية التوزيع بالوكلاء: دع النموذج يكتب، ودع الكود ينسّق.

مخطط: موجز، ثم مسودة لكل حساب، ثم موافقة العميل، ثم نشر موزّع على فترات، ثم تقرير نقرات لكل حساب.
خمس مراحل ثابتة. الموافقة على كل منشور اختيارية لكل عميل، أما النشر فيتطلب مالكًا بشريًا وصلاحية نشر منفصلة للمفتاح.

المرحلة 1: ماذا يتضمن الموجز؟

الموجز الذي يستطيع خط العمل التصرف بناءً عليه يتضمن ثمانية حقول. وأي نقص يجعل الوكيل يبدأ في التخمين.

type Brief = {
  workspace: string;          // "Cedar Clinics"
  group: string;              // "All clinics"
  message: string;            // the facts, written by a human
  mediaUrl: string;           // public URL of the finished asset
  media: { width: number; height: number; durationMs?: number };
  link: string;               // landing page to track
  campaign: string;           // "flu-shots-2026"
  publishAt: string;          // "2026-10-27T08:00:00-05:00"
};

يحمل publishAt فرق التوقيت عن قصد. يشترط Shager صيغة ISO 8601 مع فرق التوقيت في schedule_at، وكتابة الفرق في الموجز تعني ألا يضطر أحد إلى تذكّر المنطقة الزمنية التي قُصدت بعبارة «الثامنة صباحًا».

الإعداد: دالة مساعدة واحدة لكل الاستدعاءات

const API = "https://app.shager.ai/api/v1";

class ShagerError extends Error {
  constructor(public status: number, public code: string, message: string) {
    super(`${status} ${code}: ${message}`);
  }
}

async function shager<T>(method: string, path: string, body?: unknown): Promise<T> {
  for (let attempt = 0; attempt < 3; attempt++) {
    const res = await fetch(`${API}${path}`, {
      method,
      headers: {
        Authorization: `Bearer ${process.env.SHAGER_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    const json = await res.json();
    if (res.ok) return json.data as T;
    if (res.status === 429) {
      await new Promise((r) => setTimeout(r, 60_000));
      continue;
    }
    throw new ShagerError(res.status, json.error.code, json.error.message);
  }
  throw new Error("Rate limited three times in a row");
}

لا تُعاد المحاولة إلا مع 429. أما 403 أو 422 فهما معلومة، وإعادة المحاولة لا تفعل سوى تكرار الرفض نفسه. الحد هو 120 طلبًا في الدقيقة لكل مفتاح، ولن يقترب خط العمل هذا منه، لأن استدعاء create_post واحدًا يغطي كل الحسابات.

المرحلة 2: الوسائط

يستورد Shager الوسائط من رابط، فلا يحتاج ما أنتج المادة (محرر، أو مهمة تصيير، أو أداة توليد) إلا إلى وضعها في مكان يمكن الوصول إليه علنًا.

const workspaces = await shager<Array<{ id: string; name: string }>>("GET", "/workspaces");
const ws = workspaces.find((w) => w.name === brief.workspace);
if (!ws) throw new Error(`No workspace named ${brief.workspace}`);

const media = await shager<{ id: string; kind: string }>("POST", `/workspaces/${ws.id}/media`, {
  url: brief.mediaUrl,
  width: brief.media.width,
  height: brief.media.height,
  duration_ms: brief.media.durationMs,
});

للعرض والارتفاع والمدة أهمية في الفيديو العمودي. بوجودها يتحقق Shager من الريلز وShorts والقصص وفق قواعد كل شبكة قبل الجدولة، فالمقطع الأطول من المسموح لصيغة ما يفشل الآن مع سبب مفهوم، بدل أن يفشل على شبكة واحدة لحظة الإرسال.

مخطط أعمدة: النقرات على روابط Shager المُتتبَّعة لكل حساب، ويوضح أي صفحة جلبت الزيارات.
ما يحصل عليه الوكيل من get_analytics. (توضيح، وليست بيانات حقيقية)

المرحلة 3: تحديد الأهداف

type Channel = { id: string; network: string; name: string; status: string; formats: string[] };

const groups = await shager<Array<{ id: string; name: string; channel_ids: string[] }>>(
  "GET", `/workspaces/${ws.id}/groups`,
);
const group = groups.find((g) => g.name === brief.group);
if (!group) throw new Error(`No group named ${brief.group}`);

const channels = (await shager<Channel[]>("GET", `/workspaces/${ws.id}/channels`))
  .filter((c) => group.channel_ids.includes(c.id) && c.status === "ACTIVE");

التصفية على ACTIVE هنا تتيح لك الإبلاغ عن الحسابات غير المربوطة منذ البداية. الصفحة التي انتهت صلاحية رمزها موضوع حديث مع العميل، لا شيء تكتشفه بعد فشل المنشور.

المرحلة 4: نص لكل حساب

هنا يثبت نموذجك جدواه. الدالة أدناه هيكل فارغ عن قصد، فاربط بها أي نموذج وأي SDK يستخدمه فريقك.

async function writeCaptions(brief: Brief, channels: Channel[]): Promise<Map<string, string>> {
  // Prompt your model with: the brief's facts, each channel's name and network,
  // and these rules: keep facts identical, vary the opening and structure,
  // use the local detail in the channel name, no raw URLs, network-appropriate length.
  // Return channel_id -> caption, and validate that every channel got one.
  throw new Error("connect your model here");
}

const captions = await writeCaptions(brief, channels);

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

المرحلة 5: إنشاء المسودة

type PostView = {
  id: string;
  status: string;
  destinations: Array<{ channel_id: string; account: string; network: string; format: string; state: string; url: string | null; error: string | null }>;
};

const draft = await shager<PostView>("POST", `/workspaces/${ws.id}/posts`, {
  label: `${brief.campaign} ${brief.publishAt.slice(0, 10)}`,
  caption: brief.message,
  channel_ids: channels.map((c) => c.id),
  media_ids: [media.id],
  per_channel: channels.map((c) => ({ channel_id: c.id, caption: captions.get(c.id)! })),
  cta_url: brief.link,
  campaign: brief.campaign,
  first_comment: `Details: ${brief.link}`,
  stagger_seconds: [30, 90],
});

يمنح cta_url كل حساب رابطًا قصيرًا خاصًا به، مع utm_content يعرّف الحساب. ويُنشر first_comment مباشرة بعد ظهور كل منشور. أما stagger_seconds فيباعد بين الحسابات على الشبكة نفسها بفاصل عشوائي من 30 إلى 90 ثانية، وهي القيمة الافتراضية أيضًا. والنتيجة منشور بحالة DRAFT.

المرحلة 6: الموافقة

const approval = await shager<{ approval_url: string; emailed: boolean }>(
  "POST", `/workspaces/${ws.id}/posts/${draft.id}/approval`,
  { reviewer_name: "Priya Shah", reviewer_email: "priya@cedarclinics.example", send_email: true },
);
console.log("Approval link:", approval.approval_url);

يفتح المراجِع الرابط ويوافق من دون تسجيل الدخول. يتوقف خط العمل هنا ويُستأنف لاحقًا، فالموافقة تستغرق ساعات لا أجزاء من الثانية. احفظ draft.id، والتقطه في تشغيل ثانٍ يتحقق عبر get_post من الحالة APPROVED. وإن أردت أن تعرف لماذا تأتي نقطة التوقف هذه بعد الصياغة لا بعد النشر، فمقال مسارات موافقة العميل يعرض الحجة.

المرحلة 7: الجدولة

const post = await shager<PostView>("GET", `/workspaces/${ws.id}/posts/${draft.id}`);
if (post.status !== "APPROVED") {
  console.log(`Not approved yet: ${post.status}`);
} else {
  try {
    await shager<PostView>("POST", `/workspaces/${ws.id}/posts/${draft.id}/schedule`, {
      schedule_at: brief.publishAt,
    });
  } catch (e) {
    if (e instanceof ShagerError && e.code === "rejected") {
      console.error("A network rule failed:", e.message); // fix input, do not loop
    } else throw e;
  }
}

تحتاج هذه الخطوة إلى مفتاح يملك صلاحية publish، يمنحه إنسان من https://app.shager.ai/dashboard/agents. يتحقق Shager هنا من كل وجهة وفق قواعد شبكتها، فيصل الرد 422 rejected قبل أن يدخل أي شيء إلى قائمة الانتظار.

المرحلة 8: التحقق من التسليم والقياس

بعد موعد النشر، افحص كل وجهة. حالات التسليم هي PENDING وQUEUED وPROCESSING وPUBLISHED وFAILED وSKIPPED.

const after = await shager<PostView>("GET", `/workspaces/${ws.id}/posts/${draft.id}`);
for (const d of after.destinations) {
  if (d.state === "FAILED" || d.state === "SKIPPED") console.warn(d.account, d.state, d.error);
}

const stats = await shager<{ total_clicks: number; by_account: Array<{ account: string; network: string; clicks: number }> }>(
  "GET", `/workspaces/${ws.id}/analytics?from=2026-10-27T00:00:00-05:00&to=2026-11-03T00:00:00-05:00`,
);
console.table(stats.by_account);

النقرات لكل حساب هي نهاية دور Shager وبداية دور العميل. أما ربطها بالعملاء المحتملين عبر utm_content في نظام CRM فيشرحه مقال نسبة العملاء المحتملين إلى كل حساب.

الأسئلة الشائعة

هل يشغّل هذا عميلُ وكيل أم سكربت؟

الاثنان يصلحان. السكربت أفضل للمراحل الثابتة ولكل ما يعمل على مؤقّت. وعميل الوكيل مثل Claude Code أفضل حين يكون الموجز غامضًا وتريد تحسين النصوص بشكل تفاعلي. فرق كثيرة تستخدم الوكيل في المرحلة الرابعة والكود في كل ما عداها.

ماذا لو استهدف الموجز حسابات لدى أكثر من عميل؟

شغّل خط العمل مرة لكل مساحة عمل. مساحات العمل تفصل العملاء عن قصد: حساباتهم وموافقاتهم وروابطهم وتحليلاتهم لا تختلط أبدًا، وهذا أيضًا ما يتوقعه العميل حين يقرأ تقريره.

أين أجد تعريفات الحقول بدقة؟

في مستند OpenAPI، الذي يسرد كل عملية ومدخل وصلاحية. وفي صفحة الوكلاء المعلومات نفسها بصيغة أقصر للوكلاء ومن يبنونها.

الخطوة التالية

شغّل خط العمل مرة واحدة مع حساب اختبار واحد ومسودة لن تجدولها أبدًا. اقرأ المسودة في Shager، وافحص روابط كل حساب، ثم وسّع النطاق إلى المجموعة كاملة. وحين يثبت ذلك، غلّف المراحل في روتين أسبوعي كي يعمل خط العمل وفق جدول، لا وفق ذاكرة أحدهم.

جرّبه على حساباتك.

مجانًا خلال فترة الإطلاق، دون بطاقة. اربط حساباتك، واكتب مرة واحدة، وShager ينشر ويقيس.