חברו את WhatsApp ל-Claude, לא את Claude ל-WhatsApp

פורסם במקור כפוסט בלינקדאין, והורחב כאן.

הקוד ב־GitHubOmerLapidot/whatsapp-connector

בניתי connector שמכניס את WhatsApp לתוך Claude Code, במקום להכניס את Claude לתוך WhatsApp: daemon מקומי שמחזיק session של WhatsApp Web, CLI בשם wa שמדבר איתו דרך unix socket פרטי, ו-skill שמלמד את הסוכן להשתמש בו. קריאה פתוחה לגמרי — כל צ'אט, כל קבוצה, וחיפוש על פני כל השיחות. שליחה מוגבלת ל-allow list שהסוכן לא יכול להרחיב בלי לחיצה שלי על המסך.

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

הכיוון ההפוך לא פותר בעיה

לא הבנתי את הגאונות הגדולה בלהעביר את תיבת הטקסט שדרכה מתקשרים עם Claude מהאפליקציה שלהם ל-WhatsApp. איזו בעיה זה פותר? אולי פספסתי משהו, אבל לא מצאתי בזה הגיון. אתם מקבלים את אותו מודל, בממשק פחות נוח, בלי הקבצים שלכם ובלי הכלים שלכם.

הכיוון השני כן עושה שכל. Claude Code — או בגרסתו העממית Cowork — יכול לשנות את החיים של כל מי שעובד מול מחשב ביומיום. כל מה שהוא צריך כדי לעזור זה לקבל את הקונטקסט המלא של מרחב העבודה שלכם.

רוב הממשקים כבר התיישרו ובנו connectors רשמיים: מייל, Slack וכן הלאה. WhatsApp הוא דוגמה נהדרת ל-data silo שצריך לשבור ידנית. בכל עסק ישראלי שעבדתי מולו הוא הערוץ המרכזי ולא ערוץ צדדי — לקוחות, ספקים, קבוצות עבודה, לידים ישנים, כולם שם.

מה בניתי בפועל

הבסיס הוא ספריית open source שעשתה reverse engineering ל-API של WhatsApp Web. מעליה יש שלוש שכבות:

  • daemon שרץ תמיד ומחזיק את ה-session. הוא זה שמדבר עם WhatsApp; שום דבר אחר לא.
  • CLI בשם wa שמדבר עם ה-daemon דרך unix socket מקומי בלבד.
  • skill שמסביר לסוכן מה כל פקודה מחזירה, מתי לחכות, ומה אסור לו לעשות.

ההפרדה הזאת היא לא אסתטיקה. כל מגבלה שאני רוצה שתהיה דטרמיניסטית יושבת ב-daemon, במקום שבו הסוכן לא יכול לעקוף אותה על ידי ניסוח מחדש של הבקשה.

צד הקריאה פשוט ורחב: רשימת צ'אטים, קריאת הודעות אחרונות מצ'אט, חיפוש, חברי קבוצה, פענוח שם לאיש קשר, והורדת קובץ מצורף. קריאה בודדת מוגבלת ל-200 הודעות (30 כברירת מחדל), ויש דגל --all שמושך את כל מה שסונכרן לתוך ה-session — שימושי מאוד, וכבד מאוד על החלון של המודל.

פרט קטן שאני אוהב: mark-read זמין על כל צ'אט. הוא לא שולח הודעה ולא מייצר תוכן — לכל היותר מדליק וי כחול על הודעות שבאמת נקראו — ולכן אין סיבה לגדר אותו. הגדר צריך לחסום דברים שמגיעים לאנשים אחרים, לא כל פעולה שנשמעת מסוכנת.

ה-allow list היא ההחלטה

הכלל אצלי הוא: Claude יכול לקרוא הכל, ולשלוח רק למי שנמצא ברשימה שהוא לא יכול להרחיב בלי אישור שלי על המסך. זה מגודר בשלושה מקומות — שניים שה-daemon אוכף, ואחד שה-skill רק מנחה.

שליחה בשני שלבים

wa send לא שולח כלום. הוא מחזיר token ותצוגה מקדימה של הטקסט המדויק. רק wa send-confirm עם ה-token באמת משדר, וה-token חי בערך שתי דקות. ה-skill מנחה את הסוכן להראות לי את התצוגה המקדימה ולקבל "כן" מפורש באמצע.

