antenna-openclaw-plugin 1.0.1 โ†’ 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -573,11 +573,43 @@ export default function register(api: any) {
573
573
  }
574
574
 
575
575
  const _refMap: Record<string, string> = {};
576
- const profiles = results.map((p: any, i: number) => {
576
+
577
+ // Get my profile for match reason generation
578
+ const { data: myProfile } = await supabase.rpc("get_profile", { p_device_id: deviceId });
579
+ const myLines = myProfile ? [myProfile.line1, myProfile.line2, myProfile.line3].filter(Boolean).join(". ") : "";
580
+
581
+ const profiles = [];
582
+ for (let i = 0; i < results.length; i++) {
583
+ const p = results[i] as any;
577
584
  const ref = String(i + 1);
578
585
  _refMap[ref] = p.device_id;
579
- return { ref, emoji: p.emoji || "๐Ÿ‘ค", name: p.display_name || "ๅŒฟๅ", line1: p.line1, line2: p.line2, line3: p.line3 };
580
- });
586
+
587
+ const theirLines = [p.line1, p.line2, p.line3].filter(Boolean).join(". ");
588
+ let match_reason: string | null = null;
589
+
590
+ // Generate match reason with Gemini Flash
591
+ if (myLines && theirLines) {
592
+ try {
593
+ const apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
594
+ if (apiKey) {
595
+ const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`, {
596
+ method: "POST",
597
+ headers: { "Content-Type": "application/json" },
598
+ body: JSON.stringify({
599
+ contents: [{ parts: [{ text: `You are matching two people. Person A: "${myLines}". Person B: "${theirLines}". Write ONE short sentence (under 20 words) in the SAME LANGUAGE as the profiles explaining why they might click. Be specific, not generic. No fluff.` }] }],
600
+ generationConfig: { maxOutputTokens: 60, temperature: 0.7 },
601
+ }),
602
+ });
603
+ if (res.ok) {
604
+ const data = await res.json();
605
+ match_reason = data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() || null;
606
+ }
607
+ }
608
+ } catch { /* best effort */ }
609
+ }
610
+
611
+ profiles.push({ ref, emoji: p.emoji || "๐Ÿ‘ค", name: p.display_name || "ๅŒฟๅ", line1: p.line1, line2: p.line2, line3: p.line3, match_reason });
612
+ }
581
613
 
582
614
  // Persist refs + log recommendation
583
615
  (api as any)._antennaRefMap = { ...(api as any)._antennaRefMap, ..._refMap };
@@ -595,6 +627,140 @@ export default function register(api: any) {
595
627
  },
596
628
  });
597
629
 
630
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
631
+ // Tool: antenna_event_create
632
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
633
+ api.registerTool({
634
+ name: "antenna_event_create",
635
+ description: "Create an event. Returns a shareable link (antenna.fyi/e/CODE) for participants to join.",
636
+ parameters: {
637
+ type: "object",
638
+ properties: {
639
+ name: { type: "string", description: "Event name" },
640
+ sender_id: { type: "string" },
641
+ channel: { type: "string" },
642
+ lat: { type: "number", description: "Event latitude" },
643
+ lng: { type: "number", description: "Event longitude" },
644
+ starts_at: { type: "string", description: "Start time ISO" },
645
+ ends_at: { type: "string", description: "End time ISO" },
646
+ },
647
+ required: ["name", "sender_id", "channel"],
648
+ },
649
+ async execute(_id: string, params: any) {
650
+ const cfg = getConfig(api);
651
+ const supabase = getSupabase(cfg);
652
+ const deviceId = deriveDeviceId(params.sender_id, params.channel);
653
+ const { data, error } = await supabase.rpc("create_event", {
654
+ p_name: params.name,
655
+ p_lat: params.lat || null,
656
+ p_lng: params.lng || null,
657
+ p_created_by: deviceId,
658
+ p_starts_at: params.starts_at || new Date().toISOString(),
659
+ p_ends_at: params.ends_at || new Date(Date.now() + 12*60*60*1000).toISOString(),
660
+ });
661
+ if (error) return ok({ error: error.message });
662
+ return ok(data);
663
+ },
664
+ });
665
+
666
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
667
+ // Tool: antenna_event_join
668
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
669
+ api.registerTool({
670
+ name: "antenna_event_join",
671
+ description: "Join an event by its code from the event URL.",
672
+ parameters: {
673
+ type: "object",
674
+ properties: {
675
+ code: { type: "string", description: "Event code" },
676
+ sender_id: { type: "string" },
677
+ channel: { type: "string" },
678
+ },
679
+ required: ["code", "sender_id", "channel"],
680
+ },
681
+ async execute(_id: string, params: any) {
682
+ const cfg = getConfig(api);
683
+ const supabase = getSupabase(cfg);
684
+ const deviceId = deriveDeviceId(params.sender_id, params.channel);
685
+ const { data, error } = await supabase.rpc("join_event", { p_code: params.code, p_device_id: deviceId });
686
+ if (error) return ok({ error: error.message });
687
+ return ok(data);
688
+ },
689
+ });
690
+
691
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
692
+ // Tool: antenna_event_scan
693
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
694
+ api.registerTool({
695
+ name: "antenna_event_scan",
696
+ description: "Scan people in an event. No distance limit.",
697
+ parameters: {
698
+ type: "object",
699
+ properties: {
700
+ code: { type: "string", description: "Event code" },
701
+ sender_id: { type: "string" },
702
+ channel: { type: "string" },
703
+ },
704
+ required: ["code", "sender_id", "channel"],
705
+ },
706
+ async execute(_id: string, params: any) {
707
+ const cfg = getConfig(api);
708
+ const supabase = getSupabase(cfg);
709
+ const deviceId = deriveDeviceId(params.sender_id, params.channel);
710
+
711
+ const { data, error } = await supabase.rpc("event_participants_list", { p_code: params.code, p_device_id: deviceId });
712
+ if (error) return ok({ error: error.message });
713
+
714
+ const others = (data || []) as any[];
715
+ const _refMap: Record<string, string> = {};
716
+ const profiles = others.map((p, i) => {
717
+ const ref = String(i + 1);
718
+ _refMap[ref] = p.device_id;
719
+ return { ref, emoji: p.emoji || "๐Ÿ‘ค", name: p.display_name || "ๅŒฟๅ", line1: p.line1, line2: p.line2, line3: p.line3, source: "event" };
720
+ });
721
+
722
+ (api as any)._antennaRefMap = { ...(api as any)._antennaRefMap, ..._refMap };
723
+ try { await supabase.rpc("save_scan_refs", { p_owner: deviceId, p_refs: JSON.stringify(_refMap) }); } catch {}
724
+
725
+ return ok({ count: profiles.length, profiles, event: true });
726
+ },
727
+ });
728
+
729
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
730
+ // Tool: antenna_pass
731
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
732
+ api.registerTool({
733
+ name: "antenna_pass",
734
+ description: "Pass/skip a person. They won't be recommended again.",
735
+ parameters: {
736
+ type: "object",
737
+ properties: {
738
+ sender_id: { type: "string", description: "The sender's user ID" },
739
+ channel: { type: "string", description: "The channel name" },
740
+ ref: { type: "string", description: "Ref number from scan/discover results" },
741
+ target_device_id: { type: "string", description: "Device ID (use ref instead when possible)" },
742
+ },
743
+ required: ["sender_id", "channel"],
744
+ },
745
+ async execute(_id: string, params: any) {
746
+ const cfg = getConfig(api);
747
+ const supabase = getSupabase(cfg);
748
+ const deviceId = deriveDeviceId(params.sender_id, params.channel);
749
+
750
+ let targetId = params.target_device_id;
751
+ if (!targetId && params.ref) {
752
+ const { data: resolved } = await supabase.rpc("resolve_ref", { p_owner: deviceId, p_ref: params.ref });
753
+ targetId = resolved || (api as any)._antennaRefMap?.[params.ref];
754
+ }
755
+ if (!targetId) {
756
+ return ok({ error: "No target. Ref may have expired โ€” try scanning again." });
757
+ }
758
+
759
+ await supabase.rpc("pass_user", { p_device_id: deviceId, p_passed_device_id: targetId });
760
+ return ok({ passed: true, message: "ๅทฒ่ทณ่ฟ‡๏ผŒไธ‹ๆฌกไธไผšๅ†ๆŽจ่่ฟ™ไธชไบบใ€‚" });
761
+ },
762
+ });
763
+
598
764
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
599
765
  // Tool: antenna_check_matches
600
766
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-openclaw-plugin",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Antenna โ€” agent-mediated nearby people discovery for OpenClaw",
5
5
  "openclaw": {
6
6
  "extensions": ["./index.ts"]
@@ -251,3 +251,21 @@ Plugin ่‡ชๅธฆๅŽๅฐๆœๅŠก๏ผŒๆฏ 10 ๅˆ†้’Ÿ่ฝฎ่ฏขไธ€ๆฌก Supabase ๆŸฅๆ–ฐ็š„ mutual
251
251
  3. ๅฆ‚ๆžœๅฏนๆ–นๅˆ†ไบซไบ†่”็ณปๆ–นๅผ๏ผŒไธ€ๅนถๅฑ•็คบ
252
252
 
253
253
  ็”จๆˆทไธ้œ€่ฆไธปๅŠจ้—ฎ๏ผŒagent ไผš่‡ชๅŠจๆ”ถๅˆฐ้€š็Ÿฅใ€‚
254
+
255
+ ### `antenna_event_create`
256
+ Create an event. Returns a shareable link (antenna.fyi/e/CODE).
257
+ - `name`: event name
258
+ - `sender_id`, `channel`: from context
259
+ - `lat`, `lng`: optional event location
260
+ - `starts_at`, `ends_at`: optional time range (default: now to +12h)
261
+
262
+ ### `antenna_event_join`
263
+ Join an event by code.
264
+ - `code`: from the event URL (antenna.fyi/e/CODE)
265
+ - `sender_id`, `channel`: from context
266
+
267
+ ### `antenna_event_scan`
268
+ Scan people in an event. No distance limit โ€” returns all participants.
269
+ - `code`: event code
270
+ - `sender_id`, `channel`: from context
271
+ - Returns profiles with `source: "event"` tag