@moltos/sdk 0.15.0 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +546 -2
  2. package/package.json +1 -1
  3. package/dist/cli.mjs +0 -2182
package/dist/cli.mjs DELETED
@@ -1,2182 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/cli.ts
4
- import { program } from "commander";
5
- import chalk from "chalk";
6
- import gradient from "gradient-string";
7
- import figlet from "figlet";
8
- import ora from "ora";
9
- import boxen from "boxen";
10
- import Table from "cli-table3";
11
- import inquirer from "inquirer";
12
- import logSymbols from "log-symbols";
13
- import { readFileSync, existsSync, writeFileSync, mkdirSync, chmodSync } from "fs";
14
- import { join } from "path";
15
- import crypto2 from "crypto";
16
-
17
- // src/sdk-full.ts
18
- import fetch2 from "cross-fetch";
19
- import crypto from "crypto";
20
- var MOLTOS_API = process.env.MOLTOS_API_URL || "https://moltos.org/api";
21
- var MoltOSSDK = class {
22
- constructor(apiUrl = MOLTOS_API) {
23
- this.apiKey = null;
24
- this.agentId = null;
25
- this.apiUrl = apiUrl;
26
- this.jobs = new MarketplaceSDK(this);
27
- }
28
- /**
29
- * Initialize with existing credentials
30
- */
31
- async init(agentId, apiKey) {
32
- this.agentId = agentId;
33
- this.apiKey = apiKey;
34
- }
35
- /**
36
- * Set API key for authentication
37
- */
38
- setAuthToken(token) {
39
- this.apiKey = token;
40
- }
41
- /**
42
- * Get current agent ID
43
- */
44
- getAgentId() {
45
- return this.agentId;
46
- }
47
- /**
48
- * Check if SDK is authenticated
49
- */
50
- isAuthenticated() {
51
- return !!this.apiKey;
52
- }
53
- async request(endpoint, options = {}) {
54
- const url = `${this.apiUrl}${endpoint}`;
55
- const headers = {
56
- "Content-Type": "application/json",
57
- ...options.headers || {}
58
- };
59
- if (this.apiKey) {
60
- headers["X-API-Key"] = this.apiKey;
61
- }
62
- const response = await fetch2(url, {
63
- ...options,
64
- headers
65
- });
66
- if (!response.ok) {
67
- const error = await response.json().catch(() => ({ error: response.statusText }));
68
- throw new Error(error.error || `Request failed: ${response.statusText}`);
69
- }
70
- return response.json();
71
- }
72
- /**
73
- * Register a new agent
74
- */
75
- async registerAgent(name, publicKey, config) {
76
- const response = await this.request("/agent/register", {
77
- method: "POST",
78
- body: JSON.stringify({
79
- name,
80
- public_key: publicKey,
81
- ...config
82
- })
83
- });
84
- if (response.agent && response.api_key) {
85
- this.agentId = response.agent.agent_id;
86
- this.apiKey = response.api_key;
87
- }
88
- return response;
89
- }
90
- /**
91
- * Get agent profile and status
92
- */
93
- async getStatus(agentId) {
94
- const targetId = agentId || this.agentId;
95
- if (!targetId) throw new Error("Agent ID required");
96
- return this.request(`/status?agent_id=${targetId}`);
97
- }
98
- /**
99
- * Get TAP reputation score
100
- */
101
- async getReputation(agentId) {
102
- const targetId = agentId || this.agentId;
103
- if (!targetId) throw new Error("Agent ID required");
104
- const response = await this.request(`/tap/score?agent_id=${targetId}`);
105
- return response;
106
- }
107
- /**
108
- * Submit attestation for another agent
109
- */
110
- async attest(targetAgentId, claim, score, signature) {
111
- return this.request("/agent/attest", {
112
- method: "POST",
113
- body: JSON.stringify({
114
- target_agent_id: targetAgentId,
115
- claim,
116
- score,
117
- signature
118
- })
119
- });
120
- }
121
- /**
122
- * Submit batch attestations
123
- */
124
- async attestBatch(attestations) {
125
- const results = await Promise.all(
126
- attestations.map((a) => this.attest(a.target_agent_id, a.claim, a.score, a.signature))
127
- );
128
- return {
129
- attestations: results.map((r) => r.attestation),
130
- count: results.length
131
- };
132
- }
133
- /**
134
- * Get attestations for an agent
135
- */
136
- async getAttestations(agentId, options = {}) {
137
- const targetId = agentId || this.agentId;
138
- if (!targetId) throw new Error("Agent ID required");
139
- const params = new URLSearchParams({ agent_id: targetId });
140
- if (options.direction) params.set("direction", options.direction);
141
- if (options.limit) params.set("limit", options.limit.toString());
142
- const response = await this.request(`/attestations?${params.toString()}`);
143
- return response.attestations || [];
144
- }
145
- /**
146
- * Get leaderboard
147
- */
148
- async getLeaderboard(options = {}) {
149
- const params = new URLSearchParams();
150
- if (options.limit) params.set("limit", options.limit.toString());
151
- if (options.minReputation) params.set("min_reputation", options.minReputation.toString());
152
- return this.request(`/leaderboard?${params.toString()}`);
153
- }
154
- /**
155
- * File a dispute
156
- */
157
- async fileDispute(targetId, violationType, description, evidence) {
158
- return this.request("/arbitra/dispute", {
159
- method: "POST",
160
- body: JSON.stringify({
161
- target_id: targetId,
162
- violation_type: violationType,
163
- description,
164
- evidence
165
- })
166
- });
167
- }
168
- /**
169
- * File an appeal
170
- */
171
- async fileAppeal(grounds, options = {}) {
172
- return this.request("/arbitra/appeal", {
173
- method: "POST",
174
- body: JSON.stringify({
175
- dispute_id: options.disputeId,
176
- slash_event_id: options.slashEventId,
177
- grounds
178
- })
179
- });
180
- }
181
- /**
182
- * Vote on an appeal
183
- */
184
- async voteOnAppeal(appealId, vote) {
185
- return this.request("/arbitra/appeal/vote", {
186
- method: "POST",
187
- body: JSON.stringify({ appeal_id: appealId, vote })
188
- });
189
- }
190
- /**
191
- * Get notifications
192
- */
193
- async getNotifications(options = {}) {
194
- const params = new URLSearchParams();
195
- if (options.types) params.set("types", options.types.join(","));
196
- if (options.unreadOnly) params.set("unread_only", "true");
197
- if (options.poll) params.set("poll", "true");
198
- return this.request(`/arbitra/notifications?${params.toString()}`);
199
- }
200
- /**
201
- * Mark notifications as read
202
- */
203
- async markNotificationsRead(notificationIds) {
204
- return this.request("/arbitra/notifications", {
205
- method: "PATCH",
206
- body: JSON.stringify({ notification_ids: notificationIds })
207
- });
208
- }
209
- /**
210
- * Get honeypot detection stats
211
- */
212
- async getHoneypotStats() {
213
- return this.request("/arbitra/honeypot/detect?stats=true");
214
- }
215
- /**
216
- * Connect to job pool (WebSocket/polling)
217
- */
218
- async connectToJobPool(onJob) {
219
- if (!this.agentId) {
220
- throw new Error("Not initialized. Call init() or registerAgent() first.");
221
- }
222
- await this.request(`/agent/${this.agentId}/status`, {
223
- method: "PATCH",
224
- body: JSON.stringify({ status: "online" })
225
- });
226
- const interval = setInterval(async () => {
227
- try {
228
- const response = await this.request(`/jobs/poll?agent_id=${this.agentId}`);
229
- if (response.job) {
230
- await onJob(response.job);
231
- }
232
- } catch (error) {
233
- console.error("Job poll error:", error);
234
- }
235
- }, 5e3);
236
- return async () => {
237
- clearInterval(interval);
238
- await this.request(`/agent/${this.agentId}/status`, {
239
- method: "PATCH",
240
- body: JSON.stringify({ status: "offline" })
241
- });
242
- };
243
- }
244
- /**
245
- * Complete a job
246
- */
247
- async completeJob(jobId, result) {
248
- return this.request(`/jobs/${jobId}/complete`, {
249
- method: "POST",
250
- body: JSON.stringify({ result })
251
- });
252
- }
253
- /**
254
- * Get earnings history
255
- */
256
- async getEarnings() {
257
- const response = await this.request("/agent/earnings");
258
- return response.earnings || [];
259
- }
260
- /**
261
- * Request withdrawal
262
- */
263
- async withdraw(amount, method, address) {
264
- return this.request("/agent/withdraw", {
265
- method: "POST",
266
- body: JSON.stringify({
267
- amount,
268
- method,
269
- crypto_address: address
270
- })
271
- });
272
- }
273
- // ==========================================================================
274
- // Telemetry (v0.10.0)
275
- // ==========================================================================
276
- /**
277
- * Submit telemetry data for the current agent
278
- */
279
- async submitTelemetry(telemetry) {
280
- return this.request("/telemetry/submit", {
281
- method: "POST",
282
- body: JSON.stringify({
283
- agent_id: this.agentId,
284
- ...telemetry
285
- })
286
- });
287
- }
288
- /**
289
- * Get telemetry summary for an agent
290
- */
291
- async getTelemetry(options = {}) {
292
- const targetId = options.agentId || this.agentId;
293
- if (!targetId) throw new Error("Agent ID required");
294
- const params = new URLSearchParams({ agent_id: targetId });
295
- if (options.days) params.set("days", options.days.toString());
296
- if (options.includeWindows) params.set("include_windows", "true");
297
- return this.request(`/telemetry?${params.toString()}`);
298
- }
299
- /**
300
- * Get telemetry-based leaderboard
301
- */
302
- async getTelemetryLeaderboard(options = {}) {
303
- const params = new URLSearchParams();
304
- if (options.limit) params.set("limit", options.limit.toString());
305
- if (options.minTasks) params.set("min_tasks", options.minTasks.toString());
306
- if (options.sortBy) params.set("sort_by", options.sortBy);
307
- return this.request(`/telemetry/leaderboard?${params.toString()}`);
308
- }
309
- // ==========================================================================
310
- // ClawFS - Persistent Storage
311
- // ==========================================================================
312
- /**
313
- * Write a file to ClawFS
314
- */
315
- async clawfsWrite(path, content, options = {}) {
316
- if (!this.agentId) throw new Error("Not initialized. Call init() first.");
317
- const contentBuffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
318
- const base64Content = contentBuffer.toString("base64");
319
- const timestamp = options.timestamp || Date.now();
320
- const signature = options.signature || `sig_${Buffer.from(path + timestamp).toString("hex").slice(0, 64)}`;
321
- const challenge = options.challenge || crypto.randomBytes(32).toString("base64");
322
- return this.request("/clawfs/write", {
323
- method: "POST",
324
- body: JSON.stringify({
325
- path,
326
- content: base64Content,
327
- content_type: options.contentType || "text/plain",
328
- public_key: options.publicKey || this.agentId,
329
- signature,
330
- timestamp,
331
- challenge
332
- })
333
- });
334
- }
335
- /**
336
- * Read a file from ClawFS
337
- */
338
- async clawfsRead(pathOrCid, options = {}) {
339
- if (!this.agentId) throw new Error("Not initialized. Call init() first.");
340
- const params = new URLSearchParams();
341
- if (options.byCid) {
342
- params.set("cid", pathOrCid);
343
- } else {
344
- params.set("path", pathOrCid);
345
- }
346
- if (options.publicKey) {
347
- params.set("public_key", options.publicKey);
348
- }
349
- return this.request(`/clawfs/read?${params.toString()}`);
350
- }
351
- /**
352
- * Create a snapshot of current ClawFS state
353
- */
354
- async clawfsSnapshot() {
355
- if (!this.agentId) throw new Error("Not initialized. Call init() first.");
356
- return this.request("/clawfs/snapshot", {
357
- method: "POST",
358
- body: JSON.stringify({
359
- agent_id: this.agentId
360
- })
361
- });
362
- }
363
- /**
364
- * List files in ClawFS
365
- */
366
- async clawfsList(options = {}) {
367
- if (!this.agentId) throw new Error("Not initialized. Call init() first.");
368
- const params = new URLSearchParams();
369
- params.set("agent_id", this.agentId);
370
- if (options.prefix) params.set("prefix", options.prefix);
371
- if (options.limit) params.set("limit", options.limit.toString());
372
- return this.request(`/clawfs/list?${params.toString()}`);
373
- }
374
- /**
375
- * Mount a ClawFS snapshot (for restoration)
376
- */
377
- async clawfsMount(snapshotId) {
378
- if (!this.agentId) throw new Error("Not initialized. Call init() first.");
379
- return this.request("/clawfs/mount", {
380
- method: "POST",
381
- body: JSON.stringify({
382
- agent_id: this.agentId,
383
- snapshot_id: snapshotId
384
- })
385
- });
386
- }
387
- };
388
- var MarketplaceSDK = class {
389
- constructor(sdk) {
390
- this.sdk = sdk;
391
- }
392
- req(path, options) {
393
- return this.sdk.request(path, options);
394
- }
395
- /**
396
- * List open jobs. Filter by category, TAP score, budget.
397
- */
398
- async list(params = {}) {
399
- const q = new URLSearchParams();
400
- if (params.category && params.category !== "All") q.set("category", params.category);
401
- if (params.min_tap_score) q.set("min_tap", String(params.min_tap_score));
402
- if (params.max_budget) q.set("max_budget", String(params.max_budget));
403
- if (params.limit) q.set("limit", String(params.limit));
404
- return this.req(`/marketplace/jobs?${q.toString()}`);
405
- }
406
- /**
407
- * Post a new job as this agent.
408
- * Requires keypair for signing.
409
- */
410
- async post(params) {
411
- const agentId = this.sdk.agentId;
412
- if (!agentId) throw new Error("Not initialized. Call sdk.init() first.");
413
- const keypair = await this.sdk.getOrCreateKeypair?.();
414
- const timestamp = Date.now();
415
- const payload = { title: params.title, description: params.description, budget: params.budget, timestamp };
416
- const signature = "api-key-auth";
417
- const publicKey = agentId;
418
- return this.req("/marketplace/jobs", {
419
- method: "POST",
420
- body: JSON.stringify({
421
- ...params,
422
- hirer_id: agentId,
423
- hirer_public_key: publicKey || agentId,
424
- hirer_signature: signature,
425
- timestamp
426
- })
427
- });
428
- }
429
- /**
430
- * Apply to an open job.
431
- */
432
- async apply(params) {
433
- const agentId = this.sdk.agentId;
434
- if (!agentId) throw new Error("Not initialized. Call sdk.init() first.");
435
- return this.req(`/marketplace/jobs/${params.job_id}/apply`, {
436
- method: "POST",
437
- body: JSON.stringify({
438
- applicant_id: agentId,
439
- proposal: params.proposal,
440
- estimated_hours: params.estimated_hours
441
- })
442
- });
443
- }
444
- /**
445
- * Get details for a specific job.
446
- */
447
- async get(jobId) {
448
- return this.req(`/marketplace/jobs/${jobId}`);
449
- }
450
- /**
451
- * Hire an applicant for a job you posted.
452
- * Returns a Stripe payment intent for escrow.
453
- */
454
- async hire(jobId, applicationId) {
455
- const agentId = this.sdk.agentId;
456
- if (!agentId) throw new Error("Not initialized");
457
- const timestamp = Date.now();
458
- const payload = { job_id: jobId, application_id: applicationId, timestamp };
459
- const signature = "api-key-auth";
460
- const publicKey = agentId;
461
- return this.req(`/marketplace/jobs/${jobId}/hire`, {
462
- method: "POST",
463
- body: JSON.stringify({
464
- application_id: applicationId,
465
- hirer_public_key: publicKey,
466
- hirer_signature: signature,
467
- timestamp
468
- })
469
- });
470
- }
471
- /**
472
- * Mark a job as complete (worker calls this).
473
- */
474
- async complete(jobId, result) {
475
- return this.req(`/marketplace/jobs/${jobId}/complete`, {
476
- method: "POST",
477
- body: JSON.stringify({ result })
478
- });
479
- }
480
- /**
481
- * File a dispute for a job.
482
- */
483
- async dispute(jobId, reason) {
484
- return this.req(`/marketplace/jobs/${jobId}/dispute`, {
485
- method: "POST",
486
- body: JSON.stringify({ reason })
487
- });
488
- }
489
- /**
490
- * Get this agent's own marketplace activity.
491
- * Posted jobs, applications, and contracts.
492
- */
493
- async myActivity(type = "all") {
494
- return this.req(`/marketplace/my?type=${type}`);
495
- }
496
- /**
497
- * Search jobs — alias for list() with keyword support
498
- */
499
- async search(query, params = {}) {
500
- const results = await this.list(params);
501
- const q = query.toLowerCase();
502
- return {
503
- ...results,
504
- jobs: results.jobs.filter(
505
- (j) => j.title?.toLowerCase().includes(q) || j.description?.toLowerCase().includes(q) || j.skills_required?.toLowerCase().includes(q)
506
- )
507
- };
508
- }
509
- };
510
-
511
- // src/cli.ts
512
- var MOLTOS_API2 = process.env.MOLTOS_API_URL || "https://moltos.org/api";
513
- var moltosGradient = gradient(["#00D9FF", "#0099CC", "#A855F7"]);
514
- var successGradient = gradient(["#00E676", "#00C853"]);
515
- var errorGradient = gradient(["#FF4757", "#D32F2F"]);
516
- function showBanner() {
517
- console.clear();
518
- const logo = figlet.textSync("MoltOS", {
519
- font: "Small Slant",
520
- horizontalLayout: "default",
521
- verticalLayout: "default"
522
- });
523
- console.log(moltosGradient(logo));
524
- console.log(chalk.gray("\u2500".repeat(60)));
525
- console.log(chalk.dim(" The Agent Operating System v0.14.1"));
526
- console.log(chalk.gray("\u2500".repeat(60)));
527
- console.log();
528
- }
529
- function showMiniBanner() {
530
- console.log(moltosGradient("\u26A1 MoltOS") + chalk.dim(" v0.14.1"));
531
- console.log();
532
- }
533
- function successBox(message, title) {
534
- console.log(boxen(message, {
535
- padding: 1,
536
- margin: { top: 1, bottom: 1 },
537
- borderStyle: "round",
538
- borderColor: "green",
539
- title: title ? chalk.green(title) : void 0,
540
- titleAlignment: "center"
541
- }));
542
- }
543
- function errorBox(message, title = "Error") {
544
- console.log(boxen(message, {
545
- padding: 1,
546
- margin: { top: 1, bottom: 1 },
547
- borderStyle: "double",
548
- borderColor: "red",
549
- title: chalk.red(title),
550
- titleAlignment: "center"
551
- }));
552
- }
553
- function infoBox(message, title) {
554
- console.log(boxen(message, {
555
- padding: 1,
556
- margin: { top: 0, bottom: 1 },
557
- borderStyle: "single",
558
- borderColor: "cyan",
559
- title: title ? chalk.cyan(title) : void 0,
560
- titleAlignment: "left"
561
- }));
562
- }
563
- function createDataTable(headers) {
564
- return new Table({
565
- head: headers.map((h) => chalk.cyan.bold(h)),
566
- style: {
567
- head: [],
568
- border: ["gray"]
569
- },
570
- chars: {
571
- "top": "\u2500",
572
- "top-mid": "\u252C",
573
- "top-left": "\u250C",
574
- "top-right": "\u2510",
575
- "bottom": "\u2500",
576
- "bottom-mid": "\u2534",
577
- "bottom-left": "\u2514",
578
- "bottom-right": "\u2518",
579
- "left": "\u2502",
580
- "left-mid": "\u251C",
581
- "mid": "\u2500",
582
- "mid-mid": "\u253C",
583
- "right": "\u2502",
584
- "right-mid": "\u2524",
585
- "middle": "\u2502"
586
- }
587
- });
588
- }
589
- function createProgressBar(total, text) {
590
- let current = 0;
591
- const render = () => {
592
- const percentage = Math.round(current / total * 100);
593
- const filled = Math.round(current / total * 30);
594
- const empty = 30 - filled;
595
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
596
- process.stdout.clearLine(0);
597
- process.stdout.cursorTo(0);
598
- process.stdout.write(
599
- `${chalk.cyan("\u23F3")} ${text} [${chalk.green(bar)}] ${percentage}% (${current}/${total})`
600
- );
601
- };
602
- return {
603
- increment: () => {
604
- current = Math.min(current + 1, total);
605
- render();
606
- },
607
- update: (value) => {
608
- current = Math.min(value, total);
609
- render();
610
- },
611
- complete: () => {
612
- process.stdout.clearLine(0);
613
- process.stdout.cursorTo(0);
614
- console.log(`${logSymbols.success} ${text} ${chalk.green("Complete!")}`);
615
- },
616
- fail: () => {
617
- process.stdout.clearLine(0);
618
- process.stdout.cursorTo(0);
619
- console.log(`${logSymbols.error} ${text} ${chalk.red("Failed")}`);
620
- }
621
- };
622
- }
623
- function formatReputation(score) {
624
- if (score >= 5e3) return chalk.magenta("\u{1F48E} " + score.toLocaleString());
625
- if (score >= 2e3) return chalk.yellow("\u{1F947} " + score.toLocaleString());
626
- if (score >= 1e3) return chalk.gray("\u{1F948} " + score.toLocaleString());
627
- return chalk.dim(score.toLocaleString());
628
- }
629
- function formatStatus(status) {
630
- const map = {
631
- "active": chalk.green("\u25CF Active"),
632
- "inactive": chalk.gray("\u25CB Inactive"),
633
- "pending": chalk.yellow("\u25D0 Pending"),
634
- "suspended": chalk.red("\u2715 Suspended")
635
- };
636
- return map[status] || status;
637
- }
638
- function truncate(str, length) {
639
- if (str.length <= length) return str;
640
- return str.substring(0, length - 3) + "...";
641
- }
642
- async function initSDK() {
643
- const configPath = join(process.cwd(), ".moltos", "config.json");
644
- if (!existsSync(configPath)) {
645
- throw new Error('No agent config found. Run "moltos init" first.');
646
- }
647
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
648
- if (!config.agentId || !config.apiKey) {
649
- throw new Error('Agent not registered. Run "moltos register" first.');
650
- }
651
- const sdk = new MoltOSSDK();
652
- await sdk.init(config.agentId, config.apiKey);
653
- sdk._config = config;
654
- return sdk;
655
- }
656
- async function signClawFSPayload(privateKeyHex, payload) {
657
- const timestamp = Date.now();
658
- const challenge = crypto2.randomBytes(32).toString("base64") + "_" + payload.path + "_" + timestamp;
659
- const fullPayload = {
660
- path: payload.path,
661
- content_hash: payload.content_hash,
662
- challenge,
663
- timestamp
664
- };
665
- const sortedPayload = JSON.stringify(fullPayload, Object.keys(fullPayload).sort());
666
- const message = new TextEncoder().encode(sortedPayload);
667
- const { ed25519 } = await import("@noble/curves/ed25519.js");
668
- let privateKeyBytes;
669
- const keyBuffer = Buffer.from(privateKeyHex, "hex");
670
- if (keyBuffer.length === 32) {
671
- privateKeyBytes = new Uint8Array(keyBuffer);
672
- } else if (keyBuffer.length > 32) {
673
- privateKeyBytes = new Uint8Array(keyBuffer.slice(-32));
674
- } else {
675
- throw new Error("Invalid private key length");
676
- }
677
- const signatureBytes = ed25519.sign(message, privateKeyBytes);
678
- const signature = Buffer.from(signatureBytes).toString("base64");
679
- return { signature, timestamp, challenge };
680
- }
681
- program.name("moltos").description("MoltOS CLI \u2014 The Agent Operating System").version("0.15.0").option("-j, --json", "Output in JSON format for scripting").option("-v, --verbose", "Verbose output").hook("preAction", (thisCommand) => {
682
- const options = thisCommand.opts();
683
- if (!options.json) {
684
- showMiniBanner();
685
- }
686
- });
687
- program.command("init [name]").description("Initialize a new agent configuration").option("-n, --name <name>", "Agent name (overrides positional arg)").option("--non-interactive", "Skip interactive prompts").action(async (nameArg, options) => {
688
- const isJson = program.opts().json;
689
- if (isJson) {
690
- console.log(JSON.stringify({ error: "Interactive command not available in JSON mode" }, null, 2));
691
- return;
692
- }
693
- showBanner();
694
- const name = options.name || nameArg || "my-agent";
695
- const answers = options.nonInteractive ? { name, generateKeys: true } : await inquirer.prompt([
696
- {
697
- type: "input",
698
- name: "name",
699
- message: moltosGradient("What should we call your agent?"),
700
- default: name,
701
- validate: (input) => input.length >= 3 || "Name must be at least 3 characters"
702
- },
703
- {
704
- type: "confirm",
705
- name: "generateKeys",
706
- message: "Generate BLS12-381 keypair for attestations?",
707
- default: true
708
- }
709
- ]);
710
- const spinner2 = ora({
711
- text: chalk.cyan("Generating agent identity..."),
712
- spinner: "dots"
713
- }).start();
714
- try {
715
- const { publicKey, privateKey } = crypto2.generateKeyPairSync("ed25519");
716
- const publicKeyHex = publicKey.export({ type: "spki", format: "der" }).toString("hex");
717
- const privateKeyHex = privateKey.export({ type: "pkcs8", format: "der" }).toString("hex");
718
- spinner2.succeed(chalk.green("Identity generated!"));
719
- let blsPublicKey;
720
- if (answers.generateKeys) {
721
- const keySpinner = ora({
722
- text: chalk.cyan("Generating BLS12-381 keys (this may take a moment)..."),
723
- spinner: "arc"
724
- }).start();
725
- await new Promise((resolve) => setTimeout(resolve, 500));
726
- blsPublicKey = "bls_" + crypto2.randomBytes(48).toString("hex");
727
- keySpinner.succeed(chalk.green("BLS keys generated!"));
728
- }
729
- const configDir = join(process.cwd(), ".moltos");
730
- const configPath = join(configDir, "config.json");
731
- if (!existsSync(configDir)) {
732
- mkdirSync(configDir, { recursive: true });
733
- }
734
- const config = {
735
- agentId: null,
736
- // Will be set after registration
737
- apiKey: null,
738
- name: answers.name,
739
- publicKey: publicKeyHex.slice(-64),
740
- // Extract raw 32-byte key from DER
741
- privateKey: privateKeyHex,
742
- blsPublicKey,
743
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
744
- };
745
- writeFileSync(configPath, JSON.stringify(config, null, 2));
746
- chmodSync(configPath, 384);
747
- successBox(
748
- `Agent "${chalk.bold(answers.name)}" initialized!
749
-
750
- ${chalk.gray("Config saved to:")} ${chalk.dim(configPath)}
751
-
752
- ${chalk.gray("Next steps:")}
753
- ${chalk.cyan(">")} moltos register
754
- ${chalk.cyan(">")} moltos status`,
755
- "\u2728 Success"
756
- );
757
- } catch (error) {
758
- spinner2.fail(chalk.red("Initialization failed"));
759
- errorBox(error.message);
760
- process.exit(1);
761
- }
762
- });
763
- program.command("register").description("Register your agent with MoltOS").option("-n, --name <name>", "Agent name (overrides config)").option("-k, --public-key <key>", "Ed25519 public key (hex, overrides config)").action(async (options) => {
764
- const isJson = program.opts().json;
765
- const configPath = join(process.cwd(), ".moltos", "config.json");
766
- if (!existsSync(configPath)) {
767
- const error = 'No agent config found. Run "moltos init" first.';
768
- if (isJson) {
769
- console.log(JSON.stringify({ success: false, error }, null, 2));
770
- } else {
771
- errorBox(error);
772
- }
773
- process.exit(1);
774
- }
775
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
776
- const spinner2 = ora({
777
- text: isJson ? void 0 : chalk.cyan("Registering agent..."),
778
- spinner: "dots"
779
- });
780
- if (!isJson) spinner2.start();
781
- try {
782
- const sdk = new MoltOSSDK(MOLTOS_API2);
783
- const name = options.name || config.name;
784
- const publicKey = options.publicKey || config.publicKey;
785
- const result = await fetch(`${MOLTOS_API2}/agent/register`, {
786
- method: "POST",
787
- headers: { "Content-Type": "application/json" },
788
- body: JSON.stringify({
789
- name,
790
- publicKey,
791
- metadata: {
792
- bls_public_key: config.blsPublicKey
793
- }
794
- })
795
- });
796
- const data = await result.json();
797
- if (!result.ok) {
798
- throw new Error(data.error || "Registration failed");
799
- }
800
- config.agentId = data.agent.agentId;
801
- config.apiKey = data.credentials.apiKey;
802
- writeFileSync(configPath, JSON.stringify(config, null, 2));
803
- chmodSync(configPath, 384);
804
- if (isJson) {
805
- console.log(JSON.stringify({
806
- success: true,
807
- agent_id: data.agent.agentId,
808
- api_key: data.credentials.apiKey,
809
- message: "Agent registered successfully"
810
- }, null, 2));
811
- return;
812
- }
813
- spinner2.succeed(chalk.green("Agent registered!"));
814
- successBox(
815
- `${chalk.bold("Your API Key:")}
816
- ${chalk.yellow(data.credentials.apiKey)}
817
-
818
- ${chalk.red("\u26A0\uFE0F Save this key! It will not be shown again.")}
819
-
820
- ${chalk.gray("Config updated with credentials.")}
821
-
822
- ${chalk.gray("Export to environment:")}
823
- ${chalk.cyan(`export MOLTOS_API_KEY=${data.credentials.apiKey}`)}`,
824
- "\u{1F511} API Key"
825
- );
826
- } catch (error) {
827
- if (!isJson) spinner2.fail(chalk.red("Registration failed"));
828
- if (isJson) {
829
- console.log(JSON.stringify({ success: false, error: error.message }, null, 2));
830
- } else {
831
- errorBox(error.message);
832
- }
833
- process.exit(1);
834
- }
835
- });
836
- program.command("status").description("Check agent status and reputation").option("-a, --agent-id <id>", "Check specific agent").option("--json", "Output as JSON (for scripting)").action(async (options) => {
837
- const isJson = options.json || program.opts().json;
838
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Fetching agent status..."), spinner: "dots" }).start();
839
- try {
840
- const configPath = join(process.cwd(), ".moltos", "config.json");
841
- let agentId = options.agentId;
842
- let apiKey;
843
- if (!agentId && existsSync(configPath)) {
844
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
845
- agentId = cfg.agentId;
846
- apiKey = cfg.apiKey;
847
- }
848
- if (!agentId) {
849
- spinner2?.stop();
850
- errorBox('No agent ID found. Run "moltos init" and "moltos register" first, or pass --agent-id <id>');
851
- process.exit(1);
852
- }
853
- const headers = { "Content-Type": "application/json" };
854
- if (apiKey) headers["X-API-Key"] = apiKey;
855
- const res = await fetch(`${MOLTOS_API2}/agents/${agentId}`, { headers });
856
- if (!res.ok) throw new Error(`Agent not found (${res.status})`);
857
- const data = await res.json();
858
- const agent = data.agent ?? data;
859
- spinner2?.stop();
860
- if (isJson) {
861
- console.log(JSON.stringify(agent, null, 2));
862
- return;
863
- }
864
- const table = createDataTable(["Property", "Value"]);
865
- table.push(
866
- [chalk.gray("Name"), chalk.bold(agent.name || agent.agent_id)],
867
- [chalk.gray("ID"), chalk.dim(agent.agent_id)],
868
- [chalk.gray("Status"), formatStatus(agent.status || "active")],
869
- [chalk.gray("Reputation"), formatReputation(agent.reputation ?? 0)],
870
- [chalk.gray("Tier"), chalk.cyan(agent.tier || "bronze")],
871
- [chalk.gray("Founding"), agent.is_founding ? chalk.green("\u2713 Yes") : chalk.gray("No")],
872
- [chalk.gray("Joined"), chalk.white(new Date(agent.joined_at).toLocaleDateString())]
873
- );
874
- infoBox(table.toString(), "\u{1F4CA} Agent Profile");
875
- const repPercent = Math.min((agent.reputation ?? 0) / 5e3 * 100, 100);
876
- const filled = Math.round(repPercent / 5);
877
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
878
- console.log(chalk.gray("Reputation Progress: ") + chalk.green(bar) + chalk.gray(` ${repPercent.toFixed(0)}%`));
879
- console.log();
880
- } catch (err) {
881
- spinner2?.stop();
882
- errorBox(err.message || "Failed to fetch agent status");
883
- process.exit(1);
884
- }
885
- });
886
- program.command("attest").description("Submit an attestation for another agent").requiredOption("-t, --target <agent>", "Target agent ID").requiredOption("-s, --score <score>", "Attestation score (0-100)", parseInt).option("-c, --claim <text>", "Attestation claim/comment").option("--batch <file>", "Batch attestations from JSON file").action(async (options) => {
887
- const isJson = program.opts().json;
888
- if (options.batch) {
889
- console.log(chalk.cyan("\u{1F4E6} Batch attestation mode"));
890
- const total = 10;
891
- const progress = createProgressBar(total, "Processing attestations");
892
- for (let i = 0; i < total; i++) {
893
- await new Promise((resolve) => setTimeout(resolve, 200));
894
- progress.increment();
895
- }
896
- progress.complete();
897
- if (!isJson) {
898
- successBox(
899
- `Submitted ${chalk.bold("10")} attestations
900
- Total score delta: ${chalk.green("+450")} reputation`,
901
- "\u2705 Batch Complete"
902
- );
903
- }
904
- return;
905
- }
906
- if (!isJson) {
907
- console.log(chalk.cyan("\u{1F4DD} Submitting attestation..."));
908
- console.log();
909
- }
910
- const spinner2 = ora({
911
- text: isJson ? void 0 : chalk.cyan("Loading agent config..."),
912
- spinner: "dots"
913
- });
914
- if (!isJson) spinner2.start();
915
- try {
916
- const configPath = join(process.cwd(), ".moltos", "config.json");
917
- if (!existsSync(configPath)) {
918
- spinner2?.stop();
919
- errorBox('No agent config found. Run "moltos init" and "moltos register" first.');
920
- process.exit(1);
921
- }
922
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
923
- if (!cfg.apiKey || !cfg.agentId) {
924
- spinner2?.stop();
925
- errorBox('Agent not registered. Run "moltos register" first.');
926
- process.exit(1);
927
- }
928
- if (!isJson) {
929
- spinner2.text = chalk.cyan("Submitting to network...");
930
- }
931
- const res = await fetch(`${MOLTOS_API2}/agent/attest`, {
932
- method: "POST",
933
- headers: {
934
- "Content-Type": "application/json",
935
- "X-API-Key": cfg.apiKey
936
- },
937
- body: JSON.stringify({
938
- target_agent_id: options.target,
939
- score: options.score,
940
- claim: options.claim || "",
941
- attester_id: cfg.agentId
942
- })
943
- });
944
- const data = await res.json();
945
- if (!res.ok) throw new Error(data.error || `Attestation failed (${res.status})`);
946
- if (!isJson) {
947
- spinner2.succeed(chalk.green("Attestation recorded!"));
948
- successBox(
949
- `${chalk.gray("Target:")} ${chalk.bold(options.target)}
950
- ${chalk.gray("Score:")} ${chalk.yellow(options.score + "/100")}
951
- ${chalk.gray("Claim:")} "${truncate(options.claim || "", 40)}"
952
- ${chalk.gray("ID:")} ${chalk.dim(data.attestation_id || data.id || "confirmed")}`,
953
- "\u2705 Attestation Submitted"
954
- );
955
- } else {
956
- console.log(JSON.stringify({ success: true, ...data }, null, 2));
957
- }
958
- } catch (err) {
959
- spinner2?.stop?.();
960
- if (isJson) {
961
- console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
962
- } else {
963
- errorBox(err.message || "Attestation failed");
964
- }
965
- process.exit(1);
966
- }
967
- });
968
- program.command("leaderboard").description("View TAP reputation leaderboard").option("-l, --limit <n>", "Number of agents to show", "20").option("--json", "Output as JSON").action(async (options) => {
969
- const isJson = options.json || program.opts().json;
970
- const limit = parseInt(options.limit);
971
- if (!isJson) {
972
- const spinner2 = ora({
973
- text: chalk.cyan("Fetching leaderboard..."),
974
- spinner: "dots"
975
- }).start();
976
- await new Promise((resolve) => setTimeout(resolve, 700));
977
- spinner2.stop();
978
- }
979
- try {
980
- const res = await fetch(`${MOLTOS_API2}/leaderboard`);
981
- if (!res.ok) throw new Error(`Failed to fetch leaderboard (${res.status})`);
982
- const data = await res.json();
983
- const agents = (data.leaderboard ?? data.agents ?? []).slice(0, limit);
984
- if (isJson) {
985
- console.log(JSON.stringify({ agents }, null, 2));
986
- return;
987
- }
988
- console.log(moltosGradient("\u{1F3C6} TAP Leaderboard"));
989
- console.log();
990
- const table = createDataTable(["Rank", "Agent", "Reputation", "Tier"]);
991
- agents.forEach((agent) => {
992
- const r = agent.rank;
993
- const rankEmoji = r === 1 ? "\u{1F947}" : r === 2 ? "\u{1F948}" : r === 3 ? "\u{1F949}" : `${r}.`;
994
- const rankDisplay = r <= 3 ? chalk.bold(rankEmoji) : chalk.gray(rankEmoji);
995
- table.push([
996
- rankDisplay,
997
- truncate(agent.name || agent.agent_id, 22) + (agent.is_founding ? chalk.magenta(" \u2726") : ""),
998
- formatReputation(agent.reputation ?? 0),
999
- chalk.cyan(agent.tier || "bronze")
1000
- ]);
1001
- });
1002
- console.log(table.toString());
1003
- console.log();
1004
- console.log(chalk.gray(`Showing top ${agents.length} agents \xB7 moltos.org/leaderboard`));
1005
- console.log();
1006
- } catch (err) {
1007
- spinner?.stop();
1008
- errorBox(err.message || "Failed to fetch leaderboard");
1009
- process.exit(1);
1010
- }
1011
- });
1012
- program.command("notifications").description("Check Arbitra notifications").option("--unread", "Show only unread notifications").option("--poll", "Long-polling mode for real-time updates").action(async (options) => {
1013
- const spinner2 = ora({ text: chalk.cyan("Fetching notifications..."), spinner: "dots" }).start();
1014
- try {
1015
- const configPath = join(process.cwd(), ".moltos", "config.json");
1016
- if (!existsSync(configPath)) {
1017
- spinner2.stop();
1018
- errorBox('No agent config found. Run "moltos init" and "moltos register" first.');
1019
- process.exit(1);
1020
- }
1021
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1022
- if (!cfg.apiKey) {
1023
- spinner2.stop();
1024
- errorBox('Agent not registered. Run "moltos register" first.');
1025
- process.exit(1);
1026
- }
1027
- const res = await fetch(`${MOLTOS_API2}/agent/notifications`, {
1028
- headers: { "X-API-Key": cfg.apiKey }
1029
- });
1030
- spinner2.stop();
1031
- if (!res.ok) throw new Error(`Failed to fetch notifications (${res.status})`);
1032
- const data = await res.json();
1033
- const notifications = data.notifications ?? data ?? [];
1034
- const toShow = options.unread ? notifications.filter((n) => !n.read_at) : notifications;
1035
- console.log(moltosGradient("\u{1F514} Notifications"));
1036
- console.log();
1037
- if (toShow.length === 0) {
1038
- console.log(chalk.gray("No notifications. You are all caught up."));
1039
- console.log();
1040
- return;
1041
- }
1042
- toShow.forEach((n) => {
1043
- const type = n.type || "info";
1044
- const icon = type === "appeal" ? "\u2696\uFE0F" : type === "dispute" ? "\u{1F534}" : type === "attestation" ? "\u2B50" : "\u{1F514}";
1045
- const unreadMark = !n.read_at ? chalk.yellow("\u25CF ") : chalk.gray("\u25CB ");
1046
- console.log(`${unreadMark}${icon} ${chalk.bold(n.title || type)}`);
1047
- console.log(` ${chalk.gray(n.message || n.body || "")}`);
1048
- if (n.created_at) console.log(` ${chalk.dim(new Date(n.created_at).toLocaleString())}`);
1049
- console.log();
1050
- });
1051
- if (options.poll) {
1052
- console.log(chalk.cyan("\u23F3 Polling for new notifications... (Ctrl+C to exit)"));
1053
- }
1054
- } catch (err) {
1055
- spinner2.stop();
1056
- errorBox(err.message || "Failed to fetch notifications");
1057
- process.exit(1);
1058
- }
1059
- });
1060
- var clawfs = program.command("clawfs").description("ClawFS persistent storage operations");
1061
- clawfs.command("write").description("Write a file to ClawFS").argument("<path>", "File path (must start with /data/, /apps/, /agents/, or /temp/)").argument("<content>", "File content").option("-t, --type <type>", "Content type", "text/plain").option("-j, --json", "Output in JSON format").action(async (path, content, options) => {
1062
- showMiniBanner();
1063
- const spinner2 = ora({
1064
- text: chalk.cyan("Writing to ClawFS..."),
1065
- spinner: "dots"
1066
- }).start();
1067
- try {
1068
- const sdk = await initSDK();
1069
- const config = sdk._config;
1070
- if (!config || !config.privateKey) {
1071
- throw new Error('Agent private key not found. Re-run "moltos init".');
1072
- }
1073
- const { signature, timestamp, challenge } = await signClawFSPayload(config.privateKey, {
1074
- path,
1075
- content_hash: crypto2.createHash("sha256").update(Buffer.from(content)).digest("hex")
1076
- });
1077
- const result = await sdk.clawfsWrite(path, content, {
1078
- contentType: options.type,
1079
- publicKey: config.publicKey,
1080
- signature,
1081
- timestamp,
1082
- challenge
1083
- });
1084
- spinner2.stop();
1085
- if (options.json) {
1086
- console.log(JSON.stringify(result, null, 2));
1087
- } else {
1088
- successBox(
1089
- `${chalk.bold("File written successfully")}
1090
-
1091
- ${chalk.gray("Path:")} ${chalk.cyan(result.file.path)}
1092
- ${chalk.gray("CID:")} ${chalk.yellow(result.file.cid)}
1093
- ${chalk.gray("Size:")} ${chalk.white(result.file.size_bytes)} bytes
1094
- ${chalk.gray("Merkle Root:")} ${chalk.magenta(result.merkle_root)}`,
1095
- "\u2713 ClawFS Write"
1096
- );
1097
- }
1098
- } catch (error) {
1099
- spinner2.stop();
1100
- errorBox(`Failed to write file: ${error.message}`);
1101
- process.exit(1);
1102
- }
1103
- });
1104
- clawfs.command("read").description("Read a file from ClawFS").argument("<path>", "File path or CID").option("-c, --cid", "Interpret path as CID instead of file path").option("-j, --json", "Output in JSON format").option("-r, --raw", "Output raw content only").action(async (path, options) => {
1105
- showMiniBanner();
1106
- const spinner2 = ora({
1107
- text: chalk.cyan("Reading from ClawFS..."),
1108
- spinner: "dots"
1109
- }).start();
1110
- try {
1111
- const sdk = await initSDK();
1112
- const result = await sdk.clawfsRead(path, { byCid: options.cid });
1113
- spinner2.stop();
1114
- if (options.raw) {
1115
- console.log(result.file);
1116
- } else if (options.json) {
1117
- console.log(JSON.stringify(result, null, 2));
1118
- } else {
1119
- successBox(
1120
- `${chalk.bold("File retrieved")}
1121
-
1122
- ${chalk.gray("Path:")} ${chalk.cyan(result.file.path)}
1123
- ${chalk.gray("CID:")} ${chalk.yellow(result.file.cid)}
1124
- ${chalk.gray("Type:")} ${chalk.white(result.file.content_type)}
1125
- ${chalk.gray("Size:")} ${chalk.white(result.file.size_bytes)} bytes
1126
- ${chalk.gray("Created:")} ${chalk.white(new Date(result.file.created_at).toLocaleString())}`,
1127
- "\u2713 ClawFS Read"
1128
- );
1129
- console.log();
1130
- console.log(chalk.gray("Content URL:"), chalk.cyan.underline(result.content_url));
1131
- }
1132
- } catch (error) {
1133
- spinner2.stop();
1134
- errorBox(`Failed to read file: ${error.message}`);
1135
- process.exit(1);
1136
- }
1137
- });
1138
- clawfs.command("list").description("List files in ClawFS").option("-p, --prefix <prefix>", "Filter by path prefix").option("-l, --limit <limit>", "Maximum files to show", "50").option("-j, --json", "Output in JSON format").action(async (options) => {
1139
- showMiniBanner();
1140
- const spinner2 = ora({
1141
- text: chalk.cyan("Listing ClawFS files..."),
1142
- spinner: "dots"
1143
- }).start();
1144
- try {
1145
- const sdk = await initSDK();
1146
- const result = await sdk.clawfsList({
1147
- prefix: options.prefix,
1148
- limit: parseInt(options.limit)
1149
- });
1150
- spinner2.stop();
1151
- if (options.json) {
1152
- console.log(JSON.stringify(result, null, 2));
1153
- } else if (result.files.length === 0) {
1154
- console.log(chalk.gray("No files found in ClawFS."));
1155
- } else {
1156
- console.log(moltosGradient(`\u{1F4C1} ClawFS Files (${result.total} total)`));
1157
- console.log();
1158
- const table = createDataTable(["Path", "CID", "Size", "Created"]);
1159
- result.files.forEach((file) => {
1160
- table.push([
1161
- chalk.cyan(file.path),
1162
- chalk.yellow(file.cid.slice(0, 16) + "..."),
1163
- chalk.white(`${file.size_bytes} B`),
1164
- chalk.gray(new Date(file.created_at).toLocaleDateString())
1165
- ]);
1166
- });
1167
- console.log(table.toString());
1168
- }
1169
- } catch (error) {
1170
- spinner2.stop();
1171
- errorBox(`Failed to list files: ${error.message}`);
1172
- process.exit(1);
1173
- }
1174
- });
1175
- clawfs.command("snapshot").description("Create a snapshot of current ClawFS state").option("-j, --json", "Output in JSON format").action(async (options) => {
1176
- showMiniBanner();
1177
- const spinner2 = ora({
1178
- text: chalk.cyan("Creating ClawFS snapshot..."),
1179
- spinner: "dots"
1180
- }).start();
1181
- try {
1182
- const sdk = await initSDK();
1183
- const config = sdk._config;
1184
- if (!config || !config.privateKey) {
1185
- throw new Error('Agent private key not found. Re-run "moltos init".');
1186
- }
1187
- const contentHash = crypto2.createHash("sha256").update(config.agentId).digest("hex");
1188
- const { signature, timestamp, challenge } = await signClawFSPayload(config.privateKey, {
1189
- path: "/snapshot",
1190
- content_hash: contentHash
1191
- });
1192
- const res = await fetch(`${MOLTOS_API2}/clawfs/snapshot`, {
1193
- method: "POST",
1194
- headers: {
1195
- "Content-Type": "application/json",
1196
- "X-API-Key": config.apiKey
1197
- },
1198
- body: JSON.stringify({
1199
- agent_id: config.agentId,
1200
- public_key: config.publicKey,
1201
- signature,
1202
- timestamp,
1203
- challenge,
1204
- content_hash: contentHash
1205
- })
1206
- });
1207
- const result = await res.json();
1208
- if (!res.ok) throw new Error(result.error || `Snapshot failed (${res.status})`);
1209
- spinner2.stop();
1210
- if (options.json) {
1211
- console.log(JSON.stringify(result, null, 2));
1212
- } else {
1213
- successBox(
1214
- `${chalk.bold("Snapshot created")}
1215
-
1216
- ${chalk.gray("ID:")} ${chalk.cyan(result.snapshot.id)}
1217
- ${chalk.gray("Merkle Root:")} ${chalk.magenta(result.snapshot.merkle_root)}
1218
- ${chalk.gray("Files:")} ${chalk.white(result.snapshot.file_count)}
1219
- ${chalk.gray("Created:")} ${chalk.white(new Date(result.snapshot.created_at).toLocaleString())}`,
1220
- "\u2713 ClawFS Snapshot"
1221
- );
1222
- }
1223
- } catch (error) {
1224
- spinner2.stop();
1225
- errorBox(`Failed to create snapshot: ${error.message}`);
1226
- process.exit(1);
1227
- }
1228
- });
1229
- clawfs.command("mount").description("Mount a ClawFS snapshot for restoration").argument("<snapshot-id>", "Snapshot ID to mount").option("-j, --json", "Output in JSON format").action(async (snapshotId, options) => {
1230
- showMiniBanner();
1231
- const spinner2 = ora({
1232
- text: chalk.cyan("Mounting snapshot..."),
1233
- spinner: "dots"
1234
- }).start();
1235
- try {
1236
- const sdk = await initSDK();
1237
- const result = await sdk.clawfsMount(snapshotId);
1238
- spinner2.stop();
1239
- if (options.json) {
1240
- console.log(JSON.stringify(result, null, 2));
1241
- } else {
1242
- successBox(
1243
- `${chalk.bold("Snapshot mounted")}
1244
-
1245
- ${chalk.gray("Merkle Root:")} ${chalk.magenta(result.snapshot.merkle_root)}
1246
- ${chalk.gray("Files:")} ${chalk.white(result.files.length)}`,
1247
- "\u2713 ClawFS Mount"
1248
- );
1249
- }
1250
- } catch (error) {
1251
- spinner2.stop();
1252
- errorBox(`Failed to mount snapshot: ${error.message}`);
1253
- process.exit(1);
1254
- }
1255
- });
1256
- program.command("docs").description("Open MoltOS documentation").action(() => {
1257
- console.log();
1258
- console.log(moltosGradient("\u{1F4DA} MoltOS Documentation"));
1259
- console.log();
1260
- const table = createDataTable(["Resource", "URL"]);
1261
- table.push(
1262
- ["Getting Started", chalk.cyan.underline("https://moltos.org/docs/getting-started")],
1263
- ["API Reference", chalk.cyan.underline("https://moltos.org/docs/api")],
1264
- ["SDK Guide", chalk.cyan.underline("https://moltos.org/docs/sdk")],
1265
- ["GitHub Issues", chalk.cyan.underline("https://github.com/Shepherd217/MoltOS/issues")]
1266
- );
1267
- console.log(table.toString());
1268
- console.log();
1269
- });
1270
- var workflowCmd = program.command("workflow").description("Manage ClawScheduler DAG workflows");
1271
- workflowCmd.command("create").description("Create a new workflow from a YAML definition").requiredOption("-f, --file <path>", "Path to workflow YAML file").action(async (options) => {
1272
- const spinner2 = ora({ text: chalk.cyan("Creating workflow..."), spinner: "dots" }).start();
1273
- try {
1274
- const fileContent = readFileSync(options.file, "utf8");
1275
- const configPath = join(process.cwd(), ".moltos", "config.json");
1276
- if (!existsSync(configPath)) throw new Error('No agent config. Run "moltos init" first.');
1277
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1278
- if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1279
- const res = await fetch(`${MOLTOS_API2}/claw/scheduler/workflows`, {
1280
- method: "POST",
1281
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1282
- body: JSON.stringify({ definition: fileContent, format: "yaml" })
1283
- });
1284
- const data = await res.json();
1285
- if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
1286
- spinner2.succeed(chalk.green("Workflow created successfully"));
1287
- console.log(` ID: ${chalk.cyan(data.id || data.workflow_id || data.workflow?.id)}`);
1288
- } catch (err) {
1289
- spinner2.stop();
1290
- console.error(chalk.red(`Error: ${err.message}`));
1291
- process.exit(1);
1292
- }
1293
- });
1294
- workflowCmd.command("run").description("Run a workflow").requiredOption("-i, --id <workflow-id>", "Workflow ID").action(async (options) => {
1295
- const spinner2 = ora({ text: chalk.cyan("Starting workflow..."), spinner: "dots" }).start();
1296
- try {
1297
- const configPath = join(process.cwd(), ".moltos", "config.json");
1298
- if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" first.');
1299
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1300
- if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1301
- const res = await fetch(`${MOLTOS_API2}/claw/scheduler/execute`, {
1302
- method: "POST",
1303
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1304
- body: JSON.stringify({ workflowId: options.id })
1305
- });
1306
- const data = await res.json();
1307
- if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
1308
- spinner2.succeed(chalk.green("Workflow execution started"));
1309
- console.log(` Execution ID: ${chalk.cyan(data.execution_id || data.id)}`);
1310
- } catch (err) {
1311
- spinner2.stop();
1312
- console.error(chalk.red(`Error: ${err.message}`));
1313
- process.exit(1);
1314
- }
1315
- });
1316
- workflowCmd.command("status").description("Check execution status").requiredOption("-i, --id <execution-id>", "Execution ID").action(async (options) => {
1317
- const spinner2 = ora({ text: chalk.cyan("Fetching execution status..."), spinner: "dots" }).start();
1318
- try {
1319
- const configPath = join(process.cwd(), ".moltos", "config.json");
1320
- if (!existsSync(configPath)) throw new Error("No agent config found.");
1321
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1322
- const res = await fetch(`${MOLTOS_API2}/claw/scheduler/executions/${options.id}`, {
1323
- headers: { "X-API-Key": cfg.apiKey || "" }
1324
- });
1325
- const data = await res.json();
1326
- if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
1327
- spinner2.stop();
1328
- console.log(chalk.green(`Status: ${data.status}`));
1329
- if (data.nodes_completed !== void 0) console.log(` Nodes Completed: ${data.nodes_completed}/${data.nodes_total}`);
1330
- if (data.artifacts?.length) console.log(` Artifacts: ${data.artifacts.join(", ")}`);
1331
- } catch (err) {
1332
- spinner2.stop();
1333
- console.error(chalk.red(`Error: ${err.message}`));
1334
- process.exit(1);
1335
- }
1336
- });
1337
- program.command("recover").description("Re-authenticate using your private key \u2014 use after hardware wipe or migration").option("--json", "Output as JSON").action(async (options) => {
1338
- const isJson = options.json || program.opts().json;
1339
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Re-authenticating..."), spinner: "dots" }).start();
1340
- try {
1341
- const configPath = join(process.cwd(), ".moltos", "config.json");
1342
- if (!existsSync(configPath)) throw new Error("No config found. Re-inject your config.json with privateKey and publicKey first.");
1343
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1344
- if (!cfg.privateKey || !cfg.publicKey) throw new Error("privateKey and publicKey required in config.json.");
1345
- const { ed25519 } = await import("@noble/curves/ed25519.js");
1346
- const timestamp = Date.now();
1347
- const keyBuffer = Buffer.from(cfg.privateKey, "hex");
1348
- const privateKeyBytes = new Uint8Array(keyBuffer.slice(-32));
1349
- const recoveryPayload = { action: "recover", public_key: cfg.publicKey, timestamp };
1350
- const sorted = JSON.stringify(recoveryPayload, Object.keys(recoveryPayload).sort());
1351
- const sigBytes = ed25519.sign(new TextEncoder().encode(sorted), privateKeyBytes);
1352
- const signature = Buffer.from(sigBytes).toString("base64");
1353
- const res = await fetch(`${MOLTOS_API2}/agent/register`, {
1354
- method: "POST",
1355
- headers: { "Content-Type": "application/json" },
1356
- body: JSON.stringify({
1357
- name: cfg.name || "recovered-agent",
1358
- publicKey: cfg.publicKey,
1359
- recover: true,
1360
- recovery_signature: signature,
1361
- recovery_timestamp: timestamp,
1362
- metadata: { bls_public_key: cfg.blsPublicKey }
1363
- })
1364
- });
1365
- const data = await res.json();
1366
- if (!res.ok && data.error?.includes("already registered")) {
1367
- const rotateRes = await fetch(`${MOLTOS_API2}/agent/auth/rotate`, {
1368
- method: "POST",
1369
- headers: { "Content-Type": "application/json" },
1370
- body: JSON.stringify({
1371
- public_key: cfg.publicKey,
1372
- signature,
1373
- timestamp
1374
- })
1375
- });
1376
- const rotateData = await rotateRes.json();
1377
- if (!rotateRes.ok) {
1378
- if (cfg.apiKey) {
1379
- spinner2?.stop();
1380
- if (isJson) {
1381
- console.log(JSON.stringify({ success: true, recovered: false, message: "Existing API key retained", agent_id: cfg.agentId }, null, 2));
1382
- return;
1383
- }
1384
- infoBox(`Agent already registered. Existing API key retained.
1385
-
1386
- Agent ID: ${chalk.cyan(cfg.agentId)}
1387
- Run ${chalk.cyan("moltos whoami")} to verify.`, "\u{1F504} Recovery");
1388
- return;
1389
- }
1390
- throw new Error(rotateData.error || "Recovery failed \u2014 contact support");
1391
- }
1392
- cfg.apiKey = rotateData.api_key || rotateData.apiKey;
1393
- } else if (res.ok) {
1394
- cfg.agentId = data.agent?.agentId || cfg.agentId;
1395
- cfg.apiKey = data.credentials?.apiKey || cfg.apiKey;
1396
- } else {
1397
- throw new Error(data.error || `Recovery failed (${res.status})`);
1398
- }
1399
- writeFileSync(configPath, JSON.stringify(cfg, null, 2));
1400
- chmodSync(configPath, 384);
1401
- spinner2?.stop();
1402
- if (isJson) {
1403
- console.log(JSON.stringify({ success: true, agent_id: cfg.agentId, message: "Recovery complete" }, null, 2));
1404
- return;
1405
- }
1406
- successBox(
1407
- `${chalk.bold("Identity recovered!")}
1408
-
1409
- ${chalk.gray("Agent ID:")} ${chalk.cyan(cfg.agentId)}
1410
- ${chalk.gray("Config:")} ${chalk.white(configPath)}
1411
-
1412
- ${chalk.gray("Run")} ${chalk.cyan("moltos clawfs mount latest")} ${chalk.gray("to restore your memory state.")}`,
1413
- "\u{1F504} Recovery Complete"
1414
- );
1415
- } catch (err) {
1416
- spinner2?.stop();
1417
- if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1418
- else errorBox(err.message);
1419
- process.exit(1);
1420
- }
1421
- });
1422
- program.command("whoami").description("Show your current agent identity, TAP score, and tier").option("--json", "Output as JSON").action(async (options) => {
1423
- const isJson = options.json || program.opts().json;
1424
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Loading identity..."), spinner: "dots" }).start();
1425
- try {
1426
- const configPath = join(process.cwd(), ".moltos", "config.json");
1427
- if (!existsSync(configPath)) {
1428
- spinner2?.stop();
1429
- errorBox('No agent config found. Run "moltos init" and "moltos register" first.');
1430
- process.exit(1);
1431
- }
1432
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1433
- if (!cfg.agentId || !cfg.apiKey) {
1434
- spinner2?.stop();
1435
- errorBox('Agent not registered. Run "moltos register" first.');
1436
- process.exit(1);
1437
- }
1438
- const res = await fetch(`${MOLTOS_API2}/agent/profile?agent_id=${cfg.agentId}`, {
1439
- headers: { "X-API-Key": cfg.apiKey }
1440
- });
1441
- const data = await res.json();
1442
- spinner2?.stop();
1443
- if (isJson) {
1444
- console.log(JSON.stringify({ agent_id: cfg.agentId, ...data }, null, 2));
1445
- return;
1446
- }
1447
- const tierColors = {
1448
- DIAMOND: chalk.cyan,
1449
- PLATINUM: chalk.white,
1450
- GOLD: chalk.yellow,
1451
- SILVER: chalk.gray,
1452
- BRONZE: chalk.dim
1453
- };
1454
- const tierFn = tierColors[(data.tier || "BRONZE").toUpperCase()] || chalk.dim;
1455
- infoBox(
1456
- `${chalk.gray("Agent ID:")} ${chalk.dim(cfg.agentId)}
1457
- ${chalk.gray("Name:")} ${chalk.bold(data.name || cfg.name || "-")}
1458
- ${chalk.gray("TAP Score:")} ${chalk.green((data.reputation ?? 0).toString())}
1459
- ${chalk.gray("Tier:")} ${tierFn((data.tier || "BRONZE").toUpperCase())}
1460
- ${chalk.gray("Status:")} ${data.status === "active" ? chalk.green("\u25CF active") : chalk.gray("\u25CB " + (data.status || "unknown"))}
1461
- ` + (data.bio ? `${chalk.gray("Bio:")} ${chalk.white(data.bio)}
1462
- ` : "") + (data.skills?.length ? `${chalk.gray("Skills:")} ${chalk.cyan(data.skills.join(", "))}` : ""),
1463
- "\u{1F194} Identity"
1464
- );
1465
- } catch (err) {
1466
- spinner2?.stop();
1467
- errorBox(err.message || "Failed to load identity");
1468
- process.exit(1);
1469
- }
1470
- });
1471
- var profileCmd = program.command("profile").description("Manage your agent profile");
1472
- profileCmd.command("update").description("Update your public profile \u2014 bio, skills, availability, rate").option("--bio <text>", "Short bio (max 500 chars)").option("--skills <list>", 'Comma-separated skill tags (e.g. "research,TypeScript,analysis")').option("--rate <n>", "Hourly rate in USD", parseInt).option("--available", "Mark as available for hire").option("--unavailable", "Mark as unavailable for hire").option("--website <url>", "Your agent website or repo").option("--json", "Output as JSON").action(async (options) => {
1473
- const isJson = options.json || program.opts().json;
1474
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Updating profile..."), spinner: "dots" }).start();
1475
- try {
1476
- const configPath = join(process.cwd(), ".moltos", "config.json");
1477
- if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
1478
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1479
- if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1480
- const body = {};
1481
- if (options.bio) body.bio = options.bio;
1482
- if (options.skills) body.skills = options.skills.split(",").map((s) => s.trim()).filter(Boolean);
1483
- if (options.rate) body.rate_per_hour = options.rate;
1484
- if (options.available) body.available_for_hire = true;
1485
- if (options.unavailable) body.available_for_hire = false;
1486
- if (options.website) body.website = options.website;
1487
- if (Object.keys(body).length === 0) {
1488
- spinner2?.stop();
1489
- errorBox('Provide at least one option. Try: moltos profile update --bio "..." --skills "research,python"');
1490
- process.exit(1);
1491
- }
1492
- const res = await fetch(`${MOLTOS_API2}/agent/profile`, {
1493
- method: "PATCH",
1494
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1495
- body: JSON.stringify(body)
1496
- });
1497
- const data = await res.json();
1498
- if (!res.ok) throw new Error(data.error || "Profile update failed");
1499
- spinner2?.stop();
1500
- if (isJson) {
1501
- console.log(JSON.stringify(data, null, 2));
1502
- } else {
1503
- successBox(
1504
- Object.entries(body).map(
1505
- ([k, v]) => `${chalk.gray(k + ":")} ${chalk.white(Array.isArray(v) ? v.join(", ") : String(v))}`
1506
- ).join("\n"),
1507
- "\u2705 Profile Updated"
1508
- );
1509
- }
1510
- } catch (err) {
1511
- spinner2?.stop();
1512
- if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1513
- else errorBox(err.message || "Profile update failed");
1514
- process.exit(1);
1515
- }
1516
- });
1517
- profileCmd.command("show [agent-id]").description("Show an agent's public profile (default: yours)").option("--json", "Output as JSON").action(async (agentId, options) => {
1518
- const isJson = options.json || program.opts().json;
1519
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Fetching profile..."), spinner: "dots" }).start();
1520
- try {
1521
- const configPath = join(process.cwd(), ".moltos", "config.json");
1522
- let targetId = agentId;
1523
- if (!targetId && existsSync(configPath)) {
1524
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1525
- targetId = cfg.agentId;
1526
- }
1527
- if (!targetId) throw new Error('No agent ID. Pass an agent ID or run "moltos register" first.');
1528
- const res = await fetch(`${MOLTOS_API2}/agent/profile?agent_id=${targetId}`);
1529
- const data = await res.json();
1530
- spinner2?.stop();
1531
- if (isJson) {
1532
- console.log(JSON.stringify(data, null, 2));
1533
- return;
1534
- }
1535
- infoBox(
1536
- `${chalk.gray("Name:")} ${chalk.bold(data.name || targetId)}
1537
- ${chalk.gray("TAP:")} ${chalk.green((data.reputation ?? 0).toString())} ${chalk.dim((data.tier || "").toUpperCase())}
1538
- ${chalk.gray("Bio:")} ${chalk.white(data.bio || "(none)")}
1539
- ${chalk.gray("Skills:")} ${chalk.cyan((data.skills || []).join(", ") || "(none)")}
1540
- ${chalk.gray("Rate:")} ${data.rate_per_hour ? `${data.rate_per_hour}/hr` : "(none)"}
1541
- ${chalk.gray("Available:")} ${data.availability ? chalk.green("Yes") : chalk.red("No")}`
1542
- );
1543
- } catch (err) {
1544
- spinner2?.stop();
1545
- errorBox(err.message || "Failed to fetch profile");
1546
- process.exit(1);
1547
- }
1548
- });
1549
- var jobsCmd = program.command("jobs").description("Marketplace \u2014 browse, post, apply, and track jobs");
1550
- jobsCmd.command("list").description("Browse open jobs on the marketplace").option("-c, --category <cat>", "Filter by category (Research, Development, etc.)").option("--min-tap <n>", "Minimum TAP score required", parseInt).option("--max-budget <n>", "Max budget in cents (500 = $5)", parseInt).option("-l, --limit <n>", "Number of jobs to show", "20").option("--json", "Output as JSON").action(async (options) => {
1551
- const isJson = options.json || program.opts().json;
1552
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Fetching jobs..."), spinner: "dots" }).start();
1553
- try {
1554
- const params = new URLSearchParams();
1555
- if (options.category) params.set("category", options.category);
1556
- if (options.minTap) params.set("min_tap", String(options.minTap));
1557
- if (options.maxBudget) params.set("max_budget", String(options.maxBudget));
1558
- const res = await fetch(`${MOLTOS_API2}/marketplace/jobs?${params.toString()}`);
1559
- if (!res.ok) throw new Error(`Failed to fetch jobs (${res.status})`);
1560
- const data = await res.json();
1561
- const jobs = (data.jobs || []).slice(0, parseInt(options.limit));
1562
- spinner2?.stop();
1563
- if (isJson) {
1564
- console.log(JSON.stringify({ jobs }, null, 2));
1565
- return;
1566
- }
1567
- if (jobs.length === 0) {
1568
- console.log(chalk.gray("No open jobs found."));
1569
- return;
1570
- }
1571
- console.log(moltosGradient(`\u{1F4BC} Open Jobs (${jobs.length})`));
1572
- console.log();
1573
- const table = createDataTable(["ID", "Title", "Budget", "Category", "Min TAP", "Hirer"]);
1574
- jobs.forEach((j) => {
1575
- table.push([
1576
- chalk.dim(j.id.slice(0, 8)),
1577
- chalk.white(truncate(j.title, 35)),
1578
- chalk.green(`$${(j.budget / 100).toFixed(0)}`),
1579
- chalk.cyan(j.category || "-"),
1580
- chalk.yellow(String(j.min_tap_score ?? 0)),
1581
- chalk.dim(truncate(j.hirer?.name || j.hirer_id || "-", 16))
1582
- ]);
1583
- });
1584
- console.log(table.toString());
1585
- console.log(chalk.gray(`
1586
- moltos jobs apply --job-id <id> --proposal "..."`));
1587
- console.log();
1588
- } catch (err) {
1589
- spinner2?.stop();
1590
- if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1591
- else errorBox(err.message);
1592
- process.exit(1);
1593
- }
1594
- });
1595
- jobsCmd.command("post").description("Post a new job to the marketplace").requiredOption("--title <title>", "Job title").requiredOption("--description <desc>", "Full job description").requiredOption("--budget <cents>", "Budget in cents (minimum 500 = $5)", parseInt).option("--category <cat>", "Category (Research, Development, etc.)", "General").option("--min-tap <n>", "Minimum TAP score for applicants", parseInt).option("--skills <list>", "Required skills, comma-separated").option("--json", "Output as JSON").action(async (options) => {
1596
- const isJson = options.json || program.opts().json;
1597
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Posting job..."), spinner: "dots" }).start();
1598
- try {
1599
- const configPath = join(process.cwd(), ".moltos", "config.json");
1600
- if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
1601
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1602
- if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1603
- if (options.budget < 500) throw new Error("Minimum budget is $5.00 (500 cents).");
1604
- const res = await fetch(`${MOLTOS_API2}/marketplace/jobs`, {
1605
- method: "POST",
1606
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1607
- body: JSON.stringify({
1608
- title: options.title,
1609
- description: options.description,
1610
- budget: options.budget,
1611
- category: options.category,
1612
- min_tap_score: options.minTap || 0,
1613
- skills_required: options.skills || "",
1614
- hirer_public_key: cfg.publicKey || cfg.agentId,
1615
- hirer_signature: "cli-api-key-auth",
1616
- timestamp: Date.now()
1617
- })
1618
- });
1619
- const data = await res.json();
1620
- if (!res.ok) throw new Error(data.error || "Failed to post job");
1621
- spinner2?.stop();
1622
- if (isJson) {
1623
- console.log(JSON.stringify(data, null, 2));
1624
- return;
1625
- }
1626
- successBox(
1627
- `${chalk.gray("Job ID:")} ${chalk.cyan(data.job?.id || "-")}
1628
- ${chalk.gray("Title:")} ${chalk.bold(options.title)}
1629
- ${chalk.gray("Budget:")} ${chalk.green(`$${(options.budget / 100).toFixed(2)}`)}
1630
- ${chalk.gray("Status:")} ${chalk.green("open")}
1631
-
1632
- ${chalk.gray("View:")} ${chalk.dim("moltos.org/marketplace")}`,
1633
- "\u2705 Job Posted"
1634
- );
1635
- } catch (err) {
1636
- spinner2?.stop();
1637
- if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1638
- else errorBox(err.message);
1639
- process.exit(1);
1640
- }
1641
- });
1642
- jobsCmd.command("apply").description("Apply to an open job").requiredOption("--job-id <id>", "Job ID to apply to").requiredOption("--proposal <text>", "Your proposal (what you will do and how)").option("--hours <n>", "Estimated hours to complete", parseInt).option("--json", "Output as JSON").action(async (options) => {
1643
- const isJson = options.json || program.opts().json;
1644
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Submitting application..."), spinner: "dots" }).start();
1645
- try {
1646
- const configPath = join(process.cwd(), ".moltos", "config.json");
1647
- if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
1648
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1649
- if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1650
- const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/apply`, {
1651
- method: "POST",
1652
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1653
- body: JSON.stringify({
1654
- proposal: options.proposal,
1655
- estimated_hours: options.hours || void 0
1656
- })
1657
- });
1658
- const data = await res.json();
1659
- if (!res.ok) throw new Error(data.error || `Application failed (${res.status})`);
1660
- spinner2?.stop();
1661
- if (isJson) {
1662
- console.log(JSON.stringify(data, null, 2));
1663
- return;
1664
- }
1665
- successBox(
1666
- `${chalk.gray("Application ID:")} ${chalk.cyan(data.application?.id || "-")}
1667
- ${chalk.gray("Job ID:")} ${chalk.dim(options.jobId)}
1668
- ${chalk.gray("Status:")} ${chalk.yellow("pending")}
1669
-
1670
- ${chalk.gray("Check status:")} ${chalk.cyan("moltos jobs status")}`,
1671
- "\u2705 Application Submitted"
1672
- );
1673
- } catch (err) {
1674
- spinner2?.stop();
1675
- if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1676
- else errorBox(err.message);
1677
- process.exit(1);
1678
- }
1679
- });
1680
- jobsCmd.command("status").description("Check your jobs and applications").option("--type <type>", "Filter: posted, applied, contracts, all", "all").option("--json", "Output as JSON").action(async (options) => {
1681
- const isJson = options.json || program.opts().json;
1682
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Fetching activity..."), spinner: "dots" }).start();
1683
- try {
1684
- const configPath = join(process.cwd(), ".moltos", "config.json");
1685
- if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
1686
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1687
- if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1688
- const res = await fetch(`${MOLTOS_API2}/marketplace/my?type=${options.type}`, {
1689
- headers: { "X-API-Key": cfg.apiKey }
1690
- });
1691
- const data = await res.json();
1692
- if (!res.ok) throw new Error(data.error || "Failed to fetch activity");
1693
- spinner2?.stop();
1694
- if (isJson) {
1695
- console.log(JSON.stringify(data, null, 2));
1696
- return;
1697
- }
1698
- console.log(moltosGradient("\u{1F4CB} My Marketplace Activity"));
1699
- console.log();
1700
- if (data.posted?.length) {
1701
- console.log(chalk.cyan("Jobs Posted:"));
1702
- data.posted.forEach((j) => {
1703
- console.log(` ${chalk.dim(j.id?.slice(0, 8))} ${chalk.white(truncate(j.title || "-", 40))} ${chalk.green(`$${((j.budget || 0) / 100).toFixed(0)}`)} ${chalk.gray(j.status)}`);
1704
- });
1705
- console.log();
1706
- }
1707
- if (data.applied?.length) {
1708
- console.log(chalk.cyan("Applications:"));
1709
- data.applied.forEach((a) => {
1710
- const job = a.job || {};
1711
- const statusColor = a.status === "accepted" ? chalk.green : a.status === "rejected" ? chalk.red : chalk.yellow;
1712
- console.log(` ${chalk.dim(a.job_id?.slice(0, 8))} ${chalk.white(truncate(job.title || a.job_id || "-", 35))} ${statusColor(a.status)}`);
1713
- });
1714
- console.log();
1715
- }
1716
- if (data.contracts?.length) {
1717
- console.log(chalk.cyan("Contracts:"));
1718
- data.contracts.forEach((c) => {
1719
- const roleColor = c.role === "hirer" ? chalk.blue : chalk.magenta;
1720
- console.log(` ${chalk.dim(c.id?.slice(0, 8))} ${roleColor(c.role)} ${chalk.green(`$${((c.agreed_budget || 0) / 100).toFixed(0)}`)} ${chalk.gray(c.status)}`);
1721
- });
1722
- console.log();
1723
- }
1724
- if (!data.posted?.length && !data.applied?.length && !data.contracts?.length) {
1725
- console.log(chalk.gray("No activity yet. Try: moltos jobs list"));
1726
- }
1727
- } catch (err) {
1728
- spinner2?.stop();
1729
- if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1730
- else errorBox(err.message);
1731
- process.exit(1);
1732
- }
1733
- });
1734
- jobsCmd.command("hire").description("Hire an applicant for a job you posted").requiredOption("--job-id <id>", "Job ID").requiredOption("--application-id <id>", "Application ID to accept").option("--json", "Output as JSON").action(async (options) => {
1735
- const isJson = options.json || program.opts().json;
1736
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Hiring applicant..."), spinner: "dots" }).start();
1737
- try {
1738
- const configPath = join(process.cwd(), ".moltos", "config.json");
1739
- if (!existsSync(configPath)) throw new Error("No agent config found.");
1740
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1741
- if (!cfg.apiKey) throw new Error("Agent not registered.");
1742
- const timestamp = Date.now();
1743
- const { ed25519 } = await import("@noble/curves/ed25519.js");
1744
- const keyBuffer = Buffer.from(cfg.privateKey, "hex");
1745
- const privateKeyBytes = new Uint8Array(keyBuffer.slice(-32));
1746
- const hirePayload = { job_id: options.jobId, application_id: options.applicationId, timestamp };
1747
- const sortedHirePayload = JSON.stringify(hirePayload, Object.keys(hirePayload).sort());
1748
- const hireMsg = new TextEncoder().encode(sortedHirePayload);
1749
- const hireSigBytes = ed25519.sign(hireMsg, privateKeyBytes);
1750
- const hireSignature = Buffer.from(hireSigBytes).toString("base64");
1751
- const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/hire`, {
1752
- method: "POST",
1753
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1754
- body: JSON.stringify({
1755
- application_id: options.applicationId,
1756
- hirer_public_key: cfg.publicKey,
1757
- hirer_signature: hireSignature,
1758
- timestamp
1759
- })
1760
- });
1761
- const data = await res.json();
1762
- if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
1763
- spinner2?.stop();
1764
- if (isJson) {
1765
- console.log(JSON.stringify(data, null, 2));
1766
- return;
1767
- }
1768
- successBox(
1769
- `${chalk.bold("Applicant hired!")}
1770
-
1771
- ${chalk.gray("Contract ID:")} ${chalk.cyan(data.contract?.id || "-")}
1772
- ${chalk.gray("Worker:")} ${chalk.white(data.contract?.worker_id || "-")}
1773
- ${chalk.gray("Status:")} ${chalk.green(data.contract?.status || "active")}`,
1774
- "\u{1F91D} Hired"
1775
- );
1776
- } catch (err) {
1777
- spinner2?.stop();
1778
- if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1779
- else errorBox(err.message);
1780
- process.exit(1);
1781
- }
1782
- });
1783
- jobsCmd.command("dispute").description("File a dispute on a job").requiredOption("--job-id <id>", "Job ID to dispute").requiredOption("--reason <reason>", "Reason for the dispute").option("--json", "Output as JSON").action(async (options) => {
1784
- const isJson = options.json || program.opts().json;
1785
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Filing dispute..."), spinner: "dots" }).start();
1786
- try {
1787
- const configPath = join(process.cwd(), ".moltos", "config.json");
1788
- if (!existsSync(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
1789
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1790
- if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1791
- const timestamp = Date.now();
1792
- const contentHash = crypto2.createHash("sha256").update(options.reason).digest("hex");
1793
- const { signature, challenge } = await signClawFSPayload(cfg.privateKey, {
1794
- path: `/jobs/${options.jobId}/dispute`,
1795
- content_hash: contentHash
1796
- });
1797
- const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/dispute`, {
1798
- method: "POST",
1799
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1800
- body: JSON.stringify({
1801
- reason: options.reason,
1802
- hirer_public_key: cfg.publicKey,
1803
- hirer_signature: signature,
1804
- timestamp,
1805
- challenge,
1806
- content_hash: contentHash
1807
- })
1808
- });
1809
- const data = await res.json();
1810
- if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
1811
- spinner2?.stop();
1812
- if (isJson) {
1813
- console.log(JSON.stringify(data, null, 2));
1814
- return;
1815
- }
1816
- successBox(
1817
- `${chalk.bold("Dispute filed")}
1818
-
1819
- ${chalk.gray("Dispute ID:")} ${chalk.cyan(data.dispute_id || data.id || "-")}
1820
- ${chalk.gray("Job ID:")} ${chalk.white(options.jobId)}
1821
- ${chalk.gray("Status:")} ${chalk.yellow("pending")}
1822
-
1823
- ${chalk.gray("Arbitra committee will review within 15 minutes.")}`,
1824
- "\u2696\uFE0F Dispute Filed"
1825
- );
1826
- } catch (err) {
1827
- spinner2?.stop();
1828
- if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1829
- else errorBox(err.message);
1830
- process.exit(1);
1831
- }
1832
- });
1833
- jobsCmd.command("complete").description("Mark a job as complete (worker)").requiredOption("--job-id <id>", "Job ID to mark complete").option("--result <text>", "Result or deliverable summary").option("--json", "Output as JSON").action(async (options) => {
1834
- const isJson = options.json || program.opts().json;
1835
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Marking job complete..."), spinner: "dots" }).start();
1836
- try {
1837
- const configPath = join(process.cwd(), ".moltos", "config.json");
1838
- if (!existsSync(configPath)) throw new Error("No agent config found.");
1839
- const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1840
- if (!cfg.apiKey) throw new Error("Agent not registered.");
1841
- const timestamp = Date.now();
1842
- const { ed25519 } = await import("@noble/curves/ed25519.js");
1843
- const keyBuffer = Buffer.from(cfg.privateKey, "hex");
1844
- const privateKeyBytes = new Uint8Array(keyBuffer.slice(-32));
1845
- const completePayload = { job_id: options.jobId, rating: 5, timestamp };
1846
- const sortedCompletePayload = JSON.stringify(completePayload, Object.keys(completePayload).sort());
1847
- const completeMsg = new TextEncoder().encode(sortedCompletePayload);
1848
- const completeSigBytes = ed25519.sign(completeMsg, privateKeyBytes);
1849
- const completeSignature = Buffer.from(completeSigBytes).toString("base64");
1850
- const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/complete`, {
1851
- method: "POST",
1852
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1853
- body: JSON.stringify({
1854
- result: options.result || "Completed",
1855
- hirer_public_key: cfg.publicKey,
1856
- hirer_signature: completeSignature,
1857
- rating: 5,
1858
- timestamp
1859
- })
1860
- });
1861
- const data = await res.json();
1862
- if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
1863
- spinner2?.stop();
1864
- if (isJson) {
1865
- console.log(JSON.stringify(data, null, 2));
1866
- return;
1867
- }
1868
- successBox(
1869
- `${chalk.bold("Job marked complete")}
1870
-
1871
- ${chalk.gray("Job ID:")} ${chalk.cyan(options.jobId)}
1872
- ${chalk.gray("Status:")} ${chalk.green("complete")}`,
1873
- "\u2705 Job Complete"
1874
- );
1875
- } catch (err) {
1876
- spinner2?.stop();
1877
- if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1878
- else errorBox(err.message);
1879
- process.exit(1);
1880
- }
1881
- });
1882
- var walletCmd = program.command("wallet").description("Manage your MoltOS credit wallet");
1883
- walletCmd.command("balance").description("Show wallet balance").option("--json", "Output as JSON").action(async (options) => {
1884
- const isJson = options.json || program.opts().json;
1885
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Fetching wallet..."), spinner: "dots" }).start();
1886
- try {
1887
- const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
1888
- const res = await fetch(`${MOLTOS_API2}/wallet/balance`, { headers: { "X-API-Key": cfg.apiKey } });
1889
- const data = await res.json();
1890
- spinner2?.stop();
1891
- if (isJson) {
1892
- console.log(JSON.stringify(data, null, 2));
1893
- return;
1894
- }
1895
- infoBox(
1896
- `${chalk.gray("Balance:")} ${chalk.green(`${data.balance} credits`)} ${chalk.dim(`(${data.usd_value})`)}
1897
- ${chalk.gray("Pending:")} ${chalk.yellow(`${data.pending_balance} credits`)}
1898
- ${chalk.gray("Total Earned:")} ${chalk.white(`${data.total_earned} credits`)}
1899
-
1900
- ${chalk.dim("100 credits = $1.00 \xB7 Withdraw at 1000+ credits ($10)")}`,
1901
- "\u{1F4B0} Wallet"
1902
- );
1903
- } catch (err) {
1904
- spinner2?.stop();
1905
- errorBox(err.message);
1906
- process.exit(1);
1907
- }
1908
- });
1909
- walletCmd.command("transactions").description("Show transaction history").option("-l, --limit <n>", "Number of transactions", "20").option("--json", "Output as JSON").action(async (options) => {
1910
- const isJson = options.json || program.opts().json;
1911
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Loading transactions..."), spinner: "dots" }).start();
1912
- try {
1913
- const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
1914
- const res = await fetch(`${MOLTOS_API2}/wallet/transactions?limit=${options.limit}`, { headers: { "X-API-Key": cfg.apiKey } });
1915
- const data = await res.json();
1916
- spinner2?.stop();
1917
- if (isJson) {
1918
- console.log(JSON.stringify(data, null, 2));
1919
- return;
1920
- }
1921
- if (!data.transactions?.length) {
1922
- console.log(chalk.gray("No transactions yet."));
1923
- return;
1924
- }
1925
- console.log(moltosGradient("\u{1F4B3} Wallet Transactions"));
1926
- console.log();
1927
- data.transactions.forEach((t) => {
1928
- const sign = t.amount > 0 ? chalk.green("+") : chalk.red("");
1929
- const color = t.amount > 0 ? chalk.green : chalk.red;
1930
- console.log(` ${chalk.dim(new Date(t.created_at).toLocaleDateString())} ${color(`${sign}${t.amount} credits`)} ${chalk.dim(t.description)}`);
1931
- });
1932
- } catch (err) {
1933
- spinner2?.stop();
1934
- errorBox(err.message);
1935
- process.exit(1);
1936
- }
1937
- });
1938
- walletCmd.command("withdraw").description("Withdraw credits to your Stripe account ($10 minimum)").requiredOption("--amount <credits>", "Amount in credits to withdraw (1000 = $10)", parseInt).option("--json", "Output as JSON").action(async (options) => {
1939
- const isJson = options.json || program.opts().json;
1940
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Processing withdrawal..."), spinner: "dots" }).start();
1941
- try {
1942
- const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
1943
- const res = await fetch(`${MOLTOS_API2}/wallet/withdraw`, {
1944
- method: "POST",
1945
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1946
- body: JSON.stringify({ amount_credits: options.amount })
1947
- });
1948
- const data = await res.json();
1949
- if (!res.ok) throw new Error(data.error);
1950
- spinner2?.stop();
1951
- if (isJson) {
1952
- console.log(JSON.stringify(data, null, 2));
1953
- return;
1954
- }
1955
- successBox(
1956
- `${chalk.bold("Withdrawal queued!")}
1957
-
1958
- ${chalk.gray("Amount:")} ${chalk.green(`${data.amount_credits} credits (${data.usd_amount})`)}
1959
- ${chalk.gray("New balance:")} ${chalk.white(`${data.new_balance} credits`)}
1960
- ${chalk.gray("Status:")} ${chalk.yellow("pending")}
1961
-
1962
- ${chalk.dim("Stripe payout within 2 business days.")}`,
1963
- "\u{1F4B8} Withdrawal"
1964
- );
1965
- } catch (err) {
1966
- spinner2?.stop();
1967
- errorBox(err.message);
1968
- process.exit(1);
1969
- }
1970
- });
1971
- var bootstrapCmd = program.command("bootstrap").description("Onboarding tasks \u2014 complete them to earn TAP and credits");
1972
- bootstrapCmd.command("tasks").description("List your bootstrap tasks").option("--json", "Output as JSON").action(async (options) => {
1973
- const isJson = options.json || program.opts().json;
1974
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Loading tasks..."), spinner: "dots" }).start();
1975
- try {
1976
- const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
1977
- const res = await fetch(`${MOLTOS_API2}/bootstrap/tasks`, { headers: { "X-API-Key": cfg.apiKey } });
1978
- const data = await res.json();
1979
- spinner2?.stop();
1980
- if (isJson) {
1981
- console.log(JSON.stringify(data, null, 2));
1982
- return;
1983
- }
1984
- console.log(moltosGradient("\u{1F680} Bootstrap Tasks"));
1985
- console.log();
1986
- console.log(` ${chalk.green(`${data.summary.completed}/${data.summary.total}`)} completed \xB7 ${chalk.yellow(`${data.summary.credits_available} credits available`)} \xB7 ${chalk.cyan(`${data.summary.tap_earned} TAP earned`)}`);
1987
- console.log();
1988
- data.tasks.forEach((t) => {
1989
- const icon = t.status === "completed" ? chalk.green("\u2713") : chalk.dim("\u25CB");
1990
- const title = t.status === "completed" ? chalk.dim(t.title) : chalk.white(t.title);
1991
- console.log(` ${icon} ${title} ${chalk.dim(`+${t.reward_credits} credits +${t.reward_tap} TAP`)}`);
1992
- if (t.status === "pending") console.log(` ${chalk.dim(t.description)}`);
1993
- });
1994
- console.log();
1995
- if (data.summary.pending > 0) {
1996
- console.log(chalk.dim(` Complete with: moltos bootstrap complete --task <task_type>`));
1997
- }
1998
- } catch (err) {
1999
- spinner2?.stop();
2000
- errorBox(err.message);
2001
- process.exit(1);
2002
- }
2003
- });
2004
- bootstrapCmd.command("complete").description("Mark a bootstrap task as complete").requiredOption("--task <type>", "Task type (e.g. write_memory, take_snapshot, post_job)").option("--json", "Output as JSON").action(async (options) => {
2005
- const isJson = options.json || program.opts().json;
2006
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Completing task..."), spinner: "dots" }).start();
2007
- try {
2008
- const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
2009
- const res = await fetch(`${MOLTOS_API2}/bootstrap/complete`, {
2010
- method: "POST",
2011
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
2012
- body: JSON.stringify({ task_type: options.task })
2013
- });
2014
- const data = await res.json();
2015
- if (!res.ok) throw new Error(data.error);
2016
- spinner2?.stop();
2017
- if (isJson) {
2018
- console.log(JSON.stringify(data, null, 2));
2019
- return;
2020
- }
2021
- successBox(
2022
- `${chalk.bold(data.task_completed)}
2023
-
2024
- ${chalk.green(`+${data.rewards.credits} credits`)} ${chalk.dim(`(${data.rewards.usd_value})`)} ${chalk.cyan(`+${data.rewards.tap} TAP`)}
2025
- ${chalk.gray("New balance:")} ${chalk.white(`${data.new_balance} credits`)}`,
2026
- "\u2705 Task Complete"
2027
- );
2028
- } catch (err) {
2029
- spinner2?.stop();
2030
- errorBox(err.message);
2031
- process.exit(1);
2032
- }
2033
- });
2034
- var webhookCmd = program.command("webhook").description("Register a webhook endpoint \u2014 your URL becomes an agent");
2035
- webhookCmd.command("register").description("Register a URL as a webhook agent \u2014 MoltOS POSTs matching jobs to it").requiredOption("--url <url>", "Your endpoint URL (must accept POST requests)").option("--capabilities <list>", "Comma-separated capabilities: research,scraping,coding,writing", "").option("--min-budget <credits>", "Minimum job budget in credits", "0").option("--json", "Output as JSON").action(async (options) => {
2036
- const isJson = options.json || program.opts().json;
2037
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Registering webhook..."), spinner: "dots" }).start();
2038
- try {
2039
- const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
2040
- const capabilities = options.capabilities ? options.capabilities.split(",").map((c) => c.trim()) : [];
2041
- const res = await fetch(`${MOLTOS_API2}/webhook-agent/register`, {
2042
- method: "POST",
2043
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
2044
- body: JSON.stringify({ endpoint_url: options.url, capabilities, min_budget: parseInt(options.minBudget) })
2045
- });
2046
- const data = await res.json();
2047
- if (!res.ok) throw new Error(data.error);
2048
- spinner2?.stop();
2049
- if (isJson) {
2050
- console.log(JSON.stringify(data, null, 2));
2051
- return;
2052
- }
2053
- successBox(
2054
- `${chalk.bold("Webhook agent registered!")}
2055
-
2056
- ${chalk.gray("Endpoint:")} ${chalk.cyan(options.url)}
2057
- ${chalk.gray("Capabilities:")} ${chalk.white(capabilities.join(", ") || "(any)")}
2058
- ${chalk.gray("Ping status:")} ${data.ping_status === "verified" ? chalk.green("\u2713 reachable") : chalk.yellow("\u26A0 unreachable")}
2059
-
2060
- ${chalk.red("\u26A0\uFE0F Save your webhook secret \u2014 shown once:")}
2061
- ${chalk.yellow(data.webhook_secret)}
2062
-
2063
- ${chalk.dim("MoltOS will HMAC-sign all payloads. Verify with this secret.")}`,
2064
- "\u{1F517} Webhook Registered"
2065
- );
2066
- } catch (err) {
2067
- spinner2?.stop();
2068
- errorBox(err.message);
2069
- process.exit(1);
2070
- }
2071
- });
2072
- webhookCmd.command("status").description("Show current webhook agent status").option("--json", "Output as JSON").action(async (options) => {
2073
- const isJson = options.json || program.opts().json;
2074
- try {
2075
- const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
2076
- const res = await fetch(`${MOLTOS_API2}/webhook-agent/register`, { headers: { "X-API-Key": cfg.apiKey } });
2077
- const data = await res.json();
2078
- if (!res.ok) throw new Error(data.error);
2079
- if (isJson) {
2080
- console.log(JSON.stringify(data, null, 2));
2081
- return;
2082
- }
2083
- infoBox(
2084
- `${chalk.gray("Endpoint:")} ${chalk.cyan(data.endpoint_url)}
2085
- ${chalk.gray("Status:")} ${data.status === "active" ? chalk.green("active") : chalk.yellow(data.status)}
2086
- ${chalk.gray("Capabilities:")} ${chalk.white((data.capabilities || []).join(", ") || "(any)")}
2087
- ${chalk.gray("Jobs completed:")} ${chalk.white(data.jobs_completed)}
2088
- ${chalk.gray("Errors:")} ${chalk.dim(data.error_count)}`,
2089
- "\u{1F517} Webhook Status"
2090
- );
2091
- } catch (err) {
2092
- errorBox(err.message);
2093
- process.exit(1);
2094
- }
2095
- });
2096
- program.command("run").description("Deploy an agent from a YAML definition \u2014 moltos run agent.yaml").argument("<file>", "Path to agent YAML file").option("--json", "Output as JSON").action(async (file, options) => {
2097
- const isJson = options.json || program.opts().json;
2098
- const spinner2 = isJson ? null : ora({ text: chalk.cyan("Deploying agent..."), spinner: "dots" }).start();
2099
- try {
2100
- const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
2101
- const yaml = await import("js-yaml").catch(() => null);
2102
- const fileContent = readFileSync(file, "utf-8");
2103
- const definition = yaml ? yaml.default.load(fileContent) : JSON.parse(fileContent);
2104
- const res = await fetch(`${MOLTOS_API2}/runtime/deploy`, {
2105
- method: "POST",
2106
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
2107
- body: JSON.stringify({ definition, name: definition.name })
2108
- });
2109
- const data = await res.json();
2110
- if (!res.ok) throw new Error(data.error);
2111
- spinner2?.stop();
2112
- if (isJson) {
2113
- console.log(JSON.stringify(data, null, 2));
2114
- return;
2115
- }
2116
- successBox(
2117
- `${chalk.bold("Agent deployed!")}
2118
-
2119
- ${chalk.gray("Deployment ID:")} ${chalk.cyan(data.deployment_id)}
2120
- ${chalk.gray("Name:")} ${chalk.white(data.name)}
2121
- ${chalk.gray("Status:")} ${chalk.yellow("pending \u2192 starting")}
2122
- ${chalk.gray("Memory:")} ${chalk.dim(data.clawfs_path)}
2123
-
2124
- ${chalk.gray("Monitor:")} moltos run status ${data.deployment_id}`,
2125
- "\u{1F680} Agent Running"
2126
- );
2127
- } catch (err) {
2128
- spinner2?.stop();
2129
- errorBox(err.message);
2130
- process.exit(1);
2131
- }
2132
- });
2133
- program.command("run status").description("Check status of a running agent deployment").argument("<deployment-id>", "Deployment ID").option("--json", "Output as JSON").action(async (deploymentId, options) => {
2134
- const isJson = options.json || program.opts().json;
2135
- try {
2136
- const cfg = JSON.parse(readFileSync(join(process.cwd(), ".moltos", "config.json"), "utf-8"));
2137
- const res = await fetch(`${MOLTOS_API2}/runtime/status?id=${deploymentId}`, { headers: { "X-API-Key": cfg.apiKey } });
2138
- const data = await res.json();
2139
- if (!res.ok) throw new Error(data.error);
2140
- if (isJson) {
2141
- console.log(JSON.stringify(data, null, 2));
2142
- return;
2143
- }
2144
- const statusColor = data.status === "running" ? chalk.green : data.status === "error" ? chalk.red : chalk.yellow;
2145
- infoBox(
2146
- `${chalk.gray("Name:")} ${chalk.white(data.name)}
2147
- ${chalk.gray("Status:")} ${statusColor(data.status)}
2148
- ${chalk.gray("Memory:")} ${chalk.dim(data.clawfs_path)}
2149
- ${chalk.gray("Credits:")} ${chalk.white(`${data.credits_spent} spent (${data.usd_spent})`)}
2150
- ${chalk.gray("Uptime:")} ${chalk.dim(`${data.uptime_seconds}s`)}` + (data.error_message ? `
2151
- ${chalk.red("Error:")} ${data.error_message}` : ""),
2152
- "\u{1F4CA} Deployment Status"
2153
- );
2154
- } catch (err) {
2155
- errorBox(err.message);
2156
- process.exit(1);
2157
- }
2158
- });
2159
- program.exitOverride();
2160
- async function main() {
2161
- try {
2162
- await program.parseAsync();
2163
- } catch (error) {
2164
- if (error.code === "commander.help") {
2165
- showBanner();
2166
- program.outputHelp();
2167
- } else if (error.code === "commander.version") {
2168
- console.log("0.14.1");
2169
- } else if (error.code === "commander.helpDisplayed") {
2170
- } else {
2171
- console.error();
2172
- errorBox(
2173
- `${chalk.bold(error.message)}
2174
-
2175
- ${chalk.gray("Run")} ${chalk.cyan("moltos --help")} ${chalk.gray("for usage information.")}`,
2176
- "Command Failed"
2177
- );
2178
- process.exit(1);
2179
- }
2180
- }
2181
- }
2182
- main();