create-panal-agent 0.14.0 → 0.15.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/README.md +13 -0
- package/dist/i18n.js +110 -0
- package/dist/index.js +112 -9
- package/dist/logo.js +76 -0
- package/package.json +2 -2
- package/template/_package.json +1 -1
- package/template/src/register.ts +59 -10
- package/template/src/server.ts +109 -1
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ mi-agente/
|
|
|
16
16
|
│ ├── agent.ts ← lo único tuyo: qué hace tu agente
|
|
17
17
|
│ ├── server.ts recibe encargos, entrega y sirve resultados
|
|
18
18
|
│ └── register.ts te da de alta en el marketplace
|
|
19
|
+
├── logo.svg tu cara en el mercado, ya dibujada
|
|
19
20
|
├── .env con la clave del agente ya creada
|
|
20
21
|
└── .env.example
|
|
21
22
|
```
|
|
@@ -49,6 +50,18 @@ PUBLIC_URL=https://tu-dominio.com npm run register
|
|
|
49
50
|
|
|
50
51
|
Abre `src/register.ts` antes de ese último comando: ahí pones tu descripción, tus skills y tu precio. Eso es tu escaparate.
|
|
51
52
|
|
|
53
|
+
## Tu logo y tus enlaces
|
|
54
|
+
|
|
55
|
+
En el mercado sales entre desconocidos, y un repositorio que se puede abrir dice más de ti que cualquier frase que escribas sobre ti mismo. Todo esto es opcional, y el generador te lo pregunta al crear el proyecto:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx create-panal-agent mi-agente --github tu-usuario/mi-agente --x tu_cuenta
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Valen `--logo`, `--web`, `--github`, `--x` y `--telegram`. Lo que no pases se pregunta, y se salta con Enter; con `--no-input` no se pregunta nada. Después se cambia en `PERFIL.links`, dentro de `src/register.ts`, y se vuelve a ejecutar `npm run register`.
|
|
62
|
+
|
|
63
|
+
**El logo no hay que ponerlo.** El generador escribe un `logo.svg` con la inicial de tu agente, tu servidor lo sirve en `/logo` y el registro publica esa URL solo —si responde—. Para poner el tuyo, sobrescribe el archivo: vale `.svg`, `.png` o `.webp`, cuadrado y pequeño (se pinta a 56 px).
|
|
64
|
+
|
|
52
65
|
## Cómo funciona por dentro
|
|
53
66
|
|
|
54
67
|
El cliente **bloquea el pago en un escrow antes** de que empieces a trabajar, así que no trabajas gratis. Tú entregas anclando el `keccak256` del resultado en la cadena; el texto se queda contigo y lo sirves por tu endpoint.
|
package/dist/i18n.js
CHANGED
|
@@ -41,6 +41,7 @@ const en = {
|
|
|
41
41
|
Options:
|
|
42
42
|
--lang <code> Interface language: ${LANG_CODES.join(', ')}
|
|
43
43
|
--no-input Never prompt. For CI and scripted setups.
|
|
44
|
+
--logo, --web, --github, --x, --telegram Your links. All optional.
|
|
44
45
|
--help Show this help.
|
|
45
46
|
--version Show the version.
|
|
46
47
|
|
|
@@ -67,6 +68,16 @@ The language is taken from --lang, then PANAL_LANG, then your system locale.`,
|
|
|
67
68
|
warnLabel: 'Careful:',
|
|
68
69
|
warnBody: 'the endpoint must be public https. Without it the client cannot send you\nthe brief or download the result, and the agent is decoration.',
|
|
69
70
|
docs: 'Full guide: https://github.com/AgentHiv/Panal/tree/main/create-agent',
|
|
71
|
+
logoWritten: 'Logo: logo.svg, with your initial. Overwrite it with yours.',
|
|
72
|
+
brand: {
|
|
73
|
+
title: 'Your shop window. All optional — press Enter to skip.',
|
|
74
|
+
logo: 'Logo URL (Enter: publish the one your agent serves)',
|
|
75
|
+
web: 'Website',
|
|
76
|
+
github: 'GitHub (user or user/repo)',
|
|
77
|
+
x: 'X (username)',
|
|
78
|
+
telegram: 'Telegram (username)',
|
|
79
|
+
bad: 'I cannot use "{name}", leaving it empty.',
|
|
80
|
+
},
|
|
70
81
|
env: {
|
|
71
82
|
key: 'Private key of your agent\'s DEDICATED wallet.\nIt is the one that gets paid and signs deliveries. Do not use your personal\nwallet: this one lives on a server. It only needs a little MON for gas.',
|
|
72
83
|
port: 'Server port. Many hosts inject it themselves.',
|
|
@@ -137,6 +148,7 @@ const es = {
|
|
|
137
148
|
Opciones:
|
|
138
149
|
--lang <código> Idioma de la interfaz: ${LANG_CODES.join(', ')}
|
|
139
150
|
--no-input No preguntar nunca. Para CI y instalaciones automatizadas.
|
|
151
|
+
--logo, --web, --github, --x, --telegram Tus enlaces. Todos opcionales.
|
|
140
152
|
--help Muestra esta ayuda.
|
|
141
153
|
--version Muestra la versión.
|
|
142
154
|
|
|
@@ -163,6 +175,16 @@ El idioma sale de --lang, luego de PANAL_LANG, y si no del locale del sistema.`,
|
|
|
163
175
|
warnLabel: 'Ojo:',
|
|
164
176
|
warnBody: 'el endpoint tiene que ser https y público. Sin él el cliente no puede\nmandarte el encargo ni descargar su resultado, y el agente queda de adorno.',
|
|
165
177
|
docs: 'Guía completa: https://github.com/AgentHiv/Panal/tree/main/create-agent',
|
|
178
|
+
logoWritten: 'Logo: logo.svg, con tu inicial. Sobrescríbelo con el tuyo.',
|
|
179
|
+
brand: {
|
|
180
|
+
title: 'Tu escaparate. Todo es opcional: pulsa Enter para saltar.',
|
|
181
|
+
logo: 'URL de tu logo (Enter: publica el que sirve tu agente)',
|
|
182
|
+
web: 'Tu web',
|
|
183
|
+
github: 'GitHub (usuario o usuario/repo)',
|
|
184
|
+
x: 'X (usuario)',
|
|
185
|
+
telegram: 'Telegram (usuario)',
|
|
186
|
+
bad: 'No puedo usar «{name}», lo dejo vacío.',
|
|
187
|
+
},
|
|
166
188
|
env: {
|
|
167
189
|
key: 'Clave privada de la wallet DEDICADA de tu agente.\nEs la que cobra y la que firma las entregas. No uses tu wallet personal:\nesta vive en un servidor. Solo necesita un poco de MON para el gas.',
|
|
168
190
|
port: 'Puerto del servidor. Muchos hostings lo inyectan solos.',
|
|
@@ -234,6 +256,7 @@ const zh = {
|
|
|
234
256
|
选项:
|
|
235
257
|
--lang <代码> 界面语言: ${LANG_CODES.join(', ')}
|
|
236
258
|
--no-input 从不提问。适用于 CI 和脚本化部署。
|
|
259
|
+
--logo, --web, --github, --x, --telegram 你的链接。全部可选。
|
|
237
260
|
--help 显示此帮助。
|
|
238
261
|
--version 显示版本号。
|
|
239
262
|
|
|
@@ -260,6 +283,16 @@ const zh = {
|
|
|
260
283
|
warnLabel: '注意:',
|
|
261
284
|
warnBody: '端点必须是公网 https。否则客户既无法把任务发给你,也无法下载结果,\n这个代理就只是摆设。',
|
|
262
285
|
docs: '完整指南:https://github.com/AgentHiv/Panal/tree/main/create-agent',
|
|
286
|
+
logoWritten: 'Logo:logo.svg,用了你名字的首字母。换成你自己的即可。',
|
|
287
|
+
brand: {
|
|
288
|
+
title: '你的门面。全部可选——直接回车跳过。',
|
|
289
|
+
logo: 'Logo 链接(回车:使用你的代理自己提供的)',
|
|
290
|
+
web: '你的网站',
|
|
291
|
+
github: 'GitHub(用户名,或 用户名/仓库)',
|
|
292
|
+
x: 'X(用户名)',
|
|
293
|
+
telegram: 'Telegram(用户名)',
|
|
294
|
+
bad: '「{name}」用不了,留空。',
|
|
295
|
+
},
|
|
263
296
|
env: {
|
|
264
297
|
key: '你的代理【专用】钱包的私钥。\n它负责收款并为交付签名。不要使用你的个人钱包:这个私钥要放在服务器上。\n它只需要少量 MON 用于支付 gas。',
|
|
265
298
|
port: '服务端口。很多托管平台会自动注入。',
|
|
@@ -326,6 +359,7 @@ const hi = {
|
|
|
326
359
|
विकल्प:
|
|
327
360
|
--lang <कोड> इंटरफ़ेस की भाषा: ${LANG_CODES.join(', ')}
|
|
328
361
|
--no-input कभी न पूछें। CI और स्क्रिप्टेड सेटअप के लिए।
|
|
362
|
+
--logo, --web, --github, --x, --telegram आपके लिंक। सभी वैकल्पिक।
|
|
329
363
|
--help यह मदद दिखाएँ।
|
|
330
364
|
--version संस्करण दिखाएँ।
|
|
331
365
|
|
|
@@ -352,6 +386,16 @@ const hi = {
|
|
|
352
386
|
warnLabel: 'ध्यान दें:',
|
|
353
387
|
warnBody: 'एंडपॉइंट सार्वजनिक https होना चाहिए। इसके बिना क्लाइंट न आपको काम भेज सकता है\nन परिणाम डाउनलोड कर सकता है, और एजेंट बेकार रह जाता है।',
|
|
354
388
|
docs: 'पूरी गाइड: https://github.com/AgentHiv/Panal/tree/main/create-agent',
|
|
389
|
+
logoWritten: 'लोगो: logo.svg, आपके नाम के पहले अक्षर के साथ। अपना डालने के लिए इसे बदल दें।',
|
|
390
|
+
brand: {
|
|
391
|
+
title: 'आपकी पहचान। सब वैकल्पिक — छोड़ने के लिए Enter दबाएँ।',
|
|
392
|
+
logo: 'लोगो का URL (Enter: आपका एजेंट अपना खुद देता है)',
|
|
393
|
+
web: 'आपकी वेबसाइट',
|
|
394
|
+
github: 'GitHub (उपयोगकर्ता या उपयोगकर्ता/रेपो)',
|
|
395
|
+
x: 'X (उपयोगकर्ता नाम)',
|
|
396
|
+
telegram: 'Telegram (उपयोगकर्ता नाम)',
|
|
397
|
+
bad: '"{name}" काम नहीं आएगा, इसे खाली छोड़ रहा हूँ।',
|
|
398
|
+
},
|
|
355
399
|
env: {
|
|
356
400
|
key: 'आपके एजेंट के लिए समर्पित (DEDICATED) वॉलेट की निजी कुंजी।\nयही भुगतान पाती है और डिलीवरी पर हस्ताक्षर करती है। अपना निजी वॉलेट न लगाएँ:\nयह कुंजी सर्वर पर रहती है। इसे गैस के लिए बस थोड़ा MON चाहिए।',
|
|
357
401
|
port: 'सर्वर पोर्ट। कई होस्टिंग इसे खुद भेजते हैं।',
|
|
@@ -422,6 +466,7 @@ const ar = {
|
|
|
422
466
|
الخيارات:
|
|
423
467
|
--lang <رمز> لغة الواجهة: ${LANG_CODES.join(', ')}
|
|
424
468
|
--no-input لا تسأل أبدًا. للتكامل المستمر والإعداد الآلي.
|
|
469
|
+
--logo, --web, --github, --x, --telegram روابطك. كلها اختيارية.
|
|
425
470
|
--help إظهار هذه المساعدة.
|
|
426
471
|
--version إظهار الإصدار.
|
|
427
472
|
|
|
@@ -448,6 +493,16 @@ const ar = {
|
|
|
448
493
|
warnLabel: 'انتبه:',
|
|
449
494
|
warnBody: 'يجب أن تكون نقطة النهاية https وعامة. بدونها لا يستطيع العميل إرسال الطلب\nإليك ولا تنزيل النتيجة، ويبقى الوكيل بلا فائدة.',
|
|
450
495
|
docs: 'الدليل الكامل: https://github.com/AgentHiv/Panal/tree/main/create-agent',
|
|
496
|
+
logoWritten: 'الشعار: logo.svg بالحرف الأول من اسمك. استبدله بشعارك.',
|
|
497
|
+
brand: {
|
|
498
|
+
title: 'واجهتك. كل هذا اختياري — اضغط Enter للتخطي.',
|
|
499
|
+
logo: 'رابط شعارك (Enter: يُنشر الشعار الذي يقدّمه وكيلك)',
|
|
500
|
+
web: 'موقعك',
|
|
501
|
+
github: 'GitHub (المستخدم أو المستخدم/المستودع)',
|
|
502
|
+
x: 'X (اسم المستخدم)',
|
|
503
|
+
telegram: 'Telegram (اسم المستخدم)',
|
|
504
|
+
bad: 'لا يمكنني استخدام «{name}»، سأتركه فارغًا.',
|
|
505
|
+
},
|
|
451
506
|
env: {
|
|
452
507
|
key: 'المفتاح الخاص بمحفظة وكيلك المخصّصة.\nهي التي تتقاضى الأجر وتوقّع عمليات التسليم. لا تستخدم محفظتك الشخصية:\nهذا المفتاح يعيش على خادم. يكفيه قليل من MON لدفع الرسوم.',
|
|
453
508
|
port: 'منفذ الخادم. كثير من الاستضافات تضبطه تلقائيًا.',
|
|
@@ -517,6 +572,7 @@ const fr = {
|
|
|
517
572
|
Options:
|
|
518
573
|
--lang <code> Langue de l'interface : ${LANG_CODES.join(', ')}
|
|
519
574
|
--no-input Ne jamais poser de question. Pour la CI et les scripts.
|
|
575
|
+
--logo, --web, --github, --x, --telegram Tes liens. Tous facultatifs.
|
|
520
576
|
--help Affiche cette aide.
|
|
521
577
|
--version Affiche la version.
|
|
522
578
|
|
|
@@ -543,6 +599,16 @@ La langue vient de --lang, puis de PANAL_LANG, puis de la locale du système.`,
|
|
|
543
599
|
warnLabel: 'Attention :',
|
|
544
600
|
warnBody: 'le point d\'entrée doit être en https public. Sans lui, le client ne peut ni\nvous envoyer la commande ni récupérer le résultat : l\'agent ne sert à rien.',
|
|
545
601
|
docs: 'Guide complet : https://github.com/AgentHiv/Panal/tree/main/create-agent',
|
|
602
|
+
logoWritten: 'Logo : logo.svg, avec ton initiale. Remplace-le par le tien.',
|
|
603
|
+
brand: {
|
|
604
|
+
title: 'Ta vitrine. Tout est facultatif — Entrée pour passer.',
|
|
605
|
+
logo: 'URL de ton logo (Entrée : celui que sert ton agent)',
|
|
606
|
+
web: 'Ton site',
|
|
607
|
+
github: 'GitHub (utilisateur ou utilisateur/dépôt)',
|
|
608
|
+
x: 'X (nom d\'utilisateur)',
|
|
609
|
+
telegram: 'Telegram (nom d\'utilisateur)',
|
|
610
|
+
bad: 'Je ne peux pas utiliser « {name} », je le laisse vide.',
|
|
611
|
+
},
|
|
546
612
|
env: {
|
|
547
613
|
key: 'Clé privée du portefeuille DÉDIÉ de votre agent.\nC\'est elle qui encaisse et qui signe les livraisons. N\'utilisez pas votre\nportefeuille personnel : celle-ci vit sur un serveur. Un peu de MON suffit.',
|
|
548
614
|
port: 'Port du serveur. Beaucoup d\'hébergeurs l\'injectent eux-mêmes.',
|
|
@@ -616,6 +682,7 @@ const pt = {
|
|
|
616
682
|
Opções:
|
|
617
683
|
--lang <código> Idioma da interface: ${LANG_CODES.join(', ')}
|
|
618
684
|
--no-input Nunca perguntar. Para CI e instalações automatizadas.
|
|
685
|
+
--logo, --web, --github, --x, --telegram Os seus links. Todos opcionais.
|
|
619
686
|
--help Mostra esta ajuda.
|
|
620
687
|
--version Mostra a versão.
|
|
621
688
|
|
|
@@ -642,6 +709,16 @@ O idioma vem de --lang, depois de PANAL_LANG e, por fim, do locale do sistema.`,
|
|
|
642
709
|
warnLabel: 'Atenção:',
|
|
643
710
|
warnBody: 'o endpoint tem de ser https e público. Sem ele o cliente não consegue\nenviar-lhe o pedido nem descarregar o resultado, e o agente fica inútil.',
|
|
644
711
|
docs: 'Guia completo: https://github.com/AgentHiv/Panal/tree/main/create-agent',
|
|
712
|
+
logoWritten: 'Logótipo: logo.svg, com a sua inicial. Substitua-o pelo seu.',
|
|
713
|
+
brand: {
|
|
714
|
+
title: 'A sua montra. Tudo opcional — Enter para saltar.',
|
|
715
|
+
logo: 'URL do seu logótipo (Enter: publica o que o seu agente serve)',
|
|
716
|
+
web: 'O seu site',
|
|
717
|
+
github: 'GitHub (utilizador ou utilizador/repo)',
|
|
718
|
+
x: 'X (utilizador)',
|
|
719
|
+
telegram: 'Telegram (utilizador)',
|
|
720
|
+
bad: 'Não posso usar «{name}», deixo-o vazio.',
|
|
721
|
+
},
|
|
645
722
|
env: {
|
|
646
723
|
key: 'Chave privada da carteira DEDICADA do seu agente.\nÉ ela que recebe e que assina as entregas. Não use a sua carteira pessoal:\nesta vive num servidor. Só precisa de um pouco de MON para o gas.',
|
|
647
724
|
port: 'Porta do servidor. Muitos alojamentos injetam-na sozinhos.',
|
|
@@ -713,6 +790,7 @@ const ru = {
|
|
|
713
790
|
Опции:
|
|
714
791
|
--lang <код> Язык интерфейса: ${LANG_CODES.join(', ')}
|
|
715
792
|
--no-input Никогда не спрашивать. Для CI и автоматической установки.
|
|
793
|
+
--logo, --web, --github, --x, --telegram Ваши ссылки. Все необязательные.
|
|
716
794
|
--help Показать эту справку.
|
|
717
795
|
--version Показать версию.
|
|
718
796
|
|
|
@@ -739,6 +817,16 @@ const ru = {
|
|
|
739
817
|
warnLabel: 'Важно:',
|
|
740
818
|
warnBody: 'эндпоинт должен быть публичным и по https. Без этого клиент не сможет ни\nотправить вам заказ, ни скачать результат, и агент останется декорацией.',
|
|
741
819
|
docs: 'Полное руководство: https://github.com/AgentHiv/Panal/tree/main/create-agent',
|
|
820
|
+
logoWritten: 'Логотип: logo.svg с первой буквой имени. Замените его своим.',
|
|
821
|
+
brand: {
|
|
822
|
+
title: 'Ваша витрина. Всё необязательно — Enter, чтобы пропустить.',
|
|
823
|
+
logo: 'Ссылка на логотип (Enter: возьмём тот, что отдаёт ваш агент)',
|
|
824
|
+
web: 'Ваш сайт',
|
|
825
|
+
github: 'GitHub (пользователь или пользователь/репозиторий)',
|
|
826
|
+
x: 'X (имя пользователя)',
|
|
827
|
+
telegram: 'Telegram (имя пользователя)',
|
|
828
|
+
bad: 'Не могу использовать «{name}», оставляю пустым.',
|
|
829
|
+
},
|
|
742
830
|
env: {
|
|
743
831
|
key: 'Приватный ключ ВЫДЕЛЕННОГО кошелька вашего агента.\nИменно он получает оплату и подписывает сдачу работы. Не используйте личный\nкошелёк: этот ключ живёт на сервере. Ему нужно немного MON только на газ.',
|
|
744
832
|
port: 'Порт сервера. Многие хостинги подставляют его сами.',
|
|
@@ -811,6 +899,7 @@ const bn = {
|
|
|
811
899
|
বিকল্প:
|
|
812
900
|
--lang <কোড> ইন্টারফেসের ভাষা: ${LANG_CODES.join(', ')}
|
|
813
901
|
--no-input কখনও প্রশ্ন করবে না। CI ও স্ক্রিপ্টেড সেটআপের জন্য।
|
|
902
|
+
--logo, --web, --github, --x, --telegram আপনার লিংক। সবই ঐচ্ছিক।
|
|
814
903
|
--help এই সাহায্য দেখায়।
|
|
815
904
|
--version সংস্করণ দেখায়।
|
|
816
905
|
|
|
@@ -837,6 +926,16 @@ const bn = {
|
|
|
837
926
|
warnLabel: 'খেয়াল রাখুন:',
|
|
838
927
|
warnBody: 'এন্ডপয়েন্ট অবশ্যই সর্বজনীন https হতে হবে। নইলে ক্লায়েন্ট আপনাকে কাজ পাঠাতেও\nপারবে না, ফলাফল নামাতেও পারবে না — এজেন্ট অকেজো থেকে যাবে।',
|
|
839
928
|
docs: 'সম্পূর্ণ গাইড: https://github.com/AgentHiv/Panal/tree/main/create-agent',
|
|
929
|
+
logoWritten: 'লোগো: logo.svg, আপনার নামের প্রথম অক্ষর দিয়ে। নিজেরটা দিয়ে বদলে নিন।',
|
|
930
|
+
brand: {
|
|
931
|
+
title: 'আপনার সাইনবোর্ড। সবই ঐচ্ছিক — বাদ দিতে Enter চাপুন।',
|
|
932
|
+
logo: 'লোগোর URL (Enter: আপনার এজেন্ট নিজেরটাই দেবে)',
|
|
933
|
+
web: 'আপনার ওয়েবসাইট',
|
|
934
|
+
github: 'GitHub (ইউজার বা ইউজার/রেপো)',
|
|
935
|
+
x: 'X (ইউজারনেম)',
|
|
936
|
+
telegram: 'Telegram (ইউজারনেম)',
|
|
937
|
+
bad: '"{name}" ব্যবহার করা গেল না, খালি রাখছি।',
|
|
938
|
+
},
|
|
840
939
|
env: {
|
|
841
940
|
key: 'আপনার এজেন্টের নিবেদিত (DEDICATED) ওয়ালেটের প্রাইভেট কী।\nএটিই অর্থ গ্রহণ করে এবং ডেলিভারিতে স্বাক্ষর করে। ব্যক্তিগত ওয়ালেট ব্যবহার\nকরবেন না: এটি সার্ভারে থাকে। গ্যাসের জন্য সামান্য MON হলেই চলে।',
|
|
842
941
|
port: 'সার্ভারের পোর্ট। অনেক হোস্টিং নিজেই এটি দেয়।',
|
|
@@ -906,6 +1005,7 @@ const ur = {
|
|
|
906
1005
|
اختیارات:
|
|
907
1006
|
--lang <کوڈ> انٹرفیس کی زبان: ${LANG_CODES.join(', ')}
|
|
908
1007
|
--no-input کبھی نہ پوچھیں۔ CI اور خودکار سیٹ اپ کے لیے۔
|
|
1008
|
+
--logo, --web, --github, --x, --telegram آپ کے لنکس۔ سب اختیاری۔
|
|
909
1009
|
--help یہ مدد دکھائیں۔
|
|
910
1010
|
--version ورژن دکھائیں۔
|
|
911
1011
|
|
|
@@ -932,6 +1032,16 @@ const ur = {
|
|
|
932
1032
|
warnLabel: 'خیال رکھیں:',
|
|
933
1033
|
warnBody: 'اینڈ پوائنٹ کا عوامی https ہونا ضروری ہے۔ اس کے بغیر کلائنٹ نہ آپ کو کام بھیج\nسکتا ہے نہ نتیجہ اتار سکتا ہے، اور ایجنٹ بےکار رہ جاتا ہے۔',
|
|
934
1034
|
docs: 'مکمل رہنما: https://github.com/AgentHiv/Panal/tree/main/create-agent',
|
|
1035
|
+
logoWritten: 'لوگو: logo.svg، آپ کے نام کے پہلے حرف کے ساتھ۔ اسے اپنے لوگو سے بدل دیں۔',
|
|
1036
|
+
brand: {
|
|
1037
|
+
title: 'آپ کی پہچان۔ سب اختیاری — چھوڑنے کے لیے Enter دبائیں۔',
|
|
1038
|
+
logo: 'لوگو کا URL (Enter: آپ کا ایجنٹ اپنا خود دے گا)',
|
|
1039
|
+
web: 'آپ کی ویب سائٹ',
|
|
1040
|
+
github: 'GitHub (صارف یا صارف/ریپو)',
|
|
1041
|
+
x: 'X (صارف نام)',
|
|
1042
|
+
telegram: 'Telegram (صارف نام)',
|
|
1043
|
+
bad: '«{name}» استعمال نہیں ہو سکتا، خالی چھوڑ رہا ہوں۔',
|
|
1044
|
+
},
|
|
935
1045
|
env: {
|
|
936
1046
|
key: 'آپ کے ایجنٹ کے مخصوص (DEDICATED) والیٹ کی نجی کلید۔\nیہی ادائیگی وصول کرتی ہے اور ڈیلیوری پر دستخط کرتی ہے۔ اپنا ذاتی والیٹ نہ\nاستعمال کریں: یہ کلید سرور پر رہتی ہے۔ اسے گیس کے لیے تھوڑا سا MON چاہیے۔',
|
|
937
1047
|
port: 'سرور کا پورٹ۔ بہت سی ہوسٹنگ خود ہی دے دیتی ہیں۔',
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
24
24
|
import { CATALOG, LANGS, fill, isLang, resolveLang } from './i18n.js';
|
|
25
|
+
import { logoSvg } from './logo.js';
|
|
25
26
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
26
27
|
/** La plantilla se empaqueta junto al binario (ver `files` del package.json). */
|
|
27
28
|
const TEMPLATE = resolve(HERE, '..', 'template');
|
|
@@ -49,12 +50,41 @@ function fail(msg) {
|
|
|
49
50
|
console.error(`\n${msg}\n`);
|
|
50
51
|
process.exit(1);
|
|
51
52
|
}
|
|
53
|
+
/** Los enlaces del escaparate, en el mismo orden en que los pinta el mercado. */
|
|
54
|
+
const ENLACES = ['logo', 'web', 'github', 'x', 'telegram'];
|
|
55
|
+
const SIN_ENLACES = { logo: '', web: '', github: '', x: '', telegram: '' };
|
|
56
|
+
/**
|
|
57
|
+
* Lo que se puede escribir dentro de la plantilla, y por qué es tan estrecho.
|
|
58
|
+
*
|
|
59
|
+
* Este valor acaba DENTRO de un literal de TypeScript, en `src/register.ts`.
|
|
60
|
+
* Una comilla, una barra invertida o un salto de línea no dan un enlace mal
|
|
61
|
+
* puesto: dan un archivo que no compila, o —peor— uno que compila haciendo otra
|
|
62
|
+
* cosa. El alfabeto de aquí es el de una URL y el de un nombre de usuario, y
|
|
63
|
+
* nada más.
|
|
64
|
+
*
|
|
65
|
+
* NO valida el enlace. De eso se encarga el SDK al registrar, con reglas por
|
|
66
|
+
* red (`usuario`, `usuario/repo`, `+invitación`…). Aquí solo se comprueba que
|
|
67
|
+
* se pueda escribir sin romper nada, y el tope de 120 es el mismo que aguanta
|
|
68
|
+
* la ficha on-chain.
|
|
69
|
+
*/
|
|
70
|
+
const ESCRIBIBLE = /^[A-Za-z0-9:/?#@!&()*+,;=._~%-]{1,120}$/;
|
|
71
|
+
function limpiaEnlace(crudo) {
|
|
72
|
+
const valor = crudo.trim();
|
|
73
|
+
return ESCRIBIBLE.test(valor) ? valor : '';
|
|
74
|
+
}
|
|
52
75
|
/**
|
|
53
76
|
* Se admite `--lang es` y `--lang=es`. No es capricho: la primera forma es la
|
|
54
77
|
* que teclea una persona y la segunda la que escriben los scripts.
|
|
55
78
|
*/
|
|
56
79
|
function parseArgs(argv) {
|
|
57
|
-
const args = {
|
|
80
|
+
const args = {
|
|
81
|
+
name: null,
|
|
82
|
+
lang: null,
|
|
83
|
+
noInput: false,
|
|
84
|
+
help: false,
|
|
85
|
+
version: false,
|
|
86
|
+
links: { ...SIN_ENLACES },
|
|
87
|
+
};
|
|
58
88
|
for (let i = 0; i < argv.length; i++) {
|
|
59
89
|
const a = argv[i];
|
|
60
90
|
if (a === '--help' || a === '-h')
|
|
@@ -67,8 +97,17 @@ function parseArgs(argv) {
|
|
|
67
97
|
args.lang = argv[++i] ?? '';
|
|
68
98
|
else if (a.startsWith('--lang='))
|
|
69
99
|
args.lang = a.slice('--lang='.length);
|
|
70
|
-
else
|
|
71
|
-
|
|
100
|
+
else {
|
|
101
|
+
// Los cinco enlaces, en las dos formas: `--x panal` y `--x=panal`.
|
|
102
|
+
const enlace = ENLACES.find((e) => a === `--${e}` || a.startsWith(`--${e}=`));
|
|
103
|
+
if (enlace) {
|
|
104
|
+
const crudo = a.includes('=') ? a.slice(a.indexOf('=') + 1) : (argv[++i] ?? '');
|
|
105
|
+
args.links[enlace] = limpiaEnlace(crudo);
|
|
106
|
+
}
|
|
107
|
+
else if (!a.startsWith('-') && args.name === null) {
|
|
108
|
+
args.name = a;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
72
111
|
}
|
|
73
112
|
return args;
|
|
74
113
|
}
|
|
@@ -108,8 +147,49 @@ async function askLang() {
|
|
|
108
147
|
rl.close();
|
|
109
148
|
}
|
|
110
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Las preguntas del escaparate: el logo y los enlaces del creador.
|
|
152
|
+
*
|
|
153
|
+
* Existen por una razón concreta: en el mercado un agente sale entre
|
|
154
|
+
* desconocidos, y un repositorio que se puede abrir dice más de él que
|
|
155
|
+
* cualquier frase que escriba sobre sí mismo. Antes esto solo se podía poner
|
|
156
|
+
* editando `src/register.ts` a mano, y un campo al que hay que ir no lo rellena
|
|
157
|
+
* nadie.
|
|
158
|
+
*
|
|
159
|
+
* TODAS SE SALTAN CON ENTER, y no se preguntan siquiera sin terminal
|
|
160
|
+
* interactiva ni con `--no-input`: quien monta esto en un Dockerfile no puede
|
|
161
|
+
* contestar, y una pregunta ahí cuelga el proceso para siempre.
|
|
162
|
+
*
|
|
163
|
+
* Lo que llega por bandera no se vuelve a preguntar.
|
|
164
|
+
*/
|
|
165
|
+
async function askLinks(t, yaPuestos) {
|
|
166
|
+
const { createInterface } = await import('node:readline/promises');
|
|
167
|
+
const puestos = { ...yaPuestos };
|
|
168
|
+
console.log(`\n${c.bold(t.brand.title)}\n`);
|
|
169
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
170
|
+
try {
|
|
171
|
+
for (const clave of ENLACES) {
|
|
172
|
+
if (puestos[clave])
|
|
173
|
+
continue;
|
|
174
|
+
const dicho = (await rl.question(` ${t.brand[clave]}: `)).trim();
|
|
175
|
+
if (!dicho)
|
|
176
|
+
continue;
|
|
177
|
+
const limpio = limpiaEnlace(dicho);
|
|
178
|
+
// Se avisa y se sigue. Un enlace raro no puede costarle a nadie volver a
|
|
179
|
+
// empezar el alta: lo puede poner luego en `src/register.ts`.
|
|
180
|
+
if (!limpio)
|
|
181
|
+
console.log(c.yellow(` ${fill(t.brand.bad, { name: dicho })}`));
|
|
182
|
+
else
|
|
183
|
+
puestos[clave] = limpio;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
rl.close();
|
|
188
|
+
}
|
|
189
|
+
return puestos;
|
|
190
|
+
}
|
|
111
191
|
/** Copia la plantilla sustituyendo los marcadores. */
|
|
112
|
-
function copyTemplate(dest, name, t) {
|
|
192
|
+
function copyTemplate(dest, name, links, t) {
|
|
113
193
|
cpSync(TEMPLATE, dest, { recursive: true });
|
|
114
194
|
for (const [from, to] of Object.entries(RENAMES)) {
|
|
115
195
|
const src = join(dest, from);
|
|
@@ -117,6 +197,16 @@ function copyTemplate(dest, name, t) {
|
|
|
117
197
|
fail(fill(t.errTemplateMissing, { name: from }));
|
|
118
198
|
renameSync(src, join(dest, to));
|
|
119
199
|
}
|
|
200
|
+
// Un enlace vacío deja `logo: ''` en el perfil, que es exactamente lo que
|
|
201
|
+
// había antes de que se pudieran pasar: no rellenar nada no cambia nada.
|
|
202
|
+
const marcadores = {
|
|
203
|
+
__NAME__: name,
|
|
204
|
+
__LINK_LOGO__: links.logo,
|
|
205
|
+
__LINK_WEB__: links.web,
|
|
206
|
+
__LINK_GITHUB__: links.github,
|
|
207
|
+
__LINK_X__: links.x,
|
|
208
|
+
__LINK_TELEGRAM__: links.telegram,
|
|
209
|
+
};
|
|
120
210
|
const walk = (dir) => {
|
|
121
211
|
for (const entry of readdirSync(dir)) {
|
|
122
212
|
const full = join(dir, entry);
|
|
@@ -125,7 +215,10 @@ function copyTemplate(dest, name, t) {
|
|
|
125
215
|
continue;
|
|
126
216
|
}
|
|
127
217
|
const before = readFileSync(full, 'utf8');
|
|
128
|
-
|
|
218
|
+
let after = before;
|
|
219
|
+
for (const [marcador, valor] of Object.entries(marcadores)) {
|
|
220
|
+
after = after.replaceAll(marcador, valor);
|
|
221
|
+
}
|
|
129
222
|
if (after !== before)
|
|
130
223
|
writeFileSync(full, after, 'utf8');
|
|
131
224
|
}
|
|
@@ -215,21 +308,31 @@ async function main() {
|
|
|
215
308
|
fail(fill(t.errDirExists, { name }));
|
|
216
309
|
if (!existsSync(TEMPLATE))
|
|
217
310
|
fail(fill(t.errTemplateMissing, { name: TEMPLATE }));
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
311
|
+
// El escaparate se pregunta AQUÍ: después de validar el nombre y la carpeta,
|
|
312
|
+
// para no hacer cinco preguntas y fallar luego, y antes de copiar nada,
|
|
313
|
+
// porque los enlaces se escriben dentro de la plantilla al copiarla.
|
|
314
|
+
const links = interactivo ? await askLinks(t, args.links) : args.links;
|
|
221
315
|
// Wallet dedicada, generada aquí. La alternativa —"crea una wallet y pega la
|
|
222
316
|
// clave"— es donde la gente acaba pegando la de su MetaMask personal, que es
|
|
223
317
|
// exactamente lo que no debe vivir en un servidor.
|
|
224
318
|
const privateKey = generatePrivateKey();
|
|
225
319
|
const address = privateKeyToAccount(privateKey).address;
|
|
320
|
+
mkdirSync(dest, { recursive: true });
|
|
321
|
+
copyTemplate(dest, name, links, t);
|
|
322
|
+
const envExample = writeEnvExample(dest, t);
|
|
226
323
|
writeFileSync(join(dest, '.env'), envExample.replace('AGENT_PRIVATE_KEY=', `AGENT_PRIVATE_KEY=${privateKey}`), 'utf8');
|
|
324
|
+
// Su cara desde el primer minuto. El servidor de la plantilla ya sirve este
|
|
325
|
+
// archivo en `/logo`, y el registro publica esa URL solo si no has puesto
|
|
326
|
+
// otra: un agente recién generado deja de salir igual que todos los demás sin
|
|
327
|
+
// que haya que hacer nada.
|
|
328
|
+
writeFileSync(join(dest, 'logo.svg'), logoSvg(name, address), 'utf8');
|
|
227
329
|
// El README del proyecto, en su idioma. Es donde vive lo que no cabe en la
|
|
228
330
|
// pantalla de alta: cómo se cobra, qué hace cada archivo y qué rompe agentes.
|
|
229
331
|
writeFileSync(join(dest, 'README.md'), fill(t.readme, { name, address }), 'utf8');
|
|
230
332
|
console.log(`\n${c.green('✓')} ${c.bold(fill(t.created, { name }))}\n`);
|
|
231
333
|
console.log(` ${t.walletLabel} ${c.bold(address)}`);
|
|
232
|
-
console.log(c.dim(` ${fill(t.walletNote, { name })}
|
|
334
|
+
console.log(c.dim(` ${fill(t.walletNote, { name })}`));
|
|
335
|
+
console.log(c.dim(` ${t.logoWritten}\n`));
|
|
233
336
|
console.log(c.bold(`${t.stepsTitle}\n`));
|
|
234
337
|
console.log(` ${c.bold('1.')} ${t.s1Title}`);
|
|
235
338
|
console.log(c.dim(` ${fill(t.s1Install, { name })}`));
|
package/dist/logo.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* El logo con el que nace un agente.
|
|
3
|
+
*
|
|
4
|
+
* Un agente recién generado salía en el mercado con el hexágono gris que se
|
|
5
|
+
* dibuja a partir de su wallet, igual que todos los demás. No es que quedara
|
|
6
|
+
* feo: es que en una lista de agentes indistinguibles nadie recuerda cuál era
|
|
7
|
+
* el suyo, y el sitio donde eso importa es justo el escaparate.
|
|
8
|
+
*
|
|
9
|
+
* Así que se escribe un `logo.svg` en el proyecto. No hace falta hacer nada
|
|
10
|
+
* más: el servidor de la plantilla ya lo sirve en `/logo` y el registro lo
|
|
11
|
+
* publica solo. Y es un archivo normal en la carpeta, así que cambiarlo por el
|
|
12
|
+
* de verdad es sobrescribirlo.
|
|
13
|
+
*
|
|
14
|
+
* POR QUÉ UNA LETRA Y NO EL HEXÁGONO GENERADO. El avatar que ya pinta el
|
|
15
|
+
* mercado sale de la dirección, y copiarlo aquí daría exactamente la misma
|
|
16
|
+
* imagen: un logo que no se distingue del hueco que rellena no es un logo. La
|
|
17
|
+
* inicial del nombre distingue de un vistazo y funciona en cualquier alfabeto,
|
|
18
|
+
* que es más de lo que se puede decir de un icono elegido por nosotros.
|
|
19
|
+
*/
|
|
20
|
+
/** La paleta de Panal, la misma que usa el avatar del mercado. */
|
|
21
|
+
const FONDOS = ['#E29A2E', '#836EF9', '#6E7B4E', '#1B1814'];
|
|
22
|
+
/** Sobre cualquiera de esos cuatro, este contraste vale. */
|
|
23
|
+
const TINTA = '#F2EFFA';
|
|
24
|
+
/** Hash estable (djb2), el mismo que el avatar del mercado. */
|
|
25
|
+
function semilla(texto) {
|
|
26
|
+
let h = 5381;
|
|
27
|
+
for (let i = 0; i < texto.length; i++)
|
|
28
|
+
h = ((h << 5) + h + texto.charCodeAt(i)) >>> 0;
|
|
29
|
+
return h;
|
|
30
|
+
}
|
|
31
|
+
/** Un hexágono de lado plano centrado en 48,48. */
|
|
32
|
+
function hexagono(r) {
|
|
33
|
+
const puntos = [];
|
|
34
|
+
for (let k = 0; k < 6; k++) {
|
|
35
|
+
const a = (Math.PI / 3) * k;
|
|
36
|
+
puntos.push(`${(48 + r * Math.cos(a)).toFixed(2)},${(48 + r * Math.sin(a)).toFixed(2)}`);
|
|
37
|
+
}
|
|
38
|
+
return puntos.join(' ');
|
|
39
|
+
}
|
|
40
|
+
/** `&`, `<` y `"` dentro de un nombre romperían el SVG. */
|
|
41
|
+
function escapar(texto) {
|
|
42
|
+
return texto
|
|
43
|
+
.replace(/&/g, '&')
|
|
44
|
+
.replace(/</g, '<')
|
|
45
|
+
.replace(/>/g, '>')
|
|
46
|
+
.replace(/"/g, '"');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* La inicial del nombre.
|
|
50
|
+
*
|
|
51
|
+
* Se coge el primer PUNTO DE CÓDIGO, no el primer `char`: un nombre que empiece
|
|
52
|
+
* por un emoji o por un ideograma fuera del plano básico son dos `char`, y
|
|
53
|
+
* quedarse con el primero produce media letra —un carácter de reemplazo— que ni
|
|
54
|
+
* siquiera se puede pintar.
|
|
55
|
+
*/
|
|
56
|
+
export function inicial(nombre) {
|
|
57
|
+
const primero = [...nombre.trim()][0] ?? '?';
|
|
58
|
+
return primero.toLocaleUpperCase();
|
|
59
|
+
}
|
|
60
|
+
/** El SVG del logo con el que arranca un agente, listo para escribir. */
|
|
61
|
+
export function logoSvg(nombre, direccion) {
|
|
62
|
+
const h = semilla(direccion || nombre);
|
|
63
|
+
const fondo = FONDOS[h % FONDOS.length];
|
|
64
|
+
const letra = escapar(inicial(nombre));
|
|
65
|
+
return [
|
|
66
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" width="96" height="96"',
|
|
67
|
+
` role="img" aria-label="${escapar(nombre)}">`,
|
|
68
|
+
`<title>${escapar(nombre)}</title>`,
|
|
69
|
+
`<polygon points="${hexagono(46)}" fill="${fondo}"/>`,
|
|
70
|
+
`<text x="48" y="49" text-anchor="middle" dominant-baseline="central"`,
|
|
71
|
+
` font-family="system-ui, -apple-system, Segoe UI, Roboto, sans-serif"`,
|
|
72
|
+
` font-size="44" font-weight="700" fill="${TINTA}">${letra}</text>`,
|
|
73
|
+
'</svg>',
|
|
74
|
+
'',
|
|
75
|
+
].join('\n');
|
|
76
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-panal-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Crea un agente de IA para Panal, funcionando y cobrando on-chain, en cinco minutos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,6 +42,6 @@
|
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "tsc -p tsconfig.json",
|
|
44
44
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
45
|
-
"test": "tsx test/i18n.test.ts && tsx test/memoria.test.ts && tsx test/reintento.test.ts && tsx test/zip.test.ts && tsx test/adjuntos.test.ts && tsx test/subcontrata.test.ts && tsx test/salida.test.ts && tsx test/vigilante.test.ts && tsx test/scaffold.test.ts"
|
|
45
|
+
"test": "tsx test/i18n.test.ts && tsx test/logo.test.ts && tsx test/memoria.test.ts && tsx test/reintento.test.ts && tsx test/zip.test.ts && tsx test/adjuntos.test.ts && tsx test/subcontrata.test.ts && tsx test/salida.test.ts && tsx test/vigilante.test.ts && tsx test/scaffold.test.ts"
|
|
46
46
|
}
|
|
47
47
|
}
|
package/template/_package.json
CHANGED
package/template/src/register.ts
CHANGED
|
@@ -51,13 +51,19 @@ const PERFIL = {
|
|
|
51
51
|
links: {
|
|
52
52
|
// El logo sale en tu tarjeta, en el mercado y en la app. Https, cuadrado y
|
|
53
53
|
// pequeño: se pinta a 56 px, no hace falta más.
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
//
|
|
55
|
+
// DÉJALO VACÍO Y NO TIENES QUE HACER NADA: tu agente sirve el `logo.svg`
|
|
56
|
+
// que hay en esta carpeta, y al registrarte se comprueba que responde y se
|
|
57
|
+
// publica esa URL. El generador te escribió uno con la inicial de tu
|
|
58
|
+
// nombre; para poner el tuyo, sobrescribe el archivo (vale .svg, .png o
|
|
59
|
+
// .webp) y vuelve a ejecutar `npm run register`.
|
|
60
|
+
logo: '__LINK_LOGO__',
|
|
61
|
+
web: '__LINK_WEB__',
|
|
56
62
|
// Tu perfil o el repositorio del agente: valen `usuario` y `usuario/repo`.
|
|
57
|
-
github: '',
|
|
63
|
+
github: '__LINK_GITHUB__',
|
|
58
64
|
// Solo el usuario; también se traga el enlace entero si lo pegas.
|
|
59
|
-
x: '',
|
|
60
|
-
telegram: '',
|
|
65
|
+
x: '__LINK_X__',
|
|
66
|
+
telegram: '__LINK_TELEGRAM__',
|
|
61
67
|
},
|
|
62
68
|
};
|
|
63
69
|
|
|
@@ -172,6 +178,36 @@ async function compruebaEndpoint(botUrl: string, yo: string): Promise<string | n
|
|
|
172
178
|
return null;
|
|
173
179
|
}
|
|
174
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Tu logo, si no lo has puesto a mano pero tu agente sirve uno.
|
|
183
|
+
*
|
|
184
|
+
* La plantilla te deja un `logo.svg` en la carpeta y el servidor lo publica en
|
|
185
|
+
* `/logo`. Que exista el archivo no basta para escribirlo en la cadena: lo que
|
|
186
|
+
* se guarda es una URL, y una URL que no responde es un hueco en la tarjeta que
|
|
187
|
+
* cuesta otra transacción arreglar. Así que se pide de verdad antes de creerlo.
|
|
188
|
+
*
|
|
189
|
+
* NUNCA lanza ni bloquea el registro. Un logo es un extra; quedarse sin
|
|
190
|
+
* registrar por una imagen sería absurdo.
|
|
191
|
+
*/
|
|
192
|
+
async function logoQueSirves(botUrl: string): Promise<string> {
|
|
193
|
+
let url: string;
|
|
194
|
+
try {
|
|
195
|
+
url = new URL('/logo', botUrl).toString();
|
|
196
|
+
} catch {
|
|
197
|
+
return '';
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const res = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(8_000) });
|
|
201
|
+
const tipo = res.headers.get('content-type') ?? '';
|
|
202
|
+
// `image/` y no `200` a secas: un servidor delante que devuelva la página
|
|
203
|
+
// de error en HTML con estado 200 dejaría escrito en la cadena un logo que
|
|
204
|
+
// no es una imagen, y en la tarjeta un hueco sin explicación.
|
|
205
|
+
return res.ok && tipo.startsWith('image/') ? url : '';
|
|
206
|
+
} catch {
|
|
207
|
+
return '';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
175
211
|
async function main(): Promise<void> {
|
|
176
212
|
const key = process.env.AGENT_PRIVATE_KEY?.trim();
|
|
177
213
|
if (!key || !/^0x[0-9a-fA-F]{64}$/.test(key)) {
|
|
@@ -215,23 +251,36 @@ async function main(): Promise<void> {
|
|
|
215
251
|
process.exit(1);
|
|
216
252
|
}
|
|
217
253
|
|
|
218
|
-
console.log(`\nPerfil: ${formatAgentMetadata(PERFIL)}`);
|
|
219
|
-
console.log(`Precio: ${formatEther(PRECIO)} ${MONEDA === NATIVE_CURRENCY ? 'MON' : '$PANAL'} por tarea\n`);
|
|
220
|
-
|
|
221
254
|
// ¿Ya estabas? Registrarse dos veces revierte, así que se actualiza.
|
|
222
255
|
const existente = await panal.getAgent(account.address).catch(() => null);
|
|
223
256
|
const yaRegistrado = existente !== null && existente.registeredAt > 0n;
|
|
224
257
|
|
|
258
|
+
// Tu logo, si no lo pusiste a mano.
|
|
259
|
+
//
|
|
260
|
+
// Primero se mira si tu agente sirve uno; si no responde se conserva el que
|
|
261
|
+
// ya tuvieras publicado. Eso segundo importa más de lo que parece: sin ello,
|
|
262
|
+
// un corte de red de dos segundos durante un `npm run register` te borraría
|
|
263
|
+
// el logo de la ficha, y no lo dice nadie —la orden termina bien— hasta que
|
|
264
|
+
// alguien mira el mercado y ve el hexágono otra vez.
|
|
265
|
+
const puesto = PERFIL.links.logo.trim();
|
|
266
|
+
const servido = puesto ? '' : await logoQueSirves(PERFIL.botUrl);
|
|
267
|
+
const logo = puesto || servido || existente?.metadata.links.logo || '';
|
|
268
|
+
const perfil = { ...PERFIL, links: { ...PERFIL.links, logo } };
|
|
269
|
+
if (!puesto && logo) console.log(`Logo: ${logo}${servido ? '' : ' (el que ya tenías: tu /logo no responde)'}`);
|
|
270
|
+
|
|
271
|
+
console.log(`\nPerfil: ${formatAgentMetadata(perfil)}`);
|
|
272
|
+
console.log(`Precio: ${formatEther(PRECIO)} ${MONEDA === NATIVE_CURRENCY ? 'MON' : '$PANAL'} por tarea\n`);
|
|
273
|
+
|
|
225
274
|
if (yaRegistrado) {
|
|
226
275
|
console.log('Ya estabas registrado: actualizo el perfil y el precio.');
|
|
227
|
-
await panal.updateMetadata(
|
|
276
|
+
await panal.updateMetadata(perfil);
|
|
228
277
|
await panal.updatePrice(PRECIO, MONEDA);
|
|
229
278
|
if (!existente.active) {
|
|
230
279
|
await panal.setActive(true);
|
|
231
280
|
console.log('Y te vuelvo a poner activo.');
|
|
232
281
|
}
|
|
233
282
|
} else {
|
|
234
|
-
await panal.registerAgent({ metadata:
|
|
283
|
+
await panal.registerAgent({ metadata: perfil, pricePerTask: PRECIO, currency: MONEDA });
|
|
235
284
|
console.log('Registrado.');
|
|
236
285
|
}
|
|
237
286
|
|
package/template/src/server.ts
CHANGED
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
import 'dotenv/config';
|
|
24
24
|
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
25
25
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
26
|
-
import { join } from 'node:path';
|
|
26
|
+
import { dirname, join } from 'node:path';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
27
28
|
import {
|
|
28
29
|
MAX_FILE_BYTES,
|
|
29
30
|
appendFilesManifest,
|
|
@@ -103,6 +104,21 @@ if (NIVELES.length > 0 && NIVELES_OK.length !== NIVELES.length) {
|
|
|
103
104
|
/** El nivel más barato: por debajo de eso, un agente con niveles no trabaja. */
|
|
104
105
|
const NIVEL_MINIMO = NIVELES_OK[0] ?? null;
|
|
105
106
|
|
|
107
|
+
// Se dicen al arrancar. Antes sólo salían dentro del bloque de subcontratación
|
|
108
|
+
// —que no se imprime si no hay presupuesto—, así que un agente con niveles y
|
|
109
|
+
// sin subcontratar arrancaba sin decir una palabra de lo que vende, y la única
|
|
110
|
+
// forma de saber si habían entrado era pedirle la tarjeta.
|
|
111
|
+
if (NIVELES_OK.length > 0) {
|
|
112
|
+
console.log(`Niveles (${NIVELES_OK.length}):`);
|
|
113
|
+
for (const n of NIVELES_OK) {
|
|
114
|
+
const topes = [
|
|
115
|
+
n.maxBriefChars === null ? null : `encargo ${n.maxBriefChars}`,
|
|
116
|
+
n.maxAttachCharsTotal === null ? null : `adjuntos ${n.maxAttachCharsTotal}`,
|
|
117
|
+
].filter(Boolean);
|
|
118
|
+
console.log(` ${n.name ?? '(sin nombre)'} · ${n.wei} · ${topes.length ? topes.join(', ') : 'topes de siempre'}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
106
122
|
// Un nivel que se gasta subcontratando lo mismo que cobra trabaja gratis, y si
|
|
107
123
|
// se pasa, trabaja pagando. No se corrige solo —es una decisión del autor— pero
|
|
108
124
|
// no puede quedarse callado.
|
|
@@ -814,6 +830,60 @@ $('#enviar').onclick = async function(){
|
|
|
814
830
|
};
|
|
815
831
|
</script></body></html>`;
|
|
816
832
|
|
|
833
|
+
/**
|
|
834
|
+
* Las rutas por las que se pide tu logo y los archivos donde se busca.
|
|
835
|
+
*
|
|
836
|
+
* El orden importa: si tienes un `logo.png` y un `logo.svg`, gana el SVG, que
|
|
837
|
+
* es el que escribe el generador y el que escala a cualquier tamaño.
|
|
838
|
+
*/
|
|
839
|
+
const RUTA_LOGO = /^\/logo(\.(svg|png|webp|jpe?g|gif))?$/;
|
|
840
|
+
|
|
841
|
+
const LOGOS: [string, string][] = [
|
|
842
|
+
['logo.svg', 'image/svg+xml; charset=utf-8'],
|
|
843
|
+
['logo.png', 'image/png'],
|
|
844
|
+
['logo.webp', 'image/webp'],
|
|
845
|
+
['logo.jpg', 'image/jpeg'],
|
|
846
|
+
['logo.jpeg', 'image/jpeg'],
|
|
847
|
+
['logo.gif', 'image/gif'],
|
|
848
|
+
];
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* La carpeta de tu proyecto: este archivo vive en `src/`, así que se sube uno.
|
|
852
|
+
*
|
|
853
|
+
* Se calcula desde el módulo y no desde el directorio de trabajo porque no son
|
|
854
|
+
* lo mismo cuando alguien arranca el agente sin pasar por `npm start`: un
|
|
855
|
+
* `systemd` sin `WorkingDirectory=`, o un Docker con otro `WORKDIR`. Y ese caso
|
|
856
|
+
* NO se cae con estruendo —la clave puede venir de una variable de entorno de
|
|
857
|
+
* verdad en vez del `.env`— así que el agente trabaja igual y lo único que pasa
|
|
858
|
+
* es que su logo devuelve 404 y no llega a publicarse. En silencio.
|
|
859
|
+
*/
|
|
860
|
+
const RAIZ = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* El primer logo que exista, o null si no publicas ninguno.
|
|
864
|
+
*
|
|
865
|
+
* Se mira primero junto al proyecto y después en el directorio de trabajo. El
|
|
866
|
+
* respaldo se queda a propósito: si alguien coloca ahí su logo —que es lo que
|
|
867
|
+
* hacía falta hasta ahora— sigue funcionando igual.
|
|
868
|
+
*
|
|
869
|
+
* Se lee en cada petición y no se cachea en memoria a propósito: cambiar de
|
|
870
|
+
* logo es dejar caer un archivo, y tener que reiniciar el agente —cortando los
|
|
871
|
+
* encargos en curso— para cambiar una imagen sería un precio absurdo. Los
|
|
872
|
+
* clientes ya lo cachean una hora por la cabecera.
|
|
873
|
+
*/
|
|
874
|
+
function buscaLogo(): { bytes: Buffer; tipo: string } | null {
|
|
875
|
+
for (const carpeta of [RAIZ, '.']) {
|
|
876
|
+
for (const [archivo, tipo] of LOGOS) {
|
|
877
|
+
try {
|
|
878
|
+
return { bytes: readFileSync(join(carpeta, archivo)), tipo };
|
|
879
|
+
} catch {
|
|
880
|
+
// No está: se prueba el siguiente formato.
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
|
|
817
887
|
function json(res: ServerResponse, status: number, body: unknown): void {
|
|
818
888
|
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
|
|
819
889
|
res.end(JSON.stringify(body));
|
|
@@ -1034,6 +1104,44 @@ const server = createServer((req, res) => {
|
|
|
1034
1104
|
return;
|
|
1035
1105
|
}
|
|
1036
1106
|
|
|
1107
|
+
// ---- Tu logo, si lo pones ------------------------------------------------
|
|
1108
|
+
//
|
|
1109
|
+
// Un archivo `logo.svg` —o `logo.png`, o `logo.webp`— junto al package.json
|
|
1110
|
+
// y ya. El generador te deja uno escrito con la inicial de tu agente, para
|
|
1111
|
+
// que no salgas sin cara desde el primer minuto: sobrescríbelo con el tuyo
|
|
1112
|
+
// y no hay nada más que tocar.
|
|
1113
|
+
//
|
|
1114
|
+
// El registro guarda la URL, así que la imagen tiene que vivir en algún
|
|
1115
|
+
// sitio; y el sitio natural es el mismo dominio que ya sirves, porque es el
|
|
1116
|
+
// que la cadena ya dice que es tuyo.
|
|
1117
|
+
//
|
|
1118
|
+
// RESPONDE A `/logo` Y A CUALQUIER EXTENSIÓN, y sirve el archivo que de
|
|
1119
|
+
// verdad tengas, sea cual sea la que te pidan. Suena descuidado y no lo es:
|
|
1120
|
+
// los agentes que ya están registrados publicaron `…/logo.svg` y esa URL
|
|
1121
|
+
// está escrita en la cadena, así que cambiar de formato no puede obligarles
|
|
1122
|
+
// a pagar otra transacción. Lo que decide cómo se pinta una imagen es el
|
|
1123
|
+
// `content-type`, no la extensión de la URL.
|
|
1124
|
+
//
|
|
1125
|
+
// Se sirve con CORS abierto a propósito: es una imagen pública que va a
|
|
1126
|
+
// pintarse en escaparates ajenos, y sin la cabecera un `<canvas>` que la
|
|
1127
|
+
// toque para hacer una miniatura se queda a oscuras.
|
|
1128
|
+
if (RUTA_LOGO.test(url.pathname) && (req.method === 'GET' || req.method === 'HEAD')) {
|
|
1129
|
+
const logo = buscaLogo();
|
|
1130
|
+
if (!logo) {
|
|
1131
|
+
json(res, 404, { error: 'este agente no publica logo' });
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
res.writeHead(200, {
|
|
1135
|
+
'content-type': logo.tipo,
|
|
1136
|
+
'content-length': logo.bytes.byteLength,
|
|
1137
|
+
'cache-control': 'public, max-age=3600',
|
|
1138
|
+
'access-control-allow-origin': '*',
|
|
1139
|
+
'x-content-type-options': 'nosniff',
|
|
1140
|
+
});
|
|
1141
|
+
res.end(req.method === 'HEAD' ? undefined : logo.bytes);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1037
1145
|
// Tarjeta de presentación: quién eres y qué sabes hacer.
|
|
1038
1146
|
//
|
|
1039
1147
|
// Si cobras por llamada hay que ANUNCIARLO aquí. Durante meses el bot de
|