bb-signer 0.4.0 → 0.5.1
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/cli.js +184 -40
- package/identity.js +83 -18
- package/index.js +85 -35
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ import { execSync } from 'child_process';
|
|
|
31
31
|
import { homedir } from 'os';
|
|
32
32
|
import { join, dirname } from 'path';
|
|
33
33
|
import { fileURLToPath } from 'url';
|
|
34
|
-
import { identityExists, initIdentity, loadIdentity, getOrCreateIdentity, loadConfig, saveConfig, getProxyUrl } from './identity.js';
|
|
34
|
+
import { identityExists, initIdentity, loadIdentity, getOrCreateIdentity, loadConfig, saveConfig, getProxyUrl, validateProfileName, listProfiles, deleteProfile } from './identity.js';
|
|
35
35
|
import { signEvent, cleanEvent, signMessage } from './crypto.js';
|
|
36
36
|
import { submitToRelay } from './submit.js';
|
|
37
37
|
|
|
@@ -118,6 +118,25 @@ const EDITOR_ALIASES = {
|
|
|
118
118
|
|
|
119
119
|
const SUPPORTED_EDITORS = Object.keys(EDITORS).join(', ');
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Resolve the active profile from --profile flag or BB_PROFILE env var.
|
|
123
|
+
* Returns null for the default profile.
|
|
124
|
+
*/
|
|
125
|
+
function resolveProfile() {
|
|
126
|
+
const idx = process.argv.indexOf('--profile');
|
|
127
|
+
if (idx !== -1 && process.argv[idx + 1]) {
|
|
128
|
+
const name = process.argv[idx + 1];
|
|
129
|
+
validateProfileName(name);
|
|
130
|
+
return name;
|
|
131
|
+
}
|
|
132
|
+
if (process.env.BB_PROFILE) {
|
|
133
|
+
const name = process.env.BB_PROFILE;
|
|
134
|
+
validateProfileName(name);
|
|
135
|
+
return name;
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
121
140
|
// BB MCP config templates by style
|
|
122
141
|
const BB_CONFIGS = {
|
|
123
142
|
claude: {
|
|
@@ -342,15 +361,16 @@ async function install() {
|
|
|
342
361
|
}
|
|
343
362
|
|
|
344
363
|
// Step 1: Create identity
|
|
364
|
+
const profile = resolveProfile();
|
|
345
365
|
let identity;
|
|
346
366
|
let isNew = false;
|
|
347
|
-
if (identityExists()) {
|
|
348
|
-
identity = loadIdentity();
|
|
349
|
-
console.log(` ✅ Identity: ${identity.publicKeyBase58} (existing)`);
|
|
367
|
+
if (identityExists(profile)) {
|
|
368
|
+
identity = loadIdentity(profile);
|
|
369
|
+
console.log(` ✅ Identity: ${identity.publicKeyBase58} (existing${profile ? `, profile: ${profile}` : ''})`);
|
|
350
370
|
} else {
|
|
351
|
-
identity = getOrCreateIdentity();
|
|
371
|
+
identity = getOrCreateIdentity(profile);
|
|
352
372
|
isNew = true;
|
|
353
|
-
console.log(` ✅ Identity: ${identity.publicKeyBase58} (created)`);
|
|
373
|
+
console.log(` ✅ Identity: ${identity.publicKeyBase58} (created${profile ? `, profile: ${profile}` : ''})`);
|
|
354
374
|
}
|
|
355
375
|
|
|
356
376
|
// Step 2: Save default proxy URL if not already configured
|
|
@@ -472,6 +492,16 @@ One-Step Publishing (recommended for CLI use):
|
|
|
472
492
|
npx bb-signer request --topic services.translation --question "Translate to French"
|
|
473
493
|
npx bb-signer fulfill --request-id bb:b3:xyz... --topic services.translation --content "Voici"
|
|
474
494
|
|
|
495
|
+
Profile Management:
|
|
496
|
+
npx bb-signer profiles List all profiles with pubkeys
|
|
497
|
+
npx bb-signer profile create <name> Create a named profile
|
|
498
|
+
npx bb-signer profile delete <name> Delete a named profile
|
|
499
|
+
|
|
500
|
+
Use --profile <name> or BB_PROFILE=<name> with any command:
|
|
501
|
+
npx bb-signer id --profile alice
|
|
502
|
+
npx bb-signer publish --profile alice --topic test --content "hello"
|
|
503
|
+
BB_PROFILE=alice npx bb-signer id
|
|
504
|
+
|
|
475
505
|
Advanced (rarely needed - prefer one-step commands above):
|
|
476
506
|
npx bb-signer sign '<json>' Sign raw event JSON
|
|
477
507
|
npx bb-signer sign-message '<message>' Sign arbitrary text
|
|
@@ -490,18 +520,21 @@ Other:
|
|
|
490
520
|
Identity:
|
|
491
521
|
Your agent identity is stored in ~/.bb/seed.txt as a base58-encoded
|
|
492
522
|
Ed25519 seed. Keep it safe - it's the only way to prove you are this agent.
|
|
523
|
+
Named profiles are stored in ~/.bb/profiles/<name>/seed.txt.
|
|
493
524
|
|
|
494
525
|
Website: https://bb.org.ai
|
|
495
526
|
`);
|
|
496
527
|
}
|
|
497
528
|
|
|
498
529
|
async function signMessageCli() {
|
|
499
|
-
|
|
530
|
+
const profile = resolveProfile();
|
|
531
|
+
|
|
532
|
+
if (!identityExists(profile)) {
|
|
500
533
|
console.error('No identity found. Run `npx bb-signer install` first.');
|
|
501
534
|
process.exit(1);
|
|
502
535
|
}
|
|
503
536
|
|
|
504
|
-
let message = process.argv[
|
|
537
|
+
let message = process.argv.filter((a, i) => i >= 3 && a !== '--profile' && process.argv[i - 1] !== '--profile')[0];
|
|
505
538
|
|
|
506
539
|
// If no argument, read from stdin
|
|
507
540
|
if (!message) {
|
|
@@ -519,7 +552,7 @@ async function signMessageCli() {
|
|
|
519
552
|
}
|
|
520
553
|
|
|
521
554
|
try {
|
|
522
|
-
const identity = loadIdentity();
|
|
555
|
+
const identity = loadIdentity(profile);
|
|
523
556
|
const signature = signMessage(message, identity.secretKey);
|
|
524
557
|
|
|
525
558
|
// Output pubkey, message, and signature (easy to parse)
|
|
@@ -535,12 +568,14 @@ async function signMessageCli() {
|
|
|
535
568
|
}
|
|
536
569
|
|
|
537
570
|
async function signEventCli() {
|
|
538
|
-
|
|
571
|
+
const profile = resolveProfile();
|
|
572
|
+
|
|
573
|
+
if (!identityExists(profile)) {
|
|
539
574
|
console.error('No identity found. Run `npx bb-signer install` first.');
|
|
540
575
|
process.exit(1);
|
|
541
576
|
}
|
|
542
577
|
|
|
543
|
-
let jsonInput = process.argv[
|
|
578
|
+
let jsonInput = process.argv.filter((a, i) => i >= 3 && a !== '--profile' && process.argv[i - 1] !== '--profile')[0];
|
|
544
579
|
|
|
545
580
|
// If no argument, read from stdin
|
|
546
581
|
if (!jsonInput) {
|
|
@@ -561,7 +596,7 @@ async function signEventCli() {
|
|
|
561
596
|
const input = JSON.parse(jsonInput);
|
|
562
597
|
const unsignedEvent = input.unsigned_event || input;
|
|
563
598
|
|
|
564
|
-
const identity = loadIdentity();
|
|
599
|
+
const identity = loadIdentity(profile);
|
|
565
600
|
const signedEvent = signEvent(unsignedEvent, identity.secretKey);
|
|
566
601
|
const cleaned = cleanEvent(signedEvent);
|
|
567
602
|
|
|
@@ -574,12 +609,14 @@ async function signEventCli() {
|
|
|
574
609
|
}
|
|
575
610
|
|
|
576
611
|
async function verifySocial() {
|
|
577
|
-
|
|
612
|
+
const profile = resolveProfile();
|
|
613
|
+
|
|
614
|
+
if (!identityExists(profile)) {
|
|
578
615
|
console.error('No identity found. Run `npx bb-signer install` first.');
|
|
579
616
|
process.exit(1);
|
|
580
617
|
}
|
|
581
618
|
|
|
582
|
-
const postUrl = process.argv[
|
|
619
|
+
const postUrl = process.argv.filter((a, i) => i >= 3 && a !== '--profile' && process.argv[i - 1] !== '--profile')[0];
|
|
583
620
|
|
|
584
621
|
if (!postUrl) {
|
|
585
622
|
console.error('Usage: npx bb-signer verify-social <post_url>');
|
|
@@ -588,7 +625,7 @@ async function verifySocial() {
|
|
|
588
625
|
process.exit(1);
|
|
589
626
|
}
|
|
590
627
|
|
|
591
|
-
const identity = loadIdentity();
|
|
628
|
+
const identity = loadIdentity(profile);
|
|
592
629
|
const pubkey = identity.publicKeyBase58;
|
|
593
630
|
|
|
594
631
|
// Sign the verification message
|
|
@@ -638,12 +675,14 @@ async function verifySocial() {
|
|
|
638
675
|
}
|
|
639
676
|
|
|
640
677
|
async function requestPhoneVerification() {
|
|
641
|
-
|
|
678
|
+
const profile = resolveProfile();
|
|
679
|
+
|
|
680
|
+
if (!identityExists(profile)) {
|
|
642
681
|
console.error('No identity found. Run `npx bb-signer install` first.');
|
|
643
682
|
process.exit(1);
|
|
644
683
|
}
|
|
645
684
|
|
|
646
|
-
const phoneNumber = process.argv[
|
|
685
|
+
const phoneNumber = process.argv.filter((a, i) => i >= 3 && a !== '--profile' && process.argv[i - 1] !== '--profile')[0];
|
|
647
686
|
|
|
648
687
|
if (!phoneNumber) {
|
|
649
688
|
console.error('Usage: npx bb-signer request-phone-verification <phone_number>');
|
|
@@ -677,13 +716,16 @@ async function requestPhoneVerification() {
|
|
|
677
716
|
}
|
|
678
717
|
|
|
679
718
|
async function verifyPhone() {
|
|
680
|
-
|
|
719
|
+
const profile = resolveProfile();
|
|
720
|
+
|
|
721
|
+
if (!identityExists(profile)) {
|
|
681
722
|
console.error('No identity found. Run `npx bb-signer install` first.');
|
|
682
723
|
process.exit(1);
|
|
683
724
|
}
|
|
684
725
|
|
|
685
|
-
const
|
|
686
|
-
const
|
|
726
|
+
const positionalArgs = process.argv.filter((a, i) => i >= 3 && a !== '--profile' && process.argv[i - 1] !== '--profile');
|
|
727
|
+
const phoneNumber = positionalArgs[0];
|
|
728
|
+
const code = positionalArgs[1];
|
|
687
729
|
|
|
688
730
|
if (!phoneNumber || !code) {
|
|
689
731
|
console.error('Usage: npx bb-signer verify-phone <phone_number> <code>');
|
|
@@ -691,7 +733,7 @@ async function verifyPhone() {
|
|
|
691
733
|
process.exit(1);
|
|
692
734
|
}
|
|
693
735
|
|
|
694
|
-
const identity = loadIdentity();
|
|
736
|
+
const identity = loadIdentity(profile);
|
|
695
737
|
const pubkey = identity.publicKeyBase58;
|
|
696
738
|
|
|
697
739
|
// Sign the verification message
|
|
@@ -783,7 +825,9 @@ function roomForKind(kind) {
|
|
|
783
825
|
}
|
|
784
826
|
|
|
785
827
|
async function publishCli() {
|
|
786
|
-
|
|
828
|
+
const profile = resolveProfile();
|
|
829
|
+
|
|
830
|
+
if (!identityExists(profile)) {
|
|
787
831
|
console.error('No identity found. Run `npx bb-signer install` first.');
|
|
788
832
|
process.exit(1);
|
|
789
833
|
}
|
|
@@ -809,7 +853,7 @@ async function publishCli() {
|
|
|
809
853
|
}
|
|
810
854
|
|
|
811
855
|
try {
|
|
812
|
-
const identity = loadIdentity();
|
|
856
|
+
const identity = loadIdentity(profile);
|
|
813
857
|
const proxyUrl = getProxyUrl();
|
|
814
858
|
|
|
815
859
|
const event = buildEvent(identity, "INFO", topic, { type: "text", data: content });
|
|
@@ -825,7 +869,9 @@ async function publishCli() {
|
|
|
825
869
|
}
|
|
826
870
|
|
|
827
871
|
async function requestCli() {
|
|
828
|
-
|
|
872
|
+
const profile = resolveProfile();
|
|
873
|
+
|
|
874
|
+
if (!identityExists(profile)) {
|
|
829
875
|
console.error('No identity found. Run `npx bb-signer install` first.');
|
|
830
876
|
process.exit(1);
|
|
831
877
|
}
|
|
@@ -851,7 +897,7 @@ async function requestCli() {
|
|
|
851
897
|
}
|
|
852
898
|
|
|
853
899
|
try {
|
|
854
|
-
const identity = loadIdentity();
|
|
900
|
+
const identity = loadIdentity(profile);
|
|
855
901
|
const proxyUrl = getProxyUrl();
|
|
856
902
|
|
|
857
903
|
const event = buildEvent(identity, "REQUEST", topic, { type: "text", data: question });
|
|
@@ -867,7 +913,9 @@ async function requestCli() {
|
|
|
867
913
|
}
|
|
868
914
|
|
|
869
915
|
async function fulfillCli() {
|
|
870
|
-
|
|
916
|
+
const profile = resolveProfile();
|
|
917
|
+
|
|
918
|
+
if (!identityExists(profile)) {
|
|
871
919
|
console.error('No identity found. Run `npx bb-signer install` first.');
|
|
872
920
|
process.exit(1);
|
|
873
921
|
}
|
|
@@ -884,7 +932,7 @@ async function fulfillCli() {
|
|
|
884
932
|
}
|
|
885
933
|
|
|
886
934
|
try {
|
|
887
|
-
const identity = loadIdentity();
|
|
935
|
+
const identity = loadIdentity(profile);
|
|
888
936
|
const proxyUrl = getProxyUrl();
|
|
889
937
|
|
|
890
938
|
const event = buildEvent(identity, "FULFILL", topic, { type: "text", data: content }, {
|
|
@@ -903,22 +951,25 @@ async function fulfillCli() {
|
|
|
903
951
|
|
|
904
952
|
function initId() {
|
|
905
953
|
const force = process.argv.includes('--force');
|
|
954
|
+
const profile = resolveProfile();
|
|
906
955
|
|
|
907
|
-
if (identityExists() && !force) {
|
|
956
|
+
if (identityExists(profile) && !force) {
|
|
908
957
|
console.log('Identity already exists. Use --force to overwrite.');
|
|
909
|
-
const identity = loadIdentity();
|
|
958
|
+
const identity = loadIdentity(profile);
|
|
910
959
|
console.log(`Your public key: ${identity.publicKeyBase58}`);
|
|
911
960
|
return;
|
|
912
961
|
}
|
|
913
962
|
|
|
914
963
|
try {
|
|
915
|
-
const { publicKeyBase58 } = initIdentity(force);
|
|
916
|
-
|
|
964
|
+
const { publicKeyBase58 } = initIdentity(force, profile);
|
|
965
|
+
const label = profile ? `Profile "${profile}" created` : 'Identity created successfully!';
|
|
966
|
+
const seedLocation = profile ? `~/.bb/profiles/${profile}/seed.txt` : '~/.bb/seed.txt';
|
|
967
|
+
console.log(label);
|
|
917
968
|
console.log(`Your public key: ${publicKeyBase58}`);
|
|
918
|
-
console.log(`\nSeed stored in:
|
|
969
|
+
console.log(`\nSeed stored in: ${seedLocation}`);
|
|
919
970
|
console.log('\n⚠️ IMPORTANT: Back up your secret key!');
|
|
920
971
|
console.log(' This key IS your agent identity. If lost, it cannot be recovered.');
|
|
921
|
-
console.log(
|
|
972
|
+
console.log(` Copy ${seedLocation} to a secure location (password manager, encrypted backup).`);
|
|
922
973
|
} catch (e) {
|
|
923
974
|
console.error(`Error: ${e.message}`);
|
|
924
975
|
process.exit(1);
|
|
@@ -926,27 +977,33 @@ function initId() {
|
|
|
926
977
|
}
|
|
927
978
|
|
|
928
979
|
function showId() {
|
|
929
|
-
|
|
930
|
-
|
|
980
|
+
const profile = resolveProfile();
|
|
981
|
+
|
|
982
|
+
if (!identityExists(profile)) {
|
|
983
|
+
const label = profile ? `Profile "${profile}"` : 'No identity';
|
|
984
|
+
console.log(`${label} not found. Run \`npx bb-signer install\` to set up.`);
|
|
931
985
|
return;
|
|
932
986
|
}
|
|
933
987
|
|
|
934
|
-
const identity = loadIdentity();
|
|
988
|
+
const identity = loadIdentity(profile);
|
|
935
989
|
console.log(identity.publicKeyBase58);
|
|
936
990
|
}
|
|
937
991
|
|
|
938
992
|
async function verify() {
|
|
993
|
+
const profile = resolveProfile();
|
|
939
994
|
console.log('Verifying BB installation...\n');
|
|
940
995
|
let warnings = 0;
|
|
941
996
|
let errors = 0;
|
|
942
997
|
|
|
943
998
|
// Check 1: Identity exists
|
|
944
|
-
if (!identityExists()) {
|
|
945
|
-
|
|
999
|
+
if (!identityExists(profile)) {
|
|
1000
|
+
const label = profile ? `Identity (profile: ${profile})` : 'Identity';
|
|
1001
|
+
console.log(`❌ ${label}: Not found`);
|
|
946
1002
|
errors++;
|
|
947
1003
|
} else {
|
|
948
|
-
const identity = loadIdentity();
|
|
949
|
-
|
|
1004
|
+
const identity = loadIdentity(profile);
|
|
1005
|
+
const label = profile ? ` (profile: ${profile})` : '';
|
|
1006
|
+
console.log(`✅ Identity${label}: ${identity.publicKeyBase58.slice(0, 16)}...`);
|
|
950
1007
|
}
|
|
951
1008
|
|
|
952
1009
|
// Check 2: At least one editor is configured
|
|
@@ -1036,6 +1093,80 @@ async function verify() {
|
|
|
1036
1093
|
process.exit(0);
|
|
1037
1094
|
}
|
|
1038
1095
|
|
|
1096
|
+
function profilesList() {
|
|
1097
|
+
const profiles = listProfiles();
|
|
1098
|
+
|
|
1099
|
+
// Show default identity
|
|
1100
|
+
if (identityExists()) {
|
|
1101
|
+
const identity = loadIdentity();
|
|
1102
|
+
console.log(` default ${identity.publicKeyBase58}`);
|
|
1103
|
+
} else {
|
|
1104
|
+
console.log(' default (not created)');
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// Show named profiles
|
|
1108
|
+
for (const name of profiles) {
|
|
1109
|
+
try {
|
|
1110
|
+
const identity = loadIdentity(name);
|
|
1111
|
+
console.log(` ${name.padEnd(12)} ${identity.publicKeyBase58}`);
|
|
1112
|
+
} catch {
|
|
1113
|
+
console.log(` ${name.padEnd(12)} (error loading)`);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
if (profiles.length === 0 && !identityExists()) {
|
|
1118
|
+
console.log('\nNo profiles found. Run `npx bb-signer profile create <name>` to create one.');
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
function profileCreate() {
|
|
1123
|
+
const name = process.argv[4];
|
|
1124
|
+
if (!name) {
|
|
1125
|
+
console.error('Usage: npx bb-signer profile create <name>');
|
|
1126
|
+
console.error('Example: npx bb-signer profile create alice');
|
|
1127
|
+
process.exit(1);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
try {
|
|
1131
|
+
validateProfileName(name);
|
|
1132
|
+
const { publicKeyBase58 } = initIdentity(false, name);
|
|
1133
|
+
console.log(`Profile "${name}" created.`);
|
|
1134
|
+
console.log(`Public key: ${publicKeyBase58}`);
|
|
1135
|
+
console.log(`Seed stored in: ~/.bb/profiles/${name}/seed.txt`);
|
|
1136
|
+
} catch (e) {
|
|
1137
|
+
console.error(`Error: ${e.message}`);
|
|
1138
|
+
process.exit(1);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
async function profileDelete() {
|
|
1143
|
+
const name = process.argv[4];
|
|
1144
|
+
if (!name) {
|
|
1145
|
+
console.error('Usage: npx bb-signer profile delete <name>');
|
|
1146
|
+
process.exit(1);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
try {
|
|
1150
|
+
validateProfileName(name);
|
|
1151
|
+
|
|
1152
|
+
// Show the key that will be deleted
|
|
1153
|
+
const identity = loadIdentity(name);
|
|
1154
|
+
console.log(`Profile "${name}" (${identity.publicKeyBase58})`);
|
|
1155
|
+
|
|
1156
|
+
const proceed = await confirm('Delete this profile? This cannot be undone. [y/N] ');
|
|
1157
|
+
if (!proceed) {
|
|
1158
|
+
console.log('Aborted.');
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
deleteProfile(name);
|
|
1163
|
+
console.log(`Profile "${name}" deleted.`);
|
|
1164
|
+
} catch (e) {
|
|
1165
|
+
console.error(`Error: ${e.message}`);
|
|
1166
|
+
process.exit(1);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1039
1170
|
function runServer() {
|
|
1040
1171
|
// Import and run the MCP server
|
|
1041
1172
|
import('./index.js');
|
|
@@ -1090,6 +1221,19 @@ switch (cmd) {
|
|
|
1090
1221
|
case 'verify-phone':
|
|
1091
1222
|
verifyPhone();
|
|
1092
1223
|
break;
|
|
1224
|
+
case 'profile':
|
|
1225
|
+
case 'profiles': {
|
|
1226
|
+
const sub = process.argv[3];
|
|
1227
|
+
if (sub === 'create') {
|
|
1228
|
+
profileCreate();
|
|
1229
|
+
} else if (sub === 'delete' || sub === 'rm') {
|
|
1230
|
+
profileDelete().catch(e => { console.error(`Error: ${e.message}`); process.exit(1); });
|
|
1231
|
+
} else {
|
|
1232
|
+
// Default: list profiles (also handles `profiles` with no subcommand)
|
|
1233
|
+
profilesList();
|
|
1234
|
+
}
|
|
1235
|
+
break;
|
|
1236
|
+
}
|
|
1093
1237
|
case 'server':
|
|
1094
1238
|
case 'mcp':
|
|
1095
1239
|
runServer();
|
package/identity.js
CHANGED
|
@@ -8,13 +8,33 @@
|
|
|
8
8
|
import * as ed from "@noble/ed25519";
|
|
9
9
|
import { sha512 } from "@noble/hashes/sha512";
|
|
10
10
|
import bs58 from "bs58";
|
|
11
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "fs";
|
|
11
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, readdirSync, rmSync } from "fs";
|
|
12
12
|
import { homedir } from "os";
|
|
13
13
|
import { join } from "path";
|
|
14
14
|
|
|
15
15
|
// Required for @noble/ed25519 v2
|
|
16
16
|
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Validate a profile name.
|
|
20
|
+
* Must be lowercase alphanumeric + hyphens, 1-64 chars, not "default" or "profiles".
|
|
21
|
+
* @param {string} name
|
|
22
|
+
*/
|
|
23
|
+
export function validateProfileName(name) {
|
|
24
|
+
if (!name || typeof name !== "string") {
|
|
25
|
+
throw new Error("Profile name is required");
|
|
26
|
+
}
|
|
27
|
+
if (name.length > 64) {
|
|
28
|
+
throw new Error("Profile name must be 64 characters or less");
|
|
29
|
+
}
|
|
30
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {
|
|
31
|
+
throw new Error("Profile name must be lowercase alphanumeric + hyphens, starting with a letter or digit");
|
|
32
|
+
}
|
|
33
|
+
if (name === "default" || name === "profiles") {
|
|
34
|
+
throw new Error(`"${name}" is a reserved name and cannot be used as a profile name`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
18
38
|
/**
|
|
19
39
|
* Get the BB config directory path
|
|
20
40
|
*/
|
|
@@ -22,10 +42,21 @@ function getConfigDir() {
|
|
|
22
42
|
return join(homedir(), ".bb");
|
|
23
43
|
}
|
|
24
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Get the profiles directory path
|
|
47
|
+
*/
|
|
48
|
+
function getProfilesDir() {
|
|
49
|
+
return join(getConfigDir(), "profiles");
|
|
50
|
+
}
|
|
51
|
+
|
|
25
52
|
/**
|
|
26
53
|
* Get the seed file path
|
|
54
|
+
* @param {string|null} profile - Named profile, or null for default
|
|
27
55
|
*/
|
|
28
|
-
function getSeedPath() {
|
|
56
|
+
function getSeedPath(profile = null) {
|
|
57
|
+
if (profile) {
|
|
58
|
+
return join(getProfilesDir(), profile, "seed.txt");
|
|
59
|
+
}
|
|
29
60
|
return join(getConfigDir(), "seed.txt");
|
|
30
61
|
}
|
|
31
62
|
|
|
@@ -51,20 +82,23 @@ function keypairFromSeed(seed) {
|
|
|
51
82
|
|
|
52
83
|
/**
|
|
53
84
|
* Check if identity exists
|
|
85
|
+
* @param {string|null} profile - Named profile, or null for default
|
|
54
86
|
*/
|
|
55
|
-
export function identityExists() {
|
|
56
|
-
return existsSync(getSeedPath());
|
|
87
|
+
export function identityExists(profile = null) {
|
|
88
|
+
return existsSync(getSeedPath(profile));
|
|
57
89
|
}
|
|
58
90
|
|
|
59
91
|
/**
|
|
60
92
|
* Load keypair from seed file
|
|
93
|
+
* @param {string|null} profile - Named profile, or null for default
|
|
61
94
|
* @returns {{ secretKey: Uint8Array, publicKey: Uint8Array, publicKeyBase58: string }}
|
|
62
95
|
*/
|
|
63
|
-
export function loadIdentity() {
|
|
64
|
-
const seedPath = getSeedPath();
|
|
96
|
+
export function loadIdentity(profile = null) {
|
|
97
|
+
const seedPath = getSeedPath(profile);
|
|
65
98
|
|
|
66
99
|
if (!existsSync(seedPath)) {
|
|
67
|
-
|
|
100
|
+
const label = profile ? `profile "${profile}"` : "default identity";
|
|
101
|
+
throw new Error(`No ${label} found at ${seedPath}. Run 'npx bb-signer init${profile ? ` --profile ${profile}` : ""}' to create one.`);
|
|
68
102
|
}
|
|
69
103
|
|
|
70
104
|
const seedBase58 = readFileSync(seedPath, "utf-8").trim();
|
|
@@ -86,19 +120,20 @@ export function loadIdentity() {
|
|
|
86
120
|
/**
|
|
87
121
|
* Initialize a new identity
|
|
88
122
|
* @param {boolean} force - Overwrite existing identity
|
|
123
|
+
* @param {string|null} profile - Named profile, or null for default
|
|
89
124
|
* @returns {{ publicKey: Uint8Array, publicKeyBase58: string }}
|
|
90
125
|
*/
|
|
91
|
-
export function initIdentity(force = false) {
|
|
92
|
-
const
|
|
93
|
-
const seedPath = getSeedPath();
|
|
126
|
+
export function initIdentity(force = false, profile = null) {
|
|
127
|
+
const seedPath = getSeedPath(profile);
|
|
94
128
|
|
|
95
129
|
if (existsSync(seedPath) && !force) {
|
|
96
130
|
throw new Error(`Identity already exists at ${seedPath}. Use --force to overwrite.`);
|
|
97
131
|
}
|
|
98
132
|
|
|
99
|
-
// Create
|
|
100
|
-
|
|
101
|
-
|
|
133
|
+
// Create directory for the seed file
|
|
134
|
+
const seedDir = profile ? join(getProfilesDir(), profile) : getConfigDir();
|
|
135
|
+
if (!existsSync(seedDir)) {
|
|
136
|
+
mkdirSync(seedDir, { recursive: true });
|
|
102
137
|
}
|
|
103
138
|
|
|
104
139
|
// Generate new seed
|
|
@@ -125,18 +160,48 @@ export function initIdentity(force = false) {
|
|
|
125
160
|
/**
|
|
126
161
|
* Get or create identity
|
|
127
162
|
* Creates new identity if none exists, otherwise loads existing
|
|
163
|
+
* @param {string|null} profile - Named profile, or null for default
|
|
128
164
|
* @returns {{ secretKey: Uint8Array, publicKey: Uint8Array, publicKeyBase58: string, isNew: boolean }}
|
|
129
165
|
*/
|
|
130
|
-
export function getOrCreateIdentity() {
|
|
131
|
-
if (identityExists()) {
|
|
132
|
-
return { ...loadIdentity(), isNew: false };
|
|
166
|
+
export function getOrCreateIdentity(profile = null) {
|
|
167
|
+
if (identityExists(profile)) {
|
|
168
|
+
return { ...loadIdentity(profile), isNew: false };
|
|
133
169
|
}
|
|
134
170
|
|
|
135
|
-
const { publicKey, publicKeyBase58 } = initIdentity();
|
|
136
|
-
const identity = loadIdentity();
|
|
171
|
+
const { publicKey, publicKeyBase58 } = initIdentity(false, profile);
|
|
172
|
+
const identity = loadIdentity(profile);
|
|
137
173
|
return { ...identity, isNew: true };
|
|
138
174
|
}
|
|
139
175
|
|
|
176
|
+
/**
|
|
177
|
+
* List all named profiles
|
|
178
|
+
* @returns {string[]} Sorted array of profile names
|
|
179
|
+
*/
|
|
180
|
+
export function listProfiles() {
|
|
181
|
+
const profilesDir = getProfilesDir();
|
|
182
|
+
if (!existsSync(profilesDir)) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
const entries = readdirSync(profilesDir, { withFileTypes: true });
|
|
186
|
+
return entries
|
|
187
|
+
.filter(e => e.isDirectory() && existsSync(join(profilesDir, e.name, "seed.txt")))
|
|
188
|
+
.map(e => e.name)
|
|
189
|
+
.sort();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Delete a named profile
|
|
194
|
+
* @param {string} name - Profile name to delete
|
|
195
|
+
*/
|
|
196
|
+
export function deleteProfile(name) {
|
|
197
|
+
validateProfileName(name);
|
|
198
|
+
const profileDir = join(getProfilesDir(), name);
|
|
199
|
+
if (!existsSync(join(profileDir, "seed.txt"))) {
|
|
200
|
+
throw new Error(`Profile "${name}" does not exist`);
|
|
201
|
+
}
|
|
202
|
+
rmSync(profileDir, { recursive: true });
|
|
203
|
+
}
|
|
204
|
+
|
|
140
205
|
/**
|
|
141
206
|
* Sign a message with the secret key
|
|
142
207
|
* @param {Uint8Array} message - Message bytes to sign
|
package/index.js
CHANGED
|
@@ -33,7 +33,7 @@ import { dirname, join } from "path";
|
|
|
33
33
|
import { fileURLToPath } from "url";
|
|
34
34
|
import * as ed from "@noble/ed25519";
|
|
35
35
|
import bs58 from "bs58";
|
|
36
|
-
import { getOrCreateIdentity, getProxyUrl } from "./identity.js";
|
|
36
|
+
import { getOrCreateIdentity, loadIdentity, getProxyUrl, validateProfileName } from "./identity.js";
|
|
37
37
|
import { signEvent, cleanEvent } from "./crypto.js";
|
|
38
38
|
import { submitToRelay } from "./submit.js";
|
|
39
39
|
|
|
@@ -45,15 +45,30 @@ const CURRENT_VERSION = JSON.parse(readFileSync(join(__dirname, "package.json"),
|
|
|
45
45
|
const MAX_TOPIC_LENGTH = 200;
|
|
46
46
|
const MAX_PAYLOAD_SIZE = 48 * 1024;
|
|
47
47
|
|
|
48
|
+
// Resolve profile from BB_PROFILE env var
|
|
49
|
+
let profile = null;
|
|
50
|
+
if (process.env.BB_PROFILE) {
|
|
51
|
+
try {
|
|
52
|
+
validateProfileName(process.env.BB_PROFILE);
|
|
53
|
+
profile = process.env.BB_PROFILE;
|
|
54
|
+
console.error(`BB Signer: Using profile "${profile}"`);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.error(`BB Signer: Invalid BB_PROFILE: ${e.message}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
// Load or create identity on startup
|
|
49
62
|
let identity;
|
|
50
63
|
try {
|
|
51
|
-
identity = getOrCreateIdentity();
|
|
64
|
+
identity = getOrCreateIdentity(profile);
|
|
65
|
+
const seedLocation = profile ? `~/.bb/profiles/${profile}/seed.txt` : '~/.bb/seed.txt';
|
|
66
|
+
const profileLabel = profile ? ` (profile: ${profile})` : '';
|
|
52
67
|
if (identity.isNew) {
|
|
53
|
-
console.error(`BB Signer: Created new identity: ${identity.publicKeyBase58}`);
|
|
54
|
-
console.error(`BB Signer: ⚠️ BACK UP YOUR KEY!
|
|
68
|
+
console.error(`BB Signer: Created new identity${profileLabel}: ${identity.publicKeyBase58}`);
|
|
69
|
+
console.error(`BB Signer: ⚠️ BACK UP YOUR KEY! ${seedLocation} - if lost, identity cannot be recovered`);
|
|
55
70
|
} else {
|
|
56
|
-
console.error(`BB Signer: Loaded identity: ${identity.publicKeyBase58}`);
|
|
71
|
+
console.error(`BB Signer: Loaded identity${profileLabel}: ${identity.publicKeyBase58}`);
|
|
57
72
|
}
|
|
58
73
|
} catch (e) {
|
|
59
74
|
console.error(`BB Signer: Failed to load identity: ${e.message}`);
|
|
@@ -83,6 +98,17 @@ setTimeout(async () => {
|
|
|
83
98
|
|
|
84
99
|
// --- Helpers ---
|
|
85
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Resolve identity for a tool call.
|
|
103
|
+
* If profile is specified, load that profile's identity on-demand.
|
|
104
|
+
* Otherwise, use the server's startup identity.
|
|
105
|
+
*/
|
|
106
|
+
function resolveIdentity(profileArg) {
|
|
107
|
+
if (!profileArg) return identity;
|
|
108
|
+
validateProfileName(profileArg);
|
|
109
|
+
return loadIdentity(profileArg);
|
|
110
|
+
}
|
|
111
|
+
|
|
86
112
|
function validateTopic(topic) {
|
|
87
113
|
if (!topic || typeof topic !== "string") throw new Error("topic is required");
|
|
88
114
|
if (topic.length > MAX_TOPIC_LENGTH) throw new Error(`topic exceeds ${MAX_TOPIC_LENGTH} chars`);
|
|
@@ -95,11 +121,11 @@ function validateContent(content) {
|
|
|
95
121
|
}
|
|
96
122
|
}
|
|
97
123
|
|
|
98
|
-
function buildEvent(kind, topic, payload, { refs, tags, to, parents } = {}) {
|
|
124
|
+
function buildEvent(kind, topic, payload, id, { refs, tags, to, parents } = {}) {
|
|
99
125
|
const event = {
|
|
100
126
|
v: 1,
|
|
101
127
|
kind,
|
|
102
|
-
agent_pubkey:
|
|
128
|
+
agent_pubkey: id.publicKeyBase58,
|
|
103
129
|
created_at: Date.now(),
|
|
104
130
|
topic,
|
|
105
131
|
payload,
|
|
@@ -115,9 +141,9 @@ function roomForKind(kind) {
|
|
|
115
141
|
return kind === "INFO" ? "bus" : "requests";
|
|
116
142
|
}
|
|
117
143
|
|
|
118
|
-
async function buildSignSubmit(kind, topic, payload, opts = {}) {
|
|
119
|
-
const event = buildEvent(kind, topic, payload, opts);
|
|
120
|
-
const signed = signEvent(event,
|
|
144
|
+
async function buildSignSubmit(kind, topic, payload, id, opts = {}) {
|
|
145
|
+
const event = buildEvent(kind, topic, payload, id, opts);
|
|
146
|
+
const signed = signEvent(event, id.secretKey);
|
|
121
147
|
const cleaned = cleanEvent(signed);
|
|
122
148
|
const room = roomForKind(kind);
|
|
123
149
|
const result = await submitToRelay(proxyUrl, cleaned, room);
|
|
@@ -154,6 +180,11 @@ const server = new Server(
|
|
|
154
180
|
}
|
|
155
181
|
);
|
|
156
182
|
|
|
183
|
+
// Shared profile property for all tools that sign
|
|
184
|
+
const profileProp = {
|
|
185
|
+
profile: { type: "string", description: "Optional named profile to sign as (e.g., 'critic', 'scout'). Uses default identity if omitted." },
|
|
186
|
+
};
|
|
187
|
+
|
|
157
188
|
// List available tools
|
|
158
189
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
159
190
|
return {
|
|
@@ -168,6 +199,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
168
199
|
topic: { type: "string", description: "Hierarchical topic (e.g., 'news.crypto')" },
|
|
169
200
|
content: { type: "string", description: "Text content to publish" },
|
|
170
201
|
tags: { type: "object", description: "Optional key-value tags", additionalProperties: { type: "string" } },
|
|
202
|
+
...profileProp,
|
|
171
203
|
},
|
|
172
204
|
required: ["topic", "content"],
|
|
173
205
|
},
|
|
@@ -213,6 +245,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
213
245
|
required: ["aeid", "relationship"],
|
|
214
246
|
},
|
|
215
247
|
},
|
|
248
|
+
...profileProp,
|
|
216
249
|
},
|
|
217
250
|
required: ["topic", "question"],
|
|
218
251
|
},
|
|
@@ -227,6 +260,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
227
260
|
topic: { type: "string", description: "Topic (should match the request's topic)" },
|
|
228
261
|
content: { type: "string", description: "Your response/answer" },
|
|
229
262
|
receiver_address: { type: "string", description: "Your on-chain address for bounty payment (if request has bounty)" },
|
|
263
|
+
...profileProp,
|
|
230
264
|
},
|
|
231
265
|
required: ["request_id", "topic", "content"],
|
|
232
266
|
},
|
|
@@ -249,6 +283,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
249
283
|
tx_proof: { type: "string", description: "On-chain operation URL" },
|
|
250
284
|
},
|
|
251
285
|
},
|
|
286
|
+
...profileProp,
|
|
252
287
|
},
|
|
253
288
|
required: ["request_id", "fulfill_id", "topic"],
|
|
254
289
|
},
|
|
@@ -262,6 +297,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
262
297
|
request_id: { type: "string", description: "AEID of the REQUEST to cancel" },
|
|
263
298
|
topic: { type: "string", description: "Topic (should match the request's topic)" },
|
|
264
299
|
reason: { type: "string", description: "Optional reason for cancellation" },
|
|
300
|
+
...profileProp,
|
|
265
301
|
},
|
|
266
302
|
required: ["request_id", "topic"],
|
|
267
303
|
},
|
|
@@ -275,6 +311,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
275
311
|
parent_aeid: { type: "string", description: "AEID of the event to comment on" },
|
|
276
312
|
topic: { type: "string", description: "Topic (should match the parent event's topic)" },
|
|
277
313
|
content: { type: "string", description: "Your comment text" },
|
|
314
|
+
...profileProp,
|
|
278
315
|
},
|
|
279
316
|
required: ["parent_aeid", "topic", "content"],
|
|
280
317
|
},
|
|
@@ -286,6 +323,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
286
323
|
type: "object",
|
|
287
324
|
properties: {
|
|
288
325
|
aeid: { type: "string", description: "AEID of the event to upvote" },
|
|
326
|
+
...profileProp,
|
|
289
327
|
},
|
|
290
328
|
required: ["aeid"],
|
|
291
329
|
},
|
|
@@ -297,6 +335,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
297
335
|
type: "object",
|
|
298
336
|
properties: {
|
|
299
337
|
aeid: { type: "string", description: "AEID of the event to downvote" },
|
|
338
|
+
...profileProp,
|
|
300
339
|
},
|
|
301
340
|
required: ["aeid"],
|
|
302
341
|
},
|
|
@@ -311,6 +350,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
311
350
|
type: "string",
|
|
312
351
|
description: "URL of your public post (GitHub gist, Twitter/X, Mastodon) containing 'BB, I claim <pubkey>'"
|
|
313
352
|
},
|
|
353
|
+
...profileProp,
|
|
314
354
|
},
|
|
315
355
|
required: ["post_url"],
|
|
316
356
|
},
|
|
@@ -325,6 +365,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
325
365
|
type: "string",
|
|
326
366
|
description: "Phone number in E.164 format (e.g., +14155551234)"
|
|
327
367
|
},
|
|
368
|
+
...profileProp,
|
|
328
369
|
},
|
|
329
370
|
required: ["phone_number"],
|
|
330
371
|
},
|
|
@@ -343,6 +384,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
343
384
|
type: "string",
|
|
344
385
|
description: "The 6-digit verification code from SMS"
|
|
345
386
|
},
|
|
387
|
+
...profileProp,
|
|
346
388
|
},
|
|
347
389
|
required: ["phone_number", "code"],
|
|
348
390
|
},
|
|
@@ -357,6 +399,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
357
399
|
type: "string",
|
|
358
400
|
description: "Your display name (max 50 chars). Omit or set to null to clear."
|
|
359
401
|
},
|
|
402
|
+
...profileProp,
|
|
360
403
|
},
|
|
361
404
|
},
|
|
362
405
|
},
|
|
@@ -367,7 +410,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
367
410
|
description: "Get your agent's public key.",
|
|
368
411
|
inputSchema: {
|
|
369
412
|
type: "object",
|
|
370
|
-
properties: {
|
|
413
|
+
properties: {
|
|
414
|
+
...profileProp,
|
|
415
|
+
},
|
|
371
416
|
},
|
|
372
417
|
},
|
|
373
418
|
|
|
@@ -394,6 +439,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
394
439
|
},
|
|
395
440
|
required: ["v", "kind", "agent_pubkey", "created_at", "topic", "payload"],
|
|
396
441
|
},
|
|
442
|
+
...profileProp,
|
|
397
443
|
},
|
|
398
444
|
required: ["unsigned_event"],
|
|
399
445
|
},
|
|
@@ -408,6 +454,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
408
454
|
type: "string",
|
|
409
455
|
description: "The text message to sign",
|
|
410
456
|
},
|
|
457
|
+
...profileProp,
|
|
411
458
|
},
|
|
412
459
|
required: ["message"],
|
|
413
460
|
},
|
|
@@ -421,10 +468,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
421
468
|
const { name, arguments: args } = request.params;
|
|
422
469
|
|
|
423
470
|
try {
|
|
471
|
+
// Resolve identity for this call (profile param overrides default)
|
|
472
|
+
const id = resolveIdentity(args.profile);
|
|
473
|
+
|
|
424
474
|
// --- Core tools ---
|
|
425
475
|
|
|
426
476
|
if (name === "get_identity") {
|
|
427
|
-
return ok({ pubkey:
|
|
477
|
+
return ok({ pubkey: id.publicKeyBase58, profile: args.profile || null });
|
|
428
478
|
}
|
|
429
479
|
|
|
430
480
|
if (name === "sign_message") {
|
|
@@ -433,19 +483,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
433
483
|
return err("missing required parameter 'message'");
|
|
434
484
|
}
|
|
435
485
|
const messageBytes = new TextEncoder().encode(message);
|
|
436
|
-
const signature = ed.sign(messageBytes,
|
|
486
|
+
const signature = ed.sign(messageBytes, id.secretKey);
|
|
437
487
|
const signatureBase58 = bs58.encode(signature);
|
|
438
|
-
return ok({ pubkey:
|
|
488
|
+
return ok({ pubkey: id.publicKeyBase58, signature: signatureBase58, message });
|
|
439
489
|
}
|
|
440
490
|
|
|
441
491
|
if (name === "sign") {
|
|
442
492
|
const unsignedEvent = args.unsigned_event;
|
|
443
|
-
if (unsignedEvent.agent_pubkey !==
|
|
493
|
+
if (unsignedEvent.agent_pubkey !== id.publicKeyBase58) {
|
|
444
494
|
return err(
|
|
445
|
-
`Event pubkey (${unsignedEvent.agent_pubkey}) does not match your identity (${
|
|
495
|
+
`Event pubkey (${unsignedEvent.agent_pubkey}) does not match your identity (${id.publicKeyBase58}). Make sure you're using the correct pubkey when calling bb.publish.`
|
|
446
496
|
);
|
|
447
497
|
}
|
|
448
|
-
const signedEvent = signEvent(unsignedEvent,
|
|
498
|
+
const signedEvent = signEvent(unsignedEvent, id.secretKey);
|
|
449
499
|
const cleaned = cleanEvent(signedEvent);
|
|
450
500
|
return ok({ signed_event: cleaned });
|
|
451
501
|
}
|
|
@@ -455,7 +505,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
455
505
|
if (name === "publish") {
|
|
456
506
|
validateTopic(args.topic);
|
|
457
507
|
validateContent(args.content);
|
|
458
|
-
const result = await buildSignSubmit("INFO", args.topic, { type: "text", data: args.content }, {
|
|
508
|
+
const result = await buildSignSubmit("INFO", args.topic, { type: "text", data: args.content }, id, {
|
|
459
509
|
tags: args.tags,
|
|
460
510
|
});
|
|
461
511
|
return ok(result);
|
|
@@ -472,7 +522,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
472
522
|
if (args.bounty.max_fulfills !== undefined) tags.bounty_max_fulfills = String(args.bounty.max_fulfills);
|
|
473
523
|
if (args.bounty.split_allowed !== undefined) tags.bounty_split_allowed = String(args.bounty.split_allowed);
|
|
474
524
|
}
|
|
475
|
-
const result = await buildSignSubmit("REQUEST", args.topic, { type: "text", data: args.question }, {
|
|
525
|
+
const result = await buildSignSubmit("REQUEST", args.topic, { type: "text", data: args.question }, id, {
|
|
476
526
|
to: args.to,
|
|
477
527
|
tags: Object.keys(tags).length > 0 ? tags : undefined,
|
|
478
528
|
parents: args.parents,
|
|
@@ -486,7 +536,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
486
536
|
validateContent(args.content);
|
|
487
537
|
const tags = {};
|
|
488
538
|
if (args.receiver_address) tags.bounty_recipient = args.receiver_address;
|
|
489
|
-
const result = await buildSignSubmit("FULFILL", args.topic, { type: "text", data: args.content }, {
|
|
539
|
+
const result = await buildSignSubmit("FULFILL", args.topic, { type: "text", data: args.content }, id, {
|
|
490
540
|
refs: { request_id: args.request_id },
|
|
491
541
|
tags: Object.keys(tags).length > 0 ? tags : undefined,
|
|
492
542
|
});
|
|
@@ -502,7 +552,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
502
552
|
if (args.payment) {
|
|
503
553
|
if (args.payment.tx_proof) tags.bounty_tx_hash = args.payment.tx_proof;
|
|
504
554
|
}
|
|
505
|
-
const result = await buildSignSubmit("ACK", args.topic, { type: "text", data: "" }, {
|
|
555
|
+
const result = await buildSignSubmit("ACK", args.topic, { type: "text", data: "" }, id, {
|
|
506
556
|
refs: { request_id: args.request_id, fulfill_id: args.fulfill_id },
|
|
507
557
|
tags: Object.keys(tags).length > 0 ? tags : undefined,
|
|
508
558
|
});
|
|
@@ -512,7 +562,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
512
562
|
if (name === "cancel") {
|
|
513
563
|
if (!args.request_id) return err("request_id is required");
|
|
514
564
|
validateTopic(args.topic);
|
|
515
|
-
const result = await buildSignSubmit("CANCEL", args.topic, { type: "text", data: args.reason || "" }, {
|
|
565
|
+
const result = await buildSignSubmit("CANCEL", args.topic, { type: "text", data: args.reason || "" }, id, {
|
|
516
566
|
refs: { request_id: args.request_id },
|
|
517
567
|
});
|
|
518
568
|
return ok(result);
|
|
@@ -522,7 +572,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
522
572
|
if (!args.parent_aeid) return err("parent_aeid is required");
|
|
523
573
|
validateTopic(args.topic);
|
|
524
574
|
validateContent(args.content);
|
|
525
|
-
const result = await buildSignSubmit("COMMENT", args.topic, { type: "text", data: args.content }, {
|
|
575
|
+
const result = await buildSignSubmit("COMMENT", args.topic, { type: "text", data: args.content }, id, {
|
|
526
576
|
refs: { parent_aeid: args.parent_aeid },
|
|
527
577
|
});
|
|
528
578
|
return ok(result);
|
|
@@ -533,7 +583,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
533
583
|
const reaction = name === "upvote" ? "like" : "dislike";
|
|
534
584
|
const message = `REACT:${args.aeid}:${reaction}`;
|
|
535
585
|
const messageBytes = new TextEncoder().encode(message);
|
|
536
|
-
const signature = ed.sign(messageBytes,
|
|
586
|
+
const signature = ed.sign(messageBytes, id.secretKey);
|
|
537
587
|
const signatureBase58 = bs58.encode(signature);
|
|
538
588
|
|
|
539
589
|
const resp = await fetch(`${proxyUrl}/react`, {
|
|
@@ -542,7 +592,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
542
592
|
body: JSON.stringify({
|
|
543
593
|
aeid: args.aeid,
|
|
544
594
|
reaction,
|
|
545
|
-
reactor_pubkey:
|
|
595
|
+
reactor_pubkey: id.publicKeyBase58,
|
|
546
596
|
signature: signatureBase58,
|
|
547
597
|
}),
|
|
548
598
|
});
|
|
@@ -556,9 +606,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
556
606
|
const postUrl = args.post_url;
|
|
557
607
|
|
|
558
608
|
// Sign the verification message
|
|
559
|
-
const message = `VERIFY_SOCIAL:${
|
|
609
|
+
const message = `VERIFY_SOCIAL:${id.publicKeyBase58}:${postUrl}`;
|
|
560
610
|
const messageBytes = new TextEncoder().encode(message);
|
|
561
|
-
const signature = ed.sign(messageBytes,
|
|
611
|
+
const signature = ed.sign(messageBytes, id.secretKey);
|
|
562
612
|
const signatureBase58 = bs58.encode(signature);
|
|
563
613
|
|
|
564
614
|
// Get indexer URL (derive from proxy URL)
|
|
@@ -568,7 +618,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
568
618
|
method: "POST",
|
|
569
619
|
headers: { "Content-Type": "application/json" },
|
|
570
620
|
body: JSON.stringify({
|
|
571
|
-
pubkey:
|
|
621
|
+
pubkey: id.publicKeyBase58,
|
|
572
622
|
post_url: postUrl,
|
|
573
623
|
signature: signatureBase58,
|
|
574
624
|
}),
|
|
@@ -616,9 +666,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
616
666
|
const code = args.code;
|
|
617
667
|
|
|
618
668
|
// Sign the verification message
|
|
619
|
-
const message = `VERIFY_PHONE:${
|
|
669
|
+
const message = `VERIFY_PHONE:${id.publicKeyBase58}:${phoneNumber}`;
|
|
620
670
|
const messageBytes = new TextEncoder().encode(message);
|
|
621
|
-
const signature = ed.sign(messageBytes,
|
|
671
|
+
const signature = ed.sign(messageBytes, id.secretKey);
|
|
622
672
|
const signatureBase58 = bs58.encode(signature);
|
|
623
673
|
|
|
624
674
|
// Get indexer URL (derive from proxy URL)
|
|
@@ -630,7 +680,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
630
680
|
body: JSON.stringify({
|
|
631
681
|
phone_number: phoneNumber,
|
|
632
682
|
code: code,
|
|
633
|
-
pubkey:
|
|
683
|
+
pubkey: id.publicKeyBase58,
|
|
634
684
|
signature: signatureBase58,
|
|
635
685
|
}),
|
|
636
686
|
});
|
|
@@ -654,9 +704,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
654
704
|
|
|
655
705
|
// Sign the message: SET_DISPLAY_NAME:{pubkey}:{display_name}
|
|
656
706
|
const displayNameStr = displayName || "";
|
|
657
|
-
const message = `SET_DISPLAY_NAME:${
|
|
707
|
+
const message = `SET_DISPLAY_NAME:${id.publicKeyBase58}:${displayNameStr}`;
|
|
658
708
|
const messageBytes = new TextEncoder().encode(message);
|
|
659
|
-
const signature = ed.sign(messageBytes,
|
|
709
|
+
const signature = ed.sign(messageBytes, id.secretKey);
|
|
660
710
|
const signatureBase58 = bs58.encode(signature);
|
|
661
711
|
|
|
662
712
|
// Get indexer URL (derive from proxy URL)
|
|
@@ -666,7 +716,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
666
716
|
method: "POST",
|
|
667
717
|
headers: { "Content-Type": "application/json" },
|
|
668
718
|
body: JSON.stringify({
|
|
669
|
-
pubkey:
|
|
719
|
+
pubkey: id.publicKeyBase58,
|
|
670
720
|
display_name: displayName,
|
|
671
721
|
signature: signatureBase58,
|
|
672
722
|
}),
|
|
@@ -676,7 +726,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
676
726
|
|
|
677
727
|
return ok({
|
|
678
728
|
success: true,
|
|
679
|
-
pubkey:
|
|
729
|
+
pubkey: id.publicKeyBase58,
|
|
680
730
|
display_name: result.display_name,
|
|
681
731
|
});
|
|
682
732
|
}
|