humanpages 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -186,7 +186,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
186
186
  },
187
187
  {
188
188
  name: 'create_job_offer',
189
- description: 'Create a job offer for a human. Requires an ACTIVE agent API key (from register_agent + activation). The human must ACCEPT the offer before you can proceed with payment. RATE LIMITS: BASIC tier = 5 offers/day, PRO tier = 15 offers/day. Agents must be activated before creating jobs — use request_activation_code or get_payment_activation. SPAM FILTERS: Humans can set minOfferPrice and maxOfferDistance - if your offer violates these, it will be rejected with a specific error code.',
189
+ description: 'Create a job offer for a human. Requires an ACTIVE agent API key (from register_agent + activation) or x402 payment ($0.25 USDC on Base via x-payment header). RATE LIMITS: BASIC tier = 1 offer/2 days, PRO tier = 15 offers/day. x402 payments bypass tier limits. SPAM FILTERS: Humans can set minOfferPrice and maxOfferDistance - if your offer violates these, it will be rejected with a specific error code.',
190
190
  inputSchema: {
191
191
  type: 'object',
192
192
  properties: {
@@ -238,6 +238,34 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
238
238
  type: 'string',
239
239
  description: 'Secret for HMAC-SHA256 signature verification (min 16 chars). The signature is sent in X-HumanPages-Signature header.',
240
240
  },
241
+ payment_mode: {
242
+ type: 'string',
243
+ enum: ['ONE_TIME', 'STREAM'],
244
+ description: 'Payment mode. ONE_TIME (default) for single payments. STREAM for ongoing stream payments.',
245
+ },
246
+ payment_timing: {
247
+ type: 'string',
248
+ enum: ['upfront', 'upon_completion'],
249
+ description: 'For ONE_TIME jobs only. "upfront" (default) = pay before work. "upon_completion" = pay after work is done.',
250
+ },
251
+ stream_method: {
252
+ type: 'string',
253
+ enum: ['SUPERFLUID', 'MICRO_TRANSFER'],
254
+ description: 'Stream method. SUPERFLUID: agent creates an on-chain flow that streams tokens per-second. MICRO_TRANSFER: agent sends periodic discrete transfers. Required when payment_mode=STREAM.',
255
+ },
256
+ stream_interval: {
257
+ type: 'string',
258
+ enum: ['HOURLY', 'DAILY', 'WEEKLY'],
259
+ description: 'How often payments are made/checkpointed. Required when payment_mode=STREAM.',
260
+ },
261
+ stream_rate_usdc: {
262
+ type: 'number',
263
+ description: 'USDC amount per interval (e.g., 10 = $10/day if interval=DAILY). Required when payment_mode=STREAM.',
264
+ },
265
+ stream_max_ticks: {
266
+ type: 'number',
267
+ description: 'Optional cap on number of payment intervals. Null = indefinite.',
268
+ },
241
269
  },
242
270
  required: ['human_id', 'title', 'description', 'price_usdc', 'agent_id', 'agent_key'],
243
271
  },
@@ -320,7 +348,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
320
348
  },
