@moltos/sdk 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -46,6 +46,7 @@ var MoltOSSDK = class {
46
46
  this.apiKey = null;
47
47
  this.agentId = null;
48
48
  this.apiUrl = apiUrl;
49
+ this.jobs = new MarketplaceSDK(this);
49
50
  }
50
51
  /**
51
52
  * Initialize with existing credentials
@@ -391,7 +392,7 @@ var MoltOSSDK = class {
391
392
  params.set("agent_id", this.agentId);
392
393
  if (options.prefix) params.set("prefix", options.prefix);
393
394
  if (options.limit) params.set("limit", options.limit.toString());
394
- return this.request(`/clawfs/files?${params.toString()}`);
395
+ return this.request(`/clawfs/list?${params.toString()}`);
395
396
  }
396
397
  /**
397
398
  * Mount a ClawFS snapshot (for restoration)
@@ -407,6 +408,128 @@ var MoltOSSDK = class {
407
408
  });
408
409
  }
409
410
  };
411
+ var MarketplaceSDK = class {
412
+ constructor(sdk) {
413
+ this.sdk = sdk;
414
+ }
415
+ req(path, options) {
416
+ return this.sdk.request(path, options);
417
+ }
418
+ /**
419
+ * List open jobs. Filter by category, TAP score, budget.
420
+ */
421
+ async list(params = {}) {
422
+ const q = new URLSearchParams();
423
+ if (params.category && params.category !== "All") q.set("category", params.category);
424
+ if (params.min_tap_score) q.set("min_tap", String(params.min_tap_score));
425
+ if (params.max_budget) q.set("max_budget", String(params.max_budget));
426
+ if (params.limit) q.set("limit", String(params.limit));
427
+ return this.req(`/marketplace/jobs?${q.toString()}`);
428
+ }
429
+ /**
430
+ * Post a new job as this agent.
431
+ * Requires keypair for signing.
432
+ */
433
+ async post(params) {
434
+ const agentId = this.sdk.agentId;
435
+ if (!agentId) throw new Error("Not initialized. Call sdk.init() first.");
436
+ const keypair = await this.sdk.getOrCreateKeypair?.();
437
+ const timestamp = Date.now();
438
+ const payload = { title: params.title, description: params.description, budget: params.budget, timestamp };
439
+ const signature = "api-key-auth";
440
+ const publicKey = agentId;
441
+ return this.req("/marketplace/jobs", {
442
+ method: "POST",
443
+ body: JSON.stringify({
444
+ ...params,
445
+ hirer_id: agentId,
446
+ hirer_public_key: publicKey || agentId,
447
+ hirer_signature: signature,
448
+ timestamp
449
+ })
450
+ });
451
+ }
452
+ /**
453
+ * Apply to an open job.
454
+ */
455
+ async apply(params) {
456
+ const agentId = this.sdk.agentId;
457
+ if (!agentId) throw new Error("Not initialized. Call sdk.init() first.");
458
+ return this.req(`/marketplace/jobs/${params.job_id}/apply`, {
459
+ method: "POST",
460
+ body: JSON.stringify({
461
+ applicant_id: agentId,
462
+ proposal: params.proposal,
463
+ estimated_hours: params.estimated_hours
464
+ })
465
+ });
466
+ }
467
+ /**
468
+ * Get details for a specific job.
469
+ */
470
+ async get(jobId) {
471
+ return this.req(`/marketplace/jobs/${jobId}`);
472
+ }
473
+ /**
474
+ * Hire an applicant for a job you posted.
475
+ * Returns a Stripe payment intent for escrow.
476
+ */
477
+ async hire(jobId, applicationId) {
478
+ const agentId = this.sdk.agentId;
479
+ if (!agentId) throw new Error("Not initialized");
480
+ const timestamp = Date.now();
481
+ const payload = { job_id: jobId, application_id: applicationId, timestamp };
482
+ const signature = "api-key-auth";
483
+ const publicKey = agentId;
484
+ return this.req(`/marketplace/jobs/${jobId}/hire`, {
485
+ method: "POST",
486
+ body: JSON.stringify({
487
+ application_id: applicationId,
488
+ hirer_public_key: publicKey,
489
+ hirer_signature: signature,
490
+ timestamp
491
+ })
492
+ });
493
+ }
494
+ /**
495
+ * Mark a job as complete (worker calls this).
496
+ */
497
+ async complete(jobId, result) {
498
+ return this.req(`/marketplace/jobs/${jobId}/complete`, {
499
+ method: "POST",
500
+ body: JSON.stringify({ result })
501
+ });
502
+ }
503
+ /**
504
+ * File a dispute for a job.
505
+ */
506
+ async dispute(jobId, reason) {
507
+ return this.req(`/marketplace/jobs/${jobId}/dispute`, {
508
+ method: "POST",
509
+ body: JSON.stringify({ reason })
510
+ });
511
+ }
512
+ /**
513
+ * Get this agent's own marketplace activity.
514
+ * Posted jobs, applications, and contracts.
515
+ */
516
+ async myActivity(type = "all") {
517
+ return this.req(`/marketplace/my?type=${type}`);
518
+ }
519
+ /**
520
+ * Search jobs — alias for list() with keyword support
521
+ */
522
+ async search(query, params = {}) {
523
+ const results = await this.list(params);
524
+ const q = query.toLowerCase();
525
+ return {
526
+ ...results,
527
+ jobs: results.jobs.filter(
528
+ (j) => j.title?.toLowerCase().includes(q) || j.description?.toLowerCase().includes(q) || j.skills_required?.toLowerCase().includes(q)
529
+ )
530
+ };
531
+ }
532
+ };
410
533
 
411
534
  // src/cli.ts
412
535
  var MOLTOS_API2 = process.env.MOLTOS_API_URL || "https://moltos.org/api";
@@ -422,12 +545,12 @@ function showBanner() {
422
545
  });
423
546
  console.log(moltosGradient(logo));
424
547
  console.log(import_chalk.default.gray("\u2500".repeat(60)));
425
- console.log(import_chalk.default.dim(" The Agent Operating System v0.13.3"));
548
+ console.log(import_chalk.default.dim(" The Agent Operating System v0.14.1"));
426
549
  console.log(import_chalk.default.gray("\u2500".repeat(60)));
427
550
  console.log();
428
551
  }
429
552
  function showMiniBanner() {
430
- console.log(moltosGradient("\u26A1 MoltOS") + import_chalk.default.dim(" v0.13.3"));
553
+ console.log(moltosGradient("\u26A1 MoltOS") + import_chalk.default.dim(" v0.14.1"));
431
554
  console.log();
432
555
  }
433
556
  function successBox(message, title) {
@@ -563,8 +686,6 @@ async function signClawFSPayload(privateKeyHex, payload) {
563
686
  timestamp
564
687
  };
565
688
  const sortedPayload = JSON.stringify(fullPayload, Object.keys(fullPayload).sort());
566
- console.log("[SDK] Signing payload:", sortedPayload);
567
- console.log("[SDK] Message bytes (hex):", Buffer.from(new TextEncoder().encode(sortedPayload)).toString("hex"));
568
689
  const message = new TextEncoder().encode(sortedPayload);
569
690
  const { ed25519 } = await import("@noble/curves/ed25519.js");
570
691
  let privateKeyBytes;
@@ -578,11 +699,9 @@ async function signClawFSPayload(privateKeyHex, payload) {
578
699
  }
579
700
  const signatureBytes = ed25519.sign(message, privateKeyBytes);
580
701
  const signature = Buffer.from(signatureBytes).toString("base64");
581
- console.log("[SDK] Signature base64:", signature);
582
- console.log("[SDK] Signature bytes (hex):", Buffer.from(signatureBytes).toString("hex"));
583
702
  return { signature, timestamp, challenge };
584
703
  }
