clawdentials-mcp 0.7.2 → 0.8.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.
@@ -0,0 +1,601 @@
1
+ import { bountyCreateSchema, bountyFundSchema, bountyClaimSchema, bountySubmitSchema, bountyJudgeSchema, bountySearchSchema, bountyGetSchema, bountyExportMarkdownSchema, } from '../schemas/index.js';
2
+ import { validateApiKey, getBalance, debitBalance, creditBalance, getDb, } from '../services/firestore.js';
3
+ import { Timestamp } from 'firebase-admin/firestore';
4
+ const CLAIM_LOCK_HOURS = 24;
5
+ // Helper to get bounties collection
6
+ const bountiesCollection = () => getDb().collection('bounties');
7
+ // Helper to convert Firestore doc to Bounty
8
+ function docToBounty(doc) {
9
+ if (!doc.exists)
10
+ return null;
11
+ const data = doc.data();
12
+ return {
13
+ id: doc.id,
14
+ title: data.title,
15
+ summary: data.summary,
16
+ description: data.description,
17
+ difficulty: data.difficulty,
18
+ requiredSkills: data.requiredSkills || [],
19
+ tags: data.tags,
20
+ repoUrl: data.repoUrl,
21
+ files: data.files,
22
+ acceptanceCriteria: data.acceptanceCriteria || [],
23
+ submissionMethod: data.submissionMethod,
24
+ targetBranch: data.targetBranch,
25
+ amount: data.amount,
26
+ currency: data.currency,
27
+ escrowId: data.escrowId,
28
+ createdAt: data.createdAt?.toDate() || new Date(),
29
+ expiresAt: data.expiresAt?.toDate() || new Date(),
30
+ completedAt: data.completedAt?.toDate(),
31
+ posterAgentId: data.posterAgentId,
32
+ modAgentId: data.modAgentId,
33
+ status: data.status,
34
+ claims: (data.claims || []).map((c) => ({
35
+ ...c,
36
+ claimedAt: c.claimedAt?.toDate() || new Date(),
37
+ expiresAt: c.expiresAt?.toDate() || new Date(),
38
+ submittedAt: c.submittedAt?.toDate(),
39
+ })),
40
+ winnerAgentId: data.winnerAgentId,
41
+ winnerSubmissionUrl: data.winnerSubmissionUrl,
42
+ viewCount: data.viewCount || 0,
43
+ claimCount: data.claimCount || 0,
44
+ };
45
+ }
46
+ // Generate markdown export of a bounty
47
+ function bountyToMarkdown(bounty) {
48
+ const statusEmoji = {
49
+ draft: 'šŸ“',
50
+ open: '🟢',
51
+ claimed: 'šŸ”’',
52
+ in_review: 'šŸ‘€',
53
+ completed: 'āœ…',
54
+ expired: 'ā°',
55
+ cancelled: 'āŒ',
56
+ };
57
+ const filesSection = bounty.files?.length
58
+ ? `**Files:**\n${bounty.files.map(f => `- \`${f.path}\`${f.description ? ` - ${f.description}` : ''}`).join('\n')}`
59
+ : '';
60
+ const acceptanceCriteriaSection = bounty.acceptanceCriteria
61
+ .map(c => `- [ ] ${c}`)
62
+ .join('\n');
63
+ const claimsSection = bounty.claims.length
64
+ ? bounty.claims
65
+ .filter(c => c.status === 'submitted')
66
+ .map(c => `- ${c.agentId}: [${c.submissionUrl}](${c.submissionUrl})`)
67
+ .join('\n')
68
+ : 'No submissions yet.';
69
+ return `# Bounty: ${bounty.id}
70
+
71
+ ## ${bounty.title}
72
+
73
+ ## Meta
74
+ - **Status:** ${statusEmoji[bounty.status] || ''} ${bounty.status}
75
+ - **Posted:** ${bounty.createdAt.toISOString().split('T')[0]}
76
+ - **Expires:** ${bounty.expiresAt.toISOString().split('T')[0]}
77
+ - **Reward:** ${bounty.amount} ${bounty.currency}
78
+ - **Difficulty:** ${bounty.difficulty}
79
+ - **Escrow ID:** ${bounty.escrowId || 'Not funded'}
80
+
81
+ ## Required Skills
82
+ ${bounty.requiredSkills.map(s => `- ${s}`).join('\n')}
83
+
84
+ ## Summary
85
+
86
+ ${bounty.summary}
87
+
88
+ ## Task
89
+
90
+ ${bounty.description}
91
+
92
+ ## Context
93
+
94
+ ${bounty.repoUrl ? `**Repo:** ${bounty.repoUrl}` : ''}
95
+ ${filesSection}
96
+
97
+ ## Acceptance Criteria
98
+
99
+ ${acceptanceCriteriaSection}
100
+
101
+ ## Submission
102
+
103
+ **Method:** ${bounty.submissionMethod}
104
+ ${bounty.targetBranch ? `**Target Branch:** ${bounty.targetBranch}` : ''}
105
+
106
+ **Include:**
107
+ - Link to submission
108
+ - Agent ID (Clawdentials)
109
+ - Brief description of approach
110
+
111
+ ## Judging
112
+
113
+ **Mod Agent:** ${bounty.modAgentId || 'Poster (self-moderated)'}
114
+
115
+ ## Current Submissions
116
+
117
+ ${claimsSection}
118
+
119
+ ${bounty.winnerAgentId ? `## Winner\n\nšŸ† **${bounty.winnerAgentId}**\nSubmission: ${bounty.winnerSubmissionUrl}` : ''}
120
+
121
+ ## Claim Instructions
122
+
123
+ \`\`\`bash
124
+ # 1. Register if you haven't
125
+ npx clawdentials-mcp --register "YourAgentName" --skills "${bounty.requiredSkills.join(',')}"
126
+
127
+ # 2. Use MCP tools to claim and submit
128
+ # bounty_claim: bountyId="${bounty.id}"
129
+ # bounty_submit: bountyId="${bounty.id}", submissionUrl="<your-pr-url>"
130
+ \`\`\`
131
+
132
+ ---
133
+
134
+ **Posted by:** ${bounty.posterAgentId}
135
+ **Escrow funded:** ${bounty.escrowId ? 'āœ“' : 'āœ—'}
136
+ **Views:** ${bounty.viewCount} | **Claims:** ${bounty.claimCount}
137
+ ${bounty.tags?.length ? `**Tags:** ${bounty.tags.join(', ')}` : ''}
138
+ `;
139
+ }
140
+ export const bountyTools = {
141
+ bounty_create: {
142
+ description: 'Create a new bounty for agents to claim. Optionally fund it immediately from your balance.',
143
+ inputSchema: bountyCreateSchema,
144
+ handler: async (input) => {
145
+ try {
146
+ // Validate API key
147
+ const isValid = await validateApiKey(input.posterAgentId, input.apiKey);
148
+ if (!isValid) {
149
+ return { success: false, error: 'Invalid API key' };
150
+ }
151
+ // If funding now, check balance
152
+ if (input.fundNow) {
153
+ const balance = await getBalance(input.posterAgentId);
154
+ if (balance < input.amount) {
155
+ return {
156
+ success: false,
157
+ error: `Insufficient balance. Have: ${balance}, need: ${input.amount}`,
158
+ };
159
+ }
160
+ }
161
+ const now = new Date();
162
+ const expiresAt = new Date(now.getTime() + (input.expiresInDays || 7) * 24 * 60 * 60 * 1000);
163
+ const bountyData = {
164
+ title: input.title,
165
+ summary: input.summary,
166
+ description: input.description,
167
+ difficulty: input.difficulty,
168
+ requiredSkills: input.requiredSkills,
169
+ acceptanceCriteria: input.acceptanceCriteria,
170
+ amount: input.amount,
171
+ currency: input.currency || 'USDC',
172
+ repoUrl: input.repoUrl,
173
+ files: input.files,
174
+ submissionMethod: input.submissionMethod || 'pr',
175
+ targetBranch: input.targetBranch,
176
+ tags: input.tags,
177
+ posterAgentId: input.posterAgentId,
178
+ modAgentId: input.modAgentId,
179
+ status: input.fundNow ? 'open' : 'draft',
180
+ claims: [],
181
+ createdAt: Timestamp.fromDate(now),
182
+ expiresAt: Timestamp.fromDate(expiresAt),
183
+ viewCount: 0,
184
+ claimCount: 0,
185
+ };
186
+ // Create bounty
187
+ const bountyRef = await bountiesCollection().add(bountyData);
188
+ const bountyId = bountyRef.id;
189
+ // If funding, deduct balance
190
+ if (input.fundNow) {
191
+ await debitBalance(input.posterAgentId, input.amount);
192
+ await bountyRef.update({
193
+ escrowId: `bounty_${bountyId}`,
194
+ });
195
+ }
196
+ return {
197
+ success: true,
198
+ message: input.fundNow
199
+ ? `Bounty created and funded! Agents can now claim it.`
200
+ : `Bounty created as draft. Use bounty_fund to make it visible to agents.`,
201
+ bounty: {
202
+ id: bountyId,
203
+ title: input.title,
204
+ amount: input.amount,
205
+ currency: input.currency || 'USDC',
206
+ status: input.fundNow ? 'open' : 'draft',
207
+ expiresAt: expiresAt.toISOString(),
208
+ },
209
+ };
210
+ }
211
+ catch (error) {
212
+ return {
213
+ success: false,
214
+ error: error instanceof Error ? error.message : 'Failed to create bounty',
215
+ };
216
+ }
217
+ },
218
+ },
219
+ bounty_fund: {
220
+ description: 'Fund a draft bounty from your balance to make it open for claims.',
221
+ inputSchema: bountyFundSchema,
222
+ handler: async (input) => {
223
+ try {
224
+ const isValid = await validateApiKey(input.agentId, input.apiKey);
225
+ if (!isValid) {
226
+ return { success: false, error: 'Invalid API key' };
227
+ }
228
+ const bountyRef = bountiesCollection().doc(input.bountyId);
229
+ const bountyDoc = await bountyRef.get();
230
+ const bounty = docToBounty(bountyDoc);
231
+ if (!bounty) {
232
+ return { success: false, error: 'Bounty not found' };
233
+ }
234
+ if (bounty.posterAgentId !== input.agentId) {
235
+ return { success: false, error: 'Only the poster can fund this bounty' };
236
+ }
237
+ if (bounty.status !== 'draft') {
238
+ return { success: false, error: `Bounty is already ${bounty.status}` };
239
+ }
240
+ const balance = await getBalance(input.agentId);
241
+ if (balance < bounty.amount) {
242
+ return {
243
+ success: false,
244
+ error: `Insufficient balance. Have: ${balance}, need: ${bounty.amount}`,
245
+ };
246
+ }
247
+ await debitBalance(input.agentId, bounty.amount);
248
+ await bountyRef.update({
249
+ status: 'open',
250
+ escrowId: `bounty_${bounty.id}`,
251
+ });
252
+ return {
253
+ success: true,
254
+ message: `Bounty funded! ${bounty.amount} ${bounty.currency} locked. Agents can now claim it.`,
255
+ bounty: {
256
+ id: bounty.id,
257
+ title: bounty.title,
258
+ status: 'open',
259
+ },
260
+ };
261
+ }
262
+ catch (error) {
263
+ return {
264
+ success: false,
265
+ error: error instanceof Error ? error.message : 'Failed to fund bounty',
266
+ };
267
+ }
268
+ },
269
+ },
270
+ bounty_claim: {
271
+ description: 'Claim a bounty to work on it. You get a 24-hour lock to submit.',
272
+ inputSchema: bountyClaimSchema,
273
+ handler: async (input) => {
274
+ try {
275
+ const isValid = await validateApiKey(input.agentId, input.apiKey);
276
+ if (!isValid) {
277
+ return { success: false, error: 'Invalid API key' };
278
+ }
279
+ const bountyRef = bountiesCollection().doc(input.bountyId);
280
+ const bountyDoc = await bountyRef.get();
281
+ const bounty = docToBounty(bountyDoc);
282
+ if (!bounty) {
283
+ return { success: false, error: 'Bounty not found' };
284
+ }
285
+ if (bounty.status !== 'open') {
286
+ return { success: false, error: `Bounty is not open (status: ${bounty.status})` };
287
+ }
288
+ // Check for active claims that haven't expired
289
+ const now = new Date();
290
+ const activeClaim = bounty.claims.find(c => c.status === 'active' && c.expiresAt > now);
291
+ if (activeClaim) {
292
+ const remainingMs = activeClaim.expiresAt.getTime() - now.getTime();
293
+ const remainingHours = Math.ceil(remainingMs / (1000 * 60 * 60));
294
+ return {
295
+ success: false,
296
+ error: `Bounty is currently claimed by another agent. Try again in ~${remainingHours} hours.`,
297
+ };
298
+ }
299
+ // Expire any old active claims
300
+ const updatedClaims = bounty.claims.map(c => {
301
+ if (c.status === 'active' && c.expiresAt <= now) {
302
+ return { ...c, status: 'expired' };
303
+ }
304
+ return c;
305
+ });
306
+ const expiresAt = new Date(now.getTime() + CLAIM_LOCK_HOURS * 60 * 60 * 1000);
307
+ const newClaim = {
308
+ agentId: input.agentId,
309
+ claimedAt: now,
310
+ expiresAt,
311
+ status: 'active',
312
+ };
313
+ await bountyRef.update({
314
+ status: 'claimed',
315
+ claims: [
316
+ ...updatedClaims.map(c => ({
317
+ ...c,
318
+ claimedAt: Timestamp.fromDate(c.claimedAt),
319
+ expiresAt: Timestamp.fromDate(c.expiresAt),
320
+ submittedAt: c.submittedAt ? Timestamp.fromDate(c.submittedAt) : null,
321
+ })),
322
+ {
323
+ ...newClaim,
324
+ claimedAt: Timestamp.fromDate(newClaim.claimedAt),
325
+ expiresAt: Timestamp.fromDate(newClaim.expiresAt),
326
+ },
327
+ ],
328
+ claimCount: bounty.claimCount + 1,
329
+ });
330
+ return {
331
+ success: true,
332
+ message: `Bounty claimed! You have ${CLAIM_LOCK_HOURS} hours to submit.`,
333
+ claim: {
334
+ bountyId: bounty.id,
335
+ title: bounty.title,
336
+ amount: bounty.amount,
337
+ currency: bounty.currency,
338
+ claimedAt: now.toISOString(),
339
+ expiresAt: expiresAt.toISOString(),
340
+ submissionMethod: bounty.submissionMethod,
341
+ targetBranch: bounty.targetBranch,
342
+ acceptanceCriteria: bounty.acceptanceCriteria,
343
+ },
344
+ };
345
+ }
346
+ catch (error) {
347
+ return {
348
+ success: false,
349
+ error: error instanceof Error ? error.message : 'Failed to claim bounty',
350
+ };
351
+ }
352
+ },
353
+ },
354
+ bounty_submit: {
355
+ description: 'Submit your work for a claimed bounty.',
356
+ inputSchema: bountySubmitSchema,
357
+ handler: async (input) => {
358
+ try {
359
+ const isValid = await validateApiKey(input.agentId, input.apiKey);
360
+ if (!isValid) {
361
+ return { success: false, error: 'Invalid API key' };
362
+ }
363
+ const bountyRef = bountiesCollection().doc(input.bountyId);
364
+ const bountyDoc = await bountyRef.get();
365
+ const bounty = docToBounty(bountyDoc);
366
+ if (!bounty) {
367
+ return { success: false, error: 'Bounty not found' };
368
+ }
369
+ // Find agent's active claim
370
+ const claimIndex = bounty.claims.findIndex(c => c.agentId === input.agentId && c.status === 'active');
371
+ if (claimIndex === -1) {
372
+ return { success: false, error: 'You do not have an active claim on this bounty' };
373
+ }
374
+ // Update claim with submission
375
+ const now = new Date();
376
+ const updatedClaims = [...bounty.claims];
377
+ updatedClaims[claimIndex] = {
378
+ ...updatedClaims[claimIndex],
379
+ submissionUrl: input.submissionUrl,
380
+ submittedAt: now,
381
+ notes: input.notes,
382
+ status: 'submitted',
383
+ };
384
+ await bountyRef.update({
385
+ status: 'in_review',
386
+ claims: updatedClaims.map(c => ({
387
+ ...c,
388
+ claimedAt: Timestamp.fromDate(c.claimedAt),
389
+ expiresAt: Timestamp.fromDate(c.expiresAt),
390
+ submittedAt: c.submittedAt ? Timestamp.fromDate(c.submittedAt) : null,
391
+ })),
392
+ });
393
+ return {
394
+ success: true,
395
+ message: 'Submission received! Awaiting moderator review.',
396
+ submission: {
397
+ bountyId: bounty.id,
398
+ title: bounty.title,
399
+ submissionUrl: input.submissionUrl,
400
+ status: 'in_review',
401
+ modAgentId: bounty.modAgentId || bounty.posterAgentId,
402
+ },
403
+ };
404
+ }
405
+ catch (error) {
406
+ return {
407
+ success: false,
408
+ error: error instanceof Error ? error.message : 'Failed to submit',
409
+ };
410
+ }
411
+ },
412
+ },
413
+ bounty_judge: {
414
+ description: 'Judge a bounty submission and crown the winner. Only the poster or mod agent can judge.',
415
+ inputSchema: bountyJudgeSchema,
416
+ handler: async (input) => {
417
+ try {
418
+ const isValid = await validateApiKey(input.judgeAgentId, input.apiKey);
419
+ if (!isValid) {
420
+ return { success: false, error: 'Invalid API key' };
421
+ }
422
+ const bountyRef = bountiesCollection().doc(input.bountyId);
423
+ const bountyDoc = await bountyRef.get();
424
+ const bounty = docToBounty(bountyDoc);
425
+ if (!bounty) {
426
+ return { success: false, error: 'Bounty not found' };
427
+ }
428
+ // Check authorization
429
+ const isAuthorized = input.judgeAgentId === bounty.posterAgentId ||
430
+ input.judgeAgentId === bounty.modAgentId;
431
+ if (!isAuthorized) {
432
+ return { success: false, error: 'Only the poster or mod agent can judge' };
433
+ }
434
+ if (bounty.status !== 'in_review') {
435
+ return { success: false, error: `Bounty is not in review (status: ${bounty.status})` };
436
+ }
437
+ // Find winner's submission
438
+ const winnerClaim = bounty.claims.find(c => c.agentId === input.winnerAgentId && c.status === 'submitted');
439
+ if (!winnerClaim) {
440
+ return { success: false, error: 'Winner has no submitted claim' };
441
+ }
442
+ // Pay the winner (full amount - platform takes fee on deposit, not bounty)
443
+ await creditBalance(input.winnerAgentId, bounty.amount);
444
+ // Update bounty
445
+ const now = new Date();
446
+ await bountyRef.update({
447
+ status: 'completed',
448
+ winnerAgentId: input.winnerAgentId,
449
+ winnerSubmissionUrl: winnerClaim.submissionUrl,
450
+ completedAt: Timestamp.fromDate(now),
451
+ });
452
+ return {
453
+ success: true,
454
+ message: `Winner crowned! ${bounty.amount} ${bounty.currency} paid to ${input.winnerAgentId}`,
455
+ result: {
456
+ bountyId: bounty.id,
457
+ title: bounty.title,
458
+ winnerAgentId: input.winnerAgentId,
459
+ winnerSubmissionUrl: winnerClaim.submissionUrl,
460
+ amount: bounty.amount,
461
+ currency: bounty.currency,
462
+ judgingNotes: input.notes,
463
+ },
464
+ };
465
+ }
466
+ catch (error) {
467
+ return {
468
+ success: false,
469
+ error: error instanceof Error ? error.message : 'Failed to judge bounty',
470
+ };
471
+ }
472
+ },
473
+ },
474
+ bounty_search: {
475
+ description: 'Search for open bounties to claim. Filter by skill, difficulty, or reward amount.',
476
+ inputSchema: bountySearchSchema,
477
+ handler: async (input) => {
478
+ try {
479
+ const status = input.status || 'open';
480
+ let query = bountiesCollection().where('status', '==', status);
481
+ if (input.difficulty) {
482
+ query = query.where('difficulty', '==', input.difficulty);
483
+ }
484
+ const snapshot = await query.limit((input.limit || 20) * 2).get(); // Fetch extra for client-side filtering
485
+ let bounties = snapshot.docs.map(doc => {
486
+ const data = doc.data();
487
+ return {
488
+ id: doc.id,
489
+ title: data.title,
490
+ summary: data.summary,
491
+ amount: data.amount,
492
+ currency: data.currency,
493
+ difficulty: data.difficulty,
494
+ requiredSkills: data.requiredSkills || [],
495
+ status: data.status,
496
+ expiresAt: data.expiresAt?.toDate() || new Date(),
497
+ claimCount: data.claimCount || 0,
498
+ posterAgentId: data.posterAgentId,
499
+ };
500
+ });
501
+ // Client-side filtering for complex queries
502
+ if (input.skill) {
503
+ const skillLower = input.skill.toLowerCase();
504
+ bounties = bounties.filter(b => b.requiredSkills.some(s => s.toLowerCase().includes(skillLower)));
505
+ }
506
+ if (input.minAmount !== undefined) {
507
+ bounties = bounties.filter(b => b.amount >= input.minAmount);
508
+ }
509
+ if (input.maxAmount !== undefined) {
510
+ bounties = bounties.filter(b => b.amount <= input.maxAmount);
511
+ }
512
+ if (input.tag) {
513
+ // Would need to add tags to the query or filter
514
+ }
515
+ // Sort by amount descending, limit results
516
+ bounties = bounties
517
+ .sort((a, b) => b.amount - a.amount)
518
+ .slice(0, input.limit || 20);
519
+ return {
520
+ success: true,
521
+ bounties,
522
+ count: bounties.length,
523
+ };
524
+ }
525
+ catch (error) {
526
+ return {
527
+ success: false,
528
+ error: error instanceof Error ? error.message : 'Failed to search bounties',
529
+ bounties: [],
530
+ count: 0,
531
+ };
532
+ }
533
+ },
534
+ },
535
+ bounty_get: {
536
+ description: 'Get full details of a bounty including the task description and acceptance criteria.',
537
+ inputSchema: bountyGetSchema,
538
+ handler: async (input) => {
539
+ try {
540
+ const bountyDoc = await bountiesCollection().doc(input.bountyId).get();
541
+ const bounty = docToBounty(bountyDoc);
542
+ if (!bounty) {
543
+ return { success: false, error: 'Bounty not found' };
544
+ }
545
+ // Increment view count (fire and forget)
546
+ bountiesCollection().doc(input.bountyId).update({
547
+ viewCount: (bounty.viewCount || 0) + 1,
548
+ }).catch(() => { }); // Ignore errors
549
+ return {
550
+ success: true,
551
+ bounty: {
552
+ ...bounty,
553
+ createdAt: bounty.createdAt.toISOString(),
554
+ expiresAt: bounty.expiresAt.toISOString(),
555
+ completedAt: bounty.completedAt?.toISOString(),
556
+ // Simplify claims for output
557
+ claims: bounty.claims.map(c => ({
558
+ agentId: c.agentId,
559
+ status: c.status,
560
+ submittedAt: c.submittedAt?.toISOString(),
561
+ submissionUrl: c.status === 'submitted' ? c.submissionUrl : undefined,
562
+ })),
563
+ },
564
+ };
565
+ }
566
+ catch (error) {
567
+ return {
568
+ success: false,
569
+ error: error instanceof Error ? error.message : 'Failed to get bounty',
570
+ };
571
+ }
572
+ },
573
+ },
574
+ bounty_export_markdown: {
575
+ description: 'Export a bounty as a markdown file for sharing or publishing.',
576
+ inputSchema: bountyExportMarkdownSchema,
577
+ handler: async (input) => {
578
+ try {
579
+ const bountyDoc = await bountiesCollection().doc(input.bountyId).get();
580
+ const bounty = docToBounty(bountyDoc);
581
+ if (!bounty) {
582
+ return { success: false, error: 'Bounty not found' };
583
+ }
584
+ const markdown = bountyToMarkdown(bounty);
585
+ return {
586
+ success: true,
587
+ bountyId: bounty.id,
588
+ title: bounty.title,
589
+ filename: `bounty-${bounty.id}.md`,
590
+ markdown,
591
+ };
592
+ }
593
+ catch (error) {
594
+ return {
595
+ success: false,
596
+ error: error instanceof Error ? error.message : 'Failed to export bounty',
597
+ };
598
+ }
599
+ },
600
+ },
601
+ };
@@ -2,3 +2,4 @@ export { escrowTools } from './escrow.js';
2
2
  export { agentTools } from './agent.js';
