antenna-fyi 1.3.27 → 1.3.29

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
@@ -16,6 +16,7 @@ import {
16
16
  handleConfig,
17
17
  handleStatus,
18
18
  handleLinkAccount,
19
+ handleDrift,
19
20
  handleInstallSkill,
20
21
  handleInstallPlugin,
21
22
  handleInstallHermesPlugin,
@@ -41,6 +42,8 @@ async function main() {
41
42
  return handleDiscover(f);
42
43
  case "event":
43
44
  return handleEvent(f);
45
+ case "drift":
46
+ return handleDrift(f);
44
47
  case "bind":
45
48
  return handleBind(f);
46
49
  case "pass":
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, sendEventMessage, getMyEventMessages, getClient, verifyApiKey, linkAccount, initialRecommendations } 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, verifyApiKey, linkAccount, initialRecommendations, throwDriftBottle, pickDriftBottle, replyDriftBottle, checkDriftBottles, getMyBottles } 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";
@@ -46,7 +46,7 @@ export async function handleScan(f) {
46
46
  console.log(`šŸ“” ${result.count} people within ${result.radius_m}m:\n`);
47
47
  }
48
48
  result.profiles.forEach((p) => {
49
- console.log(` ${p.emoji} ${p.name}${p.distance_m != null ? ` (${Math.round(p.distance_m)}m)` : ""}`);
49
+ console.log(` ${p.name}${p.distance_m != null ? ` (${Math.round(p.distance_m)}m)` : ""}`);
50
50
  if (p.personal_description) console.log(` ${p.personal_description}`);
51
51
  if (p.looking_for) console.log(` ${p.looking_for}`);
52
52
  if (p.conversation_style) console.log(` ${p.conversation_style}`);
@@ -66,12 +66,11 @@ export async function handleScan(f) {
66
66
 
67
67
  export async function handleProfile(f) {
68
68
  const id = resolveId(f);
69
- if (!id) return console.error("Usage: antenna profile --id <platform>:<user_id> [--name Yi --emoji 🦦 --personal-description '...' --looking-for '...' --conversation-style '...'].']");
69
+ if (!id) return console.error("Usage: antenna profile --id <platform>:<user_id> [--name Yi --personal-description '...' --looking-for '...' --conversation-style '...' --hide --visible true]");
70
70
  if (f.name || f["personal-description"] || f["looking-for"] || f["conversation-style"] || f["more-information"] || f.visible !== undefined || f.hide !== undefined) {
71
71
  const visible = f.hide ? false : (f.visible !== undefined ? f.visible === 'true' || f.visible === true : undefined);
72
72
  const payload = { device_id: id };
73
73
  if (f.name) payload.display_name = f.name;
74
- if (f.emoji) payload.emoji = f.emoji;
75
74
  if (f["personal-description"] !== undefined) payload.line1 = f["personal-description"];
76
75
  if (f["looking-for"] !== undefined) payload.line2 = f["looking-for"];
77
76
  if (f["conversation-style"] !== undefined) payload.line3 = f["conversation-style"];
@@ -83,7 +82,7 @@ export async function handleProfile(f) {
83
82
  } else {
84
83
  const data = await getProfile({ device_id: id });
85
84
  if (!data) return console.log("No profile yet. Create one with --name and --personal-description");
86
- console.log(`${data.emoji || "šŸ‘¤"} ${data.display_name || "Anonymous"}`);
85
+ console.log(`${data.display_name || "Anonymous"}`);
87
86
  if (data.personal_description) console.log(` ${data.personal_description}`);
88
87
  if (data.looking_for) console.log(` Looking for: ${data.looking_for}`);
89
88
  if (data.conversation_style) console.log(` Conversation: ${data.conversation_style}`);
@@ -125,13 +124,13 @@ export async function handleMatches(f) {
125
124
  return console.log(result.message);
126
125
  }
127
126
  for (const m of result.mutual_matches) {
128
- console.log(`šŸŽ‰ MUTUAL: ${m.emoji} ${m.name}`);
127
+ console.log(`šŸŽ‰ MUTUAL: ${m.name}`);
129
128
  if (m.their_contact) console.log(` Their contact: ${m.their_contact}`);
130
129
  if (m.you_shared) console.log(` You shared: ${m.you_shared}`);
131
130
  console.log();
132
131
  }
133
132
  for (const m of result.incoming_accepts) {
134
- console.log(`šŸ“© WANTS TO MEET YOU: ${m.emoji} ${m.name}`);
133
+ console.log(`šŸ“© WANTS TO MEET YOU: ${m.name}`);
135
134
  if (m.personal_description) console.log(` ${m.personal_description}`);
136
135
  console.log(` Accept: antenna accept --id ${f.id} --ref ${m.ref}`);
137
136
  console.log();
@@ -145,7 +144,7 @@ export async function handleDiscover(f) {
145
144
  if (result.count === 0) return console.log(result.message || "šŸŒ No global recommendation available right now.");
146
145
  console.log(`šŸŒ Global discover:\n`);
147
146
  result.profiles.forEach((p) => {
148
- console.log(` ${p.emoji} ${p.name}`);
147
+ console.log(` ${p.name}`);
149
148
  if (p.personal_description) console.log(` ${p.personal_description}`);
150
149
  if (p.looking_for) console.log(` ${p.looking_for}`);
151
150
  if (p.conversation_style) console.log(` ${p.conversation_style}`);
@@ -232,7 +231,7 @@ export async function handleEvent(f) {
232
231
  const badge = p.checked_in ? " āœ…" : "";
233
232
  const creatorTag = p.role === "creator" ? " [äø»åŠž]" : "";
234
233
  const statusTag = p.status === "pending" ? " šŸŸ”å¾…å®”ę‰¹" : "";
235
- console.log(` ${p.emoji} ${p.name}${creatorTag}${badge}${statusTag}`);
234
+ console.log(` ${p.name}${creatorTag}${badge}${statusTag}`);
236
235
  if (p.personal_description) console.log(` ${p.personal_description}`);
237
236
  if (p.application_context) console.log(` šŸ“ ${p.application_context}`);
238
237
  console.log(` ref: ${p.ref}\n`);
@@ -337,7 +336,6 @@ export async function handleSetup(f) {
337
336
  if (!id) { rl.close(); return console.error("Device ID is required."); }
338
337
 
339
338
  const name = await ask("Display name: ");
340
- const emoji = (await ask("Emoji (default šŸ‘¤): ")) || "šŸ‘¤";
341
339
  const personalDesc = await ask("Personal description — who you are, what you do: ");
342
340
  const lookingFor = await ask("Looking for — the kind of people you want to meet: ");
343
341
  const convStyle = await ask("Conversation style — the type of conversations you want: ");
@@ -347,14 +345,14 @@ export async function handleSetup(f) {
347
345
  const data = await setProfile({
348
346
  device_id: id,
349
347
  display_name: name || null,
350
- emoji,
348
+
351
349
  line1: personalDesc || null,
352
350
  line2: lookingFor || null,
353
351
  line3: convStyle || null,
354
352
  });
355
353
 
356
354
  console.log("\nāœ… Profile saved!\n");
357
- console.log(` ${emoji} ${name || "Anonymous"}`);
355
+ console.log(` ${name || "Anonymous"}`);
358
356
  if (personalDesc) console.log(` ${personalDesc}`);
359
357
  if (lookingFor) console.log(` ${lookingFor}`);
360
358
  if (convStyle) console.log(` ${convStyle}`);
@@ -469,7 +467,7 @@ export async function handleStatus(f) {
469
467
  if (f.id) {
470
468
  const profile = await getProfile({ device_id: f.id });
471
469
  if (profile) {
472
- console.log(` Profile: āœ… ${profile.emoji || "šŸ‘¤"} ${profile.display_name || "Anonymous"}`);
470
+ console.log(` Profile: āœ… ${profile.display_name || "Anonymous"}`);
473
471
  } else {
474
472
  console.log(" Profile: āŒ Not created yet");
475
473
  }
@@ -779,7 +777,7 @@ export async function handleWatch(f) {
779
777
  for (const m of initial.mutual_matches) {
780
778
  const key = `mutual:${m._device_id}`;
781
779
  notified.add(key);
782
- _log(` ${m.emoji || "šŸ‘¤"} ${m.name}${m.their_contact ? " — contact: " + m.their_contact : ""}`);
780
+ _log(` ${m.name}${m.their_contact ? " — contact: " + m.their_contact : ""}`);
783
781
  }
784
782
  saveNotified(notified);
785
783
  _log("");
@@ -789,7 +787,7 @@ export async function handleWatch(f) {
789
787
  for (const m of initial.incoming_accepts) {
790
788
  const key = `incoming:${m._device_id}`;
791
789
  notified.add(key);
792
- _log(` ${m.emoji || "šŸ‘¤"} ${m.name} — ${m.personal_description || ""}`);
790
+ _log(` ${m.name} — ${m.personal_description || ""}`);
793
791
  }
794
792
  saveNotified(notified);
795
793
  _log("");
@@ -822,7 +820,6 @@ export async function handleWatch(f) {
822
820
 
823
821
  const profile = await getProfile({ device_id: row.device_id_a });
824
822
  const name = profile?.display_name || "Someone";
825
- const emoji = profile?.emoji || "šŸ‘¤";
826
823
 
827
824
  // Check if mutual
828
825
  const matches = await checkMatches({ device_id: id });
@@ -833,11 +830,11 @@ export async function handleWatch(f) {
833
830
  notified.add(mutualKey);
834
831
  saveNotified(notified);
835
832
  const contact = row.contact_info_a;
836
- pushNotify(`šŸŽ‰ MUTUAL MATCH! ${emoji} ${name} also accepted you!${contact ? " Contact: " + contact : ""}`);
833
+ pushNotify(`šŸŽ‰ MUTUAL MATCH! ${name} also accepted you!${contact ? " Contact: " + contact : ""}`);
837
834
  } else {
838
835
  notified.add(key);
839
836
  saveNotified(notified);
840
- pushNotify(`šŸ“© ${emoji} ${name} wants to meet you! Use 'antenna matches --id ${id}' to respond.`);
837
+ pushNotify(`šŸ“© ${name} wants to meet you! Use 'antenna matches --id ${id}' to respond.`);
841
838
  }
842
839
  }
843
840
 
@@ -850,7 +847,7 @@ export async function handleWatch(f) {
850
847
  if (!notified.has(mutualKey)) {
851
848
  notified.add(mutualKey);
852
849
  saveNotified(notified);
853
- pushNotify(`šŸŽ‰ MUTUAL MATCH! ${mutual.emoji || "šŸ‘¤"} ${mutual.name}!${mutual.their_contact ? " Contact: " + mutual.their_contact : ""}`);
850
+ pushNotify(`šŸŽ‰ MUTUAL MATCH! ${mutual.name}!${mutual.their_contact ? " Contact: " + mutual.their_contact : ""}`);
854
851
  }
855
852
  }
856
853
  }
@@ -897,8 +894,7 @@ export async function handleWatch(f) {
897
894
  if (!event?.found || event.created_by !== id) return;
898
895
  const applicant = await getProfile({ device_id: row.device_id });
899
896
  const name = applicant?.display_name || "Someone";
900
- const emoji = applicant?.emoji || "šŸ‘¤";
901
- pushNotify(`šŸ“© ${emoji} ${name} applied to join \"${event.name}\"! Run: antenna event --scan --code ${event.code} --id ${id}`);
897
+ pushNotify(`šŸ“© ${name} applied to join \"${event.name}\"! Run: antenna event --scan --code ${event.code} --id ${id}`);
902
898
  } catch {}
903
899
  }
904
900
  )
@@ -937,7 +933,7 @@ export async function handleWatch(f) {
937
933
  if (!notified.has(key)) {
938
934
  notified.add(key);
939
935
  saveNotified(notified);
940
- pushNotify(`šŸŽ‰ MUTUAL MATCH! ${m.emoji || "šŸ‘¤"} ${m.name}!${m.their_contact ? " Contact: " + m.their_contact : ""}`);
936
+ pushNotify(`šŸŽ‰ MUTUAL MATCH! ${m.name}!${m.their_contact ? " Contact: " + m.their_contact : ""}`);
941
937
  }
942
938
  }
943
939
  for (const m of (result.incoming_accepts || [])) {
@@ -945,7 +941,7 @@ export async function handleWatch(f) {
945
941
  if (!notified.has(key)) {
946
942
  notified.add(key);
947
943
  saveNotified(notified);
948
- pushNotify(`šŸ“© ${m.emoji || "šŸ‘¤"} ${m.name} wants to meet you!`);
944
+ pushNotify(`šŸ“© ${m.name} wants to meet you!`);
949
945
  }
950
946
  }
951
947
  } catch { /* silent */ }
@@ -976,7 +972,7 @@ export async function handleWatch(f) {
976
972
  notified.add(key);
977
973
  saveNotified(notified);
978
974
  const role = msg.sender_role === 'creator' ? '组织者' : '协办';
979
- pushNotify(`šŸ“¢ ę„č‡Ŗć€Œ${msg.event_name}怍${role} ${msg.sender_emoji || ''} ${msg.sender_name}: ${msg.message}`);
975
+ pushNotify(`šŸ“¢ ę„č‡Ŗć€Œ${msg.event_name}怍${role} ${msg.sender_name}: ${msg.message}`);
980
976
  }
981
977
  }
982
978
  } catch { /* silent */ }
@@ -991,18 +987,80 @@ export async function handleWatch(f) {
991
987
  });
992
988
  }
993
989
 
990
+ export async function handleDrift(f) {
991
+ const id = resolveId(f);
992
+ if (!id) return console.error("Usage: antenna drift --id <platform>:<user_id> --throw --message 'hello'");
993
+
994
+ if (f.throw) {
995
+ if (!f.message) return console.error("Usage: antenna drift --throw --message 'your message' --id <platform>:<user_id>");
996
+ const result = await throwDriftBottle({ device_id: id, message: f.message });
997
+ if (result.error) return console.error(`āŒ ${result.error}`);
998
+ console.log("šŸ¾ 漂流瓶已丢兄海中!");
999
+ console.log(JSON.stringify(result, null, 2));
1000
+ return;
1001
+ }
1002
+
1003
+ if (f.pick) {
1004
+ const result = await pickDriftBottle({ device_id: id });
1005
+ if (result.error) return console.error(`āŒ ${result.error}`);
1006
+ if (!result.bottle_id) return console.log(result.message || "🌊 ęµ·äøŠę²”ęœ‰ę¼‚ęµē“¶äŗ†ć€‚");
1007
+ console.log(`šŸ¾ ę”åˆ°äø€äøŖę¼‚ęµē“¶ļ¼\n`);
1008
+ console.log(` ${result.message}\n`);
1009
+ console.log(` 回复: antenna drift --reply --bottle-id ${result.bottle_id} --message 'ä½ ēš„å›žå¤' --id ${id}`);
1010
+ return;
1011
+ }
1012
+
1013
+ if (f.reply) {
1014
+ if (!f['bottle-id'] || !f.message) return console.error("Usage: antenna drift --reply --bottle-id <uuid> --message 'your reply' --id <platform>:<user_id>");
1015
+ const result = await replyDriftBottle({ bottle_id: f['bottle-id'], device_id: id, reply: f.message });
1016
+ if (result.error) return console.error(`āŒ ${result.error}`);
1017
+ console.log("šŸ’¬ å›žå¤å·²ę¼‚å›žē»™äø¢ē“¶å­ēš„äŗŗļ¼");
1018
+ return;
1019
+ }
1020
+
1021
+ if (f.check) {
1022
+ const result = await checkDriftBottles({ device_id: id });
1023
+ if (result.error) return console.error(`āŒ ${result.error}`);
1024
+ console.log(JSON.stringify(result, null, 2));
1025
+ return;
1026
+ }
1027
+
1028
+ if (f['my-bottles']) {
1029
+ const result = await getMyBottles({ device_id: id });
1030
+ if (result.error) return console.error(`āŒ ${result.error}`);
1031
+ const bottles = Array.isArray(result) ? result : (result.bottles || []);
1032
+ if (bottles.length === 0) return console.log("🌊 ä½ čæ˜ę²”äø¢čæ‡ę¼‚ęµē“¶ć€‚");
1033
+ console.log(`šŸ¾ ä½ ēš„ę¼‚ęµē“¶ (${bottles.length}):\n`);
1034
+ for (const b of bottles) {
1035
+ const status = b.reply ? "šŸ’¬ 已回复" : b.picked_by ? "šŸ‘€ č¢«ę”čµ·" : "🌊 漂流中";
1036
+ console.log(` ${status} — ${b.message}`);
1037
+ if (b.reply) console.log(` ↩ ${b.reply}`);
1038
+ console.log();
1039
+ }
1040
+ return;
1041
+ }
1042
+
1043
+ console.log(`Usage:
1044
+ antenna drift --throw --message 'hello' --id <platform>:<user_id>
1045
+ antenna drift --pick --id <platform>:<user_id>
1046
+ antenna drift --reply --bottle-id <uuid> --message 'reply' --id <platform>:<user_id>
1047
+ antenna drift --check --id <platform>:<user_id>
1048
+ antenna drift --my-bottles --id <platform>:<user_id>`);
1049
+ }
1050
+
994
1051
  export function printHelp() {
995
1052
  console.log(`šŸ“” Antenna — nearby people discovery
996
1053
 
997
1054
  Usage:
998
1055
  antenna scan --lat 39.99 --lng 116.48 [--radius 500] (max 1000) [--id <platform>:<user_id>]
999
1056
  antenna checkin --id <platform>:<user_id> --lat 39.99 --lng 116.48
1000
- antenna profile --id <platform>:<user_id> [--name Yi --emoji 🦦 --personal-description '...']
1057
+ antenna profile --id <platform>:<user_id> [--name Yi --personal-description '...' --looking-for '...' --conversation-style '...' --hide --visible true]
1001
1058
  antenna accept --id <platform>:<user_id> --target <ref_or_device_id> [--contact 'WeChat: yi']
1002
1059
  antenna pass --id <platform>:<user_id> --target <ref_or_device_id> (or --ref 1)
1003
1060
  antenna matches --id <platform>:<user_id>
1004
1061
  antenna discover --id <platform>:<user_id>
1005
1062
  antenna event --create --name 'AI Meetup' --starts-at '...' --ends-at '...' [--lat 34.05 --lng -118.25] [--desc '...'] [--og-image 'url'] [--requires-approval] [--screening-questions 'Q1|Q2'] | --join --code abc123 | --scan --code abc123 | --end --code abc123 --id <platform>:<user_id> | --upload-image --code abc123 --file /path/to/image.png | --update --code abc123 --name 'New Name' | --approve --code abc123 --ref 1 | --reject --code abc123 --ref 1 | --add-host --code abc123 --ref 1
1063
+ antenna drift --throw --message 'hello' | --pick | --reply --bottle-id <uuid> --message 'reply' | --check | --my-bottles --id <platform>:<user_id>
1006
1064
  antenna watch --id <platform>:<user_id> [--push hermes|openclaw|terminal] Watch for new matches in real-time (Ctrl+C to stop)
1007
1065
  antenna bind --id <platform>:<user_id>
1008
1066
  antenna serve Start MCP server (stdio transport)
package/lib/core.js CHANGED
@@ -121,7 +121,6 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
121
121
  return {
122
122
  ref,
123
123
  name: p.display_name || "åŒæå",
124
- emoji: p.emoji || "šŸ‘¤",
125
124
  personal_description: p.line1,
126
125
  looking_for: p.line2,
127
126
  conversation_style: p.line3,
@@ -247,7 +246,6 @@ export async function getProfile({ device_id, supabaseUrl, supabaseKey }) {
247
246
  export async function setProfile({
248
247
  device_id,
249
248
  display_name,
250
- emoji,
251
249
  line1,
252
250
  line2,
253
251
  line3,
@@ -297,7 +295,7 @@ export async function setProfile({
297
295
  const { data, error } = await sb.rpc("upsert_profile", {
298
296
  p_device_id: device_id,
299
297
  p_display_name: display_name || null,
300
- p_emoji: emoji || null,
298
+ p_emoji: null,
301
299
  p_line1: line1 || null,
302
300
  p_line2: line2 || null,
303
301
  p_line3: line3 || null,
@@ -371,6 +369,7 @@ export async function setProfile({
371
369
  await sb.rpc("upsert_profile", {
372
370
  p_device_id: device_id,
373
371
  p_matching_context: JSON.stringify(ctx),
372
+ p_visible: profile?.visible ?? true,
374
373
  });
375
374
  } catch {}
376
375
  }
@@ -501,7 +500,6 @@ export async function checkMatches({ device_id, supabaseUrl, supabaseKey }) {
501
500
  ref: String(i + 1),
502
501
  _device_id: m.target_id,
503
502
  name: m.name || "åŒæå",
504
- emoji: m.emoji || "šŸ‘¤",
505
503
  personal_description: m.line1,
506
504
  looking_for: m.line2,
507
505
  conversation_style: m.line3,
@@ -514,7 +512,6 @@ export async function checkMatches({ device_id, supabaseUrl, supabaseKey }) {
514
512
  ref: String(incomingOffset + i + 1),
515
513
  _device_id: m.target_id,
516
514
  name: m.name || "åŒæå",
517
- emoji: m.emoji || "šŸ‘¤",
518
515
  personal_description: m.line1,
519
516
  looking_for: m.line2,
520
517
  conversation_style: m.line3,
@@ -586,7 +583,6 @@ export async function discover({ device_id, supabaseUrl, supabaseKey }) {
586
583
  profiles.push({
587
584
  ref,
588
585
  name: p.display_name || "åŒæå",
589
- emoji: p.emoji || "šŸ‘¤",
590
586
  personal_description: p.line1,
591
587
  looking_for: p.line2,
592
588
  conversation_style: p.line3,
@@ -661,7 +657,6 @@ export async function initialRecommendations({ device_id, supabaseUrl, supabaseK
661
657
  profiles.push({
662
658
  ref,
663
659
  name: p.display_name || "åŒæå",
664
- emoji: p.emoji || "šŸ‘¤",
665
660
  personal_description: p.line1,
666
661
  looking_for: p.line2,
667
662
  conversation_style: p.line3,
@@ -854,7 +849,6 @@ export async function eventScan({ code, device_id, supabaseUrl, supabaseKey }) {
854
849
  return {
855
850
  ref,
856
851
  name: p.display_name || "åŒæå",
857
- emoji: p.emoji || "šŸ‘¤",
858
852
  personal_description: p.line1,
859
853
  looking_for: p.line2,
860
854
  conversation_style: p.line3,
@@ -994,3 +988,40 @@ export async function linkAccount({ device_id, api_key, supabaseUrl, supabaseKey
994
988
  message: "č“¦å·å·²å…³č”ļ¼ēŽ°åœØä½ åÆä»„åœØ antenna.fyi/me ēœ‹åˆ°ä½ ēš„å®Œę•“ profile å’ŒåŒ¹é…č®°å½•äŗ†ć€‚",
995
989
  };
996
990
  }
991
+
992
+ // ─── Drift Bottle (漂流瓶) ──────────────────────────────────────────
993
+
994
+ export async function throwDriftBottle({ device_id, message }) {
995
+ const sb = getClient();
996
+ const { data, error } = await sb.rpc("throw_drift_bottle", { p_device_id: device_id, p_message: message });
997
+ if (error) return { error: error.message };
998
+ return data;
999
+ }
1000
+
1001
+ export async function pickDriftBottle({ device_id }) {
1002
+ const sb = getClient();
1003
+ const { data, error } = await sb.rpc("pick_drift_bottle", { p_device_id: device_id });
1004
+ if (error) return { error: error.message };
1005
+ return data;
1006
+ }
1007
+
1008
+ export async function replyDriftBottle({ bottle_id, device_id, reply }) {
1009
+ const sb = getClient();
1010
+ const { data, error } = await sb.rpc("reply_drift_bottle", { p_bottle_id: bottle_id, p_device_id: device_id, p_reply: reply });
1011
+ if (error) return { error: error.message };
1012
+ return data;
1013
+ }
1014
+
1015
+ export async function checkDriftBottles({ device_id }) {
1016
+ const sb = getClient();
1017
+ const { data, error } = await sb.rpc("check_drift_bottles", { p_device_id: device_id });
1018
+ if (error) return { error: error.message };
1019
+ return data;
1020
+ }
1021
+
1022
+ export async function getMyBottles({ device_id }) {
1023
+ const sb = getClient();
1024
+ const { data, error } = await sb.rpc("get_my_bottles", { p_device_id: device_id });
1025
+ if (error) return { error: error.message };
1026
+ return data;
1027
+ }
package/lib/mcp.js CHANGED
@@ -26,6 +26,11 @@ import {
26
26
  addCohost,
27
27
  sendEventMessage,
28
28
  linkAccount,
29
+ throwDriftBottle,
30
+ pickDriftBottle,
31
+ replyDriftBottle,
32
+ checkDriftBottles,
33
+ getMyBottles,
29
34
  deriveDeviceId,
30
35
  PROFILE_FIELDS,
31
36
  } from "./core.js";
@@ -60,14 +65,14 @@ export async function startMcpServer() {
60
65
  const key = `mutual:${m.device_id}`;
61
66
  if (!_notifiedMatches.has(key)) {
62
67
  _notifiedMatches.add(key);
63
- notifications.push(`šŸŽ‰ åŒå‘åŒ¹é…ļ¼${m.emoji || "šŸ‘¤"} ${m.name} ä¹ŸęŽ„å—äŗ†ä½ ļ¼${m.their_contact ? "č”ē³»ę–¹å¼ļ¼š" + m.their_contact : ""}`);
68
+ notifications.push(`šŸŽ‰ åŒå‘åŒ¹é…ļ¼${m.name} ä¹ŸęŽ„å—äŗ†ä½ ļ¼${m.their_contact ? "č”ē³»ę–¹å¼ļ¼š" + m.their_contact : ""}`);
64
69
  }
65
70
  }
66
71
  for (const m of (matches.incoming_accepts || [])) {
67
72
  const key = `incoming:${m.device_id}`;
68
73
  if (!_notifiedMatches.has(key)) {
69
74
  _notifiedMatches.add(key);
70
- notifications.push(`šŸ“© ${m.emoji || "šŸ‘¤"} ${m.name} ęƒ³č®¤čÆ†ä½ ļ¼ē”Ø antenna_check_matches ęŸ„ēœ‹čÆ¦ęƒ…ć€‚`);
75
+ notifications.push(`šŸ“© ${m.name} ęƒ³č®¤čÆ†ä½ ļ¼ē”Ø antenna_check_matches ęŸ„ēœ‹čÆ¦ęƒ…ć€‚`);
71
76
  }
72
77
  }
73
78
 
@@ -554,6 +559,94 @@ export async function startMcpServer() {
554
559
  }
555
560
  );
556
561
 
562
+ // ─── antenna_drift_throw ──────────────────────────────────────
563
+
564
+ server.tool(
565
+ "antenna_drift_throw",
566
+ "Throw a drift bottle into the sea. Write a message (max 500 chars), a random stranger will pick it up. Completely anonymous.",
567
+ {
568
+ sender_id: z.string().describe("The sender's user ID"),
569
+ channel: z.string().describe("Channel name"),
570
+ message: z.string().describe("Message to put in the bottle (max 500 chars)"),
571
+ },
572
+ async ({ sender_id, channel, message }) => {
573
+ try {
574
+ const result = await throwDriftBottle({ device_id: deriveDeviceId(sender_id, channel), message });
575
+ return jsonResult(result);
576
+ } catch (e) { return jsonResult({ error: e.message }); }
577
+ }
578
+ );
579
+
580
+ // ─── antenna_drift_pick ───────────────────────────────────────
581
+
582
+ server.tool(
583
+ "antenna_drift_pick",
584
+ "Pick up a random drift bottle from the sea. Returns the anonymous message inside. You must reply before picking another.",
585
+ {
586
+ sender_id: z.string().describe("The sender's user ID"),
587
+ channel: z.string().describe("Channel name"),
588
+ },
589
+ async ({ sender_id, channel }) => {
590
+ try {
591
+ const result = await pickDriftBottle({ device_id: deriveDeviceId(sender_id, channel) });
592
+ return jsonResult(result);
593
+ } catch (e) { return jsonResult({ error: e.message }); }
594
+ }
595
+ );
596
+
597
+ // ─── antenna_drift_reply ──────────────────────────────────────
598
+
599
+ server.tool(
600
+ "antenna_drift_reply",
601
+ "Reply to a drift bottle you picked up. Your reply will anonymously float back to the original sender.",
602
+ {
603
+ sender_id: z.string().describe("The sender's user ID"),
604
+ channel: z.string().describe("Channel name"),
605
+ bottle_id: z.string().describe("ID of the bottle to reply to"),
606
+ reply: z.string().describe("Reply message (max 500 chars)"),
607
+ },
608
+ async ({ sender_id, channel, bottle_id, reply }) => {
609
+ try {
610
+ const result = await replyDriftBottle({ bottle_id, device_id: deriveDeviceId(sender_id, channel), reply });
611
+ return jsonResult(result);
612
+ } catch (e) { return jsonResult({ error: e.message }); }
613
+ }
614
+ );
615
+
616
+ // ─── antenna_drift_check ──────────────────────────────────────
617
+
618
+ server.tool(
619
+ "antenna_drift_check",
620
+ "Check drift bottle status — any new replies on bottles you threw, or pending bottles you picked up.",
621
+ {
622
+ sender_id: z.string().describe("The sender's user ID"),
623
+ channel: z.string().describe("Channel name"),
624
+ },
625
+ async ({ sender_id, channel }) => {
626
+ try {
627
+ const result = await checkDriftBottles({ device_id: deriveDeviceId(sender_id, channel) });
628
+ return jsonResult(result);
629
+ } catch (e) { return jsonResult({ error: e.message }); }
630
+ }
631
+ );
632
+
633
+ // ─── antenna_drift_my_bottles ─────────────────────────────────
634
+
635
+ server.tool(
636
+ "antenna_drift_my_bottles",
637
+ "View all drift bottles you've thrown. Shows status: 🌊 drifting / šŸ‘€ picked up / šŸ’¬ replied.",
638
+ {
639
+ sender_id: z.string().describe("The sender's user ID"),
640
+ channel: z.string().describe("Channel name"),
641
+ },
642
+ async ({ sender_id, channel }) => {
643
+ try {
644
+ const result = await getMyBottles({ device_id: deriveDeviceId(sender_id, channel) });
645
+ return jsonResult(result);
646
+ } catch (e) { return jsonResult({ error: e.message }); }
647
+ }
648
+ );
649
+
557
650
  const transport = new StdioServerTransport();
558
651
  await server.connect(transport);
559
652
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-fyi",
3
- "version": "1.3.27",
3
+ "version": "1.3.29",
4
4
  "description": "Antenna \u2014 nearby people discovery. CLI + MCP server + OpenClaw skill & plugin, all in one package.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,4 +26,4 @@
26
26
  "@modelcontextprotocol/sdk": "^1.12.1",
27
27
  "zod": "^3.24.4"
28
28
  }
29
- }
29
+ }
package/skill/SKILL.md CHANGED
@@ -63,17 +63,17 @@ Plugin å®‰č£…åŽ,agent **主动**开始引导,äøč¦ē­‰ē”Øęˆ·é—®ć€‚
63
63
  åē‰‡å­˜å„½åŽ,**ē«‹åˆ»**调 `antenna_initial_recommendations` čŽ·å–é¦–ę¬”ęŽØč:
64
64
  > "åē‰‡å­˜å„½äŗ†!ęˆ‘å…ˆåø®ä½ ēœ‹ēœ‹ęœ‰č°č·Ÿä½ ē‰¹åˆ«åƒ--"
65
65
 
66
- čæ™ę˜Æäø€ę¬”ę€§ēš„é¦–ę¬”ęŽØčļ¼ˆäøę¶ˆč€—ęÆę—„ discover é¢åŗ¦ļ¼‰ć€‚å¦‚ęžœčæ”å›žē©ŗļ¼ŒčÆ“ę˜Žčæ˜ę²”ęœ‰č¶³å¤Ÿēš„ē”Øęˆ·ā€”ā€”č·³čæ‡čæ™ę­„ć€‚
66
+ čæ™ę˜Æäø€ę¬”ę€§ēš„é¦–ę¬”ęŽØč(äøę¶ˆč€—ęÆę—„ discover é¢åŗ¦)ć€‚å¦‚ęžœčæ”å›žē©ŗ,čÆ“ę˜Žčæ˜ę²”ęœ‰č¶³å¤Ÿēš„ē”Øęˆ·--跳过这歄。
67
67
 
68
- **ē¬¬å››ę­„ļ¼šå‘å…¬å¼€é“¾ęŽ„ē»™ē”Øęˆ·**
68
+ **第四歄:å‘å…¬å¼€é“¾ęŽ„ē»™ē”Øęˆ·**
69
69
 
70
- Profile å­˜å„½åŽļ¼Œčæ”å›žé‡Œä¼šęœ‰ `public_url`ļ¼ˆå¦‚ `antenna.fyi/p/yi`)。**åæ…é”»å‘ē»™ē”Øęˆ·ļ¼š**
71
- > "čæ™ę˜Æä½ ēš„å…¬å¼€åē‰‡é“¾ęŽ„ļ¼š[public_url]
72
- > ä½ åÆä»„ęŠŠå®ƒå‘ē»™åˆ«äŗŗļ¼ŒåÆ¹ę–¹ēš„ agent ēœ‹åˆ°é“¾ęŽ„å°±čƒ½ē›“ęŽ„ accept 你。"
70
+ Profile 存儽后,čæ”å›žé‡Œä¼šęœ‰ `public_url`(如 `antenna.fyi/p/yi`)怂**åæ…é”»å‘ē»™ē”Øęˆ·:**
71
+ > "čæ™ę˜Æä½ ēš„å…¬å¼€åē‰‡é“¾ęŽ„:[public_url]
72
+ > ä½ åÆä»„ęŠŠå®ƒå‘ē»™åˆ«äŗŗ,åÆ¹ę–¹ēš„ agent ēœ‹åˆ°é“¾ęŽ„å°±čƒ½ē›“ęŽ„ accept 你。"
73
73
 
74
- **ē¬¬äŗ”ę­„ļ¼šč°ƒ antenna_bind čŽ·å– GPS é“¾ęŽ„**
74
+ **第五歄:调 antenna_bind čŽ·å– GPS é“¾ęŽ„**
75
75
 
76
- **å¼ŗåˆ¶ć€‚** åē‰‡å­˜å„½åŽ**åæ…é”»ē«‹åˆ»**č°ƒē”Ø `antenna_bind` ē”Ÿęˆé“¾ęŽ„å¹¶å‘ē»™ē”Øęˆ·ļ¼š
76
+ **å¼ŗåˆ¶ć€‚** åē‰‡å­˜å„½åŽ**åæ…é”»ē«‹åˆ»**č°ƒē”Ø `antenna_bind` ē”Ÿęˆé“¾ęŽ„å¹¶å‘ē»™ē”Øęˆ·:
77
77
  > "ē‚¹čæ™äøŖé“¾ęŽ„,åœØę‰‹ęœŗęµč§ˆå™Øę‰“å¼€,å…č®øå®šä½å°±č”Œ:[é“¾ęŽ„]"
78
78
 
79
79
  GPS äøę˜Æ Antenna ēš„å”Æäø€å…„å£,ä½†å®ƒå¼€åÆäŗ†é™„čæ‘å‘ēŽ°ēš„čƒ½åŠ›ć€‚
@@ -283,7 +283,62 @@ Plugin åŽå°ęÆ 10 åˆ†é’ŸęŸ„äø€ę¬”ę–°åŒ¹é…ć€‚ēœ‹åˆ° `[Antenna] šŸŽ‰` ę—¶:
283
283
 
284
284
  ## Events
285
285
 
286
- 详见 EVENTS.mdć€‚åŒ…ę‹¬:`antenna_event_create`, `antenna_event_join`, `antenna_event_scan`, `antenna_event_end`, `antenna_event_checkin`, `antenna_event_upload_image`, `antenna_event_update`, `antenna_event_approve`, `antenna_event_reject`, `antenna_event_add_host`, `antenna_event_message`怂
286
+ 详见 EVENTS.mdć€‚åŒ…ę‹¬ļ¼š`antenna_event_create`, `antenna_event_join`, `antenna_event_scan`, `antenna_event_end`, `antenna_event_checkin`, `antenna_event_upload_image`, `antenna_event_update`, `antenna_event_approve`, `antenna_event_reject`, `antenna_event_add_host`, `antenna_event_message`怂
287
+
288
+ ## Drift Bottle (漂流瓶)
289
+
290
+ å†™äø€ę®µčÆļ¼Œäø¢čæ›ęµ·é‡Œć€‚éšęœŗäø€äøŖé™Œē”Ÿäŗŗä¼šę”čµ·å®ƒć€‚å®Œå…ØåŒæåć€éšęœŗć€å„½ēŽ©ć€‚
291
+
292
+ ### č§„åˆ™
293
+ - ęÆę”ę¶ˆęÆęœ€å¤š 500 字
294
+ - äø€ę¬”åŖčƒ½ę”äø€äøŖē“¶å­ļ¼Œå›žå¤åŽę‰čƒ½ę”äø‹äø€äøŖ
295
+ - å®Œå…ØåŒæåļ¼šę°øčæœäøęš“éœ²č°äø¢ēš„ć€č°ę”ēš„
296
+ - 漂流瓶 7 å¤©åŽčæ‡ęœŸ
297
+
298
+ ### Tools
299
+
300
+ #### `antenna_drift_throw`
301
+ 丢一个漂流瓶。
302
+ - `sender_id`, `channel`
303
+ - `message`ļ¼šē“¶äø­ēš„å†…å®¹ļ¼ˆęœ€å¤š 500 字)
304
+ - čæ”å›ž bottle_id å’Œē”®č®¤
305
+
306
+ #### `antenna_drift_pick`
307
+ ę”äø€äøŖę¼‚ęµē“¶ć€‚
308
+ - `sender_id`, `channel`
309
+ - čæ”å›žē“¶äø­ę¶ˆęÆļ¼ˆåŒæåļ¼‰+ bottle_id
310
+ - å¦‚ęžœęµ·äøŠę²”ē“¶äŗ†ļ¼ŒčÆ“ę²”ęœ‰
311
+ - å¦‚ęžœčæ˜ęœ‰ę²”å›žå¤ēš„ē“¶å­ļ¼Œęē¤ŗå…ˆå›žå¤
312
+
313
+ #### `antenna_drift_reply`
314
+ å›žå¤äø€äøŖę”čµ·ēš„ę¼‚ęµē“¶ć€‚
315
+ - `sender_id`, `channel`
316
+ - `bottle_id`ļ¼šč¦å›žå¤ēš„ē“¶å­ ID
317
+ - `reply`ļ¼šå›žå¤å†…å®¹ļ¼ˆęœ€å¤š 500 字)
318
+ - å›žå¤ä¼šåŒæåę¼‚å›žē»™äø¢ē“¶å­ēš„äŗŗ
319
+
320
+ #### `antenna_drift_check`
321
+ ę£€ęŸ„ę¼‚ęµē“¶ēŠ¶ę€ć€‚
322
+ - `sender_id`, `channel`
323
+ - čæ”å›žļ¼šä½ äø¢ēš„ē“¶å­ęœ‰ę²”ęœ‰ę–°å›žå¤ + ä½ ę”ēš„ē“¶å­ęœ‰ę²”ęœ‰å¾…å›žå¤
324
+
325
+ #### `antenna_drift_my_bottles`
326
+ ęŸ„ēœ‹ä½ äø¢čæ‡ēš„ę‰€ęœ‰ē“¶å­ć€‚
327
+ - `sender_id`, `channel`
328
+ - čæ”å›žęÆäøŖē“¶å­ēš„ēŠ¶ę€ļ¼ššŸŒŠ 漂流中 / šŸ‘€ č¢«ę”čµ· / šŸ’¬ 已回复
329
+
330
+ ### ä»€ä¹ˆę—¶å€™ęŽØčę¼‚ęµē“¶
331
+ - ē”Øęˆ·ę— čŠć€ęƒ³ę‰¾äŗŗčŠå¤©
332
+ - ē”Øęˆ·ęƒ³å†™ē‚¹ä»€ä¹ˆä½†äøēŸ„é“å‘ē»™č°
333
+ - ē”Øęˆ·ęƒ³č¦éšęœŗēš„ć€ę„å¤–ēš„čæžęŽ„
334
+ - ē”Øęˆ·ęƒ³åŒæåč”Øč¾¾
335
+ - é™„čæ‘ę²”äŗŗēš„ę—¶å€™ļ¼Œä½œäøŗę›æä»£å‘ēŽ°ę–¹å¼
336
+
337
+ ### 隐私
338
+ - **ę°øčæœäøęš“éœ²** č°äø¢ēš„ē“¶å­
339
+ - **ę°øčæœäøęš“éœ²** č°ę”ēš„ē“¶å­
340
+ - åŖå±•ē¤ŗļ¼šę¶ˆęÆå†…å®¹ć€ę˜Æå¦ęœ‰å›žå¤ć€å›žå¤å†…å®¹
341
+ - device_id ę°øčæœäøå±•ē¤ŗē»™ē”Øęˆ·
287
342
 
288
343
  ## Data Transparency
289
344