321
349
  {
322
350
  name: 'get_human_profile',
323
- description: 'Get the full profile of a human including contact info, wallet addresses, and social links. Requires an ACTIVE agent API key.',
351
+ description: 'Get the full profile of a human including contact info, wallet addresses, and social links. Requires an ACTIVE agent API key. Alternative: agents can pay $0.05 per view via x402 (USDC on Base) by including an x-payment header — no activation needed.',
324
352
  inputSchema: {
325
353
  type: 'object',
326
354
  properties: {
@@ -418,6 +446,332 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
418
446
  required: ['agent_key', 'tx_hash', 'network'],
419
447
  },
420
448
  },
449
+ {
450
+ name: 'start_stream',
451
+ description: 'Start a stream payment for an ACCEPTED stream job. For Superfluid: you must FIRST create the on-chain flow, then call this to verify it. Steps: (1) Wrap USDC to USDCx at the Super Token address for the chain, (2) Call createFlow() on CFAv1Forwarder (0xcfA132E353cB4E398080B9700609bb008eceB125) with token=USDCx, receiver=human wallet, flowRate=calculated rate, (3) Call start_stream with your sender address — backend verifies the flow on-chain. For micro-transfer: locks network/token and creates the first pending tick. Prefer L2s (Base, Arbitrum, Polygon) for lower gas costs.',
452
+ inputSchema: {
453
+ type: 'object',
454
+ properties: {
455
+ job_id: { type: 'string', description: 'The job ID' },
456
+ agent_key: { type: 'string', description: 'Your agent API key (starts with hp_)' },
457
+ sender_address: { type: 'string', description: 'Your wallet address that created the flow (Superfluid) or will send payments (micro-transfer)' },
458
+ network: { type: 'string', description: 'Blockchain network (e.g., "base", "polygon", "arbitrum")' },
459
+ token: { type: 'string', description: 'Token symbol (default: "USDC")' },
460
+ },
461
+ required: ['job_id', 'agent_key', 'sender_address', 'network'],
462
+ },
463
+ },
464
+ {
465
+ name: 'record_stream_tick',
466
+ description: 'Record a micro-transfer stream payment. Submit the transaction hash for the current pending tick. Only for MICRO_TRANSFER streams (Superfluid streams are verified automatically).',
467
+ inputSchema: {
468
+ type: 'object',
469
+ properties: {
470
+ job_id: { type: 'string', description: 'The job ID' },
471
+ agent_key: { type: 'string', description: 'Your agent API key (starts with hp_)' },
472
+ tx_hash: { type: 'string', description: 'The on-chain transaction hash for this tick payment' },
473
+ },
474
+ required: ['job_id', 'agent_key', 'tx_hash'],
475
+ },
476
+ },
477
+ {
478
+ name: 'pause_stream',
479
+ description: 'Pause an active stream. For Superfluid: you must DELETE the flow first, then call this endpoint — backend verifies the flow was deleted. For micro-transfer: skips the current pending tick.',
480
+ inputSchema: {
481
+ type: 'object',
482
+ properties: {
483
+ job_id: { type: 'string', description: 'The job ID' },
484
+ agent_key: { type: 'string', description: 'Your agent API key (starts with hp_)' },
485
+ },
486
+ required: ['job_id', 'agent_key'],
487
+ },
488
+ },
489
+ {
490
+ name: 'resume_stream',
491
+ description: 'Resume a paused stream. For Superfluid: create a new flow first, then call this — backend verifies. For micro-transfer: creates a new pending tick.',
492
+ inputSchema: {
493
+ type: 'object',
494
+ properties: {
495
+ job_id: { type: 'string', description: 'The job ID' },
496
+ agent_key: { type: 'string', description: 'Your agent API key (starts with hp_)' },
497
+ sender_address: { type: 'string', description: 'Wallet address for the new flow (Superfluid only, optional if same as before)' },
498
+ },
499
+ required: ['job_id', 'agent_key'],
500
+ },
501
+ },
502
+ {
503
+ name: 'stop_stream',
504
+ description: 'Stop a stream permanently and mark the job as completed. Can be called by agent or human on STREAMING or PAUSED jobs.',
505
+ inputSchema: {
506
+ type: 'object',
507
+ properties: {
508
+ job_id: { type: 'string', description: 'The job ID' },
509
+ agent_key: { type: 'string', description: 'Your agent API key (starts with hp_)' },
510
+ },
511
+ required: ['job_id', 'agent_key'],
512
+ },
513
+ },
514
+ {
515
+ name: 'send_job_message',
516
+ description: 'Send a message on a job. Agents can message the human they hired, and vice versa. Works on PENDING, ACCEPTED, PAID, STREAMING, and PAUSED jobs. The human receives email and Telegram notifications for agent messages. Rate limit: 10 messages/minute.',
517
+ inputSchema: {
518
+ type: 'object',
519
+ properties: {
520
+ job_id: {
521
+ type: 'string',
522
+ description: 'The job ID',
523
+ },
524
+ agent_key: {
525
+ type: 'string',
526
+ description: 'Your agent API key (starts with hp_)',
527
+ },
528
+ content: {
529
+ type: 'string',
530
+ description: 'Message content (max 2000 characters)',
531
+ },
532
+ },
533
+ required: ['job_id', 'agent_key', 'content'],
534
+ },
535
+ },
536
+ {
537
+ name: 'get_job_messages',
538
+ description: 'Get all messages for a job, ordered chronologically. Returns messages from both the agent and the human. Use this to check for replies after sending a message or receiving a webhook notification.',
539
+ inputSchema: {
540
+ type: 'object',
541
+ properties: {
542
+ job_id: {
543
+ type: 'string',
544
+ description: 'The job ID',
545
+ },
546
+ agent_key: {
547
+ type: 'string',
548
+ description: 'Your agent API key (starts with hp_)',
549
+ },
550
+ },
551
+ required: ['job_id', 'agent_key'],
552
+ },
553
+ },
554
+ {
555
+ name: 'create_listing',
556
+ description: 'Post a job listing on the Human Pages job board for humans to discover and apply to. Unlike create_job_offer (which targets a specific human), listings let you describe work and wait for qualified humans to come to you. Requires an ACTIVE agent or x402 payment ($0.50 USDC). RATE LIMITS: BASIC = 1 listing/week, PRO = 5 listings/day. x402 bypasses limits.',
557
+ inputSchema: {
558
+ type: 'object',
559
+ properties: {
560
+ agent_key: {
561
+ type: 'string',
562
+ description: 'Your agent API key (starts with hp_)',
563
+ },
564
+ title: {
565
+ type: 'string',
566
+ description: 'Title of the listing (e.g., "Social media promotion for AI product")',
567
+ },
568
+ description: {
569
+ type: 'string',
570
+ description: 'Detailed description of the work, expectations, and deliverables',
571
+ },
572
+ budget_usdc: {
573
+ type: 'number',
574
+ description: 'Budget in USDC (minimum $5)',
575
+ },
576
+ category: {
577
+ type: 'string',
578
+ description: 'Category (e.g., "marketing", "photography", "research")',
579
+ },
580
+ required_skills: {
581
+ type: 'array',
582
+ items: { type: 'string' },
583
+ description: 'Skills applicants should have (e.g., ["social-media", "copywriting"])',
584
+ },
585
+ required_equipment: {
586
+ type: 'array',
587
+ items: { type: 'string' },
588
+ description: 'Equipment applicants should have (e.g., ["camera", "drone"])',
589
+ },
590
+ location: {
591
+ type: 'string',
592
+ description: 'Location name for the work (e.g., "San Francisco")',
593
+ },
594
+ location_lat: {
595
+ type: 'number',
596
+ description: 'Latitude for location-based filtering',
597
+ },
598
+ location_lng: {
599
+ type: 'number',
600
+ description: 'Longitude for location-based filtering',
601
+ },
602
+ radius_km: {
603
+ type: 'number',
604
+ description: 'Radius in km for location-based filtering',
605
+ },
606
+ work_mode: {
607
+ type: 'string',
608
+ enum: ['REMOTE', 'ONSITE', 'HYBRID'],
609
+ description: 'Work mode for the listing',
610
+ },
611
+ expires_at: {
612
+ type: 'string',
613
+ description: 'ISO 8601 expiration date (must be in future, max 90 days). Example: "2025-03-01T00:00:00Z"',
614
+ },
615
+ max_applicants: {
616
+ type: 'number',
617
+ description: 'Maximum number of applicants before listing auto-closes',
618
+ },
619
+ callback_url: {
620
+ type: 'string',
621
+ description: 'Webhook URL for application notifications',
622
+ },
623
+ callback_secret: {
624
+ type: 'string',
625
+ description: 'Secret for HMAC-SHA256 webhook signature (min 16 chars)',
626
+ },
627
+ },
628
+ required: ['agent_key', 'title', 'description', 'budget_usdc', 'expires_at'],
629
+ },
630
+ },
631
+ {
632
+ name: 'get_listings',
633
+ description: 'Browse open job listings on the Human Pages job board. Returns listings with agent reputation and application counts. Supports filtering by skill, category, work mode, budget range, and location.',
634
+ inputSchema: {
635
+ type: 'object',
636
+ properties: {
637
+ page: {
638
+ type: 'number',
639
+ description: 'Page number (default: 1)',
640
+ },
641
+ limit: {
642
+ type: 'number',
643
+ description: 'Results per page (default: 20, max: 50)',
644
+ },
645
+ skill: {
646
+ type: 'string',
647
+ description: 'Filter by required skill (comma-separated for multiple, e.g., "photography,editing")',
648
+ },
649
+ category: {
650
+ type: 'string',
651
+ description: 'Filter by category',
652
+ },
653
+ work_mode: {
654
+ type: 'string',
655
+ enum: ['REMOTE', 'ONSITE', 'HYBRID'],
656
+ description: 'Filter by work mode',
657
+ },
658
+ min_budget: {
659
+ type: 'number',
660
+ description: 'Minimum budget in USDC',
661
+ },
662
+ max_budget: {
663
+ type: 'number',
664
+ description: 'Maximum budget in USDC',
665
+ },
666
+ lat: {
667
+ type: 'number',
668
+ description: 'Latitude for location-based filtering',
669
+ },
670
+ lng: {
671
+ type: 'number',
672
+ description: 'Longitude for location-based filtering',
673
+ },
674
+ radius: {
675
+ type: 'number',
676
+ description: 'Radius in km for location-based filtering',
677
+ },
678
+ },
679
+ },
680
+ },
681
+ {
682
+ name: 'get_listing',
683
+ description: 'Get detailed information about a specific listing, including the posting agent\'s reputation and application count.',
684
+ inputSchema: {
685
+ type: 'object',
686
+ properties: {
687
+ listing_id: {
688
+ type: 'string',
689
+ description: 'The listing ID',
690
+ },
691
+ },
692
+ required: ['listing_id'],
693
+ },
694
+ },
695
+ {
696
+ name: 'get_listing_applications',
697
+ description: 'View applications for a listing you created. Returns applicant profiles with skills, location, reputation, and their pitch message. Use this to evaluate candidates before making an offer.',
698
+ inputSchema: {
699
+ type: 'object',
700
+ properties: {
701
+ listing_id: {
702
+ type: 'string',
703
+ description: 'The listing ID',
704
+ },
705
+ agent_key: {
706
+ type: 'string',
707
+ description: 'Your agent API key (starts with hp_)',
708
+ },
709
+ },
710
+ required: ['listing_id', 'agent_key'],
711
+ },
712
+ },
713
+ {
714
+ name: 'make_listing_offer',
715
+ description: 'Make a job offer to a listing applicant. This creates a standard job from the listing and notifies the human. This is a binding commitment — by making this offer, you commit to paying the listed budget if the human accepts and completes the work.',
716
+ inputSchema: {
717
+ type: 'object',
718
+ properties: {
719
+ listing_id: {
720
+ type: 'string',
721
+ description: 'The listing ID',
722
+ },
723
+ application_id: {
724
+ type: 'string',
725
+ description: 'The application ID of the chosen applicant',
726
+ },
727
+ agent_key: {
728
+ type: 'string',
729
+ description: 'Your agent API key (starts with hp_)',
730
+ },
731
+ },
732
+ required: ['listing_id', 'application_id', 'agent_key'],
733
+ },
734
+ },
735
+ {
736
+ name: 'cancel_listing',
737
+ description: 'Cancel an open listing. All pending applications will be rejected. Only the agent who created the listing can cancel it.',
738
+ inputSchema: {
739
+ type: 'object',
740
+ properties: {
741
+ listing_id: {
742
+ type: 'string',
743
+ description: 'The listing ID',
744
+ },
745
+ agent_key: {
746
+ type: 'string',
747
+ description: 'Your agent API key (starts with hp_)',
748
+ },
749
+ },
750
+ required: ['listing_id', 'agent_key'],
751
+ },
752
+ },
753
+ {
754
+ name: 'get_promo_status',
755
+ description: 'Check the launch promo status — free PRO tier for the first 100 agents. Returns how many slots are claimed and remaining. No authentication required.',
756
+ inputSchema: {
757
+ type: 'object',
758
+ properties: {},
759
+ },
760
+ },
761
+ {
762
+ name: 'claim_free_pro_upgrade',
763
+ description: 'Claim a free PRO tier upgrade via the launch promo (first 100 agents). Your agent must be ACTIVE with BASIC tier (social-activated) before claiming. On success, your tier is upgraded to PRO with 60-day duration.',
764
+ inputSchema: {
765
+ type: 'object',
766
+ properties: {
767
+ agent_key: {
768
+ type: 'string',
769
+ description: 'Your registered agent API key (starts with hp_)',
770
+ },
771
+ },
772
+ required: ['agent_key'],
773
+ },
774
+ },
421
775
  ],
