antenna-fyi 1.3.6 → 1.3.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 +28 -2
- package/lib/core.js +30 -1
- package/lib/hermes-plugin/__init__.py +16 -0
- package/lib/hermes-plugin/plugin.yaml +1 -1
- package/lib/hermes-plugin/schemas.py +19 -0
- package/lib/hermes-plugin/tools.py +22 -2
- package/lib/mcp.js +26 -2
- package/package.json +1 -1
- package/skill/SKILL.md +12 -0
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, endEvent, eventCheckin, joinEvent, eventScan, pass as passUser, uploadEventImage, updateEvent, approveParticipant, rejectParticipant, addCohost, getClient } from "./core.js";
|
|
3
|
+
import { scan, getProfile, setProfile, accept, checkMatches, checkin, createBindToken, discover, createEvent, endEvent, eventCheckin, joinEvent, eventScan, pass as passUser, uploadEventImage, updateEvent, approveParticipant, rejectParticipant, addCohost, sendEventMessage, getMyEventMessages, getClient } from "./core.js";
|
|
4
4
|
import { createInterface } from "readline";
|
|
5
5
|
import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, unlinkSync, renameSync } from "fs";
|
|
6
6
|
import path from "path";
|
|
@@ -273,13 +273,25 @@ export async function handleEvent(f) {
|
|
|
273
273
|
return;
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
if (f.message || f.msg) {
|
|
277
|
+
if (!f.code || !f.id || !(f.message || f.msg)) return console.error("Usage: antenna event --message 'Hello everyone' --code abc123 --id <platform>:<user_id> [--ref 1]");
|
|
278
|
+
const result = await sendEventMessage({ code: f.code, device_id: f.id, message: f.message || f.msg, target_ref: f.ref || undefined });
|
|
279
|
+
if (result.sent) {
|
|
280
|
+
console.log(`\n✅ Message sent${result.broadcast ? ' (broadcast to all)' : ''}\n`);
|
|
281
|
+
} else {
|
|
282
|
+
console.log(`\n❌ ${result.error}\n`);
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
276
287
|
console.log(`Usage:
|
|
277
288
|
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
289
|
antenna event --join --code abc123 --id <platform>:<user_id>
|
|
279
290
|
antenna event --scan --code abc123 [--id <platform>:<user_id>]
|
|
280
291
|
antenna event --checkin --code abc123 --id <platform>:<user_id> [--lat 34.05 --lng -118.24]
|
|
281
292
|
antenna event --end --code abc123 --id <platform>:<user_id>
|
|
282
|
-
antenna event --upload-image --code abc123 --file /path/to/image.png
|
|
293
|
+
antenna event --upload-image --code abc123 --file /path/to/image.png
|
|
294
|
+
antenna event --message 'Hello everyone' --code abc123 --id <platform>:<user_id> [--ref 1]`);
|
|
283
295
|
}
|
|
284
296
|
|
|
285
297
|
export async function handleBind(f) {
|
|
@@ -855,6 +867,20 @@ export async function handleWatch(f) {
|
|
|
855
867
|
}
|
|
856
868
|
}
|
|
857
869
|
} catch { /* silent */ }
|
|
870
|
+
|
|
871
|
+
// Poll event messages from hosts
|
|
872
|
+
try {
|
|
873
|
+
const { data: msgs } = await sb.rpc("get_my_event_messages", { p_device_id: id });
|
|
874
|
+
for (const msg of (msgs || [])) {
|
|
875
|
+
const key = `evtmsg:${msg.event_id}:${msg.created_at}`;
|
|
876
|
+
if (!notified.has(key)) {
|
|
877
|
+
notified.add(key);
|
|
878
|
+
saveNotified(notified);
|
|
879
|
+
const role = msg.sender_role === 'creator' ? '组织者' : '协办';
|
|
880
|
+
pushNotify(`📢 来自「${msg.event_name}」${role} ${msg.sender_emoji || ''} ${msg.sender_name}: ${msg.message}`);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
} catch { /* silent */ }
|
|
858
884
|
}, 2 * 60 * 1000);
|
|
859
885
|
|
|
860
886
|
// Handle Ctrl+C
|
package/lib/core.js
CHANGED
|
@@ -635,13 +635,15 @@ export async function getEvent({ code, supabaseUrl, supabaseKey }) {
|
|
|
635
635
|
return data;
|
|
636
636
|
}
|
|
637
637
|
|
|
638
|
-
export async function updateEvent({ code, device_id, name, description, og_image, lat, lng, starts_at, ends_at, supabaseUrl, supabaseKey }) {
|
|
638
|
+
export async function updateEvent({ code, device_id, name, description, og_image, lat, lng, starts_at, ends_at, requires_approval, screening_questions, supabaseUrl, supabaseKey }) {
|
|
639
639
|
const sb = getClient(supabaseUrl, supabaseKey);
|
|
640
640
|
const { data, error } = await sb.rpc("update_event", {
|
|
641
641
|
p_code: code, p_device_id: device_id,
|
|
642
642
|
p_name: name || null, p_description: description || null,
|
|
643
643
|
p_og_image: og_image || null, p_lat: lat ?? null, p_lng: lng ?? null,
|
|
644
644
|
p_starts_at: starts_at || null, p_ends_at: ends_at || null,
|
|
645
|
+
...(requires_approval != null ? { p_requires_approval: requires_approval } : {}),
|
|
646
|
+
...(screening_questions != null ? { p_screening_questions: screening_questions } : {}),
|
|
645
647
|
});
|
|
646
648
|
if (error) throw new Error(error.message);
|
|
647
649
|
return data;
|
|
@@ -686,3 +688,30 @@ export async function createBindToken({ device_id, purpose, event_code, supabase
|
|
|
686
688
|
: "发送这个链接给用户,在手机浏览器打开即可共享位置。",
|
|
687
689
|
};
|
|
688
690
|
}
|
|
691
|
+
|
|
692
|
+
// ─── sendEventMessage (host → participants) ─────────────────────────
|
|
693
|
+
|
|
694
|
+
export async function sendEventMessage({ code, device_id, message, target_ref, supabaseUrl, supabaseKey }) {
|
|
695
|
+
const sb = getClient(supabaseUrl, supabaseKey);
|
|
696
|
+
const params = {
|
|
697
|
+
p_code: code,
|
|
698
|
+
p_device_id: device_id,
|
|
699
|
+
p_message: message,
|
|
700
|
+
};
|
|
701
|
+
if (target_ref) params.p_target_ref = target_ref;
|
|
702
|
+
const { data, error } = await sb.rpc("send_event_message", params);
|
|
703
|
+
if (error) throw new Error(error.message);
|
|
704
|
+
return data;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// ─── getMyEventMessages ──────────────────────────────────────────────
|
|
708
|
+
|
|
709
|
+
export async function getMyEventMessages({ device_id, supabaseUrl, supabaseKey }) {
|
|
710
|
+
const sb = getClient(supabaseUrl, supabaseKey);
|
|
711
|
+
const { data, error } = await sb.rpc("get_my_event_messages", { p_device_id: device_id });
|
|
712
|
+
if (error) throw new Error(error.message);
|
|
713
|
+
return {
|
|
714
|
+
messages: data || [],
|
|
715
|
+
count: (data || []).length,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
@@ -32,6 +32,7 @@ from .tools import (
|
|
|
32
32
|
handle_event_approve,
|
|
33
33
|
handle_event_reject,
|
|
34
34
|
handle_event_add_host,
|
|
35
|
+
handle_event_message,
|
|
35
36
|
_sb,
|
|
36
37
|
_device_id,
|
|
37
38
|
_my_device_ids,
|
|
@@ -55,6 +56,7 @@ from .schemas import (
|
|
|
55
56
|
EVENT_APPROVE_SCHEMA,
|
|
56
57
|
EVENT_REJECT_SCHEMA,
|
|
57
58
|
EVENT_ADD_HOST_SCHEMA,
|
|
59
|
+
EVENT_MESSAGE_SCHEMA,
|
|
58
60
|
)
|
|
59
61
|
import re
|
|
60
62
|
import time
|
|
@@ -89,6 +91,7 @@ def register(ctx):
|
|
|
89
91
|
ctx.register_tool("antenna_event_approve", EVENT_APPROVE_SCHEMA, handle_event_approve)
|
|
90
92
|
ctx.register_tool("antenna_event_reject", EVENT_REJECT_SCHEMA, handle_event_reject)
|
|
91
93
|
ctx.register_tool("antenna_event_add_host", EVENT_ADD_HOST_SCHEMA, handle_event_add_host)
|
|
94
|
+
ctx.register_tool("antenna_event_message", EVENT_MESSAGE_SCHEMA, handle_event_message)
|
|
92
95
|
|
|
93
96
|
# ── Hook: auto-detect location + check web GPS events ─────────
|
|
94
97
|
def on_pre_llm(messages, **kwargs):
|
|
@@ -179,6 +182,19 @@ def register(ctx):
|
|
|
179
182
|
hints.append(
|
|
180
183
|
f"[Antenna] ❌ 你的申请未通过「{name}」"
|
|
181
184
|
)
|
|
185
|
+
|
|
186
|
+
# Check event messages from hosts
|
|
187
|
+
msg_resp = sb.rpc("get_my_event_messages", {"p_device_id": did}).execute()
|
|
188
|
+
for msg in (msg_resp.data or []):
|
|
189
|
+
key = f"evtmsg:{msg.get('event_id')}:{msg.get('created_at')}"
|
|
190
|
+
if key in _notified_match_keys:
|
|
191
|
+
continue
|
|
192
|
+
_notified_match_keys.add(key)
|
|
193
|
+
role_label = '组织者' if msg.get('sender_role') == 'creator' else '协办'
|
|
194
|
+
hints.append(
|
|
195
|
+
f"[Antenna] 📢 来自「{msg.get('event_name')}」{role_label} {msg.get('sender_emoji', '')} {msg.get('sender_name', '')}: {msg.get('message', '')}\n"
|
|
196
|
+
f"请立即通知用户这条消息。"
|
|
197
|
+
)
|
|
182
198
|
except Exception:
|
|
183
199
|
pass
|
|
184
200
|
now = time.time()
|
|
@@ -307,6 +307,8 @@ EVENT_UPDATE_SCHEMA = {
|
|
|
307
307
|
"lng": {"type": "number"},
|
|
308
308
|
"starts_at": {"type": "string"},
|
|
309
309
|
"ends_at": {"type": "string"},
|
|
310
|
+
"requires_approval": {"type": "boolean", "description": "Require host approval to join"},
|
|
311
|
+
"screening_questions": {"type": "array", "items": {"type": "string"}, "description": "Screening questions for applicants"},
|
|
310
312
|
},
|
|
311
313
|
"required": ["code", "sender_id", "channel", "chat_id"],
|
|
312
314
|
},
|
|
@@ -359,3 +361,20 @@ EVENT_ADD_HOST_SCHEMA = {
|
|
|
359
361
|
"required": ["code", "sender_id", "channel", "ref", "chat_id"],
|
|
360
362
|
},
|
|
361
363
|
}
|
|
364
|
+
|
|
365
|
+
EVENT_MESSAGE_SCHEMA = {
|
|
366
|
+
"name": "antenna_event_message",
|
|
367
|
+
"description": "Send a message to event participants. Only creator or co-host can send. Omit ref to broadcast to all.",
|
|
368
|
+
"parameters": {
|
|
369
|
+
"type": "object",
|
|
370
|
+
"properties": {
|
|
371
|
+
"code": {"type": "string", "description": "Event code"},
|
|
372
|
+
"sender_id": {"type": "string"},
|
|
373
|
+
"channel": {"type": "string"},
|
|
374
|
+
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
375
|
+
"message": {"type": "string", "description": "Message to send to participants"},
|
|
376
|
+
"ref": {"type": "string", "description": "Ref number of specific participant (omit for broadcast)"},
|
|
377
|
+
},
|
|
378
|
+
"required": ["code", "sender_id", "channel", "message", "chat_id"],
|
|
379
|
+
},
|
|
380
|
+
}
|
|
@@ -645,13 +645,18 @@ def handle_event_checkin(params: dict) -> str:
|
|
|
645
645
|
def handle_event_update(params: dict) -> str:
|
|
646
646
|
sb = _sb()
|
|
647
647
|
did = _device_id(params["sender_id"], params["channel"], params.get("chat_id"))
|
|
648
|
-
|
|
648
|
+
rpc_params = {
|
|
649
649
|
"p_code": params["code"], "p_device_id": did,
|
|
650
650
|
"p_name": params.get("name"), "p_description": params.get("description"),
|
|
651
651
|
"p_og_image": params.get("og_image"), "p_lat": params.get("lat"),
|
|
652
652
|
"p_lng": params.get("lng"), "p_starts_at": params.get("starts_at"),
|
|
653
653
|
"p_ends_at": params.get("ends_at"),
|
|
654
|
-
}
|
|
654
|
+
}
|
|
655
|
+
if params.get("requires_approval") is not None:
|
|
656
|
+
rpc_params["p_requires_approval"] = params["requires_approval"]
|
|
657
|
+
if params.get("screening_questions") is not None:
|
|
658
|
+
rpc_params["p_screening_questions"] = params["screening_questions"]
|
|
659
|
+
resp = sb.rpc("update_event", rpc_params).execute()
|
|
655
660
|
return _ok(resp.data or {"error": "update failed"})
|
|
656
661
|
|
|
657
662
|
|
|
@@ -680,3 +685,18 @@ def handle_event_add_host(params: dict) -> str:
|
|
|
680
685
|
"p_code": params["code"], "p_device_id": did, "p_target_ref": params["ref"],
|
|
681
686
|
}).execute()
|
|
682
687
|
return _ok(resp.data or {"error": "add_cohost failed"})
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def handle_event_message(params: dict) -> str:
|
|
691
|
+
"""Send a message to event participants. Only creator or co-host can send."""
|
|
692
|
+
sb = _sb()
|
|
693
|
+
did = _device_id(params["sender_id"], params["channel"], params.get("chat_id"))
|
|
694
|
+
rpc_params = {
|
|
695
|
+
"p_code": params["code"],
|
|
696
|
+
"p_device_id": did,
|
|
697
|
+
"p_message": params["message"],
|
|
698
|
+
}
|
|
699
|
+
if params.get("ref"):
|
|
700
|
+
rpc_params["p_target_ref"] = params["ref"]
|
|
701
|
+
resp = sb.rpc("send_event_message", rpc_params).execute()
|
|
702
|
+
return _ok(resp.data or {"error": "send_event_message failed"})
|
package/lib/mcp.js
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
approveParticipant,
|
|
24
24
|
rejectParticipant,
|
|
25
25
|
addCohost,
|
|
26
|
+
sendEventMessage,
|
|
26
27
|
deriveDeviceId,
|
|
27
28
|
} from "./core.js";
|
|
28
29
|
|
|
@@ -412,10 +413,12 @@ export async function startMcpServer() {
|
|
|
412
413
|
lng: z.number().optional().describe("New event longitude"),
|
|
413
414
|
starts_at: z.string().optional().describe("New start time ISO"),
|
|
414
415
|
ends_at: z.string().optional().describe("New end time ISO"),
|
|
416
|
+
requires_approval: z.boolean().optional().describe("Require host approval to join"),
|
|
417
|
+
screening_questions: z.array(z.string()).optional().describe("Screening questions for applicants"),
|
|
415
418
|
},
|
|
416
|
-
async ({ code, sender_id, channel, name, description, og_image, lat, lng, starts_at, ends_at }) => {
|
|
419
|
+
async ({ code, sender_id, channel, name, description, og_image, lat, lng, starts_at, ends_at, requires_approval, screening_questions }) => {
|
|
417
420
|
try {
|
|
418
|
-
const result = await updateEvent({ code, device_id: deriveDeviceId(sender_id, channel), name, description, og_image, lat, lng, starts_at, ends_at });
|
|
421
|
+
const result = await updateEvent({ code, device_id: deriveDeviceId(sender_id, channel), name, description, og_image, lat, lng, starts_at, ends_at, requires_approval, screening_questions });
|
|
419
422
|
return jsonResult(result);
|
|
420
423
|
} catch (e) { return jsonResult({ error: e.message }); }
|
|
421
424
|
}
|
|
@@ -478,6 +481,27 @@ export async function startMcpServer() {
|
|
|
478
481
|
}
|
|
479
482
|
);
|
|
480
483
|
|
|
484
|
+
// ─── antenna_event_message ────────────────────────────────
|
|
485
|
+
|
|
486
|
+
server.tool(
|
|
487
|
+
"antenna_event_message",
|
|
488
|
+
"Send a message to event participants. Only creator or co-host can send. Omit ref to broadcast to all.",
|
|
489
|
+
{
|
|
490
|
+
code: z.string().describe("Event code"),
|
|
491
|
+
sender_id: z.string().describe("The sender's user ID"),
|
|
492
|
+
channel: z.string().describe("Channel name"),
|
|
493
|
+
chat_id: z.string().describe("REQUIRED for notifications"),
|
|
494
|
+
message: z.string().describe("Message to send"),
|
|
495
|
+
ref: z.string().optional().describe("Ref number of specific participant (omit for broadcast)"),
|
|
496
|
+
},
|
|
497
|
+
async ({ code, sender_id, channel, chat_id, message, ref }) => {
|
|
498
|
+
try {
|
|
499
|
+
const result = await sendEventMessage({ code, device_id: deriveDeviceId(sender_id, channel), message, target_ref: ref });
|
|
500
|
+
return jsonResult(result);
|
|
501
|
+
} catch (e) { return jsonResult({ error: e.message }); }
|
|
502
|
+
}
|
|
503
|
+
);
|
|
504
|
+
|
|
481
505
|
const transport = new StdioServerTransport();
|
|
482
506
|
await server.connect(transport);
|
|
483
507
|
}
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -366,6 +366,8 @@ Update event info. Only creator or co-host can update.
|
|
|
366
366
|
- `sender_id`, `channel`: from context
|
|
367
367
|
- `chat_id`: REQUIRED for notifications
|
|
368
368
|
- `name`, `description`, `og_image`, `lat`, `lng`, `starts_at`, `ends_at`: all optional for update (only provided fields change, others stay as-is)
|
|
369
|
+
- `requires_approval`: optional boolean — enable/disable approval requirement
|
|
370
|
+
- `screening_questions`: optional string array — update screening questions
|
|
369
371
|
|
|
370
372
|
### `antenna_event_approve`
|
|
371
373
|
Approve a pending participant. Only creator or co-host.
|
|
@@ -388,6 +390,16 @@ Add a co-host to the event. Only creator can add.
|
|
|
388
390
|
- `chat_id`: REQUIRED for notifications
|
|
389
391
|
- `ref`: participant ref number to promote to co-host
|
|
390
392
|
|
|
393
|
+
### `antenna_event_message`
|
|
394
|
+
Send a message to event participants. Only creator or co-host can send.
|
|
395
|
+
- `code`: event code
|
|
396
|
+
- `sender_id`, `channel`: from context
|
|
397
|
+
- `chat_id`: REQUIRED for notifications
|
|
398
|
+
- `message`: the message text
|
|
399
|
+
- `ref`: optional — ref number of specific participant. Omit to broadcast to all active participants.
|
|
400
|
+
- Use when the host needs to notify participants about logistics, changes, or requests (e.g. "please share your WeChat in your profile").
|
|
401
|
+
- One-way: participants receive the message but cannot reply through this channel.
|
|
402
|
+
|
|
391
403
|
---
|
|
392
404
|
|
|
393
405
|
## Event Behavior Guide
|