agentdex-cli 0.4.1 → 0.4.3

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/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'url';
9
9
  import { dirname, join } from 'path';
10
10
  import { nip19 } from 'nostr-tools';
11
11
  import { AgentdexClient } from './client.js';
12
- import { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, createKind0Event, publishToRelays, createNote, generateAndSaveKeypair } from './nostr.js';
12
+ import { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, buildKind0Event, publishToRelays, createNote, generateAndSaveKeypair } from './nostr.js';
13
13
  import { payInvoice } from './nwc.js';
14
14
  const __filename = fileURLToPath(import.meta.url);
15
15
  const __dirname = dirname(__filename);
@@ -209,17 +209,17 @@ program
209
209
  console.log(chalk.gray(` Published to: ${published.join(', ')}`));
210
210
  console.log('');
211
211
  console.log(chalk.gray(` Run ${chalk.white('agentdex claim <name>')} to get ${chalk.hex('#D4A574')('<name>@agentdex.id')}`));
212
- // Publish kind 0 profile (name, about, avatar, website, lud16)
212
+ // Publish kind 0 profile (fetch existing, merge, republish)
213
213
  const k0Spinner = ora('Publishing kind 0 profile to Nostr relays...').start();
214
214
  try {
215
- const kind0 = createKind0Event(sk, {
215
+ const kind0 = await buildKind0Event(sk, {
216
216
  name,
217
217
  about: description || undefined,
218
218
  picture: options.avatar || undefined,
219
219
  lud16: options.lightning || undefined,
220
220
  ownerPubkeyHex,
221
221
  bot: !!options.bot,
222
- });
222
+ }, relays);
223
223
  await publishToRelays(kind0, relays);
224
224
  k0Spinner.succeed('Kind 0 published — visible on all Nostr clients');
225
225
  }
@@ -257,18 +257,18 @@ program
257
257
  console.log('');
258
258
  console.log(chalk.gray(` Run ${chalk.white('agentdex claim <name>')} to get ${chalk.hex('#D4A574')('<name>@agentdex.id')}`));
259
259
  console.log('');
260
- // Publish kind 0 profile (name, about, avatar, website, lud16)
260
+ // Publish kind 0 profile (fetch existing, merge, republish)
261
261
  // Kind 0 is canonical for basic profile; kind 31339 is agent-specific metadata
262
262
  const k0Spinner = ora('Publishing kind 0 profile to Nostr relays...').start();
263
263
  try {
264
- const kind0 = createKind0Event(sk, {
264
+ const kind0 = await buildKind0Event(sk, {
265
265
  name,
266
266
  about: description || undefined,
267
267
  picture: options.avatar || undefined,
268
268
  lud16: options.lightning || undefined,
269
269
  ownerPubkeyHex,
270
270
  bot: !!options.bot,
271
- });
271
+ }, relays);
272
272
  await publishToRelays(kind0, relays);
273
273
  k0Spinner.succeed('Kind 0 published — visible on all Nostr clients');
274
274
  }