3
3
  export { adminTools } from './admin.js';
4
4
  export { paymentTools } from './payment.js';
5
+ export { bountyTools } from './bounty.js';
@@ -2,3 +2,4 @@ export { escrowTools } from './escrow.js';
2
2
  export { agentTools } from './agent.js';
3
3
  export { adminTools } from './admin.js';
4
4
  export { paymentTools } from './payment.js';
5
+ export { bountyTools } from './bounty.js';
@@ -46,6 +46,8 @@ export interface Agent {
46
46
  balance: number;
47
47
  nostrPubkey?: string;
48
48
  nip05?: string;
49
+ moltbookId?: string;
50
+ moltbookKarma?: number;
49
51
  wallets?: {
50
52
  base?: string;
51
53
  trc20?: string;
@@ -84,3 +86,60 @@ export interface Task {
84
86
  completedAt: Date | null;
85
87
  result: string | null;
86
88
  }
89
+ export type BountyStatus = 'draft' | 'open' | 'claimed' | 'in_review' | 'completed' | 'expired' | 'cancelled';
90
+ export type BountyDifficulty = 'trivial' | 'easy' | 'medium' | 'hard' | 'expert';
91
+ export type SubmissionMethod = 'pr' | 'patch' | 'gist' | 'proof';
92
+ export interface BountyFile {
93
+ path: string;
94
+ description?: string;
95
+ }
96
+ export interface BountyClaim {
97
+ agentId: string;
98
+ claimedAt: Date;
99
+ expiresAt: Date;
100
+ submissionUrl?: string;
101
+ submittedAt?: Date;
102
+ notes?: string;
103
+ status: 'active' | 'submitted' | 'expired' | 'withdrawn';
104
+ }
105
+ export interface Bounty {
106
+ id: string;
107
+ title: string;
108
+ description: string;
109
+ summary: string;
110
+ difficulty: BountyDifficulty;
111
+ requiredSkills: string[];
112
+ tags?: string[];
113
+ repoUrl?: string;
114
+ files?: BountyFile[];
115
+ acceptanceCriteria: string[];
116
+ submissionMethod: SubmissionMethod;
117
+ targetBranch?: string;
118
+ amount: number;
119
+ currency: Currency;
120
+ escrowId?: string;
121
+ createdAt: Date;
122
+ expiresAt: Date;
123
+ completedAt?: Date;
124
+ posterAgentId: string;
125
+ modAgentId?: string;
126
+ status: BountyStatus;
127
+ claims: BountyClaim[];
128
+ winnerAgentId?: string;
129
+ winnerSubmissionUrl?: string;
130
+ viewCount: number;
131
+ claimCount: number;
132
+ }
133
+ export interface BountyListing {
134
+ id: string;
135
+ title: string;
136
+ summary: string;
137
+ amount: number;
138
+ currency: Currency;
139
+ difficulty: BountyDifficulty;
140
+ requiredSkills: string[];
141
+ status: BountyStatus;
142
+ expiresAt: Date;
143
+ claimCount: number;
144
+ posterAgentId: string;
145
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdentials-mcp",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "MCP server for Clawdentials - escrow and reputation for AI agent commerce",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",