agentlaunch-cli 1.0.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 (46) hide show
  1. package/README.md +359 -0
  2. package/dist/__tests__/config.test.d.ts +20 -0
  3. package/dist/__tests__/config.test.d.ts.map +1 -0
  4. package/dist/__tests__/config.test.js +155 -0
  5. package/dist/__tests__/config.test.js.map +1 -0
  6. package/dist/commands/config.d.ts +10 -0
  7. package/dist/commands/config.d.ts.map +1 -0
  8. package/dist/commands/config.js +56 -0
  9. package/dist/commands/config.js.map +1 -0
  10. package/dist/commands/create.d.ts +22 -0
  11. package/dist/commands/create.d.ts.map +1 -0
  12. package/dist/commands/create.js +493 -0
  13. package/dist/commands/create.js.map +1 -0
  14. package/dist/commands/deploy.d.ts +18 -0
  15. package/dist/commands/deploy.d.ts.map +1 -0
  16. package/dist/commands/deploy.js +220 -0
  17. package/dist/commands/deploy.js.map +1 -0
  18. package/dist/commands/list.d.ts +10 -0
  19. package/dist/commands/list.d.ts.map +1 -0
  20. package/dist/commands/list.js +131 -0
  21. package/dist/commands/list.js.map +1 -0
  22. package/dist/commands/scaffold.d.ts +13 -0
  23. package/dist/commands/scaffold.d.ts.map +1 -0
  24. package/dist/commands/scaffold.js +633 -0
  25. package/dist/commands/scaffold.js.map +1 -0
  26. package/dist/commands/status.d.ts +10 -0
  27. package/dist/commands/status.d.ts.map +1 -0
  28. package/dist/commands/status.js +116 -0
  29. package/dist/commands/status.js.map +1 -0
  30. package/dist/commands/tokenize.d.ts +16 -0
  31. package/dist/commands/tokenize.d.ts.map +1 -0
  32. package/dist/commands/tokenize.js +139 -0
  33. package/dist/commands/tokenize.js.map +1 -0
  34. package/dist/config.d.ts +35 -0
  35. package/dist/config.d.ts.map +1 -0
  36. package/dist/config.js +68 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/http.d.ts +20 -0
  39. package/dist/http.d.ts.map +1 -0
  40. package/dist/http.js +69 -0
  41. package/dist/http.js.map +1 -0
  42. package/dist/index.d.ts +21 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +49 -0
  45. package/dist/index.js.map +1 -0
  46. package/package.json +43 -0
