agentlaunch-templates 0.1.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.
Files changed (37) hide show
  1. package/dist/generator.d.ts +43 -0
  2. package/dist/generator.d.ts.map +1 -0
  3. package/dist/generator.js +213 -0
  4. package/dist/generator.js.map +1 -0
  5. package/dist/index.d.ts +41 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +39 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/registry.d.ts +49 -0
  10. package/dist/registry.d.ts.map +1 -0
  11. package/dist/registry.js +47 -0
  12. package/dist/registry.js.map +1 -0
  13. package/dist/templates/custom.d.ts +11 -0
  14. package/dist/templates/custom.d.ts.map +1 -0
  15. package/dist/templates/custom.js +458 -0
  16. package/dist/templates/custom.js.map +1 -0
  17. package/dist/templates/data-analyzer.d.ts +11 -0
  18. package/dist/templates/data-analyzer.d.ts.map +1 -0
  19. package/dist/templates/data-analyzer.js +565 -0
  20. package/dist/templates/data-analyzer.js.map +1 -0
  21. package/dist/templates/gifter.d.ts +15 -0
  22. package/dist/templates/gifter.d.ts.map +1 -0
  23. package/dist/templates/gifter.js +717 -0
  24. package/dist/templates/gifter.js.map +1 -0
  25. package/dist/templates/price-monitor.d.ts +11 -0
  26. package/dist/templates/price-monitor.d.ts.map +1 -0
  27. package/dist/templates/price-monitor.js +577 -0
  28. package/dist/templates/price-monitor.js.map +1 -0
  29. package/dist/templates/research.d.ts +11 -0
  30. package/dist/templates/research.d.ts.map +1 -0
  31. package/dist/templates/research.js +593 -0
  32. package/dist/templates/research.js.map +1 -0
  33. package/dist/templates/trading-bot.d.ts +11 -0
  34. package/dist/templates/trading-bot.d.ts.map +1 -0
  35. package/dist/templates/trading-bot.js +559 -0
  36. package/dist/templates/trading-bot.js.map +1 -0
  37. package/package.json +24 -0
