antenna-fyi 1.3.12 → 1.3.14

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/core.js CHANGED
@@ -189,6 +189,10 @@ export const PROFILE_FIELDS = {
189
189
  line2: { label: "想认识的人", description: "The kind of people you want to meet", maxLength: 140 },
190
190
  line3: { label: "想要的交流方式", description: "The type of conversations you want", maxLength: 160 },
191
191
  matching_context: { label: "匹配上下文", description: "Agent-generated rich context for better matching (not shown to others)", maxLength: 1000 },
192
+ interest_tags: { label: "兴趣标签", description: "Interest/topic tags shown on the card (up to 8)", maxItems: 8 },
193
+ city: { label: "国家/地区", description: "Country or region" },
194
+ links: { label: "社交链接", description: "Social links shown on the card footer (up to 3)", maxItems: 3 },
195
+ is_active: { label: "状态", description: "Whether the profile is active or quiet" },
192
196
  };
193
197
 
194
198
  // ─── getProfile ──────────────────────────────────────────────────────
@@ -197,7 +201,21 @@ export async function getProfile({ device_id, supabaseUrl, supabaseKey }) {
197
201
  const sb = getClient(supabaseUrl, supabaseKey);
198
202
  const { data, error } = await sb.rpc("get_profile", { p_device_id: device_id });
199
203
  if (error) throw new Error(error.message);
200
- return data || null;
204
+ if (!data) return null;
205
+
206
+ // Unpack matching_context JSON into top-level fields for easier agent consumption
207
+ if (data.matching_context) {
208
+ try {
209
+ const ctx = JSON.parse(data.matching_context);
210
+ data.interest_tags = ctx.interestTags || [];
211
+ data.city = ctx.city || null;
212
+ data.links = ctx.links || [];
213
+ data.is_active = typeof ctx.isActive === "boolean" ? ctx.isActive : true;
214
+ data.archetype_override = ctx.archetypeOverride || null;
215
+ data.context = ctx.context || null;
216
+ } catch {}
217
+ }
218
+ return data;
201
219
  }
202
220
 
203
221
  // ─── setProfile ──────────────────────────────────────────────────────
@@ -211,10 +229,42 @@ export async function setProfile({
211
229
  line3,
212
230
  matching_context,
213
231
  visible = true,
232
+ interest_tags,
233
+ city,
234
+ links,
235
+ profile_slug,
236
+ is_active,
237
+ archetype_override,
214
238
  supabaseUrl,
215
239
  supabaseKey,
216
240
  }) {
217
241
  const sb = getClient(supabaseUrl, supabaseKey);
242
+
243
+ // Pack structured fields into matching_context JSON
244
+ let contextJson = matching_context;
245
+ if (interest_tags || city || links || is_active !== undefined || archetype_override !== undefined) {
246
+ let existing = {};
247
+ // If matching_context is already JSON, parse and merge
248
+ if (matching_context) {
249
+ try { existing = JSON.parse(matching_context); } catch { existing = { context: matching_context }; }
250
+ } else {
251
+ // Read existing matching_context from DB to merge
252
+ try {
253
+ const current = await getProfile({ device_id, supabaseUrl, supabaseKey });
254
+ if (current?.matching_context) {
255
+ try { existing = JSON.parse(current.matching_context); } catch { existing = {}; }
256
+ }
257
+ } catch {}
258
+ }
259
+ if (interest_tags) existing.interestTags = interest_tags;
260
+ if (city) existing.city = city;
261
+ if (links) existing.links = links;
262
+ if (is_active !== undefined) existing.isActive = is_active;
263
+ if (archetype_override !== undefined) existing.archetypeOverride = archetype_override;
264
+ existing.version = existing.version || 1;
265
+ contextJson = JSON.stringify(existing);
266
+ }
267
+
218
268
  const { data, error } = await sb.rpc("upsert_profile", {
219
269
  p_device_id: device_id,
220
270
  p_display_name: display_name || null,
@@ -223,7 +273,7 @@ export async function setProfile({
223
273
  p_line2: line2 || null,
224
274
  p_line3: line3 || null,
225
275
  p_visible: visible,
226
- p_matching_context: matching_context || null,
276
+ p_matching_context: contextJson || null,
227
277
  });
228
278
  if (error) throw new Error(error.message);
229
279
 
package/lib/mcp.js CHANGED
@@ -111,14 +111,17 @@ export async function startMcpServer() {
111
111
  sender_id: z.string().describe("The sender's user ID"),
112
112
  channel: z.string().describe("Channel name"),
113
113
  display_name: z.string().optional().describe("Display name"),
114
- emoji: z.string().optional().describe("Profile emoji (stored but not displayed on card)"),
115
114
  line1: z.string().optional().describe("Personal description — who you are and what you do (max 220 chars)"),
116
115
  line2: z.string().optional().describe("Looking for — the kind of people you want to meet (max 140 chars)"),
117
116
  line3: z.string().optional().describe("Conversation style — the type of conversations you want (max 160 chars)"),
118
117
  matching_context: z.string().optional().describe("Agent-generated rich context for embedding-based matching (not shown to others, max 1000 chars). Generate this FIRST, then derive the three descriptions from it."),
118
+ interest_tags: z.array(z.string()).optional().describe("Interest/topic tags shown on the card (up to 8)"),
119
+ city: z.string().optional().describe("Country or region (e.g. 'United States', 'Beijing')"),
120
+ links: z.array(z.string()).optional().describe("Social links shown on the card footer (up to 3)"),
121
+ is_active: z.boolean().optional().describe("Whether the profile is active or quiet"),
119
122
  visible: z.boolean().optional().default(true),
120
123
  },
121
- async ({ action, sender_id, channel, display_name, emoji, line1, line2, line3, matching_context, visible }) => {
124
+ async ({ action, sender_id, channel, display_name, line1, line2, line3, matching_context, interest_tags, city, links, is_active, visible }) => {
122
125
  const deviceId = deriveDeviceId(sender_id, channel);
123
126
  try {
124
127
  if (action === "get") {
@@ -128,7 +131,7 @@ export async function startMcpServer() {
128
131
  : { profile: null, message: "还没有名片。跟用户聊聊他们是谁、做什么、想认识什么人,然后帮他们创建。", fields: PROFILE_FIELDS };
129
132
  return jsonResult(await withMatchNotifications(deviceId, result));
130
133
  }
131
- const data = await setProfile({ device_id: deviceId, display_name, emoji, line1, line2, line3, matching_context, visible });
134
+ const data = await setProfile({ device_id: deviceId, display_name, line1, line2, line3, matching_context, interest_tags, city, links, is_active, visible });
132
135
  return jsonResult(await withMatchNotifications(deviceId, { saved: true, profile: data }));
133
136
  } catch (e) {
134
137
  return jsonResult({ error: e.message });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antenna-fyi",
3
- "version": "1.3.12",
3
+ "version": "1.3.14",
4
4
  "description": "Antenna \u2014 nearby people discovery. CLI + MCP server + OpenClaw skill & plugin, all in one package.",
5
5
  "type": "module",
6
6
  "bin": {