antenna-fyi 1.2.6 β 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 +24 -8
- package/lib/core.js +16 -0
- package/lib/hermes-plugin/__init__.py +3 -0
- package/lib/hermes-plugin/schemas.py +14 -0
- package/lib/hermes-plugin/tools.py +12 -0
- package/lib/mcp.js +19 -0
- package/package.json +1 -1
- package/skill/SKILL.md +8 -0
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");
|
|
@@ -187,9 +200,11 @@ export async function handleEvent(f) {
|
|
|
187
200
|
if (!f.code) return console.error("Usage: antenna event --scan --code abc123 [--id telegram:123]");
|
|
188
201
|
const result = await eventScan({ code: f.code, device_id: f.id || null });
|
|
189
202
|
if (result.count === 0) return console.log("\nποΈ No participants yet.\n");
|
|
190
|
-
console.log(`\nποΈ ${result.count}
|
|
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 ? " β
" : "";
|
|
206
|
+
const creatorTag = p.role === "creator" ? " [δΈ»ε]" : "";
|
|
207
|
+
console.log(` ${p.emoji} ${p.name}${creatorTag}${badge}`);
|
|
193
208
|
if (p.line1) console.log(` ${p.line1}`);
|
|
194
209
|
console.log(` ref: ${p.ref}\n`);
|
|
195
210
|
});
|
|
@@ -201,7 +216,8 @@ export async function handleEvent(f) {
|
|
|
201
216
|
antenna event --join --code abc123 --id telegram:123
|
|
202
217
|
antenna event --scan --code abc123 [--id telegram:123]
|
|
203
218
|
antenna event --checkin --code abc123 --id telegram:123 [--lat 34.05 --lng -118.24]
|
|
204
|
-
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`);
|
|
205
221
|
}
|
|
206
222
|
|
|
207
223
|
export async function handleBind(f) {
|
|
@@ -396,7 +412,7 @@ Usage:
|
|
|
396
412
|
antenna pass --id telegram:123 --target telegram:789 (or --ref 1)
|
|
397
413
|
antenna matches --id telegram:123
|
|
398
414
|
antenna discover --id telegram:123
|
|
399
|
-
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
|
|
400
416
|
antenna bind --id telegram:123
|
|
401
417
|
antenna serve Start MCP server (stdio transport)
|
|
402
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", {
|
|
@@ -527,9 +538,11 @@ export async function eventScan({ code, device_id, supabaseUrl, supabaseKey }) {
|
|
|
527
538
|
|
|
528
539
|
const others = data || [];
|
|
529
540
|
const _refMap = {};
|
|
541
|
+
let checkedInCount = 0;
|
|
530
542
|
const profiles = others.map((p, i) => {
|
|
531
543
|
const ref = String(i + 1);
|
|
532
544
|
_refMap[ref] = p.device_id;
|
|
545
|
+
if (p.checked_in) checkedInCount++;
|
|
533
546
|
return {
|
|
534
547
|
ref,
|
|
535
548
|
name: p.display_name || "εΏε",
|
|
@@ -537,6 +550,8 @@ export async function eventScan({ code, device_id, supabaseUrl, supabaseKey }) {
|
|
|
537
550
|
line1: p.line1,
|
|
538
551
|
line2: p.line2,
|
|
539
552
|
line3: p.line3,
|
|
553
|
+
checked_in: !!p.checked_in,
|
|
554
|
+
role: p.role || "participant",
|
|
540
555
|
source: "event",
|
|
541
556
|
};
|
|
542
557
|
});
|
|
@@ -548,6 +563,7 @@ export async function eventScan({ code, device_id, supabaseUrl, supabaseKey }) {
|
|
|
548
563
|
|
|
549
564
|
return {
|
|
550
565
|
count: profiles.length,
|
|
566
|
+
checked_in_count: checkedInCount,
|
|
551
567
|
profiles,
|
|
552
568
|
_ref_map: _refMap,
|
|
553
569
|
event: true,
|
|
@@ -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
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
|