antenna-fyi 1.2.8 โ†’ 1.2.10

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/bin/antenna.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  handleEvent,
12
12
  handleBind,
13
13
  handlePass,
14
+ handleWatch,
14
15
  handleSetup,
15
16
  handleStatus,
16
17
  handleInstallSkill,
@@ -42,6 +43,8 @@ async function main() {
42
43
  return handleBind(f);
43
44
  case "pass":
44
45
  return handlePass(f);
46
+ case "watch":
47
+ return handleWatch(f);
45
48
  case "serve": {
46
49
  const { startMcpServer } = await import("../lib/mcp.js");
47
50
  return startMcpServer();
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 } from "./core.js";
3
+ import { scan, getProfile, setProfile, accept, checkMatches, checkin, createBindToken, discover, createEvent, endEvent, eventCheckin, joinEvent, eventScan, pass as passUser, uploadEventImage, getClient } from "./core.js";
4
4
  import { createInterface } from "readline";
5
5
  import { existsSync, mkdirSync, copyFileSync, readFileSync } from "fs";
6
6
  import { join, dirname, extname } from "path";
@@ -401,6 +401,197 @@ export function handleInstallHermesPlugin() {
401
401
  console.log();
402
402
  }
403
403
 
404
+ export async function handleWatch(f) {
405
+ const id = f.id;
406
+ if (!id) {
407
+ console.error("โŒ --id required (e.g. --id telegram:123)");
408
+ process.exit(1);
409
+ }
410
+
411
+ const sb = getClient();
412
+ const notified = new Set();
413
+
414
+ // Detect local agent framework for push notifications
415
+ let pushMethod = "terminal"; // default: just print
416
+ try {
417
+ execSync("which openclaw", { stdio: "pipe" });
418
+ // Verify gateway is running
419
+ try {
420
+ execSync("openclaw gateway health", { stdio: "pipe", timeout: 5000 });
421
+ pushMethod = "openclaw";
422
+ } catch { /* gateway not running */ }
423
+ } catch { /* openclaw not installed */ }
424
+
425
+ if (pushMethod === "terminal") {
426
+ try {
427
+ execSync("which hermes", { stdio: "pipe" });
428
+ try {
429
+ execSync("hermes gateway status", { stdio: "pipe", timeout: 5000 });
430
+ pushMethod = "hermes";
431
+ } catch { /* hermes gateway not running */ }
432
+ } catch { /* hermes not installed */ }
433
+ }
434
+
435
+ console.log(`๐Ÿ“ก Watching for new matches for ${id}...`);
436
+ if (pushMethod === "openclaw") {
437
+ console.log(` ๐Ÿ”— Detected OpenClaw โ€” will push notifications to your channel.`);
438
+ } else if (pushMethod === "hermes") {
439
+ console.log(` ๐Ÿ”— Detected Hermes โ€” will push notifications to your channel.`);
440
+ } else {
441
+ console.log(` โ„น๏ธ No agent framework detected โ€” notifications will print here.`);
442
+ }
443
+ console.log(` Press Ctrl+C to stop.\n`);
444
+
445
+ // Push notification helper
446
+ function pushNotify(message) {
447
+ console.log(message); // always print to terminal
448
+
449
+ if (pushMethod === "openclaw") {
450
+ try {
451
+ const parts = id.split(":");
452
+ const channel = parts[0];
453
+ const userId = parts.slice(1).join(":");
454
+ execSync(
455
+ `openclaw agent` +
456
+ ` --message ${JSON.stringify(message)}` +
457
+ ` --deliver` +
458
+ ` --reply-channel ${channel}` +
459
+ ` --reply-to "${userId}"`,
460
+ { timeout: 30_000, stdio: "pipe" }
461
+ );
462
+ } catch (err) {
463
+ // silent โ€” terminal output is the fallback
464
+ }
465
+ } else if (pushMethod === "hermes") {
466
+ try {
467
+ // Use hermes cron to create a one-shot notification
468
+ const parts = id.split(":");
469
+ const channel = parts[0];
470
+ execSync(
471
+ `hermes cron create` +
472
+ ` --name "Antenna notification"` +
473
+ ` --run-now` +
474
+ ` --once` +
475
+ ` --message ${JSON.stringify(message)}` +
476
+ ` --deliver ${channel}`,
477
+ { timeout: 30_000, stdio: "pipe" }
478
+ );
479
+ } catch (err) {
480
+ // silent โ€” terminal output is the fallback
481
+ }
482
+ }
483
+ }
484
+
485
+ // Initial check
486
+ const initial = await checkMatches({ device_id: id });
487
+ if (initial.mutual_matches?.length) {
488
+ console.log(`๐ŸŽ‰ You have ${initial.mutual_matches.length} mutual match(es)!`);
489
+ for (const m of initial.mutual_matches) {
490
+ const key = `mutual:${m.device_id}`;
491
+ notified.add(key);
492
+ console.log(` ${m.emoji || "๐Ÿ‘ค"} ${m.name}${m.their_contact ? " โ€” contact: " + m.their_contact : ""}`);
493
+ }
494
+ console.log();
495
+ }
496
+ if (initial.incoming_accepts?.length) {
497
+ console.log(`๐Ÿ“ฉ ${initial.incoming_accepts.length} person(s) want to meet you!`);
498
+ for (const m of initial.incoming_accepts) {
499
+ const key = `incoming:${m.device_id}`;
500
+ notified.add(key);
501
+ console.log(` ${m.emoji || "๐Ÿ‘ค"} ${m.name} โ€” ${m.line1 || ""}`);
502
+ }
503
+ console.log();
504
+ }
505
+
506
+ // Subscribe to realtime changes on matches table
507
+ const channel = sb
508
+ .channel("antenna-cli-watch")
509
+ .on("postgres_changes",
510
+ { event: "INSERT", schema: "public", table: "matches" },
511
+ async (payload) => {
512
+ try {
513
+ const row = payload.new;
514
+ if (!row) return;
515
+
516
+ // Someone accepted me
517
+ if (row.device_id_b === id) {
518
+ const key = `incoming:${row.device_id_a}`;
519
+ if (notified.has(key)) return;
520
+ notified.add(key);
521
+
522
+ const profile = await getProfile({ device_id: row.device_id_a });
523
+ const name = profile?.display_name || "Someone";
524
+ const emoji = profile?.emoji || "๐Ÿ‘ค";
525
+
526
+ // Check if mutual
527
+ const matches = await checkMatches({ device_id: id });
528
+ const isMutual = matches.mutual_matches?.some(m => m.device_id === row.device_id_a);
529
+
530
+ if (isMutual) {
531
+ const mutualKey = `mutual:${row.device_id_a}`;
532
+ notified.add(mutualKey);
533
+ const contact = row.contact_info_a;
534
+ pushNotify(`๐ŸŽ‰ MUTUAL MATCH! ${emoji} ${name} also accepted you!${contact ? " Contact: " + contact : ""}`);
535
+ } else {
536
+ pushNotify(`๐Ÿ“ฉ ${emoji} ${name} wants to meet you! Run: antenna accept --id ${id} --target ${row.device_id_a}`);
537
+ }
538
+ }
539
+
540
+ // I accepted someone and they also accepted me
541
+ if (row.device_id_a === id) {
542
+ const matches = await checkMatches({ device_id: id });
543
+ const mutual = matches.mutual_matches?.find(m => m.device_id === row.device_id_b);
544
+ if (mutual) {
545
+ const mutualKey = `mutual:${row.device_id_b}`;
546
+ if (!notified.has(mutualKey)) {
547
+ notified.add(mutualKey);
548
+ pushNotify(`๐ŸŽ‰ MUTUAL MATCH! ${mutual.emoji || "๐Ÿ‘ค"} ${mutual.name}!${mutual.their_contact ? " Contact: " + mutual.their_contact : ""}`);
549
+ }
550
+ }
551
+ }
552
+ } catch (err) {
553
+ // silent
554
+ }
555
+ }
556
+ )
557
+ .subscribe((status) => {
558
+ if (status === "SUBSCRIBED") {
559
+ console.log("โœ… Connected โ€” listening for matches in real-time.");
560
+ } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
561
+ console.log(`โš ๏ธ Connection issue (${status}), retrying...`);
562
+ }
563
+ });
564
+
565
+ // Keep alive โ€” also poll every 2 minutes as fallback
566
+ const pollInterval = setInterval(async () => {
567
+ try {
568
+ const result = await checkMatches({ device_id: id });
569
+ for (const m of (result.mutual_matches || [])) {
570
+ const key = `mutual:${m.device_id}`;
571
+ if (!notified.has(key)) {
572
+ notified.add(key);
573
+ pushNotify(`๐ŸŽ‰ MUTUAL MATCH! ${m.emoji || "๐Ÿ‘ค"} ${m.name}!${m.their_contact ? " Contact: " + m.their_contact : ""}`);
574
+ }
575
+ }
576
+ for (const m of (result.incoming_accepts || [])) {
577
+ const key = `incoming:${m.device_id}`;
578
+ if (!notified.has(key)) {
579
+ notified.add(key);
580
+ pushNotify(`๐Ÿ“ฉ ${m.emoji || "๐Ÿ‘ค"} ${m.name} wants to meet you!`);
581
+ }
582
+ }
583
+ } catch { /* silent */ }
584
+ }, 2 * 60 * 1000);
585
+
586
+ // Handle Ctrl+C
587
+ process.on("SIGINT", () => {
588
+ console.log("\n๐Ÿ‘‹ Stopped watching.");
589
+ clearInterval(pollInterval);
590
+ sb.removeChannel(channel);
591
+ process.exit(0);
592
+ });
593
+ }
594
+
404
595
  export function printHelp() {
405
596
  console.log(`๐Ÿ“ก Antenna โ€” nearby people discovery
406
597
 
@@ -413,6 +604,7 @@ Usage:
413
604
  antenna matches --id telegram:123
414
605
  antenna discover --id telegram:123
415
606
  antenna event --create --name 'AI Meetup' [--desc '...'] [--og-image 'url'] | --join --code abc123 | --scan --code abc123 | --end --code abc123 --id telegram:123 | --upload-image --code abc123 --file /path/to/image.png
607
+ antenna watch --id telegram:123 Watch for new matches in real-time (Ctrl+C to stop)
416
608
  antenna bind --id telegram:123
417
609
  antenna serve Start MCP server (stdio transport)
418
610
  antenna setup Interactive profile setup [--id telegram:123]
package/lib/core.js CHANGED
@@ -515,6 +515,15 @@ export async function endEvent({ code, device_id, supabaseUrl, supabaseKey }) {
515
515
 
516
516
  export async function eventCheckin({ code, device_id, lat, lng, supabaseUrl, supabaseKey }) {
517
517
  const sb = getClient(supabaseUrl, supabaseKey);
518
+
519
+ // Auto-read profile location if not provided
520
+ if (lat == null || lng == null) {
521
+ try {
522
+ const { data: loc } = await sb.rpc("get_profile_location", { p_device_id: device_id });
523
+ if (loc?.lat && loc?.lng) { lat = loc.lat; lng = loc.lng; }
524
+ } catch {}
525
+ }
526
+
518
527
  const fuzzy = (lat != null && lng != null) ? fuzzyCoord(lat, lng) : { lat: null, lng: null };
519
528
  const { data, error } = await sb.rpc("event_checkin", {
520
529
  p_code: code, p_device_id: device_id,
@@ -577,14 +586,21 @@ export async function getEvent({ code, supabaseUrl, supabaseKey }) {
577
586
  return data;
578
587
  }
579
588
 
580
- export async function createBindToken({ device_id, supabaseUrl, supabaseKey }) {
589
+ export async function createBindToken({ device_id, purpose, event_code, supabaseUrl, supabaseKey }) {
581
590
  const sb = getClient(supabaseUrl, supabaseKey);
582
- const { data, error } = await sb.rpc("create_bind_token", { p_device_id: device_id });
591
+ const { data, error } = await sb.rpc("create_bind_token", {
592
+ p_device_id: device_id,
593
+ p_purpose: purpose || "profile",
594
+ p_event_code: event_code || null,
595
+ });
583
596
  if (error) throw new Error(error.message);
584
597
  const baseUrl = "https://www.antenna.fyi";
585
598
  return {
586
599
  token: data.token,
587
600
  url: `${baseUrl}/locate?token=${data.token}`,
588
- message: "ๅ‘้€่ฟ™ไธช้“พๆŽฅ็ป™็”จๆˆท๏ผŒๅœจๆ‰‹ๆœบๆต่งˆๅ™จๆ‰“ๅผ€ๅณๅฏๅ…ฑไบซไฝ็ฝฎใ€‚",
601
+ purpose: purpose || "profile",
602
+ message: purpose === "event"
603
+ ? "ๅ‘้€่ฟ™ไธช้“พๆŽฅ็ป™ๆดปๅŠจๅˆ›ๅปบ่€…๏ผŒๅœจๆดปๅŠจๅœฐ็‚นๆ‰“ๅผ€ๅณๅฏ่ฎพๅฎšๆดปๅŠจไฝ็ฝฎใ€‚"
604
+ : "ๅ‘้€่ฟ™ไธช้“พๆŽฅ็ป™็”จๆˆท๏ผŒๅœจๆ‰‹ๆœบๆต่งˆๅ™จๆ‰“ๅผ€ๅณๅฏๅ…ฑไบซไฝ็ฝฎใ€‚",
589
605
  };
590
606
  }
@@ -122,14 +122,15 @@ CHECK_MATCHES_SCHEMA = {
122
122
  BIND_SCHEMA = {
123
123
  "name": "antenna_bind",
124
124
  "description": (
125
- "Generate a GPS binding link. Send this URL to the user so they can "
126
- "share their phone's location via the web browser."
125
+ "Generate a GPS binding link. Use purpose='event' + event_code when setting an event's location."
127
126
  ),
128
127
  "parameters": {
129
128
  "type": "object",
130
129
  "properties": {
131
130
  "sender_id": {"type": "string"},
132
131
  "channel": {"type": "string"},
132
+ "purpose": {"type": "string", "description": "'profile' (default) or 'event'"},
133
+ "event_code": {"type": "string", "description": "Event code (when purpose=event)"},
133
134
  },
134
135
  "required": ["sender_id", "channel"],
135
136
  },
@@ -452,16 +452,28 @@ def handle_event_scan(params: dict) -> str:
452
452
  def handle_bind(params: dict) -> str:
453
453
  sb = _sb()
454
454
  did = _device_id(params["sender_id"], params["channel"])
455
+ purpose = params.get("purpose", "profile")
456
+ event_code = params.get("event_code")
455
457
 
456
- resp = sb.rpc("create_bind_token", {"p_device_id": did}).execute()
458
+ resp = sb.rpc("create_bind_token", {
459
+ "p_device_id": did,
460
+ "p_purpose": purpose,
461
+ "p_event_code": event_code,
462
+ }).execute()
457
463
  if not resp.data:
458
464
  return _ok({"error": "Failed to create bind token"})
459
465
 
460
466
  token = resp.data.get("token")
467
+ msg = (
468
+ "ๅ‘้€่ฟ™ไธช้“พๆŽฅ็ป™ๆดปๅŠจๅˆ›ๅปบ่€…๏ผŒๅœจๆดปๅŠจๅœฐ็‚นๆ‰“ๅผ€ๅณๅฏ่ฎพๅฎšๆดปๅŠจไฝ็ฝฎใ€‚"
469
+ if purpose == "event"
470
+ else "ๅ‘้€่ฟ™ไธช้“พๆŽฅ็ป™็”จๆˆท๏ผŒๅœจๆ‰‹ๆœบๆต่งˆๅ™จๆ‰“ๅผ€ๅณๅฏๅ…ฑไบซไฝ็ฝฎใ€‚"
471
+ )
461
472
  return _ok({
462
473
  "token": token,
463
474
  "url": f"{BASE_URL}/locate?token={token}",
464
- "message": "ๅ‘้€่ฟ™ไธช้“พๆŽฅ็ป™็”จๆˆท๏ผŒๅœจๆ‰‹ๆœบๆต่งˆๅ™จๆ‰“ๅผ€ๅณๅฏๅ…ฑไบซไฝ็ฝฎใ€‚",
475
+ "purpose": purpose,
476
+ "message": msg,
465
477
  })
466
478
 
467
479
 
package/lib/mcp.js CHANGED
@@ -37,6 +37,39 @@ export async function startMcpServer() {
37
37
  // Store last scan ref map for resolving refs in accept
38
38
  let _lastRefMap = {};
39
39
 
40
+ // Track known device_ids and last notified matches for piggyback notifications
41
+ const _knownDeviceIds = new Set();
42
+ const _notifiedMatches = new Set();
43
+
44
+ // Piggyback match check: append pending notifications to any tool response
45
+ async function withMatchNotifications(deviceId, result) {
46
+ _knownDeviceIds.add(deviceId);
47
+ try {
48
+ const matches = await checkMatches({ device_id: deviceId });
49
+ const notifications = [];
50
+
51
+ for (const m of (matches.mutual_matches || [])) {
52
+ const key = `mutual:${m.device_id}`;
53
+ if (!_notifiedMatches.has(key)) {
54
+ _notifiedMatches.add(key);
55
+ notifications.push(`๐ŸŽ‰ ๅŒๅ‘ๅŒน้…๏ผ${m.emoji || "๐Ÿ‘ค"} ${m.name} ไนŸๆŽฅๅ—ไบ†ไฝ ๏ผ${m.their_contact ? "่”็ณปๆ–นๅผ๏ผš" + m.their_contact : ""}`);
56
+ }
57
+ }
58
+ for (const m of (matches.incoming_accepts || [])) {
59
+ const key = `incoming:${m.device_id}`;
60
+ if (!_notifiedMatches.has(key)) {
61
+ _notifiedMatches.add(key);
62
+ notifications.push(`๐Ÿ“ฉ ${m.emoji || "๐Ÿ‘ค"} ${m.name} ๆƒณ่ฎค่ฏ†ไฝ ๏ผ็”จ antenna_check_matches ๆŸฅ็œ‹่ฏฆๆƒ…ใ€‚`);
63
+ }
64
+ }
65
+
66
+ if (notifications.length > 0) {
67
+ result._pending_notifications = notifications;
68
+ }
69
+ } catch { /* silent */ }
70
+ return result;
71
+ }
72
+
40
73
  // โ”€โ”€โ”€ antenna_scan โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
41
74
 
42
75
  server.tool(
@@ -51,10 +84,11 @@ export async function startMcpServer() {
51
84
  },
52
85
  async ({ lat, lng, radius_m, sender_id, channel }) => {
53
86
  try {
54
- const result = await scan({ lat, lng, radius_m, device_id: deriveDeviceId(sender_id, channel) });
87
+ const deviceId = deriveDeviceId(sender_id, channel);
88
+ const result = await scan({ lat, lng, radius_m, device_id: deviceId });
55
89
  _lastRefMap = result._ref_map || {};
56
90
  const { _ref_map, ...clean } = result;
57
- return jsonResult(clean);
91
+ return jsonResult(await withMatchNotifications(deviceId, clean));
58
92
  } catch (e) {
59
93
  return jsonResult({ error: e.message });
60
94
  }
@@ -83,10 +117,11 @@ export async function startMcpServer() {
83
117
  try {
84
118
  if (action === "get") {
85
119
  const data = await getProfile({ device_id: deviceId });
86
- return jsonResult(data ? { profile: data } : { profile: null, message: "่ฟ˜ๆฒกๆœ‰ๅ็‰‡๏ผŒๅธฎไฝ ๅˆ›ๅปบไธ€ไธช๏ผŸ" });
120
+ const result = data ? { profile: data } : { profile: null, message: "่ฟ˜ๆฒกๆœ‰ๅ็‰‡๏ผŒๅธฎไฝ ๅˆ›ๅปบไธ€ไธช๏ผŸ" };
121
+ return jsonResult(await withMatchNotifications(deviceId, result));
87
122
  }
88
123
  const data = await setProfile({ device_id: deviceId, display_name, emoji, line1, line2, line3, matching_context, visible });
89
- return jsonResult({ saved: true, profile: data });
124
+ return jsonResult(await withMatchNotifications(deviceId, { saved: true, profile: data }));
90
125
  } catch (e) {
91
126
  return jsonResult({ error: e.message });
92
127
  }
@@ -107,8 +142,9 @@ export async function startMcpServer() {
107
142
  },
108
143
  async ({ sender_id, channel, ref, target_device_id, contact_info }) => {
109
144
  try {
110
- const result = await accept({ device_id: deriveDeviceId(sender_id, channel), target_device_id, ref, contact_info });
111
- return jsonResult(result);
145
+ const deviceId = deriveDeviceId(sender_id, channel);
146
+ const result = await accept({ device_id: deviceId, target_device_id, ref, contact_info });
147
+ return jsonResult(await withMatchNotifications(deviceId, result));
112
148
  } catch (e) {
113
149
  return jsonResult({ error: e.message });
114
150
  }
@@ -129,11 +165,12 @@ export async function startMcpServer() {
129
165
  },
130
166
  async ({ lat, lng, sender_id, channel, place_name }) => {
131
167
  try {
132
- const result = await checkin({ lat, lng, device_id: deriveDeviceId(sender_id, channel) });
168
+ const deviceId = deriveDeviceId(sender_id, channel);
169
+ const result = await checkin({ lat, lng, device_id: deviceId });
133
170
  if (result.checked_in && place_name) {
134
171
  result.message = `ๅทฒ็ญพๅˆฐ (${place_name}) ๐Ÿ“ ็Žฐๅœจ้™„่ฟ‘็š„ไบบๆ‰ซๆๅฐฑ่ƒฝ็œ‹ๅˆฐไฝ ไบ†ใ€‚`;
135
172
  }
136
- return jsonResult(result);
173
+ return jsonResult(await withMatchNotifications(deviceId, result));
137
174
  } catch (e) {
138
175
  return jsonResult({ error: e.message });
139
176
  }
@@ -151,7 +188,11 @@ export async function startMcpServer() {
151
188
  },
152
189
  async ({ sender_id, channel }) => {
153
190
  try {
154
- const result = await checkMatches({ device_id: deriveDeviceId(sender_id, channel) });
191
+ const deviceId = deriveDeviceId(sender_id, channel);
192
+ const result = await checkMatches({ device_id: deviceId });
193
+ // Mark all as notified since user explicitly checked
194
+ for (const m of (result.mutual_matches || [])) _notifiedMatches.add(`mutual:${m.device_id}`);
195
+ for (const m of (result.incoming_accepts || [])) _notifiedMatches.add(`incoming:${m.device_id}`);
155
196
  return jsonResult(result);
156
197
  } catch (e) {
157
198
  return jsonResult({ error: e.message });
@@ -163,14 +204,16 @@ export async function startMcpServer() {
163
204
 
164
205
  server.tool(
165
206
  "antenna_bind",
166
- "Generate a GPS binding link. Send this to the user so they can share their phone's location via the web.",
207
+ "Generate a GPS binding link. Send this to the user so they can share their phone's location via the web. Use purpose='event' + event_code when creating a link for setting an event's location.",
167
208
  {
168
209
  sender_id: z.string().describe("The sender's user ID"),
169
210
  channel: z.string().describe("Channel name"),
211
+ purpose: z.string().optional().describe("'profile' (default) or 'event'"),
212
+ event_code: z.string().optional().describe("Event code (required when purpose=event)"),
170
213
  },
171
- async ({ sender_id, channel }) => {
214
+ async ({ sender_id, channel, purpose, event_code }) => {
172
215
  try {
173
- const result = await createBindToken({ device_id: deriveDeviceId(sender_id, channel) });
216
+ const result = await createBindToken({ device_id: deriveDeviceId(sender_id, channel), purpose, event_code });
174
217
  return jsonResult(result);
175
218
  } catch (e) {
176
219
  return jsonResult({ error: e.message });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-fyi",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
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
@@ -112,9 +112,12 @@ Check for mutual matches and contact info updates.
112
112
  ### `antenna_bind`
113
113
  Generate a GPS binding link. **You MUST call this immediately after saving a profile.** Do not skip this step.
114
114
  - `sender_id`, `channel`: from context
115
+ - `purpose`: optional โ€” `'profile'` (default) updates user location; `'event'` sets event location
116
+ - `event_code`: required when `purpose='event'`
115
117
  - Returns a URL like `https://www.antenna.fyi/locate?token=xxx`
116
118
  - Send this link to the user โ€” they open it on their phone, allow GPS, and their location is automatically shared
117
119
  - **MANDATORY after profile save. Do not wait for user to ask.**
120
+ - **For events:** When a creator needs to set event location, call with `purpose='event'` and `event_code`. The GPS will update the event's coordinates, NOT the user's profile.
118
121
 
119
122
  ### `antenna_discover`
120
123
  Get today's global recommendation โ€” the person most similar to you worldwide. 1 per day, no repeats.
@@ -282,7 +285,7 @@ Create an event. Returns a shareable link (antenna.fyi/e/CODE).
282
285
  - `description`: optional event description
283
286
  - `og_image`: optional OG image URL for social sharing preview
284
287
 
285
- **GPS flow for events:** If the user doesn't provide coordinates, generate a bind link (`antenna_bind`) and ask them to open it at the event location. Once GPS comes in, use those coordinates for `lat`/`lng`. Don't use profile location โ€” the user may create an event at a different place.
288
+ **GPS flow for events:** If the user doesn't provide coordinates, generate a bind link (`antenna_bind`) and ask them to open it at the event location. Once GPS comes in, use those coordinates for the event's `lat`/`lng` โ€” do NOT treat this as the user's personal location. The bind link GPS for event creation goes to the event, not the user's profile. Only use `antenna_checkin` when the user wants to update their own location.
286
289
 
287
290
  ### `antenna_event_end`
288
291
  End an event. Only the creator can end it.