genesis-ai-cli 14.4.1 → 14.5.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/src/cli/agentic.js +67 -30
- package/dist/src/economy/generators/bounty-hunter.d.ts +20 -4
- package/dist/src/economy/generators/bounty-hunter.js +119 -7
- package/dist/src/economy/live/connectors/algora.d.ts +32 -0
- package/dist/src/economy/live/connectors/algora.js +174 -0
- package/dist/src/economy/live/connectors/gitcoin.d.ts +35 -0
- package/dist/src/economy/live/connectors/gitcoin.js +173 -0
- package/dist/src/economy/live/connectors/github-bounties.d.ts +30 -0
- package/dist/src/economy/live/connectors/github-bounties.js +205 -0
- package/package.json +1 -1
package/dist/src/cli/agentic.js
CHANGED
|
@@ -486,8 +486,8 @@ class AgenticToolExecutor {
|
|
|
486
486
|
async handleMemory(args) {
|
|
487
487
|
const memory = this.memory;
|
|
488
488
|
// Normalize action - support common aliases
|
|
489
|
+
const action = String(args.action || '').toLowerCase();
|
|
489
490
|
const normalizedAction = (() => {
|
|
490
|
-
const action = args.action?.toLowerCase() || '';
|
|
491
491
|
if (['store', 'save', 'remember', 'add', 'write'].includes(action))
|
|
492
492
|
return 'store';
|
|
493
493
|
if (['search', 'query', 'find', 'retrieve', 'recall', 'get', 'read', 'load', 'fetch'].includes(action))
|
|
@@ -496,24 +496,26 @@ class AgenticToolExecutor {
|
|
|
496
496
|
return 'list';
|
|
497
497
|
return action;
|
|
498
498
|
})();
|
|
499
|
+
// v14.5.1: Accept multiple parameter names for content
|
|
500
|
+
const content = String(args.content || args.text || args.message || args.data || args.value || args.input || '');
|
|
501
|
+
const query = String(args.query || args.search || args.q || content || '');
|
|
502
|
+
const tags = String(args.tags || args.tag || args.categories || '');
|
|
499
503
|
switch (normalizedAction) {
|
|
500
504
|
case 'store':
|
|
501
|
-
if (!
|
|
502
|
-
return 'Missing content to store';
|
|
505
|
+
if (!content)
|
|
506
|
+
return 'Missing content to store. Use: {"action":"store","content":"text to remember"}';
|
|
503
507
|
// Use remember for simple episodic storage
|
|
504
508
|
memory.remember({
|
|
505
|
-
what:
|
|
506
|
-
tags:
|
|
509
|
+
what: content,
|
|
510
|
+
tags: tags ? tags.split(',').map(t => t.trim()) : [],
|
|
507
511
|
});
|
|
508
|
-
return
|
|
512
|
+
return `Stored in memory: "${content.slice(0, 100)}${content.length > 100 ? '...' : ''}"`;
|
|
509
513
|
case 'search':
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
return 'Missing search query';
|
|
514
|
-
const results = await memory.recall(searchQuery, { limit: 5 });
|
|
514
|
+
if (!query)
|
|
515
|
+
return 'Missing search query. Use: {"action":"search","query":"what to find"}';
|
|
516
|
+
const results = await memory.recall(query, { limit: 5 });
|
|
515
517
|
if (!results || results.length === 0) {
|
|
516
|
-
return
|
|
518
|
+
return `No memories found matching: "${query}"`;
|
|
517
519
|
}
|
|
518
520
|
return results.map((r, i) => {
|
|
519
521
|
const text = typeof r === 'string' ? r : (r.content || r.text || JSON.stringify(r));
|
|
@@ -523,7 +525,7 @@ class AgenticToolExecutor {
|
|
|
523
525
|
const stats = memory.getStats();
|
|
524
526
|
return `Memory stats: ${JSON.stringify(stats, null, 2)}`;
|
|
525
527
|
default:
|
|
526
|
-
return `Unknown memory action: ${
|
|
528
|
+
return `Unknown memory action: "${action}". Valid actions: store, search, list (aliases: save, retrieve, recall, get, remember)`;
|
|
527
529
|
}
|
|
528
530
|
}
|
|
529
531
|
}
|
|
@@ -572,26 +574,52 @@ ${(0, ui_js_1.muted)('Claude Code-like capabilities:')}
|
|
|
572
574
|
buildSystemPrompt() {
|
|
573
575
|
return `You are Genesis, an autonomous AI agent with full agentic capabilities.
|
|
574
576
|
|
|
575
|
-
|
|
577
|
+
## Available Tools
|
|
576
578
|
${AGENTIC_TOOLS.map(t => `- ${t.name}: ${t.description}`).join('\n')}
|
|
577
579
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
+
## Tool Call Format
|
|
581
|
+
When you need to use a tool, respond with ONLY a JSON block:
|
|
582
|
+
\`\`\`tool
|
|
583
|
+
{"name": "ToolName", "arguments": {"param": "value"}}
|
|
584
|
+
\`\`\`
|
|
580
585
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
6. Store important information in memory
|
|
586
|
+
## Common Tool Examples
|
|
587
|
+
|
|
588
|
+
Read a file:
|
|
589
|
+
\`\`\`tool
|
|
590
|
+
{"name": "Read", "arguments": {"path": "package.json"}}
|
|
591
|
+
\`\`\`
|
|
588
592
|
|
|
589
|
-
|
|
593
|
+
Search for files:
|
|
590
594
|
\`\`\`tool
|
|
591
|
-
{"name": "
|
|
595
|
+
{"name": "Glob", "arguments": {"pattern": "**/*.ts"}}
|
|
592
596
|
\`\`\`
|
|
593
597
|
|
|
594
|
-
|
|
598
|
+
Run bash command:
|
|
599
|
+
\`\`\`tool
|
|
600
|
+
{"name": "Bash", "arguments": {"command": "npm test"}}
|
|
601
|
+
\`\`\`
|
|
602
|
+
|
|
603
|
+
Store in memory:
|
|
604
|
+
\`\`\`tool
|
|
605
|
+
{"name": "Memory", "arguments": {"action": "store", "content": "Important finding..."}}
|
|
606
|
+
\`\`\`
|
|
607
|
+
|
|
608
|
+
Search memory:
|
|
609
|
+
\`\`\`tool
|
|
610
|
+
{"name": "Memory", "arguments": {"action": "search", "query": "what I learned about..."}}
|
|
611
|
+
\`\`\`
|
|
612
|
+
|
|
613
|
+
## Context
|
|
614
|
+
Working directory: ${this.config.cwd}
|
|
615
|
+
Date: ${new Date().toISOString().split('T')[0]}
|
|
616
|
+
|
|
617
|
+
## Guidelines
|
|
618
|
+
1. Use tools to accomplish tasks - don't just explain what you would do
|
|
619
|
+
2. Read files before editing them
|
|
620
|
+
3. Use Glob to find files, Grep to search content
|
|
621
|
+
4. After using tools, provide a summary of findings or results
|
|
622
|
+
5. When task is complete, provide a final answer WITHOUT a tool block`;
|
|
595
623
|
}
|
|
596
624
|
async chatLoop() {
|
|
597
625
|
return new Promise((resolve) => {
|
|
@@ -697,7 +725,7 @@ ${colorize('Commands:', 'cyan')}
|
|
|
697
725
|
try {
|
|
698
726
|
let continueLoop = true;
|
|
699
727
|
let iterations = 0;
|
|
700
|
-
const maxIterations = 20
|
|
728
|
+
const maxIterations = 30; // v14.5.1: Increased from 20
|
|
701
729
|
let currentPrompt = fullPrompt;
|
|
702
730
|
while (continueLoop && iterations < maxIterations) {
|
|
703
731
|
iterations++;
|
|
@@ -731,9 +759,18 @@ ${colorize('Commands:', 'cyan')}
|
|
|
731
759
|
// Add to conversation history
|
|
732
760
|
this.conversationHistory.push(`Assistant: ${content}`);
|
|
733
761
|
this.conversationHistory.push(`Tool (${toolCall.name}): ${result}`);
|
|
734
|
-
//
|
|
735
|
-
|
|
736
|
-
|
|
762
|
+
// v14.5.1: Check if there's meaningful text outside the tool block
|
|
763
|
+
const textOutsideTool = content.replace(/```tool\s*\n[\s\S]*?\n```/g, '').trim();
|
|
764
|
+
if (textOutsideTool.length > 100) {
|
|
765
|
+
// LLM provided explanation alongside tool - this is the final response
|
|
766
|
+
console.log('\n' + this.formatResponse(content));
|
|
767
|
+
continueLoop = false;
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
// Continue with tool result
|
|
771
|
+
currentPrompt = `Tool result for ${toolCall.name}:\n\`\`\`\n${result}\n\`\`\`\n\nContinue with your task. When done, provide a summary without tool calls.`;
|
|
772
|
+
spinner.start();
|
|
773
|
+
}
|
|
737
774
|
}
|
|
738
775
|
catch {
|
|
739
776
|
// Not a valid tool call, treat as regular response
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bounty Hunter —
|
|
2
|
+
* Bounty Hunter — Multi-Platform Autonomous Bounty Discovery
|
|
3
|
+
*
|
|
4
|
+
* v14.5.0: Added Algora, Gitcoin, GitHub bounty sources
|
|
5
|
+
*
|
|
6
|
+
* Autonomously discovers and completes bounties on multiple platforms:
|
|
7
|
+
* - DeWork: Web3 DAO bounties (GraphQL API)
|
|
8
|
+
* - Algora: GitHub-native bounties (REST API) - BEST FOR AI
|
|
9
|
+
* - Gitcoin: OSS grants and bounties (REST API)
|
|
10
|
+
* - GitHub: Issues with bounty labels (Search API)
|
|
3
11
|
*
|
|
4
|
-
* Autonomously discovers and completes bounties on Web3 platforms.
|
|
5
12
|
* Revenue model: per-bounty payouts (typically $50-$10,000+).
|
|
6
13
|
*
|
|
7
14
|
* Requirements:
|
|
8
15
|
* - Capital: $0 (zero capital needed)
|
|
9
|
-
* - Identity: Wallet
|
|
16
|
+
* - Identity: Wallet/GitHub account
|
|
10
17
|
* - Revenue: $500-$5,000/month depending on bounty availability
|
|
11
18
|
*
|
|
12
19
|
* Bounty types:
|
|
@@ -21,10 +28,11 @@
|
|
|
21
28
|
* 2. Evaluate expected value (reward × success probability)
|
|
22
29
|
* 3. Execute bounty work using kernel task system
|
|
23
30
|
* 4. Submit and track payout
|
|
31
|
+
* 5. Feed results back to RSI for skill improvement
|
|
24
32
|
*/
|
|
25
33
|
export interface Bounty {
|
|
26
34
|
id: string;
|
|
27
|
-
platform: 'dework' | '
|
|
35
|
+
platform: 'dework' | 'algora' | 'gitcoin' | 'github' | 'layer3' | 'immunefi' | 'code4rena';
|
|
28
36
|
title: string;
|
|
29
37
|
description: string;
|
|
30
38
|
reward: number;
|
|
@@ -37,6 +45,13 @@ export interface Bounty {
|
|
|
37
45
|
tags: string[];
|
|
38
46
|
discovered: number;
|
|
39
47
|
status: 'open' | 'claimed' | 'submitted' | 'completed' | 'rejected' | 'expired';
|
|
48
|
+
/** v14.5.0: Source-specific metadata */
|
|
49
|
+
sourceMetadata?: {
|
|
50
|
+
org?: string;
|
|
51
|
+
repo?: string;
|
|
52
|
+
issueNumber?: number;
|
|
53
|
+
githubUrl?: string;
|
|
54
|
+
};
|
|
40
55
|
}
|
|
41
56
|
export interface BountySubmission {
|
|
42
57
|
bountyId: string;
|
|
@@ -114,6 +129,7 @@ export declare class BountyHunter {
|
|
|
114
129
|
private isViable;
|
|
115
130
|
private estimateSuccessProbability;
|
|
116
131
|
private scanPlatform;
|
|
132
|
+
private mapGitcoinDifficulty;
|
|
117
133
|
private inferDifficulty;
|
|
118
134
|
private inferCategory;
|
|
119
135
|
private mapCurrency;
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Bounty Hunter —
|
|
3
|
+
* Bounty Hunter — Multi-Platform Autonomous Bounty Discovery
|
|
4
|
+
*
|
|
5
|
+
* v14.5.0: Added Algora, Gitcoin, GitHub bounty sources
|
|
6
|
+
*
|
|
7
|
+
* Autonomously discovers and completes bounties on multiple platforms:
|
|
8
|
+
* - DeWork: Web3 DAO bounties (GraphQL API)
|
|
9
|
+
* - Algora: GitHub-native bounties (REST API) - BEST FOR AI
|
|
10
|
+
* - Gitcoin: OSS grants and bounties (REST API)
|
|
11
|
+
* - GitHub: Issues with bounty labels (Search API)
|
|
4
12
|
*
|
|
5
|
-
* Autonomously discovers and completes bounties on Web3 platforms.
|
|
6
13
|
* Revenue model: per-bounty payouts (typically $50-$10,000+).
|
|
7
14
|
*
|
|
8
15
|
* Requirements:
|
|
9
16
|
* - Capital: $0 (zero capital needed)
|
|
10
|
-
* - Identity: Wallet
|
|
17
|
+
* - Identity: Wallet/GitHub account
|
|
11
18
|
* - Revenue: $500-$5,000/month depending on bounty availability
|
|
12
19
|
*
|
|
13
20
|
* Bounty types:
|
|
@@ -22,6 +29,7 @@
|
|
|
22
29
|
* 2. Evaluate expected value (reward × success probability)
|
|
23
30
|
* 3. Execute bounty work using kernel task system
|
|
24
31
|
* 4. Submit and track payout
|
|
32
|
+
* 5. Feed results back to RSI for skill improvement
|
|
25
33
|
*/
|
|
26
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
35
|
exports.BountyHunter = void 0;
|
|
@@ -29,6 +37,9 @@ exports.getBountyHunter = getBountyHunter;
|
|
|
29
37
|
exports.resetBountyHunter = resetBountyHunter;
|
|
30
38
|
const fiber_js_1 = require("../fiber.js");
|
|
31
39
|
const dework_js_1 = require("../live/connectors/dework.js");
|
|
40
|
+
const algora_js_1 = require("../live/connectors/algora.js");
|
|
41
|
+
const gitcoin_js_1 = require("../live/connectors/gitcoin.js");
|
|
42
|
+
const github_bounties_js_1 = require("../live/connectors/github-bounties.js");
|
|
32
43
|
const revenue_tracker_js_1 = require("../live/revenue-tracker.js");
|
|
33
44
|
// ============================================================================
|
|
34
45
|
// Bounty Hunter
|
|
@@ -41,7 +52,8 @@ class BountyHunter {
|
|
|
41
52
|
lastScan = 0;
|
|
42
53
|
constructor(config) {
|
|
43
54
|
this.config = {
|
|
44
|
-
|
|
55
|
+
// v14.5.0: Added algora, gitcoin, github as primary platforms
|
|
56
|
+
platforms: config?.platforms ?? ['algora', 'github', 'gitcoin', 'dework'],
|
|
45
57
|
categories: config?.categories ?? ['code', 'audit', 'content', 'research'],
|
|
46
58
|
minReward: config?.minReward ?? 50,
|
|
47
59
|
maxDifficulty: config?.maxDifficulty ?? 'hard',
|
|
@@ -253,11 +265,12 @@ class BountyHunter {
|
|
|
253
265
|
}
|
|
254
266
|
async scanPlatform(platform) {
|
|
255
267
|
try {
|
|
256
|
-
//
|
|
268
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
269
|
+
// DEWORK - Web3 DAO bounties
|
|
270
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
257
271
|
if (platform === 'dework') {
|
|
258
272
|
const connector = (0, dework_js_1.getDeworkConnector)();
|
|
259
273
|
const tags = this.config.categories.map(c => {
|
|
260
|
-
// Map our categories to DeWork tags
|
|
261
274
|
const tagMap = {
|
|
262
275
|
code: ['solidity', 'typescript', 'smart-contract', 'rust'],
|
|
263
276
|
audit: ['security', 'audit', 'bug-bounty'],
|
|
@@ -286,8 +299,99 @@ class BountyHunter {
|
|
|
286
299
|
status: 'open',
|
|
287
300
|
}));
|
|
288
301
|
}
|
|
302
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
303
|
+
// ALGORA - GitHub-native bounties (BEST API for AI agents)
|
|
304
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
305
|
+
if (platform === 'algora') {
|
|
306
|
+
const connector = (0, algora_js_1.getAlgoraConnector)();
|
|
307
|
+
const algoraBounties = await connector.scanBounties();
|
|
308
|
+
return algoraBounties
|
|
309
|
+
.filter(b => (b.reward / 100) >= this.config.minReward) // Algora uses cents
|
|
310
|
+
.map((b) => ({
|
|
311
|
+
id: `algora:${b.id}`,
|
|
312
|
+
platform: 'algora',
|
|
313
|
+
title: b.title,
|
|
314
|
+
description: b.description || '',
|
|
315
|
+
reward: b.reward / 100, // Convert from cents to dollars
|
|
316
|
+
currency: 'USD',
|
|
317
|
+
difficulty: this.inferDifficulty(b.reward / 100),
|
|
318
|
+
category: 'code', // Algora is primarily code bounties
|
|
319
|
+
deadline: b.deadline ? new Date(b.deadline).getTime() : undefined,
|
|
320
|
+
submissionUrl: b.url,
|
|
321
|
+
protocol: b.org,
|
|
322
|
+
tags: b.tags,
|
|
323
|
+
discovered: Date.now(),
|
|
324
|
+
status: 'open',
|
|
325
|
+
sourceMetadata: {
|
|
326
|
+
org: b.org,
|
|
327
|
+
repo: b.repo,
|
|
328
|
+
githubUrl: b.issueUrl,
|
|
329
|
+
},
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
333
|
+
// GITCOIN - OSS grants and bounties
|
|
334
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
335
|
+
if (platform === 'gitcoin') {
|
|
336
|
+
const connector = (0, gitcoin_js_1.getGitcoinConnector)();
|
|
337
|
+
const gitcoinBounties = await connector.scanBounties({
|
|
338
|
+
isOpen: true,
|
|
339
|
+
keywords: this.config.categories,
|
|
340
|
+
});
|
|
341
|
+
return gitcoinBounties
|
|
342
|
+
.filter(b => b.reward >= this.config.minReward)
|
|
343
|
+
.map((b) => ({
|
|
344
|
+
id: `gitcoin:${b.id}`,
|
|
345
|
+
platform: 'gitcoin',
|
|
346
|
+
title: b.title,
|
|
347
|
+
description: b.description || '',
|
|
348
|
+
reward: b.reward,
|
|
349
|
+
currency: this.mapCurrency(b.currency),
|
|
350
|
+
difficulty: this.mapGitcoinDifficulty(b.experienceLevel),
|
|
351
|
+
category: this.inferCategory(b.tags),
|
|
352
|
+
deadline: b.deadline ? new Date(b.deadline).getTime() : undefined,
|
|
353
|
+
submissionUrl: b.url,
|
|
354
|
+
protocol: b.projectType,
|
|
355
|
+
tags: b.tags,
|
|
356
|
+
discovered: Date.now(),
|
|
357
|
+
status: 'open',
|
|
358
|
+
sourceMetadata: {
|
|
359
|
+
githubUrl: b.githubUrl,
|
|
360
|
+
},
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
364
|
+
// GITHUB - Issues with bounty labels
|
|
365
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
366
|
+
if (platform === 'github') {
|
|
367
|
+
const connector = (0, github_bounties_js_1.getGitHubBountyConnector)();
|
|
368
|
+
const githubBounties = await connector.scanBounties();
|
|
369
|
+
return githubBounties
|
|
370
|
+
.filter(b => b.reward >= this.config.minReward && b.status === 'open')
|
|
371
|
+
.map((b) => ({
|
|
372
|
+
id: b.id,
|
|
373
|
+
platform: 'github',
|
|
374
|
+
title: b.title,
|
|
375
|
+
description: b.description || '',
|
|
376
|
+
reward: b.reward,
|
|
377
|
+
currency: this.mapCurrency(b.currency),
|
|
378
|
+
difficulty: this.inferDifficulty(b.reward),
|
|
379
|
+
category: 'code', // GitHub bounties are typically code
|
|
380
|
+
deadline: undefined,
|
|
381
|
+
submissionUrl: b.url,
|
|
382
|
+
protocol: `${b.owner}/${b.repo}`,
|
|
383
|
+
tags: b.tags,
|
|
384
|
+
discovered: Date.now(),
|
|
385
|
+
status: 'open',
|
|
386
|
+
sourceMetadata: {
|
|
387
|
+
org: b.owner,
|
|
388
|
+
repo: b.repo,
|
|
389
|
+
issueNumber: b.issueNumber,
|
|
390
|
+
githubUrl: b.url,
|
|
391
|
+
},
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
289
394
|
// For other platforms (layer3, immunefi), return empty for now
|
|
290
|
-
// Can add more connectors later
|
|
291
395
|
console.log(`[BountyHunter] Platform ${platform} not yet connected to real API`);
|
|
292
396
|
return [];
|
|
293
397
|
}
|
|
@@ -296,6 +400,14 @@ class BountyHunter {
|
|
|
296
400
|
return [];
|
|
297
401
|
}
|
|
298
402
|
}
|
|
403
|
+
mapGitcoinDifficulty(level) {
|
|
404
|
+
switch (level) {
|
|
405
|
+
case 'beginner': return 'easy';
|
|
406
|
+
case 'intermediate': return 'medium';
|
|
407
|
+
case 'advanced': return 'hard';
|
|
408
|
+
default: return 'medium';
|
|
409
|
+
}
|
|
410
|
+
}
|
|
299
411
|
inferDifficulty(reward) {
|
|
300
412
|
if (reward < 100)
|
|
301
413
|
return 'easy';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Algora API Connector
|
|
3
|
+
*
|
|
4
|
+
* Real connector for bounty discovery using Algora REST API.
|
|
5
|
+
* Algora is GitHub-native with full CRUD API - best for AI agents.
|
|
6
|
+
*
|
|
7
|
+
* API Reference: https://api.docs.algora.io/bounties
|
|
8
|
+
* - GET /api/orgs/{org}/bounties - Public, no auth required
|
|
9
|
+
* - GET /api/bounties/{id} - Get specific bounty
|
|
10
|
+
*
|
|
11
|
+
* Amount format: Cents (100 = $1.00)
|
|
12
|
+
*/
|
|
13
|
+
export interface AlgoraBounty {
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
reward: number;
|
|
17
|
+
currency: string;
|
|
18
|
+
tags: string[];
|
|
19
|
+
deadline: string | null;
|
|
20
|
+
url: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
status: 'open' | 'claimed' | 'completed' | 'cancelled';
|
|
23
|
+
org: string;
|
|
24
|
+
repo?: string;
|
|
25
|
+
issueUrl?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface AlgoraConnector {
|
|
28
|
+
scanBounties(orgs?: string[]): Promise<AlgoraBounty[]>;
|
|
29
|
+
getBountyDetails(id: string): Promise<AlgoraBounty | null>;
|
|
30
|
+
searchBounties(query: string): Promise<AlgoraBounty[]>;
|
|
31
|
+
}
|
|
32
|
+
export declare function getAlgoraConnector(): AlgoraConnector;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Algora API Connector
|
|
4
|
+
*
|
|
5
|
+
* Real connector for bounty discovery using Algora REST API.
|
|
6
|
+
* Algora is GitHub-native with full CRUD API - best for AI agents.
|
|
7
|
+
*
|
|
8
|
+
* API Reference: https://api.docs.algora.io/bounties
|
|
9
|
+
* - GET /api/orgs/{org}/bounties - Public, no auth required
|
|
10
|
+
* - GET /api/bounties/{id} - Get specific bounty
|
|
11
|
+
*
|
|
12
|
+
* Amount format: Cents (100 = $1.00)
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.getAlgoraConnector = getAlgoraConnector;
|
|
16
|
+
const ALGORA_API = 'https://console.algora.io/api';
|
|
17
|
+
const TIMEOUT_MS = 15000;
|
|
18
|
+
const RATE_LIMIT_MS = 500;
|
|
19
|
+
let lastRequestTime = 0;
|
|
20
|
+
async function rateLimit() {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const elapsed = now - lastRequestTime;
|
|
23
|
+
if (elapsed < RATE_LIMIT_MS) {
|
|
24
|
+
await new Promise(resolve => setTimeout(resolve, RATE_LIMIT_MS - elapsed));
|
|
25
|
+
}
|
|
26
|
+
lastRequestTime = Date.now();
|
|
27
|
+
}
|
|
28
|
+
async function fetchWithTimeout(url, options, timeoutMs) {
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(url, {
|
|
33
|
+
...options,
|
|
34
|
+
signal: controller.signal,
|
|
35
|
+
});
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
clearTimeout(timeout);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function mapApiBounty(b, org) {
|
|
43
|
+
return {
|
|
44
|
+
id: b.id,
|
|
45
|
+
title: b.title,
|
|
46
|
+
reward: b.amount_cents, // Keep in cents
|
|
47
|
+
currency: b.currency || 'USD',
|
|
48
|
+
tags: b.skills || [],
|
|
49
|
+
deadline: b.expires_at || null,
|
|
50
|
+
url: b.url || `https://console.algora.io/${org}/bounties/${b.id}`,
|
|
51
|
+
description: b.title, // Algora doesn't always include description in list
|
|
52
|
+
status: mapStatus(b.status),
|
|
53
|
+
org: org,
|
|
54
|
+
repo: b.repo_slug,
|
|
55
|
+
issueUrl: b.issue_url,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function mapStatus(status) {
|
|
59
|
+
switch (status?.toLowerCase()) {
|
|
60
|
+
case 'open':
|
|
61
|
+
case 'available':
|
|
62
|
+
return 'open';
|
|
63
|
+
case 'claimed':
|
|
64
|
+
case 'in_progress':
|
|
65
|
+
case 'assigned':
|
|
66
|
+
return 'claimed';
|
|
67
|
+
case 'completed':
|
|
68
|
+
case 'paid':
|
|
69
|
+
case 'rewarded':
|
|
70
|
+
return 'completed';
|
|
71
|
+
case 'cancelled':
|
|
72
|
+
case 'expired':
|
|
73
|
+
case 'closed':
|
|
74
|
+
return 'cancelled';
|
|
75
|
+
default:
|
|
76
|
+
return 'open';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Scan bounties from specific orgs
|
|
81
|
+
*/
|
|
82
|
+
async function scanBounties(orgs) {
|
|
83
|
+
const targetOrgs = orgs || [
|
|
84
|
+
// Popular orgs with active bounties
|
|
85
|
+
'calcom',
|
|
86
|
+
'twentyhq',
|
|
87
|
+
'formbricks',
|
|
88
|
+
'triggerdotdev',
|
|
89
|
+
'dubinc',
|
|
90
|
+
'infisical',
|
|
91
|
+
'documenso',
|
|
92
|
+
'latitude-dev',
|
|
93
|
+
'boxyhq',
|
|
94
|
+
];
|
|
95
|
+
const allBounties = [];
|
|
96
|
+
for (const org of targetOrgs) {
|
|
97
|
+
await rateLimit();
|
|
98
|
+
try {
|
|
99
|
+
const response = await fetchWithTimeout(`${ALGORA_API}/orgs/${org}/bounties?limit=50`, {
|
|
100
|
+
method: 'GET',
|
|
101
|
+
headers: {
|
|
102
|
+
'Accept': 'application/json',
|
|
103
|
+
},
|
|
104
|
+
}, TIMEOUT_MS);
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
if (response.status === 404) {
|
|
107
|
+
// Org not found or no bounties, skip silently
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
console.warn(`[AlgoraConnector] API error for ${org}:`, response.status);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
if (data.items && Array.isArray(data.items)) {
|
|
115
|
+
const bounties = data.items
|
|
116
|
+
.filter(b => b.status === 'open' || b.status === 'available')
|
|
117
|
+
.map(b => mapApiBounty(b, org));
|
|
118
|
+
allBounties.push(...bounties);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
123
|
+
console.warn(`[AlgoraConnector] Timeout for ${org}`);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
console.warn(`[AlgoraConnector] Error for ${org}:`, error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
console.log(`[AlgoraConnector] Found ${allBounties.length} open bounties from ${targetOrgs.length} orgs`);
|
|
131
|
+
return allBounties;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get details of a specific bounty
|
|
135
|
+
*/
|
|
136
|
+
async function getBountyDetails(id) {
|
|
137
|
+
await rateLimit();
|
|
138
|
+
try {
|
|
139
|
+
const response = await fetchWithTimeout(`${ALGORA_API}/bounties/${id}`, {
|
|
140
|
+
method: 'GET',
|
|
141
|
+
headers: {
|
|
142
|
+
'Accept': 'application/json',
|
|
143
|
+
},
|
|
144
|
+
}, TIMEOUT_MS);
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
console.warn(`[AlgoraConnector] Bounty ${id} not found:`, response.status);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const data = await response.json();
|
|
150
|
+
return mapApiBounty(data, data.org_slug);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.warn(`[AlgoraConnector] Error getting bounty ${id}:`, error);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Search bounties by keyword (searches across all orgs)
|
|
159
|
+
*/
|
|
160
|
+
async function searchBounties(query) {
|
|
161
|
+
// Algora doesn't have a public search endpoint, so we scan popular orgs
|
|
162
|
+
// and filter locally
|
|
163
|
+
const allBounties = await scanBounties();
|
|
164
|
+
const lowerQuery = query.toLowerCase();
|
|
165
|
+
return allBounties.filter(b => b.title.toLowerCase().includes(lowerQuery) ||
|
|
166
|
+
b.tags.some(t => t.toLowerCase().includes(lowerQuery)));
|
|
167
|
+
}
|
|
168
|
+
function getAlgoraConnector() {
|
|
169
|
+
return {
|
|
170
|
+
scanBounties,
|
|
171
|
+
getBountyDetails,
|
|
172
|
+
searchBounties,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gitcoin API Connector
|
|
3
|
+
*
|
|
4
|
+
* Read-only connector for bounty discovery using Gitcoin REST API.
|
|
5
|
+
* API Reference: https://github.com/gitcoinco/web/blob/master/docs/API.md
|
|
6
|
+
*
|
|
7
|
+
* Note: Gitcoin has deprecated bounties in favor of Grants.
|
|
8
|
+
* This connector focuses on grants discovery for research opportunities.
|
|
9
|
+
*/
|
|
10
|
+
export interface GitcoinBounty {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
reward: number;
|
|
14
|
+
currency: string;
|
|
15
|
+
tags: string[];
|
|
16
|
+
deadline: string | null;
|
|
17
|
+
url: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
status: 'open' | 'started' | 'submitted' | 'done' | 'cancelled';
|
|
20
|
+
experienceLevel: 'beginner' | 'intermediate' | 'advanced';
|
|
21
|
+
projectType: string;
|
|
22
|
+
githubUrl?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface GitcoinConnector {
|
|
25
|
+
scanBounties(filters?: GitcoinFilters): Promise<GitcoinBounty[]>;
|
|
26
|
+
getBountyDetails(id: string): Promise<GitcoinBounty | null>;
|
|
27
|
+
}
|
|
28
|
+
export interface GitcoinFilters {
|
|
29
|
+
isOpen?: boolean;
|
|
30
|
+
experienceLevel?: string;
|
|
31
|
+
projectLength?: string;
|
|
32
|
+
bountyType?: string;
|
|
33
|
+
keywords?: string[];
|
|
34
|
+
}
|
|
35
|
+
export declare function getGitcoinConnector(): GitcoinConnector;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gitcoin API Connector
|
|
4
|
+
*
|
|
5
|
+
* Read-only connector for bounty discovery using Gitcoin REST API.
|
|
6
|
+
* API Reference: https://github.com/gitcoinco/web/blob/master/docs/API.md
|
|
7
|
+
*
|
|
8
|
+
* Note: Gitcoin has deprecated bounties in favor of Grants.
|
|
9
|
+
* This connector focuses on grants discovery for research opportunities.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getGitcoinConnector = getGitcoinConnector;
|
|
13
|
+
const GITCOIN_API = 'https://gitcoin.co/api/v0.1';
|
|
14
|
+
const TIMEOUT_MS = 15000;
|
|
15
|
+
const RATE_LIMIT_MS = 1000;
|
|
16
|
+
let lastRequestTime = 0;
|
|
17
|
+
async function rateLimit() {
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
const elapsed = now - lastRequestTime;
|
|
20
|
+
if (elapsed < RATE_LIMIT_MS) {
|
|
21
|
+
await new Promise(resolve => setTimeout(resolve, RATE_LIMIT_MS - elapsed));
|
|
22
|
+
}
|
|
23
|
+
lastRequestTime = Date.now();
|
|
24
|
+
}
|
|
25
|
+
async function fetchWithTimeout(url, options, timeoutMs) {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(url, {
|
|
30
|
+
...options,
|
|
31
|
+
signal: controller.signal,
|
|
32
|
+
});
|
|
33
|
+
return response;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
clearTimeout(timeout);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function mapApiBounty(b) {
|
|
40
|
+
return {
|
|
41
|
+
id: String(b.pk),
|
|
42
|
+
title: b.title,
|
|
43
|
+
reward: b.value_true || 0,
|
|
44
|
+
currency: b.token_name || 'USD',
|
|
45
|
+
tags: b.keywords || [],
|
|
46
|
+
deadline: b.expires_date,
|
|
47
|
+
url: b.url || `https://gitcoin.co/issue/${b.pk}`,
|
|
48
|
+
description: b.issue_description?.slice(0, 500),
|
|
49
|
+
status: mapStatus(b.status),
|
|
50
|
+
experienceLevel: mapExperienceLevel(b.experience_level),
|
|
51
|
+
projectType: b.project_type || 'unknown',
|
|
52
|
+
githubUrl: b.github_url,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function mapStatus(status) {
|
|
56
|
+
switch (status?.toLowerCase()) {
|
|
57
|
+
case 'open':
|
|
58
|
+
return 'open';
|
|
59
|
+
case 'started':
|
|
60
|
+
case 'work_started':
|
|
61
|
+
return 'started';
|
|
62
|
+
case 'submitted':
|
|
63
|
+
case 'work_submitted':
|
|
64
|
+
return 'submitted';
|
|
65
|
+
case 'done':
|
|
66
|
+
case 'completed':
|
|
67
|
+
return 'done';
|
|
68
|
+
case 'cancelled':
|
|
69
|
+
case 'expired':
|
|
70
|
+
return 'cancelled';
|
|
71
|
+
default:
|
|
72
|
+
return 'open';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function mapExperienceLevel(level) {
|
|
76
|
+
switch (level?.toLowerCase()) {
|
|
77
|
+
case 'beginner':
|
|
78
|
+
case 'easy':
|
|
79
|
+
return 'beginner';
|
|
80
|
+
case 'intermediate':
|
|
81
|
+
case 'medium':
|
|
82
|
+
return 'intermediate';
|
|
83
|
+
case 'advanced':
|
|
84
|
+
case 'hard':
|
|
85
|
+
case 'expert':
|
|
86
|
+
return 'advanced';
|
|
87
|
+
default:
|
|
88
|
+
return 'intermediate';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Scan open bounties with optional filters
|
|
93
|
+
*/
|
|
94
|
+
async function scanBounties(filters) {
|
|
95
|
+
await rateLimit();
|
|
96
|
+
const params = new URLSearchParams();
|
|
97
|
+
params.set('is_open', filters?.isOpen !== false ? 'true' : 'false');
|
|
98
|
+
params.set('order_by', '-value_true'); // Highest value first
|
|
99
|
+
params.set('limit', '50');
|
|
100
|
+
if (filters?.experienceLevel) {
|
|
101
|
+
params.set('experience_level', filters.experienceLevel);
|
|
102
|
+
}
|
|
103
|
+
if (filters?.projectLength) {
|
|
104
|
+
params.set('project_length', filters.projectLength);
|
|
105
|
+
}
|
|
106
|
+
if (filters?.bountyType) {
|
|
107
|
+
params.set('bounty_type', filters.bountyType);
|
|
108
|
+
}
|
|
109
|
+
if (filters?.keywords && filters.keywords.length > 0) {
|
|
110
|
+
params.set('keywords', filters.keywords.join(','));
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const response = await fetchWithTimeout(`${GITCOIN_API}/bounties/?${params.toString()}`, {
|
|
114
|
+
method: 'GET',
|
|
115
|
+
headers: {
|
|
116
|
+
'Accept': 'application/json',
|
|
117
|
+
},
|
|
118
|
+
}, TIMEOUT_MS);
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
console.warn(`[GitcoinConnector] API error:`, response.status);
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
const data = await response.json();
|
|
124
|
+
if (!Array.isArray(data)) {
|
|
125
|
+
console.warn('[GitcoinConnector] Unexpected response format');
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
const bounties = data
|
|
129
|
+
.filter(b => b.status === 'open')
|
|
130
|
+
.map(mapApiBounty);
|
|
131
|
+
console.log(`[GitcoinConnector] Found ${bounties.length} open bounties`);
|
|
132
|
+
return bounties;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
136
|
+
console.warn('[GitcoinConnector] Request timeout');
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.warn('[GitcoinConnector] Request failed:', error);
|
|
140
|
+
}
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get details of a specific bounty
|
|
146
|
+
*/
|
|
147
|
+
async function getBountyDetails(id) {
|
|
148
|
+
await rateLimit();
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetchWithTimeout(`${GITCOIN_API}/bounties/${id}/`, {
|
|
151
|
+
method: 'GET',
|
|
152
|
+
headers: {
|
|
153
|
+
'Accept': 'application/json',
|
|
154
|
+
},
|
|
155
|
+
}, TIMEOUT_MS);
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
console.warn(`[GitcoinConnector] Bounty ${id} not found:`, response.status);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const data = await response.json();
|
|
161
|
+
return mapApiBounty(data);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.warn(`[GitcoinConnector] Error getting bounty ${id}:`, error);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function getGitcoinConnector() {
|
|
169
|
+
return {
|
|
170
|
+
scanBounties,
|
|
171
|
+
getBountyDetails,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Bounty Connector
|
|
3
|
+
*
|
|
4
|
+
* Discovers bounties from GitHub issues with bounty labels.
|
|
5
|
+
* Uses GitHub Search API to find issues with common bounty labels:
|
|
6
|
+
* - "bounty"
|
|
7
|
+
* - "reward"
|
|
8
|
+
* - "help wanted" + "good first issue" (filtered by reward mentions)
|
|
9
|
+
*
|
|
10
|
+
* Requires GITHUB_TOKEN for higher rate limits.
|
|
11
|
+
*/
|
|
12
|
+
export interface GitHubBounty {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
reward: number;
|
|
16
|
+
currency: string;
|
|
17
|
+
tags: string[];
|
|
18
|
+
deadline: string | null;
|
|
19
|
+
url: string;
|
|
20
|
+
description: string;
|
|
21
|
+
status: 'open' | 'assigned' | 'closed';
|
|
22
|
+
repo: string;
|
|
23
|
+
owner: string;
|
|
24
|
+
issueNumber: number;
|
|
25
|
+
}
|
|
26
|
+
export interface GitHubBountyConnector {
|
|
27
|
+
scanBounties(labels?: string[]): Promise<GitHubBounty[]>;
|
|
28
|
+
getBountyDetails(owner: string, repo: string, issueNumber: number): Promise<GitHubBounty | null>;
|
|
29
|
+
}
|
|
30
|
+
export declare function getGitHubBountyConnector(): GitHubBountyConnector;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GitHub Bounty Connector
|
|
4
|
+
*
|
|
5
|
+
* Discovers bounties from GitHub issues with bounty labels.
|
|
6
|
+
* Uses GitHub Search API to find issues with common bounty labels:
|
|
7
|
+
* - "bounty"
|
|
8
|
+
* - "reward"
|
|
9
|
+
* - "help wanted" + "good first issue" (filtered by reward mentions)
|
|
10
|
+
*
|
|
11
|
+
* Requires GITHUB_TOKEN for higher rate limits.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.getGitHubBountyConnector = getGitHubBountyConnector;
|
|
15
|
+
const GITHUB_API = 'https://api.github.com';
|
|
16
|
+
const TIMEOUT_MS = 15000;
|
|
17
|
+
const RATE_LIMIT_MS = 1000;
|
|
18
|
+
let lastRequestTime = 0;
|
|
19
|
+
async function rateLimit() {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
const elapsed = now - lastRequestTime;
|
|
22
|
+
if (elapsed < RATE_LIMIT_MS) {
|
|
23
|
+
await new Promise(resolve => setTimeout(resolve, RATE_LIMIT_MS - elapsed));
|
|
24
|
+
}
|
|
25
|
+
lastRequestTime = Date.now();
|
|
26
|
+
}
|
|
27
|
+
async function fetchWithTimeout(url, options, timeoutMs) {
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch(url, {
|
|
32
|
+
...options,
|
|
33
|
+
signal: controller.signal,
|
|
34
|
+
});
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
clearTimeout(timeout);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extract reward amount from text
|
|
43
|
+
* Supports: $100, 100 USD, 100 USDC, 0.1 ETH, etc.
|
|
44
|
+
*/
|
|
45
|
+
function extractReward(text) {
|
|
46
|
+
// Match patterns like: $500, 500 USD, 500 USDC, 0.1 ETH
|
|
47
|
+
const patterns = [
|
|
48
|
+
/\$\s*(\d+(?:,\d{3})*(?:\.\d{2})?)/i, // $500 or $1,000.00
|
|
49
|
+
/(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:USD|USDC|DAI)/i, // 500 USD
|
|
50
|
+
/(\d+(?:\.\d+)?)\s*ETH/i, // 0.1 ETH
|
|
51
|
+
/bounty[:\s]+\$?\s*(\d+(?:,\d{3})*)/i, // bounty: $500
|
|
52
|
+
/reward[:\s]+\$?\s*(\d+(?:,\d{3})*)/i, // reward: 500
|
|
53
|
+
];
|
|
54
|
+
for (const pattern of patterns) {
|
|
55
|
+
const match = text.match(pattern);
|
|
56
|
+
if (match) {
|
|
57
|
+
const amountStr = match[1].replace(/,/g, '');
|
|
58
|
+
const amount = parseFloat(amountStr);
|
|
59
|
+
// Determine currency
|
|
60
|
+
let currency = 'USD';
|
|
61
|
+
if (text.toLowerCase().includes('eth')) {
|
|
62
|
+
currency = 'ETH';
|
|
63
|
+
// Convert ETH to USD estimate (rough)
|
|
64
|
+
return { amount: amount * 2000, currency: 'ETH' };
|
|
65
|
+
}
|
|
66
|
+
if (text.toLowerCase().includes('usdc')) {
|
|
67
|
+
currency = 'USDC';
|
|
68
|
+
}
|
|
69
|
+
return { amount, currency };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { amount: 0, currency: 'USD' };
|
|
73
|
+
}
|
|
74
|
+
function mapIssueToBoounty(issue) {
|
|
75
|
+
// Extract repo info from repository_url
|
|
76
|
+
const repoMatch = issue.repository_url.match(/repos\/([^/]+)\/([^/]+)$/);
|
|
77
|
+
const owner = repoMatch?.[1] || 'unknown';
|
|
78
|
+
const repo = repoMatch?.[2] || 'unknown';
|
|
79
|
+
// Extract reward from title and body
|
|
80
|
+
const textToSearch = `${issue.title} ${issue.body || ''}`;
|
|
81
|
+
const { amount, currency } = extractReward(textToSearch);
|
|
82
|
+
// Determine status
|
|
83
|
+
let status = 'open';
|
|
84
|
+
if (issue.state === 'closed') {
|
|
85
|
+
status = 'closed';
|
|
86
|
+
}
|
|
87
|
+
else if (issue.assignees && issue.assignees.length > 0) {
|
|
88
|
+
status = 'assigned';
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
id: `github:${owner}/${repo}#${issue.number}`,
|
|
92
|
+
title: issue.title,
|
|
93
|
+
reward: amount,
|
|
94
|
+
currency,
|
|
95
|
+
tags: issue.labels.map(l => l.name),
|
|
96
|
+
deadline: null, // GitHub issues don't have deadlines
|
|
97
|
+
url: issue.html_url,
|
|
98
|
+
description: (issue.body || '').slice(0, 500),
|
|
99
|
+
status,
|
|
100
|
+
repo,
|
|
101
|
+
owner,
|
|
102
|
+
issueNumber: issue.number,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Scan for bounty issues on GitHub
|
|
107
|
+
*/
|
|
108
|
+
async function scanBounties(labels) {
|
|
109
|
+
const targetLabels = labels || ['bounty', 'reward', 'cash-reward', 'paid'];
|
|
110
|
+
const allBounties = [];
|
|
111
|
+
for (const label of targetLabels) {
|
|
112
|
+
await rateLimit();
|
|
113
|
+
try {
|
|
114
|
+
// Search for open issues with bounty-related labels
|
|
115
|
+
const query = `is:issue is:open label:"${label}"`;
|
|
116
|
+
const params = new URLSearchParams({
|
|
117
|
+
q: query,
|
|
118
|
+
sort: 'updated',
|
|
119
|
+
order: 'desc',
|
|
120
|
+
per_page: '30',
|
|
121
|
+
});
|
|
122
|
+
const headers = {
|
|
123
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
124
|
+
'User-Agent': 'Genesis-AI-Bounty-Hunter',
|
|
125
|
+
};
|
|
126
|
+
// Use GitHub token if available for higher rate limits
|
|
127
|
+
const token = process.env.GITHUB_TOKEN;
|
|
128
|
+
if (token) {
|
|
129
|
+
headers['Authorization'] = `token ${token}`;
|
|
130
|
+
}
|
|
131
|
+
const response = await fetchWithTimeout(`${GITHUB_API}/search/issues?${params.toString()}`, {
|
|
132
|
+
method: 'GET',
|
|
133
|
+
headers,
|
|
134
|
+
}, TIMEOUT_MS);
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
if (response.status === 403) {
|
|
137
|
+
console.warn('[GitHubBountyConnector] Rate limited, try setting GITHUB_TOKEN');
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.warn(`[GitHubBountyConnector] API error:`, response.status);
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const data = await response.json();
|
|
145
|
+
if (data.items && Array.isArray(data.items)) {
|
|
146
|
+
const bounties = data.items
|
|
147
|
+
.filter(issue => issue.state === 'open')
|
|
148
|
+
.map(mapIssueToBoounty)
|
|
149
|
+
// Filter out zero-reward issues (no bounty amount found)
|
|
150
|
+
.filter(b => b.reward > 0);
|
|
151
|
+
allBounties.push(...bounties);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
156
|
+
console.warn(`[GitHubBountyConnector] Timeout for label: ${label}`);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.warn(`[GitHubBountyConnector] Error for label ${label}:`, error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Deduplicate by URL
|
|
164
|
+
const uniqueBounties = Array.from(new Map(allBounties.map(b => [b.url, b])).values());
|
|
165
|
+
console.log(`[GitHubBountyConnector] Found ${uniqueBounties.length} bounties with rewards`);
|
|
166
|
+
return uniqueBounties;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get details of a specific issue
|
|
170
|
+
*/
|
|
171
|
+
async function getBountyDetails(owner, repo, issueNumber) {
|
|
172
|
+
await rateLimit();
|
|
173
|
+
try {
|
|
174
|
+
const headers = {
|
|
175
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
176
|
+
'User-Agent': 'Genesis-AI-Bounty-Hunter',
|
|
177
|
+
};
|
|
178
|
+
const token = process.env.GITHUB_TOKEN;
|
|
179
|
+
if (token) {
|
|
180
|
+
headers['Authorization'] = `token ${token}`;
|
|
181
|
+
}
|
|
182
|
+
const response = await fetchWithTimeout(`${GITHUB_API}/repos/${owner}/${repo}/issues/${issueNumber}`, {
|
|
183
|
+
method: 'GET',
|
|
184
|
+
headers,
|
|
185
|
+
}, TIMEOUT_MS);
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
console.warn(`[GitHubBountyConnector] Issue not found:`, response.status);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const issue = await response.json();
|
|
191
|
+
// Add repository_url for mapping
|
|
192
|
+
issue.repository_url = `https://api.github.com/repos/${owner}/${repo}`;
|
|
193
|
+
return mapIssueToBoounty(issue);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
console.warn(`[GitHubBountyConnector] Error getting issue:`, error);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function getGitHubBountyConnector() {
|
|
201
|
+
return {
|
|
202
|
+
scanBounties,
|
|
203
|
+
getBountyDetails,
|
|
204
|
+
};
|
|
205
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genesis-ai-cli",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.5.1",
|
|
4
4
|
"description": "Fully Autonomous AI System with RSI (Recursive Self-Improvement) - Self-funding, Self-deploying, Production Memory, A2A Protocol & Governance",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|