antenna-fyi 1.2.4 → 1.2.6

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
@@ -1,6 +1,6 @@
1
1
  // antenna CLI command handlers
2
2
 
3
- import { scan, getProfile, setProfile, accept, checkMatches, checkin, createBindToken, discover, createEvent, joinEvent, eventScan, pass as passUser } from "./core.js";
3
+ import { scan, getProfile, setProfile, accept, checkMatches, checkin, createBindToken, discover, createEvent, endEvent, eventCheckin, joinEvent, eventScan, pass as passUser } from "./core.js";
4
4
  import { createInterface } from "readline";
5
5
  import { existsSync, mkdirSync, copyFileSync } from "fs";
6
6
  import { join, dirname } from "path";
@@ -47,8 +47,18 @@ export async function handleScan(f) {
47
47
  if (p.line1) console.log(` ${p.line1}`);
48
48
  if (p.line2) console.log(` ${p.line2}`);
49
49
  if (p.line3) console.log(` ${p.line3}`);
50
- console.log(` id: ${p.device_id}\n`);
50
+ console.log(` ref: ${p.ref}\n`);
51
51
  });
52
+
53
+ // Show nearby events
54
+ if (result.nearby_events?.length) {
55
+ console.log(`šŸŽ‰ Nearby events:\n`);
56
+ result.nearby_events.forEach((e) => {
57
+ console.log(` ${e.name} — ${e.participants} people`);
58
+ if (e.description) console.log(` ${e.description}`);
59
+ console.log(` Join: antenna event --join --code ${e.code}\n`);
60
+ });
61
+ }
52
62
  }
53
63
 
