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 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
- const deviceId = deriveDeviceId(params.sender_id, params.channel, params.chat_id);
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: null,
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: null });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-openclaw-plugin",
3
- "version": "1.3.41",
3
+ "version": "1.3.43",
4
4
  "description": "Antenna — agent-mediated nearby people discovery for OpenClaw",
5
5
  "openclaw": {
6
6
  "extensions": [
@@ -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