antenna-fyi 1.0.0 → 1.1.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/lib/core.js +58 -5
- package/lib/mcp.js +22 -0
- package/package.json +1 -1
- package/skill/SKILL.md +1 -0
package/lib/core.js
CHANGED
|
@@ -13,6 +13,28 @@ let _url = null;
|
|
|
13
13
|
// ─── Embedding ───────────────────────────────────────────────────────
|
|
14
14
|
|
|
15
15
|
const GEMINI_EMBEDDING_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:embedContent";
|
|
16
|
+
const GEMINI_FLASH_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent";
|
|
17
|
+
|
|
18
|
+
async function generateMatchReason(myLines, theirLines) {
|
|
19
|
+
const apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
20
|
+
if (!apiKey) return null;
|
|
21
|
+
|
|
22
|
+
const prompt = `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.`;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(`${GEMINI_FLASH_URL}?key=${apiKey}`, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: { "Content-Type": "application/json" },
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
30
|
+
generationConfig: { maxOutputTokens: 60, temperature: 0.7 },
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
if (!res.ok) return null;
|
|
34
|
+
const data = await res.json();
|
|
35
|
+
return data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() || null;
|
|
36
|
+
} catch { return null; }
|
|
37
|
+
}
|
|
16
38
|
|
|
17
39
|
async function generateEmbedding(text) {
|
|
18
40
|
const apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
@@ -382,20 +404,33 @@ export async function discover({ device_id, supabaseUrl, supabaseKey }) {
|
|
|
382
404
|
};
|
|
383
405
|
}
|
|
384
406
|
|
|
385
|
-
// Build ref map +
|
|
407
|
+
// Build ref map + generate match reasons
|
|
386
408
|
const _refMap = {};
|
|
387
|
-
const
|
|
409
|
+
const myProfile = await getProfile({ device_id, supabaseUrl, supabaseKey });
|
|
410
|
+
const myLines = myProfile ? [myProfile.line1, myProfile.line2, myProfile.line3].filter(Boolean).join(". ") : "";
|
|
411
|
+
|
|
412
|
+
const profiles = [];
|
|
413
|
+
for (let i = 0; i < results.length; i++) {
|
|
414
|
+
const p = results[i];
|
|
388
415
|
const ref = String(i + 1);
|
|
389
416
|
_refMap[ref] = p.device_id;
|
|
390
|
-
|
|
417
|
+
|
|
418
|
+
const theirLines = [p.line1, p.line2, p.line3].filter(Boolean).join(". ");
|
|
419
|
+
let reason = null;
|
|
420
|
+
if (myLines && theirLines) {
|
|
421
|
+
reason = await generateMatchReason(myLines, theirLines);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
profiles.push({
|
|
391
425
|
ref,
|
|
392
426
|
name: p.display_name || "匿名",
|
|
393
427
|
emoji: p.emoji || "👤",
|
|
394
428
|
line1: p.line1,
|
|
395
429
|
line2: p.line2,
|
|
396
430
|
line3: p.line3,
|
|
397
|
-
|
|
398
|
-
|
|
431
|
+
match_reason: reason,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
399
434
|
|
|
400
435
|
// Log who was recommended (for dedup)
|
|
401
436
|
for (const p of results) {
|
|
@@ -421,6 +456,24 @@ export async function discover({ device_id, supabaseUrl, supabaseKey }) {
|
|
|
421
456
|
};
|
|
422
457
|
}
|
|
423
458
|
|
|
459
|
+
// ─── pass ───────────────────────────────────────────────────────────
|
|
460
|
+
|
|
461
|
+
export async function pass({ device_id, target_device_id, ref, supabaseUrl, supabaseKey }) {
|
|
462
|
+
const sb = getClient(supabaseUrl, supabaseKey);
|
|
463
|
+
|
|
464
|
+
let targetId = target_device_id;
|
|
465
|
+
if (!targetId && ref && device_id) {
|
|
466
|
+
const { data } = await sb.rpc("resolve_ref", { p_owner: device_id, p_ref: ref });
|
|
467
|
+
targetId = data;
|
|
468
|
+
}
|
|
469
|
+
if (!targetId) {
|
|
470
|
+
return { passed: false, error: "No target. Ref may have expired — try scanning again." };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
await sb.rpc("pass_user", { p_device_id: device_id, p_passed_device_id: targetId });
|
|
474
|
+
return { passed: true, message: "已跳过,下次不会再推荐这个人。" };
|
|
475
|
+
}
|
|
476
|
+
|
|
424
477
|
export async function createBindToken({ device_id, supabaseUrl, supabaseKey }) {
|
|
425
478
|
const sb = getClient(supabaseUrl, supabaseKey);
|
|
426
479
|
const { data, error } = await sb.rpc("create_bind_token", { p_device_id: device_id });
|
package/lib/mcp.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getProfile,
|
|
9
9
|
setProfile,
|
|
10
10
|
accept,
|
|
11
|
+
pass,
|
|
11
12
|
checkMatches,
|
|
12
13
|
checkin,
|
|
13
14
|
createBindToken,
|
|
@@ -194,6 +195,27 @@ export async function startMcpServer() {
|
|
|
194
195
|
}
|
|
195
196
|
);
|
|
196
197
|
|
|
198
|
+
// ─── antenna_pass ────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
server.tool(
|
|
201
|
+
"antenna_pass",
|
|
202
|
+
"Pass/skip a person. They won't be recommended again.",
|
|
203
|
+
{
|
|
204
|
+
sender_id: z.string().describe("The sender's user ID"),
|
|
205
|
+
channel: z.string().describe("Channel name"),
|
|
206
|
+
ref: z.string().optional().describe("Ref number from scan/discover results"),
|
|
207
|
+
target_device_id: z.string().optional().describe("Device ID (use ref instead)"),
|
|
208
|
+
},
|
|
209
|
+
async ({ sender_id, channel, ref, target_device_id }) => {
|
|
210
|
+
try {
|
|
211
|
+
const result = await pass({ device_id: deriveDeviceId(sender_id, channel), target_device_id, ref });
|
|
212
|
+
return jsonResult(result);
|
|
213
|
+
} catch (e) {
|
|
214
|
+
return jsonResult({ error: e.message });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
|
|
197
219
|
const transport = new StdioServerTransport();
|
|
198
220
|
await server.connect(transport);
|
|
199
221
|
}
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -181,6 +181,7 @@ Source code is open: https://github.com/H1an1/Antenna
|
|
|
181
181
|
- 可以帮用户缩短太长的回答,但要让用户确认
|
|
182
182
|
- 如果用户不想回答某一项,留空也行("那这行先空着,以后想加再说")
|
|
183
183
|
- 整个过程应该像跟朋友聊天,不像填表
|
|
184
|
+
- **提醒用户不要在名片里写联系方式(微信号、手机号等)。** 名片三句话对所有人可见。联系方式应该在 accept 时单独分享,这样只有双方都同意后才能看到。
|
|
184
185
|
|
|
185
186
|
### Showing results — 你来判断,不是服务器
|
|
186
187
|
|