@@ -0,0 +1,458 @@
1
+ /**
2
+ * custom.ts — Blank boilerplate template with Chat Protocol
3
+ *
4
+ * Platform constants (source of truth: deployed smart contracts):
5
+ * - Deploy fee: 120 FET (read dynamically, can change via multi-sig)
6
+ * - Graduation target: 30,000 FET -> auto DEX listing
7
+ * - Trading fee: 2% -> 100% to protocol treasury (NO creator fee)
8
+ */
9
+ export const template = {
10
+ name: "custom",
11
+ description: "Blank boilerplate with Chat Protocol v0.3.0 — add your own business logic",
12
+ category: "General",
13
+ variables: [
14
+ { name: "agent_name", required: true, description: "Name of the agent" },
15
+ {
16
+ name: "description",
17
+ required: true,
18
+ description: "Short description of what this agent does",
19
+ },
20
+ {
21
+ name: "rate_limit_per_minute",
22
+ default: "20",
23
+ description: "Max requests per user per minute",
24
+ },
25
+ {
26
+ name: "free_requests_per_day",
27
+ default: "10",
28
+ description: "Free tier daily request quota",
29
+ },
30
+ {
31
+ name: "premium_token_threshold",
32
+ default: "1000",
33
+ description: "Token balance required for premium tier",
34
+ },
35
+ ],
36
+ dependencies: ["requests"],
37
+ secrets: ["AGENTVERSE_API_KEY", "AGENTLAUNCH_API_KEY", "AGENT_ADDRESS", "AGENT_OWNER_ADDRESS"],
38
+ code: `#!/usr/bin/env python3
39
+ """
40
+ {{agent_name}} — AgentLaunch Custom Agent
41
+ Generated by: agentlaunch scaffold {{agent_name}} --type custom
42
+
43
+ Platform constants (source of truth: deployed smart contracts):
44
+ - Deploy fee: 120 FET (read dynamically, can change via multi-sig)
45
+ - Graduation target: 30,000 FET -> auto DEX listing
46
+ - Trading fee: 2% -> 100% to protocol treasury (NO creator fee)
47
+ """
48
+
49
+ from uagents import Agent, Context, Protocol
50
+ from uagents_core.contrib.protocols.chat import (
51
+ ChatAcknowledgement,
52
+ ChatMessage,
53
+ EndSessionContent,
54
+ TextContent,
55
+ chat_protocol_spec,
56
+ )
57
+
58
+ import json
59
+ import os
60
+ import time
61
+ from collections import defaultdict
62
+ from datetime import datetime
63
+ from typing import Any, Dict, List, Optional
64
+ from uuid import uuid4
65
+
66
+ import requests
67
+
68
+ # ==============================================================================
69
+ # API CONFIG — Override via environment variables, never hardcode
70
+ # ==============================================================================
71
+
72
+ AGENTLAUNCH_API = os.environ.get("AGENTLAUNCH_API", "https://agent-launch.ai/api")
73
+
74
+ # ==============================================================================
75
+ # BUSINESS CONFIG
76
+ # ==============================================================================
77
+
78
+ OWNER_ADDRESS = os.environ.get("AGENT_OWNER_ADDRESS", "")
79
+
80
+ BUSINESS = {
81
+ "name": "{{agent_name}}",
82
+ "description": "{{description}}",
83
+ "version": "1.0.0",
84
+ "free_requests_per_day": {{free_requests_per_day}},
85
+ "premium_token_threshold": {{premium_token_threshold}},
86
+ "rate_limit_per_minute": {{rate_limit_per_minute}},
87
+ "max_input_length": 5000,
88
+ }
89
+
90
+
91
+ # ==============================================================================
92
+ # LAYER 1: FOUNDATION
93
+ # ==============================================================================
94
+
95
+
96
+ class Logger:
97
+ """Structured logging with audit trail."""
98
+
99
+ @staticmethod
100
+ def info(ctx: Context, event: str, data: Optional[Dict] = None) -> None:
101
+ ctx.logger.info(f"[{event}] {json.dumps(data or {})}")
102
+
103
+ @staticmethod
104
+ def audit(ctx: Context, user: str, action: str) -> None:
105
+ ctx.logger.info(
106
+ f"[AUDIT] user={user[:20]} action={action} "
107
+ f"ts={datetime.utcnow().isoformat()}"
108
+ )
109
+
110
+ @staticmethod
111
+ def error(ctx: Context, event: str, error: str) -> None:
112
+ ctx.logger.error(f"[{event}] {error}")
113
+
114
+
115
+ # ==============================================================================
116
+ # LAYER 2: SECURITY
117
+ # ==============================================================================
118
+
119
+
120
+ class Security:
121
+ """Rate limiting and input validation."""
122
+
123
+ def __init__(self) -> None:
124
+ self._requests: Dict[str, List[float]] = defaultdict(list)
125
+ self._check_count: int = 0
126
+
127
+ def check(self, ctx: Context, user_id: str, message: str) -> tuple:
128
+ now = time.time()
129
+
130
+ self._requests[user_id] = [
131
+ t for t in self._requests[user_id] if now - t < 60
132
+ ]
133
+ if len(self._requests[user_id]) >= BUSINESS["rate_limit_per_minute"]:
134
+ return None, "Rate limit exceeded. Please wait a moment."
135
+ self._requests[user_id].append(now)
136
+
137
+ self._check_count += 1
138
+ if self._check_count % 100 == 0:
139
+ stale = [
140
+ k
141
+ for k, v in self._requests.items()
142
+ if not v or (now - max(v)) > 300
143
+ ]
144
+ for k in stale:
145
+ del self._requests[k]
146
+
147
+ if not message or not message.strip():
148
+ return None, "Empty message."
149
+ if len(message) > BUSINESS["max_input_length"]:
150
+ return None, f"Message too long (max {BUSINESS['max_input_length']} chars)."
151
+
152
+ return message.strip(), None
153
+
154
+
155
+ # ==============================================================================
156
+ # LAYER 3: STABILITY
157
+ # ==============================================================================
158
+
159
+
160
+ class Health:
161
+ """Track uptime and error rate."""
162
+
163
+ def __init__(self) -> None:
164
+ self._start: datetime = datetime.utcnow()
165
+ self._requests: int = 0
166
+ self._errors: int = 0
167
+
168
+ def record(self, success: bool) -> None:
169
+ self._requests += 1
170
+ if not success:
171
+ self._errors += 1
172
+
173
+ def status(self) -> Dict[str, Any]:
174
+ uptime = (datetime.utcnow() - self._start).total_seconds()
175
+ error_rate = (self._errors / self._requests * 100) if self._requests else 0
176
+ return {
177
+ "status": "healthy" if error_rate < 10 else "degraded",
178
+ "uptime_seconds": int(uptime),
179
+ "requests": self._requests,
180
+ "error_rate": f"{error_rate:.1f}%",
181
+ }
182
+
183
+
184
+ # ==============================================================================
185
+ # LAYER 4: SPEED
186
+ # ==============================================================================
187
+
188
+
189
+ class Cache:
190
+ """In-memory TTL cache with SHA256 keys."""
191
+
192
+ def __init__(self, max_size: int = 1000) -> None:
193
+ self._data: Dict[str, tuple] = {}
194
+ self._max_size: int = max_size
195
+
196
+ def get(self, key: str) -> Any:
197
+ if key in self._data:
198
+ value, expires = self._data[key]
199
+ if expires > time.time():
200
+ return value
201
+ del self._data[key]
202
+ return None
203
+
204
+ def set(self, key: str, value: Any, ttl: int = 300) -> None:
205
+ if len(self._data) >= self._max_size:
206
+ now = time.time()
207
+ expired = [k for k, (_, exp) in self._data.items() if exp <= now]
208
+ for k in expired:
209
+ del self._data[k]
210
+ if len(self._data) >= self._max_size:
211
+ to_drop = sorted(self._data.items(), key=lambda x: x[1][1])[
212
+ : self._max_size // 10
213
+ ]
214
+ for k, _ in to_drop:
215
+ del self._data[k]
216
+ self._data[key] = (value, time.time() + ttl)
217
+
218
+
219
+ # ==============================================================================
220
+ # LAYER 5: REVENUE
221
+ # ==============================================================================
222
+
223
+
224
+ class Revenue:
225
+ """Token-gated access and daily usage quotas."""
226
+
227
+ def __init__(self, cache: Cache) -> None:
228
+ self._cache = cache
229
+ self._usage: Dict[str, List[str]] = defaultdict(list)
230
+
231
+ def get_tier(self, user_address: str) -> str:
232
+ cached = self._cache.get(f"tier:{user_address}")
233
+ if cached is not None:
234
+ return cached
235
+ try:
236
+ r = requests.get(
237
+ f"{AGENTLAUNCH_API}/agents/token/{user_address}", timeout=5
238
+ )
239
+ if r.status_code == 200:
240
+ data = r.json()
241
+ balance = data.get("balance", 0)
242
+ tier = (
243
+ "premium"
244
+ if balance >= BUSINESS["premium_token_threshold"]
245
+ else "free"
246
+ )
247
+ self._cache.set(f"tier:{user_address}", tier, ttl=300)
248
+ return tier
249
+ except Exception:
250
+ pass
251
+ return "free"
252
+
253
+ def check_quota(self, user_id: str, tier: str) -> tuple:
254
+ today = datetime.utcnow().date().isoformat()
255
+ self._usage[user_id] = [
256
+ t for t in self._usage[user_id] if t.startswith(today)
257
+ ]
258
+ today_usage = len(self._usage[user_id])
259
+ limit = 1000 if tier == "premium" else BUSINESS["free_requests_per_day"]
260
+ if today_usage >= limit:
261
+ if tier == "free":
262
+ return False, (
263
+ f"Free limit reached ({limit}/day). "
264
+ f"Hold {BUSINESS['premium_token_threshold']} tokens for premium!"
265
+ )
266
+ return False, f"Daily limit reached ({limit}/day)."
267
+ self._usage[user_id].append(datetime.utcnow().isoformat())
268
+ return True, None
269
+
270
+
271
+ # ==============================================================================
272
+ # AGENTLAUNCH INTEGRATION
273
+ # ==============================================================================
274
+
275
+
276
+ class AgentLaunch:
277
+ """Create and manage tokens on AgentLaunch."""
278
+
279
+ @staticmethod
280
+ def tokenize() -> Dict:
281
+ agent_address = os.environ.get("AGENT_ADDRESS")
282
+ if not agent_address:
283
+ return {"error": "AGENT_ADDRESS env var not set."}
284
+ try:
285
+ r = requests.post(
286
+ f"{AGENTLAUNCH_API}/agents/tokenize",
287
+ headers={
288
+ "X-API-Key": os.environ.get("AGENTLAUNCH_API_KEY", ""),
289
+ "Content-Type": "application/json",
290
+ },
291
+ json={
292
+ "agentAddress": agent_address,
293
+ "name": BUSINESS["name"],
294
+ "description": BUSINESS["description"],
295
+ },
296
+ timeout=30,
297
+ )
298
+ return r.json() if r.status_code in [200, 201] else {"error": r.text}
299
+ except Exception as e:
300
+ return {"error": str(e)}
301
+
302
+
303
+ # ==============================================================================
304
+ # YOUR BUSINESS LOGIC — Customize this section
305
+ # ==============================================================================
306
+
307
+
308
+ class CustomBusiness:
309
+ """
310
+ CUSTOMIZE THIS for your agent.
311
+
312
+ Pattern: Agent + Wallet + Chat + Value Exchange = Economy
313
+ """
314
+
315
+ def __init__(self) -> None:
316
+ pass
317
+
318
+ async def handle(self, ctx: Context, user_id: str, message: str, tier: str) -> str:
319
+ # Sanitize to prevent prompt injection
320
+ safe = message.replace("\\n", " ").replace("\\\\n", " ")[:500]
321
+
322
+ # TODO: Add your business logic here
323
+ return (
324
+ f"Hello from {BUSINESS['name']}! You said: {safe}\\n\\n"
325
+ f"Tier: {tier}. This is a scaffold — add your logic in CustomBusiness.handle()."
326
+ )
327
+
328
+
329
+ # ==============================================================================
330
+ # REPLY HELPER
331
+ # ==============================================================================
332
+
333
+
334
+ async def reply(ctx: Context, sender: str, text: str, end: bool = False) -> None:
335
+ content = [TextContent(type="text", text=text)]
336
+ if end:
337
+ content.append(EndSessionContent(type="end-session"))
338
+ try:
339
+ await ctx.send(
340
+ sender,
341
+ ChatMessage(timestamp=datetime.utcnow(), msg_id=uuid4(), content=content),
342
+ )
343
+ except Exception as e:
344
+ ctx.logger.error(f"Failed to send reply to {sender[:20]}: {e}")
345
+
346
+
347
+ # ==============================================================================
348
+ # MAIN AGENT
349
+ # ==============================================================================
350
+
351
+ cache = Cache(max_size=1000)
352
+ security = Security()
353
+ health = Health()
354
+ revenue = Revenue(cache)
355
+ business = CustomBusiness()
356
+
357
+ agent = Agent()
358
+ chat_proto = Protocol(spec=chat_protocol_spec)
359
+
360
+
361
+ @chat_proto.on_message(ChatMessage)
362
+ async def handle_chat(ctx: Context, sender: str, msg: ChatMessage) -> None:
363
+ try:
364
+ await ctx.send(
365
+ sender,
366
+ ChatAcknowledgement(
367
+ timestamp=datetime.utcnow(), acknowledged_msg_id=msg.msg_id
368
+ ),
369
+ )
370
+ except Exception as e:
371
+ ctx.logger.error(f"Failed to send ack to {sender[:20]}: {e}")
372
+
373
+ text = " ".join(
374
+ item.text for item in msg.content if isinstance(item, TextContent)
375
+ ).strip()
376
+ text = text[: BUSINESS["max_input_length"]]
377
+
378
+ clean, error = security.check(ctx, sender, text)
379
+ if error:
380
+ health.record(False)
381
+ await reply(ctx, sender, error, end=True)
382
+ return
383
+
384
+ Logger.audit(ctx, sender, "request")
385
+
386
+ lower = clean.lower()
387
+
388
+ if lower in ("help", "?"):
389
+ tier = revenue.get_tier(sender)
390
+ await reply(
391
+ ctx,
392
+ sender,
393
+ f"**{BUSINESS['name']}** v{BUSINESS['version']}\\n\\n"
394
+ f"{BUSINESS['description']}\\n\\n"
395
+ f"Your tier: {tier.upper()}\\n\\n"
396
+ f"Commands: help, status, tokenize",
397
+ )
398
+ return
399
+
400
+ if lower == "status":
401
+ s = health.status()
402
+ await reply(
403
+ ctx,
404
+ sender,
405
+ f"Status: {s['status']} | Uptime: {s['uptime_seconds']}s | "
406
+ f"Requests: {s['requests']} | Error rate: {s['error_rate']}",
407
+ )
408
+ return
409
+
410
+ if "tokenize" in lower:
411
+ if OWNER_ADDRESS and sender != OWNER_ADDRESS:
412
+ await reply(ctx, sender, "Only the agent owner can trigger tokenization.", end=True)
413
+ return
414
+ result = AgentLaunch.tokenize()
415
+ link = result.get("data", {}).get("handoff_link") or result.get("handoff_link")
416
+ await reply(
417
+ ctx,
418
+ sender,
419
+ f"Token created! Deploy here: {link}" if link else f"Result: {json.dumps(result)}",
420
+ end=True,
421
+ )
422
+ return
423
+
424
+ tier = revenue.get_tier(sender)
425
+ allowed, quota_error = revenue.check_quota(sender, tier)
426
+ if not allowed:
427
+ health.record(False)
428
+ await reply(ctx, sender, quota_error, end=True)
429
+ return
430
+
431
+ try:
432
+ response = await business.handle(ctx, sender, clean, tier)
433
+ health.record(True)
434
+ except Exception as e:
435
+ health.record(False)
436
+ Logger.error(ctx, "business_handle", str(e))
437
+ response = "Something went wrong. Please try again."
438
+
439
+ await reply(ctx, sender, response, end=True)
440
+
441
+
442
+ @chat_proto.on_message(ChatAcknowledgement)
443
+ async def handle_ack(ctx: Context, sender: str, msg: ChatAcknowledgement) -> None:
444
+ ctx.logger.debug(f"Ack from {sender[:20]} for msg {msg.acknowledged_msg_id}")
445
+
446
+
447
+ @agent.on_interval(period=3600)
448
+ async def periodic_health(ctx: Context) -> None:
449
+ ctx.logger.info(f"[HEALTH] {json.dumps(health.status())}")
450
+
451
+
452
+ agent.include(chat_proto, publish_manifest=True)
453
+
454
+ if __name__ == "__main__":
455
+ agent.run()
456
+ `,
457
+ };
458
+ //# sourceMappingURL=custom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom.js","sourceRoot":"","sources":["../../src/templates/custom.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,2EAA2E;IACxF,QAAQ,EAAE,SAAS;IACnB,SAAS,EAAE;QACT,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,mBAAmB,EAAE;QACxE;YACE,IAAI,EAAE,aAAa;YACnB,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,2CAA2C;SACzD;QACD;YACE,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,kCAAkC;SAChD;QACD;YACE,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,+BAA+B;SAC7C;QACD;YACE,IAAI,EAAE,yBAAyB;YAC/B,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,yCAAyC;SACvD;KACF;IACD,YAAY,EAAE,CAAC,UAAU,CAAC;IAC1B,OAAO,EAAE,CAAC,oBAAoB,EAAE,qBAAqB,EAAE,eAAe,EAAE,qBAAqB,CAAC;IAC9F,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkaP;CACA,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * data-analyzer.ts — Serves structured data feeds and query results
3
+ *
4
+ * Platform constants (source of truth: deployed smart contracts):
5
+ * - Deploy fee: 120 FET (read dynamically, can change via multi-sig)
6
+ * - Graduation target: 30,000 FET -> auto DEX listing
7
+ * - Trading fee: 2% -> 100% to protocol treasury (NO creator fee)
8
+ */
9
+ import type { AgentTemplate } from "../registry.js";
10
+ export declare const template: AgentTemplate;
11
+ //# sourceMappingURL=data-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-analyzer.d.ts","sourceRoot":"","sources":["../../src/templates/data-analyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,eAAO,MAAM,QAAQ,EAAE,aA2iBtB,CAAC"}