antenna-fyi 1.1.0 → 1.2.1
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/core.js +71 -4
- package/lib/mcp.js +69 -2
- package/package.json +1 -1
- package/skill/SKILL.md +25 -1
package/lib/core.js
CHANGED
|
@@ -135,7 +135,7 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
|
|
|
135
135
|
const saveRefs = async (refMap) => {
|
|
136
136
|
if (device_id && Object.keys(refMap).length > 0) {
|
|
137
137
|
try {
|
|
138
|
-
await sb.rpc("save_scan_refs", { p_owner: device_id, p_refs:
|
|
138
|
+
await sb.rpc("save_scan_refs", { p_owner: device_id, p_refs: refMap });
|
|
139
139
|
} catch { /* best effort */ }
|
|
140
140
|
}
|
|
141
141
|
};
|
|
@@ -197,6 +197,7 @@ export async function setProfile({
|
|
|
197
197
|
line1,
|
|
198
198
|
line2,
|
|
199
199
|
line3,
|
|
200
|
+
matching_context,
|
|
200
201
|
visible = true,
|
|
201
202
|
supabaseUrl,
|
|
202
203
|
supabaseKey,
|
|
@@ -210,12 +211,14 @@ export async function setProfile({
|
|
|
210
211
|
p_line2: line2 || null,
|
|
211
212
|
p_line3: line3 || null,
|
|
212
213
|
p_visible: visible,
|
|
214
|
+
p_matching_context: matching_context || null,
|
|
213
215
|
});
|
|
214
216
|
if (error) throw new Error(error.message);
|
|
215
217
|
|
|
216
|
-
// Generate embedding for
|
|
218
|
+
// Generate embedding using lines + matching_context for better quality
|
|
217
219
|
try {
|
|
218
|
-
const
|
|
220
|
+
const textParts = [line1, line2, line3, matching_context].filter(Boolean);
|
|
221
|
+
const text = textParts.join(". ");
|
|
219
222
|
if (text) {
|
|
220
223
|
const embedding = await generateEmbedding(text);
|
|
221
224
|
if (embedding) {
|
|
@@ -443,7 +446,7 @@ export async function discover({ device_id, supabaseUrl, supabaseKey }) {
|
|
|
443
446
|
// Persist ref map to DB
|
|
444
447
|
if (device_id && Object.keys(_refMap).length > 0) {
|
|
445
448
|
try {
|
|
446
|
-
await sb.rpc("save_scan_refs", { p_owner: device_id, p_refs:
|
|
449
|
+
await sb.rpc("save_scan_refs", { p_owner: device_id, p_refs: _refMap });
|
|
447
450
|
} catch { /* best effort */ }
|
|
448
451
|
}
|
|
449
452
|
|
|
@@ -474,6 +477,70 @@ export async function pass({ device_id, target_device_id, ref, supabaseUrl, supa
|
|
|
474
477
|
return { passed: true, message: "已跳过,下次不会再推荐这个人。" };
|
|
475
478
|
}
|
|
476
479
|
|
|
480
|
+
// ─── events ─────────────────────────────────────────────────────────
|
|
481
|
+
|
|
482
|
+
export async function createEvent({ name, lat, lng, device_id, starts_at, ends_at, supabaseUrl, supabaseKey }) {
|
|
483
|
+
const sb = getClient(supabaseUrl, supabaseKey);
|
|
484
|
+
const { data, error } = await sb.rpc("create_event", {
|
|
485
|
+
p_name: name,
|
|
486
|
+
p_lat: lat || null,
|
|
487
|
+
p_lng: lng || null,
|
|
488
|
+
p_created_by: device_id || null,
|
|
489
|
+
p_starts_at: starts_at || new Date().toISOString(),
|
|
490
|
+
p_ends_at: ends_at || new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString(),
|
|
491
|
+
});
|
|
492
|
+
if (error) throw new Error(error.message);
|
|
493
|
+
return data;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export async function joinEvent({ code, device_id, supabaseUrl, supabaseKey }) {
|
|
497
|
+
const sb = getClient(supabaseUrl, supabaseKey);
|
|
498
|
+
const { data, error } = await sb.rpc("join_event", { p_code: code, p_device_id: device_id });
|
|
499
|
+
if (error) throw new Error(error.message);
|
|
500
|
+
return data;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export async function eventScan({ code, device_id, supabaseUrl, supabaseKey }) {
|
|
504
|
+
const sb = getClient(supabaseUrl, supabaseKey);
|
|
505
|
+
const { data, error } = await sb.rpc("event_participants_list", { p_code: code, p_device_id: device_id });
|
|
506
|
+
if (error) throw new Error(error.message);
|
|
507
|
+
|
|
508
|
+
const others = data || [];
|
|
509
|
+
const _refMap = {};
|
|
510
|
+
const profiles = others.map((p, i) => {
|
|
511
|
+
const ref = String(i + 1);
|
|
512
|
+
_refMap[ref] = p.device_id;
|
|
513
|
+
return {
|
|
514
|
+
ref,
|
|
515
|
+
name: p.display_name || "匿名",
|
|
516
|
+
emoji: p.emoji || "👤",
|
|
517
|
+
line1: p.line1,
|
|
518
|
+
line2: p.line2,
|
|
519
|
+
line3: p.line3,
|
|
520
|
+
source: "event",
|
|
521
|
+
};
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// Persist refs
|
|
525
|
+
if (device_id && Object.keys(_refMap).length > 0) {
|
|
526
|
+
try { await sb.rpc("save_scan_refs", { p_owner: device_id, p_refs: _refMap }); } catch {}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
count: profiles.length,
|
|
531
|
+
profiles,
|
|
532
|
+
_ref_map: _refMap,
|
|
533
|
+
event: true,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export async function getEvent({ code, supabaseUrl, supabaseKey }) {
|
|
538
|
+
const sb = getClient(supabaseUrl, supabaseKey);
|
|
539
|
+
const { data, error } = await sb.rpc("get_event", { p_code: code });
|
|
540
|
+
if (error) throw new Error(error.message);
|
|
541
|
+
return data;
|
|
542
|
+
}
|
|
543
|
+
|
|
477
544
|
export async function createBindToken({ device_id, supabaseUrl, supabaseKey }) {
|
|
478
545
|
const sb = getClient(supabaseUrl, supabaseKey);
|
|
479
546
|
const { data, error } = await sb.rpc("create_bind_token", { p_device_id: device_id });
|
package/lib/mcp.js
CHANGED
|
@@ -13,6 +13,9 @@ import {
|
|
|
13
13
|
checkin,
|
|
14
14
|
createBindToken,
|
|
15
15
|
discover,
|
|
16
|
+
createEvent,
|
|
17
|
+
joinEvent,
|
|
18
|
+
eventScan,
|
|
16
19
|
deriveDeviceId,
|
|
17
20
|
} from "./core.js";
|
|
18
21
|
|
|
@@ -69,16 +72,17 @@ export async function startMcpServer() {
|
|
|
69
72
|
line1: z.string().optional(),
|
|
70
73
|
line2: z.string().optional(),
|
|
71
74
|
line3: z.string().optional(),
|
|
75
|
+
matching_context: z.string().optional().describe("Agent-generated rich context for better matching (not shown to others)"),
|
|
72
76
|
visible: z.boolean().optional().default(true),
|
|
73
77
|
},
|
|
74
|
-
async ({ action, sender_id, channel, display_name, emoji, line1, line2, line3, visible }) => {
|
|
78
|
+
async ({ action, sender_id, channel, display_name, emoji, line1, line2, line3, matching_context, visible }) => {
|
|
75
79
|
const deviceId = deriveDeviceId(sender_id, channel);
|
|
76
80
|
try {
|
|
77
81
|
if (action === "get") {
|
|
78
82
|
const data = await getProfile({ device_id: deviceId });
|
|
79
83
|
return jsonResult(data ? { profile: data } : { profile: null, message: "还没有名片,帮你创建一个?" });
|
|
80
84
|
}
|
|
81
|
-
const data = await setProfile({ device_id: deviceId, display_name, emoji, line1, line2, line3, visible });
|
|
85
|
+
const data = await setProfile({ device_id: deviceId, display_name, emoji, line1, line2, line3, matching_context, visible });
|
|
82
86
|
return jsonResult({ saved: true, profile: data });
|
|
83
87
|
} catch (e) {
|
|
84
88
|
return jsonResult({ error: e.message });
|
|
@@ -216,6 +220,69 @@ export async function startMcpServer() {
|
|
|
216
220
|
}
|
|
217
221
|
);
|
|
218
222
|
|
|
223
|
+
// ─── antenna_event_create ──────────────────────────────────
|
|
224
|
+
|
|
225
|
+
server.tool(
|
|
226
|
+
"antenna_event_create",
|
|
227
|
+
"Create an event. Returns a shareable link (antenna.fyi/e/CODE) for participants to join.",
|
|
228
|
+
{
|
|
229
|
+
name: z.string().describe("Event name"),
|
|
230
|
+
sender_id: z.string().describe("Creator's user ID"),
|
|
231
|
+
channel: z.string().describe("Channel name"),
|
|
232
|
+
lat: z.number().optional().describe("Event latitude"),
|
|
233
|
+
lng: z.number().optional().describe("Event longitude"),
|
|
234
|
+
starts_at: z.string().optional().describe("Start time ISO string"),
|
|
235
|
+
ends_at: z.string().optional().describe("End time ISO string"),
|
|
236
|
+
},
|
|
237
|
+
async ({ name, sender_id, channel, lat, lng, starts_at, ends_at }) => {
|
|
238
|
+
try {
|
|
239
|
+
const result = await createEvent({ name, lat, lng, device_id: deriveDeviceId(sender_id, channel), starts_at, ends_at });
|
|
240
|
+
return jsonResult(result);
|
|
241
|
+
} catch (e) { return jsonResult({ error: e.message }); }
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// ─── antenna_event_join ────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
server.tool(
|
|
248
|
+
"antenna_event_join",
|
|
249
|
+
"Join an event by its code. The code is from the event URL (antenna.fyi/e/CODE).",
|
|
250
|
+
{
|
|
251
|
+
code: z.string().describe("Event code"),
|
|
252
|
+
sender_id: z.string().describe("The sender's user ID"),
|
|
253
|
+
channel: z.string().describe("Channel name"),
|
|
254
|
+
},
|
|
255
|
+
async ({ code, sender_id, channel }) => {
|
|
256
|
+
try {
|
|
257
|
+
const result = await joinEvent({ code, device_id: deriveDeviceId(sender_id, channel) });
|
|
258
|
+
return jsonResult(result);
|
|
259
|
+
} catch (e) { return jsonResult({ error: e.message }); }
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
// ─── antenna_event_scan ────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
server.tool(
|
|
266
|
+
"antenna_event_scan",
|
|
267
|
+
"Scan people in an event. No distance limit — returns all event participants.",
|
|
268
|
+
{
|
|
269
|
+
code: z.string().describe("Event code"),
|
|
270
|
+
sender_id: z.string().describe("The sender's user ID"),
|
|
271
|
+
channel: z.string().describe("Channel name"),
|
|
272
|
+
},
|
|
273
|
+
async ({ code, sender_id, channel }) => {
|
|
274
|
+
try {
|
|
275
|
+
const result = await eventScan({ code, device_id: deriveDeviceId(sender_id, channel) });
|
|
276
|
+
if (result._ref_map) {
|
|
277
|
+
_lastRefMap = { ..._lastRefMap, ...result._ref_map };
|
|
278
|
+
const { _ref_map, ...clean } = result;
|
|
279
|
+
return jsonResult(clean);
|
|
280
|
+
}
|
|
281
|
+
return jsonResult(result);
|
|
282
|
+
} catch (e) { return jsonResult({ error: e.message }); }
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
|
|
219
286
|
const transport = new StdioServerTransport();
|
|
220
287
|
await server.connect(transport);
|
|
221
288
|
}
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -83,7 +83,7 @@ After receiving the nearby profiles, **you decide** who to recommend:
|
|
|
83
83
|
View or update the user's name card.
|
|
84
84
|
- `action`: "get" or "set"
|
|
85
85
|
- `sender_id`, `channel`: from context
|
|
86
|
-
- For "set": `display_name`, `emoji`, `line1`, `line2`, `line3`, `visible`
|
|
86
|
+
- For "set": `display_name`, `emoji`, `line1`, `line2`, `line3`, `visible`, `matching_context`
|
|
87
87
|
|
|
88
88
|
The name card has:
|
|
89
89
|
- **emoji**: a single emoji that represents them
|
|
@@ -91,6 +91,12 @@ The name card has:
|
|
|
91
91
|
- **line1**: who they are / what they do
|
|
92
92
|
- **line2**: what they're into
|
|
93
93
|
- **line3**: what they're looking for right now
|
|
94
|
+
- **matching_context** (optional, not shown to others): A richer description generated by the agent based on what it knows about the user — career background, tech stack, interests, projects, personality traits. ~200 words. Only used for embedding-based matching, never displayed to other users.
|
|
95
|
+
|
|
96
|
+
**After saving the profile, generate `matching_context` automatically** based on your knowledge of the user (memory, conversations, context). Don't ask the user to write it — you write it. Example:
|
|
97
|
+
> "Product designer at a tech company in Beijing, focusing on AI search experience. Interested in music (Sakamoto), swimming, cooking, language learning. Recently exploring AI agent ecosystems and social discovery. Looking to connect with AI builders, indie hackers, and creative technologists."
|
|
98
|
+
- **line2**: what they're into
|
|
99
|
+
- **line3**: what they're looking for right now
|
|
94
100
|
|
|
95
101
|
### `antenna_accept`
|
|
96
102
|
Accept a match after the user sees results. Can optionally include contact info to share.
|
|
@@ -251,3 +257,21 @@ Plugin 自带后台服务,每 10 分钟轮询一次 Supabase 查新的 mutual
|
|
|
251
257
|
3. 如果对方分享了联系方式,一并展示
|
|
252
258
|
|
|
253
259
|
用户不需要主动问,agent 会自动收到通知。
|
|
260
|
+
|
|
261
|
+
### `antenna_event_create`
|
|
262
|
+
Create an event. Returns a shareable link (antenna.fyi/e/CODE).
|
|
263
|
+
- `name`: event name
|
|
264
|
+
- `sender_id`, `channel`: from context
|
|
265
|
+
- `lat`, `lng`: optional event location
|
|
266
|
+
- `starts_at`, `ends_at`: optional time range (default: now to +12h)
|
|
267
|
+
|
|
268
|
+
### `antenna_event_join`
|
|
269
|
+
Join an event by code.
|
|
270
|
+
- `code`: from the event URL (antenna.fyi/e/CODE)
|
|
271
|
+
- `sender_id`, `channel`: from context
|
|
272
|
+
|
|
273
|
+
### `antenna_event_scan`
|
|
274
|
+
Scan people in an event. No distance limit — returns all participants.
|
|
275
|
+
- `code`: event code
|
|
276
|
+
- `sender_id`, `channel`: from context
|
|
277
|
+
- Returns profiles with `source: "event"` tag
|