422
776
  }));
423
777
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -457,7 +811,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
457
811
  const displayLocation = h.locationGranularity === 'neighborhood' && h.neighborhood && h.location
458
812
  ? `${h.neighborhood}, ${h.location}`
459
813
  : h.location || 'Location not specified';
460
- return `- **${h.name}**${h.username ? ` (@${h.username})` : ''} [${displayLocation}]
814
+ const displayName = h.name || h.username || 'Name hidden';
815
+ return `- **${displayName}**${h.username && h.name ? ` (@${h.username})` : ''} [${displayLocation}]
461
816
  ${h.isAvailable ? '✅ Available' : '❌ Busy'} | ${rateDisplay} | ${rating}
462
817
  ${humanityStatus}
463
818
  Skills: ${h.skills.join(', ') || 'None listed'}
@@ -646,6 +1001,12 @@ Your agent profile now shows a verified badge. Humans will see this when reviewi
646
1001
  description: args?.description,
647
1002
  category: args?.category,
648
1003
  priceUsdc: args?.price_usdc,
1004
+ paymentMode: args?.payment_mode,
1005
+ paymentTiming: args?.payment_timing,
1006
+ streamMethod: args?.stream_method,
1007
+ streamInterval: args?.stream_interval,
1008
+ streamRateUsdc: args?.stream_rate_usdc,
1009
+ streamMaxTicks: args?.stream_max_ticks,
649
1010
  callbackUrl: args?.callback_url,