הבדיקה יושבת ב-daemon

כל שליחה, שליחת מדיה, reaction ותזמון נבדקים מול קובץ ה-allow list ברגע הביצוע, בתוך ה-daemon. גם הצד השני של השליחה הדו-שלבית נבדק שוב, ולא רק הצד הראשון. משימה מתוזמנת נבדקת שוב ברגע שהיא נורית — אם הוצאתי צ'אט מהרשימה אחרי שקבעתי לו הודעה, ההודעה לא תצא.

רק בן אדם מרחיב את הרשימה

wa allow add לא כותב לקובץ. הוא גורם ל-daemon להקפיץ דיאלוג מערכת על המסך שלי, עם הצ'אטים שמבקשים אליהם הרשאה, כשכפתור ברירת המחדל הוא Deny. הפקודה תוקעת את עצמה עד דקה ומחכה. סירוב, פסק זמן, מכונה בלי מסך, או כל תקלה בדיאלוג — כולם נספרים כ"לא אושר", וכל בקשה ותוצאה נרשמות ללוג.

בתוך הדיאלוג יושבת פרנויה קטנה שהתגלתה תוך כדי בנייה: שם של קבוצה הוא טקסט חופשי שמישהו אחר שולט בו. אז לפני שהשם מוצג, ה-daemon מנקה ממנו תווי בקרה, תווי bidi ותווים ברוחב אפס, ומקצר אותו ל-60 תווים. אחרת מספיק לקרוא לקבוצה בשם עם שורה חדשה בפנים כדי לזייף שורה נוספת בדיאלוג האישור שלי, או לדחוף את המזהה האמיתי אל מחוץ למסך.

השגיאות שחוזרות לסוכן הן קודים קצרים, וה-skill אומר לו במפורש מה לעשות עם כל אחד: NOT_ALLOWED פירושו לשאול אותי אם להוסיף את הצ'אט לרשימה, ו-NOT_APPROVED פירושו שאמרתי לא. סירוב הוא סופי — בלי ניסיון נוסף ובלי לגעת בקובץ. סוכן שמנסה שוב אחרי שאמרתי לא הוא לא סוכן חרוץ, הוא באג.

עוד משהו שיצא מההפרדה הזאת: כל הלוגיקה של הרשימה, האישור וה-token נבדקת ב-suite בדיקות שרץ ב-Node בלבד, בלי שום חיבור ל-WhatsApp. אני יכול לשנות כלל גידור ולדעת תוך שניות אם שברתי אותו.

מה זה לא

זה לא sandbox, וכתבתי את זה במפורש ב-README כדי שלא אשכח. הכל רץ תחת המשתמש שלי. תהליך מקומי עם ההרשאות שלי יכול לערוך את קובץ ה-allow list ישירות, להפעיל את ה-daemon מחדש כשהוא מצביע על קובץ אחר, או — אם נתתי לסוכן שליטה על המסך — ללחוץ על הדיאלוג בעצמו. ההגנה של ה-harness — כללי deny על עריכת הקובץ — היא הגנה של ה-harness, לא של ה-daemon.

מה שהדיאלוג כן עושה זה להעלות את הרף מ"הסוכן יכול להרחיב לעצמו הרשאות בשקט" ל"צריך לחיצה על המסך שלי או חבלה גלויה". אם מישהו צריך גבול קשיח, התשובה היא להריץ את ה-daemon תחת משתמש מערכת נפרד. זה הבדל שכדאי להגיד ללקוח בקול, ולא להסתיר מאחורי המילה "מאובטח".

רוב העבודה היא לא הפיצ'רים

הדבר שלקח הכי הרבה זמן הוא לא הפקודות אלא ההתנהגות של החיבור עצמו. WhatsApp Web מסתנכרן לאט אחרי הפעלה מחדש, וההודעות שהצטברו בזמן שה-daemon היה כבוי ממשיכות לזרום פנימה גם אחרי שהוא כבר מדווח שהוא מוכן. אז ה-daemon לא מכריז על עצמו כמוכן עד שהזרימה הזאת נרגעת, ועד אז כל פקודה נכשלת עם not ready (state: syncing) מפורש, במקום להחזיר תשובה מצ'אט שסונכרן חלקית.

