antenna-fyi 1.3.0 → 1.3.2
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/README.md +8 -8
- package/lib/cli.js +38 -38
- package/lib/core.js +1 -1
- package/lib/hermes-plugin/__init__.py +1 -1
- package/lib/hermes-plugin/schemas.py +37 -25
- package/lib/hermes-plugin/tools.py +51 -42
- package/package.json +1 -1
- package/skill/SKILL.md +11 -6
- package/skill/EVENTS.md +0 -112
package/README.md
CHANGED
|
@@ -12,26 +12,26 @@ npm install -g antenna-fyi
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
# Create your profile card
|
|
15
|
-
antenna setup --id
|
|
15
|
+
antenna setup --id <platform>:<user_id>
|
|
16
16
|
|
|
17
17
|
# Scan for nearby people
|
|
18
|
-
antenna scan --lat 39.99 --lng 116.48 --radius 500 --id
|
|
18
|
+
antenna scan --lat 39.99 --lng 116.48 --radius 500 --id <platform>:<user_id>
|
|
19
19
|
|
|
20
20
|
# Check in at a location
|
|
21
|
-
antenna checkin --id
|
|
21
|
+
antenna checkin --id <platform>:<user_id> --lat 39.99 --lng 116.48
|
|
22
22
|
|
|
23
23
|
# View/edit your profile
|
|
24
|
-
antenna profile --id
|
|
25
|
-
antenna profile --id
|
|
24
|
+
antenna profile --id <platform>:<user_id>
|
|
25
|
+
antenna profile --id <platform>:<user_id> --name Yi --emoji 🦦 --line1 'Product Designer'
|
|
26
26
|
|
|
27
27
|
# Accept a match
|
|
28
|
-
antenna accept --id
|
|
28
|
+
antenna accept --id <platform>:<user_id> --target <ref_or_id> --contact 'WeChat: yi'
|
|
29
29
|
|
|
30
30
|
# Check match status
|
|
31
|
-
antenna matches --id
|
|
31
|
+
antenna matches --id <platform>:<user_id>
|
|
32
32
|
|
|
33
33
|
# Show status
|
|
34
|
-
antenna status --id
|
|
34
|
+
antenna status --id <platform>:<user_id>
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
## MCP Server
|
package/lib/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ export function parseFlags(args) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export async function handleScan(f) {
|
|
34
|
-
if (!f.lat && !f.lng && !f.id) return console.error("Usage: antenna scan --lat 39.99 --lng 116.48 [--radius 500] (max 1000) [--id
|
|
34
|
+
if (!f.lat && !f.lng && !f.id) return console.error("Usage: antenna scan --lat 39.99 --lng 116.48 [--radius 500] (max 1000) [--id <platform>:<user_id>]\n Or just: antenna scan --id <platform>:<user_id> (uses saved location from GPS bind)");
|
|
35
35
|
const result = await scan({
|
|
36
36
|
lat: f.lat ? +f.lat : undefined,
|
|
37
37
|
lng: f.lng ? +f.lng : undefined,
|
|
@@ -64,7 +64,7 @@ export async function handleScan(f) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export async function handleProfile(f) {
|
|
67
|
-
if (!f.id) return console.error("Usage: antenna profile --id
|
|
67
|
+
if (!f.id) return console.error("Usage: antenna profile --id <platform>:<user_id> [--name Yi --emoji 🦦 --line1 '...' --line2 '...' --line3 '...']");
|
|
68
68
|
if (f.name || f.line1 || f.line2 || f.line3 || f.visible !== undefined || f.hide !== undefined) {
|
|
69
69
|
const visible = f.hide ? false : (f.visible !== undefined ? f.visible === 'true' || f.visible === true : undefined);
|
|
70
70
|
const data = await setProfile({
|
|
@@ -89,7 +89,7 @@ export async function handleProfile(f) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
export async function handleAccept(f) {
|
|
92
|
-
if (!f.id || (!f.target && !f.ref)) return console.error("Usage: antenna accept --id
|
|
92
|
+
if (!f.id || (!f.target && !f.ref)) return console.error("Usage: antenna accept --id <platform>:<user_id> --ref 1 [--contact 'WeChat: yi']\n antenna accept --id <platform>:<user_id> --target <ref_or_device_id> [--contact 'WeChat: yi']");
|
|
93
93
|
const result = await accept({
|
|
94
94
|
device_id: f.id,
|
|
95
95
|
target_device_id: f.target || null,
|
|
@@ -101,7 +101,7 @@ export async function handleAccept(f) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
export async function handleCheckin(f) {
|
|
104
|
-
if (!f.id || !f.lat || !f.lng) return console.error("Usage: antenna checkin --id
|
|
104
|
+
if (!f.id || !f.lat || !f.lng) return console.error("Usage: antenna checkin --id <platform>:<user_id> --lat 39.99 --lng 116.48 [--place '三里屯']");
|
|
105
105
|
const result = await checkin({
|
|
106
106
|
lat: +f.lat,
|
|
107
107
|
lng: +f.lng,
|
|
@@ -111,7 +111,7 @@ export async function handleCheckin(f) {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
export async function handleMatches(f) {
|
|
114
|
-
if (!f.id) return console.error("Usage: antenna matches --id
|
|
114
|
+
if (!f.id) return console.error("Usage: antenna matches --id <platform>:<user_id>");
|
|
115
115
|
const result = await checkMatches({ device_id: f.id });
|
|
116
116
|
if (!result.mutual_matches.length && !result.incoming_accepts.length) {
|
|
117
117
|
return console.log(result.message);
|
|
@@ -131,7 +131,7 @@ export async function handleMatches(f) {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
export async function handleDiscover(f) {
|
|
134
|
-
if (!f.id) return console.error("Usage: antenna discover --id
|
|
134
|
+
if (!f.id) return console.error("Usage: antenna discover --id <platform>:<user_id>");
|
|
135
135
|
const result = await discover({ device_id: f.id });
|
|
136
136
|
if (result.count === 0) return console.log(result.message || "🌍 No global recommendation available right now.");
|
|
137
137
|
console.log(`🌍 Global discover:\n`);
|
|
@@ -162,7 +162,7 @@ export async function handleEvent(f) {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
if (f.end) {
|
|
165
|
-
if (!f.code || !f.id) return console.error("Usage: antenna event --end --code abc123 --id
|
|
165
|
+
if (!f.code || !f.id) return console.error("Usage: antenna event --end --code abc123 --id <platform>:<user_id>");
|
|
166
166
|
const result = await endEvent({ code: f.code, device_id: f.id });
|
|
167
167
|
if (result.ended) {
|
|
168
168
|
console.log(`\n✅ Event ended.\n`);
|
|
@@ -173,15 +173,15 @@ export async function handleEvent(f) {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
if (f.checkin) {
|
|
176
|
-
if (!f.code || !f.id) return console.error("Usage: antenna event --checkin --code abc123 --id
|
|
176
|
+
if (!f.code || !f.id) return console.error("Usage: antenna event --checkin --code abc123 --id <platform>:<user_id> [--lat 34.05 --lng -118.24]");
|
|
177
177
|
const result = await eventCheckin({ code: f.code, device_id: f.id, lat: f.lat ? +f.lat : undefined, lng: f.lng ? +f.lng : undefined });
|
|
178
178
|
console.log(`\n✅ Checked in to event.\n`);
|
|
179
179
|
return;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
if (f.create || (!f.join && !f.scan && !f.end && !f.update && !f.approve && !f.reject && !f['add-host'] && f.name)) {
|
|
183
|
-
if (!f.name) return console.error("Usage: antenna event --create --name 'AI Meetup' --id
|
|
184
|
-
if (!f.id) return console.error("❌ --id is required (e.g. --id
|
|
183
|
+
if (!f.name) return console.error("Usage: antenna event --create --name 'AI Meetup' --id <platform>:<user_id> --starts-at '2026-04-19T14:00' --ends-at '2026-04-19T18:00' [--lat 34.05 --lng -118.25] [--desc 'description'] [--og-image 'url'] [--requires-approval] [--screening-questions 'Q1|Q2']");
|
|
184
|
+
if (!f.id) return console.error("❌ --id is required (e.g. --id <platform>:<user_id>). Creator identity needed to manage the event.");
|
|
185
185
|
if (!f['starts-at'] || !f['ends-at']) return console.error("❌ --starts-at and --ends-at are required. Example: --starts-at '2026-04-19T14:00' --ends-at '2026-04-19T18:00'");
|
|
186
186
|
const result = await createEvent({ name: f.name, device_id: f.id, lat: f.lat ? +f.lat : undefined, lng: f.lng ? +f.lng : undefined, starts_at: f['starts-at'], ends_at: f['ends-at'], description: f.desc || undefined, og_image: f['og-image'] || undefined, requires_approval: f['requires-approval'] === true || f['requires-approval'] === 'true' || undefined, screening_questions: f['screening-questions'] ? f['screening-questions'].split('|') : undefined });
|
|
187
187
|
console.log(`\n🎉 Event created!\n`);
|
|
@@ -193,7 +193,7 @@ export async function handleEvent(f) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
if (f.join) {
|
|
196
|
-
if (!f.code || !f.id) return console.error("Usage: antenna event --join --code abc123 --id
|
|
196
|
+
if (!f.code || !f.id) return console.error("Usage: antenna event --join --code abc123 --id <platform>:<user_id>");
|
|
197
197
|
const result = await joinEvent({ code: f.code, device_id: f.id, lat: f.lat ? +f.lat : undefined, lng: f.lng ? +f.lng : undefined, application_context: f['application-context'] || undefined });
|
|
198
198
|
if (result.joined) {
|
|
199
199
|
if (result.status === 'pending') {
|
|
@@ -213,7 +213,7 @@ export async function handleEvent(f) {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
if (f.scan) {
|
|
216
|
-
if (!f.code) return console.error("Usage: antenna event --scan --code abc123 [--id
|
|
216
|
+
if (!f.code) return console.error("Usage: antenna event --scan --code abc123 [--id <platform>:<user_id>]");
|
|
217
217
|
const result = await eventScan({ code: f.code, device_id: f.id || null });
|
|
218
218
|
if (result.count === 0) return console.log("\n🏟️ No participants yet.\n");
|
|
219
219
|
console.log(`\n🏟️ ${result.count} joined, ${result.checked_in_count || 0} checked in:\n`);
|
|
@@ -230,7 +230,7 @@ export async function handleEvent(f) {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
if (f.approve) {
|
|
233
|
-
if (!f.code || !f.id || !f.ref) return console.error("Usage: antenna event --approve --code abc123 --id
|
|
233
|
+
if (!f.code || !f.id || !f.ref) return console.error("Usage: antenna event --approve --code abc123 --id <platform>:<user_id> --ref 1");
|
|
234
234
|
const result = await approveParticipant({ code: f.code, device_id: f.id, ref: f.ref });
|
|
235
235
|
if (result.approved) {
|
|
236
236
|
console.log("\n✅ Participant approved\n");
|
|
@@ -241,7 +241,7 @@ export async function handleEvent(f) {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
if (f.reject) {
|
|
244
|
-
if (!f.code || !f.id || !f.ref) return console.error("Usage: antenna event --reject --code abc123 --id
|
|
244
|
+
if (!f.code || !f.id || !f.ref) return console.error("Usage: antenna event --reject --code abc123 --id <platform>:<user_id> --ref 1");
|
|
245
245
|
const result = await rejectParticipant({ code: f.code, device_id: f.id, ref: f.ref });
|
|
246
246
|
if (result.rejected) {
|
|
247
247
|
console.log("\n✅ Participant rejected\n");
|
|
@@ -252,7 +252,7 @@ export async function handleEvent(f) {
|
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
if (f.update) {
|
|
255
|
-
if (!f.code || !f.id) return console.error("Usage: antenna event --update --code abc123 --id
|
|
255
|
+
if (!f.code || !f.id) return console.error("Usage: antenna event --update --code abc123 --id <platform>:<user_id> [--name 'New Name'] [--desc 'New desc']");
|
|
256
256
|
const result = await updateEvent({ code: f.code, device_id: f.id, name: f.name, description: f.desc, og_image: f['og-image'], lat: f.lat ? +f.lat : undefined, lng: f.lng ? +f.lng : undefined, starts_at: f['starts-at'], ends_at: f['ends-at'] });
|
|
257
257
|
if (result.updated) {
|
|
258
258
|
console.log("\n✅ Event updated\n");
|
|
@@ -263,7 +263,7 @@ export async function handleEvent(f) {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
if (f['add-host']) {
|
|
266
|
-
if (!f.code || !f.id || !f.ref) return console.error("Usage: antenna event --add-host --code abc123 --id
|
|
266
|
+
if (!f.code || !f.id || !f.ref) return console.error("Usage: antenna event --add-host --code abc123 --id <platform>:<user_id> --ref 1");
|
|
267
267
|
const result = await addCohost({ code: f.code, device_id: f.id, ref: f.ref });
|
|
268
268
|
if (result.added) {
|
|
269
269
|
console.log("\n✅ Co-host added\n");
|
|
@@ -274,16 +274,16 @@ export async function handleEvent(f) {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
console.log(`Usage:
|
|
277
|
-
antenna event --create --name 'AI Meetup' --starts-at '...' --ends-at '...' [--id
|
|
278
|
-
antenna event --join --code abc123 --id
|
|
279
|
-
antenna event --scan --code abc123 [--id
|
|
280
|
-
antenna event --checkin --code abc123 --id
|
|
281
|
-
antenna event --end --code abc123 --id
|
|
277
|
+
antenna event --create --name 'AI Meetup' --starts-at '...' --ends-at '...' [--id <platform>:<user_id>] [--lat 34.05 --lng -118.25] [--desc 'description'] [--og-image 'url'] [--requires-approval] [--screening-questions 'Q1|Q2']
|
|
278
|
+
antenna event --join --code abc123 --id <platform>:<user_id>
|
|
279
|
+
antenna event --scan --code abc123 [--id <platform>:<user_id>]
|
|
280
|
+
antenna event --checkin --code abc123 --id <platform>:<user_id> [--lat 34.05 --lng -118.24]
|
|
281
|
+
antenna event --end --code abc123 --id <platform>:<user_id>
|
|
282
282
|
antenna event --upload-image --code abc123 --file /path/to/image.png`);
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
export async function handleBind(f) {
|
|
286
|
-
if (!f.id) return console.error("Usage: antenna bind --id
|
|
286
|
+
if (!f.id) return console.error("Usage: antenna bind --id <platform>:<user_id>");
|
|
287
287
|
const result = await createBindToken({ device_id: f.id });
|
|
288
288
|
console.log("\n🔗 GPS Binding Link:\n");
|
|
289
289
|
console.log(` ${result.url}\n`);
|
|
@@ -292,8 +292,8 @@ export async function handleBind(f) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
export async function handlePass(f) {
|
|
295
|
-
if (!f.id) return console.error("Usage: antenna pass --id
|
|
296
|
-
if (!f.target && !f.ref) return console.error("Usage: antenna pass --id
|
|
295
|
+
if (!f.id) return console.error("Usage: antenna pass --id <platform>:<user_id> --target <ref_or_device_id>");
|
|
296
|
+
if (!f.target && !f.ref) return console.error("Usage: antenna pass --id <platform>:<user_id> --target <ref_or_device_id> (or --ref 1)");
|
|
297
297
|
const result = await passUser({
|
|
298
298
|
device_id: f.id,
|
|
299
299
|
target_device_id: f.target,
|
|
@@ -308,7 +308,7 @@ export async function handleSetup(f) {
|
|
|
308
308
|
|
|
309
309
|
console.log("\n📡 Antenna Setup — 创建你的名片\n");
|
|
310
310
|
|
|
311
|
-
const id = f.id || await ask("Your device ID (e.g.
|
|
311
|
+
const id = f.id || await ask("Your device ID (e.g. discord:12345, hermes:myname): ");
|
|
312
312
|
if (!id) { rl.close(); return console.error("Device ID is required."); }
|
|
313
313
|
|
|
314
314
|
const name = await ask("Display name: ");
|
|
@@ -499,7 +499,7 @@ export function handleInstallHermesPlugin() {
|
|
|
499
499
|
export async function handleWatch(f) {
|
|
500
500
|
const id = f.id;
|
|
501
501
|
if (!id) {
|
|
502
|
-
console.error("❌ --id required (e.g. --id
|
|
502
|
+
console.error("❌ --id required (e.g. --id <platform>:<user_id>)");
|
|
503
503
|
process.exit(1);
|
|
504
504
|
}
|
|
505
505
|
|
|
@@ -870,19 +870,19 @@ export function printHelp() {
|
|
|
870
870
|
console.log(`📡 Antenna — nearby people discovery
|
|
871
871
|
|
|
872
872
|
Usage:
|
|
873
|
-
antenna scan --lat 39.99 --lng 116.48 [--radius 500] (max 1000) [--id
|
|
874
|
-
antenna checkin --id
|
|
875
|
-
antenna profile --id
|
|
876
|
-
antenna accept --id
|
|
877
|
-
antenna pass --id
|
|
878
|
-
antenna matches --id
|
|
879
|
-
antenna discover --id
|
|
880
|
-
antenna event --create --name 'AI Meetup' --starts-at '...' --ends-at '...' [--lat 34.05 --lng -118.25] [--desc '...'] [--og-image 'url'] [--requires-approval] [--screening-questions 'Q1|Q2'] | --join --code abc123 | --scan --code abc123 | --end --code abc123 --id
|
|
881
|
-
antenna watch --id
|
|
882
|
-
antenna bind --id
|
|
873
|
+
antenna scan --lat 39.99 --lng 116.48 [--radius 500] (max 1000) [--id <platform>:<user_id>]
|
|
874
|
+
antenna checkin --id <platform>:<user_id> --lat 39.99 --lng 116.48
|
|
875
|
+
antenna profile --id <platform>:<user_id> [--name Yi --emoji 🦦 --line1 '...']
|
|
876
|
+
antenna accept --id <platform>:<user_id> --target <ref_or_device_id> [--contact 'WeChat: yi']
|
|
877
|
+
antenna pass --id <platform>:<user_id> --target <ref_or_device_id> (or --ref 1)
|
|
878
|
+
antenna matches --id <platform>:<user_id>
|
|
879
|
+
antenna discover --id <platform>:<user_id>
|
|
880
|
+
antenna event --create --name 'AI Meetup' --starts-at '...' --ends-at '...' [--lat 34.05 --lng -118.25] [--desc '...'] [--og-image 'url'] [--requires-approval] [--screening-questions 'Q1|Q2'] | --join --code abc123 | --scan --code abc123 | --end --code abc123 --id <platform>:<user_id> | --upload-image --code abc123 --file /path/to/image.png | --update --code abc123 --name 'New Name' | --approve --code abc123 --ref 1 | --reject --code abc123 --ref 1 | --add-host --code abc123 --ref 1
|
|
881
|
+
antenna watch --id <platform>:<user_id> [--push hermes|openclaw|terminal] Watch for new matches in real-time (Ctrl+C to stop)
|
|
882
|
+
antenna bind --id <platform>:<user_id>
|
|
883
883
|
antenna serve Start MCP server (stdio transport)
|
|
884
|
-
antenna setup Interactive profile setup [--id
|
|
885
|
-
antenna status Show config & status [--id
|
|
884
|
+
antenna setup Interactive profile setup [--id <platform>:<user_id>]
|
|
885
|
+
antenna status Show config & status [--id <platform>:<user_id>]
|
|
886
886
|
antenna install-skill Install SKILL.md (detects OpenClaw + Hermes)
|
|
887
887
|
antenna install-plugin Copy OpenClaw plugin template to cwd
|
|
888
888
|
antenna install-hermes One-step Hermes setup (Plugin + Skill + deps)
|
package/lib/core.js
CHANGED
|
@@ -146,7 +146,7 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
|
|
|
146
146
|
radius_m,
|
|
147
147
|
profiles: [],
|
|
148
148
|
_ref_map: {},
|
|
149
|
-
message:
|
|
149
|
+
message: `附近暂时没人,今天的全球推荐也用完了。明天再来!提示:Antenna 每天有 1 次全球推荐(antenna_discover),下次可以试试。`,
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
|
|
@@ -21,10 +21,14 @@ SCAN_SCHEMA = {
|
|
|
21
21
|
},
|
|
22
22
|
"channel": {
|
|
23
23
|
"type": "string",
|
|
24
|
-
"description": "Platform name (telegram, discord, etc.)",
|
|
24
|
+
"description": "Platform name (any platform: telegram, discord, webchat, signal, slack, etc.)",
|
|
25
|
+
},
|
|
26
|
+
"chat_id": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "REQUIRED for notifications. Pass chat/channel ID from message context.",
|
|
25
29
|
},
|
|
26
30
|
},
|
|
27
|
-
"required": ["sender_id", "channel"],
|
|
31
|
+
"required": ["sender_id", "channel", "chat_id"],
|
|
28
32
|
},
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -51,8 +55,9 @@ PROFILE_SCHEMA = {
|
|
|
51
55
|
"line2": {"type": "string", "description": "What you're into"},
|
|
52
56
|
"line3": {"type": "string", "description": "What you're looking for"},
|
|
53
57
|
"visible": {"type": "boolean", "description": "Visible to others"},
|
|
58
|
+
"matching_context": {"type": "string", "description": "Free-form context for AI matching (interests, goals, etc.)"},
|
|
54
59
|
},
|
|
55
|
-
"required": ["action", "sender_id", "channel"],
|
|
60
|
+
"required": ["action", "sender_id", "channel", "chat_id"],
|
|
56
61
|
},
|
|
57
62
|
}
|
|
58
63
|
|
|
@@ -81,7 +86,7 @@ ACCEPT_SCHEMA = {
|
|
|
81
86
|
"description": "Contact info to share (e.g. 'WeChat: yi')",
|
|
82
87
|
},
|
|
83
88
|
},
|
|
84
|
-
"required": ["sender_id", "channel"],
|
|
89
|
+
"required": ["sender_id", "channel", "chat_id"],
|
|
85
90
|
},
|
|
86
91
|
}
|
|
87
92
|
|
|
@@ -103,7 +108,7 @@ CHECKIN_SCHEMA = {
|
|
|
103
108
|
"description": "Name of the place (optional)",
|
|
104
109
|
},
|
|
105
110
|
},
|
|
106
|
-
"required": ["lat", "lng", "sender_id", "channel"],
|
|
111
|
+
"required": ["lat", "lng", "sender_id", "channel", "chat_id"],
|
|
107
112
|
},
|
|
108
113
|
}
|
|
109
114
|
|
|
@@ -119,7 +124,7 @@ CHECK_MATCHES_SCHEMA = {
|
|
|
119
124
|
"channel": {"type": "string"},
|
|
120
125
|
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
121
126
|
},
|
|
122
|
-
"required": ["sender_id", "channel"],
|
|
127
|
+
"required": ["sender_id", "channel", "chat_id"],
|
|
123
128
|
},
|
|
124
129
|
}
|
|
125
130
|
|
|
@@ -137,7 +142,7 @@ BIND_SCHEMA = {
|
|
|
137
142
|
"purpose": {"type": "string", "description": "'profile' (default) or 'event'"},
|
|
138
143
|
"event_code": {"type": "string", "description": "Event code (when purpose=event)"},
|
|
139
144
|
},
|
|
140
|
-
"required": ["sender_id", "channel"],
|
|
145
|
+
"required": ["sender_id", "channel", "chat_id"],
|
|
141
146
|
},
|
|
142
147
|
}
|
|
143
148
|
|
|
@@ -148,7 +153,8 @@ PASS_SCHEMA = {
|
|
|
148
153
|
"type": "object",
|
|
149
154
|
"properties": {
|
|
150
155
|
"sender_id": {"type": "string", "description": "The sender's user ID"},
|
|
151
|
-
"channel": {"type": "string", "description": "Platform name"},
|
|
156
|
+
"channel": {"type": "string", "description": "Platform name (any platform works)"},
|
|
157
|
+
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
152
158
|
"ref": {
|
|
153
159
|
"type": "string",
|
|
154
160
|
"description": "Ref number from scan/discover results (e.g. '1')",
|
|
@@ -158,7 +164,7 @@ PASS_SCHEMA = {
|
|
|
158
164
|
"description": "Device ID (use ref instead when possible)",
|
|
159
165
|
},
|
|
160
166
|
},
|
|
161
|
-
"required": ["sender_id", "channel"],
|
|
167
|
+
"required": ["sender_id", "channel", "chat_id"],
|
|
162
168
|
},
|
|
163
169
|
}
|
|
164
170
|
|
|
@@ -172,9 +178,10 @@ DISCOVER_SCHEMA = {
|
|
|
172
178
|
"type": "object",
|
|
173
179
|
"properties": {
|
|
174
180
|
"sender_id": {"type": "string", "description": "The sender's user ID"},
|
|
175
|
-
"channel": {"type": "string", "description": "Platform name"},
|
|
181
|
+
"channel": {"type": "string", "description": "Platform name (any platform works)"},
|
|
182
|
+
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
176
183
|
},
|
|
177
|
-
"required": ["sender_id", "channel"],
|
|
184
|
+
"required": ["sender_id", "channel", "chat_id"],
|
|
178
185
|
},
|
|
179
186
|
}
|
|
180
187
|
|
|
@@ -189,7 +196,7 @@ EVENT_CREATE_SCHEMA = {
|
|
|
189
196
|
"properties": {
|
|
190
197
|
"name": {"type": "string", "description": "Event name"},
|
|
191
198
|
"sender_id": {"type": "string", "description": "The sender's user ID"},
|
|
192
|
-
"channel": {"type": "string", "description": "Platform name"},
|
|
199
|
+
"channel": {"type": "string", "description": "Platform name (any platform works)"},
|
|
193
200
|
"lat": {"type": "number", "description": "Event latitude"},
|
|
194
201
|
"lng": {"type": "number", "description": "Event longitude"},
|
|
195
202
|
"starts_at": {"type": "string", "description": "Start time ISO (required)"},
|
|
@@ -198,8 +205,9 @@ EVENT_CREATE_SCHEMA = {
|
|
|
198
205
|
"og_image": {"type": "string", "description": "OG image URL for social sharing"},
|
|
199
206
|
"requires_approval": {"type": "boolean", "description": "Require host approval to join (default false)"},
|
|
200
207
|
"screening_questions": {"type": "array", "items": {"type": "string"}, "description": "Screening questions for applicants"},
|
|
208
|
+
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
201
209
|
},
|
|
202
|
-
"required": ["name", "sender_id", "channel", "starts_at", "ends_at"],
|
|
210
|
+
"required": ["name", "sender_id", "channel", "starts_at", "ends_at", "chat_id"],
|
|
203
211
|
},
|
|
204
212
|
}
|
|
205
213
|
|
|
@@ -211,12 +219,13 @@ EVENT_JOIN_SCHEMA = {
|
|
|
211
219
|
"properties": {
|
|
212
220
|
"code": {"type": "string", "description": "Event code"},
|
|
213
221
|
"sender_id": {"type": "string", "description": "The sender's user ID"},
|
|
214
|
-
"channel": {"type": "string", "description": "Platform name"},
|
|
222
|
+
"channel": {"type": "string", "description": "Platform name (any platform works)"},
|
|
215
223
|
"lat": {"type": "number", "description": "Latitude (optional, for auto-checkin)"},
|
|
216
224
|
"lng": {"type": "number", "description": "Longitude (optional, for auto-checkin)"},
|
|
217
225
|
"application_context": {"type": "string", "description": "Application context from screening conversation"},
|
|
226
|
+
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
218
227
|
},
|
|
219
|
-
"required": ["code", "sender_id", "channel"],
|
|
228
|
+
"required": ["code", "sender_id", "channel", "chat_id"],
|
|
220
229
|
},
|
|
221
230
|
}
|
|
222
231
|
|
|
@@ -228,9 +237,10 @@ EVENT_SCAN_SCHEMA = {
|
|
|
228
237
|
"properties": {
|
|
229
238
|
"code": {"type": "string", "description": "Event code"},
|
|
230
239
|
"sender_id": {"type": "string", "description": "The sender's user ID"},
|
|
231
|
-
"channel": {"type": "string", "description": "Platform name"},
|
|
240
|
+
"channel": {"type": "string", "description": "Platform name (any platform works)"},
|
|
241
|
+
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
232
242
|
},
|
|
233
|
-
"required": ["code", "sender_id", "channel"],
|
|
243
|
+
"required": ["code", "sender_id", "channel", "chat_id"],
|
|
234
244
|
},
|
|
235
245
|
}
|
|
236
246
|
|
|
@@ -242,9 +252,10 @@ EVENT_END_SCHEMA = {
|
|
|
242
252
|
"properties": {
|
|
243
253
|
"code": {"type": "string", "description": "Event code"},
|
|
244
254
|
"sender_id": {"type": "string", "description": "The sender's user ID"},
|
|
245
|
-
"channel": {"type": "string", "description": "Platform name"},
|
|
255
|
+
"channel": {"type": "string", "description": "Platform name (any platform works)"},
|
|
256
|
+
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
246
257
|
},
|
|
247
|
-
"required": ["code", "sender_id", "channel"],
|
|
258
|
+
"required": ["code", "sender_id", "channel", "chat_id"],
|
|
248
259
|
},
|
|
249
260
|
}
|
|
250
261
|
|
|
@@ -270,11 +281,12 @@ EVENT_CHECKIN_SCHEMA = {
|
|
|
270
281
|
"properties": {
|
|
271
282
|
"code": {"type": "string", "description": "Event code"},
|
|
272
283
|
"sender_id": {"type": "string", "description": "The sender's user ID"},
|
|
273
|
-
"channel": {"type": "string", "description": "Platform name"},
|
|
284
|
+
"channel": {"type": "string", "description": "Platform name (any platform works)"},
|
|
274
285
|
"lat": {"type": "number", "description": "Latitude (optional)"},
|
|
275
286
|
"lng": {"type": "number", "description": "Longitude (optional)"},
|
|
287
|
+
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
276
288
|
},
|
|
277
|
-
"required": ["code", "sender_id", "channel"],
|
|
289
|
+
"required": ["code", "sender_id", "channel", "chat_id"],
|
|
278
290
|
},
|
|
279
291
|
}
|
|
280
292
|
|
|
@@ -296,7 +308,7 @@ EVENT_UPDATE_SCHEMA = {
|
|
|
296
308
|
"starts_at": {"type": "string"},
|
|
297
309
|
"ends_at": {"type": "string"},
|
|
298
310
|
},
|
|
299
|
-
"required": ["code", "sender_id", "channel"],
|
|
311
|
+
"required": ["code", "sender_id", "channel", "chat_id"],
|
|
300
312
|
},
|
|
301
313
|
}
|
|
302
314
|
|
|
@@ -312,7 +324,7 @@ EVENT_APPROVE_SCHEMA = {
|
|
|
312
324
|
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
313
325
|
"ref": {"type": "string"},
|
|
314
326
|
},
|
|
315
|
-
"required": ["code", "sender_id", "channel", "ref"],
|
|
327
|
+
"required": ["code", "sender_id", "channel", "ref", "chat_id"],
|
|
316
328
|
},
|
|
317
329
|
}
|
|
318
330
|
|
|
@@ -328,7 +340,7 @@ EVENT_REJECT_SCHEMA = {
|
|
|
328
340
|
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
329
341
|
"ref": {"type": "string"},
|
|
330
342
|
},
|
|
331
|
-
"required": ["code", "sender_id", "channel", "ref"],
|
|
343
|
+
"required": ["code", "sender_id", "channel", "ref", "chat_id"],
|
|
332
344
|
},
|
|
333
345
|
}
|
|
334
346
|
|
|
@@ -344,6 +356,6 @@ EVENT_ADD_HOST_SCHEMA = {
|
|
|
344
356
|
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
345
357
|
"ref": {"type": "string"},
|
|
346
358
|
},
|
|
347
|
-
"required": ["code", "sender_id", "channel", "ref"],
|
|
359
|
+
"required": ["code", "sender_id", "channel", "ref", "chat_id"],
|
|
348
360
|
},
|
|
349
361
|
}
|
|
@@ -85,7 +85,7 @@ def handle_scan(params: dict) -> str:
|
|
|
85
85
|
# Rate limit
|
|
86
86
|
now = time.time()
|
|
87
87
|
if did in _last_scan and now - _last_scan[did] < SCAN_DEBOUNCE_S:
|
|
88
|
-
return _ok({"
|
|
88
|
+
return _ok({"profiles": [], "message": "刚刚才扫描过,稍等一会儿再试。", "rate_limited": True})
|
|
89
89
|
_last_scan[did] = now
|
|
90
90
|
|
|
91
91
|
lat = params.get("lat")
|
|
@@ -99,7 +99,7 @@ def handle_scan(params: dict) -> str:
|
|
|
99
99
|
lat = loc["lat"]
|
|
100
100
|
lng = loc["lng"]
|
|
101
101
|
else:
|
|
102
|
-
return _ok({"
|
|
102
|
+
return _ok({"profiles": [], "message": "还没有位置信息。请先通过链接分享位置,或者发送位置消息。"})
|
|
103
103
|
|
|
104
104
|
flat, flng = _fuzzy(lat, lng)
|
|
105
105
|
|
|
@@ -111,7 +111,7 @@ def handle_scan(params: dict) -> str:
|
|
|
111
111
|
others = [p for p in (resp.data or []) if p.get("device_id") != did]
|
|
112
112
|
|
|
113
113
|
if not others:
|
|
114
|
-
return _ok({"
|
|
114
|
+
return _ok({"profiles": [], "message": f"在 {radius}m 范围内没有发现其他人。"})
|
|
115
115
|
|
|
116
116
|
# Build ref mapping — never expose device_id to agent/user
|
|
117
117
|
global _last_ref_map
|
|
@@ -131,8 +131,8 @@ def handle_scan(params: dict) -> str:
|
|
|
131
131
|
})
|
|
132
132
|
|
|
133
133
|
return _ok({
|
|
134
|
-
"
|
|
135
|
-
"
|
|
134
|
+
"profiles": profiles,
|
|
135
|
+
"count": len(others),
|
|
136
136
|
"radius_m": radius,
|
|
137
137
|
"instruction": "根据你对用户的了解,判断哪些人值得推荐,为每个推荐写一句个性化的匹配理由。使用 ref 编号(如 '1', '2')来引用人员,不要显示 device_id。",
|
|
138
138
|
})
|
|
@@ -149,7 +149,7 @@ def handle_profile(params: dict) -> str:
|
|
|
149
149
|
return _ok({"exists": True, "profile": resp.data})
|
|
150
150
|
|
|
151
151
|
# set
|
|
152
|
-
|
|
152
|
+
rpc_params = {
|
|
153
153
|
"p_device_id": did,
|
|
154
154
|
"p_display_name": params.get("display_name"),
|
|
155
155
|
"p_emoji": params.get("emoji"),
|
|
@@ -157,7 +157,10 @@ def handle_profile(params: dict) -> str:
|
|
|
157
157
|
"p_line2": params.get("line2"),
|
|
158
158
|
"p_line3": params.get("line3"),
|
|
159
159
|
"p_visible": params.get("visible", True),
|
|
160
|
-
}
|
|
160
|
+
}
|
|
161
|
+
if params.get("matching_context") is not None:
|
|
162
|
+
rpc_params["p_matching_context"] = params["matching_context"]
|
|
163
|
+
resp = sb.rpc("upsert_profile", rpc_params).execute()
|
|
161
164
|
|
|
162
165
|
if resp.data:
|
|
163
166
|
return _ok({"updated": True, "profile": resp.data, "next_step": "IMPORTANT: Now call antenna_bind to generate a GPS link for the user. Do not skip this."})
|
|
@@ -225,43 +228,37 @@ def handle_check_matches(params: dict) -> str:
|
|
|
225
228
|
sb = _sb()
|
|
226
229
|
did = _device_id(params["sender_id"], params["channel"], params.get("chat_id"))
|
|
227
230
|
|
|
228
|
-
resp = sb.rpc("
|
|
229
|
-
|
|
231
|
+
resp = sb.rpc("get_my_matches_with_profiles", {"p_device_id": did}).execute()
|
|
232
|
+
data = resp.data or {}
|
|
230
233
|
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
raw_mutual = data.get("mutual_matches") or []
|
|
235
|
+
raw_incoming = data.get("incoming_accepts") or []
|
|
233
236
|
|
|
234
|
-
|
|
235
|
-
|
|
237
|
+
if not raw_mutual and not raw_incoming:
|
|
238
|
+
return _ok({"mutual_matches": [], "incoming_accepts": [], "message": "目前没有进行中的匹配。"})
|
|
236
239
|
|
|
237
|
-
# Mutual
|
|
238
240
|
mutual = []
|
|
239
|
-
for m in
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
"you_shared": m.get("contact_info_a"),
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
# Incoming only
|
|
241
|
+
for i, m in enumerate(raw_mutual):
|
|
242
|
+
mutual.append({
|
|
243
|
+
"ref": str(i + 1),
|
|
244
|
+
"_device_id": m.get("target_id"),
|
|
245
|
+
"name": m.get("name") or "匿名",
|
|
246
|
+
"emoji": m.get("emoji") or "👤",
|
|
247
|
+
"their_contact": m.get("their_contact"),
|
|
248
|
+
"you_shared": m.get("you_shared"),
|
|
249
|
+
})
|
|
250
|
+
|
|
253
251
|
inc_only = []
|
|
254
|
-
for m in
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
})
|
|
252
|
+
for i, m in enumerate(raw_incoming):
|
|
253
|
+
inc_only.append({
|
|
254
|
+
"ref": str(i + 1),
|
|
255
|
+
"_device_id": m.get("target_id"),
|
|
256
|
+
"name": m.get("name") or "匿名",
|
|
257
|
+
"emoji": m.get("emoji") or "👤",
|
|
258
|
+
"line1": m.get("line1"),
|
|
259
|
+
"line2": m.get("line2"),
|
|
260
|
+
"line3": m.get("line3"),
|
|
261
|
+
})
|
|
265
262
|
|
|
266
263
|
msgs = []
|
|
267
264
|
if mutual:
|
|
@@ -292,7 +289,7 @@ def handle_pass(params: dict) -> str:
|
|
|
292
289
|
if not target and ref:
|
|
293
290
|
# Try resolve via RPC
|
|
294
291
|
try:
|
|
295
|
-
resp = sb.rpc("resolve_ref", {"
|
|
292
|
+
resp = sb.rpc("resolve_ref", {"p_owner": did, "p_ref": ref}).execute()
|
|
296
293
|
if resp.data:
|
|
297
294
|
target = resp.data.get("target_device_id")
|
|
298
295
|
except Exception:
|
|
@@ -302,7 +299,7 @@ def handle_pass(params: dict) -> str:
|
|
|
302
299
|
|
|
303
300
|
sb.rpc("pass_user", {
|
|
304
301
|
"p_device_id": did,
|
|
305
|
-
"
|
|
302
|
+
"p_passed_device_id": target,
|
|
306
303
|
}).execute()
|
|
307
304
|
|
|
308
305
|
return _ok({"passed": True, "message": "已跳过,不会再推荐这个人了。"})
|
|
@@ -327,9 +324,11 @@ def handle_discover(params: dict) -> str:
|
|
|
327
324
|
my_data = my_prof.data or {}
|
|
328
325
|
my_lines = [my_data.get("line1", ""), my_data.get("line2", ""), my_data.get("line3", "")]
|
|
329
326
|
|
|
327
|
+
ref_map = {}
|
|
330
328
|
for i, p in enumerate(results):
|
|
331
329
|
ref = str(i + 1)
|
|
332
330
|
_last_ref_map[ref] = p.get("device_id")
|
|
331
|
+
ref_map[ref] = p.get("device_id")
|
|
333
332
|
|
|
334
333
|
their_lines = [p.get("line1", ""), p.get("line2", ""), p.get("line3", "")]
|
|
335
334
|
|
|
@@ -359,6 +358,17 @@ def handle_discover(params: dict) -> str:
|
|
|
359
358
|
profile["match_reason"] = match_reason
|
|
360
359
|
profiles.append(profile)
|
|
361
360
|
|
|
361
|
+
# Save refs and log recommendations
|
|
362
|
+
try:
|
|
363
|
+
sb.rpc("save_scan_refs", {"p_owner": did, "p_refs": ref_map}).execute()
|
|
364
|
+
except Exception:
|
|
365
|
+
pass
|
|
366
|
+
for p in results:
|
|
367
|
+
try:
|
|
368
|
+
sb.rpc("log_recommendation", {"p_device_id": did, "p_recommended_id": p["device_id"]}).execute()
|
|
369
|
+
except Exception:
|
|
370
|
+
pass
|
|
371
|
+
|
|
362
372
|
return _ok({
|
|
363
373
|
"count": len(profiles),
|
|
364
374
|
"profiles": profiles,
|
|
@@ -499,7 +509,6 @@ def handle_event_scan(params: dict) -> str:
|
|
|
499
509
|
if not others:
|
|
500
510
|
return _ok({"count": 0, "profiles": [], "message": "活动里还没有其他人。"})
|
|
501
511
|
|
|
502
|
-
global _last_ref_map
|
|
503
512
|
global _last_ref_map
|
|
504
513
|
_last_ref_map = {}
|
|
505
514
|
checked_in_count = 0
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -10,7 +10,7 @@ You have access to Antenna for location-based social discovery.
|
|
|
10
10
|
|
|
11
11
|
**How to call Antenna:**
|
|
12
12
|
- If you have Antenna plugin tools registered (antenna_scan, antenna_profile, etc.) — use them directly.
|
|
13
|
-
- If not, use CLI: `antenna <command> [flags]`. Example: `antenna scan --id
|
|
13
|
+
- If not, use CLI: `antenna <command> [flags]`. Example: `antenna scan --id <platform>:<user_id>`. The CLI supports all the same features.
|
|
14
14
|
- All tool names map to CLI commands: `antenna_scan` → `antenna scan`, `antenna_event_create` → `antenna event --create`, etc.
|
|
15
15
|
- **Always pass `chat_id`** when calling any Antenna tool. This is the chat/channel ID from your message context (e.g. Discord channel ID, Telegram chat ID). Antenna uses it to send you notifications about matches and event approvals.
|
|
16
16
|
|
|
@@ -230,7 +230,7 @@ Source code is open: https://github.com/H1an1/Antenna
|
|
|
230
230
|
> "最后——你想被叫什么?再选个 emoji 代表你自己。"
|
|
231
231
|
|
|
232
232
|
**第六步:确认**
|
|
233
|
-
|
|
233
|
+
把名片组装好,**确认三行都有内容后**展示给用户确认:
|
|
234
234
|
> 你的名片:
|
|
235
235
|
>
|
|
236
236
|
> 🎸 **小林**
|
|
@@ -240,7 +240,8 @@ Source code is open: https://github.com/H1an1/Antenna
|
|
|
240
240
|
>
|
|
241
241
|
> 看看有没有要改的?OK 的话我就存了。
|
|
242
242
|
|
|
243
|
-
用户说 OK → `antenna_profile` action="set" 保存。
|
|
243
|
+
用户说 OK → **检查 line1、line2、line3 都不为空后** 调 `antenna_profile` action="set" 保存。
|
|
244
|
+
如果某一行为空,先问用户补完再保存。后端会拒绝没有 line1 的新 profile,并对缺失的 line2/line3 返回 warning。
|
|
244
245
|
用户说要改 → 改完再确认。
|
|
245
246
|
|
|
246
247
|
**关键原则:**
|
|
@@ -253,6 +254,10 @@ Source code is open: https://github.com/H1an1/Antenna
|
|
|
253
254
|
|
|
254
255
|
### Showing results — 你来判断,不是服务器
|
|
255
256
|
|
|
257
|
+
**第一次 scan 的新用户:** 简短一句解释:"这是附近的人。Antenna 基于 AI 匹配,看到感兴趣的人 accept,双向匹配后交换联系方式。"
|
|
258
|
+
|
|
259
|
+
**Profile 不完整时:** 如果用户的 profile 只有 1 行,提示:"你的名片只填了一行,补完后匹配质量会更好。要现在补吗?"
|
|
260
|
+
|
|
256
261
|
`antenna_scan` 返回的是附近所有人的名片,**没有打分、没有预匹配**。你需要:
|
|
257
262
|
|
|
258
263
|
**全球推荐 fallback:** 如果 scan 结果里有 `global: true`,说明附近没人,这些是全球推荐。告诉用户“附近暂时没人,但全球有这几个有意思的人”,然后正常推荐。用户仍然可以 accept。
|
|
@@ -261,7 +266,7 @@ Source code is open: https://github.com/H1an1/Antenna
|
|
|
261
266
|
2. 结合你对用户的全部了解,判断谁值得推荐
|
|
262
267
|
3. 为每个推荐的人写一句**个性化的理由**——不是"你们都提到了 X",而是真正有洞察的话
|
|
263
268
|
|
|
264
|
-
**⚠️ 隐私规则:展示结果时绝对不要显示 device_id。** `device_id`(如 `
|
|
269
|
+
**⚠️ 隐私规则:展示结果时绝对不要显示 device_id。** `device_id`(如 `platform:user123`)是内部标识符,包含用户的平台和 ID,属于隐私信息。只显示 emoji + 名字 + 三句话 + 你写的匹配理由。`device_id` 只在内部调 `antenna_accept` 时用,不要展示给用户。
|
|
265
270
|
|
|
266
271
|
比如你知道用户最近在学吉他,看到附近有人写"组乐队找吉他手":
|
|
267
272
|
> 🎸 **小林** — 在组后摇乐队,找吉他手
|
|
@@ -296,7 +301,7 @@ Use `antenna_check_matches` when:
|
|
|
296
301
|
|
|
297
302
|
### Privacy
|
|
298
303
|
- Never reveal exact coordinates to other users
|
|
299
|
-
- **Never show device_id to users** (e.g. `telegram:
|
|
304
|
+
- **Never show device_id to users** (e.g. `telegram:12345`, `discord:67890`) — this is internal only
|
|
300
305
|
- Never share someone's platform or username with another user
|
|
301
306
|
- Only show the profile info (name, emoji, three lines)
|
|
302
307
|
- Contact info is only shared when the user explicitly agrees
|
|
@@ -376,7 +381,7 @@ Update event info. Only creator or co-host can update.
|
|
|
376
381
|
- `code`: event code
|
|
377
382
|
- `sender_id`, `channel`: from context
|
|
378
383
|
- `chat_id`: REQUIRED for notifications
|
|
379
|
-
- `name`, `description`, `og_image`, `lat`, `lng`, `starts_at`, `ends_at`: all optional
|
|
384
|
+
- `name`, `description`, `og_image`, `lat`, `lng`, `starts_at`, `ends_at`: all optional for update (only provided fields change, others stay as-is)
|
|
380
385
|
|
|
381
386
|
### `antenna_event_approve`
|
|
382
387
|
Approve a pending participant. Only creator or co-host.
|
package/skill/EVENTS.md
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: antenna-events
|
|
3
|
-
description: "Antenna Event Mode — create events, manage participants, GPS check-in. Use when a user wants to create an event, join an event, check in, scan event participants, or manage event settings."
|
|
4
|
-
tools:
|
|
5
|
-
- antenna_event_create
|
|
6
|
-
- antenna_event_join
|
|
7
|
-
- antenna_event_scan
|
|
8
|
-
- antenna_event_end
|
|
9
|
-
- antenna_event_checkin
|
|
10
|
-
- antenna_event_upload_image
|
|
11
|
-
- antenna_bind
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# Antenna Event Mode
|
|
15
|
-
|
|
16
|
-
Create real-world events where everyone's AI agent handles the networking.
|
|
17
|
-
|
|
18
|
-
## Quick Start
|
|
19
|
-
|
|
20
|
-
1. **Create**: `antenna_event_create(name, description)` → get shareable link
|
|
21
|
-
2. **Share**: Send `antenna.fyi/events/CODE` to attendees
|
|
22
|
-
3. **Join**: Attendees' agents call `antenna_event_join(code)`
|
|
23
|
-
4. **Check in**: At the venue, `antenna_event_checkin(code)` — GPS verified ≤1km
|
|
24
|
-
5. **Discover**: `antenna_event_scan(code)` — no distance limit inside events
|
|
25
|
-
|
|
26
|
-
## Tools
|
|
27
|
-
|
|
28
|
-
### `antenna_event_create`
|
|
29
|
-
Create an event. Returns a shareable link (antenna.fyi/events/CODE).
|
|
30
|
-
- `name`: event name
|
|
31
|
-
- `sender_id`, `channel`: from context
|
|
32
|
-
- `lat`, `lng`: optional event location
|
|
33
|
-
- `starts_at`, `ends_at`: optional time range (default: now to +12h)
|
|
34
|
-
- `description`: optional event description
|
|
35
|
-
- `og_image`: optional OG image URL for social sharing preview
|
|
36
|
-
- `requires_approval`: boolean, default false. If true, participants must be approved by the organizer before they become visible.
|
|
37
|
-
- `screening_questions`: string array. Questions to ask applicants. Agent should collect answers via conversation and submit as `application_context` when joining.
|
|
38
|
-
|
|
39
|
-
**When the user says anything about "审批" / "approval" / "筛选" / "报名表"**, set `requires_approval: true` and ask what screening questions they want.
|
|
40
|
-
|
|
41
|
-
**GPS flow for events:** If the user doesn't provide coordinates, generate a bind link (`antenna_bind` with `purpose="event"` and `event_code`) and ask them to open it at the event location. The GPS will update the event's coordinates, NOT the user's profile.
|
|
42
|
-
|
|
43
|
-
### `antenna_event_join`
|
|
44
|
-
Join an event by code. Auto-checks in if event already started and user GPS is within 1km.
|
|
45
|
-
- `code`: from the event URL (antenna.fyi/events/CODE)
|
|
46
|
-
- `sender_id`, `channel`: from context
|
|
47
|
-
- **Requires profile** — user must have a profile before joining
|
|
48
|
-
- If event has started + user has GPS + within 1km → auto check-in
|
|
49
|
-
|
|
50
|
-
### `antenna_event_scan`
|
|
51
|
-
Scan people in an event. No distance limit — returns all participants.
|
|
52
|
-
- `code`: event code
|
|
53
|
-
- `sender_id`, `channel`: from context
|
|
54
|
-
- Returns profiles with `checked_in` status and `role` (creator/participant)
|
|
55
|
-
- Header shows "X joined, Y checked in"
|
|
56
|
-
|
|
57
|
-
### `antenna_event_end`
|
|
58
|
-
End an event. Only the creator can end it.
|
|
59
|
-
- `code`: event code
|
|
60
|
-
- `sender_id`, `channel`: from context
|
|
61
|
-
|
|
62
|
-
### `antenna_event_checkin`
|
|
63
|
-
Check in at an event — marks you as present at the event location.
|
|
64
|
-
- `code`: event code
|
|
65
|
-
- `sender_id`, `channel`: from context
|
|
66
|
-
- `lat`, `lng`: optional GPS (auto-reads profile location if not provided)
|
|
67
|
-
- GPS verified: must be within 1km of event location
|
|
68
|
-
- Event must have GPS set for check-in to work
|
|
69
|
-
|
|
70
|
-
### `antenna_event_upload_image`
|
|
71
|
-
Upload an image for an event OG preview. Returns a public URL.
|
|
72
|
-
- `image_base64`: base64-encoded image data
|
|
73
|
-
- `content_type`: MIME type (default image/png)
|
|
74
|
-
- `event_code`: event code
|
|
75
|
-
|
|
76
|
-
### `antenna_bind` (for events)
|
|
77
|
-
Generate a GPS link for setting event location.
|
|
78
|
-
- `purpose`: set to `"event"`
|
|
79
|
-
- `event_code`: the event code
|
|
80
|
-
- GPS from this link writes to the event, not the user's profile
|
|
81
|
-
|
|
82
|
-
## Agent Behavior
|
|
83
|
-
|
|
84
|
-
### When someone says "create an event"
|
|
85
|
-
Collect the following info through conversation (ask one by one, don't dump all at once):
|
|
86
|
-
1. **Event name** (required) — "活动叫什么名字?"
|
|
87
|
-
2. **Description** — "简单描述一下这个活动?"
|
|
88
|
-
3. **Time** — "什么时候开始?大概多长?" (convert to starts_at / ends_at ISO strings)
|
|
89
|
-
4. **Location** — "活动在哪里?" If user gives an address, geocode it. If vague, generate a bind link after creation.
|
|
90
|
-
5. **Approval** — "需要审批参与者吗?" If yes:
|
|
91
|
-
6. **Screening questions** — "你想问报名者什么问题?" Collect as a list.
|
|
92
|
-
|
|
93
|
-
Then call `antenna_event_create` with all collected info.
|
|
94
|
-
If no GPS, call `antenna_bind(purpose="event", event_code=CODE)` and send the link.
|
|
95
|
-
Share the event URL with the user.
|
|
96
|
-
|
|
97
|
-
### When someone shares an event link
|
|
98
|
-
1. Extract the code from `antenna.fyi/events/CODE`
|
|
99
|
-
2. Call `antenna_event_join(code)` — this checks everything:
|
|
100
|
-
- If no profile → "Create a profile first"
|
|
101
|
-
- If event requires approval and no `application_context` provided → returns `needs_screening: true` + `screening_questions` array
|
|
102
|
-
- If screening questions returned: **ask the user each question**, collect answers, then call `antenna_event_join(code, application_context="collected answers")` again
|
|
103
|
-
- If join succeeds with `status: pending` → tell user "waiting for organizer approval"
|
|
104
|
-
- If join succeeds with `status: active` → user is in!
|
|
105
|
-
3. Auto check-in happens automatically if event has started + GPS within 1km
|
|
106
|
-
|
|
107
|
-
### When someone says "who's here" at an event
|
|
108
|
-
1. Call `antenna_event_scan(code)`
|
|
109
|
-
2. Analyze profiles against what you know about the user
|
|
110
|
-
3. Recommend who they should meet and why
|
|
111
|
-
4. Creator appears with organizer badge
|
|
112
|
-
|