agentdex-cli 0.2.3 → 0.2.6
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 +48 -4
- package/dist/nostr.d.ts +25 -0
- package/dist/nostr.js +79 -3
- package/package.json +1 -1
- package/src/cli.ts +49 -4
- package/src/nostr.ts +92 -3
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import inquirer from 'inquirer';
|
|
|
6
6
|
import qrcode from 'qrcode-terminal';
|
|
7
7
|
import { readFileSync } from 'fs';
|
|
8
8
|
import { AgentdexClient } from './client.js';
|
|
9
|
-
import { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, createKind0Event, publishToRelays, createNote } from './nostr.js';
|
|
9
|
+
import { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, createKind0Event, publishToRelays, createNote, updateKind0, generateAndSaveKeypair } from './nostr.js';
|
|
10
10
|
import { payInvoice } from './nwc.js';
|
|
11
11
|
const program = new Command();
|
|
12
12
|
program
|
|
@@ -16,7 +16,8 @@ program
|
|
|
16
16
|
/**
|
|
17
17
|
* Resolve secret key from flags, env, or key file
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
const DEFAULT_KEY_PATH = `${process.env.HOME}/.config/nostr/agent.json`;
|
|
20
|
+
function resolveKey(options, allowGenerate = false) {
|
|
20
21
|
const raw = options.nsec || process.env.NOSTR_NSEC;
|
|
21
22
|
if (raw)
|
|
22
23
|
return parseSecretKey(raw);
|
|
@@ -28,6 +29,20 @@ function resolveKey(options) {
|
|
|
28
29
|
return parseSecretKey(data.nsec);
|
|
29
30
|
throw new Error('Key file must contain sk_hex or nsec');
|
|
30
31
|
}
|
|
32
|
+
// Auto-generate for register if no key provided
|
|
33
|
+
if (allowGenerate) {
|
|
34
|
+
const { sk, npub, path } = generateAndSaveKeypair(DEFAULT_KEY_PATH);
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(chalk.hex('#D4A574')(' 🔑 No key found. Generated a new Nostr keypair.'));
|
|
37
|
+
console.log(chalk.gray(` Saved to: ${path}`));
|
|
38
|
+
console.log(chalk.gray(` npub: ${npub}`));
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log(chalk.yellow(' ⚠️ IMPORTANT: Your secret key is in that file.'));
|
|
41
|
+
console.log(chalk.yellow(' Store it somewhere secure. If you lose it, you lose this identity.'));
|
|
42
|
+
console.log(chalk.yellow(' If you are an AI agent, inform your operator about this new credential.'));
|
|
43
|
+
console.log('');
|
|
44
|
+
return sk;
|
|
45
|
+
}
|
|
31
46
|
throw new Error('No key provided. Use --nsec, --key-file, or set NOSTR_NSEC env var.');
|
|
32
47
|
}
|
|
33
48
|
// ==================== REGISTER ====================
|
|
@@ -42,15 +57,18 @@ program
|
|
|
42
57
|
.option('--framework <fw>', 'Framework (e.g., langchain, openclaw)')
|
|
43
58
|
.option('--model <model>', 'Model (e.g., claude-3.5-sonnet)')
|
|
44
59
|
.option('--website <url>', 'Website URL')
|
|
45
|
-
.option('--lightning <addr>', 'Lightning address')
|
|
60
|
+
.option('--lightning <addr>', 'Lightning address (sets lud16 in kind 0 profile)')
|
|
46
61
|
.option('--owner-x <handle>', 'Owner X/Twitter handle (e.g., @username)')
|
|
62
|
+
.option('--portfolio <entry>', 'Portfolio URL (format: "url,name,description") — repeatable', (val, acc) => [...acc, val], [])
|
|
63
|
+
.option('--skill <skill>', 'Skill tag (repeatable)', (val, acc) => [...acc, val], [])
|
|
64
|
+
.option('--experience <exp>', 'Experience tag (repeatable)', (val, acc) => [...acc, val], [])
|
|
47
65
|
.option('--nwc <uri>', 'Nostr Wallet Connect URI for auto-pay')
|
|
48
66
|
.option('--api-key <key>', 'Agentdex API key')
|
|
49
67
|
.option('--relay <url>', 'Additional relay (repeatable)', (val, acc) => [...acc, val], [])
|
|
50
68
|
.option('--json', 'Output JSON')
|
|
51
69
|
.action(async (options) => {
|
|
52
70
|
try {
|
|
53
|
-
const sk = resolveKey(options);
|
|
71
|
+
const sk = resolveKey(options, true);
|
|
54
72
|
const npub = getNpub(sk);
|
|
55
73
|
const pubHex = getPubkeyHex(sk);
|
|
56
74
|
let name = options.name;
|
|
@@ -71,6 +89,11 @@ program
|
|
|
71
89
|
framework = answers.framework || framework;
|
|
72
90
|
}
|
|
73
91
|
const spinner = ora('Signing event...').start();
|
|
92
|
+
// Parse portfolio entries ("url,name,description")
|
|
93
|
+
const portfolio = (options.portfolio || []).map((entry) => {
|
|
94
|
+
const parts = entry.split(',').map((s) => s.trim());
|
|
95
|
+
return { url: parts[0], name: parts[1], description: parts[2] };
|
|
96
|
+
});
|
|
74
97
|
const event = createProfileEvent(sk, {
|
|
75
98
|
name,
|
|
76
99
|
description,
|
|
@@ -81,6 +104,9 @@ program
|
|
|
81
104
|
lightning: options.lightning,
|
|
82
105
|
ownerX: options.ownerX,
|
|
83
106
|
status: 'active',
|
|
107
|
+
portfolio: portfolio.length > 0 ? portfolio : undefined,
|
|
108
|
+
skills: options.skill?.length > 0 ? options.skill : undefined,
|
|
109
|
+
experience: options.experience?.length > 0 ? options.experience : undefined,
|
|
84
110
|
});
|
|
85
111
|
spinner.text = 'Registering on agentdex...';
|
|
86
112
|
const client = new AgentdexClient({ apiKey: options.apiKey });
|
|
@@ -136,6 +162,14 @@ program
|
|
|
136
162
|
console.log(chalk.gray(` Published to: ${published.join(', ')}`));
|
|
137
163
|
console.log('');
|
|
138
164
|
console.log(chalk.gray(` Run ${chalk.white('agentdex claim <name>')} to get ${chalk.hex('#D4A574')('<name>@agentdex.id')}`));
|
|
165
|
+
// Update kind 0 with lightning address if provided
|
|
166
|
+
if (options.lightning) {
|
|
167
|
+
try {
|
|
168
|
+
await updateKind0(sk, { lud16: options.lightning }, relays);
|
|
169
|
+
console.log(chalk.gray(` ⚡ Lightning address set in kind 0: ${options.lightning}`));
|
|
170
|
+
}
|
|
171
|
+
catch { }
|
|
172
|
+
}
|
|
139
173
|
}
|
|
140
174
|
return;
|
|
141
175
|
}
|
|
@@ -161,6 +195,14 @@ program
|
|
|
161
195
|
console.log('');
|
|
162
196
|
console.log(chalk.gray(` Run ${chalk.white('agentdex claim <name>')} to get ${chalk.hex('#D4A574')('<name>@agentdex.id')}`));
|
|
163
197
|
console.log('');
|
|
198
|
+
// Update kind 0 with lightning address if provided
|
|
199
|
+
if (options.lightning) {
|
|
200
|
+
try {
|
|
201
|
+
await updateKind0(sk, { lud16: options.lightning }, relays);
|
|
202
|
+
console.log(chalk.gray(` ⚡ Lightning address set in kind 0: ${options.lightning}`));
|
|
203
|
+
}
|
|
204
|
+
catch { }
|
|
205
|
+
}
|
|
164
206
|
console.log(chalk.gray(' Next: Claim a NIP-05 name to get verified (first 100 free, then 5000 sats).'));
|
|
165
207
|
}
|
|
166
208
|
}
|
|
@@ -188,6 +230,7 @@ program
|
|
|
188
230
|
.option('--key-file <path>', 'Path to JSON key file')
|
|
189
231
|
.option('--nwc <uri>', 'Nostr Wallet Connect URI for auto-pay')
|
|
190
232
|
.option('--api-key <key>', 'Agentdex API key')
|
|
233
|
+
.option('--lightning <addr>', 'Lightning address (lud16) to set in kind 0 profile')
|
|
191
234
|
.option('--skip-kind0', 'Skip publishing kind 0 profile to relays')
|
|
192
235
|
.option('--relay <url>', 'Additional relay', (val, acc) => [...acc, val], [])
|
|
193
236
|
.option('--json', 'Output JSON')
|
|
@@ -214,6 +257,7 @@ program
|
|
|
214
257
|
about: claim.agent?.description || undefined,
|
|
215
258
|
picture: claim.agent?.avatarUrl || undefined,
|
|
216
259
|
nip05: `${name}@agentdex.id`,
|
|
260
|
+
lud16: options.lightning || undefined,
|
|
217
261
|
});
|
|
218
262
|
const relays = ['wss://nos.lol', 'wss://relay.damus.io', ...(options.relay || [])];
|
|
219
263
|
const published = await publishToRelays(kind0, relays);
|
package/dist/nostr.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Nostr utilities — event creation, signing, publishing
|
|
3
3
|
*/
|
|
4
|
+
export interface PortfolioItem {
|
|
5
|
+
url: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
4
9
|
export interface AgentProfile {
|
|
5
10
|
name: string;
|
|
6
11
|
description?: string;
|
|
@@ -16,7 +21,19 @@ export interface AgentProfile {
|
|
|
16
21
|
messagingPolicy?: string;
|
|
17
22
|
messagingMinTrust?: number;
|
|
18
23
|
messagingFee?: number;
|
|
24
|
+
portfolio?: PortfolioItem[];
|
|
25
|
+
skills?: string[];
|
|
26
|
+
experience?: string[];
|
|
19
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Generate a new Nostr keypair and save to a JSON file.
|
|
30
|
+
* Returns the secret key as Uint8Array.
|
|
31
|
+
*/
|
|
32
|
+
export declare function generateAndSaveKeypair(outputPath: string): {
|
|
33
|
+
sk: Uint8Array;
|
|
34
|
+
npub: string;
|
|
35
|
+
path: string;
|
|
36
|
+
};
|
|
20
37
|
/**
|
|
21
38
|
* Parse a secret key from nsec, hex, or key file
|
|
22
39
|
*/
|
|
@@ -37,6 +54,13 @@ export declare function createProfileEvent(sk: Uint8Array, profile: AgentProfile
|
|
|
37
54
|
* Publish an event to Nostr relays
|
|
38
55
|
*/
|
|
39
56
|
export declare function publishToRelays(event: object, relays?: string[]): Promise<string[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Fetch existing kind 0, merge new fields, and republish.
|
|
59
|
+
* Used to set lud16 (lightning address) during registration.
|
|
60
|
+
*/
|
|
61
|
+
export declare function updateKind0(sk: Uint8Array, updates: {
|
|
62
|
+
lud16?: string;
|
|
63
|
+
}, relays?: string[]): Promise<string[]>;
|
|
40
64
|
/**
|
|
41
65
|
* Build and sign a kind 0 profile metadata event (for NIP-05 verification).
|
|
42
66
|
* After claiming a NIP-05 name, publish this to relays so Nostr clients
|
|
@@ -47,6 +71,7 @@ export declare function createKind0Event(sk: Uint8Array, profile: {
|
|
|
47
71
|
about?: string;
|
|
48
72
|
nip05?: string;
|
|
49
73
|
picture?: string;
|
|
74
|
+
lud16?: string;
|
|
50
75
|
}): import("nostr-tools").VerifiedEvent;
|
|
51
76
|
/**
|
|
52
77
|
* Create and sign a kind 1 note tagged #agentdex
|
package/dist/nostr.js
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Nostr utilities — event creation, signing, publishing
|
|
3
3
|
*/
|
|
4
|
-
import { finalizeEvent, getPublicKey } from 'nostr-tools/pure';
|
|
4
|
+
import { finalizeEvent, getPublicKey, generateSecretKey } from 'nostr-tools/pure';
|
|
5
5
|
import { nip19 } from 'nostr-tools';
|
|
6
6
|
import { SimplePool } from 'nostr-tools/pool';
|
|
7
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
7
8
|
const DEFAULT_RELAYS = ['wss://nos.lol', 'wss://relay.damus.io'];
|
|
9
|
+
/**
|
|
10
|
+
* Generate a new Nostr keypair and save to a JSON file.
|
|
11
|
+
* Returns the secret key as Uint8Array.
|
|
12
|
+
*/
|
|
13
|
+
export function generateAndSaveKeypair(outputPath) {
|
|
14
|
+
const sk = generateSecretKey();
|
|
15
|
+
const pk = getPublicKey(sk);
|
|
16
|
+
const nsec = nip19.nsecEncode(sk);
|
|
17
|
+
const npub = nip19.npubEncode(pk);
|
|
18
|
+
const skHex = Buffer.from(sk).toString('hex');
|
|
19
|
+
const data = JSON.stringify({ nsec, npub, sk_hex: skHex, pk_hex: pk }, null, 2);
|
|
20
|
+
// Ensure directory exists
|
|
21
|
+
const dir = outputPath.substring(0, outputPath.lastIndexOf('/'));
|
|
22
|
+
if (dir)
|
|
23
|
+
mkdirSync(dir, { recursive: true });
|
|
24
|
+
writeFileSync(outputPath, data, { mode: 0o600 });
|
|
25
|
+
return { sk, npub, path: outputPath };
|
|
26
|
+
}
|
|
8
27
|
/**
|
|
9
28
|
* Parse a secret key from nsec, hex, or key file
|
|
10
29
|
*/
|
|
@@ -58,8 +77,8 @@ export function createProfileEvent(sk, profile) {
|
|
|
58
77
|
tags.push(['website', profile.website]);
|
|
59
78
|
if (profile.avatar)
|
|
60
79
|
tags.push(['avatar', profile.avatar]);
|
|
61
|
-
|
|
62
|
-
|
|
80
|
+
// Lightning address belongs in kind 0 (lud16), not kind 31337
|
|
81
|
+
// Use --lightning flag to set lud16 in kind 0 during claim
|
|
63
82
|
if (profile.human)
|
|
64
83
|
tags.push(['human', profile.human]);
|
|
65
84
|
if (profile.ownerX)
|
|
@@ -72,6 +91,26 @@ export function createProfileEvent(sk, profile) {
|
|
|
72
91
|
tags.push(['messaging_min_trust', String(profile.messagingMinTrust)]);
|
|
73
92
|
if (profile.messagingFee)
|
|
74
93
|
tags.push(['messaging_fee', String(profile.messagingFee)]);
|
|
94
|
+
if (profile.portfolio) {
|
|
95
|
+
for (const item of profile.portfolio) {
|
|
96
|
+
const tag = ['portfolio', item.url];
|
|
97
|
+
if (item.name)
|
|
98
|
+
tag.push(item.name);
|
|
99
|
+
if (item.description)
|
|
100
|
+
tag.push(item.description);
|
|
101
|
+
tags.push(tag);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (profile.skills) {
|
|
105
|
+
for (const skill of profile.skills) {
|
|
106
|
+
tags.push(['skill', skill]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (profile.experience) {
|
|
110
|
+
for (const exp of profile.experience) {
|
|
111
|
+
tags.push(['experience', exp]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
75
114
|
const event = finalizeEvent({
|
|
76
115
|
kind: 31337,
|
|
77
116
|
created_at: Math.floor(Date.now() / 1000),
|
|
@@ -102,6 +141,42 @@ export async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
102
141
|
}
|
|
103
142
|
return published;
|
|
104
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Fetch existing kind 0, merge new fields, and republish.
|
|
146
|
+
* Used to set lud16 (lightning address) during registration.
|
|
147
|
+
*/
|
|
148
|
+
export async function updateKind0(sk, updates, relays = DEFAULT_RELAYS) {
|
|
149
|
+
const pool = new SimplePool();
|
|
150
|
+
const pubkey = getPublicKey(sk);
|
|
151
|
+
try {
|
|
152
|
+
// Fetch existing kind 0
|
|
153
|
+
let existing = {};
|
|
154
|
+
try {
|
|
155
|
+
const events = await Promise.race([
|
|
156
|
+
pool.querySync(relays, { kinds: [0], authors: [pubkey] }),
|
|
157
|
+
new Promise((resolve) => setTimeout(() => resolve([]), 5000)),
|
|
158
|
+
]);
|
|
159
|
+
if (events.length > 0) {
|
|
160
|
+
existing = JSON.parse(events[0].content);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch { }
|
|
164
|
+
// Merge updates
|
|
165
|
+
if (updates.lud16)
|
|
166
|
+
existing.lud16 = updates.lud16;
|
|
167
|
+
const event = finalizeEvent({
|
|
168
|
+
kind: 0,
|
|
169
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
170
|
+
tags: [],
|
|
171
|
+
content: JSON.stringify(existing),
|
|
172
|
+
}, sk);
|
|
173
|
+
const published = await publishToRelays(event, relays);
|
|
174
|
+
return published;
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
pool.close(relays);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
105
180
|
/**
|
|
106
181
|
* Build and sign a kind 0 profile metadata event (for NIP-05 verification).
|
|
107
182
|
* After claiming a NIP-05 name, publish this to relays so Nostr clients
|
|
@@ -117,6 +192,7 @@ export function createKind0Event(sk, profile) {
|
|
|
117
192
|
...(profile.about && { about: profile.about }),
|
|
118
193
|
...(profile.nip05 && { nip05: profile.nip05 }),
|
|
119
194
|
...(profile.picture && { picture: profile.picture }),
|
|
195
|
+
...(profile.lud16 && { lud16: profile.lud16 }),
|
|
120
196
|
}),
|
|
121
197
|
}, sk);
|
|
122
198
|
}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -7,7 +7,7 @@ import inquirer from 'inquirer';
|
|
|
7
7
|
import qrcode from 'qrcode-terminal';
|
|
8
8
|
import { readFileSync } from 'fs';
|
|
9
9
|
import { AgentdexClient } from './client.js';
|
|
10
|
-
import { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, createKind0Event, publishToRelays, createNote } from './nostr.js';
|
|
10
|
+
import { parseSecretKey, getNpub, getPubkeyHex, createProfileEvent, createKind0Event, publishToRelays, createNote, updateKind0, generateAndSaveKeypair } from './nostr.js';
|
|
11
11
|
import { payInvoice } from './nwc.js';
|
|
12
12
|
|
|
13
13
|
const program = new Command();
|
|
@@ -20,7 +20,9 @@ program
|
|
|
20
20
|
/**
|
|
21
21
|
* Resolve secret key from flags, env, or key file
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
const DEFAULT_KEY_PATH = `${process.env.HOME}/.config/nostr/agent.json`;
|
|
24
|
+
|
|
25
|
+
function resolveKey(options: { nsec?: string; keyFile?: string }, allowGenerate = false): Uint8Array {
|
|
24
26
|
const raw = options.nsec || process.env.NOSTR_NSEC;
|
|
25
27
|
if (raw) return parseSecretKey(raw);
|
|
26
28
|
|
|
@@ -31,6 +33,21 @@ function resolveKey(options: { nsec?: string; keyFile?: string }): Uint8Array {
|
|
|
31
33
|
throw new Error('Key file must contain sk_hex or nsec');
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
// Auto-generate for register if no key provided
|
|
37
|
+
if (allowGenerate) {
|
|
38
|
+
const { sk, npub, path } = generateAndSaveKeypair(DEFAULT_KEY_PATH);
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log(chalk.hex('#D4A574')(' 🔑 No key found. Generated a new Nostr keypair.'));
|
|
41
|
+
console.log(chalk.gray(` Saved to: ${path}`));
|
|
42
|
+
console.log(chalk.gray(` npub: ${npub}`));
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(chalk.yellow(' ⚠️ IMPORTANT: Your secret key is in that file.'));
|
|
45
|
+
console.log(chalk.yellow(' Store it somewhere secure. If you lose it, you lose this identity.'));
|
|
46
|
+
console.log(chalk.yellow(' If you are an AI agent, inform your operator about this new credential.'));
|
|
47
|
+
console.log('');
|
|
48
|
+
return sk;
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
throw new Error('No key provided. Use --nsec, --key-file, or set NOSTR_NSEC env var.');
|
|
35
52
|
}
|
|
36
53
|
|
|
@@ -47,15 +64,18 @@ program
|
|
|
47
64
|
.option('--framework <fw>', 'Framework (e.g., langchain, openclaw)')
|
|
48
65
|
.option('--model <model>', 'Model (e.g., claude-3.5-sonnet)')
|
|
49
66
|
.option('--website <url>', 'Website URL')
|
|
50
|
-
.option('--lightning <addr>', 'Lightning address')
|
|
67
|
+
.option('--lightning <addr>', 'Lightning address (sets lud16 in kind 0 profile)')
|
|
51
68
|
.option('--owner-x <handle>', 'Owner X/Twitter handle (e.g., @username)')
|
|
69
|
+
.option('--portfolio <entry>', 'Portfolio URL (format: "url,name,description") — repeatable', (val: string, acc: string[]) => [...acc, val], [])
|
|
70
|
+
.option('--skill <skill>', 'Skill tag (repeatable)', (val: string, acc: string[]) => [...acc, val], [])
|
|
71
|
+
.option('--experience <exp>', 'Experience tag (repeatable)', (val: string, acc: string[]) => [...acc, val], [])
|
|
52
72
|
.option('--nwc <uri>', 'Nostr Wallet Connect URI for auto-pay')
|
|
53
73
|
.option('--api-key <key>', 'Agentdex API key')
|
|
54
74
|
.option('--relay <url>', 'Additional relay (repeatable)', (val: string, acc: string[]) => [...acc, val], [])
|
|
55
75
|
.option('--json', 'Output JSON')
|
|
56
76
|
.action(async (options) => {
|
|
57
77
|
try {
|
|
58
|
-
const sk = resolveKey(options);
|
|
78
|
+
const sk = resolveKey(options, true);
|
|
59
79
|
const npub = getNpub(sk);
|
|
60
80
|
const pubHex = getPubkeyHex(sk);
|
|
61
81
|
|
|
@@ -80,6 +100,12 @@ program
|
|
|
80
100
|
|
|
81
101
|
const spinner = ora('Signing event...').start();
|
|
82
102
|
|
|
103
|
+
// Parse portfolio entries ("url,name,description")
|
|
104
|
+
const portfolio = (options.portfolio || []).map((entry: string) => {
|
|
105
|
+
const parts = entry.split(',').map((s: string) => s.trim());
|
|
106
|
+
return { url: parts[0], name: parts[1], description: parts[2] };
|
|
107
|
+
});
|
|
108
|
+
|
|
83
109
|
const event = createProfileEvent(sk, {
|
|
84
110
|
name,
|
|
85
111
|
description,
|
|
@@ -90,6 +116,9 @@ program
|
|
|
90
116
|
lightning: options.lightning,
|
|
91
117
|
ownerX: options.ownerX,
|
|
92
118
|
status: 'active',
|
|
119
|
+
portfolio: portfolio.length > 0 ? portfolio : undefined,
|
|
120
|
+
skills: options.skill?.length > 0 ? options.skill : undefined,
|
|
121
|
+
experience: options.experience?.length > 0 ? options.experience : undefined,
|
|
93
122
|
});
|
|
94
123
|
|
|
95
124
|
spinner.text = 'Registering on agentdex...';
|
|
@@ -151,6 +180,13 @@ program
|
|
|
151
180
|
console.log(chalk.gray(` Published to: ${published.join(', ')}`));
|
|
152
181
|
console.log('');
|
|
153
182
|
console.log(chalk.gray(` Run ${chalk.white('agentdex claim <name>')} to get ${chalk.hex('#D4A574')('<name>@agentdex.id')}`));
|
|
183
|
+
// Update kind 0 with lightning address if provided
|
|
184
|
+
if (options.lightning) {
|
|
185
|
+
try {
|
|
186
|
+
await updateKind0(sk, { lud16: options.lightning }, relays);
|
|
187
|
+
console.log(chalk.gray(` ⚡ Lightning address set in kind 0: ${options.lightning}`));
|
|
188
|
+
} catch {}
|
|
189
|
+
}
|
|
154
190
|
}
|
|
155
191
|
return;
|
|
156
192
|
}
|
|
@@ -179,6 +215,13 @@ program
|
|
|
179
215
|
console.log('');
|
|
180
216
|
console.log(chalk.gray(` Run ${chalk.white('agentdex claim <name>')} to get ${chalk.hex('#D4A574')('<name>@agentdex.id')}`));
|
|
181
217
|
console.log('');
|
|
218
|
+
// Update kind 0 with lightning address if provided
|
|
219
|
+
if (options.lightning) {
|
|
220
|
+
try {
|
|
221
|
+
await updateKind0(sk, { lud16: options.lightning }, relays);
|
|
222
|
+
console.log(chalk.gray(` ⚡ Lightning address set in kind 0: ${options.lightning}`));
|
|
223
|
+
} catch {}
|
|
224
|
+
}
|
|
182
225
|
console.log(chalk.gray(' Next: Claim a NIP-05 name to get verified (first 100 free, then 5000 sats).'));
|
|
183
226
|
}
|
|
184
227
|
} catch (err) {
|
|
@@ -205,6 +248,7 @@ program
|
|
|
205
248
|
.option('--key-file <path>', 'Path to JSON key file')
|
|
206
249
|
.option('--nwc <uri>', 'Nostr Wallet Connect URI for auto-pay')
|
|
207
250
|
.option('--api-key <key>', 'Agentdex API key')
|
|
251
|
+
.option('--lightning <addr>', 'Lightning address (lud16) to set in kind 0 profile')
|
|
208
252
|
.option('--skip-kind0', 'Skip publishing kind 0 profile to relays')
|
|
209
253
|
.option('--relay <url>', 'Additional relay', (val: string, acc: string[]) => [...acc, val], [])
|
|
210
254
|
.option('--json', 'Output JSON')
|
|
@@ -236,6 +280,7 @@ program
|
|
|
236
280
|
about: claim.agent?.description || undefined,
|
|
237
281
|
picture: claim.agent?.avatarUrl || undefined,
|
|
238
282
|
nip05: `${name}@agentdex.id`,
|
|
283
|
+
lud16: options.lightning || undefined,
|
|
239
284
|
});
|
|
240
285
|
const relays = ['wss://nos.lol', 'wss://relay.damus.io', ...(options.relay || [])];
|
|
241
286
|
const published = await publishToRelays(kind0, relays);
|
package/src/nostr.ts
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
* Nostr utilities — event creation, signing, publishing
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { finalizeEvent, getPublicKey } from 'nostr-tools/pure';
|
|
5
|
+
import { finalizeEvent, getPublicKey, generateSecretKey } from 'nostr-tools/pure';
|
|
6
6
|
import { nip19 } from 'nostr-tools';
|
|
7
7
|
import { SimplePool } from 'nostr-tools/pool';
|
|
8
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
8
9
|
|
|
9
10
|
const DEFAULT_RELAYS = ['wss://nos.lol', 'wss://relay.damus.io'];
|
|
10
11
|
|
|
12
|
+
export interface PortfolioItem {
|
|
13
|
+
url: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
export interface AgentProfile {
|
|
12
19
|
name: string;
|
|
13
20
|
description?: string;
|
|
@@ -23,6 +30,30 @@ export interface AgentProfile {
|
|
|
23
30
|
messagingPolicy?: string;
|
|
24
31
|
messagingMinTrust?: number;
|
|
25
32
|
messagingFee?: number;
|
|
33
|
+
portfolio?: PortfolioItem[];
|
|
34
|
+
skills?: string[];
|
|
35
|
+
experience?: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generate a new Nostr keypair and save to a JSON file.
|
|
40
|
+
* Returns the secret key as Uint8Array.
|
|
41
|
+
*/
|
|
42
|
+
export function generateAndSaveKeypair(outputPath: string): { sk: Uint8Array; npub: string; path: string } {
|
|
43
|
+
const sk = generateSecretKey();
|
|
44
|
+
const pk = getPublicKey(sk);
|
|
45
|
+
const nsec = nip19.nsecEncode(sk);
|
|
46
|
+
const npub = nip19.npubEncode(pk);
|
|
47
|
+
const skHex = Buffer.from(sk).toString('hex');
|
|
48
|
+
|
|
49
|
+
const data = JSON.stringify({ nsec, npub, sk_hex: skHex, pk_hex: pk }, null, 2);
|
|
50
|
+
|
|
51
|
+
// Ensure directory exists
|
|
52
|
+
const dir = outputPath.substring(0, outputPath.lastIndexOf('/'));
|
|
53
|
+
if (dir) mkdirSync(dir, { recursive: true });
|
|
54
|
+
|
|
55
|
+
writeFileSync(outputPath, data, { mode: 0o600 });
|
|
56
|
+
return { sk, npub, path: outputPath };
|
|
26
57
|
}
|
|
27
58
|
|
|
28
59
|
/**
|
|
@@ -76,13 +107,32 @@ export function createProfileEvent(sk: Uint8Array, profile: AgentProfile) {
|
|
|
76
107
|
if (profile.model) tags.push(['model', profile.model]);
|
|
77
108
|
if (profile.website) tags.push(['website', profile.website]);
|
|
78
109
|
if (profile.avatar) tags.push(['avatar', profile.avatar]);
|
|
79
|
-
|
|
110
|
+
// Lightning address belongs in kind 0 (lud16), not kind 31337
|
|
111
|
+
// Use --lightning flag to set lud16 in kind 0 during claim
|
|
80
112
|
if (profile.human) tags.push(['human', profile.human]);
|
|
81
113
|
if (profile.ownerX) tags.push(['owner_x', profile.ownerX]);
|
|
82
114
|
if (profile.status) tags.push(['status', profile.status || 'active']);
|
|
83
115
|
if (profile.messagingPolicy) tags.push(['messaging_policy', profile.messagingPolicy]);
|
|
84
116
|
if (profile.messagingMinTrust) tags.push(['messaging_min_trust', String(profile.messagingMinTrust)]);
|
|
85
117
|
if (profile.messagingFee) tags.push(['messaging_fee', String(profile.messagingFee)]);
|
|
118
|
+
if (profile.portfolio) {
|
|
119
|
+
for (const item of profile.portfolio) {
|
|
120
|
+
const tag = ['portfolio', item.url];
|
|
121
|
+
if (item.name) tag.push(item.name);
|
|
122
|
+
if (item.description) tag.push(item.description);
|
|
123
|
+
tags.push(tag);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (profile.skills) {
|
|
127
|
+
for (const skill of profile.skills) {
|
|
128
|
+
tags.push(['skill', skill]);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (profile.experience) {
|
|
132
|
+
for (const exp of profile.experience) {
|
|
133
|
+
tags.push(['experience', exp]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
86
136
|
|
|
87
137
|
const event = finalizeEvent({
|
|
88
138
|
kind: 31337,
|
|
@@ -121,12 +171,50 @@ export async function publishToRelays(event: object, relays: string[] = DEFAULT_
|
|
|
121
171
|
return published;
|
|
122
172
|
}
|
|
123
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Fetch existing kind 0, merge new fields, and republish.
|
|
176
|
+
* Used to set lud16 (lightning address) during registration.
|
|
177
|
+
*/
|
|
178
|
+
export async function updateKind0(sk: Uint8Array, updates: { lud16?: string }, relays: string[] = DEFAULT_RELAYS): Promise<string[]> {
|
|
179
|
+
const pool = new SimplePool();
|
|
180
|
+
const pubkey = getPublicKey(sk);
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// Fetch existing kind 0
|
|
184
|
+
let existing: Record<string, unknown> = {};
|
|
185
|
+
try {
|
|
186
|
+
const events = await Promise.race([
|
|
187
|
+
pool.querySync(relays, { kinds: [0], authors: [pubkey] }),
|
|
188
|
+
new Promise<any[]>((resolve) => setTimeout(() => resolve([]), 5000)),
|
|
189
|
+
]);
|
|
190
|
+
if (events.length > 0) {
|
|
191
|
+
existing = JSON.parse(events[0].content);
|
|
192
|
+
}
|
|
193
|
+
} catch {}
|
|
194
|
+
|
|
195
|
+
// Merge updates
|
|
196
|
+
if (updates.lud16) existing.lud16 = updates.lud16;
|
|
197
|
+
|
|
198
|
+
const event = finalizeEvent({
|
|
199
|
+
kind: 0,
|
|
200
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
201
|
+
tags: [],
|
|
202
|
+
content: JSON.stringify(existing),
|
|
203
|
+
}, sk);
|
|
204
|
+
|
|
205
|
+
const published = await publishToRelays(event, relays);
|
|
206
|
+
return published;
|
|
207
|
+
} finally {
|
|
208
|
+
pool.close(relays);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
124
212
|
/**
|
|
125
213
|
* Build and sign a kind 0 profile metadata event (for NIP-05 verification).
|
|
126
214
|
* After claiming a NIP-05 name, publish this to relays so Nostr clients
|
|
127
215
|
* (njump, Damus, Primal) can verify the identity.
|
|
128
216
|
*/
|
|
129
|
-
export function createKind0Event(sk: Uint8Array, profile: { name: string; about?: string; nip05?: string; picture?: string }) {
|
|
217
|
+
export function createKind0Event(sk: Uint8Array, profile: { name: string; about?: string; nip05?: string; picture?: string; lud16?: string }) {
|
|
130
218
|
return finalizeEvent({
|
|
131
219
|
kind: 0,
|
|
132
220
|
created_at: Math.floor(Date.now() / 1000),
|
|
@@ -136,6 +224,7 @@ export function createKind0Event(sk: Uint8Array, profile: { name: string; about?
|
|
|
136
224
|
...(profile.about && { about: profile.about }),
|
|
137
225
|
...(profile.nip05 && { nip05: profile.nip05 }),
|
|
138
226
|
...(profile.picture && { picture: profile.picture }),
|
|
227
|
+
...(profile.lud16 && { lud16: profile.lud16 }),
|
|
139
228
|
}),
|
|
140
229
|
}, sk);
|
|
141
230
|
}
|