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 +2 -2
- package/lib/core.js +16 -6
- package/lib/hermes-plugin/schemas.py +9 -5
- package/lib/hermes-plugin/tools.py +27 -13
- package/lib/mcp.js +23 -10
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
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.
|
|
62
|
-
"
|
|
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
|
|
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"
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
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
|
|
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
|
|