antenna-fyi 1.3.5 → 1.3.7
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 +27 -0
- package/lib/hermes-plugin/__init__.py +16 -0
- package/lib/hermes-plugin/plugin.yaml +1 -1
- package/lib/hermes-plugin/schemas.py +17 -0
- package/lib/hermes-plugin/tools.py +45 -2
- package/lib/mcp.js +22 -0
- package/package.json +1 -1
- package/skill/SKILL.md +10 -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
|
@@ -686,3 +686,30 @@ export async function createBindToken({ device_id, purpose, event_code, supabase
|
|
|
686
686
|
: "发送这个链接给用户,在手机浏览器打开即可共享位置。",
|
|
687
687
|
};
|
|
688
688
|
}
|
|
689
|
+
|
|
690
|
+
// ─── sendEventMessage (host → participants) ─────────────────────────
|
|
691
|
+
|
|
692
|
+
export async function sendEventMessage({ code, device_id, message, target_ref, supabaseUrl, supabaseKey }) {
|
|
693
|
+
const sb = getClient(supabaseUrl, supabaseKey);
|
|
694
|
+
const params = {
|
|
695
|
+
p_code: code,
|
|
696
|
+
p_device_id: device_id,
|
|
697
|
+
p_message: message,
|
|
698
|
+
};
|
|
699
|
+
if (target_ref) params.p_target_ref = target_ref;
|
|
700
|
+
const { data, error } = await sb.rpc("send_event_message", params);
|
|
701
|
+
if (error) throw new Error(error.message);
|
|
702
|
+
return data;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// ─── getMyEventMessages ──────────────────────────────────────────────
|
|
706
|
+
|
|
707
|
+
export async function getMyEventMessages({ device_id, supabaseUrl, supabaseKey }) {
|
|
708
|
+
const sb = getClient(supabaseUrl, supabaseKey);
|
|
709
|
+
const { data, error } = await sb.rpc("get_my_event_messages", { p_device_id: device_id });
|
|
710
|
+
if (error) throw new Error(error.message);
|
|
711
|
+
return {
|
|
712
|
+
messages: data || [],
|
|
713
|
+
count: (data || []).length,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
@@ -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()
|
|
@@ -359,3 +359,20 @@ EVENT_ADD_HOST_SCHEMA = {
|
|
|
359
359
|
"required": ["code", "sender_id", "channel", "ref", "chat_id"],
|
|
360
360
|
},
|
|
361
361
|
}
|
|
362
|
+
|
|
363
|
+
EVENT_MESSAGE_SCHEMA = {
|
|
364
|
+
"name": "antenna_event_message",
|
|
365
|
+
"description": "Send a message to event participants. Only creator or co-host can send. Omit ref to broadcast to all.",
|
|
366
|
+
"parameters": {
|
|
367
|
+
"type": "object",
|
|
368
|
+
"properties": {
|
|
369
|
+
"code": {"type": "string", "description": "Event code"},
|
|
370
|
+
"sender_id": {"type": "string"},
|
|
371
|
+
"channel": {"type": "string"},
|
|
372
|
+
"chat_id": {"type": "string", "description": "REQUIRED for notifications. Pass chat/channel ID from message context."},
|
|
373
|
+
"message": {"type": "string", "description": "Message to send to participants"},
|
|
374
|
+
"ref": {"type": "string", "description": "Ref number of specific participant (omit for broadcast)"},
|
|
375
|
+
},
|
|
376
|
+
"required": ["code", "sender_id", "channel", "message", "chat_id"],
|
|
377
|
+
},
|
|
378
|
+
}
|
|
@@ -130,6 +130,13 @@ def handle_scan(params: dict) -> str:
|
|
|
130
130
|
"distance_m": p.get("distance_m") or p.get("dist_meters"),
|
|
131
131
|
})
|
|
132
132
|
|
|
133
|
+
# Persist refs to DB so accept works after restart
|
|
134
|
+
if did and _last_ref_map:
|
|
135
|
+
try:
|
|
136
|
+
sb.rpc("save_scan_refs", {"p_owner": did, "p_refs": _last_ref_map}).execute()
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
|
|
133
140
|
return _ok({
|
|
134
141
|
"profiles": profiles,
|
|
135
142
|
"count": len(others),
|
|
@@ -176,6 +183,14 @@ def handle_accept(params: dict) -> str:
|
|
|
176
183
|
target = params.get("target_device_id")
|
|
177
184
|
if ref and ref in _last_ref_map:
|
|
178
185
|
target = _last_ref_map[ref]
|
|
186
|
+
if not target and ref:
|
|
187
|
+
# DB fallback
|
|
188
|
+
try:
|
|
189
|
+
rr = sb.rpc("resolve_ref", {"p_owner": did, "p_ref": ref}).execute()
|
|
190
|
+
if rr.data:
|
|
191
|
+
target = rr.data
|
|
192
|
+
except Exception:
|
|
193
|
+
pass
|
|
179
194
|
if not target:
|
|
180
195
|
return _ok({"error": "No target. Use 'ref' from scan results or 'target_device_id'."})
|
|
181
196
|
|
|
@@ -251,7 +266,7 @@ def handle_check_matches(params: dict) -> str:
|
|
|
251
266
|
inc_only = []
|
|
252
267
|
for i, m in enumerate(raw_incoming):
|
|
253
268
|
inc_only.append({
|
|
254
|
-
"ref": str(i + 1),
|
|
269
|
+
"ref": str(len(mutual) + i + 1),
|
|
255
270
|
"_device_id": m.get("target_id"),
|
|
256
271
|
"name": m.get("name") or "匿名",
|
|
257
272
|
"emoji": m.get("emoji") or "👤",
|
|
@@ -268,6 +283,19 @@ def handle_check_matches(params: dict) -> str:
|
|
|
268
283
|
if not msgs:
|
|
269
284
|
msgs.append("你接受了一些匹配,但对方还没有回应。耐心等等 ⏳")
|
|
270
285
|
|
|
286
|
+
# Persist refs so accept(ref) resolves correctly
|
|
287
|
+
global _last_ref_map
|
|
288
|
+
_last_ref_map = {}
|
|
289
|
+
for m in mutual:
|
|
290
|
+
_last_ref_map[m["ref"]] = m["_device_id"]
|
|
291
|
+
for m in inc_only:
|
|
292
|
+
_last_ref_map[m["ref"]] = m["_device_id"]
|
|
293
|
+
if did and _last_ref_map:
|
|
294
|
+
try:
|
|
295
|
+
sb.rpc("save_scan_refs", {"p_owner": did, "p_refs": _last_ref_map}).execute()
|
|
296
|
+
except Exception:
|
|
297
|
+
pass
|
|
298
|
+
|
|
271
299
|
return _ok({
|
|
272
300
|
"mutual_matches": mutual,
|
|
273
301
|
"incoming_accepts": inc_only,
|
|
@@ -291,7 +319,7 @@ def handle_pass(params: dict) -> str:
|
|
|
291
319
|
try:
|
|
292
320
|
resp = sb.rpc("resolve_ref", {"p_owner": did, "p_ref": ref}).execute()
|
|
293
321
|
if resp.data:
|
|
294
|
-
target = resp.data
|
|
322
|
+
target = resp.data
|
|
295
323
|
except Exception:
|
|
296
324
|
pass
|
|
297
325
|
if not target:
|
|
@@ -652,3 +680,18 @@ def handle_event_add_host(params: dict) -> str:
|
|
|
652
680
|
"p_code": params["code"], "p_device_id": did, "p_target_ref": params["ref"],
|
|
653
681
|
}).execute()
|
|
654
682
|
return _ok(resp.data or {"error": "add_cohost failed"})
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def handle_event_message(params: dict) -> str:
|
|
686
|
+
"""Send a message to event participants. Only creator or co-host can send."""
|
|
687
|
+
sb = _sb()
|
|
688
|
+
did = _device_id(params["sender_id"], params["channel"], params.get("chat_id"))
|
|
689
|
+
rpc_params = {
|
|
690
|
+
"p_code": params["code"],
|
|
691
|
+
"p_device_id": did,
|
|
692
|
+
"p_message": params["message"],
|
|
693
|
+
}
|
|
694
|
+
if params.get("ref"):
|
|
695
|
+
rpc_params["p_target_ref"] = params["ref"]
|
|
696
|
+
resp = sb.rpc("send_event_message", rpc_params).execute()
|
|
697
|
+
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
|
|
|
@@ -478,6 +479,27 @@ export async function startMcpServer() {
|
|
|
478
479
|
}
|
|
479
480
|
);
|
|
480
481
|
|
|
482
|
+
// ─── antenna_event_message ────────────────────────────────
|
|
483
|
+
|
|
484
|
+
server.tool(
|
|
485
|
+
"antenna_event_message",
|
|
486
|
+
"Send a message to event participants. Only creator or co-host can send. Omit ref to broadcast to all.",
|
|
487
|
+
{
|
|
488
|
+
code: z.string().describe("Event code"),
|
|
489
|
+
sender_id: z.string().describe("The sender's user ID"),
|
|
490
|
+
channel: z.string().describe("Channel name"),
|
|
491
|
+
chat_id: z.string().describe("REQUIRED for notifications"),
|
|
492
|
+
message: z.string().describe("Message to send"),
|
|
493
|
+
ref: z.string().optional().describe("Ref number of specific participant (omit for broadcast)"),
|
|
494
|
+
},
|
|
495
|
+
async ({ code, sender_id, channel, chat_id, message, ref }) => {
|
|
496
|
+
try {
|
|
497
|
+
const result = await sendEventMessage({ code, device_id: deriveDeviceId(sender_id, channel), message, target_ref: ref });
|
|
498
|
+
return jsonResult(result);
|
|
499
|
+
} catch (e) { return jsonResult({ error: e.message }); }
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
|
|
481
503
|
const transport = new StdioServerTransport();
|
|
482
504
|
await server.connect(transport);
|
|
483
505
|
}
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -388,6 +388,16 @@ Add a co-host to the event. Only creator can add.
|
|
|
388
388
|
- `chat_id`: REQUIRED for notifications
|
|
389
389
|
- `ref`: participant ref number to promote to co-host
|
|
390
390
|
|
|
391
|
+
### `antenna_event_message`
|
|
392
|
+
Send a message to event participants. Only creator or co-host can send.
|
|
393
|
+
- `code`: event code
|
|
394
|
+
- `sender_id`, `channel`: from context
|
|
395
|
+
- `chat_id`: REQUIRED for notifications
|
|
396
|
+
- `message`: the message text
|
|
397
|
+
- `ref`: optional — ref number of specific participant. Omit to broadcast to all active participants.
|
|
398
|
+
- Use when the host needs to notify participants about logistics, changes, or requests (e.g. "please share your WeChat in your profile").
|
|
399
|
+
- One-way: participants receive the message but cannot reply through this channel.
|
|
400
|
+
|
|
391
401
|
---
|
|
392
402
|
|
|
393
403
|
## Event Behavior Guide
|