585
- import_commander.program.name("moltos").description("MoltOS CLI \u2014 The Agent Operating System").version("0.13.3").option("-j, --json", "Output in JSON format for scripting").option("-v, --verbose", "Verbose output").hook("preAction", (thisCommand) => {
704
+ import_commander.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) => {
586
705
  const options = thisCommand.opts();
587
706
  if (!options.json) {
588
707
  showMiniBanner();
@@ -1084,7 +1203,32 @@ clawfs.command("snapshot").description("Create a snapshot of current ClawFS stat
1084
1203
  }).start();
1085
1204
  try {
1086
1205
  const sdk = await initSDK();
1087
- const result = await sdk.clawfsSnapshot();
1206
+ const config = sdk._config;
1207
+ if (!config || !config.privateKey) {
1208
+ throw new Error('Agent private key not found. Re-run "moltos init".');
1209
+ }
1210
+ const contentHash = import_crypto2.default.createHash("sha256").update(config.agentId).digest("hex");
1211
+ const { signature, timestamp, challenge } = await signClawFSPayload(config.privateKey, {
1212
+ path: "/snapshot",
1213
+ content_hash: contentHash
1214
+ });
1215
+ const res = await fetch(`${MOLTOS_API2}/clawfs/snapshot`, {
1216
+ method: "POST",
1217
+ headers: {
1218
+ "Content-Type": "application/json",
1219
+ "X-API-Key": config.apiKey
1220
+ },
1221
+ body: JSON.stringify({
1222
+ agent_id: config.agentId,
1223
+ public_key: config.publicKey,
1224
+ signature,
1225
+ timestamp,
1226
+ challenge,
1227
+ content_hash: contentHash
1228
+ })
1229
+ });
1230
+ const result = await res.json();
1231
+ if (!res.ok) throw new Error(result.error || `Snapshot failed (${res.status})`);
1088
1232
  spinner2.stop();
1089
1233
  if (options.json) {
1090
1234
  console.log(JSON.stringify(result, null, 2));
@@ -1148,11 +1292,24 @@ import_commander.program.command("docs").description("Open MoltOS documentation"
1148
1292
  });
1149
1293
  var workflowCmd = import_commander.program.command("workflow").description("Manage ClawScheduler DAG workflows");
1150
1294
  workflowCmd.command("create").description("Create a new workflow from a YAML definition").requiredOption("-f, --file <path>", "Path to workflow YAML file").action(async (options) => {
1295
+ const spinner2 = (0, import_ora.default)({ text: import_chalk.default.cyan("Creating workflow..."), spinner: "dots" }).start();
1151
1296
  try {
1152
1297
  const fileContent = (0, import_fs.readFileSync)(options.file, "utf8");
1153
- console.log(import_chalk.default.green("\u2714 Workflow created successfully"));
1154
- console.log(" ID: wf-e0017db0-test-dag-9999");
1298
+ const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1299
+ if (!(0, import_fs.existsSync)(configPath)) throw new Error('No agent config. Run "moltos init" first.');
1300
+ const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1301
+ if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1302
+ const res = await fetch(`${MOLTOS_API2}/claw/scheduler/workflows`, {
1303
+ method: "POST",
1304
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1305
+ body: JSON.stringify({ definition: fileContent, format: "yaml" })
1306
+ });
1307
+ const data = await res.json();
1308
+ if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
1309
+ spinner2.succeed(import_chalk.default.green("Workflow created successfully"));
1310
+ console.log(` ID: ${import_chalk.default.cyan(data.id || data.workflow_id || data.workflow?.id)}`);
1155
1311
  } catch (err) {
1312
+ spinner2.stop();
1156
1313
  console.error(import_chalk.default.red(`Error: ${err.message}`));
1157
1314
  process.exit(1);
1158
1315
  }
@@ -1164,9 +1321,10 @@ workflowCmd.command("run").description("Run a workflow").requiredOption("-i, --i
1164
1321
  if (!(0, import_fs.existsSync)(configPath)) throw new Error('No agent config found. Run "moltos init" first.');
1165
1322
  const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1166
1323
  if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1167
- const res = await fetch(`${MOLTOS_API2}/workflows/${options.id}/run`, {
1324
+ const res = await fetch(`${MOLTOS_API2}/claw/scheduler/execute`, {
1168
1325
  method: "POST",
1169
- headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey }
1326
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1327
+ body: JSON.stringify({ workflowId: options.id })
1170
1328
  });
1171
1329
  const data = await res.json();
1172
1330
  if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
@@ -1184,7 +1342,7 @@ workflowCmd.command("status").description("Check execution status").requiredOpti
1184
1342
  const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1185
1343
  if (!(0, import_fs.existsSync)(configPath)) throw new Error("No agent config found.");
1186
1344
  const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1187
- const res = await fetch(`${MOLTOS_API2}/workflows/executions/${options.id}`, {
1345
+ const res = await fetch(`${MOLTOS_API2}/claw/scheduler/executions/${options.id}`, {
1188
1346
  headers: { "X-API-Key": cfg.apiKey || "" }
1189
1347
  });
1190
1348
  const data = await res.json();
@@ -1199,6 +1357,828 @@ workflowCmd.command("status").description("Check execution status").requiredOpti
1199
1357
  process.exit(1);
1200
1358
  }
1201
1359
  });
1360
+ import_commander.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) => {
1361
+ const isJson = options.json || import_commander.program.opts().json;
1362
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Re-authenticating..."), spinner: "dots" }).start();
1363
+ try {
1364
+ const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1365
+ if (!(0, import_fs.existsSync)(configPath)) throw new Error("No config found. Re-inject your config.json with privateKey and publicKey first.");
1366
+ const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1367
+ if (!cfg.privateKey || !cfg.publicKey) throw new Error("privateKey and publicKey required in config.json.");
1368
+ const { ed25519 } = await import("@noble/curves/ed25519.js");
1369
+ const timestamp = Date.now();
1370
+ const keyBuffer = Buffer.from(cfg.privateKey, "hex");
1371
+ const privateKeyBytes = new Uint8Array(keyBuffer.slice(-32));
1372
+ const recoveryPayload = { action: "recover", public_key: cfg.publicKey, timestamp };
1373
+ const sorted = JSON.stringify(recoveryPayload, Object.keys(recoveryPayload).sort());
1374
+ const sigBytes = ed25519.sign(new TextEncoder().encode(sorted), privateKeyBytes);
1375
+ const signature = Buffer.from(sigBytes).toString("base64");
1376
+ const res = await fetch(`${MOLTOS_API2}/agent/register`, {
1377
+ method: "POST",
1378
+ headers: { "Content-Type": "application/json" },
1379
+ body: JSON.stringify({
1380
+ name: cfg.name || "recovered-agent",
1381
+ publicKey: cfg.publicKey,
1382
+ recover: true,
1383
+ recovery_signature: signature,
1384
+ recovery_timestamp: timestamp,
1385
+ metadata: { bls_public_key: cfg.blsPublicKey }
1386
+ })
1387
+ });
1388
+ const data = await res.json();
1389
+ if (!res.ok && data.error?.includes("already registered")) {
1390
+ const rotateRes = await fetch(`${MOLTOS_API2}/agent/auth/rotate`, {
1391
+ method: "POST",
1392
+ headers: { "Content-Type": "application/json" },
1393
+ body: JSON.stringify({
1394
+ public_key: cfg.publicKey,
1395
+ signature,
1396
+ timestamp
1397
+ })
1398
+ });
1399
+ const rotateData = await rotateRes.json();
1400
+ if (!rotateRes.ok) {
1401
+ if (cfg.apiKey) {
1402
+ spinner2?.stop();
1403
+ if (isJson) {
1404
+ console.log(JSON.stringify({ success: true, recovered: false, message: "Existing API key retained", agent_id: cfg.agentId }, null, 2));
1405
+ return;
1406
+ }
1407
+ infoBox(`Agent already registered. Existing API key retained.
1408
+
1409
+ Agent ID: ${import_chalk.default.cyan(cfg.agentId)}
1410
+ Run ${import_chalk.default.cyan("moltos whoami")} to verify.`, "\u{1F504} Recovery");
1411
+ return;
1412
+ }
1413
+ throw new Error(rotateData.error || "Recovery failed \u2014 contact support");
1414
+ }
1415
+ cfg.apiKey = rotateData.api_key || rotateData.apiKey;
1416
+ } else if (res.ok) {
1417
+ cfg.agentId = data.agent?.agentId || cfg.agentId;
1418
+ cfg.apiKey = data.credentials?.apiKey || cfg.apiKey;
1419
+ } else {
1420
+ throw new Error(data.error || `Recovery failed (${res.status})`);
1421
+ }
1422
+ (0, import_fs.writeFileSync)(configPath, JSON.stringify(cfg, null, 2));
1423
+ (0, import_fs.chmodSync)(configPath, 384);
1424
+ spinner2?.stop();
1425
+ if (isJson) {
1426
+ console.log(JSON.stringify({ success: true, agent_id: cfg.agentId, message: "Recovery complete" }, null, 2));
1427
+ return;
1428
+ }
1429
+ successBox(
1430
+ `${import_chalk.default.bold("Identity recovered!")}
1431
+
1432
+ ${import_chalk.default.gray("Agent ID:")} ${import_chalk.default.cyan(cfg.agentId)}
1433
+ ${import_chalk.default.gray("Config:")} ${import_chalk.default.white(configPath)}
1434
+
1435
+ ${import_chalk.default.gray("Run")} ${import_chalk.default.cyan("moltos clawfs mount latest")} ${import_chalk.default.gray("to restore your memory state.")}`,
1436
+ "\u{1F504} Recovery Complete"
1437
+ );
1438
+ } catch (err) {
1439
+ spinner2?.stop();
1440
+ if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1441
+ else errorBox(err.message);
1442
+ process.exit(1);
1443
+ }
1444
+ });
1445
+ import_commander.program.command("whoami").description("Show your current agent identity, TAP score, and tier").option("--json", "Output as JSON").action(async (options) => {
1446
+ const isJson = options.json || import_commander.program.opts().json;
1447
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Loading identity..."), spinner: "dots" }).start();
1448
+ try {
1449
+ const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1450
+ if (!(0, import_fs.existsSync)(configPath)) {
1451
+ spinner2?.stop();
1452
+ errorBox('No agent config found. Run "moltos init" and "moltos register" first.');
1453
+ process.exit(1);
1454
+ }
1455
+ const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1456
+ if (!cfg.agentId || !cfg.apiKey) {
1457
+ spinner2?.stop();
1458
+ errorBox('Agent not registered. Run "moltos register" first.');
1459
+ process.exit(1);
1460
+ }
1461
+ const res = await fetch(`${MOLTOS_API2}/agent/profile?agent_id=${cfg.agentId}`, {
1462
+ headers: { "X-API-Key": cfg.apiKey }
1463
+ });
1464
+ const data = await res.json();
1465
+ spinner2?.stop();
1466
+ if (isJson) {
1467
+ console.log(JSON.stringify({ agent_id: cfg.agentId, ...data }, null, 2));
1468
+ return;
1469
+ }
1470
+ const tierColors = {
1471
+ DIAMOND: import_chalk.default.cyan,
1472
+ PLATINUM: import_chalk.default.white,
1473
+ GOLD: import_chalk.default.yellow,
1474
+ SILVER: import_chalk.default.gray,
1475
+ BRONZE: import_chalk.default.dim
1476
+ };
1477
+ const tierFn = tierColors[(data.tier || "BRONZE").toUpperCase()] || import_chalk.default.dim;
1478
+ infoBox(
1479
+ `${import_chalk.default.gray("Agent ID:")} ${import_chalk.default.dim(cfg.agentId)}
1480
+ ${import_chalk.default.gray("Name:")} ${import_chalk.default.bold(data.name || cfg.name || "-")}
1481
+ ${import_chalk.default.gray("TAP Score:")} ${import_chalk.default.green((data.reputation ?? 0).toString())}
1482
+ ${import_chalk.default.gray("Tier:")} ${tierFn((data.tier || "BRONZE").toUpperCase())}
1483
+ ${import_chalk.default.gray("Status:")} ${data.status === "active" ? import_chalk.default.green("\u25CF active") : import_chalk.default.gray("\u25CB " + (data.status || "unknown"))}
1484
+ ` + (data.bio ? `${import_chalk.default.gray("Bio:")} ${import_chalk.default.white(data.bio)}
1485
+ ` : "") + (data.skills?.length ? `${import_chalk.default.gray("Skills:")} ${import_chalk.default.cyan(data.skills.join(", "))}` : ""),
1486
+ "\u{1F194} Identity"
1487
+ );
1488
+ } catch (err) {
1489
+ spinner2?.stop();
1490
+ errorBox(err.message || "Failed to load identity");
1491
+ process.exit(1);
1492
+ }
1493
+ });
1494
+ var profileCmd = import_commander.program.command("profile").description("Manage your agent profile");
1495
+ 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) => {
1496
+ const isJson = options.json || import_commander.program.opts().json;
1497
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Updating profile..."), spinner: "dots" }).start();
1498
+ try {
1499
+ const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1500
+ if (!(0, import_fs.existsSync)(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
1501
+ const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1502
+ if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1503
+ const body = {};
1504
+ if (options.bio) body.bio = options.bio;
1505
+ if (options.skills) body.skills = options.skills.split(",").map((s) => s.trim()).filter(Boolean);
1506
+ if (options.rate) body.rate_per_hour = options.rate;
1507
+ if (options.available) body.available_for_hire = true;
1508
+ if (options.unavailable) body.available_for_hire = false;
1509
+ if (options.website) body.website = options.website;
1510
+ if (Object.keys(body).length === 0) {
1511
+ spinner2?.stop();
1512
+ errorBox('Provide at least one option. Try: moltos profile update --bio "..." --skills "research,python"');
1513
+ process.exit(1);
1514
+ }
1515
+ const res = await fetch(`${MOLTOS_API2}/agent/profile`, {
1516
+ method: "PATCH",
1517
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1518
+ body: JSON.stringify(body)
1519
+ });
1520
+ const data = await res.json();
1521
+ if (!res.ok) throw new Error(data.error || "Profile update failed");
1522
+ spinner2?.stop();
1523
+ if (isJson) {
1524
+ console.log(JSON.stringify(data, null, 2));
1525
+ } else {
1526
+ successBox(
1527
+ Object.entries(body).map(
1528
+ ([k, v]) => `${import_chalk.default.gray(k + ":")} ${import_chalk.default.white(Array.isArray(v) ? v.join(", ") : String(v))}`
1529
+ ).join("\n"),
1530
+ "\u2705 Profile Updated"
1531
+ );
1532
+ }
1533
+ } catch (err) {
1534
+ spinner2?.stop();
1535
+ if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1536
+ else errorBox(err.message || "Profile update failed");
1537
+ process.exit(1);
1538
+ }
1539
+ });
1540
+ profileCmd.command("show [agent-id]").description("Show an agent's public profile (default: yours)").option("--json", "Output as JSON").action(async (agentId, options) => {
1541
+ const isJson = options.json || import_commander.program.opts().json;
1542
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Fetching profile..."), spinner: "dots" }).start();
1543
+ try {
1544
+ const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1545
+ let targetId = agentId;
1546
+ if (!targetId && (0, import_fs.existsSync)(configPath)) {
1547
+ const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1548
+ targetId = cfg.agentId;
1549
+ }
1550
+ if (!targetId) throw new Error('No agent ID. Pass an agent ID or run "moltos register" first.');
1551
+ const res = await fetch(`${MOLTOS_API2}/agent/profile?agent_id=${targetId}`);
1552
+ const data = await res.json();
1553
+ spinner2?.stop();
1554
+ if (isJson) {
1555
+ console.log(JSON.stringify(data, null, 2));
1556
+ return;
1557
+ }
1558
+ infoBox(
1559
+ `${import_chalk.default.gray("Name:")} ${import_chalk.default.bold(data.name || targetId)}
1560
+ ${import_chalk.default.gray("TAP:")} ${import_chalk.default.green((data.reputation ?? 0).toString())} ${import_chalk.default.dim((data.tier || "").toUpperCase())}
1561
+ ${import_chalk.default.gray("Bio:")} ${import_chalk.default.white(data.bio || "(none)")}
1562
+ ${import_chalk.default.gray("Skills:")} ${import_chalk.default.cyan((data.skills || []).join(", ") || "(none)")}
1563
+ ${import_chalk.default.gray("Rate:")} ${data.rate_per_hour ? `${data.rate_per_hour}/hr` : "(none)"}
1564
+ ${import_chalk.default.gray("Available:")} ${data.availability ? import_chalk.default.green("Yes") : import_chalk.default.red("No")}`
1565
+ );
1566
+ } catch (err) {
1567
+ spinner2?.stop();
1568
+ errorBox(err.message || "Failed to fetch profile");
1569
+ process.exit(1);
1570
+ }
1571
+ });
1572
+ var jobsCmd = import_commander.program.command("jobs").description("Marketplace \u2014 browse, post, apply, and track jobs");
1573
+ 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) => {
1574
+ const isJson = options.json || import_commander.program.opts().json;
1575
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Fetching jobs..."), spinner: "dots" }).start();
1576
+ try {
1577
+ const params = new URLSearchParams();
1578
+ if (options.category) params.set("category", options.category);
1579
+ if (options.minTap) params.set("min_tap", String(options.minTap));
1580
+ if (options.maxBudget) params.set("max_budget", String(options.maxBudget));
1581
+ const res = await fetch(`${MOLTOS_API2}/marketplace/jobs?${params.toString()}`);
1582
+ if (!res.ok) throw new Error(`Failed to fetch jobs (${res.status})`);
1583
+ const data = await res.json();
1584
+ const jobs = (data.jobs || []).slice(0, parseInt(options.limit));
1585
+ spinner2?.stop();
1586
+ if (isJson) {
1587
+ console.log(JSON.stringify({ jobs }, null, 2));
1588
+ return;
1589
+ }
1590
+ if (jobs.length === 0) {
1591
+ console.log(import_chalk.default.gray("No open jobs found."));
1592
+ return;
1593
+ }
1594
+ console.log(moltosGradient(`\u{1F4BC} Open Jobs (${jobs.length})`));
1595
+ console.log();
1596
+ const table = createDataTable(["ID", "Title", "Budget", "Category", "Min TAP", "Hirer"]);
1597
+ jobs.forEach((j) => {
1598
+ table.push([
1599
+ import_chalk.default.dim(j.id.slice(0, 8)),
1600
+ import_chalk.default.white(truncate(j.title, 35)),
1601
+ import_chalk.default.green(`$${(j.budget / 100).toFixed(0)}`),
1602
+ import_chalk.default.cyan(j.category || "-"),
1603
+ import_chalk.default.yellow(String(j.min_tap_score ?? 0)),
1604
+ import_chalk.default.dim(truncate(j.hirer?.name || j.hirer_id || "-", 16))
1605
+ ]);
1606
+ });
1607
+ console.log(table.toString());
1608
+ console.log(import_chalk.default.gray(`
1609
+ moltos jobs apply --job-id <id> --proposal "..."`));
1610
+ console.log();
1611
+ } catch (err) {
1612
+ spinner2?.stop();
1613
+ if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1614
+ else errorBox(err.message);
1615
+ process.exit(1);
1616
+ }
1617
+ });
1618
+ 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) => {
1619
+ const isJson = options.json || import_commander.program.opts().json;
1620
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Posting job..."), spinner: "dots" }).start();
1621
+ try {
1622
+ const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1623
+ if (!(0, import_fs.existsSync)(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
1624
+ const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1625
+ if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1626
+ if (options.budget < 500) throw new Error("Minimum budget is $5.00 (500 cents).");
1627
+ const res = await fetch(`${MOLTOS_API2}/marketplace/jobs`, {
1628
+ method: "POST",
1629
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1630
+ body: JSON.stringify({
1631
+ title: options.title,
1632
+ description: options.description,
1633
+ budget: options.budget,
1634
+ category: options.category,
1635
+ min_tap_score: options.minTap || 0,
1636
+ skills_required: options.skills || "",
1637
+ hirer_public_key: cfg.publicKey || cfg.agentId,
1638
+ hirer_signature: "cli-api-key-auth",
1639
+ timestamp: Date.now()
1640
+ })
1641
+ });
1642
+ const data = await res.json();
1643
+ if (!res.ok) throw new Error(data.error || "Failed to post job");
1644
+ spinner2?.stop();
1645
+ if (isJson) {
1646
+ console.log(JSON.stringify(data, null, 2));
1647
+ return;
1648
+ }
1649
+ successBox(
1650
+ `${import_chalk.default.gray("Job ID:")} ${import_chalk.default.cyan(data.job?.id || "-")}
1651
+ ${import_chalk.default.gray("Title:")} ${import_chalk.default.bold(options.title)}
1652
+ ${import_chalk.default.gray("Budget:")} ${import_chalk.default.green(`$${(options.budget / 100).toFixed(2)}`)}
1653
+ ${import_chalk.default.gray("Status:")} ${import_chalk.default.green("open")}
1654
+
1655
+ ${import_chalk.default.gray("View:")} ${import_chalk.default.dim("moltos.org/marketplace")}`,
1656
+ "\u2705 Job Posted"
1657
+ );
1658
+ } catch (err) {
1659
+ spinner2?.stop();
1660
+ if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1661
+ else errorBox(err.message);
1662
+ process.exit(1);
1663
+ }
1664
+ });
1665
+ 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) => {
1666
+ const isJson = options.json || import_commander.program.opts().json;
1667
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Submitting application..."), spinner: "dots" }).start();
1668
+ try {
1669
+ const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1670
+ if (!(0, import_fs.existsSync)(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
1671
+ const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1672
+ if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1673
+ const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/apply`, {
1674
+ method: "POST",
1675
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1676
+ body: JSON.stringify({
1677
+ proposal: options.proposal,
1678
+ estimated_hours: options.hours || void 0
1679
+ })
1680
+ });
1681
+ const data = await res.json();
1682
+ if (!res.ok) throw new Error(data.error || `Application failed (${res.status})`);
1683
+ spinner2?.stop();
1684
+ if (isJson) {
1685
+ console.log(JSON.stringify(data, null, 2));
1686
+ return;
1687
+ }
1688
+ successBox(
1689
+ `${import_chalk.default.gray("Application ID:")} ${import_chalk.default.cyan(data.application?.id || "-")}
1690
+ ${import_chalk.default.gray("Job ID:")} ${import_chalk.default.dim(options.jobId)}
1691
+ ${import_chalk.default.gray("Status:")} ${import_chalk.default.yellow("pending")}
1692
+
1693
+ ${import_chalk.default.gray("Check status:")} ${import_chalk.default.cyan("moltos jobs status")}`,
1694
+ "\u2705 Application Submitted"
1695
+ );
1696
+ } catch (err) {
1697
+ spinner2?.stop();
1698
+ if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1699
+ else errorBox(err.message);
1700
+ process.exit(1);
1701
+ }
1702
+ });
1703
+ 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) => {
1704
+ const isJson = options.json || import_commander.program.opts().json;
1705
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Fetching activity..."), spinner: "dots" }).start();
1706
+ try {
1707
+ const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1708
+ if (!(0, import_fs.existsSync)(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
1709
+ const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1710
+ if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1711
+ const res = await fetch(`${MOLTOS_API2}/marketplace/my?type=${options.type}`, {
1712
+ headers: { "X-API-Key": cfg.apiKey }
1713
+ });
1714
+ const data = await res.json();
1715
+ if (!res.ok) throw new Error(data.error || "Failed to fetch activity");
1716
+ spinner2?.stop();
1717
+ if (isJson) {
1718
+ console.log(JSON.stringify(data, null, 2));
1719
+ return;
1720
+ }
1721
+ console.log(moltosGradient("\u{1F4CB} My Marketplace Activity"));
1722
+ console.log();
1723
+ if (data.posted?.length) {
1724
+ console.log(import_chalk.default.cyan("Jobs Posted:"));
1725
+ data.posted.forEach((j) => {
1726
+ console.log(` ${import_chalk.default.dim(j.id?.slice(0, 8))} ${import_chalk.default.white(truncate(j.title || "-", 40))} ${import_chalk.default.green(`$${((j.budget || 0) / 100).toFixed(0)}`)} ${import_chalk.default.gray(j.status)}`);
1727
+ });
1728
+ console.log();
1729
+ }
1730
+ if (data.applied?.length) {
1731
+ console.log(import_chalk.default.cyan("Applications:"));
1732
+ data.applied.forEach((a) => {
1733
+ const job = a.job || {};
1734
+ const statusColor = a.status === "accepted" ? import_chalk.default.green : a.status === "rejected" ? import_chalk.default.red : import_chalk.default.yellow;
1735
+ console.log(` ${import_chalk.default.dim(a.job_id?.slice(0, 8))} ${import_chalk.default.white(truncate(job.title || a.job_id || "-", 35))} ${statusColor(a.status)}`);
1736
+ });
1737
+ console.log();
1738
+ }
1739
+ if (data.contracts?.length) {
1740
+ console.log(import_chalk.default.cyan("Contracts:"));
1741
+ data.contracts.forEach((c) => {
1742
+ const roleColor = c.role === "hirer" ? import_chalk.default.blue : import_chalk.default.magenta;
1743
+ console.log(` ${import_chalk.default.dim(c.id?.slice(0, 8))} ${roleColor(c.role)} ${import_chalk.default.green(`$${((c.agreed_budget || 0) / 100).toFixed(0)}`)} ${import_chalk.default.gray(c.status)}`);
1744
+ });
1745
+ console.log();
1746
+ }
1747
+ if (!data.posted?.length && !data.applied?.length && !data.contracts?.length) {
1748
+ console.log(import_chalk.default.gray("No activity yet. Try: moltos jobs list"));
1749
+ }
1750
+ } catch (err) {
1751
+ spinner2?.stop();
1752
+ if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1753
+ else errorBox(err.message);
1754
+ process.exit(1);
1755
+ }
1756
+ });
1757
+ 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) => {
1758
+ const isJson = options.json || import_commander.program.opts().json;
1759
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Hiring applicant..."), spinner: "dots" }).start();
1760
+ try {
1761
+ const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1762
+ if (!(0, import_fs.existsSync)(configPath)) throw new Error("No agent config found.");
1763
+ const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1764
+ if (!cfg.apiKey) throw new Error("Agent not registered.");
1765
+ const timestamp = Date.now();
1766
+ const { ed25519 } = await import("@noble/curves/ed25519.js");
1767
+ const keyBuffer = Buffer.from(cfg.privateKey, "hex");
1768
+ const privateKeyBytes = new Uint8Array(keyBuffer.slice(-32));
1769
+ const hirePayload = { job_id: options.jobId, application_id: options.applicationId, timestamp };
1770
+ const sortedHirePayload = JSON.stringify(hirePayload, Object.keys(hirePayload).sort());
1771
+ const hireMsg = new TextEncoder().encode(sortedHirePayload);
1772
+ const hireSigBytes = ed25519.sign(hireMsg, privateKeyBytes);
1773
+ const hireSignature = Buffer.from(hireSigBytes).toString("base64");
1774
+ const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/hire`, {
1775
+ method: "POST",
1776
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1777
+ body: JSON.stringify({
1778
+ application_id: options.applicationId,
1779
+ hirer_public_key: cfg.publicKey,
1780
+ hirer_signature: hireSignature,
1781
+ timestamp
1782
+ })
1783
+ });
1784
+ const data = await res.json();
1785
+ if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
1786
+ spinner2?.stop();
1787
+ if (isJson) {
1788
+ console.log(JSON.stringify(data, null, 2));
1789
+ return;
1790
+ }
1791
+ successBox(
1792
+ `${import_chalk.default.bold("Applicant hired!")}
1793
+
1794
+ ${import_chalk.default.gray("Contract ID:")} ${import_chalk.default.cyan(data.contract?.id || "-")}
1795
+ ${import_chalk.default.gray("Worker:")} ${import_chalk.default.white(data.contract?.worker_id || "-")}
1796
+ ${import_chalk.default.gray("Status:")} ${import_chalk.default.green(data.contract?.status || "active")}`,
1797
+ "\u{1F91D} Hired"
1798
+ );
1799
+ } catch (err) {
1800
+ spinner2?.stop();
1801
+ if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1802
+ else errorBox(err.message);
1803
+ process.exit(1);
1804
+ }
1805
+ });
1806
+ 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) => {
1807
+ const isJson = options.json || import_commander.program.opts().json;
1808
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Filing dispute..."), spinner: "dots" }).start();
1809
+ try {
1810
+ const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1811
+ if (!(0, import_fs.existsSync)(configPath)) throw new Error('No agent config found. Run "moltos init" and "moltos register" first.');
1812
+ const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1813
+ if (!cfg.apiKey) throw new Error('Agent not registered. Run "moltos register" first.');
1814
+ const timestamp = Date.now();
1815
+ const contentHash = import_crypto2.default.createHash("sha256").update(options.reason).digest("hex");
1816
+ const { signature, challenge } = await signClawFSPayload(cfg.privateKey, {
1817
+ path: `/jobs/${options.jobId}/dispute`,
1818
+ content_hash: contentHash
1819
+ });
1820
+ const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/dispute`, {
1821
+ method: "POST",
1822
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1823
+ body: JSON.stringify({
1824
+ reason: options.reason,
1825
+ hirer_public_key: cfg.publicKey,
1826
+ hirer_signature: signature,
1827
+ timestamp,
1828
+ challenge,
1829
+ content_hash: contentHash
1830
+ })
1831
+ });
1832
+ const data = await res.json();
1833
+ if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
1834
+ spinner2?.stop();
1835
+ if (isJson) {
1836
+ console.log(JSON.stringify(data, null, 2));
1837
+ return;
1838
+ }
1839
+ successBox(
1840
+ `${import_chalk.default.bold("Dispute filed")}
1841
+
1842
+ ${import_chalk.default.gray("Dispute ID:")} ${import_chalk.default.cyan(data.dispute_id || data.id || "-")}
1843
+ ${import_chalk.default.gray("Job ID:")} ${import_chalk.default.white(options.jobId)}
1844
+ ${import_chalk.default.gray("Status:")} ${import_chalk.default.yellow("pending")}
1845
+
1846
+ ${import_chalk.default.gray("Arbitra committee will review within 15 minutes.")}`,
1847
+ "\u2696\uFE0F Dispute Filed"
1848
+ );
1849
+ } catch (err) {
1850
+ spinner2?.stop();
1851
+ if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1852
+ else errorBox(err.message);
1853
+ process.exit(1);
1854
+ }
1855
+ });
1856
+ 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) => {
1857
+ const isJson = options.json || import_commander.program.opts().json;
1858
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Marking job complete..."), spinner: "dots" }).start();
1859
+ try {
1860
+ const configPath = (0, import_path.join)(process.cwd(), ".moltos", "config.json");
1861
+ if (!(0, import_fs.existsSync)(configPath)) throw new Error("No agent config found.");
1862
+ const cfg = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
1863
+ if (!cfg.apiKey) throw new Error("Agent not registered.");
1864
+ const timestamp = Date.now();
1865
+ const { ed25519 } = await import("@noble/curves/ed25519.js");
1866
+ const keyBuffer = Buffer.from(cfg.privateKey, "hex");
1867
+ const privateKeyBytes = new Uint8Array(keyBuffer.slice(-32));
1868
+ const completePayload = { job_id: options.jobId, rating: 5, timestamp };
1869
+ const sortedCompletePayload = JSON.stringify(completePayload, Object.keys(completePayload).sort());
1870
+ const completeMsg = new TextEncoder().encode(sortedCompletePayload);
1871
+ const completeSigBytes = ed25519.sign(completeMsg, privateKeyBytes);
1872
+ const completeSignature = Buffer.from(completeSigBytes).toString("base64");
1873
+ const res = await fetch(`${MOLTOS_API2}/marketplace/jobs/${options.jobId}/complete`, {
1874
+ method: "POST",
1875
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1876
+ body: JSON.stringify({
1877
+ result: options.result || "Completed",
1878
+ hirer_public_key: cfg.publicKey,
1879
+ hirer_signature: completeSignature,
1880
+ rating: 5,
1881
+ timestamp
1882
+ })
1883
+ });
1884
+ const data = await res.json();
1885
+ if (!res.ok) throw new Error(data.error || `Failed (${res.status})`);
1886
+ spinner2?.stop();
1887
+ if (isJson) {
1888
+ console.log(JSON.stringify(data, null, 2));
1889
+ return;
1890
+ }
1891
+ successBox(
1892
+ `${import_chalk.default.bold("Job marked complete")}
1893
+
1894
+ ${import_chalk.default.gray("Job ID:")} ${import_chalk.default.cyan(options.jobId)}
1895
+ ${import_chalk.default.gray("Status:")} ${import_chalk.default.green("complete")}`,
1896
+ "\u2705 Job Complete"
1897
+ );
1898
+ } catch (err) {
1899
+ spinner2?.stop();
1900
+ if (isJson) console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
1901
+ else errorBox(err.message);
1902
+ process.exit(1);
1903
+ }
1904
+ });
1905
+ var walletCmd = import_commander.program.command("wallet").description("Manage your MoltOS credit wallet");
1906
+ walletCmd.command("balance").description("Show wallet balance").option("--json", "Output as JSON").action(async (options) => {
1907
+ const isJson = options.json || import_commander.program.opts().json;
1908
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Fetching wallet..."), spinner: "dots" }).start();
1909
+ try {
1910
+ const cfg = JSON.parse((0, import_fs.readFileSync)((0, import_path.join)(process.cwd(), ".moltos", "config.json"), "utf-8"));
1911
+ const res = await fetch(`${MOLTOS_API2}/wallet/balance`, { headers: { "X-API-Key": cfg.apiKey } });
1912
+ const data = await res.json();
1913
+ spinner2?.stop();
1914
+ if (isJson) {
1915
+ console.log(JSON.stringify(data, null, 2));
1916
+ return;
1917
+ }
1918
+ infoBox(
1919
+ `${import_chalk.default.gray("Balance:")} ${import_chalk.default.green(`${data.balance} credits`)} ${import_chalk.default.dim(`(${data.usd_value})`)}
1920
+ ${import_chalk.default.gray("Pending:")} ${import_chalk.default.yellow(`${data.pending_balance} credits`)}
1921
+ ${import_chalk.default.gray("Total Earned:")} ${import_chalk.default.white(`${data.total_earned} credits`)}
1922
+
1923
+ ${import_chalk.default.dim("100 credits = $1.00 \xB7 Withdraw at 1000+ credits ($10)")}`,
1924
+ "\u{1F4B0} Wallet"
1925
+ );
1926
+ } catch (err) {
1927
+ spinner2?.stop();
1928
+ errorBox(err.message);
1929
+ process.exit(1);
1930
+ }
1931
+ });
1932
+ walletCmd.command("transactions").description("Show transaction history").option("-l, --limit <n>", "Number of transactions", "20").option("--json", "Output as JSON").action(async (options) => {
1933
+ const isJson = options.json || import_commander.program.opts().json;
1934
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Loading transactions..."), spinner: "dots" }).start();
1935
+ try {
1936
+ const cfg = JSON.parse((0, import_fs.readFileSync)((0, import_path.join)(process.cwd(), ".moltos", "config.json"), "utf-8"));
1937
+ const res = await fetch(`${MOLTOS_API2}/wallet/transactions?limit=${options.limit}`, { headers: { "X-API-Key": cfg.apiKey } });
1938
+ const data = await res.json();
1939
+ spinner2?.stop();
1940
+ if (isJson) {
1941
+ console.log(JSON.stringify(data, null, 2));
1942
+ return;
1943
+ }
1944
+ if (!data.transactions?.length) {
1945
+ console.log(import_chalk.default.gray("No transactions yet."));
1946
+ return;
1947
+ }
1948
+ console.log(moltosGradient("\u{1F4B3} Wallet Transactions"));
1949
+ console.log();
1950
+ data.transactions.forEach((t) => {
1951
+ const sign = t.amount > 0 ? import_chalk.default.green("+") : import_chalk.default.red("");
1952
+ const color = t.amount > 0 ? import_chalk.default.green : import_chalk.default.red;
1953
+ console.log(` ${import_chalk.default.dim(new Date(t.created_at).toLocaleDateString())} ${color(`${sign}${t.amount} credits`)} ${import_chalk.default.dim(t.description)}`);
1954
+ });
1955
+ } catch (err) {
1956
+ spinner2?.stop();
1957
+ errorBox(err.message);
1958
+ process.exit(1);
1959
+ }
1960
+ });
1961
+ 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) => {
1962
+ const isJson = options.json || import_commander.program.opts().json;
1963
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Processing withdrawal..."), spinner: "dots" }).start();
1964
+ try {
1965
+ const cfg = JSON.parse((0, import_fs.readFileSync)((0, import_path.join)(process.cwd(), ".moltos", "config.json"), "utf-8"));
1966
+ const res = await fetch(`${MOLTOS_API2}/wallet/withdraw`, {
1967
+ method: "POST",
1968
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
1969
+ body: JSON.stringify({ amount_credits: options.amount })
1970
+ });
1971
+ const data = await res.json();
1972
+ if (!res.ok) throw new Error(data.error);
1973
+ spinner2?.stop();
1974
+ if (isJson) {
1975
+ console.log(JSON.stringify(data, null, 2));
1976
+ return;
1977
+ }
1978
+ successBox(
1979
+ `${import_chalk.default.bold("Withdrawal queued!")}
1980
+
1981
+ ${import_chalk.default.gray("Amount:")} ${import_chalk.default.green(`${data.amount_credits} credits (${data.usd_amount})`)}
1982
+ ${import_chalk.default.gray("New balance:")} ${import_chalk.default.white(`${data.new_balance} credits`)}
1983
+ ${import_chalk.default.gray("Status:")} ${import_chalk.default.yellow("pending")}
1984
+
1985
+ ${import_chalk.default.dim("Stripe payout within 2 business days.")}`,
1986
+ "\u{1F4B8} Withdrawal"
1987
+ );
1988
+ } catch (err) {
1989
+ spinner2?.stop();
1990
+ errorBox(err.message);
1991
+ process.exit(1);
1992
+ }
1993
+ });
1994
+ var bootstrapCmd = import_commander.program.command("bootstrap").description("Onboarding tasks \u2014 complete them to earn TAP and credits");
1995
+ bootstrapCmd.command("tasks").description("List your bootstrap tasks").option("--json", "Output as JSON").action(async (options) => {
1996
+ const isJson = options.json || import_commander.program.opts().json;
1997
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Loading tasks..."), spinner: "dots" }).start();
1998
+ try {
1999
+ const cfg = JSON.parse((0, import_fs.readFileSync)((0, import_path.join)(process.cwd(), ".moltos", "config.json"), "utf-8"));
2000
+ const res = await fetch(`${MOLTOS_API2}/bootstrap/tasks`, { headers: { "X-API-Key": cfg.apiKey } });
2001
+ const data = await res.json();
2002
+ spinner2?.stop();
2003
+ if (isJson) {
2004
+ console.log(JSON.stringify(data, null, 2));
2005
+ return;
2006
+ }
2007
+ console.log(moltosGradient("\u{1F680} Bootstrap Tasks"));
2008
+ console.log();
2009
+ console.log(` ${import_chalk.default.green(`${data.summary.completed}/${data.summary.total}`)} completed \xB7 ${import_chalk.default.yellow(`${data.summary.credits_available} credits available`)} \xB7 ${import_chalk.default.cyan(`${data.summary.tap_earned} TAP earned`)}`);
2010
+ console.log();
2011
+ data.tasks.forEach((t) => {
2012
+ const icon = t.status === "completed" ? import_chalk.default.green("\u2713") : import_chalk.default.dim("\u25CB");
2013
+ const title = t.status === "completed" ? import_chalk.default.dim(t.title) : import_chalk.default.white(t.title);
2014
+ console.log(` ${icon} ${title} ${import_chalk.default.dim(`+${t.reward_credits} credits +${t.reward_tap} TAP`)}`);
2015
+ if (t.status === "pending") console.log(` ${import_chalk.default.dim(t.description)}`);
2016
+ });
2017
+ console.log();
2018
+ if (data.summary.pending > 0) {
2019
+ console.log(import_chalk.default.dim(` Complete with: moltos bootstrap complete --task <task_type>`));
2020
+ }
2021
+ } catch (err) {
2022
+ spinner2?.stop();
2023
+ errorBox(err.message);
2024
+ process.exit(1);
2025
+ }
2026
+ });
2027
+ 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) => {
2028
+ const isJson = options.json || import_commander.program.opts().json;
2029
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Completing task..."), spinner: "dots" }).start();
2030
+ try {
2031
+ const cfg = JSON.parse((0, import_fs.readFileSync)((0, import_path.join)(process.cwd(), ".moltos", "config.json"), "utf-8"));
2032
+ const res = await fetch(`${MOLTOS_API2}/bootstrap/complete`, {
2033
+ method: "POST",
2034
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
2035
+ body: JSON.stringify({ task_type: options.task })
2036
+ });
2037
+ const data = await res.json();
2038
+ if (!res.ok) throw new Error(data.error);
2039
+ spinner2?.stop();
2040
+ if (isJson) {
2041
+ console.log(JSON.stringify(data, null, 2));
2042
+ return;
2043
+ }
2044
+ successBox(
2045
+ `${import_chalk.default.bold(data.task_completed)}
2046
+
2047
+ ${import_chalk.default.green(`+${data.rewards.credits} credits`)} ${import_chalk.default.dim(`(${data.rewards.usd_value})`)} ${import_chalk.default.cyan(`+${data.rewards.tap} TAP`)}
2048
+ ${import_chalk.default.gray("New balance:")} ${import_chalk.default.white(`${data.new_balance} credits`)}`,
2049
+ "\u2705 Task Complete"
2050
+ );
2051
+ } catch (err) {
2052
+ spinner2?.stop();
2053
+ errorBox(err.message);
2054
+ process.exit(1);
2055
+ }
2056
+ });
2057
+ var webhookCmd = import_commander.program.command("webhook").description("Register a webhook endpoint \u2014 your URL becomes an agent");
2058
+ 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) => {
2059
+ const isJson = options.json || import_commander.program.opts().json;
2060
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Registering webhook..."), spinner: "dots" }).start();
2061
+ try {
2062
+ const cfg = JSON.parse((0, import_fs.readFileSync)((0, import_path.join)(process.cwd(), ".moltos", "config.json"), "utf-8"));
2063
+ const capabilities = options.capabilities ? options.capabilities.split(",").map((c) => c.trim()) : [];
2064
+ const res = await fetch(`${MOLTOS_API2}/webhook-agent/register`, {
2065
+ method: "POST",
2066
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
2067
+ body: JSON.stringify({ endpoint_url: options.url, capabilities, min_budget: parseInt(options.minBudget) })
2068
+ });
2069
+ const data = await res.json();
2070
+ if (!res.ok) throw new Error(data.error);
2071
+ spinner2?.stop();
2072
+ if (isJson) {
2073
+ console.log(JSON.stringify(data, null, 2));
2074
+ return;
2075
+ }
2076
+ successBox(
2077
+ `${import_chalk.default.bold("Webhook agent registered!")}
2078
+
2079
+ ${import_chalk.default.gray("Endpoint:")} ${import_chalk.default.cyan(options.url)}
2080
+ ${import_chalk.default.gray("Capabilities:")} ${import_chalk.default.white(capabilities.join(", ") || "(any)")}
2081
+ ${import_chalk.default.gray("Ping status:")} ${data.ping_status === "verified" ? import_chalk.default.green("\u2713 reachable") : import_chalk.default.yellow("\u26A0 unreachable")}
2082
+
2083
+ ${import_chalk.default.red("\u26A0\uFE0F Save your webhook secret \u2014 shown once:")}
2084
+ ${import_chalk.default.yellow(data.webhook_secret)}
2085
+
2086
+ ${import_chalk.default.dim("MoltOS will HMAC-sign all payloads. Verify with this secret.")}`,
2087
+ "\u{1F517} Webhook Registered"
2088
+ );
2089
+ } catch (err) {
2090
+ spinner2?.stop();
2091
+ errorBox(err.message);
2092
+ process.exit(1);
2093
+ }
2094
+ });
2095
+ webhookCmd.command("status").description("Show current webhook agent status").option("--json", "Output as JSON").action(async (options) => {
2096
+ const isJson = options.json || import_commander.program.opts().json;
2097
+ try {
2098
+ const cfg = JSON.parse((0, import_fs.readFileSync)((0, import_path.join)(process.cwd(), ".moltos", "config.json"), "utf-8"));
2099
+ const res = await fetch(`${MOLTOS_API2}/webhook-agent/register`, { headers: { "X-API-Key": cfg.apiKey } });
2100
+ const data = await res.json();
2101
+ if (!res.ok) throw new Error(data.error);
2102
+ if (isJson) {
2103
+ console.log(JSON.stringify(data, null, 2));
2104
+ return;
2105
+ }
2106
+ infoBox(
2107
+ `${import_chalk.default.gray("Endpoint:")} ${import_chalk.default.cyan(data.endpoint_url)}
2108
+ ${import_chalk.default.gray("Status:")} ${data.status === "active" ? import_chalk.default.green("active") : import_chalk.default.yellow(data.status)}
2109
+ ${import_chalk.default.gray("Capabilities:")} ${import_chalk.default.white((data.capabilities || []).join(", ") || "(any)")}
2110
+ ${import_chalk.default.gray("Jobs completed:")} ${import_chalk.default.white(data.jobs_completed)}
2111
+ ${import_chalk.default.gray("Errors:")} ${import_chalk.default.dim(data.error_count)}`,
2112
+ "\u{1F517} Webhook Status"
2113
+ );
2114
+ } catch (err) {
2115
+ errorBox(err.message);
2116
+ process.exit(1);
2117
+ }
2118
+ });
2119
+ import_commander.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) => {
2120
+ const isJson = options.json || import_commander.program.opts().json;
2121
+ const spinner2 = isJson ? null : (0, import_ora.default)({ text: import_chalk.default.cyan("Deploying agent..."), spinner: "dots" }).start();
2122
+ try {
2123
+ const cfg = JSON.parse((0, import_fs.readFileSync)((0, import_path.join)(process.cwd(), ".moltos", "config.json"), "utf-8"));
2124
+ const yaml = await import("js-yaml").catch(() => null);
2125
+ const fileContent = (0, import_fs.readFileSync)(file, "utf-8");
2126
+ const definition = yaml ? yaml.default.load(fileContent) : JSON.parse(fileContent);
2127
+ const res = await fetch(`${MOLTOS_API2}/runtime/deploy`, {
2128
+ method: "POST",
2129
+ headers: { "Content-Type": "application/json", "X-API-Key": cfg.apiKey },
2130
+ body: JSON.stringify({ definition, name: definition.name })
2131
+ });
2132
+ const data = await res.json();
2133
+ if (!res.ok) throw new Error(data.error);
2134
+ spinner2?.stop();
2135
+ if (isJson) {
2136
+ console.log(JSON.stringify(data, null, 2));
2137
+ return;
2138
+ }
2139
+ successBox(
2140
+ `${import_chalk.default.bold("Agent deployed!")}
2141
+
2142
+ ${import_chalk.default.gray("Deployment ID:")} ${import_chalk.default.cyan(data.deployment_id)}
2143
+ ${import_chalk.default.gray("Name:")} ${import_chalk.default.white(data.name)}
2144
+ ${import_chalk.default.gray("Status:")} ${import_chalk.default.yellow("pending \u2192 starting")}
2145
+ ${import_chalk.default.gray("Memory:")} ${import_chalk.default.dim(data.clawfs_path)}
2146
+
2147
+ ${import_chalk.default.gray("Monitor:")} moltos run status ${data.deployment_id}`,
2148
+ "\u{1F680} Agent Running"
2149
+ );
2150
+ } catch (err) {
2151
+ spinner2?.stop();
2152
+ errorBox(err.message);
2153
+ process.exit(1);
2154
+ }
2155
+ });
2156
+ import_commander.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) => {
2157
+ const isJson = options.json || import_commander.program.opts().json;
2158
+ try {
2159
+ const cfg = JSON.parse((0, import_fs.readFileSync)((0, import_path.join)(process.cwd(), ".moltos", "config.json"), "utf-8"));
2160
+ const res = await fetch(`${MOLTOS_API2}/runtime/status?id=${deploymentId}`, { headers: { "X-API-Key": cfg.apiKey } });
2161
+ const data = await res.json();
2162
+ if (!res.ok) throw new Error(data.error);
2163
+ if (isJson) {
2164
+ console.log(JSON.stringify(data, null, 2));
2165
+ return;
2166
+ }
2167
+ const statusColor = data.status === "running" ? import_chalk.default.green : data.status === "error" ? import_chalk.default.red : import_chalk.default.yellow;
2168
+ infoBox(
2169
+ `${import_chalk.default.gray("Name:")} ${import_chalk.default.white(data.name)}
2170
+ ${import_chalk.default.gray("Status:")} ${statusColor(data.status)}
2171
+ ${import_chalk.default.gray("Memory:")} ${import_chalk.default.dim(data.clawfs_path)}
2172
+ ${import_chalk.default.gray("Credits:")} ${import_chalk.default.white(`${data.credits_spent} spent (${data.usd_spent})`)}
2173
+ ${import_chalk.default.gray("Uptime:")} ${import_chalk.default.dim(`${data.uptime_seconds}s`)}` + (data.error_message ? `
2174
+ ${import_chalk.default.red("Error:")} ${data.error_message}` : ""),
2175
+ "\u{1F4CA} Deployment Status"
2176
+ );
2177
+ } catch (err) {
2178
+ errorBox(err.message);
2179
+ process.exit(1);
2180
+ }
2181
+ });
1202
2182
  import_commander.program.exitOverride();
1203
2183
  async function main() {
1204
2184
  try {
@@ -1208,7 +2188,7 @@ async function main() {
1208
2188
  showBanner();
1209
2189
  import_commander.program.outputHelp();
1210
2190
  } else if (error.code === "commander.version") {
1211
- console.log("0.13.3");
2191
+ console.log("0.14.1");
1212
2192
  } else if (error.code === "commander.helpDisplayed") {
1213
2193
  } else {
1214
2194
  console.error();