יש גם מצב מעצבן שבו ההזדהות מצליחה אבל ה-session פשוט לא מגיע למוכנות. במקרה כזה ה-daemon ממחזר את עצמו כמה פעמים תוך שימוש חוזר ב-session ששמור על הדיסק — כלומר בלי QR חדש — ורק אם זה נכשל שוב ושוב הוא עובר לחיבור מחדש שדורש סריקה. שווה גם לדעת שאחרי הפעלה מחדש עומק ההיסטוריה הזמינה מתאפס ומתמלא בהדרגה, כך ש---all יכול להחזיר שתי הודעות בצ'אט עמוס. זה סוג הפרט שאם הסוכן לא יודע אותו, הוא יגיד לכם בביטחון מלא שהצ'אט ריק.

מה זה מאפשר ביום-יום

  • סיכום של מה שקורה בקבוצות עם הרבה תנועה.
  • מעבר על כל הצ'אטים וניתוח לידים ישנים.
  • מעבר יומי על כל הצ'אטים לאיתור מטלות ושיחות פתוחות.
  • שמירה על קשר עם לקוחות, עובדים וחברים.

הוספתי גם הודעות מתוזמנות — לזמן מוחלט, בהשהיה יחסית, או בחזרה יומית, שבועית או חודשית. ה-scheduler לא שולח באיחור: משימה שלא בוצעה בחלון החסד שלה נרשמת כ"פוספסה" ולא נשלחת. הודעת "בוקר טוב" שיוצאת בשלוש בלילה כי המחשב היה כבוי היא לא באג נחמד.

המסקנה הכללית

כשמציעים לכם "AI בתוך X", שווה לעצור ולשאול איזו בעיה זה פותר. ברוב המקרים מה שבאמת עוזר הוא ההפך: להכניס את X אל תוך הסוכן, יחד עם כל שאר הכלים, ואז לגדר בזהירות רק את הפעולות שיוצאות החוצה אל אנשים אמיתיים. גישה לקריאה היא מה שהופך סוכן למועיל; גישה לכתיבה היא מה שצריך לתכנן בזהירות.

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

שאלות שחוזרות

איך מחברים את WhatsApp ל-Claude?

בניתי daemon מקומי שמחזיק session של WhatsApp Web (מעל הספרייה הפתוחה whatsapp-web.js), CLI בשם wa שמדבר איתו דרך unix socket פרטי, ו-skill שמלמד את Claude Code איך להשתמש בפקודות. הכל רץ על המחשב שלכם — אין שרת של צד שלישי באמצע.

זה בטוח לתת לסוכן AI גישה לוואטסאפ שלי?

אצלי קריאה פתוחה לגמרי ושליחה מוגבלת: כל שליחה נבדקת מול allow list בתוך ה-daemon, והוספה לרשימה דורשת לחיצה על Approve בדיאלוג מערכת שנפתח על המסך, כשברירת המחדל היא Deny. זה לא sandbox — הכל רץ תחת המשתמש שלכם — אבל זה מעלה את הרף מ'הסוכן יכול להרחיב לעצמו הרשאות בשקט' ל'צריך לחיצה על המסך'.

למה עדיף להכניס את WhatsApp לתוך Claude ולא את Claude לתוך WhatsApp?

כי הערך של Claude Code מגיע מהקונטקסט של מרחב העבודה — קבצים, מיילים, כלים. להעביר את תיבת הטקסט של Claude ל-WhatsApp נותן לכם את אותו מודל בממשק פחות טוב ובלי הקונטקסט. הכיוון ההפוך שובר data silo ומוסיף מקור מידע לסוכן.

אפשר לתזמן הודעות WhatsApp דרך Claude?

כן. הוספתי פקודת schedule שמקבלת זמן מוחלט, השהיה יחסית, או חזרה יומית, שבועית או חודשית. התזמון עובר את אותו אישור דו-שלבי, וה-allow list נבדקת שוב ברגע שהמשימה נורית בפועל, לא רק כשקבעתם אותה.