antenna-fyi 0.9.2 → 0.10.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 CHANGED
@@ -69,18 +69,27 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
69
69
  ? (data || []).filter((p) => p.device_id !== device_id)
70
70
  : data || [];
71
71
 
72
- return {
73
- count: others.length,
74
- radius_m,
75
- profiles: others.map((p) => ({
76
- device_id: p.device_id,
72
+ // Build ref mapping (1-indexed) so device_id is never exposed to the agent/user
73
+ const _refMap = {};
74
+ const profiles = others.map((p, i) => {
75
+ const ref = String(i + 1);
76
+ _refMap[ref] = p.device_id;
77
+ return {
78
+ ref,
77
79
  name: p.display_name || "匿名",
78
80
  emoji: p.emoji || "👤",
79
81
  line1: p.line1,
80
82
  line2: p.line2,
81
83
  line3: p.line3,
82
84
  distance_m: p.distance_m ?? p.dist_meters ?? null,
83
- })),
85
+ };
86
+ });
87
+
88
+ return {
89
+ count: others.length,
90
+ radius_m,
91
+ profiles,
92
+ _ref_map: _refMap,
84
93
  };
85
94
  }
86
95
 
@@ -58,24 +58,28 @@ PROFILE_SCHEMA = {
58
58
  ACCEPT_SCHEMA = {
59
59
  "name": "antenna_accept",
60
60
  "description": (
61
- "Accept a match. Optionally share contact info. "
62
- "If both sides accept, they can exchange contact info."
61
+ "Accept a match. Use 'ref' from scan results (e.g. '1', '2') or target_device_id. "
62
+ "Optionally share contact info."
63
63
  ),
64
64
  "parameters": {
65
65
  "type": "object",
66
66
  "properties": {
67
67
  "sender_id": {"type": "string"},
68
68
  "channel": {"type": "string"},
69
+ "ref": {
70
+ "type": "string",
71
+ "description": "Ref number from scan results (e.g. '1')",
72
+ },
69
73
  "target_device_id": {
70
74
  "type": "string",
71
- "description": "Device ID of the person to accept",
75
+ "description": "Device ID (use ref instead when possible)",
72
76
  },
73
77
  "contact_info": {
74
78
  "type": "string",
75
79
  "description": "Contact info to share (e.g. 'WeChat: yi')",
76
80
  },
77
81
  },
78
- "required": ["sender_id", "channel", "target_device_id"],
82
+ "required": ["sender_id", "channel"],
79
83
  },
80
84
  }
81
85
 
@@ -28,6 +28,7 @@ _client = None
28
28
  _client_url = None
29
29
  _last_scan: dict[str, float] = {}
30
30
  SCAN_DEBOUNCE_S = 30
31
+ _last_ref_map: dict[str, str] = {} # ref → device_id from last scan
31
32
 
32
33
 
33
34
  def _get_url():
@@ -106,21 +107,27 @@ def handle_scan(params: dict) -> str:
106
107
  if not others:
107
108
  return _ok({"nearby": [], "message": f"在 {radius}m 范围内没有发现其他人。"})
108
109
 
110
+ # Build ref mapping — never expose device_id to agent/user
111
+ global _last_ref_map
112
+ _last_ref_map = {}
113
+ profiles = []
114
+ for i, p in enumerate(others):
115
+ ref = str(i + 1)
116
+ _last_ref_map[ref] = p.get("device_id")
117
+ profiles.append({
118
+ "ref": ref,
119
+ "emoji": p.get("emoji") or "👤",
120
+ "name": p.get("display_name") or "匿名",
121
+ "line1": p.get("line1"),
122
+ "line2": p.get("line2"),
123
+ "line3": p.get("line3"),
124
+ })
125
+
109
126
  return _ok({
110
- "nearby": [
111
- {
112
- "device_id": p.get("device_id"),
113
- "emoji": p.get("emoji") or "👤",
114
- "name": p.get("display_name") or "匿名",
115
- "line1": p.get("line1"),
116
- "line2": p.get("line2"),
117
- "line3": p.get("line3"),
118
- }
119
- for p in others
120
- ],
127
+ "nearby": profiles,
121
128
  "total": len(others),
122
129
  "radius_m": radius,
123
- "instruction": "根据你对用户的了解,判断哪些人值得推荐,为每个推荐写一句个性化的匹配理由。",
130
+ "instruction": "根据你对用户的了解,判断哪些人值得推荐,为每个推荐写一句个性化的匹配理由。使用 ref 编号(如 '1', '2')来引用人员,不要显示 device_id。",
124
131
  })
125
132
 
126
133
 
@@ -153,7 +160,14 @@ def handle_profile(params: dict) -> str:
153
160
  def handle_accept(params: dict) -> str:
154
161
  sb = _sb()
155
162
  did = _device_id(params["sender_id"], params["channel"])
156
- target = params["target_device_id"]
163
+
164
+ # Resolve ref to device_id
165
+ ref = params.get("ref")
166
+ target = params.get("target_device_id")
167
+ if ref and ref in _last_ref_map:
168
+ target = _last_ref_map[ref]
169
+ if not target:
170
+ return _ok({"error": "No target. Use 'ref' from scan results or 'target_device_id'."})
157
171
 
158
172
  sb.rpc("upsert_match", {
159
173
  "p_device_id_a": did,
package/lib/mcp.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  export async function startMcpServer() {
18
18
  const server = new McpServer({
19
19
  name: "Antenna",
20
- version: "0.5.0",
20
+ version: "0.9.2",
21
21
  description:
22
22
  "Nearby people discovery — scan for people around you, set up your profile card, accept matches, and check match status.",
23
23
  });
@@ -26,22 +26,27 @@ export async function startMcpServer() {
26
26
  return { content: [{ type: "text", text: JSON.stringify(obj) }] };
27
27
  }
28
28
 
29
+ // Store last scan ref map for resolving refs in accept
30
+ let _lastRefMap = {};
31
+
29
32
  // ─── antenna_scan ──────────────────────────────────────────────────
30
33
 
31
34
  server.tool(
32
35
  "antenna_scan",
33
- "Scan for nearby people. If lat/lng are omitted, uses the location from the user's web GPS binding.",
36
+ "Scan for nearby people. Returns profiles with 'ref' numbers (1, 2, 3...) for privacy. Use these ref numbers when calling antenna_accept.",
34
37
  {
35
38
  lat: z.number().optional().describe("Latitude (optional if location was shared via web)"),
36
39
  lng: z.number().optional().describe("Longitude (optional if location was shared via web)"),
37
40
  radius_m: z.number().optional().default(500).describe("Search radius in meters"),
38
41
  sender_id: z.string().describe("The sender's user ID"),
39
- channel: z.string().describe("Channel name (telegram, whatsapp, discord, etc.)"),
42
+ channel: z.string().describe("Channel name"),
40
43
  },
41
44
  async ({ lat, lng, radius_m, sender_id, channel }) => {
42
45
  try {
43
46
  const result = await scan({ lat, lng, radius_m, device_id: deriveDeviceId(sender_id, channel) });
44
- return jsonResult(result);
47
+ _lastRefMap = result._ref_map || {};
48
+ const { _ref_map, ...clean } = result;
49
+ return jsonResult(clean);
45
50
  } catch (e) {
46
51
  return jsonResult({ error: e.message });
47
52
  }
@@ -83,16 +88,24 @@ export async function startMcpServer() {
83
88
 
84
89
  server.tool(
85
90
  "antenna_accept",
86
- "Accept a match with another person.",
91
+ "Accept a match. Use 'ref' from scan results (e.g. '1', '2') OR target_device_id directly.",
87
92
  {
88
93
  sender_id: z.string().describe("The sender's user ID"),
89
94
  channel: z.string().describe("Channel name"),
90
- target_device_id: z.string().describe("Device ID of the person to accept"),
95
+ ref: z.string().optional().describe("Ref number from scan results (e.g. '1')"),
96
+ target_device_id: z.string().optional().describe("Device ID (use ref instead when possible)"),
91
97
  contact_info: z.string().optional().describe("Contact info to share"),
92
98
  },
93
- async ({ sender_id, channel, target_device_id, contact_info }) => {
99
+ async ({ sender_id, channel, ref, target_device_id, contact_info }) => {
94
100
  try {
95
- const result = await accept({ device_id: deriveDeviceId(sender_id, channel), target_device_id, contact_info });
101
+ let targetId = target_device_id;
102
+ if (ref && _lastRefMap[ref]) {
103
+ targetId = _lastRefMap[ref];
104
+ }
105
+ if (!targetId) {
106
+ return jsonResult({ error: "No target specified. Use 'ref' from scan results or 'target_device_id'." });
107
+ }
108
+ const result = await accept({ device_id: deriveDeviceId(sender_id, channel), target_device_id: targetId, contact_info });
96
109
  return jsonResult(result);
97
110
  } catch (e) {
98
111
  return jsonResult({ error: e.message });
@@ -144,7 +157,7 @@ export async function startMcpServer() {
144
157
  }
145
158
  );
146
159
 
147
- // ─── antenna_bind ────────────────────────────────────────────
160
+ // ─── antenna_bind ──────────────────────────────────────────────────
148
161
 
149
162
  server.tool(
150
163
  "antenna_bind",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-fyi",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "description": "Antenna — nearby people discovery. CLI + MCP server + OpenClaw skill & plugin, all in one package.",
5
5
  "type": "module",
6
6
  "bin": {