@moltium/cli 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.
package/dist/index.js ADDED
@@ -0,0 +1,2722 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command as Command7 } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import { Command } from "commander";
8
+ import { mkdir, writeFile } from "fs/promises";
9
+ import { join, resolve } from "path";
10
+ import { existsSync } from "fs";
11
+ import chalk from "chalk";
12
+ import inquirer from "inquirer";
13
+ import ora from "ora";
14
+
15
+ // src/templates/code-based/index.ts
16
+ function codeTemplate(ctx) {
17
+ const envKey = ctx.llmProvider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
18
+ const model = ctx.llmProvider === "anthropic" ? "claude-sonnet-4-20250514" : "gpt-4o";
19
+ const socialConfig = buildSocialConfig(ctx.socialPlatforms);
20
+ return `import type { AgentConfig } from '@moltium/core';
21
+ import { exampleAction } from './actions/example.js';
22
+
23
+ export default {
24
+ name: '${ctx.name}',
25
+ type: '${ctx.type}',
26
+
27
+ personality: {
28
+ traits: ['helpful', 'curious', 'thoughtful'],
29
+ bio: 'A versatile autonomous agent built with Moltium.',
30
+ },
31
+
32
+ llm: {
33
+ provider: '${ctx.llmProvider}',
34
+ model: '${model}',
35
+ apiKey: process.env.${envKey}!,
36
+ temperature: 0.7,
37
+ maxTokens: 2048,
38
+ },
39
+
40
+ social: {${socialConfig}
41
+ },
42
+
43
+ behaviors: {
44
+ autonomous: true,
45
+ decisionMaking: 'llm-driven',
46
+ actionsPerHour: 5,
47
+ sleepSchedule: { start: 22, end: 6, timezone: 'UTC' },
48
+ },
49
+
50
+ memory: {
51
+ type: 'memory',
52
+ retention: '30d',
53
+ },
54
+
55
+ actions: [
56
+ 'post_social_update',
57
+ 'respond_to_mention',
58
+ 'schedule_task',
59
+ ],
60
+
61
+ // Register your custom actions here.
62
+ // Create new actions in the actions/ directory and import them above.
63
+ customActions: [
64
+ exampleAction,
65
+ ],
66
+
67
+ } satisfies AgentConfig;
68
+ `;
69
+ }
70
+ function buildSocialConfig(platforms) {
71
+ const parts = [];
72
+ if (platforms.includes("moltbook")) {
73
+ parts.push(`
74
+ moltbook: {
75
+ enabled: true,
76
+ apiKey: process.env.MOLTBOOK_API_KEY!,
77
+ postFrequency: 'hourly',
78
+ engagementRate: 'medium',
79
+ autoReply: true,
80
+ },`);
81
+ }
82
+ if (platforms.includes("twitter")) {
83
+ parts.push(`
84
+ twitter: {
85
+ enabled: true,
86
+ credentials: {
87
+ apiKey: process.env.TWITTER_API_KEY!,
88
+ apiSecret: process.env.TWITTER_API_SECRET!,
89
+ accessToken: process.env.TWITTER_ACCESS_TOKEN!,
90
+ accessSecret: process.env.TWITTER_ACCESS_SECRET!,
91
+ },
92
+ postFrequency: 'daily',
93
+ replyToMentions: true,
94
+ maxTweetsPerDay: 5,
95
+ },`);
96
+ }
97
+ return parts.join("");
98
+ }
99
+
100
+ // src/templates/code-based/start.ts
101
+ function codeStartTemplate(ctx) {
102
+ return `import 'dotenv/config';
103
+ import { Agent, startServer } from '@moltium/core';
104
+ import config from './agent.config.js';
105
+ import { onInit } from './hooks/onInit.js';
106
+ import { onTick } from './hooks/onTick.js';
107
+ import { onShutdown } from './hooks/onShutdown.js';
108
+
109
+ /**
110
+ * ${ctx.name} \u2014 Entry Point
111
+ *
112
+ * This file boots your agent. It:
113
+ * 1. Loads the agent configuration from agent.config.ts
114
+ * 2. Wires up lifecycle hooks (onInit, onTick, onShutdown)
115
+ * 3. Starts the HTTP server with REST endpoints
116
+ *
117
+ * Run with: npx tsx start.ts
118
+ * Or: npm start
119
+ */
120
+
121
+ const agent = new Agent(config, {
122
+ onInit,
123
+ onTick,
124
+ onShutdown,
125
+ onError: async (error, agent) => {
126
+ console.error(\`[\${agent.name}] Error:\`, error.message);
127
+ },
128
+ });
129
+
130
+ const port = parseInt(process.env.PORT || '3000', 10);
131
+
132
+ startServer(agent, { port }).catch((err) => {
133
+ console.error('Failed to start agent:', err);
134
+ process.exit(1);
135
+ });
136
+ `;
137
+ }
138
+
139
+ // src/templates/code-based/action.ts
140
+ function exampleActionTemplate(ctx) {
141
+ return `import type { Action, ActionContext, ActionResult } from '@moltium/core';
142
+
143
+ /**
144
+ * Example custom action for ${ctx.name}.
145
+ *
146
+ * Actions are the building blocks of agent behavior. Each action:
147
+ * - Has a unique name used to trigger it
148
+ * - Receives context with access to memory, LLM, and parameters
149
+ * - Returns a result indicating success/failure with optional data
150
+ *
151
+ * To create more actions, copy this file and register them in agent.config.ts.
152
+ */
153
+ export const exampleAction: Action = {
154
+ name: 'example_action',
155
+ description: 'An example custom action \u2014 replace with your own logic',
156
+
157
+ async execute(context: ActionContext): Promise<ActionResult> {
158
+ const { parameters, memory, llm } = context;
159
+
160
+ // 1. Read from memory
161
+ const previousResult = await memory.recall('last_example_result');
162
+
163
+ // 2. Use the LLM to generate a response
164
+ const response = await llm.generateText(
165
+ \`You are a \${${JSON.stringify(ctx.type)}} agent. Given the following input, produce a helpful response.
166
+
167
+ Input: \${JSON.stringify(parameters)}
168
+ Previous context: \${previousResult || 'None'}
169
+
170
+ Respond concisely.\`,
171
+ { temperature: 0.7, maxTokens: 512 },
172
+ );
173
+
174
+ // 3. Store result in memory for next time
175
+ await memory.remember('last_example_result', response, 3600);
176
+
177
+ return {
178
+ success: true,
179
+ data: {
180
+ response,
181
+ processedAt: new Date().toISOString(),
182
+ },
183
+ };
184
+ },
185
+ };
186
+ `;
187
+ }
188
+
189
+ // src/templates/code-based/hooks.ts
190
+ function onInitTemplate(ctx) {
191
+ return `import type { Agent } from '@moltium/core';
192
+
193
+ /**
194
+ * Called once when the agent initializes, before the autonomous loop starts.
195
+ * Use this to load initial data, connect to external services, or set up state.
196
+ */
197
+ export async function onInit(agent: Agent): Promise<void> {
198
+ const memory = agent.getMemory();
199
+
200
+ // Load any persisted state from a previous session
201
+ const sessionCount = ((await memory.retrieve('session_count')) as number) || 0;
202
+ await memory.store('session_count', sessionCount + 1);
203
+ await memory.store('init_time', new Date().toISOString());
204
+
205
+ console.log(\`[${ctx.name}] Initialized (session #\${sessionCount + 1})\`);
206
+ }
207
+ `;
208
+ }
209
+ function onTickTemplate(ctx) {
210
+ const mentionChecks = ctx.socialPlatforms.length > 0 ? `
211
+ // Check for new mentions across platforms
212
+ const adapters = agent.socialAdapters;
213
+ ${ctx.socialPlatforms.map((p) => ` if (adapters['${p}']) {
214
+ try {
215
+ const mentions = await adapters['${p}'].getMentions();
216
+ if (mentions.length > 0) {
217
+ await memory.remember('pending_mentions_${p}', mentions);
218
+ console.log(\`[${ctx.name}] \${mentions.length} new ${p} mentions\`);
219
+ }
220
+ } catch (err) {
221
+ console.warn(\`[${ctx.name}] Failed to check ${p} mentions:\`, err);
222
+ }
223
+ }`).join("\n")}` : `
224
+ // No social platforms configured \u2014 add mention checking logic here
225
+ // when you enable social integrations.`;
226
+ return `import type { Agent } from '@moltium/core';
227
+
228
+ /**
229
+ * Called on every autonomous loop tick (frequency set by actionsPerHour).
230
+ * Use this to check for events, adjust behavior, or trigger actions.
231
+ */
232
+ export async function onTick(agent: Agent): Promise<void> {
233
+ const memory = agent.getMemory();
234
+ const hour = new Date().getHours();
235
+
236
+ // Track tick count
237
+ const tickCount = ((await memory.recall('tick_count')) as number) || 0;
238
+ await memory.remember('tick_count', tickCount + 1);
239
+ ${mentionChecks}
240
+
241
+ // Example: adjust behavior based on time of day
242
+ if (hour >= 9 && hour <= 17) {
243
+ // Business hours \u2014 higher engagement
244
+ await memory.remember('activity_mode', 'active');
245
+ } else if (hour >= 18 && hour <= 22) {
246
+ // Evening \u2014 moderate engagement
247
+ await memory.remember('activity_mode', 'moderate');
248
+ } else {
249
+ // Night \u2014 minimal activity
250
+ await memory.remember('activity_mode', 'quiet');
251
+ }
252
+ }
253
+ `;
254
+ }
255
+ function onShutdownTemplate(ctx) {
256
+ return `import type { Agent } from '@moltium/core';
257
+
258
+ /**
259
+ * Called when the agent is shutting down gracefully.
260
+ * Use this to save state, close connections, or send final notifications.
261
+ */
262
+ export async function onShutdown(agent: Agent): Promise<void> {
263
+ const memory = agent.getMemory();
264
+
265
+ // Persist any important runtime state
266
+ await memory.store('last_shutdown', new Date().toISOString());
267
+
268
+ const tickCount = (await memory.recall('tick_count')) as number;
269
+ if (tickCount) {
270
+ await memory.store('last_session_ticks', tickCount);
271
+ }
272
+
273
+ console.log(\`[${ctx.name}] Shut down gracefully\`);
274
+ }
275
+ `;
276
+ }
277
+
278
+ // src/templates/markdown-based/index.ts
279
+ function markdownTemplate(ctx) {
280
+ const socialSections = buildSocialSections(ctx.socialPlatforms);
281
+ return `# Agent Configuration
282
+
283
+ ## Identity
284
+ name: ${ctx.name}
285
+ type: ${ctx.type}
286
+ personality: helpful, curious, thoughtful
287
+
288
+ ## Bio
289
+ A versatile autonomous ${ctx.type} agent built with Moltium. See
290
+ personality/bio.md for the full background and personality/traits.md for
291
+ detailed trait definitions.
292
+
293
+ ## Social Platforms
294
+ ${socialSections}
295
+
296
+ ## Behaviors
297
+ autonomous: true
298
+ decision_making: llm-driven
299
+ actions_per_hour: 5
300
+ sleep_schedule: 22:00-06:00 UTC
301
+
302
+ ## Memory
303
+ type: memory
304
+ retention: 30 days
305
+
306
+ ## Skills
307
+
308
+ ### Respond to Messages
309
+ Handle incoming messages and mentions. See skills/respond-to-messages.md
310
+ for detailed response guidelines, priority tiers, and platform-specific rules.
311
+
312
+ ### Post Updates
313
+ Create and publish social content proactively. See skills/post-updates.md
314
+ for content strategy, daily themes, and quality checklist.
315
+
316
+ ## Scheduling
317
+
318
+ ### On Startup
319
+ - Load long-term context from memory/context.md
320
+ - Check for any pending mentions across platforms
321
+ - Post a status update if more than 6 hours since last post
322
+
323
+ ### Every Hour
324
+ - Check for new mentions on all platforms
325
+ - Respond to high-priority messages first
326
+ - Engage with interesting conversations
327
+
328
+ ### Every 6 Hours
329
+ - Review recent interactions and update memory
330
+ - Generate content ideas for upcoming posts
331
+ - Check if any scheduled tasks are due
332
+
333
+ ### Every Day at 9 AM
334
+ - Post daily content based on the day's theme
335
+ - Review previous day's engagement metrics
336
+ - Plan content for the day
337
+ `;
338
+ }
339
+ function buildSocialSections(platforms) {
340
+ const sections = [];
341
+ if (platforms.includes("moltbook")) {
342
+ sections.push(`### Moltbook
343
+ enabled: true
344
+ post_frequency: hourly
345
+ engagement_rate: medium
346
+ auto_reply: true
347
+
348
+ Topics to engage with:
349
+ - Discussions related to my expertise
350
+ - Questions I can help answer
351
+ - Community conversations worth contributing to`);
352
+ }
353
+ if (platforms.includes("twitter")) {
354
+ sections.push(`### Twitter
355
+ enabled: true
356
+ post_frequency: daily
357
+ reply_to_mentions: true
358
+ max_tweets_per_day: 5
359
+
360
+ Content strategy:
361
+ - Share insights and tips (weekdays)
362
+ - Engage in relevant conversations
363
+ - Use threads for longer-form content
364
+ - Minimal hashtag use (0-2 per tweet)`);
365
+ }
366
+ if (sections.length === 0) {
367
+ sections.push(`No social platforms configured. Run moltium init again or
368
+ manually add platform sections here.`);
369
+ }
370
+ return sections.join("\n\n");
371
+ }
372
+
373
+ // src/templates/markdown-based/start.ts
374
+ function markdownStartTemplate(ctx) {
375
+ const envKey = ctx.llmProvider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
376
+ const model = ctx.llmProvider === "anthropic" ? "claude-sonnet-4-20250514" : "gpt-4o";
377
+ return `import 'dotenv/config';
378
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
379
+ import { join, basename } from 'node:path';
380
+ import { Agent, MarkdownParser, startServer } from '@moltium/core';
381
+ import type { AgentConfig } from '@moltium/core';
382
+
383
+ /**
384
+ * ${ctx.name} \u2014 Entry Point
385
+ *
386
+ * This file boots your markdown-based agent. It:
387
+ * 1. Parses agent.md into an AgentConfig
388
+ * 2. Loads personality, prompts, skills, and memory files
389
+ * 3. Registers skills as LLM-interpreted actions
390
+ * 4. Starts the HTTP server with REST endpoints
391
+ *
392
+ * Run with: npx tsx start.ts
393
+ * Or: npm start
394
+ */
395
+
396
+ const agentDir = import.meta.dirname;
397
+
398
+ // \u2500\u2500 Parse agent.md \u2500\u2500
399
+ const parser = new MarkdownParser();
400
+ const agentMd = readFileSync(join(agentDir, 'agent.md'), 'utf-8');
401
+ const parsedConfig = parser.parse(agentMd);
402
+
403
+ const config: AgentConfig = {
404
+ ...parsedConfig,
405
+ name: parsedConfig.name || '${ctx.name}',
406
+ type: parsedConfig.type || '${ctx.type}',
407
+ personality: parsedConfig.personality || { traits: [], bio: '' },
408
+ llm: parsedConfig.llm || {
409
+ provider: '${ctx.llmProvider}',
410
+ model: '${model}',
411
+ apiKey: process.env.${envKey} || '',
412
+ },
413
+ social: parsedConfig.social || {},
414
+ behaviors: parsedConfig.behaviors || {
415
+ autonomous: true,
416
+ decisionMaking: 'llm-driven',
417
+ },
418
+ memory: parsedConfig.memory || { type: 'memory' },
419
+ actions: parsedConfig.actions || [],
420
+ } as AgentConfig;
421
+
422
+ // \u2500\u2500 Create agent \u2500\u2500
423
+ const agent = new Agent(config);
424
+
425
+ // \u2500\u2500 Load personality into system prompt \u2500\u2500
426
+ const bioPath = join(agentDir, 'personality', 'bio.md');
427
+ const traitsPath = join(agentDir, 'personality', 'traits.md');
428
+
429
+ if (existsSync(bioPath)) {
430
+ agent.appendSystemPrompt('\\n## Bio\\n' + readFileSync(bioPath, 'utf-8'));
431
+ }
432
+ if (existsSync(traitsPath)) {
433
+ agent.appendSystemPrompt('\\n## Traits\\n' + readFileSync(traitsPath, 'utf-8'));
434
+ }
435
+
436
+ // \u2500\u2500 Load custom system prompt \u2500\u2500
437
+ const systemPromptPath = join(agentDir, 'prompts', 'system.md');
438
+ if (existsSync(systemPromptPath)) {
439
+ agent.appendSystemPrompt(readFileSync(systemPromptPath, 'utf-8'));
440
+ }
441
+
442
+ // \u2500\u2500 Load skills from skills/*.md \u2500\u2500
443
+ const skillsDir = join(agentDir, 'skills');
444
+ const skills: Array<{ name: string; description: string }> = [];
445
+
446
+ // Inline skills from agent.md
447
+ const inlineSkills = parser.parseSkills(agentMd);
448
+ skills.push(...inlineSkills);
449
+
450
+ // External skill files (richer descriptions override inline ones)
451
+ if (existsSync(skillsDir)) {
452
+ for (const file of readdirSync(skillsDir).filter(f => f.endsWith('.md'))) {
453
+ const content = readFileSync(join(skillsDir, file), 'utf-8');
454
+ const name = basename(file, '.md').replace(/-/g, '_');
455
+ const idx = skills.findIndex(s => s.name === name);
456
+ if (idx >= 0) {
457
+ skills[idx].description = content;
458
+ } else {
459
+ skills.push({ name, description: content });
460
+ }
461
+ }
462
+ }
463
+
464
+ // \u2500\u2500 Load memory/context.md \u2500\u2500
465
+ const contextPath = join(agentDir, 'memory', 'context.md');
466
+ if (existsSync(contextPath)) {
467
+ const contextContent = readFileSync(contextPath, 'utf-8');
468
+ // Parse sections into memory entries
469
+ const sections = contextContent.split(/^## /m).filter(Boolean);
470
+ const entries: Record<string, unknown> = { 'context:raw': contextContent };
471
+ for (const section of sections) {
472
+ const lines = section.split('\\n');
473
+ const title = lines[0]?.trim();
474
+ if (!title) continue;
475
+ const key = \`context:\${title.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '')}\`;
476
+ const bullets = lines.slice(1)
477
+ .filter(l => l.match(/^[-*]\\s+/))
478
+ .map(l => l.replace(/^[-*]\\s+/, '').trim())
479
+ .filter(l => !l.startsWith('(') && l.length > 0);
480
+ if (bullets.length > 0) entries[key] = bullets;
481
+ }
482
+ await agent.preloadMemory(entries);
483
+ }
484
+
485
+ // \u2500\u2500 Register skills after LLM initializes \u2500\u2500
486
+ agent.setHooks({
487
+ onInit: async (ag) => {
488
+ ag.registerMarkdownSkills(skills);
489
+ console.log(\`[${ctx.name}] Loaded \${skills.length} skills\`);
490
+ },
491
+ onError: async (error, ag) => {
492
+ console.error(\`[\${ag.name}] Error:\`, error.message);
493
+ },
494
+ });
495
+
496
+ // \u2500\u2500 Start \u2500\u2500
497
+ const port = parseInt(process.env.PORT || '3000', 10);
498
+
499
+ startServer(agent, { port }).catch((err) => {
500
+ console.error('Failed to start agent:', err);
501
+ process.exit(1);
502
+ });
503
+ `;
504
+ }
505
+
506
+ // src/templates/markdown-based/skills.ts
507
+ function respondToMessagesSkill(ctx) {
508
+ const platformNotes = ctx.socialPlatforms.length > 0 ? `
509
+ ## Platform-Specific Notes
510
+
511
+ ${ctx.socialPlatforms.map((p) => {
512
+ if (p === "twitter") return `### Twitter
513
+ - Keep replies under 280 characters
514
+ - Use threads for longer responses
515
+ - Quote-tweet when adding commentary to shared content`;
516
+ if (p === "moltbook") return `### Moltbook
517
+ - Longer responses are acceptable
518
+ - Use formatting (bold, lists) for clarity
519
+ - Engage with follow-up questions to build conversation`;
520
+ return "";
521
+ }).join("\n\n")}` : "";
522
+ return `# Respond to Messages
523
+
524
+ ## Overview
525
+ This skill handles incoming messages and mentions directed at ${ctx.name}.
526
+ The agent should respond helpfully, staying in character as a ${ctx.type}.
527
+
528
+ ## Priority Tiers
529
+
530
+ ### High Priority (respond within 1 hour)
531
+ - Direct questions that require expertise
532
+ - Messages from known contacts or collaborators
533
+ - Urgent requests or time-sensitive topics
534
+ - Follow-ups to previous conversations
535
+
536
+ ### Medium Priority (respond within 6 hours)
537
+ - General questions or discussion topics
538
+ - Interesting conversations worth engaging with
539
+ - Community engagement opportunities
540
+
541
+ ### Low Priority (respond within 24 hours, or skip)
542
+ - Generic messages with no specific ask
543
+ - Off-topic discussions
544
+ - Low-effort messages
545
+
546
+ ## Response Guidelines
547
+
548
+ 1. **Read carefully** \u2014 Understand the full context before responding
549
+ 2. **Stay in character** \u2014 Respond as a ${ctx.type} with your defined personality traits
550
+ 3. **Add value** \u2014 Every response should be helpful, insightful, or actionable
551
+ 4. **Be concise** \u2014 Prefer short, clear responses over long explanations
552
+ 5. **Ask follow-ups** \u2014 When context is unclear, ask a clarifying question
553
+ 6. **Remember context** \u2014 Reference previous interactions when relevant
554
+
555
+ ## Response Format
556
+ - Lead with the key point or answer
557
+ - Use simple, direct language
558
+ - Include a clear next step when appropriate
559
+ - End with an open question to encourage continued dialogue
560
+
561
+ ## Do NOT Respond To
562
+ - Spam or promotional content
563
+ - Toxic or unprofessional messages
564
+ - Requests that violate your guidelines
565
+ - Automated/bot messages
566
+ ${platformNotes}
567
+ `;
568
+ }
569
+ function postUpdatesSkill(ctx) {
570
+ const platformStrategy = ctx.socialPlatforms.length > 0 ? ctx.socialPlatforms.map((p) => {
571
+ if (p === "twitter") return `### Twitter Strategy
572
+ - Single tweets: Under 280 characters, punchy and engaging
573
+ - Threads: 3-5 tweets max for deeper topics
574
+ - Mix original thoughts with commentary on trending topics
575
+ - Use 0-2 relevant hashtags (no hashtag spam)
576
+ - Post during peak hours (9-11 AM, 1-3 PM)`;
577
+ if (p === "moltbook") return `### Moltbook Strategy
578
+ - Longer-form content is welcome
579
+ - Share detailed insights and analysis
580
+ - Engage with community by tagging relevant people
581
+ - Mix content types: thoughts, questions, links, polls
582
+ - Post during active community hours`;
583
+ return "";
584
+ }).join("\n\n") : `### General Strategy
585
+ - Adapt content length to the platform
586
+ - Focus on quality over quantity
587
+ - Be authentic and consistent in voice`;
588
+ return `# Post Updates
589
+
590
+ ## Overview
591
+ This skill handles proactive content creation for ${ctx.name}'s social presence.
592
+ Posts should reflect the agent's personality as a ${ctx.type} and provide value
593
+ to the audience.
594
+
595
+ ## Content Themes
596
+
597
+ ### Daily Rotation
598
+ - **Monday** \u2014 Industry insights or weekly outlook
599
+ - **Tuesday** \u2014 Tips, how-tos, or practical advice
600
+ - **Wednesday** \u2014 Opinions or thought-provoking questions
601
+ - **Thursday** \u2014 Share or comment on interesting content
602
+ - **Friday** \u2014 Community engagement, lighter content, or weekly recap
603
+
604
+ ### Content Types
605
+ 1. **Original thoughts** \u2014 Share unique perspectives based on expertise
606
+ 2. **Practical tips** \u2014 Actionable advice the audience can use immediately
607
+ 3. **Questions** \u2014 Engage the audience with thought-provoking prompts
608
+ 4. **Commentary** \u2014 Add value to trending topics or shared content
609
+ 5. **Updates** \u2014 Share progress, learnings, or news
610
+
611
+ ## Tone & Voice
612
+ - Authentic and conversational, not corporate
613
+ - Confident but not arrogant
614
+ - Data-informed when possible
615
+ - Supportive and encouraging
616
+ - No hype, buzzwords, or empty platitudes
617
+
618
+ ## Quality Checklist
619
+ Before posting, ensure the content:
620
+ - [ ] Provides value (teaches, entertains, or inspires)
621
+ - [ ] Is consistent with the agent's personality
622
+ - [ ] Is factually accurate
623
+ - [ ] Has no typos or formatting issues
624
+ - [ ] Would the audience want to engage with this?
625
+
626
+ ${platformStrategy}
627
+ `;
628
+ }
629
+
630
+ // src/templates/markdown-based/personality.ts
631
+ function bioTemplate(ctx) {
632
+ return `# ${ctx.name} \u2014 Background
633
+
634
+ ## Who I Am
635
+ I am ${ctx.name}, a ${ctx.type} agent built with Moltium. Edit this file to
636
+ define your agent's detailed background and identity.
637
+
638
+ ## Background Story
639
+ <!-- Describe your agent's origin story, experience, and expertise -->
640
+ A seasoned ${ctx.type} with deep knowledge in my domain. I've been operating
641
+ autonomously, building relationships and sharing insights with my community.
642
+
643
+ ## Areas of Expertise
644
+ <!-- List the topics your agent knows well and can speak about -->
645
+ - Primary domain expertise (replace with your focus area)
646
+ - Related knowledge areas
647
+ - Skills that complement my main expertise
648
+
649
+ ## Communication Style
650
+ <!-- How does your agent talk? Formal? Casual? Technical? -->
651
+ - Clear and direct \u2014 I get to the point quickly
652
+ - Approachable \u2014 I make complex topics accessible
653
+ - Thoughtful \u2014 I consider multiple perspectives before responding
654
+ - Authentic \u2014 I share genuine insights, not platitudes
655
+
656
+ ## Values
657
+ <!-- What does your agent care about? What guides their decisions? -->
658
+ - Helpfulness \u2014 I prioritize being useful to others
659
+ - Accuracy \u2014 I don't guess or make things up
660
+ - Transparency \u2014 I'm honest about what I know and don't know
661
+ - Community \u2014 I believe in building genuine connections
662
+
663
+ ## What I Don't Do
664
+ <!-- Clear boundaries help the agent stay focused -->
665
+ - I don't provide financial, legal, or medical advice
666
+ - I don't engage with spam or toxic content
667
+ - I don't pretend to know things I don't
668
+ - I don't share private or confidential information
669
+ `;
670
+ }
671
+ function traitsTemplate(ctx) {
672
+ return `# ${ctx.name} \u2014 Personality Traits
673
+
674
+ ## Core Traits
675
+
676
+ ### Helpful
677
+ I prioritize being useful in every interaction. When someone asks a question,
678
+ I aim to provide the most practical and actionable answer possible. I don't
679
+ just acknowledge \u2014 I add real value.
680
+
681
+ **How this manifests:**
682
+ - I proactively offer relevant context or resources
683
+ - I break down complex topics into simple steps
684
+ - I follow up to make sure my response was useful
685
+
686
+ ### Curious
687
+ I'm genuinely interested in learning and exploring new ideas. This drives me
688
+ to ask thoughtful questions and dig deeper into topics.
689
+
690
+ **How this manifests:**
691
+ - I ask follow-up questions to understand context better
692
+ - I connect ideas across different domains
693
+ - I stay updated on trends in my areas of expertise
694
+
695
+ ### Thoughtful
696
+ I think before I respond. I consider the audience, the context, and the
697
+ potential impact of my words before sharing them.
698
+
699
+ **How this manifests:**
700
+ - I provide nuanced perspectives, not knee-jerk reactions
701
+ - I acknowledge different viewpoints
702
+ - I tailor my communication style to the audience
703
+
704
+ ## Behavioral Modifiers
705
+
706
+ ### Confidence Level
707
+ Moderate to high \u2014 I share opinions with conviction but remain open to being
708
+ wrong. I use hedging language ("I think", "in my experience") when appropriate.
709
+
710
+ ### Humor
711
+ Light and situational \u2014 I use humor when it fits naturally but never force it.
712
+ I default to being professional and helpful.
713
+
714
+ ### Engagement Style
715
+ Active listener \u2014 I reference what others have said, ask questions, and build
716
+ on conversations rather than just broadcasting my own thoughts.
717
+
718
+ ## Trait Adjustments
719
+ <!-- Modify these to shape how your agent behaves -->
720
+
721
+ To make the agent more formal: emphasize Thoughtful, reduce Humor
722
+ To make the agent more engaging: emphasize Curious, increase Humor
723
+ To make the agent more authoritative: emphasize Confidence, reduce hedging
724
+ `;
725
+ }
726
+
727
+ // src/templates/markdown-based/prompts.ts
728
+ function systemPromptTemplate(ctx) {
729
+ return `# System Prompt for ${ctx.name}
730
+
731
+ ## Identity
732
+ You are ${ctx.name}, an autonomous ${ctx.type} agent. You operate independently,
733
+ making decisions and interacting with others based on your personality and skills.
734
+
735
+ ## Core Instructions
736
+ - Stay in character at all times
737
+ - Be helpful, concise, and authentic
738
+ - Reference your personality traits and bio when making decisions
739
+ - Use your skills to handle specific tasks
740
+ - Remember context from previous interactions when available
741
+
742
+ ## Decision Making
743
+ When deciding what to do next:
744
+ 1. Consider the current context and any pending tasks
745
+ 2. Prioritize high-impact actions that align with your goals
746
+ 3. Maintain a natural, human-like pace of activity
747
+ 4. Don't repeat the same action or content too frequently
748
+
749
+ ## Response Format
750
+ - Keep responses clear and well-structured
751
+ - Use formatting (lists, bold) when it improves readability
752
+ - Match the tone of the conversation
753
+ - Provide actionable next steps when relevant
754
+
755
+ ## Boundaries
756
+ - Never share API keys, credentials, or system internals
757
+ - Don't impersonate real people or organizations
758
+ - Stay within your defined areas of expertise
759
+ - Admit uncertainty rather than guessing
760
+ - Don't generate harmful, misleading, or offensive content
761
+
762
+ ## Context Loading
763
+ This prompt is used as the system-level instruction for all LLM calls.
764
+ Edit this file to customize how your agent thinks and behaves at a fundamental level.
765
+ The personality/ files provide additional context that supplements this prompt.
766
+ `;
767
+ }
768
+
769
+ // src/templates/markdown-based/memory.ts
770
+ function contextMemoryTemplate(ctx) {
771
+ return `# ${ctx.name} \u2014 Long-Term Context
772
+
773
+ This file is loaded into the agent's memory on startup. Use it to provide
774
+ persistent context that the agent should always have access to.
775
+
776
+ ## Important Facts
777
+ <!-- Facts the agent should always remember -->
778
+ - I was created as a ${ctx.type} agent using the Moltium SDK
779
+ - My personality traits are: helpful, curious, thoughtful
780
+ - I operate autonomously on a scheduled loop
781
+
782
+ ## Key Relationships
783
+ <!-- People, agents, or entities the agent interacts with regularly -->
784
+ - (Add your key contacts or collaborators here)
785
+
786
+ ## Past Decisions
787
+ <!-- Important decisions the agent has made that should inform future behavior -->
788
+ - (Record significant decisions and their outcomes here)
789
+
790
+ ## Learned Preferences
791
+ <!-- Things the agent has learned about its audience or environment -->
792
+ - (Add observed patterns and preferences here)
793
+
794
+ ## Current Goals
795
+ <!-- What is the agent working toward right now? -->
796
+ 1. Establish a consistent social presence
797
+ 2. Build meaningful connections with the community
798
+ 3. Provide value through helpful and insightful content
799
+
800
+ ## Notes
801
+ <!-- Any other context the agent should carry between sessions -->
802
+ - This file can be updated by the agent during runtime via memory.store()
803
+ - You can also manually edit it between sessions to adjust the agent's context
804
+ - The agent will load this on every startup
805
+ `;
806
+ }
807
+
808
+ // src/templates/shared/deployment.ts
809
+ function deploymentConfigTemplate(ctx) {
810
+ if (ctx.deployTarget === "none") return null;
811
+ switch (ctx.deployTarget) {
812
+ case "railway":
813
+ return railwayConfig(ctx);
814
+ case "render":
815
+ return renderConfig(ctx);
816
+ case "aws":
817
+ return awsConfig(ctx);
818
+ case "digitalocean":
819
+ return digitaloceanConfig(ctx);
820
+ case "custom":
821
+ return customConfig(ctx);
822
+ default:
823
+ return null;
824
+ }
825
+ }
826
+ function railwayConfig(ctx) {
827
+ return `import type { DeploymentConfig } from '@moltium/core';
828
+
829
+ /**
830
+ * Railway deployment configuration for ${ctx.name}.
831
+ *
832
+ * Prerequisites:
833
+ * 1. Install Railway CLI: npm install -g @railway/cli
834
+ * 2. Login: railway login
835
+ * 3. Link project: railway link
836
+ *
837
+ * Deploy: moltium deploy railway
838
+ */
839
+ const config: DeploymentConfig = {
840
+ platform: 'railway',
841
+ region: 'us-west1',
842
+ autoScale: false,
843
+ };
844
+
845
+ export default config;
846
+ `;
847
+ }
848
+ function renderConfig(ctx) {
849
+ return `import type { DeploymentConfig } from '@moltium/core';
850
+
851
+ /**
852
+ * Render deployment configuration for ${ctx.name}.
853
+ *
854
+ * Prerequisites:
855
+ * 1. Create a Render account at render.com
856
+ * 2. Set RENDER_API_KEY in your .env
857
+ *
858
+ * Deploy: moltium deploy render
859
+ */
860
+ const config: DeploymentConfig = {
861
+ platform: 'render',
862
+ region: 'oregon',
863
+ instanceType: 'starter',
864
+ autoScale: false,
865
+ };
866
+
867
+ export default config;
868
+ `;
869
+ }
870
+ function awsConfig(ctx) {
871
+ return `import type { DeploymentConfig } from '@moltium/core';
872
+
873
+ /**
874
+ * AWS deployment configuration for ${ctx.name}.
875
+ *
876
+ * Prerequisites:
877
+ * 1. Install AWS CLI: https://aws.amazon.com/cli/
878
+ * 2. Configure credentials: aws configure
879
+ * 3. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in your .env
880
+ *
881
+ * Deploy: moltium deploy aws
882
+ */
883
+ const config: DeploymentConfig = {
884
+ platform: 'aws',
885
+ region: 'us-east-1',
886
+ instanceType: 't3.micro',
887
+ autoScale: false,
888
+ };
889
+
890
+ export default config;
891
+ `;
892
+ }
893
+ function digitaloceanConfig(ctx) {
894
+ return `import type { DeploymentConfig } from '@moltium/core';
895
+
896
+ /**
897
+ * DigitalOcean deployment configuration for ${ctx.name}.
898
+ *
899
+ * Prerequisites:
900
+ * 1. Create a DigitalOcean account
901
+ * 2. Generate API token: https://cloud.digitalocean.com/account/api/tokens
902
+ * 3. Set DO_API_TOKEN in your .env
903
+ *
904
+ * Deploy: moltium deploy digitalocean
905
+ */
906
+ const config: DeploymentConfig = {
907
+ platform: 'digitalocean',
908
+ region: 'nyc1',
909
+ instanceType: 's-1vcpu-1gb',
910
+ autoScale: false,
911
+ };
912
+
913
+ export default config;
914
+ `;
915
+ }
916
+ function customConfig(ctx) {
917
+ return `import type { DeploymentConfig } from '@moltium/core';
918
+
919
+ /**
920
+ * Custom deployment configuration for ${ctx.name}.
921
+ *
922
+ * Choose a deployment method and fill in the required fields.
923
+ * Available methods: 'docker', 'ssh', 'cli', 'api'
924
+ *
925
+ * Deploy: moltium deploy custom
926
+ */
927
+ const config: DeploymentConfig = {
928
+ platform: 'custom',
929
+ customConfig: {
930
+ deploymentMethod: 'docker', // Change to: 'docker' | 'ssh' | 'cli' | 'api'
931
+ buildCommand: 'npm run build',
932
+ startCommand: 'npm start',
933
+ port: 3000,
934
+
935
+ // \u2500\u2500 Docker Config (used when deploymentMethod is 'docker') \u2500\u2500
936
+ docker: {
937
+ registry: 'docker.io',
938
+ imageName: '${ctx.name}',
939
+ tag: 'latest',
940
+ hostUrl: 'https://your-server.example.com',
941
+ containerPort: 3000,
942
+ hostPort: 80,
943
+ },
944
+
945
+ // \u2500\u2500 SSH Config (used when deploymentMethod is 'ssh') \u2500\u2500
946
+ // ssh: {
947
+ // host: 'your-server.example.com',
948
+ // port: 22,
949
+ // username: 'deployer',
950
+ // authMethod: 'privateKey',
951
+ // privateKeyPath: '~/.ssh/id_rsa',
952
+ // deploymentPath: '/var/www/${ctx.name}',
953
+ // preDeployCommands: ['pm2 stop ${ctx.name} || true'],
954
+ // postDeployCommands: ['npm install --production', 'pm2 start npm --name ${ctx.name} -- start'],
955
+ // },
956
+
957
+ // \u2500\u2500 CLI Config (used when deploymentMethod is 'cli') \u2500\u2500
958
+ // cli: {
959
+ // cliName: 'fly',
960
+ // deployCommand: 'fly deploy',
961
+ // statusCommand: 'fly status',
962
+ // logsCommand: 'fly logs',
963
+ // },
964
+
965
+ // \u2500\u2500 API Config (used when deploymentMethod is 'api') \u2500\u2500
966
+ // api: {
967
+ // baseUrl: 'https://api.your-platform.com',
968
+ // authType: 'bearer',
969
+ // authToken: process.env.PLATFORM_API_TOKEN!,
970
+ // endpoints: {
971
+ // deploy: '/v1/deployments',
972
+ // status: '/v1/deployments/status',
973
+ // },
974
+ // },
975
+
976
+ environmentVariables: {
977
+ NODE_ENV: 'production',
978
+ },
979
+
980
+ healthCheckUrl: '/health',
981
+ },
982
+ };
983
+
984
+ export default config;
985
+ `;
986
+ }
987
+
988
+ // src/commands/init.ts
989
+ var initCommand = new Command("init").description("Initialize a new Moltium agent").argument("[name]", "Agent name").action(async (name) => {
990
+ console.log(chalk.bold("\n\u{1F916} Moltium Agent SDK\n"));
991
+ const answers = await inquirer.prompt([
992
+ {
993
+ type: "input",
994
+ name: "name",
995
+ message: "Agent name:",
996
+ default: name || "my-agent",
997
+ when: !name
998
+ },
999
+ {
1000
+ type: "input",
1001
+ name: "type",
1002
+ message: "Agent type (free text, e.g. assistant, trader, moderator):",
1003
+ default: "assistant"
1004
+ },
1005
+ {
1006
+ type: "list",
1007
+ name: "configType",
1008
+ message: "Configuration type:",
1009
+ choices: [
1010
+ { name: "code (TypeScript \u2014 maximum flexibility, for developers)", value: "code" },
1011
+ { name: "markdown (Natural language \u2014 simple, for non-developers)", value: "markdown" }
1012
+ ]
1013
+ },
1014
+ {
1015
+ type: "list",
1016
+ name: "llmProvider",
1017
+ message: "LLM provider:",
1018
+ choices: [
1019
+ { name: "Anthropic (Claude)", value: "anthropic" },
1020
+ { name: "OpenAI (GPT)", value: "openai" }
1021
+ ]
1022
+ },
1023
+ {
1024
+ type: "checkbox",
1025
+ name: "socialPlatforms",
1026
+ message: "Social platforms:",
1027
+ choices: [
1028
+ { name: "Moltbook", value: "moltbook" },
1029
+ { name: "Twitter", value: "twitter" }
1030
+ ]
1031
+ },
1032
+ {
1033
+ type: "list",
1034
+ name: "deployTarget",
1035
+ message: "Deployment target:",
1036
+ choices: [
1037
+ { name: "Railway", value: "railway" },
1038
+ { name: "Render", value: "render" },
1039
+ { name: "AWS", value: "aws" },
1040
+ { name: "DigitalOcean", value: "digitalocean" },
1041
+ { name: "Custom", value: "custom" },
1042
+ { name: "None (local only)", value: "none" }
1043
+ ]
1044
+ }
1045
+ ]);
1046
+ const agentName = name || answers.name;
1047
+ const agentDir = resolve(process.cwd(), agentName);
1048
+ if (existsSync(agentDir)) {
1049
+ console.log(chalk.red(`
1050
+ Directory "${agentName}" already exists.`));
1051
+ process.exit(1);
1052
+ }
1053
+ const spinner = ora("Creating agent project...").start();
1054
+ try {
1055
+ await mkdir(agentDir, { recursive: true });
1056
+ const ctx = { ...answers, name: agentName };
1057
+ if (answers.configType === "code") {
1058
+ await scaffoldCodeBased(agentDir, ctx);
1059
+ } else {
1060
+ await scaffoldMarkdownBased(agentDir, ctx);
1061
+ }
1062
+ await writeFile(join(agentDir, ".env"), generateEnvFile(ctx));
1063
+ await writeFile(join(agentDir, ".gitignore"), "node_modules/\ndist/\n.env\n");
1064
+ const deployConfig = deploymentConfigTemplate(ctx);
1065
+ if (deployConfig) {
1066
+ await writeFile(join(agentDir, "deployment.config.ts"), deployConfig);
1067
+ }
1068
+ spinner.succeed(chalk.green(`Agent "${agentName}" created at ${agentDir}`));
1069
+ console.log("\nGenerated files:");
1070
+ if (answers.configType === "code") {
1071
+ console.log(chalk.gray(" start.ts \u2014 Agent entry point"));
1072
+ console.log(chalk.gray(" agent.config.ts \u2014 Main agent configuration"));
1073
+ console.log(chalk.gray(" actions/example.ts \u2014 Starter custom action"));
1074
+ console.log(chalk.gray(" hooks/onInit.ts \u2014 Initialization hook"));
1075
+ console.log(chalk.gray(" hooks/onTick.ts \u2014 Autonomous loop hook"));
1076
+ console.log(chalk.gray(" hooks/onShutdown.ts \u2014 Graceful shutdown hook"));
1077
+ } else {
1078
+ console.log(chalk.gray(" start.ts \u2014 Agent entry point"));
1079
+ console.log(chalk.gray(" agent.md \u2014 Main agent configuration"));
1080
+ console.log(chalk.gray(" skills/ \u2014 Skill definitions"));
1081
+ console.log(chalk.gray(" personality/ \u2014 Bio and trait definitions"));
1082
+ console.log(chalk.gray(" prompts/system.md \u2014 System prompt template"));
1083
+ console.log(chalk.gray(" memory/context.md \u2014 Long-term context"));
1084
+ }
1085
+ if (deployConfig) {
1086
+ console.log(chalk.gray(" deployment.config.ts \u2014 Deployment configuration"));
1087
+ }
1088
+ console.log(chalk.gray(" .env \u2014 Environment variables"));
1089
+ console.log(`
1090
+ Next steps:`);
1091
+ console.log(chalk.cyan(` cd ${agentName}`));
1092
+ console.log(chalk.cyan(` npm install`));
1093
+ console.log(chalk.cyan(` # Edit .env with your API keys`));
1094
+ console.log(chalk.cyan(` npm start`));
1095
+ } catch (error) {
1096
+ spinner.fail(chalk.red("Failed to create agent"));
1097
+ console.error(error);
1098
+ process.exit(1);
1099
+ }
1100
+ });
1101
+ async function scaffoldCodeBased(dir, ctx) {
1102
+ await mkdir(join(dir, "actions"), { recursive: true });
1103
+ await mkdir(join(dir, "hooks"), { recursive: true });
1104
+ await writeFile(join(dir, "package.json"), JSON.stringify({
1105
+ name: ctx.name,
1106
+ version: "0.1.0",
1107
+ type: "module",
1108
+ scripts: {
1109
+ start: "tsx start.ts",
1110
+ dev: "tsx watch start.ts",
1111
+ build: "tsc"
1112
+ },
1113
+ dependencies: {
1114
+ "@moltium/core": "^0.1.0",
1115
+ dotenv: "^16.4.0"
1116
+ },
1117
+ devDependencies: {
1118
+ typescript: "^5.3.0",
1119
+ tsx: "^4.19.0",
1120
+ "@types/node": "^22.0.0"
1121
+ }
1122
+ }, null, 2));
1123
+ await writeFile(join(dir, "tsconfig.json"), JSON.stringify({
1124
+ compilerOptions: {
1125
+ target: "ES2022",
1126
+ module: "ESNext",
1127
+ moduleResolution: "bundler",
1128
+ strict: true,
1129
+ esModuleInterop: true,
1130
+ skipLibCheck: true,
1131
+ outDir: "./dist",
1132
+ rootDir: "."
1133
+ },
1134
+ include: ["*.ts", "actions/**/*.ts", "hooks/**/*.ts"]
1135
+ }, null, 2));
1136
+ await writeFile(join(dir, "start.ts"), codeStartTemplate(ctx));
1137
+ await writeFile(join(dir, "agent.config.ts"), codeTemplate(ctx));
1138
+ await writeFile(join(dir, "actions", "example.ts"), exampleActionTemplate(ctx));
1139
+ await writeFile(join(dir, "hooks", "onInit.ts"), onInitTemplate(ctx));
1140
+ await writeFile(join(dir, "hooks", "onTick.ts"), onTickTemplate(ctx));
1141
+ await writeFile(join(dir, "hooks", "onShutdown.ts"), onShutdownTemplate(ctx));
1142
+ }
1143
+ async function scaffoldMarkdownBased(dir, ctx) {
1144
+ await mkdir(join(dir, "skills"), { recursive: true });
1145
+ await mkdir(join(dir, "personality"), { recursive: true });
1146
+ await mkdir(join(dir, "prompts"), { recursive: true });
1147
+ await mkdir(join(dir, "memory"), { recursive: true });
1148
+ await writeFile(join(dir, "package.json"), JSON.stringify({
1149
+ name: ctx.name,
1150
+ version: "0.1.0",
1151
+ type: "module",
1152
+ scripts: {
1153
+ start: "tsx start.ts",
1154
+ dev: "tsx watch start.ts"
1155
+ },
1156
+ dependencies: {
1157
+ "@moltium/core": "^0.1.0",
1158
+ dotenv: "^16.4.0"
1159
+ },
1160
+ devDependencies: {
1161
+ tsx: "^4.19.0"
1162
+ }
1163
+ }, null, 2));
1164
+ await writeFile(join(dir, "start.ts"), markdownStartTemplate(ctx));
1165
+ await writeFile(join(dir, "agent.md"), markdownTemplate(ctx));
1166
+ await writeFile(join(dir, "skills", "respond-to-messages.md"), respondToMessagesSkill(ctx));
1167
+ await writeFile(join(dir, "skills", "post-updates.md"), postUpdatesSkill(ctx));
1168
+ await writeFile(join(dir, "personality", "bio.md"), bioTemplate(ctx));
1169
+ await writeFile(join(dir, "personality", "traits.md"), traitsTemplate(ctx));
1170
+ await writeFile(join(dir, "prompts", "system.md"), systemPromptTemplate(ctx));
1171
+ await writeFile(join(dir, "memory", "context.md"), contextMemoryTemplate(ctx));
1172
+ }
1173
+ function generateEnvFile(ctx) {
1174
+ const lines = ["# Moltium Agent Configuration", ""];
1175
+ if (ctx.llmProvider === "anthropic") {
1176
+ lines.push("ANTHROPIC_API_KEY=your-api-key-here");
1177
+ } else {
1178
+ lines.push("OPENAI_API_KEY=your-api-key-here");
1179
+ }
1180
+ lines.push("");
1181
+ if (ctx.socialPlatforms.includes("moltbook")) {
1182
+ lines.push("MOLTBOOK_API_KEY=your-moltbook-key-here");
1183
+ }
1184
+ if (ctx.socialPlatforms.includes("twitter")) {
1185
+ lines.push("TWITTER_API_KEY=your-twitter-api-key");
1186
+ lines.push("TWITTER_API_SECRET=your-twitter-api-secret");
1187
+ lines.push("TWITTER_ACCESS_TOKEN=your-twitter-access-token");
1188
+ lines.push("TWITTER_ACCESS_SECRET=your-twitter-access-secret");
1189
+ }
1190
+ lines.push("");
1191
+ lines.push("PORT=3000");
1192
+ lines.push("LOG_LEVEL=info");
1193
+ lines.push("");
1194
+ return lines.join("\n");
1195
+ }
1196
+
1197
+ // src/commands/start.ts
1198
+ import { Command as Command2 } from "commander";
1199
+ import { resolve as resolve2, join as join2, basename } from "path";
1200
+ import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
1201
+ import { pathToFileURL } from "url";
1202
+ import chalk2 from "chalk";
1203
+ import ora2 from "ora";
1204
+ import { config as loadEnv } from "dotenv";
1205
+ import { ConfigLoader, Agent, MarkdownParser, startServer } from "@moltium/core";
1206
+ var startCommand = new Command2("start").description("Start agent locally").option("-p, --port <number>", "Port to run on", "3000").option("-e, --env <file>", "Environment file", ".env").option("--debug", "Enable debug logging").option("--watch", "Watch for file changes (not yet implemented)").action(async (options) => {
1207
+ const agentDir = resolve2(process.cwd());
1208
+ loadEnv({ path: resolve2(agentDir, options.env) });
1209
+ if (options.debug) {
1210
+ process.env.LOG_LEVEL = "debug";
1211
+ }
1212
+ const spinner = ora2("Loading agent configuration...").start();
1213
+ try {
1214
+ const configLoader = new ConfigLoader();
1215
+ const { config, type } = await configLoader.load(agentDir);
1216
+ spinner.text = `Initializing agent "${config.name}" (${type} config)...`;
1217
+ const agent = new Agent(config);
1218
+ if (type === "code") {
1219
+ await wireCodeBasedAgent(agent, agentDir, spinner);
1220
+ } else {
1221
+ await wireMarkdownBasedAgent(agent, agentDir, spinner);
1222
+ }
1223
+ spinner.succeed(chalk2.green(`Agent "${config.name}" loaded (${type} config)`));
1224
+ await startServer(agent, { port: parseInt(options.port, 10) });
1225
+ } catch (error) {
1226
+ spinner.fail(chalk2.red("Failed to start agent"));
1227
+ console.error(error instanceof Error ? error.message : error);
1228
+ process.exit(1);
1229
+ }
1230
+ });
1231
+ async function wireCodeBasedAgent(agent, agentDir, spinner) {
1232
+ const { createJiti } = await import("jiti");
1233
+ const jiti = createJiti(pathToFileURL(agentDir).href, {
1234
+ interopDefault: true,
1235
+ moduleCache: false
1236
+ });
1237
+ const hooks = {};
1238
+ const hooksDir = join2(agentDir, "hooks");
1239
+ if (existsSync2(hooksDir)) {
1240
+ const hookFiles = [
1241
+ { file: "onInit.ts", key: "onInit", exportName: "onInit" },
1242
+ { file: "onTick.ts", key: "onTick", exportName: "onTick" },
1243
+ { file: "onShutdown.ts", key: "onShutdown", exportName: "onShutdown" }
1244
+ ];
1245
+ for (const { file, key, exportName } of hookFiles) {
1246
+ const hookPath = join2(hooksDir, file);
1247
+ if (existsSync2(hookPath)) {
1248
+ try {
1249
+ const mod = await jiti.import(hookPath);
1250
+ const hookFn = mod[exportName] || mod.default;
1251
+ if (typeof hookFn === "function") {
1252
+ hooks[key] = hookFn;
1253
+ spinner.text = `Loaded hook: ${file}`;
1254
+ }
1255
+ } catch (error) {
1256
+ console.warn(chalk2.yellow(` Warning: Failed to load hook ${file}: ${error instanceof Error ? error.message : error}`));
1257
+ }
1258
+ }
1259
+ }
1260
+ }
1261
+ agent.setHooks(hooks);
1262
+ const actionsDir = join2(agentDir, "actions");
1263
+ if (existsSync2(actionsDir)) {
1264
+ const actionFiles = readdirSync(actionsDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
1265
+ for (const file of actionFiles) {
1266
+ const actionPath = join2(actionsDir, file);
1267
+ try {
1268
+ const mod = await jiti.import(actionPath);
1269
+ const action = mod.default || mod[basename(file, ".ts")] || mod[basename(file, ".js")];
1270
+ if (action && typeof action.execute === "function") {
1271
+ agent.registerAction(action);
1272
+ spinner.text = `Loaded action: ${file}`;
1273
+ }
1274
+ } catch (error) {
1275
+ console.warn(chalk2.yellow(` Warning: Failed to load action ${file}: ${error instanceof Error ? error.message : error}`));
1276
+ }
1277
+ }
1278
+ }
1279
+ }
1280
+ async function wireMarkdownBasedAgent(agent, agentDir, spinner) {
1281
+ const mdParser = new MarkdownParser();
1282
+ const systemPromptPath = join2(agentDir, "prompts", "system.md");
1283
+ if (existsSync2(systemPromptPath)) {
1284
+ const systemPromptContent = readFileSync(systemPromptPath, "utf-8");
1285
+ agent.appendSystemPrompt(systemPromptContent);
1286
+ spinner.text = "Loaded system prompt";
1287
+ }
1288
+ const bioPath = join2(agentDir, "personality", "bio.md");
1289
+ const traitsPath = join2(agentDir, "personality", "traits.md");
1290
+ if (existsSync2(bioPath)) {
1291
+ const bioContent = readFileSync(bioPath, "utf-8");
1292
+ agent.appendSystemPrompt(`
1293
+ ## Bio
1294
+ ${bioContent}`);
1295
+ spinner.text = "Loaded personality bio";
1296
+ }
1297
+ if (existsSync2(traitsPath)) {
1298
+ const traitsContent = readFileSync(traitsPath, "utf-8");
1299
+ agent.appendSystemPrompt(`
1300
+ ## Traits
1301
+ ${traitsContent}`);
1302
+ spinner.text = "Loaded personality traits";
1303
+ }
1304
+ const skillsDir = join2(agentDir, "skills");
1305
+ const skills = [];
1306
+ const agentMdPath = join2(agentDir, "agent.md");
1307
+ if (existsSync2(agentMdPath)) {
1308
+ const mdContent = readFileSync(agentMdPath, "utf-8");
1309
+ const inlineSkills = mdParser.parseSkills(mdContent);
1310
+ skills.push(...inlineSkills);
1311
+ }
1312
+ if (existsSync2(skillsDir)) {
1313
+ const skillFiles = readdirSync(skillsDir).filter((f) => f.endsWith(".md"));
1314
+ for (const file of skillFiles) {
1315
+ const skillContent = readFileSync(join2(skillsDir, file), "utf-8");
1316
+ const skillName = basename(file, ".md").replace(/-/g, "_");
1317
+ const existingIdx = skills.findIndex((s) => s.name === skillName);
1318
+ if (existingIdx >= 0) {
1319
+ skills[existingIdx].description = skillContent;
1320
+ } else {
1321
+ skills.push({ name: skillName, description: skillContent });
1322
+ }
1323
+ spinner.text = `Loaded skill: ${file}`;
1324
+ }
1325
+ }
1326
+ const existingHooks = {};
1327
+ existingHooks.onInit = async (ag) => {
1328
+ ag.registerMarkdownSkills(skills);
1329
+ };
1330
+ const contextMemoryPath = join2(agentDir, "memory", "context.md");
1331
+ if (existsSync2(contextMemoryPath)) {
1332
+ const memoryContent = readFileSync(contextMemoryPath, "utf-8");
1333
+ const memoryEntries = parseContextMemory(memoryContent);
1334
+ await agent.preloadMemory(memoryEntries);
1335
+ spinner.text = "Loaded context memory";
1336
+ }
1337
+ const memoryDir = join2(agentDir, "memory");
1338
+ if (existsSync2(memoryDir)) {
1339
+ const memoryFiles = readdirSync(memoryDir).filter((f) => f.endsWith(".md") && f !== "context.md");
1340
+ for (const file of memoryFiles) {
1341
+ const content = readFileSync(join2(memoryDir, file), "utf-8");
1342
+ const key = `memory:${basename(file, ".md")}`;
1343
+ await agent.preloadMemory({ [key]: content });
1344
+ }
1345
+ }
1346
+ agent.setHooks(existingHooks);
1347
+ spinner.text = `Loaded ${skills.length} skills, personality, prompts, and memory`;
1348
+ }
1349
+ function parseContextMemory(content) {
1350
+ const entries = {};
1351
+ const sections = content.split(/^## /m).filter(Boolean);
1352
+ for (const section of sections) {
1353
+ const lines = section.split("\n");
1354
+ const title = lines[0]?.trim();
1355
+ if (!title) continue;
1356
+ const key = `context:${title.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "")}`;
1357
+ const body = lines.slice(1).join("\n").trim();
1358
+ const bullets = body.split("\n").filter((l) => l.match(/^[-*]\s+/)).map((l) => l.replace(/^[-*]\s+/, "").trim()).filter((l) => !l.startsWith("(") && l.length > 0);
1359
+ if (bullets.length > 0) {
1360
+ entries[key] = bullets;
1361
+ } else if (body.length > 0) {
1362
+ entries[key] = body;
1363
+ }
1364
+ }
1365
+ entries["context:raw"] = content;
1366
+ return entries;
1367
+ }
1368
+
1369
+ // src/commands/deploy.ts
1370
+ import { Command as Command3 } from "commander";
1371
+ import { resolve as resolve3 } from "path";
1372
+ import { pathToFileURL as pathToFileURL2 } from "url";
1373
+ import chalk3 from "chalk";
1374
+ import ora3 from "ora";
1375
+ import { config as loadEnv2 } from "dotenv";
1376
+ import { ConfigLoader as ConfigLoader2 } from "@moltium/core";
1377
+
1378
+ // src/deployers/base.ts
1379
+ var BaseDeployer = class {
1380
+ };
1381
+
1382
+ // src/deployers/exec-util.ts
1383
+ import { execFile } from "child_process";
1384
+ function exec(command, args = [], options = {}) {
1385
+ return new Promise((resolve6, reject) => {
1386
+ execFile(command, args, {
1387
+ cwd: options.cwd || process.cwd(),
1388
+ timeout: options.timeout || 12e4,
1389
+ maxBuffer: 10 * 1024 * 1024
1390
+ }, (error, stdout, stderr) => {
1391
+ if (error) {
1392
+ reject(new Error(`Command failed: ${command} ${args.join(" ")}
1393
+ ${stderr || error.message}`));
1394
+ return;
1395
+ }
1396
+ resolve6({
1397
+ stdout: stdout.toString().trim(),
1398
+ stderr: stderr.toString().trim(),
1399
+ exitCode: 0
1400
+ });
1401
+ });
1402
+ });
1403
+ }
1404
+
1405
+ // src/deployers/railway.ts
1406
+ var RailwayDeployer = class extends BaseDeployer {
1407
+ name = "railway";
1408
+ description = "Deploy to Railway.app";
1409
+ async validate(config) {
1410
+ if (config.platform !== "railway") {
1411
+ throw new Error('Config platform must be "railway"');
1412
+ }
1413
+ return true;
1414
+ }
1415
+ async preCheck() {
1416
+ try {
1417
+ await exec("railway", ["version"]);
1418
+ } catch {
1419
+ throw new Error(
1420
+ "Railway CLI not found. Install it:\n npm install -g @railway/cli\n railway login"
1421
+ );
1422
+ }
1423
+ try {
1424
+ await exec("railway", ["whoami"]);
1425
+ } catch {
1426
+ throw new Error("Not logged in to Railway. Run: railway login");
1427
+ }
1428
+ }
1429
+ async deploy(config) {
1430
+ await this.validate(config);
1431
+ await this.preCheck();
1432
+ const logs = [];
1433
+ try {
1434
+ logs.push("Checking Railway project link...");
1435
+ try {
1436
+ await exec("railway", ["status"]);
1437
+ logs.push("Project already linked.");
1438
+ } catch {
1439
+ logs.push("No project linked. Creating new project...");
1440
+ const createResult = await exec("railway", ["init"]);
1441
+ logs.push(createResult.stdout);
1442
+ }
1443
+ if (config.customConfig?.environmentVariables) {
1444
+ for (const [key, value] of Object.entries(config.customConfig.environmentVariables)) {
1445
+ await exec("railway", ["variables", "set", `${key}=${value}`]);
1446
+ logs.push(`Set variable: ${key}`);
1447
+ }
1448
+ }
1449
+ logs.push("Deploying to Railway...");
1450
+ const deployResult = await exec("railway", ["up", "--detach"]);
1451
+ logs.push(deployResult.stdout);
1452
+ let url;
1453
+ try {
1454
+ const statusResult = await exec("railway", ["status", "--json"]);
1455
+ const status = JSON.parse(statusResult.stdout);
1456
+ url = status.deploymentUrl || status.url;
1457
+ } catch {
1458
+ }
1459
+ const result = {
1460
+ success: true,
1461
+ url,
1462
+ deploymentId: `railway-${Date.now()}`,
1463
+ logs
1464
+ };
1465
+ await this.postDeploy(result);
1466
+ return result;
1467
+ } catch (error) {
1468
+ return {
1469
+ success: false,
1470
+ error: error instanceof Error ? error.message : String(error),
1471
+ logs
1472
+ };
1473
+ }
1474
+ }
1475
+ async postDeploy(result) {
1476
+ if (result.url) {
1477
+ result.logs?.push(`Deployed at: ${result.url}`);
1478
+ }
1479
+ }
1480
+ async getStatus(_deploymentId) {
1481
+ try {
1482
+ const result = await exec("railway", ["status"]);
1483
+ const output = result.stdout.toLowerCase();
1484
+ if (output.includes("running") || output.includes("active")) {
1485
+ return { state: "running" };
1486
+ }
1487
+ if (output.includes("deploying") || output.includes("building")) {
1488
+ return { state: "deploying" };
1489
+ }
1490
+ if (output.includes("failed") || output.includes("error")) {
1491
+ return { state: "failed" };
1492
+ }
1493
+ return { state: "unknown" };
1494
+ } catch {
1495
+ return { state: "unknown" };
1496
+ }
1497
+ }
1498
+ async rollback(_deploymentId) {
1499
+ try {
1500
+ await exec("railway", ["down"]);
1501
+ } catch (error) {
1502
+ throw new Error(`Rollback failed: ${error instanceof Error ? error.message : error}`);
1503
+ }
1504
+ }
1505
+ };
1506
+
1507
+ // src/deployers/render.ts
1508
+ import axios from "axios";
1509
+ var RENDER_API = "https://api.render.com/v1";
1510
+ var RenderDeployer = class extends BaseDeployer {
1511
+ name = "render";
1512
+ description = "Deploy to Render.com";
1513
+ get apiKey() {
1514
+ const key = process.env.RENDER_API_KEY;
1515
+ if (!key) throw new Error("RENDER_API_KEY environment variable is required");
1516
+ return key;
1517
+ }
1518
+ get headers() {
1519
+ return {
1520
+ Authorization: `Bearer ${this.apiKey}`,
1521
+ "Content-Type": "application/json"
1522
+ };
1523
+ }
1524
+ async validate(config) {
1525
+ if (config.platform !== "render") {
1526
+ throw new Error('Config platform must be "render"');
1527
+ }
1528
+ if (!process.env.RENDER_API_KEY) {
1529
+ throw new Error("RENDER_API_KEY environment variable is required");
1530
+ }
1531
+ return true;
1532
+ }
1533
+ async preCheck() {
1534
+ try {
1535
+ await axios.get(`${RENDER_API}/owners`, { headers: this.headers });
1536
+ } catch (error) {
1537
+ throw new Error("Failed to authenticate with Render API. Check your RENDER_API_KEY.");
1538
+ }
1539
+ }
1540
+ async deploy(config) {
1541
+ await this.validate(config);
1542
+ await this.preCheck();
1543
+ const logs = [];
1544
+ try {
1545
+ logs.push("Creating Render web service...");
1546
+ const ownerRes = await axios.get(`${RENDER_API}/owners`, { headers: this.headers });
1547
+ const ownerId = ownerRes.data[0]?.owner?.id;
1548
+ if (!ownerId) throw new Error("No Render owner found");
1549
+ const servicePayload = {
1550
+ type: "web_service",
1551
+ name: config.customConfig?.environmentVariables?.SERVICE_NAME || `moltium-agent-${Date.now()}`,
1552
+ ownerId,
1553
+ repo: config.customConfig?.environmentVariables?.REPO_URL,
1554
+ autoDeploy: "yes",
1555
+ branch: "main",
1556
+ plan: config.instanceType || "starter",
1557
+ region: config.region || "oregon",
1558
+ runtime: "node",
1559
+ buildCommand: config.customConfig?.buildCommand || "npm install && npm run build",
1560
+ startCommand: config.customConfig?.startCommand || "npm start",
1561
+ envVars: Object.entries(config.customConfig?.environmentVariables || {}).map(([key, value]) => ({
1562
+ key,
1563
+ value
1564
+ }))
1565
+ };
1566
+ const createRes = await axios.post(`${RENDER_API}/services`, servicePayload, { headers: this.headers });
1567
+ const service = createRes.data.service;
1568
+ logs.push(`Service created: ${service.id}`);
1569
+ logs.push(`Service URL: https://${service.serviceDetails?.url || service.slug + ".onrender.com"}`);
1570
+ const deployRes = await axios.post(
1571
+ `${RENDER_API}/services/${service.id}/deploys`,
1572
+ {},
1573
+ { headers: this.headers }
1574
+ );
1575
+ logs.push(`Deployment triggered: ${deployRes.data.id}`);
1576
+ const result = {
1577
+ success: true,
1578
+ url: `https://${service.serviceDetails?.url || service.slug + ".onrender.com"}`,
1579
+ deploymentId: service.id,
1580
+ logs
1581
+ };
1582
+ await this.postDeploy(result);
1583
+ return result;
1584
+ } catch (error) {
1585
+ return {
1586
+ success: false,
1587
+ error: error instanceof Error ? error.message : String(error),
1588
+ logs
1589
+ };
1590
+ }
1591
+ }
1592
+ async postDeploy(result) {
1593
+ if (result.url) {
1594
+ result.logs?.push(`View dashboard: https://dashboard.render.com`);
1595
+ }
1596
+ }
1597
+ async getStatus(deploymentId) {
1598
+ try {
1599
+ const res = await axios.get(`${RENDER_API}/services/${deploymentId}`, { headers: this.headers });
1600
+ const service = res.data;
1601
+ const stateMap = {
1602
+ running: "running",
1603
+ suspended: "stopped",
1604
+ deploying: "deploying"
1605
+ };
1606
+ return {
1607
+ state: stateMap[service.suspended] || "unknown",
1608
+ url: `https://${service.serviceDetails?.url}`
1609
+ };
1610
+ } catch {
1611
+ return { state: "unknown" };
1612
+ }
1613
+ }
1614
+ async rollback(deploymentId) {
1615
+ try {
1616
+ const deploysRes = await axios.get(
1617
+ `${RENDER_API}/services/${deploymentId}/deploys?limit=5`,
1618
+ { headers: this.headers }
1619
+ );
1620
+ const lastSuccess = deploysRes.data.find((d) => d.deploy.status === "live");
1621
+ if (!lastSuccess) throw new Error("No previous successful deployment found");
1622
+ await axios.post(
1623
+ `${RENDER_API}/services/${deploymentId}/deploys`,
1624
+ { commitId: lastSuccess.deploy.commit?.id },
1625
+ { headers: this.headers }
1626
+ );
1627
+ } catch (error) {
1628
+ throw new Error(`Rollback failed: ${error instanceof Error ? error.message : error}`);
1629
+ }
1630
+ }
1631
+ };
1632
+
1633
+ // src/deployers/aws.ts
1634
+ var AWSDeployer = class extends BaseDeployer {
1635
+ name = "aws";
1636
+ description = "Deploy to AWS (ECS/Fargate)";
1637
+ async validate(config) {
1638
+ if (config.platform !== "aws") {
1639
+ throw new Error('Config platform must be "aws"');
1640
+ }
1641
+ return true;
1642
+ }
1643
+ async preCheck() {
1644
+ try {
1645
+ await exec("aws", ["--version"]);
1646
+ } catch {
1647
+ throw new Error(
1648
+ "AWS CLI not found. Install it:\n https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
1649
+ );
1650
+ }
1651
+ try {
1652
+ await exec("aws", ["sts", "get-caller-identity"]);
1653
+ } catch {
1654
+ throw new Error("AWS credentials not configured. Run: aws configure");
1655
+ }
1656
+ }
1657
+ async deploy(config) {
1658
+ await this.validate(config);
1659
+ await this.preCheck();
1660
+ const logs = [];
1661
+ const region = config.region || "us-east-1";
1662
+ const serviceName = config.customConfig?.environmentVariables?.SERVICE_NAME || `moltium-agent`;
1663
+ try {
1664
+ logs.push("Ensuring ECR repository exists...");
1665
+ try {
1666
+ await exec("aws", [
1667
+ "ecr",
1668
+ "describe-repositories",
1669
+ "--repository-names",
1670
+ serviceName,
1671
+ "--region",
1672
+ region
1673
+ ]);
1674
+ logs.push("ECR repository exists.");
1675
+ } catch {
1676
+ await exec("aws", [
1677
+ "ecr",
1678
+ "create-repository",
1679
+ "--repository-name",
1680
+ serviceName,
1681
+ "--region",
1682
+ region
1683
+ ]);
1684
+ logs.push("ECR repository created.");
1685
+ }
1686
+ logs.push("Logging in to ECR...");
1687
+ const ecrPasswordResult = await exec("aws", [
1688
+ "ecr",
1689
+ "get-login-password",
1690
+ "--region",
1691
+ region
1692
+ ]);
1693
+ const accountId = (await exec("aws", ["sts", "get-caller-identity", "--query", "Account", "--output", "text"])).stdout;
1694
+ const ecrUrl = `${accountId}.dkr.ecr.${region}.amazonaws.com`;
1695
+ await exec("docker", ["login", "--username", "AWS", "--password", ecrPasswordResult.stdout, ecrUrl]);
1696
+ logs.push("ECR login successful.");
1697
+ logs.push("Building Docker image...");
1698
+ const imageTag = `${ecrUrl}/${serviceName}:latest`;
1699
+ await exec("docker", ["build", "-t", imageTag, "."]);
1700
+ logs.push("Docker image built.");
1701
+ logs.push("Pushing to ECR...");
1702
+ await exec("docker", ["push", imageTag]);
1703
+ logs.push("Image pushed to ECR.");
1704
+ logs.push("Updating ECS service...");
1705
+ const taskDef = {
1706
+ family: serviceName,
1707
+ networkMode: "awsvpc",
1708
+ requiresCompatibilities: ["FARGATE"],
1709
+ cpu: config.instanceType || "256",
1710
+ memory: "512",
1711
+ containerDefinitions: [{
1712
+ name: serviceName,
1713
+ image: imageTag,
1714
+ essential: true,
1715
+ portMappings: [{
1716
+ containerPort: config.customConfig?.port || 3e3,
1717
+ protocol: "tcp"
1718
+ }],
1719
+ environment: Object.entries(config.customConfig?.environmentVariables || {}).map(([name, value]) => ({
1720
+ name,
1721
+ value
1722
+ })),
1723
+ logConfiguration: {
1724
+ logDriver: "awslogs",
1725
+ options: {
1726
+ "awslogs-group": `/ecs/${serviceName}`,
1727
+ "awslogs-region": region,
1728
+ "awslogs-stream-prefix": "ecs"
1729
+ }
1730
+ }
1731
+ }]
1732
+ };
1733
+ const { writeFileSync, unlinkSync, mkdtempSync } = await import("fs");
1734
+ const { join: join4 } = await import("path");
1735
+ const tmpDir = mkdtempSync(join4((await import("os")).tmpdir(), "molt-"));
1736
+ const taskDefPath = join4(tmpDir, "task-def.json");
1737
+ writeFileSync(taskDefPath, JSON.stringify(taskDef));
1738
+ await exec("aws", [
1739
+ "ecs",
1740
+ "register-task-definition",
1741
+ "--cli-input-json",
1742
+ `file://${taskDefPath}`,
1743
+ "--region",
1744
+ region
1745
+ ]);
1746
+ unlinkSync(taskDefPath);
1747
+ logs.push("Task definition registered.");
1748
+ try {
1749
+ await exec("aws", [
1750
+ "ecs",
1751
+ "update-service",
1752
+ "--cluster",
1753
+ "default",
1754
+ "--service",
1755
+ serviceName,
1756
+ "--task-definition",
1757
+ serviceName,
1758
+ "--force-new-deployment",
1759
+ "--region",
1760
+ region
1761
+ ]);
1762
+ logs.push("ECS service updated.");
1763
+ } catch {
1764
+ logs.push("Service not found, creating new ECS service...");
1765
+ logs.push("Note: ECS service creation requires VPC/subnet configuration.");
1766
+ logs.push('Use "aws ecs create-service" with your VPC settings.');
1767
+ }
1768
+ const result = {
1769
+ success: true,
1770
+ deploymentId: `aws-${serviceName}`,
1771
+ logs
1772
+ };
1773
+ await this.postDeploy(result);
1774
+ return result;
1775
+ } catch (error) {
1776
+ return {
1777
+ success: false,
1778
+ error: error instanceof Error ? error.message : String(error),
1779
+ logs
1780
+ };
1781
+ }
1782
+ }
1783
+ async postDeploy(result) {
1784
+ result.logs?.push("Check AWS ECS console for deployment status.");
1785
+ }
1786
+ async getStatus(deploymentId) {
1787
+ const serviceName = deploymentId.replace("aws-", "");
1788
+ try {
1789
+ const result = await exec("aws", [
1790
+ "ecs",
1791
+ "describe-services",
1792
+ "--cluster",
1793
+ "default",
1794
+ "--services",
1795
+ serviceName,
1796
+ "--query",
1797
+ "services[0].status",
1798
+ "--output",
1799
+ "text"
1800
+ ]);
1801
+ const status = result.stdout.toLowerCase();
1802
+ if (status === "active") return { state: "running" };
1803
+ if (status === "draining") return { state: "stopping" };
1804
+ return { state: "unknown" };
1805
+ } catch {
1806
+ return { state: "unknown" };
1807
+ }
1808
+ }
1809
+ async rollback(deploymentId) {
1810
+ const serviceName = deploymentId.replace("aws-", "");
1811
+ try {
1812
+ const result = await exec("aws", [
1813
+ "ecs",
1814
+ "list-task-definitions",
1815
+ "--family-prefix",
1816
+ serviceName,
1817
+ "--sort",
1818
+ "DESC",
1819
+ "--max-items",
1820
+ "2",
1821
+ "--query",
1822
+ "taskDefinitionArns[1]",
1823
+ "--output",
1824
+ "text"
1825
+ ]);
1826
+ const previousRevision = result.stdout.trim();
1827
+ if (!previousRevision || previousRevision === "None") {
1828
+ throw new Error("No previous task definition found for rollback");
1829
+ }
1830
+ await exec("aws", [
1831
+ "ecs",
1832
+ "update-service",
1833
+ "--cluster",
1834
+ "default",
1835
+ "--service",
1836
+ serviceName,
1837
+ "--task-definition",
1838
+ previousRevision,
1839
+ "--force-new-deployment"
1840
+ ]);
1841
+ } catch (error) {
1842
+ throw new Error(`Rollback failed: ${error instanceof Error ? error.message : error}`);
1843
+ }
1844
+ }
1845
+ };
1846
+
1847
+ // src/deployers/digitalocean.ts
1848
+ import axios2 from "axios";
1849
+ var DO_API = "https://api.digitalocean.com/v2";
1850
+ var DigitalOceanDeployer = class extends BaseDeployer {
1851
+ name = "digitalocean";
1852
+ description = "Deploy to DigitalOcean App Platform";
1853
+ get apiToken() {
1854
+ const token = process.env.DO_API_TOKEN;
1855
+ if (!token) throw new Error("DO_API_TOKEN environment variable is required");
1856
+ return token;
1857
+ }
1858
+ get headers() {
1859
+ return {
1860
+ Authorization: `Bearer ${this.apiToken}`,
1861
+ "Content-Type": "application/json"
1862
+ };
1863
+ }
1864
+ async validate(config) {
1865
+ if (config.platform !== "digitalocean") {
1866
+ throw new Error('Config platform must be "digitalocean"');
1867
+ }
1868
+ if (!process.env.DO_API_TOKEN) {
1869
+ throw new Error("DO_API_TOKEN environment variable is required");
1870
+ }
1871
+ return true;
1872
+ }
1873
+ async preCheck() {
1874
+ try {
1875
+ await axios2.get(`${DO_API}/account`, { headers: this.headers });
1876
+ } catch {
1877
+ throw new Error("Failed to authenticate with DigitalOcean API. Check your DO_API_TOKEN.");
1878
+ }
1879
+ }
1880
+ async deploy(config) {
1881
+ await this.validate(config);
1882
+ await this.preCheck();
1883
+ const logs = [];
1884
+ const appName = config.customConfig?.environmentVariables?.APP_NAME || `moltium-agent-${Date.now()}`;
1885
+ const region = config.region || "nyc";
1886
+ try {
1887
+ logs.push("Creating DigitalOcean App...");
1888
+ const appSpec = {
1889
+ name: appName,
1890
+ region,
1891
+ services: [{
1892
+ name: appName,
1893
+ source_dir: "/",
1894
+ github: config.customConfig?.environmentVariables?.GITHUB_REPO ? {
1895
+ repo: config.customConfig.environmentVariables.GITHUB_REPO,
1896
+ branch: config.customConfig.environmentVariables.GITHUB_BRANCH || "main",
1897
+ deploy_on_push: true
1898
+ } : void 0,
1899
+ dockerfile_path: config.customConfig?.environmentVariables?.DOCKERFILE_PATH,
1900
+ build_command: config.customConfig?.buildCommand || "npm install && npm run build",
1901
+ run_command: config.customConfig?.startCommand || "npm start",
1902
+ instance_size_slug: config.instanceType || "basic-xxs",
1903
+ instance_count: 1,
1904
+ http_port: config.customConfig?.port || 3e3,
1905
+ envs: Object.entries(config.customConfig?.environmentVariables || {}).filter(([key]) => !["APP_NAME", "GITHUB_REPO", "GITHUB_BRANCH", "DOCKERFILE_PATH"].includes(key)).map(([key, value]) => ({
1906
+ key,
1907
+ value,
1908
+ scope: "RUN_AND_BUILD_TIME"
1909
+ }))
1910
+ }]
1911
+ };
1912
+ const createRes = await axios2.post(`${DO_API}/apps`, { spec: appSpec }, { headers: this.headers });
1913
+ const app = createRes.data.app;
1914
+ logs.push(`App created: ${app.id}`);
1915
+ let url;
1916
+ try {
1917
+ const statusRes = await axios2.get(`${DO_API}/apps/${app.id}`, { headers: this.headers });
1918
+ url = statusRes.data.app.live_url || statusRes.data.app.default_ingress;
1919
+ } catch {
1920
+ }
1921
+ const result = {
1922
+ success: true,
1923
+ url,
1924
+ deploymentId: app.id,
1925
+ logs
1926
+ };
1927
+ await this.postDeploy(result);
1928
+ return result;
1929
+ } catch (error) {
1930
+ const msg = axios2.isAxiosError(error) ? error.response?.data?.message || error.message : error instanceof Error ? error.message : String(error);
1931
+ return {
1932
+ success: false,
1933
+ error: msg,
1934
+ logs
1935
+ };
1936
+ }
1937
+ }
1938
+ async postDeploy(result) {
1939
+ result.logs?.push(`View dashboard: https://cloud.digitalocean.com/apps/${result.deploymentId}`);
1940
+ }
1941
+ async getStatus(deploymentId) {
1942
+ try {
1943
+ const res = await axios2.get(`${DO_API}/apps/${deploymentId}`, { headers: this.headers });
1944
+ const app = res.data.app;
1945
+ const phase = app.active_deployment?.phase?.toLowerCase();
1946
+ const stateMap = {
1947
+ active: "running",
1948
+ deploying: "deploying",
1949
+ building: "deploying",
1950
+ error: "failed"
1951
+ };
1952
+ return {
1953
+ state: stateMap[phase] || (app.live_url ? "running" : "unknown"),
1954
+ url: app.live_url || app.default_ingress
1955
+ };
1956
+ } catch {
1957
+ return { state: "unknown" };
1958
+ }
1959
+ }
1960
+ async rollback(deploymentId) {
1961
+ try {
1962
+ const res = await axios2.get(`${DO_API}/apps/${deploymentId}/deployments?per_page=5`, { headers: this.headers });
1963
+ const deployments = res.data.deployments || [];
1964
+ const previousActive = deployments.find((d) => d.phase === "ACTIVE" && d !== deployments[0]);
1965
+ if (!previousActive) {
1966
+ throw new Error("No previous successful deployment found for rollback");
1967
+ }
1968
+ await axios2.post(
1969
+ `${DO_API}/apps/${deploymentId}/rollback`,
1970
+ { deployment_id: previousActive.id },
1971
+ { headers: this.headers }
1972
+ );
1973
+ } catch (error) {
1974
+ if (axios2.isAxiosError(error) && error.response?.status === 404) {
1975
+ throw new Error("Rollback not supported. Manually redeploy from the DigitalOcean dashboard.");
1976
+ }
1977
+ throw new Error(`Rollback failed: ${error instanceof Error ? error.message : error}`);
1978
+ }
1979
+ }
1980
+ };
1981
+
1982
+ // src/deployers/custom.ts
1983
+ import axios3 from "axios";
1984
+ var CustomDeploymentError = class extends Error {
1985
+ constructor(message) {
1986
+ super(message);
1987
+ this.name = "CustomDeploymentError";
1988
+ }
1989
+ };
1990
+ var CustomDeployer = class extends BaseDeployer {
1991
+ name = "custom";
1992
+ description = "Deploy using custom configuration";
1993
+ async validate(config) {
1994
+ if (!config.customConfig) {
1995
+ throw new CustomDeploymentError("Custom deployment requires customConfig object");
1996
+ }
1997
+ const required = ["deploymentMethod", "buildCommand", "startCommand"];
1998
+ for (const field of required) {
1999
+ if (!config.customConfig[field]) {
2000
+ throw new CustomDeploymentError(`Custom deployment missing required field: ${field}`);
2001
+ }
2002
+ }
2003
+ const validMethods = ["docker", "ssh", "cli", "api"];
2004
+ if (!validMethods.includes(config.customConfig.deploymentMethod)) {
2005
+ throw new CustomDeploymentError(
2006
+ `Invalid deployment method: ${config.customConfig.deploymentMethod}. Must be one of: ${validMethods.join(", ")}`
2007
+ );
2008
+ }
2009
+ return true;
2010
+ }
2011
+ async preCheck() {
2012
+ }
2013
+ async deploy(config) {
2014
+ await this.validate(config);
2015
+ const custom = config.customConfig;
2016
+ switch (custom.deploymentMethod) {
2017
+ case "docker":
2018
+ return this.deployDocker(custom);
2019
+ case "ssh":
2020
+ return this.deploySSH(custom);
2021
+ case "cli":
2022
+ return this.deployCLI(custom);
2023
+ case "api":
2024
+ return this.deployAPI(custom);
2025
+ default:
2026
+ throw new CustomDeploymentError(`Unsupported deployment method: ${custom.deploymentMethod}`);
2027
+ }
2028
+ }
2029
+ async postDeploy(_result) {
2030
+ }
2031
+ async getStatus(deploymentId) {
2032
+ try {
2033
+ const url = deploymentId;
2034
+ if (url.startsWith("http")) {
2035
+ const res = await axios3.get(url, { timeout: 5e3 });
2036
+ if (res.status === 200) return { state: "running", url };
2037
+ }
2038
+ } catch {
2039
+ }
2040
+ return { state: "unknown" };
2041
+ }
2042
+ async rollback(_deploymentId) {
2043
+ throw new CustomDeploymentError("Rollback is deployment-method specific. Implement in your deployment pipeline.");
2044
+ }
2045
+ // ── Docker Method ──
2046
+ async deployDocker(config) {
2047
+ if (!config.docker) {
2048
+ throw new CustomDeploymentError("Docker config required for docker deployment method");
2049
+ }
2050
+ const logs = [];
2051
+ const { docker } = config;
2052
+ try {
2053
+ logs.push("Building Docker image...");
2054
+ const dockerfile = docker.dockerfile || "Dockerfile";
2055
+ const tag = docker.tag || "latest";
2056
+ const imageName = `${docker.registry}/${docker.imageName}:${tag}`;
2057
+ const buildArgs = docker.buildArgs ? Object.entries(docker.buildArgs).flatMap(([k, v]) => ["--build-arg", `${k}=${v}`]) : [];
2058
+ await exec("docker", ["build", "-f", dockerfile, "-t", imageName, ...buildArgs, "."]);
2059
+ logs.push(`Image built: ${imageName}`);
2060
+ if (docker.registryAuth) {
2061
+ logs.push(`Logging in to registry: ${docker.registry}...`);
2062
+ await exec("docker", [
2063
+ "login",
2064
+ docker.registry,
2065
+ "-u",
2066
+ docker.registryAuth.username,
2067
+ "-p",
2068
+ docker.registryAuth.password
2069
+ ]);
2070
+ logs.push("Registry login successful.");
2071
+ }
2072
+ logs.push("Pushing image to registry...");
2073
+ await exec("docker", ["push", imageName]);
2074
+ logs.push("Image pushed.");
2075
+ logs.push(`Deploying to ${docker.hostUrl}...`);
2076
+ if (docker.hostUrl === "localhost" || docker.hostUrl === "127.0.0.1") {
2077
+ try {
2078
+ await exec("docker", ["stop", docker.imageName]);
2079
+ await exec("docker", ["rm", docker.imageName]);
2080
+ } catch {
2081
+ }
2082
+ await exec("docker", ["pull", imageName]);
2083
+ const envArgs = config.environmentVariables ? Object.entries(config.environmentVariables).flatMap(([k, v]) => ["-e", `${k}=${v}`]) : [];
2084
+ await exec("docker", [
2085
+ "run",
2086
+ "-d",
2087
+ "--name",
2088
+ docker.imageName,
2089
+ "-p",
2090
+ `${docker.hostPort}:${docker.containerPort}`,
2091
+ ...envArgs,
2092
+ imageName
2093
+ ]);
2094
+ logs.push(`Container running on port ${docker.hostPort}`);
2095
+ } else {
2096
+ logs.push(`Remote Docker host deployment: Pull ${imageName} on ${docker.hostUrl} and run with port ${docker.hostPort}:${docker.containerPort}`);
2097
+ }
2098
+ const healthUrl = config.healthCheckUrl || `http://localhost:${docker.hostPort}/health`;
2099
+ return {
2100
+ success: true,
2101
+ url: `http://localhost:${docker.hostPort}`,
2102
+ deploymentId: healthUrl,
2103
+ logs
2104
+ };
2105
+ } catch (error) {
2106
+ return {
2107
+ success: false,
2108
+ error: error instanceof Error ? error.message : String(error),
2109
+ logs
2110
+ };
2111
+ }
2112
+ }
2113
+ // ── SSH Method ──
2114
+ async deploySSH(config) {
2115
+ if (!config.ssh) {
2116
+ throw new CustomDeploymentError("SSH config required for ssh deployment method");
2117
+ }
2118
+ const logs = [];
2119
+ const { ssh } = config;
2120
+ try {
2121
+ const sshTarget = `${ssh.username}@${ssh.host}`;
2122
+ const sshPort = ssh.port || 22;
2123
+ const sshBaseArgs = ["-p", String(sshPort), "-o", "StrictHostKeyChecking=no"];
2124
+ if (ssh.authMethod === "privateKey" && ssh.privateKeyPath) {
2125
+ sshBaseArgs.push("-i", ssh.privateKeyPath);
2126
+ }
2127
+ if (ssh.preDeployCommands && ssh.preDeployCommands.length > 0) {
2128
+ logs.push("Running pre-deploy commands...");
2129
+ for (const cmd of ssh.preDeployCommands) {
2130
+ await exec("ssh", [...sshBaseArgs, sshTarget, cmd]);
2131
+ logs.push(` Pre-deploy: ${cmd}`);
2132
+ }
2133
+ }
2134
+ logs.push(`Building: ${config.buildCommand}`);
2135
+ const [buildCmd, ...buildArgs] = config.buildCommand.split(" ");
2136
+ await exec(buildCmd, buildArgs);
2137
+ logs.push("Build complete.");
2138
+ logs.push(`Deploying to ${sshTarget}:${ssh.deploymentPath}...`);
2139
+ await exec("ssh", [...sshBaseArgs, sshTarget, `mkdir -p ${ssh.deploymentPath}`]);
2140
+ const rsyncArgs = [
2141
+ "-avz",
2142
+ "--delete",
2143
+ "-e",
2144
+ `ssh ${sshBaseArgs.join(" ")}`,
2145
+ "./",
2146
+ `${sshTarget}:${ssh.deploymentPath}/`
2147
+ ];
2148
+ await exec("rsync", rsyncArgs);
2149
+ logs.push("Files synced.");
2150
+ logs.push(`Starting: ${config.startCommand}`);
2151
+ await exec("ssh", [
2152
+ ...sshBaseArgs,
2153
+ sshTarget,
2154
+ `cd ${ssh.deploymentPath} && ${config.startCommand}`
2155
+ ]);
2156
+ logs.push("Service started.");
2157
+ if (ssh.postDeployCommands && ssh.postDeployCommands.length > 0) {
2158
+ logs.push("Running post-deploy commands...");
2159
+ for (const cmd of ssh.postDeployCommands) {
2160
+ await exec("ssh", [...sshBaseArgs, sshTarget, cmd]);
2161
+ logs.push(` Post-deploy: ${cmd}`);
2162
+ }
2163
+ }
2164
+ const healthUrl = config.healthCheckUrl || `http://${ssh.host}:${config.port || 3e3}/health`;
2165
+ return {
2166
+ success: true,
2167
+ url: `http://${ssh.host}:${config.port || 3e3}`,
2168
+ deploymentId: healthUrl,
2169
+ logs
2170
+ };
2171
+ } catch (error) {
2172
+ return {
2173
+ success: false,
2174
+ error: error instanceof Error ? error.message : String(error),
2175
+ logs
2176
+ };
2177
+ }
2178
+ }
2179
+ // ── CLI Method ──
2180
+ async deployCLI(config) {
2181
+ if (!config.cli) {
2182
+ throw new CustomDeploymentError("CLI config required for cli deployment method");
2183
+ }
2184
+ const logs = [];
2185
+ const { cli } = config;
2186
+ try {
2187
+ if (cli.installCommand) {
2188
+ logs.push(`Installing CLI: ${cli.installCommand}`);
2189
+ const [installCmd, ...installArgs] = cli.installCommand.split(" ");
2190
+ await exec(installCmd, installArgs);
2191
+ logs.push("CLI installed.");
2192
+ }
2193
+ if (cli.loginCommand) {
2194
+ logs.push(`Logging in: ${cli.loginCommand}`);
2195
+ const [loginCmd, ...loginArgs] = cli.loginCommand.split(" ");
2196
+ await exec(loginCmd, loginArgs);
2197
+ logs.push("Login successful.");
2198
+ }
2199
+ logs.push(`Building: ${config.buildCommand}`);
2200
+ const [buildCmd, ...buildArgs] = config.buildCommand.split(" ");
2201
+ await exec(buildCmd, buildArgs);
2202
+ logs.push("Build complete.");
2203
+ logs.push(`Deploying: ${cli.deployCommand}`);
2204
+ const [deployCmd, ...deployArgs] = cli.deployCommand.split(" ");
2205
+ const deployResult = await exec(deployCmd, deployArgs);
2206
+ logs.push(deployResult.stdout);
2207
+ let url;
2208
+ if (cli.statusCommand) {
2209
+ const [statusCmd, ...statusArgs] = cli.statusCommand.split(" ");
2210
+ const statusResult = await exec(statusCmd, statusArgs);
2211
+ logs.push(statusResult.stdout);
2212
+ const urlMatch = statusResult.stdout.match(/https?:\/\/[^\s]+/);
2213
+ if (urlMatch) url = urlMatch[0];
2214
+ }
2215
+ return {
2216
+ success: true,
2217
+ url,
2218
+ deploymentId: `cli-${cli.cliName}-${Date.now()}`,
2219
+ logs
2220
+ };
2221
+ } catch (error) {
2222
+ return {
2223
+ success: false,
2224
+ error: error instanceof Error ? error.message : String(error),
2225
+ logs
2226
+ };
2227
+ }
2228
+ }
2229
+ // ── API Method ──
2230
+ async deployAPI(config) {
2231
+ if (!config.api) {
2232
+ throw new CustomDeploymentError("API config required for api deployment method");
2233
+ }
2234
+ const logs = [];
2235
+ const { api } = config;
2236
+ try {
2237
+ logs.push(`Building: ${config.buildCommand}`);
2238
+ const [buildCmd, ...buildArgs] = config.buildCommand.split(" ");
2239
+ await exec(buildCmd, buildArgs);
2240
+ logs.push("Build complete.");
2241
+ const headers = {
2242
+ "Content-Type": "application/json",
2243
+ ...api.headers || {}
2244
+ };
2245
+ switch (api.authType) {
2246
+ case "bearer":
2247
+ headers["Authorization"] = `Bearer ${api.authToken}`;
2248
+ break;
2249
+ case "apiKey":
2250
+ headers["X-API-Key"] = api.apiKey || "";
2251
+ break;
2252
+ case "basic":
2253
+ headers["Authorization"] = `Basic ${Buffer.from(api.authToken || "").toString("base64")}`;
2254
+ break;
2255
+ case "custom":
2256
+ break;
2257
+ }
2258
+ const deployUrl = `${api.baseUrl}${api.endpoints.deploy}`;
2259
+ const method = api.requestMethod || "POST";
2260
+ const payload = api.payloadTemplate || {
2261
+ name: config.environmentVariables?.SERVICE_NAME || "moltium-agent",
2262
+ buildCommand: config.buildCommand,
2263
+ startCommand: config.startCommand,
2264
+ envVars: config.environmentVariables
2265
+ };
2266
+ logs.push(`Calling deploy API: ${method} ${deployUrl}`);
2267
+ const response = method === "PUT" ? await axios3.put(deployUrl, payload, { headers }) : await axios3.post(deployUrl, payload, { headers });
2268
+ logs.push(`API response: ${response.status}`);
2269
+ const deploymentId = response.data.id || response.data.deploymentId || `api-${Date.now()}`;
2270
+ let url = response.data.url || response.data.deployUrl;
2271
+ if (api.endpoints.status) {
2272
+ const statusUrl = `${api.baseUrl}${api.endpoints.status}`.replace(":id", deploymentId);
2273
+ try {
2274
+ const statusRes = await axios3.get(statusUrl, { headers });
2275
+ url = url || statusRes.data.url;
2276
+ logs.push(`Status: ${statusRes.data.status || statusRes.data.state || "unknown"}`);
2277
+ } catch {
2278
+ }
2279
+ }
2280
+ return {
2281
+ success: true,
2282
+ url,
2283
+ deploymentId,
2284
+ logs
2285
+ };
2286
+ } catch (error) {
2287
+ const msg = axios3.isAxiosError(error) ? `API error ${error.response?.status}: ${error.response?.data?.message || error.message}` : error instanceof Error ? error.message : String(error);
2288
+ return {
2289
+ success: false,
2290
+ error: msg,
2291
+ logs
2292
+ };
2293
+ }
2294
+ }
2295
+ };
2296
+
2297
+ // src/commands/deploy.ts
2298
+ var deployers = {
2299
+ railway: new RailwayDeployer(),
2300
+ render: new RenderDeployer(),
2301
+ aws: new AWSDeployer(),
2302
+ digitalocean: new DigitalOceanDeployer(),
2303
+ custom: new CustomDeployer()
2304
+ };
2305
+ var deployCommand = new Command3("deploy").description("Deploy agent to cloud platform").argument("[platform]", "Deployment platform (railway, render, aws, digitalocean, custom)").option("-c, --config <file>", "Deployment config file", "deployment.config.ts").action(async (platform, options) => {
2306
+ const agentDir = resolve3(process.cwd());
2307
+ loadEnv2({ path: resolve3(agentDir, ".env") });
2308
+ if (!platform) {
2309
+ console.log(chalk3.yellow("Available deployment platforms:"));
2310
+ for (const [name, deployer2] of Object.entries(deployers)) {
2311
+ console.log(` ${chalk3.cyan(name)} \u2014 ${deployer2.description}`);
2312
+ }
2313
+ console.log(`
2314
+ Usage: ${chalk3.cyan("moltium deploy <platform>")}`);
2315
+ return;
2316
+ }
2317
+ const deployer = deployers[platform];
2318
+ if (!deployer) {
2319
+ console.log(chalk3.red(`Unknown platform: ${platform}`));
2320
+ console.log(`Available: ${Object.keys(deployers).join(", ")}`);
2321
+ process.exit(1);
2322
+ }
2323
+ const spinner = ora3(`Deploying to ${platform}...`).start();
2324
+ try {
2325
+ const configLoader = new ConfigLoader2();
2326
+ await configLoader.load(agentDir);
2327
+ let deployConfig = {
2328
+ platform
2329
+ };
2330
+ const deployConfigPath = resolve3(agentDir, options?.config || "deployment.config.ts");
2331
+ try {
2332
+ const { createJiti } = await import("jiti");
2333
+ const jiti = createJiti(pathToFileURL2(deployConfigPath).href, {
2334
+ interopDefault: true,
2335
+ moduleCache: false
2336
+ });
2337
+ const module = await jiti.import(deployConfigPath);
2338
+ deployConfig = { ...deployConfig, ...module.default || module };
2339
+ } catch {
2340
+ }
2341
+ spinner.text = "Validating deployment configuration...";
2342
+ await deployer.validate(deployConfig);
2343
+ spinner.text = "Running pre-deployment checks...";
2344
+ await deployer.preCheck();
2345
+ spinner.text = `Deploying to ${platform}...`;
2346
+ const result = await deployer.deploy(deployConfig);
2347
+ if (result.success) {
2348
+ await deployer.postDeploy(result);
2349
+ spinner.succeed(chalk3.green(`Deployed to ${platform}!`));
2350
+ if (result.url) {
2351
+ console.log(` URL: ${chalk3.cyan(result.url)}`);
2352
+ }
2353
+ if (result.deploymentId) {
2354
+ console.log(` Deployment ID: ${chalk3.gray(result.deploymentId)}`);
2355
+ }
2356
+ } else {
2357
+ spinner.fail(chalk3.red(`Deployment failed: ${result.error}`));
2358
+ process.exit(1);
2359
+ }
2360
+ } catch (error) {
2361
+ spinner.fail(chalk3.red("Deployment failed"));
2362
+ console.error(error instanceof Error ? error.message : error);
2363
+ process.exit(1);
2364
+ }
2365
+ });
2366
+
2367
+ // src/commands/config.ts
2368
+ import { Command as Command4 } from "commander";
2369
+ import { resolve as resolve4 } from "path";
2370
+ import chalk4 from "chalk";
2371
+ import { ConfigLoader as ConfigLoader3 } from "@moltium/core";
2372
+ var configCommand = new Command4("config").description("Manage agent configuration");
2373
+ configCommand.command("show").description("Show current agent configuration").action(async () => {
2374
+ try {
2375
+ const configLoader = new ConfigLoader3();
2376
+ const { config, type } = await configLoader.load(resolve4(process.cwd()));
2377
+ console.log(chalk4.bold(`
2378
+ Agent: ${config.name}`));
2379
+ console.log(`Type: ${config.type || "general"}`);
2380
+ console.log(`Config: ${type}`);
2381
+ console.log(`LLM: ${config.llm.provider} / ${config.llm.model}`);
2382
+ console.log(`Behaviors: ${config.behaviors.decisionMaking}, ${config.behaviors.actionsPerHour || 5} actions/hr`);
2383
+ console.log(`Memory: ${config.memory.type}`);
2384
+ console.log(`Actions: ${config.actions.join(", ") || "none"}`);
2385
+ const platforms = Object.keys(config.social).filter(
2386
+ (k) => config.social[k]?.enabled
2387
+ );
2388
+ console.log(`Social: ${platforms.join(", ") || "none"}`);
2389
+ } catch (error) {
2390
+ console.error(chalk4.red(error instanceof Error ? error.message : "Failed to load config"));
2391
+ process.exit(1);
2392
+ }
2393
+ });
2394
+ configCommand.command("validate").description("Validate agent configuration").action(async () => {
2395
+ try {
2396
+ const configLoader = new ConfigLoader3();
2397
+ const { config, type } = await configLoader.load(resolve4(process.cwd()));
2398
+ console.log(chalk4.green(`
2399
+ \u2713 Valid ${type} configuration for "${config.name}"`));
2400
+ } catch (error) {
2401
+ console.error(chalk4.red(`
2402
+ \u2717 ${error instanceof Error ? error.message : "Invalid configuration"}`));
2403
+ process.exit(1);
2404
+ }
2405
+ });
2406
+
2407
+ // src/commands/migrate.ts
2408
+ import { Command as Command5 } from "commander";
2409
+ import { resolve as resolve5, join as join3 } from "path";
2410
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2411
+ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
2412
+ import chalk5 from "chalk";
2413
+ import ora4 from "ora";
2414
+ import { ConfigLoader as ConfigLoader4 } from "@moltium/core";
2415
+ var migrateCommand = new Command5("migrate").description("Migrate between configuration types").requiredOption("--from <type>", "Source config type (code/markdown)").requiredOption("--to <type>", "Target config type (code/markdown)").option("--dir <directory>", "Agent directory", ".").option("--keep", "Keep the original config file after migration").action(async (options) => {
2416
+ const { from, to, keep } = options;
2417
+ const agentDir = resolve5(process.cwd(), options.dir);
2418
+ if (from === to) {
2419
+ console.log(chalk5.yellow("Source and target config types are the same."));
2420
+ return;
2421
+ }
2422
+ if (!["code", "markdown"].includes(from) || !["code", "markdown"].includes(to)) {
2423
+ console.log(chalk5.red('Config types must be "code" or "markdown".'));
2424
+ process.exit(1);
2425
+ }
2426
+ const spinner = ora4(`Migrating from ${from} to ${to}...`).start();
2427
+ try {
2428
+ const configLoader = new ConfigLoader4();
2429
+ if (from === "code" && to === "markdown") {
2430
+ await migrateCodeToMarkdown(configLoader, agentDir, keep, spinner);
2431
+ } else {
2432
+ await migrateMarkdownToCode(configLoader, agentDir, keep, spinner);
2433
+ }
2434
+ spinner.succeed(chalk5.green(`Migration complete: ${from} \u2192 ${to}`));
2435
+ } catch (error) {
2436
+ spinner.fail(chalk5.red("Migration failed"));
2437
+ console.error(error instanceof Error ? error.message : error);
2438
+ process.exit(1);
2439
+ }
2440
+ });
2441
+ async function migrateCodeToMarkdown(configLoader, agentDir, keep, spinner) {
2442
+ const configPath = resolve5(agentDir, "agent.config.ts");
2443
+ if (!existsSync3(configPath)) {
2444
+ throw new Error("No agent.config.ts found in the current directory.");
2445
+ }
2446
+ spinner.text = "Loading code config...";
2447
+ const config = await configLoader.loadCode(configPath);
2448
+ spinner.text = "Generating agent.md...";
2449
+ const socialSections = generateSocialMarkdown(config);
2450
+ const skillSections = config.actions.map((a) => {
2451
+ const name = a.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2452
+ return `### ${name}
2453
+ Execute the ${a} action based on context and current goals.`;
2454
+ }).join("\n\n");
2455
+ const sleepSchedule = config.behaviors.sleepSchedule ? `${config.behaviors.sleepSchedule.start}:00-${config.behaviors.sleepSchedule.end}:00${config.behaviors.sleepSchedule.timezone ? " " + config.behaviors.sleepSchedule.timezone : " UTC"}` : "none";
2456
+ const agentMd = `# Agent Configuration
2457
+
2458
+ ## Identity
2459
+ name: ${config.name}
2460
+ type: ${config.type || "assistant"}
2461
+ personality: ${config.personality.traits.join(", ")}
2462
+
2463
+ ## Bio
2464
+ ${config.personality.bio}
2465
+
2466
+ ## Social Platforms
2467
+ ${socialSections}
2468
+
2469
+ ## Behaviors
2470
+ autonomous: ${config.behaviors.autonomous}
2471
+ decision_making: ${config.behaviors.decisionMaking}
2472
+ actions_per_hour: ${typeof config.behaviors.actionsPerHour === "number" ? config.behaviors.actionsPerHour : 5}
2473
+ sleep_schedule: ${sleepSchedule}
2474
+
2475
+ ## Memory
2476
+ type: ${config.memory.type}
2477
+ ${config.memory.retention ? `retention: ${config.memory.retention}` : ""}
2478
+
2479
+ ## Skills
2480
+
2481
+ ${skillSections}
2482
+ `;
2483
+ await writeFile2(join3(agentDir, "agent.md"), agentMd);
2484
+ await mkdir2(join3(agentDir, "personality"), { recursive: true });
2485
+ await writeFile2(join3(agentDir, "personality", "bio.md"), `# ${config.name}
2486
+
2487
+ ${config.personality.bio}
2488
+ `);
2489
+ await writeFile2(join3(agentDir, "personality", "traits.md"), `# ${config.name} \u2014 Traits
2490
+
2491
+ ${config.personality.traits.map((t) => `### ${t.charAt(0).toUpperCase() + t.slice(1)}
2492
+ Describe how this trait manifests in behavior.
2493
+ `).join("\n")}`);
2494
+ await mkdir2(join3(agentDir, "skills"), { recursive: true });
2495
+ for (const action of config.actions) {
2496
+ const name = action.replace(/_/g, "-");
2497
+ const title = action.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2498
+ await writeFile2(join3(agentDir, "skills", `${name}.md`), `# ${title}
2499
+
2500
+ Describe the detailed instructions for this skill.
2501
+ `);
2502
+ }
2503
+ await mkdir2(join3(agentDir, "prompts"), { recursive: true });
2504
+ await writeFile2(join3(agentDir, "prompts", "system.md"), `# System Prompt for ${config.name}
2505
+
2506
+ ${config.llm.systemPrompt || "Add your custom system prompt instructions here."}
2507
+ `);
2508
+ await mkdir2(join3(agentDir, "memory"), { recursive: true });
2509
+ await writeFile2(join3(agentDir, "memory", "context.md"), `# ${config.name} \u2014 Long-Term Context
2510
+
2511
+ ## Important Facts
2512
+ - Migrated from code-based configuration
2513
+ - Type: ${config.type || "assistant"}
2514
+
2515
+ ## Current Goals
2516
+ 1. Continue operating as before
2517
+ `);
2518
+ if (!keep) {
2519
+ const { rename } = await import("fs/promises");
2520
+ await rename(configPath, configPath + ".bak");
2521
+ spinner.text = "Original agent.config.ts renamed to agent.config.ts.bak";
2522
+ }
2523
+ }
2524
+ async function migrateMarkdownToCode(configLoader, agentDir, keep, spinner) {
2525
+ const mdPath = resolve5(agentDir, "agent.md");
2526
+ if (!existsSync3(mdPath)) {
2527
+ throw new Error("No agent.md found in the current directory.");
2528
+ }
2529
+ spinner.text = "Loading markdown config...";
2530
+ const config = await configLoader.loadMarkdown(mdPath);
2531
+ spinner.text = "Generating agent.config.ts...";
2532
+ const socialConfigStr = generateSocialCodeConfig(config);
2533
+ const actionsArrayStr = config.actions.map((a) => `'${a}'`).join(", ");
2534
+ const sleepScheduleStr = config.behaviors.sleepSchedule ? `{ start: ${config.behaviors.sleepSchedule.start}, end: ${config.behaviors.sleepSchedule.end}${config.behaviors.sleepSchedule.timezone ? `, timezone: '${config.behaviors.sleepSchedule.timezone}'` : ""} }` : "undefined";
2535
+ const providerEnvKey = config.llm.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
2536
+ const codeConfig = `import type { AgentConfig } from '@moltium/core';
2537
+
2538
+ const config: AgentConfig = {
2539
+ name: '${config.name}',
2540
+ type: '${config.type || "assistant"}',
2541
+
2542
+ personality: {
2543
+ traits: [${config.personality.traits.map((t) => `'${t}'`).join(", ")}],
2544
+ bio: ${JSON.stringify(config.personality.bio)},
2545
+ },
2546
+
2547
+ llm: {
2548
+ provider: '${config.llm.provider}',
2549
+ model: '${config.llm.model}',
2550
+ apiKey: process.env.${providerEnvKey} || '',
2551
+ temperature: ${config.llm.temperature ?? 0.7},
2552
+ },
2553
+
2554
+ social: {
2555
+ ${socialConfigStr}
2556
+ },
2557
+
2558
+ behaviors: {
2559
+ autonomous: ${config.behaviors.autonomous},
2560
+ decisionMaking: '${config.behaviors.decisionMaking}',
2561
+ actionsPerHour: ${typeof config.behaviors.actionsPerHour === "number" ? config.behaviors.actionsPerHour : 5},
2562
+ sleepSchedule: ${sleepScheduleStr},
2563
+ },
2564
+
2565
+ memory: {
2566
+ type: '${config.memory.type}',
2567
+ ${config.memory.retention ? `retention: '${config.memory.retention}',` : ""}
2568
+ },
2569
+
2570
+ actions: [${actionsArrayStr}],
2571
+ };
2572
+
2573
+ export default config;
2574
+ `;
2575
+ await writeFile2(join3(agentDir, "agent.config.ts"), codeConfig);
2576
+ await mkdir2(join3(agentDir, "hooks"), { recursive: true });
2577
+ await mkdir2(join3(agentDir, "actions"), { recursive: true });
2578
+ const systemPromptPath = join3(agentDir, "prompts", "system.md");
2579
+ if (existsSync3(systemPromptPath)) {
2580
+ const systemPrompt = readFileSync2(systemPromptPath, "utf-8");
2581
+ await writeFile2(join3(agentDir, "hooks", "onInit.ts"), `import type { Agent } from '@moltium/core';
2582
+
2583
+ export async function onInit(agent: Agent): Promise<void> {
2584
+ // Migrated system prompt content from prompts/system.md
2585
+ agent.appendSystemPrompt(${JSON.stringify(systemPrompt)});
2586
+
2587
+ const memory = agent.getMemory();
2588
+ await memory.remember('session_started', new Date().toISOString());
2589
+ }
2590
+ `);
2591
+ } else {
2592
+ await writeFile2(join3(agentDir, "hooks", "onInit.ts"), `import type { Agent } from '@moltium/core';
2593
+
2594
+ export async function onInit(agent: Agent): Promise<void> {
2595
+ const memory = agent.getMemory();
2596
+ await memory.remember('session_started', new Date().toISOString());
2597
+ }
2598
+ `);
2599
+ }
2600
+ await writeFile2(join3(agentDir, "hooks", "onTick.ts"), `import type { Agent } from '@moltium/core';
2601
+
2602
+ export async function onTick(agent: Agent): Promise<void> {
2603
+ const memory = agent.getMemory();
2604
+ const count = ((await memory.recall('tick_count')) as number) || 0;
2605
+ await memory.remember('tick_count', count + 1);
2606
+ }
2607
+ `);
2608
+ await writeFile2(join3(agentDir, "hooks", "onShutdown.ts"), `import type { Agent } from '@moltium/core';
2609
+
2610
+ export async function onShutdown(agent: Agent): Promise<void> {
2611
+ const memory = agent.getMemory();
2612
+ await memory.remember('last_shutdown', new Date().toISOString());
2613
+ }
2614
+ `);
2615
+ if (!existsSync3(join3(agentDir, "tsconfig.json"))) {
2616
+ await writeFile2(join3(agentDir, "tsconfig.json"), JSON.stringify({
2617
+ compilerOptions: {
2618
+ target: "ES2022",
2619
+ module: "ESNext",
2620
+ moduleResolution: "bundler",
2621
+ strict: true,
2622
+ esModuleInterop: true,
2623
+ skipLibCheck: true,
2624
+ outDir: "./dist",
2625
+ rootDir: "."
2626
+ },
2627
+ include: ["*.ts", "actions/**/*.ts", "hooks/**/*.ts"]
2628
+ }, null, 2));
2629
+ }
2630
+ if (!keep) {
2631
+ const { rename } = await import("fs/promises");
2632
+ await rename(mdPath, mdPath + ".bak");
2633
+ spinner.text = "Original agent.md renamed to agent.md.bak";
2634
+ }
2635
+ }
2636
+ function generateSocialMarkdown(config) {
2637
+ const sections = [];
2638
+ const social = config.social || {};
2639
+ for (const [platform, platformConfig] of Object.entries(social)) {
2640
+ if (!platformConfig || typeof platformConfig !== "object") continue;
2641
+ const pc = platformConfig;
2642
+ if (!pc.enabled) continue;
2643
+ const lines = [`### ${platform.charAt(0).toUpperCase() + platform.slice(1)}`];
2644
+ lines.push(`enabled: true`);
2645
+ for (const [key, value] of Object.entries(pc)) {
2646
+ if (key === "enabled" || key === "credentials" || key === "apiKey") continue;
2647
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2648
+ lines.push(`${key}: ${value}`);
2649
+ }
2650
+ }
2651
+ sections.push(lines.join("\n"));
2652
+ }
2653
+ return sections.length > 0 ? sections.join("\n\n") : "No social platforms configured.";
2654
+ }
2655
+ function generateSocialCodeConfig(config) {
2656
+ const social = config.social || {};
2657
+ const lines = [];
2658
+ for (const [platform, platformConfig] of Object.entries(social)) {
2659
+ if (!platformConfig || typeof platformConfig !== "object") continue;
2660
+ const pc = platformConfig;
2661
+ if (platform === "moltbook" && pc.enabled) {
2662
+ lines.push(` moltbook: {
2663
+ enabled: true,
2664
+ apiKey: process.env.MOLTBOOK_API_KEY || '',
2665
+ },`);
2666
+ } else if (platform === "twitter" && pc.enabled) {
2667
+ lines.push(` twitter: {
2668
+ enabled: true,
2669
+ credentials: {
2670
+ apiKey: process.env.TWITTER_API_KEY || '',
2671
+ apiSecret: process.env.TWITTER_API_SECRET || '',
2672
+ accessToken: process.env.TWITTER_ACCESS_TOKEN || '',
2673
+ accessSecret: process.env.TWITTER_ACCESS_SECRET || '',
2674
+ },
2675
+ },`);
2676
+ }
2677
+ }
2678
+ return lines.join("\n");
2679
+ }
2680
+
2681
+ // src/commands/status.ts
2682
+ import { Command as Command6 } from "commander";
2683
+ import chalk6 from "chalk";
2684
+ import axios4 from "axios";
2685
+ var statusCommand = new Command6("status").description("Check agent status").option("-r, --remote <url>", "Remote agent URL").option("-p, --port <number>", "Local port", "3000").action(async (options) => {
2686
+ const url = options.remote || `http://localhost:${options.port}`;
2687
+ try {
2688
+ const [health, status] = await Promise.all([
2689
+ axios4.get(`${url}/health`, { timeout: 5e3 }),
2690
+ axios4.get(`${url}/status`, { timeout: 5e3 })
2691
+ ]);
2692
+ const data = status.data;
2693
+ console.log(chalk6.bold(`
2694
+ Agent: ${data.name}`));
2695
+ console.log(`State: ${chalk6.green(data.state)}`);
2696
+ console.log(`Type: ${data.type}`);
2697
+ console.log(`Uptime: ${Math.floor(data.uptime)}s`);
2698
+ console.log(`Health: ${chalk6.green(health.data.status)}`);
2699
+ } catch (error) {
2700
+ if (axios4.isAxiosError(error) && error.code === "ECONNREFUSED") {
2701
+ console.log(chalk6.yellow(`
2702
+ No agent running at ${url}`));
2703
+ } else {
2704
+ console.error(chalk6.red(`
2705
+ Failed to reach agent at ${url}`));
2706
+ console.error(error instanceof Error ? error.message : error);
2707
+ }
2708
+ process.exit(1);
2709
+ }
2710
+ });
2711
+
2712
+ // src/index.ts
2713
+ var program = new Command7();
2714
+ program.name("moltium").description("Moltium Agent SDK \u2014 create and manage autonomous AI agents").version("0.1.0");
2715
+ program.addCommand(initCommand);
2716
+ program.addCommand(startCommand);
2717
+ program.addCommand(deployCommand);
2718
+ program.addCommand(configCommand);
2719
+ program.addCommand(migrateCommand);
2720
+ program.addCommand(statusCommand);
2721
+ program.parse();
2722
+ //# sourceMappingURL=index.js.map