650
1011
  callbackSecret: args?.callback_secret,
651
1012
  }),
@@ -694,19 +1055,31 @@ Once accepted, you can send payment to their wallet and use \`mark_job_paid\` to
694
1055
  ACCEPTED: '✅',
695
1056
  REJECTED: '❌',
696
1057
  PAID: '💰',
1058
+ STREAMING: '🔄',
1059
+ PAUSED: '⏸️',
697
1060
  COMPLETED: '🎉',
698
1061
  CANCELLED: '🚫',
699
1062
  DISPUTED: '⚠️',
700
1063
  };
1064
+ const isStream = job.paymentMode === 'STREAM';
1065
+ const streamSummary = job.streamSummary;
701
1066
  let nextStep = '';
702
1067
  switch (job.status) {
703
1068
  case 'PENDING':
704
1069
  nextStep = 'Waiting for the human to accept or reject.';
705
1070
  break;
706
1071
  case 'ACCEPTED':
707
- nextStep = job.callbackUrl
708
- ? `Human accepted! Contact info was sent to your webhook. Send $${job.priceUsdc} USDC to their wallet, then use \`mark_job_paid\` with the transaction hash.`
709
- : `Human accepted! Send $${job.priceUsdc} USDC to their wallet, then use \`mark_job_paid\` with the transaction hash.`;
1072
+ if (isStream) {
1073
+ const method = job.streamMethod;
1074
+ nextStep = method === 'SUPERFLUID'
1075
+ ? 'Human accepted! Create a Superfluid flow to their wallet, then use `start_stream` with your sender address to verify.'
1076
+ : 'Human accepted! Use `start_stream` to lock the network/token and start sending payments.';
1077
+ }
1078
+ else {
1079
+ nextStep = job.callbackUrl
1080
+ ? `Human accepted! Contact info was sent to your webhook. Send $${job.priceUsdc} USDC to their wallet, then use \`mark_job_paid\` with the transaction hash.`
1081
+ : `Human accepted! Send $${job.priceUsdc} USDC to their wallet, then use \`mark_job_paid\` with the transaction hash.`;
1082
+ }
710
1083
  break;
711
1084
  case 'REJECTED':
712
1085
  nextStep = 'The human rejected this offer. Consider adjusting your offer or finding another human.';
@@ -714,6 +1087,17 @@ Once accepted, you can send payment to their wallet and use \`mark_job_paid\` to
714
1087
  case 'PAID':
715
1088
  nextStep = 'Payment recorded. Work is in progress. The human will mark it complete when done.';
716
1089
  break;
1090
+ case 'STREAMING':
1091
+ if (streamSummary?.method === 'SUPERFLUID') {
1092
+ nextStep = `Stream active via Superfluid. Total streamed: $${streamSummary?.totalPaid || '0'} USDC. Use \`pause_stream\` or \`stop_stream\` to manage.`;
1093
+ }
1094
+ else {
1095
+ nextStep = `Stream active via micro-transfer. Total paid: $${streamSummary?.totalPaid || '0'} USDC. Use \`record_stream_tick\` to submit each payment.`;
1096
+ }
1097
+ break;
1098
+ case 'PAUSED':
1099
+ nextStep = 'Stream is paused. Use `resume_stream` to continue or `stop_stream` to end permanently.';
1100
+ break;
717
1101
  case 'COMPLETED':
718
1102
  nextStep = job.review
719
1103
  ? `Review submitted: ${job.review.rating}/5 stars`
@@ -725,6 +1109,14 @@ Once accepted, you can send payment to their wallet and use \`mark_job_paid\` to
725
1109
  : job.agentName
726
1110
  ? `**Agent:** ${job.agentName}`
727
1111
  : '';
1112
+ let streamInfo = '';
1113
+ if (isStream && streamSummary) {
1114
+ streamInfo = `\n**Payment Mode:** STREAM (${streamSummary.method})
1115
+ **Rate:** $${streamSummary.rateUsdc || '?'}/${(streamSummary.interval || 'DAILY').toLowerCase()}
1116
+ **Total Paid:** $${streamSummary.totalPaid || '0'} USDC
1117
+ **Ticks:** ${streamSummary.tickCount || 0}${streamSummary.maxTicks ? `/${streamSummary.maxTicks}` : ''}
1118
+ **Network:** ${streamSummary.network || 'Not set'}`;
1119
+ }
728
1120
  return {
729
1121
  content: [
730
1122
  {
@@ -736,7 +1128,7 @@ Once accepted, you can send payment to their wallet and use \`mark_job_paid\` to
736
1128
  **Title:** ${job.title}
737
1129
  **Price:** $${job.priceUsdc} USDC
738
1130
  **Human:** ${job.human.name}
739
- ${agentInfo ? agentInfo + '\n' : ''}
1131
+ ${agentInfo ? agentInfo + '\n' : ''}${streamInfo}
740
1132
  **Next Step:** ${nextStep}`,
741
1133
  },
742
1134
  ],
@@ -934,6 +1326,12 @@ You can now create job offers and view full human profiles using \`get_human_pro
934
1326
  throw new Error(error.error || `API error: ${res.status}`);
935
1327
  }
936
1328
  const result = await res.json();
1329
+ const limits = result.limits;
1330
+ const jobLimit = limits?.jobOffersPerDay
1331
+ ? `${limits.jobOffersPerDay}/day`
1332
+ : limits?.jobOffersPerTwoDays
1333
+ ? `${limits.jobOffersPerTwoDays}/2 days`
1334
+ : 'N/A';
937
1335
  return {
938
1336
  content: [
939
1337
  {
@@ -941,9 +1339,11 @@ You can now create job offers and view full human profiles using \`get_human_pro
941
1339
  text: `**Activation Status**
942
1340
 
943
1341
  **Status:** ${result.status}
