antenna-fyi 0.9.2 โ†’ 0.10.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/cli.js CHANGED
@@ -24,7 +24,7 @@ export function parseFlags(args) {
24
24
  }
25
25
 
26
26
  export async function handleScan(f) {
27
- if (!f.lat || !f.lng) return console.error("Usage: antenna scan --lat 39.99 --lng 116.48 [--radius 500] [--id telegram:123]");
27
+ if (!f.lat || !f.lng) return console.error("Usage: antenna scan --lat 39.99 --lng 116.48 [--radius 500] (max 1000) [--id telegram:123]");
28
28
  const result = await scan({
29
29
  lat: +f.lat,
30
30
  lng: +f.lng,
@@ -280,7 +280,7 @@ export function printHelp() {
280
280
  console.log(`๐Ÿ“ก Antenna โ€” nearby people discovery
281
281
 
282
282
  Usage:
283
- antenna scan --lat 39.99 --lng 116.48 [--radius 500] [--id telegram:123]
283
+ antenna scan --lat 39.99 --lng 116.48 [--radius 500] (max 1000) [--id telegram:123]
284
284
  antenna checkin --id telegram:123 --lat 39.99 --lng 116.48
285
285
  antenna profile --id telegram:123 [--name Yi --emoji ๐Ÿฆฆ --line1 '...']
286
286
  antenna accept --id telegram:123 --target telegram:789 [--contact 'WeChat: yi']
package/lib/core.js CHANGED
@@ -35,6 +35,7 @@ export function fuzzyCoord(lat, lng) {
35
35
 
36
36
  export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, supabaseKey }) {
37
37
  const sb = getClient(supabaseUrl, supabaseKey);
38
+ radius_m = Math.min(radius_m, 1000); // server caps at 1km too
38
39
 
39
40
  // If no lat/lng provided, read from profile (web GPS bind scenario)
40
41
  if ((lat == null || lng == null) && device_id) {
@@ -69,18 +70,27 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
69
70
  ? (data || []).filter((p) => p.device_id !== device_id)
70
71
  : data || [];
71
72
 
72
- return {
73
- count: others.length,
74
- radius_m,
75
- profiles: others.map((p) => ({
76
- device_id: p.device_id,
73
+ // Build ref mapping (1-indexed) so device_id is never exposed to the agent/user
74
+ const _refMap = {};
75
+ const profiles = others.map((p, i) => {
76
+ const ref = String(i + 1);
77
+ _refMap[ref] = p.device_id;
78
+ return {
79
+ ref,
77
80
  name: p.display_name || "ๅŒฟๅ",
78
81
  emoji: p.emoji || "๐Ÿ‘ค",
79
82
  line1: p.line1,
80
83
  line2: p.line2,
81
84
  line3: p.line3,
82
85
  distance_m: p.distance_m ?? p.dist_meters ?? null,
83
- })),
86
+ };
87
+ });
88
+
89
+ return {
90
+ count: others.length,
91
+ radius_m,
92
+ profiles,
93
+ _ref_map: _refMap,
84
94
  };
85
95
  }
86
96
 
@@ -13,7 +13,7 @@ SCAN_SCHEMA = {
13
13
  "lng": {"type": "number", "description": "Longitude (optional if location was shared via web)"},
14
14
  "radius_m": {
15
15
  "type": "number",
16
- "description": "Search radius in meters (default: 500)",
16
+ "description": "Search radius in meters (default 500, max 1000)",
17
17
  },
18
18
  "sender_id": {
19
19
  "type": "string",
@@ -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
- radius_m: z.number().optional().default(500).describe("Search radius in meters"),
40
+ radius_m: z.number().optional().default(500).describe("Search radius in meters (default 500, max 1000)"),
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.1",
4
4
  "description": "Antenna โ€” nearby people discovery. CLI + MCP server + OpenClaw skill & plugin, all in one package.",
5
5
  "type": "module",
6
6
  "bin": {
package/skill/SKILL.md CHANGED
@@ -46,7 +46,7 @@ Plugin ๅฎ‰่ฃ…ๅŽ๏ผŒagent ๅบ”่ฏฅ**ไธปๅŠจ**ๅผ€ๅง‹ๅผ•ๅฏผ๏ผŒไธ่ฆ็ญ‰็”จๆˆท้—ฎ"ๆ€Ž
46
46
  ### `antenna_scan`
47
47
  Scan for nearby people. Returns **raw profile cards** โ€” no scores, no pre-matching. **You are the matching engine.**
48
48
  - `lat`, `lng`: coordinates (from `LocationLat`/`LocationLon` context, or geocoded from user input)
49
- - `radius_m`: search radius (default 500m)
49
+ - `radius_m`: search radius in meters (default 500, max 1000)
50
50
  - `sender_id`: the user's id from message context
51
51
  - `channel`: the channel name (telegram, whatsapp, discord, etc.)
52
52