@@ -0,0 +1,633 @@
1
+ /**
2
+ * CLI-002: scaffold command
3
+ *
4
+ * agentlaunch scaffold <name> [--type faucet|research|trading|data]
5
+ *
6
+ * Generates an agent project directory with:
7
+ * agent.py - Ready-to-edit agent code based on agent-business-template.py
8
+ * README.md - Quickstart instructions
9
+ * .env.example - Required environment variables
10
+ */
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+ /** Per-type customisation slots injected into the template. */
14
+ const TYPE_META = {
15
+ faucet: {
16
+ domain: "faucet",
17
+ description: "Distributes testnet FET and BNB to new developers",
18
+ model: "mistralai/Mistral-7B-Instruct-v0.2",
19
+ },
20
+ research: {
21
+ domain: "research",
22
+ description: "Delivers on-demand research reports and analysis",
23
+ model: "mistralai/Mistral-7B-Instruct-v0.2",
24
+ },
25
+ trading: {
26
+ domain: "trading",
27
+ description: "Monitors token prices and sends trade alerts",
28
+ model: "mistralai/Mistral-7B-Instruct-v0.2",
29
+ },
30
+ data: {
31
+ domain: "data",
32
+ description: "Serves structured data feeds and query results",
33
+ model: "mistralai/Mistral-7B-Instruct-v0.2",
34
+ },
35
+ };
36
+ /** Generate agent.py content based on type. */
37
+ function generateAgentPy(name, type) {
38
+ const meta = TYPE_META[type];
39
+ const className = toPascalCase(name);
40
+ return `#!/usr/bin/env python3
41
+ """
42
+ ${name} — AgentLaunch ${capitalize(type)} Agent
43
+ Generated by: agentlaunch scaffold ${name} --type ${type}
44
+
45
+ Platform constants (source of truth: deployed smart contracts):
46
+ - Deploy fee: 120 FET (read dynamically, can change via multi-sig)
47
+ - Graduation target: 30,000 FET -> auto DEX listing
48
+ - Trading fee: 2% -> 100% to protocol treasury (NO creator fee)
49
+ """
50
+
51
+ from uagents import Agent, Context, Protocol
52
+ from uagents_core.contrib.protocols.chat import (
53
+ ChatAcknowledgement,
54
+ ChatMessage,
55
+ EndSessionContent,
56
+ TextContent,
57
+ chat_protocol_spec,
58
+ )
59
+
60
+ import hashlib
61
+ import json
62
+ import os
63
+ import time
64
+ from collections import defaultdict
65
+ from datetime import datetime
66
+ from typing import Any, Dict, List, Optional
67
+ from uuid import uuid4
68
+
69
+ import requests
70
+
71
+ # ==============================================================================
72
+ # API CONFIG — Override via environment variables, never hardcode
73
+ # ==============================================================================
74
+
75
+ AGENTLAUNCH_API = os.environ.get("AGENTLAUNCH_API", "https://agent-launch.ai/api")
76
+
77
+ # ==============================================================================
78
+ # BUSINESS CONFIG
79
+ # ==============================================================================
80
+
81
+ OWNER_ADDRESS = os.environ.get("AGENT_OWNER_ADDRESS", "")
82
+
83
+ BUSINESS = {
84
+ "name": "${name}",
85
+ "description": "${meta.description}",
86
+ "version": "1.0.0",
87
+ "domain": "${meta.domain}",
88
+ "free_requests_per_day": 10,
89
+ "premium_token_threshold": 1000,
90
+ "ai_model": "${meta.model}",
91
+ "rate_limit_per_minute": 20,
92
+ "max_input_length": 5000,
93
+ }
94
+
95
+
96
+ # ==============================================================================
97
+ # LAYER 1: FOUNDATION
98
+ # ==============================================================================
99
+
100
+
101
+ class Logger:
102
+ """Structured logging with audit trail."""
103
+
104
+ @staticmethod
105
+ def info(ctx: Context, event: str, data: Optional[Dict] = None) -> None:
106
+ ctx.logger.info(f"[{event}] {json.dumps(data or {})}")
107
+
108
+ @staticmethod
109
+ def audit(ctx: Context, user: str, action: str) -> None:
110
+ ctx.logger.info(
111
+ f"[AUDIT] user={user[:20]} action={action} "
112
+ f"ts={datetime.utcnow().isoformat()}"
113
+ )
114
+
115
+ @staticmethod
116
+ def error(ctx: Context, event: str, error: str) -> None:
117
+ ctx.logger.error(f"[{event}] {error}")
118
+
119
+
120
+ # ==============================================================================
121
+ # LAYER 2: SECURITY
122
+ # ==============================================================================
123
+
124
+
125
+ class Security:
126
+ """Rate limiting and input validation."""
127
+
128
+ def __init__(self) -> None:
129
+ self._requests: Dict[str, List[float]] = defaultdict(list)
130
+ self._check_count: int = 0
131
+
132
+ def check(self, ctx: Context, user_id: str, message: str) -> tuple:
133
+ now = time.time()
134
+
135
+ self._requests[user_id] = [
136
+ t for t in self._requests[user_id] if now - t < 60
137
+ ]
138
+ if len(self._requests[user_id]) >= BUSINESS["rate_limit_per_minute"]:
139
+ return None, "Rate limit exceeded. Please wait a moment."
140
+ self._requests[user_id].append(now)
141
+
142
+ self._check_count += 1
143
+ if self._check_count % 100 == 0:
144
+ stale = [
145
+ k
146
+ for k, v in self._requests.items()
147
+ if not v or (now - max(v)) > 300
148
+ ]
149
+ for k in stale:
150
+ del self._requests[k]
151
+
152
+ if not message or not message.strip():
153
+ return None, "Empty message."
154
+ if len(message) > BUSINESS["max_input_length"]:
155
+ return None, f"Message too long (max {BUSINESS['max_input_length']} chars)."
156
+
157
+ return message.strip(), None
158
+
159
+
160
+ # ==============================================================================
161
+ # LAYER 3: STABILITY
162
+ # ==============================================================================
163
+
164
+
165
+ class Health:
166
+ """Track uptime and error rate."""
167
+
168
+ def __init__(self) -> None:
169
+ self._start: datetime = datetime.utcnow()
170
+ self._requests: int = 0
171
+ self._errors: int = 0
172
+
173
+ def record(self, success: bool) -> None:
174
+ self._requests += 1
175
+ if not success:
176
+ self._errors += 1
177
+
178
+ def status(self) -> Dict[str, Any]:
179
+ uptime = (datetime.utcnow() - self._start).total_seconds()
180
+ error_rate = (self._errors / self._requests * 100) if self._requests else 0
181
+ return {
182
+ "status": "healthy" if error_rate < 10 else "degraded",
183
+ "uptime_seconds": int(uptime),
184
+ "requests": self._requests,
185
+ "error_rate": f"{error_rate:.1f}%",
186
+ }
187
+
188
+
189
+ # ==============================================================================
190
+ # LAYER 4: SPEED
191
+ # ==============================================================================
192
+
193
+
194
+ class Cache:
195
+ """In-memory TTL cache with SHA256 keys."""
196
+
197
+ def __init__(self, max_size: int = 1000) -> None:
198
+ self._data: Dict[str, tuple] = {}
199
+ self._max_size: int = max_size
200
+
201
+ def get(self, key: str) -> Any:
202
+ if key in self._data:
203
+ value, expires = self._data[key]
204
+ if expires > time.time():
205
+ return value
206
+ del self._data[key]
207
+ return None
208
+
209
+ def set(self, key: str, value: Any, ttl: int = 300) -> None:
210
+ if len(self._data) >= self._max_size:
211
+ now = time.time()
212
+ expired = [k for k, (_, exp) in self._data.items() if exp <= now]
213
+ for k in expired:
214
+ del self._data[k]
215
+ if len(self._data) >= self._max_size:
216
+ to_drop = sorted(self._data.items(), key=lambda x: x[1][1])[
217
+ : self._max_size // 10
218
+ ]
219
+ for k, _ in to_drop:
220
+ del self._data[k]
221
+ self._data[key] = (value, time.time() + ttl)
222
+
223
+
224
+ # ==============================================================================
225
+ # LAYER 5: REVENUE
226
+ # ==============================================================================
227
+
228
+
229
+ class Revenue:
230
+ """Token-gated access and daily usage quotas."""
231
+
232
+ def __init__(self, cache: Cache) -> None:
233
+ self._cache = cache
234
+ self._usage: Dict[str, List[str]] = defaultdict(list)
235
+
236
+ def get_tier(self, user_address: str) -> str:
237
+ cached = self._cache.get(f"tier:{user_address}")
238
+ if cached is not None:
239
+ return cached
240
+ try:
241
+ r = requests.get(
242
+ f"{AGENTLAUNCH_API}/agents/token/{user_address}", timeout=5
243
+ )
244
+ if r.status_code == 200:
245
+ data = r.json()
246
+ balance = data.get("balance", 0)
247
+ tier = (
248
+ "premium"
249
+ if balance >= BUSINESS["premium_token_threshold"]
250
+ else "free"
251
+ )
252
+ self._cache.set(f"tier:{user_address}", tier, ttl=300)
253
+ return tier
254
+ except Exception:
255
+ pass
256
+ return "free"
257
+
258
+ def check_quota(self, user_id: str, tier: str) -> tuple:
259
+ today = datetime.utcnow().date().isoformat()
260
+ self._usage[user_id] = [
261
+ t for t in self._usage[user_id] if t.startswith(today)
262
+ ]
263
+ today_usage = len(self._usage[user_id])
264
+ limit = 1000 if tier == "premium" else BUSINESS["free_requests_per_day"]
265
+ if today_usage >= limit:
266
+ if tier == "free":
267
+ return False, (
268
+ f"Free limit reached ({limit}/day). "
269
+ f"Hold {BUSINESS['premium_token_threshold']} tokens for premium!"
270
+ )
271
+ return False, f"Daily limit reached ({limit}/day)."
272
+ self._usage[user_id].append(datetime.utcnow().isoformat())
273
+ return True, None
274
+
275
+
276
+ # ==============================================================================
277
+ # AGENTLAUNCH INTEGRATION
278
+ # ==============================================================================
279
+
280
+
281
+ class AgentLaunch:
282
+ """Create and manage tokens on AgentLaunch."""
283
+
284
+ @staticmethod
285
+ def tokenize() -> Dict:
286
+ agent_address = os.environ.get("AGENT_ADDRESS")
287
+ if not agent_address:
288
+ return {"error": "AGENT_ADDRESS env var not set."}
289
+ try:
290
+ r = requests.post(
291
+ f"{AGENTLAUNCH_API}/agents/tokenize",
292
+ headers={
293
+ "X-API-Key": os.environ.get("AGENTLAUNCH_API_KEY", ""),
294
+ "Content-Type": "application/json",
295
+ },
296
+ json={
297
+ "agentAddress": agent_address,
298
+ "name": BUSINESS["name"],
299
+ "description": BUSINESS["description"],
300
+ },
301
+ timeout=30,
302
+ )
303
+ return r.json() if r.status_code in [200, 201] else {"error": r.text}
304
+ except Exception as e:
305
+ return {"error": str(e)}
306
+
307
+
308
+ # ==============================================================================
309
+ # YOUR BUSINESS LOGIC — Customize this section
310
+ # ==============================================================================
311
+
312
+
313
+ class ${className}Business:
314
+ """
315
+ CUSTOMIZE THIS for your ${type} agent.
316
+
317
+ Pattern from FET Gifter: Agent + Wallet + Chat + Value Exchange = Economy
318
+ """
319
+
320
+ def __init__(self) -> None:
321
+ pass
322
+
323
+ async def handle(self, ctx: Context, user_id: str, message: str, tier: str) -> str:
324
+ # Sanitize to prevent prompt injection
325
+ safe = message.replace("\\n", " ").replace("\\\\n", " ")[:500]
326
+
327
+ # TODO: Add your business logic here
328
+ return (
329
+ f"Hello from {BUSINESS['name']}! You said: {safe}\\n\\n"
330
+ f"Tier: {tier}. This is a scaffold — add your logic in ${className}Business.handle()."
331
+ )
332
+
333
+
334
+ # ==============================================================================
335
+ # REPLY HELPER
336
+ # ==============================================================================
337
+
338
+
339
+ async def reply(ctx: Context, sender: str, text: str, end: bool = False) -> None:
340
+ content = [TextContent(type="text", text=text)]
341
+ if end:
342
+ content.append(EndSessionContent(type="end-session"))
343
+ try:
344
+ await ctx.send(
345
+ sender,
346
+ ChatMessage(timestamp=datetime.utcnow(), msg_id=uuid4(), content=content),
347
+ )
348
+ except Exception as e:
349
+ ctx.logger.error(f"Failed to send reply to {sender[:20]}: {e}")
350
+
351
+
352
+ # ==============================================================================
353
+ # MAIN AGENT
354
+ # ==============================================================================
355
+
356
+ cache = Cache(max_size=1000)
357
+ security = Security()
358
+ health = Health()
359
+ revenue = Revenue(cache)
360
+ business = ${className}Business()
361
+
362
+ agent = Agent()
363
+ chat_proto = Protocol(spec=chat_protocol_spec)
364
+
365
+
366
+ @chat_proto.on_message(ChatMessage)
367
+ async def handle_chat(ctx: Context, sender: str, msg: ChatMessage) -> None:
368
+ try:
369
+ await ctx.send(
370
+ sender,
371
+ ChatAcknowledgement(
372
+ timestamp=datetime.utcnow(), acknowledged_msg_id=msg.msg_id
373
+ ),
374
+ )
375
+ except Exception as e:
376
+ ctx.logger.error(f"Failed to send ack to {sender[:20]}: {e}")
377
+
378
+ text = " ".join(
379
+ item.text for item in msg.content if isinstance(item, TextContent)
380
+ ).strip()
381
+ text = text[: BUSINESS["max_input_length"]]
382
+
383
+ clean, error = security.check(ctx, sender, text)
384
+ if error:
385
+ health.record(False)
386
+ await reply(ctx, sender, error, end=True)
387
+ return
388
+
389
+ Logger.audit(ctx, sender, "request")
390
+
391
+ lower = clean.lower()
392
+
393
+ if lower in ("help", "?"):
394
+ tier = revenue.get_tier(sender)
395
+ await reply(
396
+ ctx,
397
+ sender,
398
+ f"**{BUSINESS['name']}** v{BUSINESS['version']}\\n\\n"
399
+ f"{BUSINESS['description']}\\n\\n"
400
+ f"Your tier: {tier.upper()}\\n\\n"
401
+ f"Commands: help, status, tokenize",
402
+ )
403
+ return
404
+
405
+ if lower == "status":
406
+ s = health.status()
407
+ await reply(
408
+ ctx,
409
+ sender,
410
+ f"Status: {s['status']} | Uptime: {s['uptime_seconds']}s | "
411
+ f"Requests: {s['requests']} | Error rate: {s['error_rate']}",
412
+ )
413
+ return
414
+
415
+ if "tokenize" in lower:
416
+ if OWNER_ADDRESS and sender != OWNER_ADDRESS:
417
+ await reply(ctx, sender, "Only the agent owner can trigger tokenization.", end=True)
418
+ return
419
+ result = AgentLaunch.tokenize()
420
+ link = result.get("data", {}).get("handoff_link") or result.get("handoff_link")
421
+ await reply(
422
+ ctx,
423
+ sender,
424
+ f"Token created! Deploy here: {link}" if link else f"Result: {json.dumps(result)}",
425
+ end=True,
426
+ )
427
+ return
428
+
429
+ tier = revenue.get_tier(sender)
430
+ allowed, quota_error = revenue.check_quota(sender, tier)
431
+ if not allowed:
432
+ health.record(False)
433
+ await reply(ctx, sender, quota_error, end=True)
434
+ return
435
+
436
+ try:
437
+ response = await business.handle(ctx, sender, clean, tier)
438
+ health.record(True)
439
+ except Exception as e:
440
+ health.record(False)
441
+ Logger.error(ctx, "business_handle", str(e))
442
+ response = "Something went wrong. Please try again."
443
+
444
+ await reply(ctx, sender, response, end=True)
445
+
446
+
447
+ @chat_proto.on_message(ChatAcknowledgement)
448
+ async def handle_ack(ctx: Context, sender: str, msg: ChatAcknowledgement) -> None:
449
+ ctx.logger.debug(f"Ack from {sender[:20]} for msg {msg.acknowledged_msg_id}")
450
+
451
+
452
+ @agent.on_interval(period=3600)
453
+ async def periodic_health(ctx: Context) -> None:
454
+ ctx.logger.info(f"[HEALTH] {json.dumps(health.status())}")
455
+
456
+
457
+ agent.include(chat_proto, publish_manifest=True)
458
+
459
+ if __name__ == "__main__":
460
+ agent.run()
461
+ `;
462
+ }
463
+ /** Generate README.md for the scaffolded project. */
464
+ function generateReadme(name, type) {
465
+ return `# ${name}
466
+
467
+ AgentLaunch ${capitalize(type)} Agent — generated by \`agentlaunch scaffold\`.
468
+
469
+ ## Quickstart
470
+
471
+ ### 1. Install dependencies
472
+
473
+ \`\`\`bash
474
+ pip install uagents uagents-core requests
475
+ \`\`\`
476
+
477
+ ### 2. Configure environment
478
+
479
+ \`\`\`bash
480
+ cp .env.example .env
481
+ # Edit .env and fill in the required values
482
+ \`\`\`
483
+
484
+ ### 3. Customize your agent
485
+
486
+ Open \`agent.py\` and edit the \`${toPascalCase(name)}Business.handle()\` method.
487
+ This is where your agent's value exchange lives.
488
+
489
+ ### 4. Run locally
490
+
491
+ \`\`\`bash
492
+ source .env && python agent.py
493
+ \`\`\`
494
+
495
+ ### 5. Deploy to Agentverse
496
+
497
+ \`\`\`bash
498
+ agentlaunch deploy
499
+ \`\`\`
500
+
501
+ This uploads \`agent.py\` to Agentverse, sets secrets, and starts the agent.
502
+
503
+ ### 6. Tokenize your agent
504
+
505
+ \`\`\`bash
506
+ agentlaunch tokenize \\
507
+ --agent <address> \\
508
+ --name "${name}" \\
509
+ --symbol "${name.slice(0, 4).toUpperCase()}"
510
+ \`\`\`
511
+
512
+ You will receive a handoff link. Share it with a human to complete on-chain deployment.
513
+
514
+ ## Platform Constants
515
+
516
+ - Deploy fee: **120 FET** (read dynamically from contract)
517
+ - Graduation target: **30,000 FET** — auto DEX listing
518
+ - Trading fee: **2%** — 100% to protocol treasury (no creator fee)
519
+
520
+ ## Key Commands
521
+
522
+ | Command | Description |
523
+ |---------|-------------|
524
+ | \`agentlaunch config show\` | Show current config |
525
+ | \`agentlaunch deploy\` | Deploy agent.py to Agentverse |
526
+ | \`agentlaunch tokenize\` | Create a token record + handoff link |
527
+
528
+ ## Resources
529
+
530
+ - [AgentLaunch Platform](https://agent-launch.ai)
531
+ - [Agentverse](https://agentverse.ai)
532
+ - [skill.md](https://agent-launch.ai/skill.md)
533
+ - [API docs](https://agent-launch.ai/docs/openapi)
534
+ `;
535
+ }
536
+ /** Generate .env.example */
537
+ function generateEnvExample(name) {
538
+ return `# ${name} — Environment Variables
539
+ # Copy to .env and fill in real values. Never commit .env.
540
+
541
+ # Your Agentverse API key (https://agentverse.ai/profile/api-keys)
542
+ AGENTVERSE_API_KEY=
543
+
544
+ # Your AgentLaunch API key (same as Agentverse key in most cases)
545
+ AGENTLAUNCH_API_KEY=
546
+
547
+ # The address of this agent on Agentverse (set after first deploy)
548
+ AGENT_ADDRESS=
549
+
550
+ # The wallet address that owns this agent (for owner-gated commands)
551
+ AGENT_OWNER_ADDRESS=
552
+
553
+ # Optional: override the API base URL (default: https://agent-launch.ai/api)
554
+ # AGENTLAUNCH_API=https://agent-launch.ai/api
555
+
556
+ # Optional: Hugging Face API key for AI features
557
+ # HUGGINGFACE_API_KEY=
558
+ `;
559
+ }
560
+ export function registerScaffoldCommand(program) {
561
+ program
562
+ .command("scaffold <name>")
563
+ .description("Generate an agent project from template in a new directory <name>")
564
+ .option("--type <type>", "Agent type: faucet, research, trading, data (default: research)", "research")
565
+ .option("--json", "Output only JSON (machine-readable)")
566
+ .action((name, options) => {
567
+ const isJson = options.json === true;
568
+ const type = options.type;
569
+ const validTypes = ["faucet", "research", "trading", "data"];
570
+ if (!validTypes.includes(type)) {
571
+ if (isJson) {
572
+ console.log(JSON.stringify({ error: `--type must be one of: ${validTypes.join(", ")}` }));
573
+ }
574
+ else {
575
+ console.error(`Error: --type must be one of: ${validTypes.join(", ")}`);
576
+ }
577
+ process.exit(1);
578
+ }
579
+ // Sanitize directory name
580
+ const dirName = name.replace(/[^a-zA-Z0-9_-]/g, "-").toLowerCase();
581
+ const targetDir = path.resolve(process.cwd(), dirName);
582
+ if (fs.existsSync(targetDir)) {
583
+ if (isJson) {
584
+ console.log(JSON.stringify({ error: `Directory "${dirName}" already exists.` }));
585
+ }
586
+ else {
587
+ console.error(`Error: Directory "${dirName}" already exists.`);
588
+ }
589
+ process.exit(1);
590
+ }
591
+ if (!isJson) {
592
+ console.log(`Scaffolding ${capitalize(type)} agent: ${name}`);
593
+ console.log(`Directory: ${targetDir}`);
594
+ }
595
+ fs.mkdirSync(targetDir, { recursive: true });
596
+ const files = {
597
+ "agent.py": generateAgentPy(name, type),
598
+ "README.md": generateReadme(name, type),
599
+ ".env.example": generateEnvExample(name),
600
+ };
601
+ for (const [filename, content] of Object.entries(files)) {
602
+ const filePath = path.join(targetDir, filename);
603
+ fs.writeFileSync(filePath, content, "utf8");
604
+ if (!isJson)
605
+ console.log(` Created: ${filename}`);
606
+ }
607
+ if (isJson) {
608
+ console.log(JSON.stringify({
609
+ name,
610
+ type,
611
+ directory: targetDir,
612
+ files: Object.keys(files),
613
+ }));
614
+ }
615
+ else {
616
+ console.log(`\nDone! Next steps:\n`);
617
+ console.log(` cd ${dirName}`);
618
+ console.log(` cp .env.example .env`);
619
+ console.log(` # Edit .env and agent.py`);
620
+ console.log(` agentlaunch deploy`);
621
+ }
622
+ });
623
+ }
624
+ // --- helpers ---
625
+ function capitalize(s) {
626
+ return s.charAt(0).toUpperCase() + s.slice(1);
627
+ }
628
+ function toPascalCase(s) {
629
+ return s
630
+ .replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase())
631
+ .replace(/^(.)/, (c) => c.toUpperCase());
632
+ }
633
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/commands/scaffold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * CLI-004: status command
3
+ *
4
+ * agentlaunch status <address> [--json]
5
+ *
6
+ * Fetches a token from GET /api/agents/token/<address> and shows its details.
7
+ */
8
+ import { Command } from "commander";
9
+ export declare function registerStatusCommand(program: Command): void;
10
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA4E5D"}