antenna-fyi 1.3.6 → 1.3.8

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/lib/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // antenna CLI command handlers
2
2
 
3
- import { scan, getProfile, setProfile, accept, checkMatches, checkin, createBindToken, discover, createEvent, endEvent, eventCheckin, joinEvent, eventScan, pass as passUser, uploadEventImage, updateEvent, approveParticipant, rejectParticipant, addCohost, getClient } from "./core.js";
3
+ import { scan, getProfile, setProfile, accept, checkMatches, checkin, createBindToken, discover, createEvent, endEvent, eventCheckin, joinEvent, eventScan, pass as passUser, uploadEventImage, updateEvent, approveParticipant, rejectParticipant, addCohost, sendEventMessage, getMyEventMessages, getClient } from "./core.js";
4
4
  import { createInterface } from "readline";
5
5
  import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, unlinkSync, renameSync } from "fs";
6
6
  import path from "path";
@@ -273,13 +273,25 @@ export async function handleEvent(f) {
273
273
  return;
274
274
  }
275
275
 
276
+ if (f.message || f.msg) {
277
+ if (!f.code || !f.id || !(f.message || f.msg)) return console.error("Usage: antenna event --message 'Hello everyone' --code abc123 --id <platform>:<user_id> [--ref 1]");
278
+ const result = await sendEventMessage({ code: f.code, device_id: f.id, message: f.message || f.msg, target_ref: f.ref || undefined });
279
+ if (result.sent) {
280
+ console.log(`\n✅ Message sent${result.broadcast ? ' (broadcast to all)' : ''}\n`);
281
+ } else {
282
+ console.log(`\n❌ ${result.error}\n`);
283
+ }
284
+ return;
285
+ }
286
+
276
287
  console.log(`Usage:
277
288
  antenna event --create --name 'AI Meetup' --starts-at '...' --ends-at '...' [--id <platform>:<user_id>] [--lat 34.05 --lng -118.25] [--desc 'description'] [--og-image 'url'] [--requires-approval] [--screening-questions 'Q1|Q2']
278
289
  antenna event --join --code abc123 --id <platform>:<user_id>
279
290
  antenna event --scan --code abc123 [--id <platform>:<user_id>]
280
291
  antenna event --checkin --code abc123 --id <platform>:<user_id> [--lat 34.05 --lng -118.24]
281
292
  antenna event --end --code abc123 --id <platform>:<user_id>
282
- antenna event --upload-image --code abc123 --file /path/to/image.png`);
293
+ antenna event --upload-image --code abc123 --file /path/to/image.png
294
+ antenna event --message 'Hello everyone' --code abc123 --id <platform>:<user_id> [--ref 1]`);
283
295
  }
284
296
 
285
297
  export async function handleBind(f) {
@@ -855,6 +867,20 @@ export async function handleWatch(f) {
855
867
  }
856
868
  }
857
869
  } catch { /* silent */ }
870
+
871
+ // Poll event messages from hosts
872
+ try {
873
+ const { data: msgs } = await sb.rpc("get_my_event_messages", { p_device_id: id });
874
+ for (const msg of (msgs || [])) {
875
+ const key = `evtmsg:${msg.event_id}:${msg.created_at}`;
876
+ if (!notified.has(key)) {
877
+ notified.add(key);
878
+ saveNotified(notified);
879
+ const role = msg.sender_role === 'creator' ? '组织者' : '协办';
880
+ pushNotify(`📢 来自「${msg.event_name}」${role} ${msg.sender_emoji || ''} ${msg.sender_name}: ${msg.message}`);
881
+ }
882
+ }
883
+ } catch { /* silent */ }
858
884
  }, 2 * 60 * 1000);
859
885
 
860
886
  // Handle Ctrl+C
package/lib/core.js CHANGED
@@ -635,13 +635,15 @@ export async function getEvent({ code, supabaseUrl, supabaseKey }) {
635
635
  return data;
636
636
  }
637
637
 
638
- export async function updateEvent({ code, device_id, name, description, og_image, lat, lng, starts_at, ends_at, supabaseUrl, supabaseKey }) {
638
+ export async function updateEvent({ code, device_id, name, description, og_image, lat, lng, starts_at, ends_at, requires_approval, screening_questions, supabaseUrl, supabaseKey }) {
639
639
  const sb = getClient(supabaseUrl, supabaseKey);
640
640
  const { data, error } = await sb.rpc("update_event", {
641
641
  p_code: code, p_device_id: device_id,
642
642
  p_name: name || null, p_description: description || null,
643
643
  p_og_image: og_image || null, p_lat: lat ?? null, p_lng: lng ?? null,
644
644
  p_starts_at: starts_at || null, p_ends_at: ends_at || null,
645
+ ...(requires_approval != null ? { p_requires_approval: requires_approval } : {}),
646
+ ...(screening_questions != null ? { p_screening_questions: screening_questions } : {}),
645
647
  });
646
648
  if (error) throw new Error(error.message);
647
649
  return data;
@@ -686,3 +688,30 @@ export async function createBindToken({ device_id, purpose, event_code, supabase
686
688
  : "发送这个链接给用户,在手机浏览器打开即可共享位置。",
687
689
  };
688
690
  }
691
+
692
+ // ─── sendEventMessage (host → participants) ─────────────────────────
693
+
694
+ export async function sendEventMessage({ code, device_id, message, target_ref, supabaseUrl, supabaseKey }) {
695
+ const sb = getClient(supabaseUrl, supabaseKey);
696
+ const params = {
697
+ p_code: code,
698
+ p_device_id: device_id,
699
+ p_message: message,
700
+ };
701
+ if (target_ref) params.p_target_ref = target_ref;
702
+ const { data, error } = await sb.rpc("send_event_message", params);
703
+ if (error) throw new Error(error.message);
704
+ return data;
705
+ }
706
+
707
+ // ─── getMyEventMessages ──────────────────────────────────────────────
708
+
709
+ export async function getMyEventMessages({ device_id, supabaseUrl, supabaseKey }) {
710
+ const sb = getClient(supabaseUrl, supabaseKey);
711
+ const { data, error } = await sb.rpc("get_my_event_messages", { p_device_id: device_id });
712
+ if (error) throw new Error(error.message);
713
+ return {
714
+ messages: data || [],
715
+ count: (data || []).length,
716
+ };
717
+ }
@@ -32,6 +32,7 @@ from .tools import (
32
32
  handle_event_approve,
33
33
  handle_event_reject,
34
34
  handle_event_add_host,
35
+ handle_event_message,
35
36
  _sb,
36
37
  _device_id,
37
38
  _my_device_ids,
@@ -55,6 +56,7 @@ from .schemas import (
55
56
  EVENT_APPROVE_SCHEMA,
56
57
  EVENT_REJECT_SCHEMA,
57
58
  EVENT_ADD_HOST_SCHEMA,
59
+ EVENT_MESSAGE_SCHEMA,
58
60
  )
59
61
  import re
60
62
  import time
@@ -89,6 +91,7 @@ def register(ctx):
89
91
  ctx.register_tool("antenna_event_approve", EVENT_APPROVE_SCHEMA, handle_event_approve)
90
92
  ctx.register_tool("antenna_event_reject", EVENT_REJECT_SCHEMA, handle_event_reject)
91
93
  ctx.register_tool("antenna_event_add_host", EVENT_ADD_HOST_SCHEMA, handle_event_add_host)
94
+ ctx.register_tool("antenna_event_message", EVENT_MESSAGE_SCHEMA, handle_event_message)
92
95
 
93
96
  # ── Hook: auto-detect location + check web GPS events ─────────
94
97
  def on_pre_llm(messages, **kwargs):
@@ -179,6 +182,19 @@ def register(ctx):
179
182
  hints.append(
180
183
  f"[Antenna] ❌ 你的申请未通过「{name}」"
181
184
  )
185
+
186
+ # Check event messages from hosts
187
+ msg_resp = sb.rpc("get_my_event_messages", {"p_device_id": did}).execute()
188
+ for msg in (msg_resp.data or []):
189
+ key = f"evtmsg:{msg.get('event_id')}:{msg.get('created_at')}"
190
+ if key in _notified_match_keys:
191
+ continue
192
+ _notified_match_keys.add(key)
193
+ role_label = '组织者' if msg.get('sender_role') == 'creator' else '协办'
194
+ hints.append(
195
+ f"[Antenna] 📢 来自「{msg.get('event_name')}」{role_label} {msg.get('sender_emoji', '')} {msg.get('sender_name', '')}: {msg.get('message', '')}\n"
196
+ f"请立即通知用户这条消息。"
197
+ )
182
198
  except Exception:
183
199
  pass
184
200
  now = time.time()
@@ -1,5 +1,5 @@
1
1
  name: antenna
2
- version: "1.3.6"
2
+ version: "1.3.8"
3
3
  description: |
4
4
  Nearby people discovery + events — scan for people, set up your profile card,
5
5
  accept matches, create/join events, and get real-time notifications.
@@ -307,6 +307,8 @@ EVENT_UPDATE_SCHEMA = {
307
307
  "lng": {"type": "number"},
308
308
  "starts_at": {"type": "string"},
309
309
  "ends_at": {"type": "string"},
310
+ "requires_approval": {"type": "boolean", "description": "Require host approval to join"},
311
+ "screening_questions": {"type": "array", "items": {"type": "string"}, "description": "Screening questions for applicants"},
310
312
  },
311
313
  "required": ["code", "sender_id", "channel", "chat_id"],
312
314
  },
@@ -359,3 +361,20 @@ EVENT_ADD_HOST_SCHEMA = {
359
361
  "required": ["code", "sender_id", "channel", "ref", "chat_id"],
360
362
  },
361
363
  }
364
+
365
+ EVENT_MESSAGE_SCHEMA = {
366
+ "name": "antenna_event_message",
367
+ "description": "Send a message to event participants. Only creator or co-host can send. Omit ref to broadcast to all.",
368
+ "parameters": {
369
+ "type": "object",
370
+ "properties": {
371
+ "code": {"type": "string", "description": "Event code"},
372
+ "sender_id": {"type": "string"},
373
+ "channel": {"type": "string"},
374
+ "chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
375
+ "message": {"type": "string", "description": "Message to send to participants"},
376
+ "ref": {"type": "string", "description": "Ref number of specific participant (omit for broadcast)"},
377
+ },
378
+ "required": ["code", "sender_id", "channel", "message", "chat_id"],
379
+ },
380
+ }
@@ -645,13 +645,18 @@ def handle_event_checkin(params: dict) -> str:
645
645
  def handle_event_update(params: dict) -> str:
646
646
  sb = _sb()
647
647
  did = _device_id(params["sender_id"], params["channel"], params.get("chat_id"))
648
- resp = sb.rpc("update_event", {
648
+ rpc_params = {
649
649
  "p_code": params["code"], "p_device_id": did,
650
650
  "p_name": params.get("name"), "p_description": params.get("description"),
651
651
  "p_og_image": params.get("og_image"), "p_lat": params.get("lat"),
652
652
  "p_lng": params.get("lng"), "p_starts_at": params.get("starts_at"),
653
653
  "p_ends_at": params.get("ends_at"),
654
- }).execute()
654
+ }
655
+ if params.get("requires_approval") is not None:
656
+ rpc_params["p_requires_approval"] = params["requires_approval"]
657
+ if params.get("screening_questions") is not None:
658
+ rpc_params["p_screening_questions"] = params["screening_questions"]
659
+ resp = sb.rpc("update_event", rpc_params).execute()
655
660
  return _ok(resp.data or {"error": "update failed"})
656
661
 
657
662
 
@@ -680,3 +685,18 @@ def handle_event_add_host(params: dict) -> str:
680
685
  "p_code": params["code"], "p_device_id": did, "p_target_ref": params["ref"],
681
686
  }).execute()
682
687
  return _ok(resp.data or {"error": "add_cohost failed"})
688
+
689
+
690
+ def handle_event_message(params: dict) -> str:
691
+ """Send a message to event participants. Only creator or co-host can send."""
692
+ sb = _sb()
693
+ did = _device_id(params["sender_id"], params["channel"], params.get("chat_id"))
694
+ rpc_params = {
695
+ "p_code": params["code"],
696
+ "p_device_id": did,
697
+ "p_message": params["message"],
698
+ }
699
+ if params.get("ref"):
700
+ rpc_params["p_target_ref"] = params["ref"]
701
+ resp = sb.rpc("send_event_message", rpc_params).execute()
702
+ return _ok(resp.data or {"error": "send_event_message failed"})
package/lib/mcp.js CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  approveParticipant,
24
24
  rejectParticipant,
25
25
  addCohost,
26
+ sendEventMessage,
26
27
  deriveDeviceId,
27
28
  } from "./core.js";
28
29
 
@@ -412,10 +413,12 @@ export async function startMcpServer() {
412
413
  lng: z.number().optional().describe("New event longitude"),
413
414
  starts_at: z.string().optional().describe("New start time ISO"),
414
415
  ends_at: z.string().optional().describe("New end time ISO"),
416
+ requires_approval: z.boolean().optional().describe("Require host approval to join"),
417
+ screening_questions: z.array(z.string()).optional().describe("Screening questions for applicants"),
415
418
  },
416
- async ({ code, sender_id, channel, name, description, og_image, lat, lng, starts_at, ends_at }) => {
419
+ async ({ code, sender_id, channel, name, description, og_image, lat, lng, starts_at, ends_at, requires_approval, screening_questions }) => {
417
420
  try {
418
- const result = await updateEvent({ code, device_id: deriveDeviceId(sender_id, channel), name, description, og_image, lat, lng, starts_at, ends_at });
421
+ const result = await updateEvent({ code, device_id: deriveDeviceId(sender_id, channel), name, description, og_image, lat, lng, starts_at, ends_at, requires_approval, screening_questions });
419
422
  return jsonResult(result);
420
423
  } catch (e) { return jsonResult({ error: e.message }); }
421
424
  }
@@ -478,6 +481,27 @@ export async function startMcpServer() {
478
481
  }
479
482
  );
480
483
 
484
+ // ─── antenna_event_message ────────────────────────────────
485
+
486
+ server.tool(
487
+ "antenna_event_message",
488
+ "Send a message to event participants. Only creator or co-host can send. Omit ref to broadcast to all.",
489
+ {
490
+ code: z.string().describe("Event code"),
491
+ sender_id: z.string().describe("The sender's user ID"),
492
+ channel: z.string().describe("Channel name"),
493
+ chat_id: z.string().describe("REQUIRED for notifications"),
494
+ message: z.string().describe("Message to send"),
495
+ ref: z.string().optional().describe("Ref number of specific participant (omit for broadcast)"),
496
+ },
497
+ async ({ code, sender_id, channel, chat_id, message, ref }) => {
498
+ try {
499
+ const result = await sendEventMessage({ code, device_id: deriveDeviceId(sender_id, channel), message, target_ref: ref });
500
+ return jsonResult(result);
501
+ } catch (e) { return jsonResult({ error: e.message }); }
502
+ }
503
+ );
504
+
481
505
  const transport = new StdioServerTransport();
482
506
  await server.connect(transport);
483
507
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-fyi",
3
- "version": "1.3.6",
3
+ "version": "1.3.8",
4
4
  "description": "Antenna — nearby people discovery. CLI + MCP server + OpenClaw skill & plugin, all in one package.",
5
5
  "type": "module",
6
6
  "bin": {
package/skill/SKILL.md CHANGED
@@ -366,6 +366,8 @@ Update event info. Only creator or co-host can update.
366
366
  - `sender_id`, `channel`: from context
367
367
  - `chat_id`: REQUIRED for notifications
368
368
  - `name`, `description`, `og_image`, `lat`, `lng`, `starts_at`, `ends_at`: all optional for update (only provided fields change, others stay as-is)
369
+ - `requires_approval`: optional boolean — enable/disable approval requirement
370
+ - `screening_questions`: optional string array — update screening questions
369
371
 
370
372
  ### `antenna_event_approve`
371
373
  Approve a pending participant. Only creator or co-host.
@@ -388,6 +390,16 @@ Add a co-host to the event. Only creator can add.
388
390
  - `chat_id`: REQUIRED for notifications
389
391
  - `ref`: participant ref number to promote to co-host
390
392
 
393
+ ### `antenna_event_message`
394
+ Send a message to event participants. Only creator or co-host can send.
395
+ - `code`: event code
396
+ - `sender_id`, `channel`: from context
397
+ - `chat_id`: REQUIRED for notifications
398
+ - `message`: the message text
399
+ - `ref`: optional — ref number of specific participant. Omit to broadcast to all active participants.
400
+ - Use when the host needs to notify participants about logistics, changes, or requests (e.g. "please share your WeChat in your profile").
401
+ - One-way: participants receive the message but cannot reply through this channel.
402
+
391
403
  ---
392
404
 
393
405
  ## Event Behavior Guide