@@ -325,14 +325,14 @@ program
325
325
  if (!options.skipKind0) {
326
326
  const k0Spinner = ora('Publishing kind 0 profile to Nostr relays...').start();
327
327
  try {
328
- const kind0 = createKind0Event(sk, {
328
+ const relays = ['wss://nos.lol', 'wss://relay.damus.io', ...(options.relay || [])];
329
+ const kind0 = await buildKind0Event(sk, {
329
330
  name: claim.agent?.name || name,
330
331
  about: claim.agent?.description || undefined,
331
332
  picture: claim.agent?.avatarUrl || undefined,
332
333
  nip05: `${name}@agentdex.id`,
333
334
  lud16: options.lightning || undefined,
334
- });
335
- const relays = ['wss://nos.lol', 'wss://relay.damus.io', ...(options.relay || [])];
335
+ }, relays);
336
336
  const published = await publishToRelays(kind0, relays);
337
337
  k0Spinner.succeed(`Kind 0 published to ${published.join(', ')}`);
338
338
  console.log(chalk.gray(' NIP-05 will appear on njump/Damus/Primal once relays propagate (~30s)'));
@@ -392,8 +392,8 @@ program
392
392
  if (!options.skipKind0) {
393
393
  const k0Spinner = ora('Publishing kind 0 profile to Nostr relays...').start();
394
394
  try {
395
- const kind0 = createKind0Event(sk, { name, nip05: `${name}@agentdex.id` });
396
395
  const relays = ['wss://nos.lol', 'wss://relay.damus.io', ...(options.relay || [])];
396
+ const kind0 = await buildKind0Event(sk, { name, nip05: `${name}@agentdex.id` }, relays);
397
397
  await publishToRelays(kind0, relays);
398
398
  k0Spinner.succeed('Kind 0 published — NIP-05 active on all Nostr clients');
399
399
  }
package/dist/index.d.ts CHANGED
@@ -11,5 +11,5 @@
11
11
  */
12
12
  export { AgentdexClient } from './client.js';
13
13
  export type { AgentdexConfig, RegisterOptions, VerifyResult, ClaimResult, ClaimStatus, SearchOptions, } from './client.js';
14
- export { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, createKind0Event, publishToRelays, createNote, } from './nostr.js';
14
+ export { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, buildKind0Event, publishToRelays, createNote, } from './nostr.js';
15
15
  export type { AgentProfile } from './nostr.js';
package/dist/index.js CHANGED
@@ -10,4 +10,4 @@
10
10
  * ```
11
11
  */
12
12
  export { AgentdexClient } from './client.js';
13
- export { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, createKind0Event, publishToRelays, createNote, } from './nostr.js';
13
+ export { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, buildKind0Event, publishToRelays, createNote, } from './nostr.js';
package/dist/nostr.d.ts CHANGED
@@ -64,15 +64,20 @@ export declare function updateKind0(sk: Uint8Array, updates: {
64
64
  * After claiming a NIP-05 name, publish this to relays so Nostr clients
65
65
  * (njump, Damus, Primal) can verify the identity.
66
66
  */
67
- export declare function createKind0Event(sk: Uint8Array, profile: {
68
- name: string;
67
+ /**
68
+ * Fetch existing kind 0, merge explicit updates, and return signed event.
69
+ * Safe for existing Nostr users — only overwrites fields explicitly passed.
70
+ */
71
+ export declare function buildKind0Event(sk: Uint8Array, updates: {
72
+ name?: string;
69
73
  about?: string;
70
74
  nip05?: string;
71
75
  picture?: string;
72
76
  lud16?: string;
77
+ website?: string;
73
78
  ownerPubkeyHex?: string;
74
79
  bot?: boolean;
75
- }): import("nostr-tools").VerifiedEvent;
80
+ }, relays?: string[]): Promise<import("nostr-tools").VerifiedEvent>;
76
81
  /**
77
82
  * Create and sign a kind 1 note tagged #agentdex
78
83
  */
package/dist/nostr.js CHANGED
@@ -63,6 +63,9 @@ export function createProfileEvent(sk, profile) {
63
63
  ];
64
64
  // Kind 31339 is agent-specific metadata ONLY
65
65
  // Basic profile (name, avatar, website, nip05, lud16) lives exclusively in kind 0
66
+ // name is included for API registration (server needs it) but kind 0 is canonical
67
+ if (profile.name)
68
+ tags.push(['name', profile.name]);
66
69
  if (profile.description)
67
70
  tags.push(['description', profile.description]);
68
71
  if (profile.capabilities) {
@@ -182,24 +185,79 @@ export async function updateKind0(sk, updates, relays = DEFAULT_RELAYS) {
182
185
  * After claiming a NIP-05 name, publish this to relays so Nostr clients
183
186
  * (njump, Damus, Primal) can verify the identity.
184
187
  */
185
- export function createKind0Event(sk, profile) {
188
+ /**
189
+ * Fetch existing kind 0, merge explicit updates, and return signed event.
190
+ * Safe for existing Nostr users — only overwrites fields explicitly passed.
191
+ */
192
+ export async function buildKind0Event(sk, updates, relays = DEFAULT_RELAYS) {
193
+ const pool = new SimplePool();
194
+ const pubkey = getPublicKey(sk);
195
+ // Fetch existing kind 0 from relays
196
+ let existing = {};
197
+ let existingTags = [];
198
+ try {
199
+ const events = await Promise.race([
200
+ pool.querySync(relays, { kinds: [0], authors: [pubkey] }),
201
+ new Promise((resolve) => setTimeout(() => resolve([]), 5000)),
202
+ ]);
203
+ if (events.length > 0) {
204
+ // Get newest event
205
+ const newest = events.reduce((a, b) => a.created_at > b.created_at ? a : b);
206
+ existing = JSON.parse(newest.content);
207
+ existingTags = newest.tags || [];
208
+ }
209
+ }
210
+ catch { }
211
+ pool.close(relays);
212
+ // Merge content fields — only overwrite if explicitly provided (not undefined)
213
+ if (updates.name !== undefined)
214
+ existing.name = updates.name;
215
+ if (updates.about !== undefined)
216
+ existing.about = updates.about;
217
+ if (updates.nip05 !== undefined)
218
+ existing.nip05 = updates.nip05;
219
+ if (updates.picture !== undefined)
220
+ existing.picture = updates.picture;
221
+ if (updates.lud16 !== undefined)
222
+ existing.lud16 = updates.lud16;
223
+ if (updates.website !== undefined)
224
+ existing.website = updates.website;
225
+ // Merge tags
186
226
  const tags = [];
187
- if (profile.bot)
188
- tags.push(["bot"]);
189
- if (profile.ownerPubkeyHex) {
190
- tags.push(["p", profile.ownerPubkeyHex, "", "owner"]);
227
+ // Preserve existing tags that we don't manage (e.g., user's own tags)
228
+ for (const tag of existingTags) {
229
+ // Skip tags we manage — we'll re-add them below
230
+ if (tag[0] === 'p' && tag[3] === 'owner')
231
+ continue;
232
+ if (tag[0] === 'bot')
233
+ continue;
234
+ tags.push(tag);
235
+ }
236
+ // Add/update owner p tag
237
+ if (updates.ownerPubkeyHex) {
238
+ tags.push(['p', updates.ownerPubkeyHex, '', 'owner']);
239
+ }
240
+ else {
241
+ // Preserve existing owner p tag if we're not updating it
242
+ const existingOwner = existingTags.find(t => t[0] === 'p' && t[3] === 'owner');
243
+ if (existingOwner)
244
+ tags.push(existingOwner);
245
+ }
246
+ // Add bot tag only if explicitly requested
247
+ if (updates.bot) {
248
+ tags.push(['bot']);
249
+ }
250
+ else {
251
+ // Preserve existing bot tag if we're not changing it
252
+ const existingBot = existingTags.find(t => t[0] === 'bot');
253
+ if (existingBot)
254
+ tags.push(existingBot);
191
255
  }
192
256
  return finalizeEvent({
193
257
  kind: 0,
194
258
  created_at: Math.floor(Date.now() / 1000),
195
259
  tags,
196
- content: JSON.stringify({
197
- name: profile.name,
198
- ...(profile.about && { about: profile.about }),
199
- ...(profile.nip05 && { nip05: profile.nip05 }),
200
- ...(profile.picture && { picture: profile.picture }),
201
- ...(profile.lud16 && { lud16: profile.lud16 }),
202
- }),
260
+ content: JSON.stringify(existing),
203
261
  }, sk);
204
262
  }
205
263
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentdex-cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "CLI and SDK for the agentdex AI agent directory",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -10,7 +10,7 @@ import { fileURLToPath } from 'url';
10
10
  import { dirname, join } from 'path';
11
11
  import { nip19 } from 'nostr-tools';
12
12
  import { AgentdexClient } from './client.js';
13
- import { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, createKind0Event, publishToRelays, createNote, updateKind0, generateAndSaveKeypair } from './nostr.js';
13
+ import { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, buildKind0Event, publishToRelays, createNote, updateKind0, generateAndSaveKeypair } from './nostr.js';
14
14
  import { payInvoice } from './nwc.js';
15
15
 
16
16
  const __filename = fileURLToPath(import.meta.url);
@@ -227,17 +227,17 @@ program
227
227
  console.log('');
228
228
  console.log(chalk.gray(` Run ${chalk.white('agentdex claim <name>')} to get ${chalk.hex('#D4A574')('<name>@agentdex.id')}`));
229
229
 
230
- // Publish kind 0 profile (name, about, avatar, website, lud16)
230
+ // Publish kind 0 profile (fetch existing, merge, republish)
231
231
  const k0Spinner = ora('Publishing kind 0 profile to Nostr relays...').start();
232
232
  try {
233
- const kind0 = createKind0Event(sk, {
233
+ const kind0 = await buildKind0Event(sk, {
234
234
  name,
235
235
  about: description || undefined,
236
236
  picture: options.avatar || undefined,
237
237
  lud16: options.lightning || undefined,
238
238
  ownerPubkeyHex,
239
239
  bot: !!options.bot,
240
- });
240
+ }, relays);
241
241
  await publishToRelays(kind0, relays);
242
242
  k0Spinner.succeed('Kind 0 published — visible on all Nostr clients');
243
243
  } catch {
@@ -277,18 +277,18 @@ program
277
277
  console.log('');
278
278
  console.log(chalk.gray(` Run ${chalk.white('agentdex claim <name>')} to get ${chalk.hex('#D4A574')('<name>@agentdex.id')}`));
279
279
  console.log('');
280
- // Publish kind 0 profile (name, about, avatar, website, lud16)
280
+ // Publish kind 0 profile (fetch existing, merge, republish)
281
281
  // Kind 0 is canonical for basic profile; kind 31339 is agent-specific metadata
282
282
  const k0Spinner = ora('Publishing kind 0 profile to Nostr relays...').start();
283
283
  try {
284
- const kind0 = createKind0Event(sk, {
284
+ const kind0 = await buildKind0Event(sk, {
285
285
  name,
286
286
  about: description || undefined,
287
287
  picture: options.avatar || undefined,
288
288
  lud16: options.lightning || undefined,
289
289
  ownerPubkeyHex,
290
290
  bot: !!options.bot,
291
- });
291
+ }, relays);
292
292
  await publishToRelays(kind0, relays);
293
293
  k0Spinner.succeed('Kind 0 published — visible on all Nostr clients');
294
294
  } catch {
@@ -349,14 +349,14 @@ program
349
349
  if (!options.skipKind0) {
350
350
  const k0Spinner = ora('Publishing kind 0 profile to Nostr relays...').start();
351
351
  try {
352
- const kind0 = createKind0Event(sk, {
352
+ const relays = ['wss://nos.lol', 'wss://relay.damus.io', ...(options.relay || [])];
353
+ const kind0 = await buildKind0Event(sk, {
353
354
  name: claim.agent?.name || name,
354
355
  about: claim.agent?.description || undefined,
355
356
  picture: claim.agent?.avatarUrl || undefined,
356
357
  nip05: `${name}@agentdex.id`,
357
358
  lud16: options.lightning || undefined,
358
- });
359
- const relays = ['wss://nos.lol', 'wss://relay.damus.io', ...(options.relay || [])];
359
+ }, relays);
360
360
  const published = await publishToRelays(kind0, relays);
361
361
  k0Spinner.succeed(`Kind 0 published to ${published.join(', ')}`);
362
362
  console.log(chalk.gray(' NIP-05 will appear on njump/Damus/Primal once relays propagate (~30s)'));
@@ -419,8 +419,8 @@ program
419
419
  if (!options.skipKind0) {
420
420
  const k0Spinner = ora('Publishing kind 0 profile to Nostr relays...').start();
421
421
  try {
422
- const kind0 = createKind0Event(sk, { name, nip05: `${name}@agentdex.id` });
423
422
  const relays = ['wss://nos.lol', 'wss://relay.damus.io', ...(options.relay || [])];
423
+ const kind0 = await buildKind0Event(sk, { name, nip05: `${name}@agentdex.id` }, relays);
424
424
  await publishToRelays(kind0, relays);
425
425
  k0Spinner.succeed('Kind 0 published — NIP-05 active on all Nostr clients');
426
426
  } catch {
package/src/index.ts CHANGED
@@ -25,7 +25,7 @@ export {
25
25
  getNpub,
26
26
  getPubkeyHex,
27
27
  createProfileEvent,
28
- createKind0Event,
28
+ buildKind0Event,
29
29
  publishToRelays,
30
30
  createNote,
31
31
  } from './nostr.js';
package/src/nostr.ts CHANGED
@@ -96,6 +96,8 @@ export function createProfileEvent(sk: Uint8Array, profile: AgentProfile) {
96
96
 
97
97
  // Kind 31339 is agent-specific metadata ONLY
98
98
  // Basic profile (name, avatar, website, nip05, lud16) lives exclusively in kind 0
99
+ // name is included for API registration (server needs it) but kind 0 is canonical
100
+ if (profile.name) tags.push(['name', profile.name]);
99
101
  if (profile.description) tags.push(['description', profile.description]);
100
102
  if (profile.capabilities) {
101
103
  for (const cap of profile.capabilities) {
@@ -213,23 +215,82 @@ export async function updateKind0(sk: Uint8Array, updates: { lud16?: string }, r
213
215
  * After claiming a NIP-05 name, publish this to relays so Nostr clients
214
216
  * (njump, Damus, Primal) can verify the identity.
215
217
  */
216
- export function createKind0Event(sk: Uint8Array, profile: { name: string; about?: string; nip05?: string; picture?: string; lud16?: string; ownerPubkeyHex?: string; bot?: boolean }) {
218
+ /**
219
+ * Fetch existing kind 0, merge explicit updates, and return signed event.
220
+ * Safe for existing Nostr users — only overwrites fields explicitly passed.
221
+ */
222
+ export async function buildKind0Event(sk: Uint8Array, updates: {
223
+ name?: string;
224
+ about?: string;
225
+ nip05?: string;
226
+ picture?: string;
227
+ lud16?: string;
228
+ website?: string;
229
+ ownerPubkeyHex?: string;
230
+ bot?: boolean;
231
+ }, relays: string[] = DEFAULT_RELAYS) {
232
+ const pool = new SimplePool();
233
+ const pubkey = getPublicKey(sk);
234
+
235
+ // Fetch existing kind 0 from relays
236
+ let existing: Record<string, unknown> = {};
237
+ let existingTags: string[][] = [];
238
+ try {
239
+ const events = await Promise.race([
240
+ pool.querySync(relays, { kinds: [0], authors: [pubkey] }),
241
+ new Promise<any[]>((resolve) => setTimeout(() => resolve([]), 5000)),
242
+ ]);
243
+ if (events.length > 0) {
244
+ // Get newest event
245
+ const newest = events.reduce((a: any, b: any) => a.created_at > b.created_at ? a : b);
246
+ existing = JSON.parse(newest.content);
247
+ existingTags = newest.tags || [];
248
+ }
249
+ } catch {}
250
+ pool.close(relays);
251
+
252
+ // Merge content fields — only overwrite if explicitly provided (not undefined)
253
+ if (updates.name !== undefined) existing.name = updates.name;
254
+ if (updates.about !== undefined) existing.about = updates.about;
255
+ if (updates.nip05 !== undefined) existing.nip05 = updates.nip05;
256
+ if (updates.picture !== undefined) existing.picture = updates.picture;
257
+ if (updates.lud16 !== undefined) existing.lud16 = updates.lud16;
258
+ if (updates.website !== undefined) existing.website = updates.website;
259
+
260
+ // Merge tags
217
261
  const tags: string[][] = [];
218
- if (profile.bot) tags.push(["bot"]);
219
- if (profile.ownerPubkeyHex) {
220
- tags.push(["p", profile.ownerPubkeyHex, "", "owner"]);
262
+
263
+ // Preserve existing tags that we don't manage (e.g., user's own tags)
264
+ for (const tag of existingTags) {
265
+ // Skip tags we manage — we'll re-add them below
266
+ if (tag[0] === 'p' && tag[3] === 'owner') continue;
267
+ if (tag[0] === 'bot') continue;
268
+ tags.push(tag);
269
+ }
270
+
271
+ // Add/update owner p tag
272
+ if (updates.ownerPubkeyHex) {
273
+ tags.push(['p', updates.ownerPubkeyHex, '', 'owner']);
274
+ } else {
275
+ // Preserve existing owner p tag if we're not updating it
276
+ const existingOwner = existingTags.find(t => t[0] === 'p' && t[3] === 'owner');
277
+ if (existingOwner) tags.push(existingOwner);
278
+ }
279
+
280
+ // Add bot tag only if explicitly requested
281
+ if (updates.bot) {
282
+ tags.push(['bot']);
283
+ } else {
284
+ // Preserve existing bot tag if we're not changing it
285
+ const existingBot = existingTags.find(t => t[0] === 'bot');
286
+ if (existingBot) tags.push(existingBot);
221
287
  }
288
+
222
289
  return finalizeEvent({
223
290
  kind: 0,
224
291
  created_at: Math.floor(Date.now() / 1000),
225
292
  tags,
226
- content: JSON.stringify({
227
- name: profile.name,
228
- ...(profile.about && { about: profile.about }),
229
- ...(profile.nip05 && { nip05: profile.nip05 }),
230
- ...(profile.picture && { picture: profile.picture }),
231
- ...(profile.lud16 && { lud16: profile.lud16 }),
232
- }),
293
+ content: JSON.stringify(existing),
233
294
  }, sk);
234
295
  }
235
296