antenna-fyi 1.2.7 β†’ 1.2.8

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,9 +1,9 @@
1
1
  // antenna CLI command handlers
2
2
 
3
- import { scan, getProfile, setProfile, accept, checkMatches, checkin, createBindToken, discover, createEvent, endEvent, eventCheckin, 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, uploadEventImage } from "./core.js";
4
4
  import { createInterface } from "readline";
5
- import { existsSync, mkdirSync, copyFileSync } from "fs";
6
- import { join, dirname } from "path";
5
+ import { existsSync, mkdirSync, copyFileSync, readFileSync } from "fs";
6
+ import { join, dirname, extname } from "path";
7
7
  import { fileURLToPath } from "url";
8
8
  import { homedir } from "os";
9
9
  import { execSync } from "child_process";
@@ -141,7 +141,20 @@ export async function handleDiscover(f) {
141
141
  }
142
142
 
143
143
  export async function handleEvent(f) {
144
- const sub = f._?.[0] || Object.keys(f).find(k => ["create", "join", "scan", "end", "checkin"].includes(k));
144
+ const sub = f._?.[0] || Object.keys(f).find(k => ["create", "join", "scan", "end", "checkin", "upload-image"].includes(k));
145
+
146
+ if (f['upload-image']) {
147
+ if (!f.code || !f.file) return console.error("Usage: antenna event --upload-image --code abc123 --file /path/to/image.png");
148
+ const fileBuf = readFileSync(f.file);
149
+ const image_data = fileBuf.toString("base64");
150
+ const ext = extname(f.file).slice(1) || "png";
151
+ const mimeMap = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp" };
152
+ const content_type = mimeMap[ext] || "image/png";
153
+ const url = await uploadEventImage({ image_data, content_type, event_code: f.code });
154
+ console.log(`\nβœ… Image uploaded!\n`);
155
+ console.log(` URL: ${url}\n`);
156
+ return;
157
+ }
145
158
 
146
159
  if (f.end) {
147
160
  if (!f.code || !f.id) return console.error("Usage: antenna event --end --code abc123 --id telegram:123");
@@ -190,7 +203,8 @@ export async function handleEvent(f) {
190
203
  console.log(`\n🏟️ ${result.count} joined, ${result.checked_in_count || 0} checked in:\n`);
191
204
  result.profiles.forEach((p) => {
192
205
  const badge = p.checked_in ? " βœ…" : "";
193
- console.log(` ${p.emoji} ${p.name}${badge}`);
206
+ const creatorTag = p.role === "creator" ? " [主办]" : "";
207
+ console.log(` ${p.emoji} ${p.name}${creatorTag}${badge}`);
194
208
  if (p.line1) console.log(` ${p.line1}`);
195
209
  console.log(` ref: ${p.ref}\n`);
196
210
  });
@@ -202,7 +216,8 @@ export async function handleEvent(f) {
202
216
  antenna event --join --code abc123 --id telegram:123
203
217
  antenna event --scan --code abc123 [--id telegram:123]
204
218
  antenna event --checkin --code abc123 --id telegram:123 [--lat 34.05 --lng -118.24]
205
- antenna event --end --code abc123 --id telegram:123`);
219
+ antenna event --end --code abc123 --id telegram:123
220
+ antenna event --upload-image --code abc123 --file /path/to/image.png`);
206
221
  }
207
222
 
208
223
  export async function handleBind(f) {
@@ -397,7 +412,7 @@ Usage:
397
412
  antenna pass --id telegram:123 --target telegram:789 (or --ref 1)
398
413
  antenna matches --id telegram:123
399
414
  antenna discover --id telegram:123
400
- antenna event --create --name 'AI Meetup' [--desc '...'] [--og-image 'url'] | --join --code abc123 | --scan --code abc123 | --end --code abc123 --id telegram:123
415
+ antenna event --create --name 'AI Meetup' [--desc '...'] [--og-image 'url'] | --join --code abc123 | --scan --code abc123 | --end --code abc123 --id telegram:123 | --upload-image --code abc123 --file /path/to/image.png
401
416
  antenna bind --id telegram:123
402
417
  antenna serve Start MCP server (stdio transport)
403
418
  antenna setup Interactive profile setup [--id telegram:123]
package/lib/core.js CHANGED
@@ -479,6 +479,17 @@ export async function pass({ device_id, target_device_id, ref, supabaseUrl, supa
479
479
 
480
480
  // ─── events ─────────────────────────────────────────────────────────
481
481
 
482
+ export async function uploadEventImage({ image_data, content_type, event_code, supabaseUrl, supabaseKey }) {
483
+ const sb = getClient(supabaseUrl, supabaseKey);
484
+ const ext = (content_type || "image/png").split("/")[1] || "png";
485
+ const path = `${event_code || Date.now()}.${ext}`;
486
+ const buf = typeof image_data === "string" ? Buffer.from(image_data, "base64") : image_data;
487
+ const { error } = await sb.storage.from("event-images").upload(path, buf, { contentType: content_type || "image/png", upsert: true });
488
+ if (error) throw new Error(error.message);
489
+ const { data } = sb.storage.from("event-images").getPublicUrl(path);
490
+ return data.publicUrl;
491
+ }
492
+
482
493
  export async function createEvent({ name, lat, lng, device_id, starts_at, ends_at, description, og_image, supabaseUrl, supabaseKey }) {
483
494
  const sb = getClient(supabaseUrl, supabaseKey);
484
495
  const { data, error } = await sb.rpc("create_event", {
@@ -540,6 +551,7 @@ export async function eventScan({ code, device_id, supabaseUrl, supabaseKey }) {
540
551
  line2: p.line2,
541
552
  line3: p.line3,
542
553
  checked_in: !!p.checked_in,
554
+ role: p.role || "participant",
543
555
  source: "event",
544
556
  };
545
557
  });
@@ -20,6 +20,7 @@ from .tools import (
20
20
  handle_event_scan,
21
21
  handle_event_end,
22
22
  handle_event_checkin,
23
+ handle_event_upload_image,
23
24
  _sb,
24
25
  _device_id,
25
26
  _my_device_ids,
@@ -38,6 +39,7 @@ from .schemas import (
38
39
  EVENT_SCAN_SCHEMA,
39
40
  EVENT_END_SCHEMA,
40
41
  EVENT_CHECKIN_SCHEMA,
42
+ EVENT_UPLOAD_IMAGE_SCHEMA,
41
43
  )
42
44
  import re
43
45
  import time
@@ -67,6 +69,7 @@ def register(ctx):
67
69
  ctx.register_tool("antenna_event_scan", EVENT_SCAN_SCHEMA, handle_event_scan)
68
70
  ctx.register_tool("antenna_event_end", EVENT_END_SCHEMA, handle_event_end)
69
71
  ctx.register_tool("antenna_event_checkin", EVENT_CHECKIN_SCHEMA, handle_event_checkin)
72
+ ctx.register_tool("antenna_event_upload_image", EVENT_UPLOAD_IMAGE_SCHEMA, handle_event_upload_image)
70
73
 
71
74
  # ── Hook: auto-detect location + check web GPS events ─────────
72
75
  def on_pre_llm(messages, **kwargs):
@@ -237,6 +237,20 @@ EVENT_END_SCHEMA = {
237
237
  },
238
238
  }
239
239
 
240
+ EVENT_UPLOAD_IMAGE_SCHEMA = {
241
+ "name": "antenna_event_upload_image",
242
+ "description": "Upload an image for an event OG preview. Returns a public URL.",
243
+ "parameters": {
244
+ "type": "object",
245
+ "properties": {
246
+ "image_base64": {"type": "string", "description": "Base64-encoded image data"},
247
+ "content_type": {"type": "string", "description": "MIME type (default image/png)"},
248
+ "event_code": {"type": "string", "description": "Event code"},
249
+ },
250
+ "required": ["image_base64", "event_code"],
251
+ },
252
+ }
253
+
240
254
  EVENT_CHECKIN_SCHEMA = {
241
255
  "name": "antenna_event_checkin",
242
256
  "description": "Check in at an event \u2014 marks you as present at the event location. Optionally updates GPS.",
@@ -480,6 +480,18 @@ def handle_event_end(params: dict) -> str:
480
480
  return _ok({"ended": False, "error": data.get("error", "η»“ζŸζ΄»εŠ¨ε€±θ΄₯")})
481
481
 
482
482
 
483
+ def handle_event_upload_image(params: dict) -> str:
484
+ import base64 as b64mod
485
+ sb = _sb()
486
+ content_type = params.get("content_type") or "image/png"
487
+ ext = content_type.split("/")[1] if "/" in content_type else "png"
488
+ path = f"{params['event_code']}.{ext}"
489
+ buf = b64mod.b64decode(params["image_base64"])
490
+ resp = sb.storage.from_("event-images").upload(path, buf, {"content-type": content_type, "upsert": "true"})
491
+ pub = sb.storage.from_("event-images").get_public_url(path)
492
+ return _ok({"url": pub})
493
+
494
+
483
495
  def handle_event_checkin(params: dict) -> str:
484
496
  sb = _sb()
485
497
  did = _device_id(params["sender_id"], params["channel"])
package/lib/mcp.js CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  eventCheckin,
19
19
  joinEvent,
20
20
  eventScan,
21
+ uploadEventImage,
21
22
  deriveDeviceId,
22
23
  } from "./core.js";
23
24
 
@@ -246,6 +247,24 @@ export async function startMcpServer() {
246
247
  }
247
248
  );
248
249
 
250
+ // ─── antenna_event_upload_image ──────────────────────────────
251
+
252
+ server.tool(
253
+ "antenna_event_upload_image",
254
+ "Upload an image for an event. Returns a public URL to use as og_image.",
255
+ {
256
+ image_base64: z.string().describe("Base64-encoded image data"),
257
+ content_type: z.string().optional().describe("MIME type (default image/png)"),
258
+ event_code: z.string().describe("Event code to associate the image with"),
259
+ },
260
+ async ({ image_base64, content_type, event_code }) => {
261
+ try {
262
+ const url = await uploadEventImage({ image_data: image_base64, content_type, event_code });
263
+ return jsonResult({ url });
264
+ } catch (e) { return jsonResult({ error: e.message }); }
265
+ }
266
+ );
267
+
249
268
  // ─── antenna_event_end ───────────────────────────────────────
250
269
 
251
270
  server.tool(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-fyi",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
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
@@ -282,6 +282,8 @@ Create an event. Returns a shareable link (antenna.fyi/e/CODE).
282
282
  - `description`: optional event description
283
283
  - `og_image`: optional OG image URL for social sharing preview
284
284
 
285
+ **GPS flow for events:** If the user doesn't provide coordinates, generate a bind link (`antenna_bind`) and ask them to open it at the event location. Once GPS comes in, use those coordinates for `lat`/`lng`. Don't use profile location β€” the user may create an event at a different place.
286
+
285
287
  ### `antenna_event_end`
286
288
  End an event. Only the creator can end it.
287
289
  - `code`: event code
@@ -303,3 +305,9 @@ Check in at an event β€” marks you as present at the event location. Optionally
303
305
  - `code`: event code
304
306
  - `sender_id`, `channel`: from context
305
307
  - `lat`, `lng`: optional GPS coordinates
308
+
309
+ ### `antenna_event_upload_image`
310
+ Upload an image for an event OG preview. Returns a public URL.
311
+ - `image_base64`: base64-encoded image data
312
+ - `content_type`: MIME type (default image/png)
313
+ - `event_code`: event code