54
64
  export async function handleProfile(f) {
@@ -131,11 +141,29 @@ export async function handleDiscover(f) {
131
141
  }
132
142
 
133
143
  export async function handleEvent(f) {
134
- const sub = f._?.[0] || Object.keys(f).find(k => ["create", "join", "scan"].includes(k));
144
+ const sub = f._?.[0] || Object.keys(f).find(k => ["create", "join", "scan", "end", "checkin"].includes(k));
145
+
146
+ if (f.end) {
147
+ if (!f.code || !f.id) return console.error("Usage: antenna event --end --code abc123 --id telegram:123");
148
+ const result = await endEvent({ code: f.code, device_id: f.id });
149
+ if (result.ended) {
150
+ console.log(`\nāœ… Event ended.\n`);
151
+ } else {
152
+ console.log(`\nāŒ ${result.error || 'Failed to end event'}\n`);
153
+ }
154
+ return;
155
+ }
156
+
157
+ if (f.checkin) {
158
+ if (!f.code || !f.id) return console.error("Usage: antenna event --checkin --code abc123 --id telegram:123 [--lat 34.05 --lng -118.24]");
159
+ const result = await eventCheckin({ code: f.code, device_id: f.id, lat: f.lat ? +f.lat : undefined, lng: f.lng ? +f.lng : undefined });
160
+ console.log(`\nāœ… Checked in to event.\n`);
161
+ return;
162
+ }
135
163
 
136
- if (f.create || (!f.join && !f.scan && f.name)) {
137
- if (!f.name) return console.error("Usage: antenna event --create --name 'AI Meetup'");
138
- const result = await createEvent({ name: f.name, device_id: f.id || null, lat: f.lat ? +f.lat : undefined, lng: f.lng ? +f.lng : undefined });
164
+ if (f.create || (!f.join && !f.scan && !f.end && f.name)) {
165
+ if (!f.name) return console.error("Usage: antenna event --create --name 'AI Meetup' [--desc 'description'] [--og-image 'url']");
166
+ const result = await createEvent({ name: f.name, device_id: f.id || null, lat: f.lat ? +f.lat : undefined, lng: f.lng ? +f.lng : undefined, description: f.desc || undefined, og_image: f['og-image'] || undefined });
139
167
  console.log(`\nšŸŽ‰ Event created!\n`);
140
168
  console.log(` Name: ${result.name}`);
141
169
  console.log(` Code: ${result.code}`);
@@ -169,9 +197,11 @@ export async function handleEvent(f) {
169
197
  }
170
198
 
171
199
  console.log(`Usage:
172
- antenna event --create --name 'AI Meetup' [--id telegram:123]
200
+ antenna event --create --name 'AI Meetup' [--id telegram:123] [--desc 'description'] [--og-image 'url']
173
201
  antenna event --join --code abc123 --id telegram:123
174
- antenna event --scan --code abc123 [--id telegram:123]`);
202
+ antenna event --scan --code abc123 [--id telegram:123]
203
+ antenna event --checkin --code abc123 --id telegram:123 [--lat 34.05 --lng -118.24]
204
+ antenna event --end --code abc123 --id telegram:123`);
175
205
  }
176
206
 
177
207
  export async function handleBind(f) {
@@ -366,7 +396,7 @@ Usage:
366
396
  antenna pass --id telegram:123 --target telegram:789 (or --ref 1)
367
397
  antenna matches --id telegram:123
368
398
  antenna discover --id telegram:123
369
- antenna event --create --name 'AI Meetup' | --join --code abc123 | --scan --code abc123
399
+ antenna event --create --name 'AI Meetup' [--desc '...'] [--og-image 'url'] | --join --code abc123 | --scan --code abc123 | --end --code abc123 --id telegram:123
370
400
  antenna bind --id telegram:123
371
401
  antenna serve Start MCP server (stdio transport)
372
402
  antenna setup Interactive profile setup [--id telegram:123]
package/lib/core.js CHANGED
@@ -161,11 +161,21 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
161
161
  const profs = buildProfiles(others);
162
162
  await saveRefs(_refMap);
163
163
 
164
+ // Fetch nearby events
165
+ let nearby_events = [];
166
+ if (lat != null && lng != null) {
167
+ try {
168
+ const { data: evts } = await sb.rpc("nearby_events", { p_lat: lat, p_lng: lng, p_radius_m: 5000 });
169
+ nearby_events = evts || [];
170
+ } catch { /* best effort */ }
171
+ }
172
+
164
173
  return {
165
174
  count: others.length,
166
175
  radius_m,
167
176
  profiles: profs,
168
177
  _ref_map: _refMap,
178
+ nearby_events,
169
179
  };
170
180
  }
171
181
 
@@ -469,7 +479,7 @@ export async function pass({ device_id, target_device_id, ref, supabaseUrl, supa
469
479
 
470
480
  // ─── events ─────────────────────────────────────────────────────────
471
481
 
472
- export async function createEvent({ name, lat, lng, device_id, starts_at, ends_at, supabaseUrl, supabaseKey }) {
482
+ export async function createEvent({ name, lat, lng, device_id, starts_at, ends_at, description, og_image, supabaseUrl, supabaseKey }) {
473
483
  const sb = getClient(supabaseUrl, supabaseKey);
474
484
  const { data, error } = await sb.rpc("create_event", {
475
485
  p_name: name,
@@ -478,6 +488,26 @@ export async function createEvent({ name, lat, lng, device_id, starts_at, ends_a
478
488
  p_created_by: device_id || null,
479
489
  p_starts_at: starts_at || new Date().toISOString(),
480
490
  p_ends_at: ends_at || new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString(),
491
+ p_description: description || null,
492
+ p_og_image: og_image || null,
493
+ });
494
+ if (error) throw new Error(error.message);
495
+ return data;
496
+ }
497
+
498
+ export async function endEvent({ code, device_id, supabaseUrl, supabaseKey }) {
499
+ const sb = getClient(supabaseUrl, supabaseKey);
500
+ const { data, error } = await sb.rpc("end_event", { p_code: code, p_device_id: device_id });
501
+ if (error) throw new Error(error.message);
502
+ return data;
503
+ }
504
+
505
+ export async function eventCheckin({ code, device_id, lat, lng, supabaseUrl, supabaseKey }) {
506
+ const sb = getClient(supabaseUrl, supabaseKey);
507
+ const fuzzy = (lat != null && lng != null) ? fuzzyCoord(lat, lng) : { lat: null, lng: null };
508
+ const { data, error } = await sb.rpc("event_checkin", {
509
+ p_code: code, p_device_id: device_id,
510
+ p_lat: fuzzy.lat, p_lng: fuzzy.lng,
481
511
  });
482
512
  if (error) throw new Error(error.message);
483
513
  return data;
@@ -18,6 +18,8 @@ from .tools import (
18
18
  handle_event_create,
19
19
  handle_event_join,
20
20
  handle_event_scan,
21
+ handle_event_end,
22
+ handle_event_checkin,
21
23
  _sb,
22
24
  _device_id,
23
25
  _my_device_ids,
@@ -34,6 +36,8 @@ from .schemas import (
34
36
  EVENT_CREATE_SCHEMA,
35
37
  EVENT_JOIN_SCHEMA,
36
38
  EVENT_SCAN_SCHEMA,
39
+ EVENT_END_SCHEMA,
40
+ EVENT_CHECKIN_SCHEMA,
37
41
  )
38
42
  import re
39
43
  import time
@@ -61,6 +65,8 @@ def register(ctx):
61
65
  ctx.register_tool("antenna_event_create", EVENT_CREATE_SCHEMA, handle_event_create)
62
66
  ctx.register_tool("antenna_event_join", EVENT_JOIN_SCHEMA, handle_event_join)
63
67
  ctx.register_tool("antenna_event_scan", EVENT_SCAN_SCHEMA, handle_event_scan)
68
+ ctx.register_tool("antenna_event_end", EVENT_END_SCHEMA, handle_event_end)
69
+ ctx.register_tool("antenna_event_checkin", EVENT_CHECKIN_SCHEMA, handle_event_checkin)
64
70
 
65
71
  # ── Hook: auto-detect location + check web GPS events ─────────
66
72
  def on_pre_llm(messages, **kwargs):
@@ -176,7 +176,7 @@ EVENT_CREATE_SCHEMA = {
176
176
  "name": "antenna_event_create",
177
177
  "description": (
178
178
  "Create an event. Returns a shareable link (antenna.fyi/e/CODE) "
179
- "for participants to join."
179
+ "for participants to join. Optionally include a description and OG image URL."
180
180
  ),
181
181
  "parameters": {
182
182
  "type": "object",
@@ -188,6 +188,8 @@ EVENT_CREATE_SCHEMA = {
188
188
  "lng": {"type": "number", "description": "Event longitude"},
189
189
  "starts_at": {"type": "string", "description": "Start time ISO"},
190
190
  "ends_at": {"type": "string", "description": "End time ISO"},
191
+ "description": {"type": "string", "description": "Event description"},
192
+ "og_image": {"type": "string", "description": "OG image URL for social sharing"},
191
193
  },
192
194
  "required": ["name", "sender_id", "channel"],
193
195
  },
@@ -220,3 +222,33 @@ EVENT_SCAN_SCHEMA = {
220
222
  "required": ["code", "sender_id", "channel"],
221
223
  },
222
224
  }
225
+
226
+ EVENT_END_SCHEMA = {
227
+ "name": "antenna_event_end",
228
+ "description": "End an event. Only the creator can end it.",
229
+ "parameters": {
230
+ "type": "object",
231
+ "properties": {
232
+ "code": {"type": "string", "description": "Event code"},
233
+ "sender_id": {"type": "string", "description": "The sender's user ID"},
234
+ "channel": {"type": "string", "description": "Platform name"},
235
+ },
236
+ "required": ["code", "sender_id", "channel"],
237
+ },
238
+ }
239
+
240
+ EVENT_CHECKIN_SCHEMA = {
241
+ "name": "antenna_event_checkin",
242
+ "description": "Check in at an event \u2014 marks you as present at the event location. Optionally updates GPS.",
243
+ "parameters": {
244
+ "type": "object",
245
+ "properties": {
246
+ "code": {"type": "string", "description": "Event code"},
247
+ "sender_id": {"type": "string", "description": "The sender's user ID"},
248
+ "channel": {"type": "string", "description": "Platform name"},
249
+ "lat": {"type": "number", "description": "Latitude (optional)"},
250
+ "lng": {"type": "number", "description": "Longitude (optional)"},
251
+ },
252
+ "required": ["code", "sender_id", "channel"],
253
+ },
254
+ }
@@ -379,6 +379,10 @@ def handle_event_create(params: dict) -> str:
379
379
  rpc_params["p_starts_at"] = params["starts_at"]
380
380
  if params.get("ends_at"):
381
381
  rpc_params["p_ends_at"] = params["ends_at"]
382
+ if params.get("description"):
383
+ rpc_params["p_description"] = params["description"]
384
+ if params.get("og_image"):
385
+ rpc_params["p_og_image"] = params["og_image"]
382
386
 
383
387
  resp = sb.rpc("create_event", rpc_params).execute()
384
388
  data = resp.data or {}
@@ -459,3 +463,38 @@ def handle_bind(params: dict) -> str:
459
463
  "url": f"{BASE_URL}/locate?token={token}",
460
464
  "message": "å‘é€čæ™äøŖé“¾ęŽ„ē»™ē”Øęˆ·ļ¼ŒåœØę‰‹ęœŗęµč§ˆå™Øę‰“å¼€å³åÆå…±äŗ«ä½ē½®ć€‚",
461
465
  })
466
+
467
+
468
+ def handle_event_end(params: dict) -> str:
469
+ sb = _sb()
470
+ did = _device_id(params["sender_id"], params["channel"])
471
+
472
+ resp = sb.rpc("end_event", {
473
+ "p_code": params["code"],
474
+ "p_device_id": did,
475
+ }).execute()
476
+ data = resp.data or {}
477
+
478
+ if data.get("ended"):
479
+ return _ok({"ended": True, "message": f"ę“»åŠØå·²ē»“ęŸć€‚"})
480
+ return _ok({"ended": False, "error": data.get("error", "ē»“ęŸę“»åŠØå¤±č“„")})
481
+
482
+
483
+ def handle_event_checkin(params: dict) -> str:
484
+ sb = _sb()
485
+ did = _device_id(params["sender_id"], params["channel"])
486
+
487
+ lat = params.get("lat")
488
+ lng = params.get("lng")
489
+ if lat is not None and lng is not None:
490
+ flat, flng = _fuzzy(lat, lng)
491
+ else:
492
+ flat, flng = None, None
493
+
494
+ resp = sb.rpc("event_checkin", {
495
+ "p_code": params["code"],
496
+ "p_device_id": did,
497
+ "p_lat": flat,
498
+ "p_lng": flng,
499
+ }).execute()
500
+ return _ok(resp.data or {})
package/lib/mcp.js CHANGED
@@ -14,6 +14,8 @@ import {
14
14
  createBindToken,
15
15
  discover,
16
16
  createEvent,
17
+ endEvent,
18
+ eventCheckin,
17
19
  joinEvent,
18
20
  eventScan,
19
21
  deriveDeviceId,
@@ -233,10 +235,30 @@ export async function startMcpServer() {
233
235
  lng: z.number().optional().describe("Event longitude"),
234
236
  starts_at: z.string().optional().describe("Start time ISO string"),
235
237
  ends_at: z.string().optional().describe("End time ISO string"),
238
+ description: z.string().optional().describe("Event description"),
239
+ og_image: z.string().optional().describe("OG image URL for social sharing"),
236
240
  },
237
- async ({ name, sender_id, channel, lat, lng, starts_at, ends_at }) => {
241
+ async ({ name, sender_id, channel, lat, lng, starts_at, ends_at, description, og_image }) => {
238
242
  try {
239
- const result = await createEvent({ name, lat, lng, device_id: deriveDeviceId(sender_id, channel), starts_at, ends_at });
243
+ const result = await createEvent({ name, lat, lng, device_id: deriveDeviceId(sender_id, channel), starts_at, ends_at, description, og_image });
244
+ return jsonResult(result);
245
+ } catch (e) { return jsonResult({ error: e.message }); }
246
+ }
247
+ );
248
+
249
+ // ─── antenna_event_end ───────────────────────────────────────
250
+
251
+ server.tool(
252
+ "antenna_event_end",
253
+ "End an event. Only the creator can end it.",
254
+ {
255
+ code: z.string().describe("Event code"),
256
+ sender_id: z.string().describe("The sender's user ID"),
257
+ channel: z.string().describe("Channel name"),
258
+ },
259
+ async ({ code, sender_id, channel }) => {
260
+ try {
261
+ const result = await endEvent({ code, device_id: deriveDeviceId(sender_id, channel) });
240
262
  return jsonResult(result);
241
263
  } catch (e) { return jsonResult({ error: e.message }); }
242
264
  }
@@ -260,6 +282,26 @@ export async function startMcpServer() {
260
282
  }
261
283
  );
262
284
 
285
+ // ─── antenna_event_checkin ─────────────────────────────────
286
+
287
+ server.tool(
288
+ "antenna_event_checkin",
289
+ "Check in at an event — marks you as present at the event location. Optionally updates GPS.",
290
+ {
291
+ code: z.string().describe("Event code"),
292
+ sender_id: z.string().describe("The sender's user ID"),
293
+ channel: z.string().describe("Channel name"),
294
+ lat: z.number().optional().describe("Latitude (optional)"),
295
+ lng: z.number().optional().describe("Longitude (optional)"),
296
+ },
297
+ async ({ code, sender_id, channel, lat, lng }) => {
298
+ try {
299
+ const result = await eventCheckin({ code, device_id: deriveDeviceId(sender_id, channel), lat, lng });
300
+ return jsonResult(result);
301
+ } catch (e) { return jsonResult({ error: e.message }); }
302
+ }
303
+ );
304
+
263
305
  // ─── antenna_event_scan ────────────────────────────────────
264
306
 
265
307
  server.tool(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-fyi",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
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
@@ -66,11 +66,12 @@ cron č®¾å®ŒåŽļ¼Œé—®ē”Øęˆ·äø€å„ļ¼š
66
66
  ## Tools
67
67
 
68
68
  ### `antenna_scan`
69
- Scan for nearby people. Returns **raw profile cards** — no scores, no pre-matching. **You are the matching engine.**
69
+ Scan for nearby people **and events**. Returns raw profile cards + active events within 5km.
70
70
  - `lat`, `lng`: coordinates (from `LocationLat`/`LocationLon` context, or geocoded from user input)
71
- - `radius_m`: search radius in meters (default 500, max 1000)
71
+ - `radius_m`: search radius in meters (default 500, max 1000) for people; events search uses 5km
72
72
  - `sender_id`: the user's id from message context
73
73
  - `channel`: the channel name (telegram, whatsapp, discord, etc.)
74
+ - Returns `profiles` (nearby people) + `nearby_events` (active events with name, participants count, code)
74
75
 
75
76
  After receiving the nearby profiles, **you decide** who to recommend:
76
77
  - Use everything you know about the user: their SOUL.md, memory, recent conversations, interests, current mood
@@ -278,6 +279,13 @@ Create an event. Returns a shareable link (antenna.fyi/e/CODE).
278
279
  - `sender_id`, `channel`: from context
279
280
  - `lat`, `lng`: optional event location
280
281
  - `starts_at`, `ends_at`: optional time range (default: now to +12h)
282
+ - `description`: optional event description
283
+ - `og_image`: optional OG image URL for social sharing preview
284
+
285
+ ### `antenna_event_end`
286
+ End an event. Only the creator can end it.
287
+ - `code`: event code
288
+ - `sender_id`, `channel`: from context
281
289
 
282
290
  ### `antenna_event_join`
283
291
  Join an event by code.
@@ -289,3 +297,9 @@ Scan people in an event. No distance limit — returns all participants.
289
297
  - `code`: event code
290
298
  - `sender_id`, `channel`: from context
291
299
  - Returns profiles with `source: "event"` tag
300
+
301
+ ### `antenna_event_checkin`
302
+ Check in at an event — marks you as present at the event location. Optionally updates GPS.
303
+ - `code`: event code
304
+ - `sender_id`, `channel`: from context
305
+ - `lat`, `lng`: optional GPS coordinates