create-panal-agent 0.7.0 → 0.8.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-panal-agent",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
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",
@@ -19,7 +19,7 @@
19
19
  "starter"
20
20
  ],
21
21
  "bin": {
22
- "create-panal-agent": "./dist/index.js"
22
+ "create-panal-agent": "dist/index.js"
23
23
  },
24
24
  "files": [
25
25
  "dist",
@@ -339,7 +339,15 @@ async function pedirAlModelo(
339
339
  // 3. Sin la del registro, envuelve el trabajo en "¡Claro! Aquí
340
340
  // tienes…" y el entregable parece un chat, no un producto.
341
341
  'You are a professional agent on the Panal marketplace. ' +
342
- 'RULE 1: detect the language of the request and reply in that exact same language; never switch. ' +
342
+ // El "never fall back to English" y lo de los títulos no son
343
+ // adorno: en producción, una petición en portugués volvió entera en
344
+ // inglés porque el prompt listaba los títulos de sección en inglés y
345
+ // el modelo los copiaba; y otra en chino devolvió las claves en
346
+ // inglés. Los dos fallos con la regla del idioma ya puesta.
347
+ 'RULE 1, before anything else: detect the language of the request and reply in that exact same ' +
348
+ 'language; never switch part-way, and never fall back to English because the request is not in ' +
349
+ 'English. If the instructions below name sections, headings or field names, translate those too: ' +
350
+ 'they are written in one language only because these instructions are. ' +
343
351
  'RULE 2: plain text only, never Markdown — no # headings, no ** bold, no backticks. ' +
344
352
  'RULE 3: deliver finished professional work, with no preamble or meta-commentary.\n' +
345
353
  // El agente adjunta el archivo por su cuenta; el modelo no se entera
@@ -14,7 +14,7 @@
14
14
  import 'dotenv/config';
15
15
  import { createPanalClient, formatAgentMetadata, NATIVE_CURRENCY } from '@panal/sdk';
16
16
  import { privateKeyToAccount } from 'viem/accounts';
17
- import { formatEther, parseEther } from 'viem';
17
+ import { createPublicClient, createWalletClient, formatEther, http, parseEther } from 'viem';
18
18
 
19
19
  // ────────────────────────────────────────────────────────────────────────────
20
20
  // RELLENA ESTO. Es tu escaparate: lo que verá quien busque un agente.
@@ -214,10 +214,138 @@ async function main(): Promise<void> {
214
214
  console.log('Registrado.');
215
215
  }
216
216
 
217
+ // El nombre va DESPUÉS del registro y no puede tumbarlo: `reclamar` exige
218
+ // estar registrado y activo, así que el orden es obligatorio, y si algo falla
219
+ // —nombre cogido, sin saldo, contrato no desplegado— el agente ya está
220
+ // trabajando igual. El nombre es un extra, no un requisito.
221
+ await reclamaTuNombre(account, PERFIL.name);
222
+
217
223
  console.log(`\nYa apareces en https://panal.lat/market`);
218
224
  console.log(`Compruébalo desde Claude: "¿qué agentes hay en Panal?"`);
219
225
  }
220
226
 
227
+ /**
228
+ * Convierte el nombre del perfil en un handle válido para PanalNames.
229
+ *
230
+ * El contrato solo acepta `a-z`, `0-9` y `-`, y ahí es donde mueren los
231
+ * homoglifos: la `а` cirílica no colisiona con la latina, es que no se puede
232
+ * escribir. Así que "LexPanal" pasa a `lexpanal` y "Traductor ES→DE" a
233
+ * `traductor-es-de`.
234
+ *
235
+ * Los acentos se quitan descomponiendo el texto (NFD) y tirando las marcas:
236
+ * "Ágil" -> `agil`. Transliterar a ojo cada idioma sería inventar.
237
+ */
238
+ export function aHandle(nombre: string): string {
239
+ return nombre
240
+ .normalize('NFD')
241
+ .replace(/\p{M}/gu, '')
242
+ .toLowerCase()
243
+ .replace(/[^a-z0-9]+/g, '-')
244
+ .replace(/^-+|-+$/g, '')
245
+ .slice(0, 32)
246
+ .replace(/-+$/, '');
247
+ }
248
+
249
+ /**
250
+ * PanalNames en Monad mainnet. Vacío mientras no esté desplegado, y entonces
251
+ * este paso no hace nada — que es lo correcto: no hay contrato al que pedirle.
252
+ * Se puede apuntar a otro con PANAL_NAMES_ADDRESS.
253
+ */
254
+ const PANAL_NAMES = '';
255
+
256
+ const NOMBRES_ABI = [
257
+ {
258
+ type: 'function',
259
+ name: 'disponible',
260
+ stateMutability: 'view',
261
+ inputs: [{ name: 'nombre', type: 'string' }],
262
+ outputs: [{ name: '', type: 'bool' }],
263
+ },
264
+ {
265
+ type: 'function',
266
+ name: 'nombreDe',
267
+ stateMutability: 'view',
268
+ inputs: [{ name: 'agente', type: 'address' }],
269
+ outputs: [{ name: '', type: 'string' }],
270
+ },
271
+ {
272
+ type: 'function',
273
+ name: 'tarifaDe',
274
+ stateMutability: 'view',
275
+ inputs: [{ name: 'nombre', type: 'string' }],
276
+ outputs: [{ name: '', type: 'uint256' }],
277
+ },
278
+ {
279
+ type: 'function',
280
+ name: 'reclamar',
281
+ stateMutability: 'nonpayable',
282
+ inputs: [{ name: 'nombre', type: 'string' }],
283
+ outputs: [],
284
+ },
285
+ ] as const;
286
+
287
+ /**
288
+ * Reclama tu nombre único en PanalNames, si se puede.
289
+ *
290
+ * NUNCA lanza. Todo lo que puede salir mal aquí —que el contrato no esté
291
+ * desplegado, que el nombre esté cogido, que no tengas saldo— es un extra que
292
+ * no sale, y el agente ya está registrado y trabajando. Se avisa y se sigue.
293
+ *
294
+ * Se hace en el mismo comando a propósito: si hay que volver días después a
295
+ * reclamarlo, para entonces se lo habrá quedado otro.
296
+ */
297
+ async function reclamaTuNombre(account: ReturnType<typeof privateKeyToAccount>, nombre: string): Promise<void> {
298
+ const contrato = process.env.PANAL_NAMES_ADDRESS?.trim() || PANAL_NAMES;
299
+ if (!contrato || !/^0x[0-9a-fA-F]{40}$/.test(contrato)) return;
300
+
301
+ const handle = aHandle(nombre);
302
+ if (handle.length < 3) {
303
+ // Pasa con los nombres en alfabetos no latinos: el contrato solo acepta
304
+ // `a-z0-9-`, que es lo que impide los homoglifos, así que de "日本語" no
305
+ // sale nada. No es un fallo, pero hay que decir qué hacer.
306
+ console.log(`\nNo te reclamo nombre: de "${nombre}" no sale un handle de 3 letras o más.`);
307
+ console.log(`Los nombres solo admiten a-z, 0-9 y guion. Elige uno a mano desde https://panal.lat/dashboard`);
308
+ return;
309
+ }
310
+
311
+ try {
312
+ const rpc = process.env.RPC_URL?.trim() || 'https://rpc.monad.xyz';
313
+ const chain = { id: 143, name: 'Monad', nativeCurrency: { name: 'MON', symbol: 'MON', decimals: 18 }, rpcUrls: { default: { http: [rpc] } } } as const;
314
+ const publico = createPublicClient({ transport: http(rpc) });
315
+ const cartera = createWalletClient({ account, chain, transport: http(rpc) });
316
+ const donde = contrato as `0x${string}`;
317
+
318
+ const yaTengo = await publico.readContract({ address: donde, abi: NOMBRES_ABI, functionName: 'nombreDe', args: [account.address] });
319
+ if (yaTengo) {
320
+ console.log(`\nTu nombre en Panal ya es: ${yaTengo}`);
321
+ return;
322
+ }
323
+
324
+ const libre = await publico.readContract({ address: donde, abi: NOMBRES_ABI, functionName: 'disponible', args: [handle] });
325
+ if (!libre) {
326
+ console.log(`\nEl nombre "${handle}" ya está cogido. Puedes reclamar otro desde https://panal.lat/dashboard`);
327
+ return;
328
+ }
329
+
330
+ const tarifa = await publico.readContract({ address: donde, abi: NOMBRES_ABI, functionName: 'tarifaDe', args: [handle] });
331
+ if (tarifa > 0n) {
332
+ // Con tarifa hay que aprobar el gasto antes, y eso es otra firma y otra
333
+ // decision. No se hace a tus espaldas: se te dice y lo haces tu.
334
+ console.log(`\nTu nombre "${handle}" está libre, pero cuesta ${formatEther(tarifa)} $PANAL.`);
335
+ console.log(`Reclámalo desde https://panal.lat/dashboard cuando quieras.`);
336
+ return;
337
+ }
338
+
339
+ const hash = await cartera.writeContract({ address: donde, abi: NOMBRES_ABI, functionName: 'reclamar', args: [handle], chain });
340
+ await publico.waitForTransactionReceipt({ hash });
341
+ console.log(`\nTu nombre único en Panal: ${handle}`);
342
+ } catch (err) {
343
+ // Un fallo aqui no es grave: el agente ya esta registrado y puede trabajar.
344
+ console.log(`\nNo pude reclamarte el nombre (${err instanceof Error ? err.message.split('\n')[0] : err}).`);
345
+ console.log(`Puedes hacerlo luego desde https://panal.lat/dashboard`);
346
+ }
347
+ }
348
+
221
349
  main().catch((err) => {
222
350
  console.error(`\nFalló: ${err instanceof Error ? err.message : err}`);
223
351
  process.exit(1);