antenna-openclaw-plugin 1.3.41 → 1.3.43
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/README.md +2 -0
- package/index.ts +24 -3
- package/package.json +1 -1
- package/skills/antenna/SKILL.md +5 -2
package/README.md
CHANGED
|
@@ -83,6 +83,8 @@ Supabase
|
|
|
83
83
|
└── pg_cron cleanup — hourly expired match cleanup
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
Profile writes require the user's Antenna API key from antenna.fyi/me. Agents must pass `api_key` to `antenna_profile(action="set")`; the plugin verifies it and writes to the dashboard-linked `user:<uuid>` profile instead of creating a profile from `channel:sender_id`.
|
|
87
|
+
|
|
86
88
|
## Supported platforms
|
|
87
89
|
|
|
88
90
|
Location auto-detection (OpenClaw parses the coordinates):
|
package/index.ts
CHANGED
|
@@ -83,6 +83,18 @@ function profileSlugCandidate(displayName: string | null | undefined, deviceId:
|
|
|
83
83
|
return `user-${createHash("sha1").update(deviceId).digest("hex").slice(0, 10)}`;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
async function resolveDashboardDeviceId(supabase: SupabaseClient, apiKey: string | null | undefined) {
|
|
87
|
+
if (!apiKey) {
|
|
88
|
+
return { error: "Profile writes require the user's Antenna API key from antenna.fyi/me. Do not create an agent-only profile from sender_id/channel." };
|
|
89
|
+
}
|
|
90
|
+
const { data, error } = await supabase.rpc("verify_api_key", { p_key: apiKey });
|
|
91
|
+
if (error) return { error: error.message };
|
|
92
|
+
if (!data?.valid) return { error: data?.error || "Invalid Antenna API key" };
|
|
93
|
+
const deviceId = data.user_id ? `user:${data.user_id}` : data.device_id;
|
|
94
|
+
if (!deviceId) return { error: "API key verified but did not return a dashboard device_id" };
|
|
95
|
+
return { deviceId, userId: data.user_id, displayName: data.display_name };
|
|
96
|
+
}
|
|
97
|
+
|
|
86
98
|
function isRateLimited(deviceId: string): boolean {
|
|
87
99
|
const now = Date.now();
|
|
88
100
|
const last = _lastScanTime.get(deviceId);
|
|
@@ -431,13 +443,14 @@ export default function register(api: any) {
|
|
|
431
443
|
line3: { type: "string", description: "Third line (what you're looking for)" },
|
|
432
444
|
visible: { type: "boolean", description: "Whether to be visible to others" },
|
|
433
445
|
matching_context: { type: "string", description: "More information / free-form context for AI matching (interests, goals, background, etc.)" },
|
|
446
|
+
api_key: { type: "string", description: "Required for action='set': user's Antenna API key from antenna.fyi/me. Profile writes use the dashboard-linked user:<uuid> profile." },
|
|
434
447
|
},
|
|
435
448
|
required: ["action", "sender_id", "channel", "chat_id"],
|
|
436
449
|
},
|
|
437
450
|
async execute(_id: string, params: any) {
|
|
438
451
|
const cfg = getConfig(api);
|
|
439
452
|
const supabase = getSupabase(cfg);
|
|
440
|
-
|
|
453
|
+
let deviceId = deriveDeviceId(params.sender_id, params.channel, params.chat_id);
|
|
441
454
|
|
|
442
455
|
if (params.action === "get") {
|
|
443
456
|
const { data, error } = await supabase.rpc("get_profile", { p_device_id: deviceId });
|
|
@@ -451,16 +464,22 @@ export default function register(api: any) {
|
|
|
451
464
|
});
|
|
452
465
|
}
|
|
453
466
|
|
|
467
|
+
const resolved = await resolveDashboardDeviceId(supabase, params.api_key);
|
|
468
|
+
if (resolved.error) return ok({ error: resolved.error });
|
|
469
|
+
deviceId = resolved.deviceId!;
|
|
470
|
+
|
|
454
471
|
const { data, error } = await supabase.rpc("upsert_profile", {
|
|
455
472
|
p_device_id: deviceId,
|
|
456
473
|
p_display_name: params.display_name ?? null, p_emoji: params.emoji ?? null,
|
|
457
474
|
p_line1: params.line1 ?? null, p_line2: params.line2 ?? null,
|
|
458
475
|
p_line3: params.line3 ?? null, p_visible: params.visible ?? true,
|
|
459
476
|
...(params.matching_context != null ? { p_matching_context: params.matching_context } : {}),
|
|
460
|
-
p_api_key:
|
|
477
|
+
p_api_key: params.api_key,
|
|
461
478
|
});
|
|
462
479
|
|
|
463
480
|
if (error) return ok({ error: error.message });
|
|
481
|
+
if (data?.error) return ok({ error: data.message || data.error });
|
|
482
|
+
if (data?.device_id) deviceId = data.device_id;
|
|
464
483
|
|
|
465
484
|
// Read back profile to get slug for public page link
|
|
466
485
|
let publicUrl = null;
|
|
@@ -470,7 +489,7 @@ export default function register(api: any) {
|
|
|
470
489
|
let profileSlug = profile?.profile_slug || null;
|
|
471
490
|
if (!profileSlug) {
|
|
472
491
|
const targetSlug = profileSlugCandidate(params.display_name, deviceId);
|
|
473
|
-
const { data: slugResult } = await supabase.rpc("set_profile_slug", { p_device_id: deviceId, p_slug: targetSlug, p_api_key:
|
|
492
|
+
const { data: slugResult } = await supabase.rpc("set_profile_slug", { p_device_id: deviceId, p_slug: targetSlug, p_api_key: params.api_key });
|
|
474
493
|
if (slugResult?.set) profileSlug = targetSlug;
|
|
475
494
|
}
|
|
476
495
|
if (profileSlug) {
|
|
@@ -520,6 +539,8 @@ export default function register(api: any) {
|
|
|
520
539
|
profile: { display_name: data.display_name,
|
|
521
540
|
line1: data.line1, line2: data.line2, line3: data.line3, visible: data.visible },
|
|
522
541
|
public_url: publicUrl,
|
|
542
|
+
api_key_verified: true,
|
|
543
|
+
dashboard_device_id: deviceId,
|
|
523
544
|
archetype: archetypeResult || null,
|
|
524
545
|
next_step: "IMPORTANT: 1) Send the public_url to the user — this is their shareable profile link. If public_url is null, say profile link generation failed and retry profile save. 2) Tell the user their archetype and the personalized reason. 3) Call antenna_bind to generate a GPS link. Do not skip any step.",
|
|
525
546
|
});
|
package/package.json
CHANGED
package/skills/antenna/SKILL.md
CHANGED
|
@@ -25,6 +25,8 @@ Plugin 安装后,agent 应该**主动**开始引导,不要等用户问"怎么用
|
|
|
25
25
|
**第一步:打招呼 + 解释**
|
|
26
26
|
> "嘿,你装了 Antenna--它能帮你发现附近有意思的人。先确认一下,你在 antenna.fyi 注册过了吗?拿到 API key 了吗?有了的话我帮你做张名片,然后看看附近有谁。"
|
|
27
27
|
|
|
28
|
+
**硬约束:** Profile 写入必须通过用户从 antenna.fyi/me 拿到的 API key。`antenna_profile(action="set")` 必须传 `api_key`,tool 会验证 key 并写入 dashboard 绑定的 `user:<uuid>` profile。不要自己拼 `channel:sender_id`,不要在用户拿到 API key 前凭空创建 profile。
|
|
29
|
+
|
|
28
30
|
**第二步:聊天收集 → 生成名片 → 确认**
|
|
29
31
|
|
|
30
32
|
Agent 跟用户聊几句,了解他们是谁、做什么、对什么感兴趣、想认识什么人。然后 agent 自己完成以下工作(不需要用户参与):
|
|
@@ -40,7 +42,7 @@ Agent 跟用户聊几句,了解他们是谁、做什么、对什么感兴趣、
|
|
|
40
42
|
>
|
|
41
43
|
> 这样可以吗?要改哪里告诉我。
|
|
42
44
|
|
|
43
|
-
用户确认后才调 `antenna_profile(action="set")` 保存(matching_context + line1/2/3 + emoji + name 一起存)。
|
|
45
|
+
用户确认后才调 `antenna_profile(action="set", api_key="ant_xxx")` 保存(matching_context + line1/2/3 + emoji + name 一起存)。
|
|
44
46
|
用户要改 → 改完重新预览 → 再确认。
|
|
45
47
|
|
|
46
48
|
**不要跳过确认。名片是展示给别人看的,必须让用户看过才存。**
|
|
@@ -159,7 +161,8 @@ After receiving the nearby profiles, **you decide** who to recommend:
|
|
|
159
161
|
View or update the user's name card.
|
|
160
162
|
- `action`: "get" or "set"
|
|
161
163
|
- `sender_id`, `channel`: from context
|
|
162
|
-
- For "set": `display_name`, `emoji`, `line1`, `line2`, `line3`, `visible`, `matching_context`
|
|
164
|
+
- For "set": `api_key`, `display_name`, `emoji`, `line1`, `line2`, `line3`, `visible`, `matching_context`
|
|
165
|
+
- `api_key` is mandatory for writes. It must be the user's Antenna API key from antenna.fyi/me. The tool writes to the dashboard-linked `user:<uuid>` profile; do not create profiles from `sender_id/channel`.
|
|
163
166
|
|
|
164
167
|
The name card has:
|
|
165
168
|
- **emoji**: a single emoji that represents them
|