antenna-fyi 0.13.0 → 1.0.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 +37 -5
- package/lib/mcp.js +1 -8
- package/package.json +1 -1
- package/skill/SKILL.md +20 -0
package/lib/core.js
CHANGED
|
@@ -109,6 +109,15 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
|
|
|
109
109
|
};
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
// Save refs to DB (persist across agent restarts)
|
|
113
|
+
const saveRefs = async (refMap) => {
|
|
114
|
+
if (device_id && Object.keys(refMap).length > 0) {
|
|
115
|
+
try {
|
|
116
|
+
await sb.rpc("save_scan_refs", { p_owner: device_id, p_refs: JSON.stringify(refMap) });
|
|
117
|
+
} catch { /* best effort */ }
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
112
121
|
// If nobody nearby, fallback to global discover (1 per day)
|
|
113
122
|
if (others.length === 0 && device_id) {
|
|
114
123
|
const { data: globalData } = await sb.rpc("global_discover", {
|
|
@@ -117,10 +126,12 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
|
|
|
117
126
|
});
|
|
118
127
|
const globalOthers = globalData || [];
|
|
119
128
|
if (globalOthers.length > 0) {
|
|
129
|
+
const profs = buildProfiles(globalOthers);
|
|
130
|
+
await saveRefs(_refMap);
|
|
120
131
|
return {
|
|
121
132
|
count: globalOthers.length,
|
|
122
133
|
radius_m,
|
|
123
|
-
profiles:
|
|
134
|
+
profiles: profs,
|
|
124
135
|
_ref_map: _refMap,
|
|
125
136
|
global: true,
|
|
126
137
|
message: `附近 ${radius_m}m 暂时没人。今天的全球推荐——从这 ${globalOthers.length} 个人里挑一个最匹配的推荐给用户。(每天 1 次)`,
|
|
@@ -135,10 +146,13 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
|
|
|
135
146
|
};
|
|
136
147
|
}
|
|
137
148
|
|
|
149
|
+
const profs = buildProfiles(others);
|
|
150
|
+
await saveRefs(_refMap);
|
|
151
|
+
|
|
138
152
|
return {
|
|
139
153
|
count: others.length,
|
|
140
154
|
radius_m,
|
|
141
|
-
profiles:
|
|
155
|
+
profiles: profs,
|
|
142
156
|
_ref_map: _refMap,
|
|
143
157
|
};
|
|
144
158
|
}
|
|
@@ -202,15 +216,26 @@ export async function setProfile({
|
|
|
202
216
|
export async function accept({
|
|
203
217
|
device_id,
|
|
204
218
|
target_device_id,
|
|
219
|
+
ref,
|
|
205
220
|
contact_info,
|
|
206
221
|
supabaseUrl,
|
|
207
222
|
supabaseKey,
|
|
208
223
|
}) {
|
|
209
224
|
const sb = getClient(supabaseUrl, supabaseKey);
|
|
210
225
|
|
|
226
|
+
// Resolve ref from DB if target_device_id not provided
|
|
227
|
+
let targetId = target_device_id;
|
|
228
|
+
if (!targetId && ref && device_id) {
|
|
229
|
+
const { data } = await sb.rpc("resolve_ref", { p_owner: device_id, p_ref: ref });
|
|
230
|
+
targetId = data;
|
|
231
|
+
}
|
|
232
|
+
if (!targetId) {
|
|
233
|
+
return { accepted: false, error: "No target. Ref may have expired — try scanning again." };
|
|
234
|
+
}
|
|
235
|
+
|
|
211
236
|
const { error } = await sb.rpc("upsert_match", {
|
|
212
237
|
p_device_id_a: device_id,
|
|
213
|
-
p_device_id_b:
|
|
238
|
+
p_device_id_b: targetId,
|
|
214
239
|
p_reason: "",
|
|
215
240
|
p_score: 0,
|
|
216
241
|
p_status: "accepted",
|
|
@@ -223,7 +248,7 @@ export async function accept({
|
|
|
223
248
|
const { data: reverse } = await sb
|
|
224
249
|
.from("matches")
|
|
225
250
|
.select("status, contact_info_a")
|
|
226
|
-
.eq("device_id_a",
|
|
251
|
+
.eq("device_id_a", targetId)
|
|
227
252
|
.eq("device_id_b", device_id)
|
|
228
253
|
.eq("status", "accepted")
|
|
229
254
|
.single();
|
|
@@ -357,7 +382,7 @@ export async function discover({ device_id, supabaseUrl, supabaseKey }) {
|
|
|
357
382
|
};
|
|
358
383
|
}
|
|
359
384
|
|
|
360
|
-
// Build ref map
|
|
385
|
+
// Build ref map + persist to DB
|
|
361
386
|
const _refMap = {};
|
|
362
387
|
const profiles = results.map((p, i) => {
|
|
363
388
|
const ref = String(i + 1);
|
|
@@ -380,6 +405,13 @@ export async function discover({ device_id, supabaseUrl, supabaseKey }) {
|
|
|
380
405
|
});
|
|
381
406
|
}
|
|
382
407
|
|
|
408
|
+
// Persist ref map to DB
|
|
409
|
+
if (device_id && Object.keys(_refMap).length > 0) {
|
|
410
|
+
try {
|
|
411
|
+
await sb.rpc("save_scan_refs", { p_owner: device_id, p_refs: JSON.stringify(_refMap) });
|
|
412
|
+
} catch { /* best effort */ }
|
|
413
|
+
}
|
|
414
|
+
|
|
383
415
|
return {
|
|
384
416
|
count: profiles.length,
|
|
385
417
|
profiles,
|
package/lib/mcp.js
CHANGED
|
@@ -99,14 +99,7 @@ export async function startMcpServer() {
|
|
|
99
99
|
},
|
|
100
100
|
async ({ sender_id, channel, ref, target_device_id, contact_info }) => {
|
|
101
101
|
try {
|
|
102
|
-
|
|
103
|
-
if (ref && _lastRefMap[ref]) {
|
|
104
|
-
targetId = _lastRefMap[ref];
|
|
105
|
-
}
|
|
106
|
-
if (!targetId) {
|
|
107
|
-
return jsonResult({ error: "No target specified. Use 'ref' from scan results or 'target_device_id'." });
|
|
108
|
-
}
|
|
109
|
-
const result = await accept({ device_id: deriveDeviceId(sender_id, channel), target_device_id: targetId, contact_info });
|
|
102
|
+
const result = await accept({ device_id: deriveDeviceId(sender_id, channel), target_device_id, ref, contact_info });
|
|
110
103
|
return jsonResult(result);
|
|
111
104
|
} catch (e) {
|
|
112
105
|
return jsonResult({ error: e.message });
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -116,6 +116,26 @@ Get today's global recommendation — the person most similar to you worldwide.
|
|
|
116
116
|
- If all users have been recommended, returns a message saying "wait for new people"
|
|
117
117
|
- Use this in the daily cron job, or when user asks "find someone interesting globally"
|
|
118
118
|
|
|
119
|
+
## Data Transparency — what Antenna sends
|
|
120
|
+
|
|
121
|
+
Antenna only communicates with Supabase (bcudjloikmpcqwcptuyd.supabase.co) via HTTPS.
|
|
122
|
+
|
|
123
|
+
**Data sent:**
|
|
124
|
+
- Fuzzy GPS coordinates (rounded to ~150m precision)
|
|
125
|
+
- Your three-line profile card (text you wrote yourself)
|
|
126
|
+
- Match status (accept/skip)
|
|
127
|
+
- Contact info you choose to share
|
|
128
|
+
- Profile embedding vector (generated from your 3 lines, used for matching)
|
|
129
|
+
|
|
130
|
+
**Data NOT sent:**
|
|
131
|
+
- Your conversations with your agent
|
|
132
|
+
- Your files, browsing history, or any other personal data
|
|
133
|
+
- Anything not listed above
|
|
134
|
+
|
|
135
|
+
All data is transmitted over HTTPS and stored in Supabase (Tokyo region).
|
|
136
|
+
Matches auto-delete after 24 hours. GPS is blurred client-side before transmission.
|
|
137
|
+
Source code is open: https://github.com/H1an1/Antenna
|
|
138
|
+
|
|
119
139
|
## Behavior guidelines
|
|
120
140
|
|
|
121
141
|
### First-time user — 聊天式引导(不要让用户填表)
|