antenna-fyi 1.3.10 โ 1.3.12
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/bin/antenna.js +3 -0
- package/lib/cli.js +94 -19
- package/lib/core.js +19 -0
- package/lib/mcp.js +11 -8
- package/package.json +1 -1
package/bin/antenna.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
handlePass,
|
|
14
14
|
handleWatch,
|
|
15
15
|
handleSetup,
|
|
16
|
+
handleConfig,
|
|
16
17
|
handleStatus,
|
|
17
18
|
handleInstallSkill,
|
|
18
19
|
handleInstallPlugin,
|
|
@@ -51,6 +52,8 @@ async function main() {
|
|
|
51
52
|
}
|
|
52
53
|
case "setup":
|
|
53
54
|
return handleSetup(f);
|
|
55
|
+
case "config":
|
|
56
|
+
return handleConfig(f);
|
|
54
57
|
case "status":
|
|
55
58
|
return handleStatus(f);
|
|
56
59
|
case "install-skill":
|
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, sendEventMessage, getMyEventMessages, 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, verifyApiKey } 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";
|
|
@@ -31,12 +31,13 @@ export function parseFlags(args) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export async function handleScan(f) {
|
|
34
|
-
|
|
34
|
+
const _scanId = resolveId(f);
|
|
35
|
+
if (!f.lat && !f.lng && !_scanId) return console.error("Usage: antenna scan --lat 39.99 --lng 116.48 [--radius 500] (max 1000) [--id <platform>:<user_id>]\n Or just: antenna scan --id <platform>:<user_id> (uses saved location from GPS bind)");
|
|
35
36
|
const result = await scan({
|
|
36
37
|
lat: f.lat ? +f.lat : undefined,
|
|
37
38
|
lng: f.lng ? +f.lng : undefined,
|
|
38
39
|
radius_m: +(f.radius || 500),
|
|
39
|
-
device_id:
|
|
40
|
+
device_id: _scanId || null,
|
|
40
41
|
});
|
|
41
42
|
if (result.count === 0) return console.log(result.message || "๐ก No one nearby");
|
|
42
43
|
if (result.global) {
|
|
@@ -64,10 +65,11 @@ export async function handleScan(f) {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export async function handleProfile(f) {
|
|
67
|
-
|
|
68
|
+
const id = resolveId(f);
|
|
69
|
+
if (!id) return console.error("Usage: antenna profile --id <platform>:<user_id> [--name Yi --emoji ๐ฆฆ --line1 '...' --line2 '...' --line3 '...']");
|
|
68
70
|
if (f.name || f.line1 || f.line2 || f.line3 || f.visible !== undefined || f.hide !== undefined) {
|
|
69
71
|
const visible = f.hide ? false : (f.visible !== undefined ? f.visible === 'true' || f.visible === true : undefined);
|
|
70
|
-
const payload = { device_id:
|
|
72
|
+
const payload = { device_id: id };
|
|
71
73
|
if (f.name) payload.display_name = f.name;
|
|
72
74
|
if (f.emoji) payload.emoji = f.emoji;
|
|
73
75
|
if (f.line1 !== undefined) payload.line1 = f.line1;
|
|
@@ -78,7 +80,7 @@ export async function handleProfile(f) {
|
|
|
78
80
|
console.log("โ
Profile saved");
|
|
79
81
|
console.log(JSON.stringify(data, null, 2));
|
|
80
82
|
} else {
|
|
81
|
-
const data = await getProfile({ device_id:
|
|
83
|
+
const data = await getProfile({ device_id: id });
|
|
82
84
|
if (!data) return console.log("No profile yet. Create one with --name and --line1/2/3");
|
|
83
85
|
console.log(`${data.emoji || "๐ค"} ${data.display_name || "Anonymous"}`);
|
|
84
86
|
if (data.line1) console.log(` ${data.line1}`);
|
|
@@ -88,9 +90,10 @@ export async function handleProfile(f) {
|
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
export async function handleAccept(f) {
|
|
91
|
-
|
|
93
|
+
const id = resolveId(f);
|
|
94
|
+
if (!id || (!f.target && !f.ref)) return console.error("Usage: antenna accept --id <platform>:<user_id> --ref 1 [--contact 'WeChat: yi']\n antenna accept --id <platform>:<user_id> --target <ref_or_device_id> [--contact 'WeChat: yi']");
|
|
92
95
|
const result = await accept({
|
|
93
|
-
device_id:
|
|
96
|
+
device_id: id,
|
|
94
97
|
target_device_id: f.target || null,
|
|
95
98
|
ref: f.ref || null,
|
|
96
99
|
contact_info: f.contact,
|
|
@@ -100,18 +103,20 @@ export async function handleAccept(f) {
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
export async function handleCheckin(f) {
|
|
103
|
-
|
|
106
|
+
const id = resolveId(f);
|
|
107
|
+
if (!id || !f.lat || !f.lng) return console.error("Usage: antenna checkin --id <platform>:<user_id> --lat 39.99 --lng 116.48 [--place 'ไธ้ๅฑฏ']");
|
|
104
108
|
const result = await checkin({
|
|
105
109
|
lat: +f.lat,
|
|
106
110
|
lng: +f.lng,
|
|
107
|
-
device_id:
|
|
111
|
+
device_id: id,
|
|
108
112
|
});
|
|
109
113
|
console.log(result.checked_in ? "โ
" + result.message : "โ " + result.message);
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
export async function handleMatches(f) {
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
const id = resolveId(f);
|
|
118
|
+
if (!id) return console.error("Usage: antenna matches --id <platform>:<user_id>");
|
|
119
|
+
const result = await checkMatches({ device_id: id });
|
|
115
120
|
if (!result.mutual_matches.length && !result.incoming_accepts.length) {
|
|
116
121
|
return console.log(result.message);
|
|
117
122
|
}
|
|
@@ -130,8 +135,9 @@ export async function handleMatches(f) {
|
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
export async function handleDiscover(f) {
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
const id = resolveId(f);
|
|
139
|
+
if (!id) return console.error("Usage: antenna discover --id <platform>:<user_id>");
|
|
140
|
+
const result = await discover({ device_id: id });
|
|
135
141
|
if (result.count === 0) return console.log(result.message || "๐ No global recommendation available right now.");
|
|
136
142
|
console.log(`๐ Global discover:\n`);
|
|
137
143
|
result.profiles.forEach((p) => {
|
|
@@ -145,6 +151,7 @@ export async function handleDiscover(f) {
|
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
export async function handleEvent(f) {
|
|
154
|
+
f.id = f.id || resolveId(f);
|
|
148
155
|
const sub = f._?.[0] || Object.keys(f).find(k => ["create", "join", "scan", "end", "checkin", "upload-image"].includes(k));
|
|
149
156
|
|
|
150
157
|
if (f['upload-image']) {
|
|
@@ -180,7 +187,8 @@ export async function handleEvent(f) {
|
|
|
180
187
|
|
|
181
188
|
if (f.create || (!f.join && !f.scan && !f.end && !f.update && !f.approve && !f.reject && !f['add-host'] && f.name)) {
|
|
182
189
|
if (!f.name) return console.error("Usage: antenna event --create --name 'AI Meetup' --id <platform>:<user_id> --starts-at '2026-04-19T14:00' --ends-at '2026-04-19T18:00' [--lat 34.05 --lng -118.25] [--desc 'description'] [--og-image 'url'] [--requires-approval] [--screening-questions 'Q1|Q2']");
|
|
183
|
-
|
|
190
|
+
const id = resolveId(f);
|
|
191
|
+
if (!id) return console.error("โ --id is required (e.g. --id <platform>:<user_id>). Creator identity needed to manage the event.");
|
|
184
192
|
if (!f['starts-at'] || !f['ends-at']) return console.error("โ --starts-at and --ends-at are required. Example: --starts-at '2026-04-19T14:00' --ends-at '2026-04-19T18:00'");
|
|
185
193
|
const result = await createEvent({ name: f.name, device_id: f.id, lat: f.lat ? +f.lat : undefined, lng: f.lng ? +f.lng : undefined, starts_at: f['starts-at'], ends_at: f['ends-at'], description: f.desc || undefined, og_image: f['og-image'] || undefined, requires_approval: f['requires-approval'] === true || f['requires-approval'] === 'true' || undefined, screening_questions: f['screening-questions'] ? f['screening-questions'].split('|') : undefined });
|
|
186
194
|
console.log(`\n๐ Event created!\n`);
|
|
@@ -294,8 +302,9 @@ export async function handleEvent(f) {
|
|
|
294
302
|
}
|
|
295
303
|
|
|
296
304
|
export async function handleBind(f) {
|
|
297
|
-
|
|
298
|
-
|
|
305
|
+
const id = resolveId(f);
|
|
306
|
+
if (!id) return console.error("Usage: antenna bind --id <platform>:<user_id>");
|
|
307
|
+
const result = await createBindToken({ device_id: id });
|
|
299
308
|
console.log("\n๐ GPS Binding Link:\n");
|
|
300
309
|
console.log(` ${result.url}\n`);
|
|
301
310
|
console.log("Send this to the user. Opening it on their phone will share GPS with their agent.");
|
|
@@ -303,10 +312,11 @@ export async function handleBind(f) {
|
|
|
303
312
|
}
|
|
304
313
|
|
|
305
314
|
export async function handlePass(f) {
|
|
306
|
-
|
|
315
|
+
const id = resolveId(f);
|
|
316
|
+
if (!id) return console.error("Usage: antenna pass --id <platform>:<user_id> --target <ref_or_device_id>");
|
|
307
317
|
if (!f.target && !f.ref) return console.error("Usage: antenna pass --id <platform>:<user_id> --target <ref_or_device_id> (or --ref 1)");
|
|
308
318
|
const result = await passUser({
|
|
309
|
-
device_id:
|
|
319
|
+
device_id: id,
|
|
310
320
|
target_device_id: f.target,
|
|
311
321
|
ref: f.ref,
|
|
312
322
|
});
|
|
@@ -347,6 +357,70 @@ export async function handleSetup(f) {
|
|
|
347
357
|
console.log();
|
|
348
358
|
}
|
|
349
359
|
|
|
360
|
+
// โโโ Config file helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
361
|
+
|
|
362
|
+
const CONFIG_DIR = path.join(os.homedir(), '.antenna');
|
|
363
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
364
|
+
|
|
365
|
+
export function loadConfig() {
|
|
366
|
+
try {
|
|
367
|
+
if (existsSync(CONFIG_FILE)) {
|
|
368
|
+
return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
369
|
+
}
|
|
370
|
+
} catch {}
|
|
371
|
+
return {};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function saveConfig(config) {
|
|
375
|
+
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
376
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Resolve device_id: --id flag > config file > null */
|
|
380
|
+
function resolveId(f) {
|
|
381
|
+
if (f.id) return f.id;
|
|
382
|
+
const config = loadConfig();
|
|
383
|
+
if (config.device_id) return config.device_id;
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export async function handleConfig(f) {
|
|
388
|
+
if (f.key) {
|
|
389
|
+
// Verify the key against Supabase
|
|
390
|
+
console.log('\n๐ก Verifying API key...');
|
|
391
|
+
try {
|
|
392
|
+
const result = await verifyApiKey({ key: f.key });
|
|
393
|
+
if (!result.valid) {
|
|
394
|
+
console.error(`\nโ ${result.error || 'Invalid API key'}`);
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
const config = loadConfig();
|
|
398
|
+
config.key = f.key;
|
|
399
|
+
config.device_id = result.device_id;
|
|
400
|
+
config.display_name = result.display_name;
|
|
401
|
+
saveConfig(config);
|
|
402
|
+
console.log(`\nโ
Authenticated as ${result.display_name || 'Antenna user'}`);
|
|
403
|
+
console.log(` Device ID: ${result.device_id}`);
|
|
404
|
+
console.log(` Config saved to ~/.antenna/config.json\n`);
|
|
405
|
+
} catch (e) {
|
|
406
|
+
console.error(`\nโ Failed to verify key: ${e.message}`);
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
// Show current config
|
|
411
|
+
const config = loadConfig();
|
|
412
|
+
if (config.key) {
|
|
413
|
+
console.log('\n๐ก Antenna Config\n');
|
|
414
|
+
console.log(` Key: ${config.key.slice(0, 10)}...${config.key.slice(-4)}`);
|
|
415
|
+
console.log(` Device ID: ${config.device_id || '(unknown)'}`);
|
|
416
|
+
console.log(` Name: ${config.display_name || '(unknown)'}\n`);
|
|
417
|
+
} else {
|
|
418
|
+
console.log('\n๐ก No API key configured.');
|
|
419
|
+
console.log(' Run: antenna config --key <your-api-key>\n');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
350
424
|
export async function handleStatus(f) {
|
|
351
425
|
const supabaseUrl = process.env.ANTENNA_SUPABASE_URL || process.env.ANTENNA_URL || "https://bcudjloikmpcqwcptuyd.supabase.co";
|
|
352
426
|
console.log("\n๐ก Antenna Status\n");
|
|
@@ -907,6 +981,7 @@ Usage:
|
|
|
907
981
|
antenna bind --id <platform>:<user_id>
|
|
908
982
|
antenna serve Start MCP server (stdio transport)
|
|
909
983
|
antenna setup Interactive profile setup [--id <platform>:<user_id>]
|
|
984
|
+
antenna config Configure API key [--key <ant_...>]
|
|
910
985
|
antenna status Show config & status [--id <platform>:<user_id>]
|
|
911
986
|
antenna install-skill Install SKILL.md (detects OpenClaw + Hermes)
|
|
912
987
|
antenna install-plugin Copy OpenClaw plugin template to cwd
|
package/lib/core.js
CHANGED
|
@@ -181,6 +181,16 @@ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, s
|
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// โโโ Profile field metadata (self-describing API) โโโโโโโโโโโโโโโโโโโ
|
|
185
|
+
|
|
186
|
+
export const PROFILE_FIELDS = {
|
|
187
|
+
display_name: { label: "ๆพ็คบๅ็งฐ", description: "How you want to be called" },
|
|
188
|
+
line1: { label: "ไธชไบบๆ่ฟฐ", description: "Who you are and what you do", maxLength: 220, required: true },
|
|
189
|
+
line2: { label: "ๆณ่ฎค่ฏ็ไบบ", description: "The kind of people you want to meet", maxLength: 140 },
|
|
190
|
+
line3: { label: "ๆณ่ฆ็ไบคๆตๆนๅผ", description: "The type of conversations you want", maxLength: 160 },
|
|
191
|
+
matching_context: { label: "ๅน้
ไธไธๆ", description: "Agent-generated rich context for better matching (not shown to others)", maxLength: 1000 },
|
|
192
|
+
};
|
|
193
|
+
|
|
184
194
|
// โโโ getProfile โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
185
195
|
|
|
186
196
|
export async function getProfile({ device_id, supabaseUrl, supabaseKey }) {
|
|
@@ -725,3 +735,12 @@ export async function getMyEventMessages({ device_id, supabaseUrl, supabaseKey }
|
|
|
725
735
|
count: (data || []).length,
|
|
726
736
|
};
|
|
727
737
|
}
|
|
738
|
+
|
|
739
|
+
// โโโ verifyApiKey โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
740
|
+
|
|
741
|
+
export async function verifyApiKey({ key, supabaseUrl, supabaseKey }) {
|
|
742
|
+
const sb = getClient(supabaseUrl, supabaseKey);
|
|
743
|
+
const { data, error } = await sb.rpc("verify_api_key", { p_key: key });
|
|
744
|
+
if (error) throw new Error(error.message);
|
|
745
|
+
return data;
|
|
746
|
+
}
|
package/lib/mcp.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
addCohost,
|
|
26
26
|
sendEventMessage,
|
|
27
27
|
deriveDeviceId,
|
|
28
|
+
PROFILE_FIELDS,
|
|
28
29
|
} from "./core.js";
|
|
29
30
|
|
|
30
31
|
export async function startMcpServer() {
|
|
@@ -104,17 +105,17 @@ export async function startMcpServer() {
|
|
|
104
105
|
|
|
105
106
|
server.tool(
|
|
106
107
|
"antenna_profile",
|
|
107
|
-
"Get or set the user's Antenna profile card.",
|
|
108
|
+
"Get or set the user's Antenna profile card. The profile has a display name and three descriptions: personal description, looking for, and conversation style.",
|
|
108
109
|
{
|
|
109
110
|
action: z.enum(["get", "set"]).describe("'get' to read, 'set' to write"),
|
|
110
111
|
sender_id: z.string().describe("The sender's user ID"),
|
|
111
112
|
channel: z.string().describe("Channel name"),
|
|
112
|
-
display_name: z.string().optional(),
|
|
113
|
-
emoji: z.string().optional(),
|
|
114
|
-
line1: z.string().optional(),
|
|
115
|
-
line2: z.string().optional(),
|
|
116
|
-
line3: z.string().optional(),
|
|
117
|
-
matching_context: z.string().optional().describe("Agent-generated rich context for
|
|
113
|
+
display_name: z.string().optional().describe("Display name"),
|
|
114
|
+
emoji: z.string().optional().describe("Profile emoji (stored but not displayed on card)"),
|
|
115
|
+
line1: z.string().optional().describe("Personal description โ who you are and what you do (max 220 chars)"),
|
|
116
|
+
line2: z.string().optional().describe("Looking for โ the kind of people you want to meet (max 140 chars)"),
|
|
117
|
+
line3: z.string().optional().describe("Conversation style โ the type of conversations you want (max 160 chars)"),
|
|
118
|
+
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
119
|
visible: z.boolean().optional().default(true),
|
|
119
120
|
},
|
|
120
121
|
async ({ action, sender_id, channel, display_name, emoji, line1, line2, line3, matching_context, visible }) => {
|
|
@@ -122,7 +123,9 @@ export async function startMcpServer() {
|
|
|
122
123
|
try {
|
|
123
124
|
if (action === "get") {
|
|
124
125
|
const data = await getProfile({ device_id: deviceId });
|
|
125
|
-
const result = data
|
|
126
|
+
const result = data
|
|
127
|
+
? { profile: data, fields: PROFILE_FIELDS }
|
|
128
|
+
: { profile: null, message: "่ฟๆฒกๆๅ็ใ่ท็จๆท่่ไปไปฌๆฏ่ฐใๅไปไนใๆณ่ฎค่ฏไปไนไบบ๏ผ็ถๅๅธฎไปไปฌๅๅปบใ", fields: PROFILE_FIELDS };
|
|
126
129
|
return jsonResult(await withMatchNotifications(deviceId, result));
|
|
127
130
|
}
|
|
128
131
|
const data = await setProfile({ device_id: deviceId, display_name, emoji, line1, line2, line3, matching_context, visible });
|