antenna-fyi 1.2.28 → 1.2.30
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 +94 -30
- package/lib/core.js +32 -57
- package/package.json +1 -1
package/lib/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
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";
|
|
4
4
|
import { createInterface } from "readline";
|
|
5
|
-
import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
5
|
+
import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, unlinkSync, renameSync } from "fs";
|
|
6
6
|
import path from "path";
|
|
7
7
|
import os from "os";
|
|
8
8
|
import { join, dirname, extname } from "path";
|
|
@@ -125,7 +125,7 @@ export async function handleMatches(f) {
|
|
|
125
125
|
for (const m of result.incoming_accepts) {
|
|
126
126
|
console.log(`📩 WANTS TO MEET YOU: ${m.emoji} ${m.name}`);
|
|
127
127
|
if (m.line1) console.log(` ${m.line1}`);
|
|
128
|
-
console.log(` Accept: antenna accept --id ${f.id} --
|
|
128
|
+
console.log(` Accept: antenna accept --id ${f.id} --ref ${m.ref}`);
|
|
129
129
|
console.log();
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -483,6 +483,20 @@ export async function handleWatch(f) {
|
|
|
483
483
|
// Write PID file for health check
|
|
484
484
|
const pidDir = path.join(os.homedir(), '.antenna');
|
|
485
485
|
const pidFile = path.join(pidDir, 'watch.pid');
|
|
486
|
+
|
|
487
|
+
// Check for existing watch process
|
|
488
|
+
if (existsSync(pidFile)) {
|
|
489
|
+
try {
|
|
490
|
+
const existingPid = parseInt(readFileSync(pidFile, 'utf8').trim());
|
|
491
|
+
process.kill(existingPid, 0); // throws if not running
|
|
492
|
+
console.error(`❌ Watch already running (PID ${existingPid}). Kill it first or remove ${pidFile}`);
|
|
493
|
+
process.exit(1);
|
|
494
|
+
} catch (e) {
|
|
495
|
+
if (e.code !== 'ESRCH') { /* process exists */ throw e; }
|
|
496
|
+
// Process not running, stale PID file — continue
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
486
500
|
try {
|
|
487
501
|
if (!existsSync(pidDir)) mkdirSync(pidDir, { recursive: true });
|
|
488
502
|
writeFileSync(pidFile, String(process.pid));
|
|
@@ -492,25 +506,52 @@ export async function handleWatch(f) {
|
|
|
492
506
|
process.on('SIGTERM', () => { try { unlinkSync(pidFile); } catch {} process.exit(0); });
|
|
493
507
|
|
|
494
508
|
const sb = getClient();
|
|
495
|
-
const notified = new Set();
|
|
496
509
|
|
|
497
|
-
//
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
// Verify gateway is running
|
|
510
|
+
// Persist notified set to disk
|
|
511
|
+
const notifiedFile = path.join(pidDir, 'notified.json');
|
|
512
|
+
|
|
513
|
+
function loadNotified() {
|
|
502
514
|
try {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
515
|
+
const data = JSON.parse(readFileSync(notifiedFile, 'utf8'));
|
|
516
|
+
const now = Date.now();
|
|
517
|
+
const TTL = 48 * 60 * 60 * 1000;
|
|
518
|
+
const set = new Set();
|
|
519
|
+
for (const [key, ts] of Object.entries(data)) {
|
|
520
|
+
if (now - ts < TTL) set.add(key);
|
|
521
|
+
}
|
|
522
|
+
return set;
|
|
523
|
+
} catch { return new Set(); }
|
|
524
|
+
}
|
|
507
525
|
|
|
508
|
-
|
|
526
|
+
function saveNotified(set) {
|
|
527
|
+
const now = Date.now();
|
|
528
|
+
const obj = {};
|
|
529
|
+
for (const key of set) obj[key] = now;
|
|
530
|
+
const tmp = notifiedFile + '.tmp';
|
|
531
|
+
writeFileSync(tmp, JSON.stringify(obj));
|
|
532
|
+
renameSync(tmp, notifiedFile);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const notified = loadNotified();
|
|
536
|
+
|
|
537
|
+
// Detect local agent frameworks for push notifications
|
|
538
|
+
// Push to ALL available frameworks, not just one
|
|
539
|
+
const pushMethods = new Set();
|
|
540
|
+
if (f.push) {
|
|
541
|
+
f.push.split(",").forEach(m => pushMethods.add(m.trim()));
|
|
542
|
+
} else {
|
|
543
|
+
try {
|
|
544
|
+
execSync("which openclaw", { stdio: "pipe" });
|
|
545
|
+
try {
|
|
546
|
+
execSync("openclaw gateway health", { stdio: "pipe", timeout: 5000 });
|
|
547
|
+
pushMethods.add("openclaw");
|
|
548
|
+
} catch { /* gateway not running */ }
|
|
549
|
+
} catch { /* openclaw not installed */ }
|
|
509
550
|
try {
|
|
510
551
|
execSync("which hermes", { stdio: "pipe" });
|
|
511
552
|
try {
|
|
512
553
|
execSync("hermes gateway status", { stdio: "pipe", timeout: 5000 });
|
|
513
|
-
|
|
554
|
+
pushMethods.add("hermes");
|
|
514
555
|
} catch { /* hermes gateway not running */ }
|
|
515
556
|
} catch { /* hermes not installed */ }
|
|
516
557
|
}
|
|
@@ -530,10 +571,8 @@ export async function handleWatch(f) {
|
|
|
530
571
|
};
|
|
531
572
|
|
|
532
573
|
_log(`📡 Watching for new matches for ${id}...`);
|
|
533
|
-
if (
|
|
534
|
-
_log(` 🔗
|
|
535
|
-
} else if (pushMethod === "hermes") {
|
|
536
|
-
_log(` 🔗 Detected Hermes — will push notifications to your channel.`);
|
|
574
|
+
if (pushMethods.size > 0) {
|
|
575
|
+
_log(` 🔗 Push targets: ${[...pushMethods].join(", ")}`);
|
|
537
576
|
} else {
|
|
538
577
|
_log(` ℹ️ No agent framework detected — notifications will print here.`);
|
|
539
578
|
}
|
|
@@ -546,9 +585,9 @@ export async function handleWatch(f) {
|
|
|
546
585
|
async function pushNotify(message) {
|
|
547
586
|
_log(message); // always print to terminal
|
|
548
587
|
|
|
549
|
-
|
|
588
|
+
// Push to ALL available frameworks
|
|
589
|
+
if (pushMethods.has("openclaw")) {
|
|
550
590
|
try {
|
|
551
|
-
// Try to get chat_id from DB for direct message send
|
|
552
591
|
const profile = await getProfile({ device_id: id });
|
|
553
592
|
const chatId = profile?.last_chat_id;
|
|
554
593
|
const parts = id.split(":");
|
|
@@ -565,7 +604,8 @@ export async function handleWatch(f) {
|
|
|
565
604
|
);
|
|
566
605
|
}
|
|
567
606
|
} catch (err) { /* terminal output is the fallback */ }
|
|
568
|
-
}
|
|
607
|
+
}
|
|
608
|
+
if (pushMethods.has("hermes")) {
|
|
569
609
|
try {
|
|
570
610
|
execSync(
|
|
571
611
|
`hermes cron create --name "Antenna notification" --run-now --once --message ${JSON.stringify(message)}`,
|
|
@@ -580,19 +620,21 @@ export async function handleWatch(f) {
|
|
|
580
620
|
if (initial.mutual_matches?.length) {
|
|
581
621
|
_log(`🎉 You have ${initial.mutual_matches.length} mutual match(es)!`);
|
|
582
622
|
for (const m of initial.mutual_matches) {
|
|
583
|
-
const key = `mutual:${m.
|
|
623
|
+
const key = `mutual:${m._device_id}`;
|
|
584
624
|
notified.add(key);
|
|
585
625
|
_log(` ${m.emoji || "👤"} ${m.name}${m.their_contact ? " — contact: " + m.their_contact : ""}`);
|
|
586
626
|
}
|
|
627
|
+
saveNotified(notified);
|
|
587
628
|
_log();
|
|
588
629
|
}
|
|
589
630
|
if (initial.incoming_accepts?.length) {
|
|
590
631
|
_log(`📩 ${initial.incoming_accepts.length} person(s) want to meet you!`);
|
|
591
632
|
for (const m of initial.incoming_accepts) {
|
|
592
|
-
const key = `incoming:${m.
|
|
633
|
+
const key = `incoming:${m._device_id}`;
|
|
593
634
|
notified.add(key);
|
|
594
635
|
_log(` ${m.emoji || "👤"} ${m.name} — ${m.line1 || ""}`);
|
|
595
636
|
}
|
|
637
|
+
saveNotified(notified);
|
|
596
638
|
_log();
|
|
597
639
|
}
|
|
598
640
|
|
|
@@ -618,26 +660,30 @@ export async function handleWatch(f) {
|
|
|
618
660
|
|
|
619
661
|
// Check if mutual
|
|
620
662
|
const matches = await checkMatches({ device_id: id });
|
|
621
|
-
const isMutual = matches.mutual_matches?.some(m => m.
|
|
663
|
+
const isMutual = matches.mutual_matches?.some(m => m._device_id === row.device_id_a);
|
|
622
664
|
|
|
623
665
|
if (isMutual) {
|
|
624
666
|
const mutualKey = `mutual:${row.device_id_a}`;
|
|
625
667
|
notified.add(mutualKey);
|
|
668
|
+
saveNotified(notified);
|
|
626
669
|
const contact = row.contact_info_a;
|
|
627
670
|
pushNotify(`🎉 MUTUAL MATCH! ${emoji} ${name} also accepted you!${contact ? " Contact: " + contact : ""}`);
|
|
628
671
|
} else {
|
|
629
|
-
|
|
672
|
+
notified.add(key);
|
|
673
|
+
saveNotified(notified);
|
|
674
|
+
pushNotify(`📩 ${emoji} ${name} wants to meet you! Use 'antenna matches --id ${id}' to respond.`);
|
|
630
675
|
}
|
|
631
676
|
}
|
|
632
677
|
|
|
633
678
|
// I accepted someone and they also accepted me
|
|
634
679
|
if (row.device_id_a === id) {
|
|
635
680
|
const matches = await checkMatches({ device_id: id });
|
|
636
|
-
const mutual = matches.mutual_matches?.find(m => m.
|
|
681
|
+
const mutual = matches.mutual_matches?.find(m => m._device_id === row.device_id_b);
|
|
637
682
|
if (mutual) {
|
|
638
683
|
const mutualKey = `mutual:${row.device_id_b}`;
|
|
639
684
|
if (!notified.has(mutualKey)) {
|
|
640
685
|
notified.add(mutualKey);
|
|
686
|
+
saveNotified(notified);
|
|
641
687
|
pushNotify(`🎉 MUTUAL MATCH! ${mutual.emoji || "👤"} ${mutual.name}!${mutual.their_contact ? " Contact: " + mutual.their_contact : ""}`);
|
|
642
688
|
}
|
|
643
689
|
}
|
|
@@ -649,12 +695,28 @@ export async function handleWatch(f) {
|
|
|
649
695
|
)
|
|
650
696
|
.subscribe((status) => {
|
|
651
697
|
if (status === "SUBSCRIBED") {
|
|
698
|
+
retryCount = 0;
|
|
652
699
|
_log("✅ Connected — listening for matches in real-time.");
|
|
653
700
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
|
|
654
|
-
|
|
701
|
+
retryCount++;
|
|
702
|
+
if (retryCount < MAX_FAST_RETRIES) {
|
|
703
|
+
const delay = Math.min(1000 * Math.pow(2, retryCount), 60000);
|
|
704
|
+
_log(`⚠️ Connection issue (${status}), retry #${retryCount} in ${Math.round(delay/1000)}s...`);
|
|
705
|
+
setTimeout(() => { try { channel.subscribe(); } catch {} }, delay);
|
|
706
|
+
} else if (retryCount === MAX_FAST_RETRIES) {
|
|
707
|
+
_log(`⚠️ Connection unstable after ${MAX_FAST_RETRIES} retries. Switching to ${COOLDOWN_MS/1000}s cooldown. Polling fallback still active.`);
|
|
708
|
+
setTimeout(() => { try { channel.subscribe(); } catch {} }, COOLDOWN_MS);
|
|
709
|
+
} else {
|
|
710
|
+
setTimeout(() => { try { channel.subscribe(); } catch {} }, COOLDOWN_MS);
|
|
711
|
+
}
|
|
655
712
|
}
|
|
656
713
|
});
|
|
657
714
|
|
|
715
|
+
// Reconnection state for matches channel
|
|
716
|
+
let retryCount = 0;
|
|
717
|
+
const MAX_FAST_RETRIES = 20;
|
|
718
|
+
const COOLDOWN_MS = 5 * 60 * 1000; // 5 min
|
|
719
|
+
|
|
658
720
|
// Subscribe to event_participants changes (approval notifications)
|
|
659
721
|
sb
|
|
660
722
|
.channel("antenna-cli-watch-events")
|
|
@@ -704,16 +766,18 @@ export async function handleWatch(f) {
|
|
|
704
766
|
try {
|
|
705
767
|
const result = await checkMatches({ device_id: id });
|
|
706
768
|
for (const m of (result.mutual_matches || [])) {
|
|
707
|
-
const key = `mutual:${m.
|
|
769
|
+
const key = `mutual:${m._device_id}`;
|
|
708
770
|
if (!notified.has(key)) {
|
|
709
771
|
notified.add(key);
|
|
772
|
+
saveNotified(notified);
|
|
710
773
|
pushNotify(`🎉 MUTUAL MATCH! ${m.emoji || "👤"} ${m.name}!${m.their_contact ? " Contact: " + m.their_contact : ""}`);
|
|
711
774
|
}
|
|
712
775
|
}
|
|
713
776
|
for (const m of (result.incoming_accepts || [])) {
|
|
714
|
-
const key = `incoming:${m.
|
|
777
|
+
const key = `incoming:${m._device_id}`;
|
|
715
778
|
if (!notified.has(key)) {
|
|
716
779
|
notified.add(key);
|
|
780
|
+
saveNotified(notified);
|
|
717
781
|
pushNotify(`📩 ${m.emoji || "👤"} ${m.name} wants to meet you!`);
|
|
718
782
|
}
|
|
719
783
|
}
|
|
@@ -741,7 +805,7 @@ Usage:
|
|
|
741
805
|
antenna matches --id telegram:123
|
|
742
806
|
antenna discover --id telegram:123
|
|
743
807
|
antenna event --create --name 'AI Meetup' [--desc '...'] [--og-image 'url'] [--requires-approval] [--screening-questions 'Q1|Q2'] | --join --code abc123 | --scan --code abc123 | --end --code abc123 --id telegram:123 | --upload-image --code abc123 --file /path/to/image.png | --update --code abc123 --name 'New Name' | --approve --code abc123 --ref 1 | --reject --code abc123 --ref 1 | --add-host --code abc123 --ref 1
|
|
744
|
-
antenna watch --id telegram:123 Watch for new matches in real-time (Ctrl+C to stop)
|
|
808
|
+
antenna watch --id telegram:123 [--push hermes|openclaw|terminal] Watch for new matches in real-time (Ctrl+C to stop)
|
|
745
809
|
antenna bind --id telegram:123
|
|
746
810
|
antenna serve Start MCP server (stdio transport)
|
|
747
811
|
antenna setup Interactive profile setup [--id telegram:123]
|
package/lib/core.js
CHANGED
|
@@ -79,20 +79,12 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
|
|
|
79
79
|
lat = loc.lat;
|
|
80
80
|
lng = loc.lng;
|
|
81
81
|
} else {
|
|
82
|
-
return { count: 0, radius_m, profiles: [], message: "
|
|
82
|
+
return { count: 0, radius_m, profiles: [], message: "还没有位置信息。请先用 'antenna checkin' 分享位置,或通过链接分享位置。" };
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const fuzzy = fuzzyCoord(lat, lng);
|
|
87
87
|
|
|
88
|
-
if (device_id) {
|
|
89
|
-
await sb.rpc("upsert_profile_location", {
|
|
90
|
-
p_device_id: device_id,
|
|
91
|
-
p_lng: fuzzy.lng,
|
|
92
|
-
p_lat: fuzzy.lat,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
88
|
const { data, error } = await sb.rpc("nearby_profiles", {
|
|
97
89
|
p_lat: fuzzy.lat,
|
|
98
90
|
p_lng: fuzzy.lng,
|
|
@@ -323,60 +315,43 @@ export async function checkin({ lat, lng, device_id, supabaseUrl, supabaseKey })
|
|
|
323
315
|
export async function checkMatches({ device_id, supabaseUrl, supabaseKey }) {
|
|
324
316
|
const sb = getClient(supabaseUrl, supabaseKey);
|
|
325
317
|
|
|
326
|
-
|
|
318
|
+
// Single RPC with JOINed profiles — no N+1
|
|
319
|
+
const { data, error } = await sb.rpc("get_my_matches_with_profiles", { p_device_id: device_id });
|
|
327
320
|
if (error) throw new Error(error.message);
|
|
328
321
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
their_contact: reverse.contact_info_a || null,
|
|
354
|
-
you_shared: match.contact_info_a || null,
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Incoming only
|
|
360
|
-
const incomingAccepts = [];
|
|
361
|
-
for (const match of incomingMatches) {
|
|
362
|
-
const iAccepted = myMatches.find((m) => m.device_id_b === match.device_id_a);
|
|
363
|
-
if (!iAccepted) {
|
|
364
|
-
const profile = await getProfile({ device_id: match.device_id_a, supabaseUrl, supabaseKey });
|
|
365
|
-
incomingAccepts.push({
|
|
366
|
-
device_id: match.device_id_a,
|
|
367
|
-
name: profile?.display_name || "匿名",
|
|
368
|
-
emoji: profile?.emoji || "👤",
|
|
369
|
-
line1: profile?.line1,
|
|
370
|
-
line2: profile?.line2,
|
|
371
|
-
line3: profile?.line3,
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
}
|
|
322
|
+
const raw = data || { mutual_matches: [], incoming_accepts: [] };
|
|
323
|
+
|
|
324
|
+
// Add refs and rename target_id to _device_id (internal only)
|
|
325
|
+
const mutualMatches = (raw.mutual_matches || []).map((m, i) => ({
|
|
326
|
+
ref: String(i + 1),
|
|
327
|
+
_device_id: m.target_id,
|
|
328
|
+
name: m.name || "匿名",
|
|
329
|
+
emoji: m.emoji || "👤",
|
|
330
|
+
line1: m.line1,
|
|
331
|
+
line2: m.line2,
|
|
332
|
+
line3: m.line3,
|
|
333
|
+
their_contact: m.their_contact || null,
|
|
334
|
+
you_shared: m.you_shared || null,
|
|
335
|
+
}));
|
|
336
|
+
|
|
337
|
+
const incomingAccepts = (raw.incoming_accepts || []).map((m, i) => ({
|
|
338
|
+
ref: String(i + 1),
|
|
339
|
+
_device_id: m.target_id,
|
|
340
|
+
name: m.name || "匿名",
|
|
341
|
+
emoji: m.emoji || "👤",
|
|
342
|
+
line1: m.line1,
|
|
343
|
+
line2: m.line2,
|
|
344
|
+
line3: m.line3,
|
|
345
|
+
}));
|
|
375
346
|
|
|
376
347
|
const messages = [];
|
|
377
348
|
if (mutualMatches.length > 0) messages.push(`${mutualMatches.length} 个双向匹配!可以交换联系方式了`);
|
|
378
349
|
if (incomingAccepts.length > 0) messages.push(`${incomingAccepts.length} 个人想认识你,等你回应`);
|
|
379
|
-
if (messages.length === 0
|
|
350
|
+
if (messages.length === 0 && mutualMatches.length === 0 && incomingAccepts.length === 0) {
|
|
351
|
+
messages.push("目前没有进行中的匹配。");
|
|
352
|
+
} else if (messages.length === 0) {
|
|
353
|
+
messages.push("你接受了一些匹配,但对方还没有回应。耐心等等 ⏳");
|
|
354
|
+
}
|
|
380
355
|
|
|
381
356
|
return {
|
|
382
357
|
mutual_matches: mutualMatches,
|