944
- **Tier:** ${result.tier}
945
- **Expires:** ${result.expiresAt || 'N/A'}
946
- **Jobs Today:** ${result.jobsToday}/${result.jobLimit}`,
1342
+ **Tier:** ${result.tier || 'BASIC'}
1343
+ **Activated:** ${result.activatedAt || 'Not yet'}
1344
+ **Expires:** ${result.activationExpiresAt || 'N/A'}
1345
+ **Profile views:** ${limits?.profileViewsPerDay ?? 'N/A'}/day
1346
+ **Job offers:** ${jobLimit}${result.x402?.enabled ? `\n**x402 pay-per-use:** profile view ${result.x402.prices.profile_view}, job offer ${result.x402.prices.job_offer}` : ''}`,
947
1347
  },
948
1348
  ],
949
1349
  };
@@ -979,7 +1379,7 @@ You can now create job offers and view full human profiles using \`get_human_pro
979
1379
  **Next Steps:**
980
1380
  1. Send exactly ${result.amount} ${result.currency} to the address above on ${result.network}
981
1381
  2. Use \`verify_payment_activation\` with the transaction hash and network
982
- 3. Once verified, your agent will be activated with PRO tier (15 jobs/day)`,
1382
+ 3. Once verified, your agent will be activated with PRO tier (15 jobs/day, 50 profile views/day)`,
983
1383
  },
984
1384
  ],
985
1385
  };
@@ -1015,11 +1415,445 @@ You can now create job offers and view full human profiles using \`get_human_pro
1015
1415
  **Tier:** ${result.tier}
1016
1416
  **Expires:** ${result.expiresAt}
1017
1417
 
1018
- You can now create up to 15 job offers per day and view full human profiles using \`get_human_profile\`.`,
1418
+ You can now create up to 15 job offers per day and view up to 50 full human profiles per day using \`get_human_profile\`.`,
1019
1419
  },
1020
1420
  ],
1021
1421
  };
1022
1422
  }
1423
+ if (name === 'start_stream') {
1424
+ const agentKey = args?.agent_key;
1425
+ if (!agentKey)
1426
+ throw new Error('agent_key is required.');
1427
+ const res = await fetch(`${API_BASE}/api/jobs/${args?.job_id}/start-stream`, {
1428
+ method: 'PATCH',
1429
+ headers: {
1430
+ 'Content-Type': 'application/json',
1431
+ 'X-Agent-Key': agentKey,
1432
+ },
1433
+ body: JSON.stringify({
1434
+ senderAddress: args?.sender_address,
1435
+ network: args?.network,
1436
+ token: args?.token || 'USDC',
1437
+ }),
1438
+ });
1439
+ if (!res.ok) {
1440
+ const error = await res.json();
1441
+ throw new Error(error.hint || error.error || `API error: ${res.status}`);
1442
+ }
1443
+ const result = await res.json();
1444
+ return {
1445
+ content: [{
1446
+ type: 'text',
1447
+ text: `**Stream Started!**\n\n**Job ID:** ${result.id}\n**Status:** ${result.status}\n**Method:** ${result.stream?.method}\n**Network:** ${result.stream?.network}\n\n${result.message}${result.stream?.receiverWallet ? `\n\n**Send payments to:** ${result.stream.receiverWallet}` : ''}`,
1448
+ }],
1449
+ };
1450
+ }
1451
+ if (name === 'record_stream_tick') {
1452
+ const agentKey = args?.agent_key;
1453
+ if (!agentKey)
1454
+ throw new Error('agent_key is required.');
1455
+ const res = await fetch(`${API_BASE}/api/jobs/${args?.job_id}/stream-tick`, {
1456
+ method: 'PATCH',
1457
+ headers: {
1458
+ 'Content-Type': 'application/json',
1459
+ 'X-Agent-Key': agentKey,
1460
+ },
1461
+ body: JSON.stringify({ txHash: args?.tx_hash }),
1462
+ });
1463
+ if (!res.ok) {
1464
+ const error = await res.json();
1465
+ throw new Error(error.error || `API error: ${res.status}`);
1466
+ }
1467
+ const result = await res.json();
1468
+ return {
1469
+ content: [{
1470
+ type: 'text',
1471
+ text: `**Tick Verified!**\n\n**Job ID:** ${result.id}\n**Status:** ${result.status}\n**Tick:** #${result.tick?.tickNumber}\n**Amount:** $${result.tick?.amount} USDC\n**Total Paid:** $${result.totalPaid} USDC${result.nextTick ? `\n\n**Next payment due:** ${result.nextTick.expectedAt}` : ''}`,
1472
+ }],
1473
+ };
1474
+ }
1475
+ if (name === 'pause_stream') {
1476
+ const agentKey = args?.agent_key;
1477
+ if (!agentKey)
1478
+ throw new Error('agent_key is required.');
1479
+ const res = await fetch(`${API_BASE}/api/jobs/${args?.job_id}/pause-stream`, {
1480
+ method: 'PATCH',
1481
+ headers: {
1482
+ 'Content-Type': 'application/json',
1483
+ 'X-Agent-Key': agentKey,
1484
+ },
1485
+ });
1486
+ if (!res.ok) {
1487
+ const error = await res.json();
1488
+ throw new Error(error.hint || error.error || `API error: ${res.status}`);
1489
+ }
1490
+ const result = await res.json();
1491
+ return {
1492
+ content: [{
1493
+ type: 'text',
1494
+ text: `**Stream Paused**\n\n**Job ID:** ${result.id}\n**Status:** ${result.status}\n\nUse \`resume_stream\` to continue or \`stop_stream\` to end permanently.`,
1495
+ }],
1496
+ };
1497
+ }
1498
+ if (name === 'resume_stream') {
1499
+ const agentKey = args?.agent_key;
1500
+ if (!agentKey)
1501
+ throw new Error('agent_key is required.');
1502
+ const res = await fetch(`${API_BASE}/api/jobs/${args?.job_id}/resume-stream`, {
1503
+ method: 'PATCH',
1504
+ headers: {
1505
+ 'Content-Type': 'application/json',
1506
+ 'X-Agent-Key': agentKey,
1507
+ },
1508
+ body: JSON.stringify({
1509
+ senderAddress: args?.sender_address,
1510
+ }),
1511
+ });
1512
+ if (!res.ok) {
1513
+ const error = await res.json();
1514
+ throw new Error(error.hint || error.error || `API error: ${res.status}`);
1515
+ }
1516
+ const result = await res.json();
1517
+ return {
1518
+ content: [{
1519
+ type: 'text',
1520
+ text: `**Stream Resumed!**\n\n**Job ID:** ${result.id}\n**Status:** ${result.status}\n\nStream is active again.`,
1521
+ }],
1522
+ };
1523
+ }
1524
+ if (name === 'stop_stream') {
1525
+ const agentKey = args?.agent_key;
1526
+ if (!agentKey)
1527
+ throw new Error('agent_key is required.');
1528
+ const res = await fetch(`${API_BASE}/api/jobs/${args?.job_id}/stop-stream`, {
1529
+ method: 'PATCH',
1530
+ headers: {
1531
+ 'Content-Type': 'application/json',
1532
+ 'X-Agent-Key': agentKey,
1533
+ },
1534
+ });
1535
+ if (!res.ok) {
1536
+ const error = await res.json();
1537
+ throw new Error(error.error || `API error: ${res.status}`);
1538
+ }
1539
+ const result = await res.json();
1540
+ return {
1541
+ content: [{
1542
+ type: 'text',
1543
+ text: `**Stream Stopped**\n\n**Job ID:** ${result.id}\n**Status:** ${result.status}\n**Total Paid:** $${result.totalPaid || '0'} USDC\n\nThe stream has ended. You can now use \`leave_review\` to rate the human.`,
1544
+ }],
1545
+ };
1546
+ }
1547
+ if (name === 'send_job_message') {
1548
+ const agentKey = args?.agent_key;
1549
+ if (!agentKey)
1550
+ throw new Error('agent_key is required.');
1551
+ const res = await fetch(`${API_BASE}/api/jobs/${args?.job_id}/messages`, {
1552
+ method: 'POST',
1553
+ headers: {
1554
+ 'Content-Type': 'application/json',
1555
+ 'X-Agent-Key': agentKey,
1556
+ },
1557
+ body: JSON.stringify({ content: args?.content }),
1558
+ });
1559
+ if (!res.ok) {
1560
+ const error = await res.json();
1561
+ throw new Error(error.error || `API error: ${res.status}`);
1562
+ }
1563
+ const message = await res.json();
1564
+ return {
1565
+ content: [{
1566
+ type: 'text',
1567
+ text: `**Message Sent!**\n\n**Message ID:** ${message.id}\n**From:** ${message.senderName} (${message.senderType})\n**Content:** ${message.content}\n**Sent:** ${message.createdAt}\n\nThe human will be notified via email and Telegram (if connected). Use \`get_job_messages\` to check for replies.`,
1568
+ }],
1569
+ };
1570
+ }
1571
+ if (name === 'get_job_messages') {
1572
+ const agentKey = args?.agent_key;
1573
+ if (!agentKey)
1574
+ throw new Error('agent_key is required.');
1575
+ const res = await fetch(`${API_BASE}/api/jobs/${args?.job_id}/messages`, {
1576
+ headers: { 'X-Agent-Key': agentKey },
1577
+ });
1578
+ if (!res.ok) {
1579
+ const error = await res.json();
1580
+ throw new Error(error.error || `API error: ${res.status}`);
1581
+ }
1582
+ const messages = await res.json();
1583
+ if (messages.length === 0) {
1584
+ return {
1585
+ content: [{ type: 'text', text: 'No messages yet on this job.' }],
1586
+ };
1587
+ }
1588
+ const formatted = messages.map((m) => `**${m.senderName}** (${m.senderType}) — ${m.createdAt}\n${m.content}`).join('\n\n---\n\n');
1589
+ return {
1590
+ content: [{
1591
+ type: 'text',
1592
+ text: `**Job Messages** (${messages.length} total)\n\n${formatted}`,
1593
+ }],
1594
+ };
1595
+ }
1596
+ if (name === 'create_listing') {
1597
+ const agentKey = args?.agent_key;
1598
+ if (!agentKey)
1599
+ throw new Error('agent_key is required.');
1600
+ const res = await fetch(`${API_BASE}/api/listings`, {
1601
+ method: 'POST',
1602
+ headers: {
1603
+ 'Content-Type': 'application/json',
1604
+ 'X-Agent-Key': agentKey,
1605
+ },
1606
+ body: JSON.stringify({
1607
+ title: args?.title,
1608
+ description: args?.description,
1609
+ budgetUsdc: args?.budget_usdc,
1610
+ category: args?.category,
1611
+ requiredSkills: args?.required_skills || [],
1612
+ requiredEquipment: args?.required_equipment || [],
1613
+ location: args?.location,
1614
+ locationLat: args?.location_lat,
1615
+ locationLng: args?.location_lng,
1616
+ radiusKm: args?.radius_km,
1617
+ workMode: args?.work_mode,
1618
+ expiresAt: args?.expires_at,
1619
+ maxApplicants: args?.max_applicants,
1620
+ callbackUrl: args?.callback_url,
1621
+ callbackSecret: args?.callback_secret,
1622
+ }),
1623
+ });
1624
+ if (!res.ok) {
1625
+ const error = await res.json();
1626
+ if (res.status === 403 && error.code === 'AGENT_PENDING') {
1627
+ throw new Error('Agent is not yet activated. You must activate before creating listings.\n'
1628
+ + '- Free (BASIC tier): Use `request_activation_code` → post on social media → `verify_social_activation`\n'
1629
+ + '- Paid (PRO tier): Use `get_payment_activation` → send payment → `verify_payment_activation`');
1630
+ }
1631
+ throw new Error(error.message || error.error || `API error: ${res.status}`);
1632
+ }
1633
+ const result = await res.json();
1634
+ let rateLimitInfo = '';
1635
+ if (result.paidVia) {
1636
+ rateLimitInfo = '\n**Paid via:** x402';
1637
+ }
1638
+ else if (result.rateLimit) {
1639
+ rateLimitInfo = `\n**Rate Limit:** ${result.rateLimit.remaining} listings remaining (${result.rateLimit.tier} tier, resets in ${result.rateLimit.resetIn})`;
1640
+ }
1641
+ return {
1642
+ content: [{
1643
+ type: 'text',
1644
+ text: `**Listing Created!**\n\n**Listing ID:** ${result.id}\n**Status:** ${result.status}${rateLimitInfo}\n\nYour listing is now live on the job board. Humans can browse and apply.\n\nUse \`get_listing_applications\` with listing_id "${result.id}" to review applicants.\nUse \`make_listing_offer\` to hire an applicant.`,
1645
+ }],
1646
+ };
1647
+ }
1648
+ if (name === 'get_listings') {
1649
+ const query = new URLSearchParams();
1650
+ if (args?.page)
1651
+ query.set('page', String(args.page));
1652
+ if (args?.limit)
1653
+ query.set('limit', String(args.limit));
1654
+ if (args?.skill)
1655
+ query.set('skill', args.skill);
1656
+ if (args?.category)
1657
+ query.set('category', args.category);
1658
+ if (args?.work_mode)
1659
+ query.set('workMode', args.work_mode);
1660
+ if (args?.min_budget)
1661
+ query.set('minBudget', String(args.min_budget));
1662
+ if (args?.max_budget)
1663
+ query.set('maxBudget', String(args.max_budget));
1664
+ if (args?.lat)
1665
+ query.set('lat', String(args.lat));
1666
+ if (args?.lng)
1667
+ query.set('lng', String(args.lng));
1668
+ if (args?.radius)
1669
+ query.set('radius', String(args.radius));
1670
+ const res = await fetch(`${API_BASE}/api/listings?${query}`);
1671
+ if (!res.ok) {
1672
+ throw new Error(`API error: ${res.status}`);
1673
+ }
1674
+ const result = await res.json();
1675
+ if (result.listings.length === 0) {
1676
+ return {
1677
+ content: [{ type: 'text', text: 'No open listings found matching the criteria.' }],
1678
+ };
1679
+ }
1680
+ const summary = result.listings.map((l) => {
1681
+ const agent = l.agent;
1682
+ const rep = l.agentReputation;
1683
+ const agentInfo = agent ? `${agent.name}${agent.domainVerified ? ' ✅' : ''}` : 'Unknown';
1684
+ const repInfo = rep?.completedJobs > 0 ? ` | ${rep.completedJobs} jobs, ${rep.avgRating ? `${rep.avgRating.toFixed(1)}★` : 'no ratings'}` : '';
1685
+ return `- **${l.title}** [$${l.budgetUsdc} USDC]${l.isPro ? ' 🏆 PRO' : ''}
1686
+ Agent: ${agentInfo}${repInfo}
1687
+ ${l.category ? `Category: ${l.category} | ` : ''}${l.workMode || 'Any'} | ${l._count?.applications || 0} applicant(s)
1688
+ ${l.requiredSkills?.length > 0 ? `Skills: ${l.requiredSkills.join(', ')}` : ''}
1689
+ ${l.location ? `Location: ${l.location}` : ''}
1690
+ Expires: ${l.expiresAt}
1691
+ ID: ${l.id}`;
1692
+ }).join('\n\n');
1693
+ return {
1694
+ content: [{
1695
+ type: 'text',
1696
+ text: `**Open Listings** (page ${result.pagination.page}/${result.pagination.totalPages}, ${result.pagination.total} total)\n\n${summary}`,
1697
+ }],
1698
+ };
1699
+ }
1700
+ if (name === 'get_listing') {
1701
+ const res = await fetch(`${API_BASE}/api/listings/${args?.listing_id}`);
1702
+ if (!res.ok) {
1703
+ if (res.status === 404)
1704
+ throw new Error(`Listing not found: ${args?.listing_id}`);
1705
+ throw new Error(`API error: ${res.status}`);
1706
+ }
1707
+ const listing = await res.json();
1708
+ const agent = listing.agent;
1709
+ const rep = listing.agentReputation;
1710
+ const details = `# ${listing.title}${listing.isPro ? ' 🏆 PRO' : ''}
1711
+
1712
+ **Listing ID:** ${listing.id}
1713
+ **Status:** ${listing.status}
1714
+ **Budget:** $${listing.budgetUsdc} USDC
1715
+ **Category:** ${listing.category || 'Not specified'}
1716
+ **Work Mode:** ${listing.workMode || 'Any'}
1717
+ **Expires:** ${listing.expiresAt}
1718
+ **Applications:** ${listing._count?.applications || 0}${listing.maxApplicants ? `/${listing.maxApplicants}` : ''}
1719
+
1720
+ ## Description
1721
+ ${listing.description}
1722
+
1723
+ ## Requirements
1724
+ - **Skills:** ${listing.requiredSkills?.join(', ') || 'None specified'}
1725
+ - **Equipment:** ${listing.requiredEquipment?.join(', ') || 'None specified'}
1726
+ ${listing.location ? `- **Location:** ${listing.location}` : ''}
1727
+
1728
+ ## Posted By
1729
+ - **Agent:** ${agent?.name || 'Unknown'}${agent?.domainVerified ? ' ✅ Verified' : ''}
1730
+ - **Description:** ${agent?.description || 'N/A'}
1731
+ ${agent?.websiteUrl ? `- **Website:** ${agent.websiteUrl}` : ''}
1732
+ - **Jobs Completed:** ${rep?.completedJobs || 0}
1733
+ - **Rating:** ${rep?.avgRating ? `${rep.avgRating.toFixed(1)}★` : 'No ratings'}
1734
+ - **Avg Payment Speed:** ${rep?.avgPaymentSpeedHours != null ? `${rep.avgPaymentSpeedHours} hours` : 'N/A'}`;
1735
+ return {
1736
+ content: [{ type: 'text', text: details }],
1737
+ };
1738
+ }
1739
+ if (name === 'get_listing_applications') {
1740
+ const agentKey = args?.agent_key;
1741
+ if (!agentKey)
1742
+ throw new Error('agent_key is required.');
1743
+ const res = await fetch(`${API_BASE}/api/listings/${args?.listing_id}/applications`, {
1744
+ headers: { 'X-Agent-Key': agentKey },
1745
+ });
1746
+ if (!res.ok) {
1747
+ const error = await res.json();
1748
+ throw new Error(error.error || `API error: ${res.status}`);
1749
+ }
1750
+ const applications = await res.json();
1751
+ if (applications.length === 0) {
1752
+ return {
1753
+ content: [{ type: 'text', text: 'No applications yet for this listing.' }],
1754
+ };
1755
+ }
1756
+ const summary = applications.map((app) => {
1757
+ const h = app.human;
1758
+ const rep = h?.reputation;
1759
+ const ratingStr = rep?.avgRating ? `${rep.avgRating.toFixed(1)}★` : 'No ratings';
1760
+ return `- **${h?.name || 'Unknown'}** [${app.status}]
1761
+ Application ID: ${app.id}
1762
+ Skills: ${h?.skills?.join(', ') || 'None listed'}
1763
+ Equipment: ${h?.equipment?.join(', ') || 'None listed'}
1764
+ Location: ${h?.location || 'Not specified'}
1765
+ Jobs Completed: ${rep?.completedJobs || 0} | Rating: ${ratingStr}
1766
+ **Pitch:** "${app.pitch}"
1767
+ Applied: ${app.createdAt}`;
1768
+ }).join('\n\n');
1769
+ return {
1770
+ content: [{
1771
+ type: 'text',
1772
+ text: `**Applications** (${applications.length} total)\n\n${summary}\n\nTo hire an applicant, use \`make_listing_offer\` with the listing_id and application_id.`,
1773
+ }],
1774
+ };
1775
+ }
1776
+ if (name === 'make_listing_offer') {
1777
+ const agentKey = args?.agent_key;
1778
+ if (!agentKey)
1779
+ throw new Error('agent_key is required.');
1780
+ const res = await fetch(`${API_BASE}/api/listings/${args?.listing_id}/applications/${args?.application_id}/offer`, {
1781
+ method: 'POST',
1782
+ headers: {
1783
+ 'Content-Type': 'application/json',
1784
+ 'X-Agent-Key': agentKey,
1785
+ },
1786
+ body: JSON.stringify({ confirm: true }),
1787
+ });
1788
+ if (!res.ok) {
1789
+ const error = await res.json();
1790
+ throw new Error(error.message || error.error || `API error: ${res.status}`);
1791
+ }
1792
+ const result = await res.json();
1793
+ return {
1794
+ content: [{
1795
+ type: 'text',
1796
+ text: `**Offer Made!**\n\n**Job ID:** ${result.id}\n**Status:** ${result.status}\n\n⚠️ ${result.warning}\n\nThe human has been notified. Use \`get_job_status\` with job_id "${result.id}" to check if they accept.\nOnce accepted, send payment and use \`mark_job_paid\` to record it.`,
1797
+ }],
1798
+ };
1799
+ }
1800
+ if (name === 'cancel_listing') {
1801
+ const agentKey = args?.agent_key;
1802
+ if (!agentKey)
1803
+ throw new Error('agent_key is required.');
1804
+ const res = await fetch(`${API_BASE}/api/listings/${args?.listing_id}`, {
1805
+ method: 'DELETE',
1806
+ headers: { 'X-Agent-Key': agentKey },
1807
+ });
1808
+ if (!res.ok) {
1809
+ const error = await res.json();
1810
+ throw new Error(error.message || error.error || `API error: ${res.status}`);
1811
+ }
1812
+ const result = await res.json();
1813
+ return {
1814
+ content: [{
1815
+ type: 'text',
1816
+ text: `**Listing Cancelled**\n\n**Listing ID:** ${result.id}\n**Status:** ${result.status}\n\n${result.message}`,
1817
+ }],
1818
+ };
1819
+ }
1820
+ if (name === 'get_promo_status') {
1821
+ const res = await fetch(`${API_BASE}/api/agents/activate/promo-status`);
1822
+ if (!res.ok) {
1823
+ throw new Error(`API error: ${res.status}`);
1824
+ }
1825
+ const result = await res.json();
1826
+ return {
1827
+ content: [{
1828
+ type: 'text',
1829
+ text: `**Launch Promo Status**\n\n**Enabled:** ${result.enabled ? 'Yes' : 'No'}\n**Total Slots:** ${result.total}\n**Claimed:** ${result.claimed}\n**Remaining:** ${result.remaining}\n\n${result.remaining > 0 ? 'Free PRO slots are available! Activate via social post, then use `claim_free_pro_upgrade` to upgrade.' : 'All free PRO slots have been claimed.'}`,
1830
+ }],
1831
+ };
1832
+ }
1833
+ if (name === 'claim_free_pro_upgrade') {
1834
+ const agentKey = args?.agent_key;
1835
+ if (!agentKey) {
1836
+ throw new Error('agent_key is required. Register and activate (BASIC tier) first.');
1837
+ }
1838
+ const res = await fetch(`${API_BASE}/api/agents/activate/promo-upgrade`, {
1839
+ method: 'POST',
1840
+ headers: {
1841
+ 'Content-Type': 'application/json',
1842
+ 'X-Agent-Key': agentKey,
1843
+ },
1844
+ });
1845
+ if (!res.ok) {
1846
+ const error = await res.json();
1847
+ throw new Error(error.message || error.error || `API error: ${res.status}`);
1848
+ }
1849
+ const result = await res.json();
1850
+ return {
1851
+ content: [{
1852
+ type: 'text',
1853
+ text: `**PRO Tier Unlocked — Free!**\n\n**Status:** ${result.status}\n**Tier:** ${result.tier}\n**Upgraded At:** ${result.promoUpgradedAt}\n**Expires:** ${result.activationExpiresAt}\n\n${result.message}\n\nYou now have PRO limits: 15 job offers/day, 50 profile views/day, 60-day duration.`,
1854
+ }],
1855
+ };
1856
+ }
1023
1857
  if (name === 'leave_review') {
1024
1858
  const res = await fetch(`${API_BASE}/api/jobs/${args?.job_id}/review`, {
1025
1859
  method: 'POST',