@nordsym/apiclaw 1.5.9 ā 1.5.10
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/CHANGELOG-WHITELIST-V2.md +269 -0
- package/HIVR-INTEGRATION.md +281 -0
- package/HIVR-WHITELIST-STATUS.md +205 -0
- package/HIVR-WHITELIST.md +148 -0
- package/WHITELIST-ARCHITECTURE.md +379 -0
- package/api/discover.ts +71 -0
- package/api/health.ts +20 -0
- package/convex/http.d.ts.map +1 -1
- package/convex/http.js +8 -0
- package/convex/http.js.map +1 -1
- package/convex/http.ts +8 -0
- package/dist/access-control.d.ts +45 -0
- package/dist/access-control.d.ts.map +1 -0
- package/dist/access-control.js +142 -0
- package/dist/access-control.js.map +1 -0
- package/dist/analytics.d.ts +4 -0
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +1 -0
- package/dist/analytics.js.map +1 -1
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +20 -0
- package/dist/credentials.js.map +1 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +245 -0
- package/dist/execute.js.map +1 -1
- package/dist/hivr-whitelist.d.ts +18 -0
- package/dist/hivr-whitelist.d.ts.map +1 -0
- package/dist/hivr-whitelist.js +95 -0
- package/dist/hivr-whitelist.js.map +1 -0
- package/dist/http-api.d.ts.map +1 -1
- package/dist/http-api.js +17 -33
- package/dist/http-api.js.map +1 -1
- package/dist/http-server-minimal.d.ts +7 -0
- package/dist/http-server-minimal.d.ts.map +1 -0
- package/dist/http-server-minimal.js +126 -0
- package/dist/http-server-minimal.js.map +1 -0
- package/dist/product-whitelist.d.ts +37 -0
- package/dist/product-whitelist.d.ts.map +1 -0
- package/dist/product-whitelist.js +203 -0
- package/dist/product-whitelist.js.map +1 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +1 -1
- package/dist/proxy.js.map +1 -1
- package/landing/next-env.d.ts +1 -0
- package/landing/pages/api/discover.ts +43 -0
- package/landing/pages/api/health.ts +20 -0
- package/landing/src/app/auth/verify/page.tsx +6 -0
- package/landing/src/app/dashboard/verify/page.tsx +6 -0
- package/landing/src/app/join/page.tsx +6 -0
- package/landing/src/app/mou/[partnerId]/page.tsx +6 -0
- package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +6 -0
- package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +5 -0
- package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +5 -0
- package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +5 -0
- package/landing/src/app/providers/dashboard/[apiId]/page.tsx +5 -0
- package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +5 -0
- package/landing/src/app/providers/dashboard/layout.tsx +6 -6
- package/landing/src/app/providers/dashboard/verify/page.tsx +6 -0
- package/landing/src/app/upgrade/page.tsx +6 -0
- package/landing/src/app/workspace/page.tsx +6 -0
- package/landing/src/lib/stats.json +1 -1
- package/package.json +4 -2
- package/scripts/test-whitelist-v2.sh +128 -0
- package/src/access-control.ts +174 -0
- package/src/analytics.ts +5 -0
- package/src/credentials.ts +20 -0
- package/src/execute.ts +247 -0
- package/src/hivr-whitelist.ts +110 -0
- package/src/http-api.ts +18 -34
- package/src/http-server-minimal.ts +154 -0
- package/src/product-whitelist.ts +246 -0
- package/src/proxy.ts +1 -1
package/src/execute.ts
CHANGED
|
@@ -402,6 +402,21 @@ const apiEndpoints: Record<string, Record<string, { url: string; method: string;
|
|
|
402
402
|
run_code: { url: 'https://api.e2b.dev/v1/sandboxes', method: 'POST' },
|
|
403
403
|
run_shell: { url: 'https://api.e2b.dev/v1/sandboxes', method: 'POST' },
|
|
404
404
|
},
|
|
405
|
+
apilayer: {
|
|
406
|
+
exchange_rates: { url: 'https://api.apilayer.com/exchangerates_data/latest', method: 'GET' },
|
|
407
|
+
market_data: { url: 'http://api.marketstack.com/v1/eod', method: 'GET' },
|
|
408
|
+
aviation: { url: 'http://api.aviationstack.com/v1/flights', method: 'GET' },
|
|
409
|
+
pdf_generate: { url: 'https://api.pdflayer.com/api/convert', method: 'GET' },
|
|
410
|
+
screenshot: { url: 'https://api.screenshotlayer.com/api/capture', method: 'GET' },
|
|
411
|
+
verify_email: { url: 'https://api.apilayer.com/email_verification/check', method: 'GET' },
|
|
412
|
+
verify_number: { url: 'https://api.apilayer.com/number_verification/validate', method: 'GET' },
|
|
413
|
+
vat_check: { url: 'http://apilayer.net/api/validate', method: 'GET' },
|
|
414
|
+
world_news: { url: 'https://api.apilayer.com/world_news/search-news', method: 'GET' },
|
|
415
|
+
finance_news: { url: 'https://api.apilayer.com/financelayer/news', method: 'GET' },
|
|
416
|
+
scrape: { url: 'https://api.apilayer.com/adv_scraper/scraper', method: 'GET' },
|
|
417
|
+
image_crop: { url: 'https://api.apilayer.com/image_crop/crop', method: 'GET' },
|
|
418
|
+
skills: { url: 'https://api.apilayer.com/skills', method: 'GET' },
|
|
419
|
+
},
|
|
405
420
|
};
|
|
406
421
|
|
|
407
422
|
/**
|
|
@@ -1644,6 +1659,238 @@ const handlers: Record<string, Record<string, (params: any, creds: any) => Promi
|
|
|
1644
1659
|
};
|
|
1645
1660
|
},
|
|
1646
1661
|
},
|
|
1662
|
+
|
|
1663
|
+
// APILayer - 14 APIs via one provider
|
|
1664
|
+
apilayer: {
|
|
1665
|
+
// Helper to pick the right key per action
|
|
1666
|
+
exchange_rates: async (params, creds) => {
|
|
1667
|
+
const key = creds.APILAYER_EXCHANGERATE_KEY || creds.api_key;
|
|
1668
|
+
const { base = 'USD', symbols, date } = params;
|
|
1669
|
+
const endpoint = date ? 'historical' : 'latest';
|
|
1670
|
+
const url = new URL(`https://api.apilayer.com/exchangerates_data/${endpoint}`);
|
|
1671
|
+
url.searchParams.set('base', base);
|
|
1672
|
+
if (symbols) url.searchParams.set('symbols', symbols);
|
|
1673
|
+
if (date) url.searchParams.set('date', date);
|
|
1674
|
+
|
|
1675
|
+
const response = await fetchWithRetry(url.toString(), {
|
|
1676
|
+
headers: { 'apikey': key },
|
|
1677
|
+
}, { provider: 'apilayer', action: 'exchange_rates' });
|
|
1678
|
+
|
|
1679
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1680
|
+
if (!response.ok) return createErrorResult('apilayer', 'exchange_rates', (data.message as string) || 'Request failed', statusToErrorCode(response.status));
|
|
1681
|
+
return { success: true, provider: 'apilayer', action: 'exchange_rates', data };
|
|
1682
|
+
},
|
|
1683
|
+
|
|
1684
|
+
market_data: async (params, creds) => {
|
|
1685
|
+
const key = creds.APILAYER_MARKETSTACK_KEY || creds.api_key;
|
|
1686
|
+
const { symbols, date_from, date_to, limit = 10 } = params;
|
|
1687
|
+
if (!symbols) return createErrorResult('apilayer', 'market_data', 'Missing required param: symbols', ERROR_CODES.INVALID_PARAMS);
|
|
1688
|
+
|
|
1689
|
+
const url = new URL('http://api.marketstack.com/v1/eod');
|
|
1690
|
+
url.searchParams.set('access_key', key);
|
|
1691
|
+
url.searchParams.set('symbols', symbols);
|
|
1692
|
+
url.searchParams.set('limit', limit.toString());
|
|
1693
|
+
if (date_from) url.searchParams.set('date_from', date_from);
|
|
1694
|
+
if (date_to) url.searchParams.set('date_to', date_to);
|
|
1695
|
+
|
|
1696
|
+
const response = await fetchWithRetry(url.toString(), {}, { provider: 'apilayer', action: 'market_data' });
|
|
1697
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1698
|
+
if (!response.ok) return createErrorResult('apilayer', 'market_data', (data.error as any)?.message || 'Request failed', statusToErrorCode(response.status));
|
|
1699
|
+
return { success: true, provider: 'apilayer', action: 'market_data', data };
|
|
1700
|
+
},
|
|
1701
|
+
|
|
1702
|
+
aviation: async (params, creds) => {
|
|
1703
|
+
const key = creds.APILAYER_AVIATIONSTACK_KEY || creds.api_key;
|
|
1704
|
+
const { flight_iata, dep_iata, arr_iata, airline_iata } = params;
|
|
1705
|
+
const url = new URL('http://api.aviationstack.com/v1/flights');
|
|
1706
|
+
url.searchParams.set('access_key', key);
|
|
1707
|
+
if (flight_iata) url.searchParams.set('flight_iata', flight_iata);
|
|
1708
|
+
if (dep_iata) url.searchParams.set('dep_iata', dep_iata);
|
|
1709
|
+
if (arr_iata) url.searchParams.set('arr_iata', arr_iata);
|
|
1710
|
+
if (airline_iata) url.searchParams.set('airline_iata', airline_iata);
|
|
1711
|
+
|
|
1712
|
+
const response = await fetchWithRetry(url.toString(), {}, { provider: 'apilayer', action: 'aviation' });
|
|
1713
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1714
|
+
if (!response.ok) return createErrorResult('apilayer', 'aviation', 'Request failed', statusToErrorCode(response.status));
|
|
1715
|
+
return { success: true, provider: 'apilayer', action: 'aviation', data };
|
|
1716
|
+
},
|
|
1717
|
+
|
|
1718
|
+
pdf_generate: async (params, creds) => {
|
|
1719
|
+
const key = creds.APILAYER_PDFLAYER_KEY || creds.api_key;
|
|
1720
|
+
const { document_url, document_html, page_size = 'A4' } = params;
|
|
1721
|
+
if (!document_url && !document_html) return createErrorResult('apilayer', 'pdf_generate', 'Missing: document_url or document_html', ERROR_CODES.INVALID_PARAMS);
|
|
1722
|
+
|
|
1723
|
+
const url = new URL('https://api.pdflayer.com/api/convert');
|
|
1724
|
+
url.searchParams.set('access_key', key);
|
|
1725
|
+
url.searchParams.set('page_size', page_size);
|
|
1726
|
+
if (document_url) url.searchParams.set('document_url', document_url);
|
|
1727
|
+
if (document_html) url.searchParams.set('document_html', document_html);
|
|
1728
|
+
|
|
1729
|
+
const response = await fetchWithRetry(url.toString(), {}, { provider: 'apilayer', action: 'pdf_generate' });
|
|
1730
|
+
const contentType = response.headers.get('content-type') || '';
|
|
1731
|
+
if (contentType.includes('application/pdf')) {
|
|
1732
|
+
return { success: true, provider: 'apilayer', action: 'pdf_generate', data: { message: 'PDF generated', content_type: 'application/pdf', size: response.headers.get('content-length') } };
|
|
1733
|
+
}
|
|
1734
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1735
|
+
if (!response.ok) return createErrorResult('apilayer', 'pdf_generate', (data.error as any)?.info || 'Request failed', statusToErrorCode(response.status));
|
|
1736
|
+
return { success: true, provider: 'apilayer', action: 'pdf_generate', data };
|
|
1737
|
+
},
|
|
1738
|
+
|
|
1739
|
+
screenshot: async (params, creds) => {
|
|
1740
|
+
const key = creds.APILAYER_SCREENSHOTLAYER_KEY || creds.api_key;
|
|
1741
|
+
const { url: targetUrl, viewport = '1440x900', fullpage = 0 } = params;
|
|
1742
|
+
if (!targetUrl) return createErrorResult('apilayer', 'screenshot', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
|
|
1743
|
+
|
|
1744
|
+
const url = new URL('https://api.screenshotlayer.com/api/capture');
|
|
1745
|
+
url.searchParams.set('access_key', key);
|
|
1746
|
+
url.searchParams.set('url', targetUrl);
|
|
1747
|
+
url.searchParams.set('viewport', viewport);
|
|
1748
|
+
url.searchParams.set('fullpage', fullpage.toString());
|
|
1749
|
+
|
|
1750
|
+
const response = await fetchWithRetry(url.toString(), {}, { provider: 'apilayer', action: 'screenshot' });
|
|
1751
|
+
const contentType = response.headers.get('content-type') || '';
|
|
1752
|
+
if (contentType.includes('image/')) {
|
|
1753
|
+
return { success: true, provider: 'apilayer', action: 'screenshot', data: { message: 'Screenshot captured', content_type: contentType, url: url.toString() } };
|
|
1754
|
+
}
|
|
1755
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1756
|
+
return { success: true, provider: 'apilayer', action: 'screenshot', data };
|
|
1757
|
+
},
|
|
1758
|
+
|
|
1759
|
+
verify_email: async (params, creds) => {
|
|
1760
|
+
const key = creds.APILAYER_EMAILVERIFY_KEY || creds.api_key;
|
|
1761
|
+
const { email } = params;
|
|
1762
|
+
if (!email) return createErrorResult('apilayer', 'verify_email', 'Missing required param: email', ERROR_CODES.INVALID_PARAMS);
|
|
1763
|
+
|
|
1764
|
+
const url = new URL('https://api.apilayer.com/email_verification/check');
|
|
1765
|
+
url.searchParams.set('email', email);
|
|
1766
|
+
|
|
1767
|
+
const response = await fetchWithRetry(url.toString(), {
|
|
1768
|
+
headers: { 'apikey': key },
|
|
1769
|
+
}, { provider: 'apilayer', action: 'verify_email' });
|
|
1770
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1771
|
+
if (!response.ok) return createErrorResult('apilayer', 'verify_email', 'Request failed', statusToErrorCode(response.status));
|
|
1772
|
+
return { success: true, provider: 'apilayer', action: 'verify_email', data };
|
|
1773
|
+
},
|
|
1774
|
+
|
|
1775
|
+
verify_number: async (params, creds) => {
|
|
1776
|
+
const key = creds.APILAYER_NUMVERIFY_KEY || creds.api_key;
|
|
1777
|
+
const { number } = params;
|
|
1778
|
+
if (!number) return createErrorResult('apilayer', 'verify_number', 'Missing required param: number', ERROR_CODES.INVALID_PARAMS);
|
|
1779
|
+
|
|
1780
|
+
const url = new URL('https://api.apilayer.com/number_verification/validate');
|
|
1781
|
+
url.searchParams.set('number', number);
|
|
1782
|
+
|
|
1783
|
+
const response = await fetchWithRetry(url.toString(), {
|
|
1784
|
+
headers: { 'apikey': key },
|
|
1785
|
+
}, { provider: 'apilayer', action: 'verify_number' });
|
|
1786
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1787
|
+
if (!response.ok) return createErrorResult('apilayer', 'verify_number', 'Request failed', statusToErrorCode(response.status));
|
|
1788
|
+
return { success: true, provider: 'apilayer', action: 'verify_number', data };
|
|
1789
|
+
},
|
|
1790
|
+
|
|
1791
|
+
vat_check: async (params, creds) => {
|
|
1792
|
+
const key = creds.APILAYER_VATLAYER_KEY || creds.api_key;
|
|
1793
|
+
const { vat_number } = params;
|
|
1794
|
+
if (!vat_number) return createErrorResult('apilayer', 'vat_check', 'Missing required param: vat_number', ERROR_CODES.INVALID_PARAMS);
|
|
1795
|
+
|
|
1796
|
+
const url = new URL('http://apilayer.net/api/validate');
|
|
1797
|
+
url.searchParams.set('access_key', key);
|
|
1798
|
+
url.searchParams.set('vat_number', vat_number);
|
|
1799
|
+
|
|
1800
|
+
const response = await fetchWithRetry(url.toString(), {}, { provider: 'apilayer', action: 'vat_check' });
|
|
1801
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1802
|
+
if (!response.ok) return createErrorResult('apilayer', 'vat_check', 'Request failed', statusToErrorCode(response.status));
|
|
1803
|
+
return { success: true, provider: 'apilayer', action: 'vat_check', data };
|
|
1804
|
+
},
|
|
1805
|
+
|
|
1806
|
+
world_news: async (params, creds) => {
|
|
1807
|
+
const key = creds.APILAYER_WORLDNEWS_KEY || creds.api_key;
|
|
1808
|
+
const { text, source_countries, language = 'en', number = 5 } = params;
|
|
1809
|
+
|
|
1810
|
+
const url = new URL('https://api.apilayer.com/world_news/search-news');
|
|
1811
|
+
if (text) url.searchParams.set('text', text);
|
|
1812
|
+
if (source_countries) url.searchParams.set('source-countries', source_countries);
|
|
1813
|
+
url.searchParams.set('language', language);
|
|
1814
|
+
url.searchParams.set('number', number.toString());
|
|
1815
|
+
|
|
1816
|
+
const response = await fetchWithRetry(url.toString(), {
|
|
1817
|
+
headers: { 'apikey': key },
|
|
1818
|
+
}, { provider: 'apilayer', action: 'world_news' });
|
|
1819
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1820
|
+
if (!response.ok) return createErrorResult('apilayer', 'world_news', 'Request failed', statusToErrorCode(response.status));
|
|
1821
|
+
return { success: true, provider: 'apilayer', action: 'world_news', data };
|
|
1822
|
+
},
|
|
1823
|
+
|
|
1824
|
+
finance_news: async (params, creds) => {
|
|
1825
|
+
const key = creds.APILAYER_FINANCENEWS_KEY || creds.api_key;
|
|
1826
|
+
const { tickers, text, number = 5 } = params;
|
|
1827
|
+
|
|
1828
|
+
const url = new URL('https://api.apilayer.com/financelayer/news');
|
|
1829
|
+
if (tickers) url.searchParams.set('tickers', tickers);
|
|
1830
|
+
if (text) url.searchParams.set('keywords', text);
|
|
1831
|
+
url.searchParams.set('limit', number.toString());
|
|
1832
|
+
|
|
1833
|
+
const response = await fetchWithRetry(url.toString(), {
|
|
1834
|
+
headers: { 'apikey': key },
|
|
1835
|
+
}, { provider: 'apilayer', action: 'finance_news' });
|
|
1836
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1837
|
+
if (!response.ok) return createErrorResult('apilayer', 'finance_news', 'Request failed', statusToErrorCode(response.status));
|
|
1838
|
+
return { success: true, provider: 'apilayer', action: 'finance_news', data };
|
|
1839
|
+
},
|
|
1840
|
+
|
|
1841
|
+
scrape: async (params, creds) => {
|
|
1842
|
+
const key = creds.APILAYER_SCRAPER_KEY || creds.api_key;
|
|
1843
|
+
const { url: targetUrl } = params;
|
|
1844
|
+
if (!targetUrl) return createErrorResult('apilayer', 'scrape', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
|
|
1845
|
+
|
|
1846
|
+
const url = new URL('https://api.apilayer.com/adv_scraper/scraper');
|
|
1847
|
+
url.searchParams.set('url', targetUrl);
|
|
1848
|
+
|
|
1849
|
+
const response = await fetchWithRetry(url.toString(), {
|
|
1850
|
+
headers: { 'apikey': key },
|
|
1851
|
+
}, { provider: 'apilayer', action: 'scrape' });
|
|
1852
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1853
|
+
if (!response.ok) return createErrorResult('apilayer', 'scrape', 'Request failed', statusToErrorCode(response.status));
|
|
1854
|
+
return { success: true, provider: 'apilayer', action: 'scrape', data };
|
|
1855
|
+
},
|
|
1856
|
+
|
|
1857
|
+
image_crop: async (params, creds) => {
|
|
1858
|
+
const key = creds.APILAYER_IMAGECROP_KEY || creds.api_key;
|
|
1859
|
+
const { url: imageUrl, width, height } = params;
|
|
1860
|
+
if (!imageUrl) return createErrorResult('apilayer', 'image_crop', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
|
|
1861
|
+
|
|
1862
|
+
const url = new URL('https://api.apilayer.com/image_crop/crop');
|
|
1863
|
+
url.searchParams.set('url', imageUrl);
|
|
1864
|
+
if (width) url.searchParams.set('width', width.toString());
|
|
1865
|
+
if (height) url.searchParams.set('height', height.toString());
|
|
1866
|
+
|
|
1867
|
+
const response = await fetchWithRetry(url.toString(), {
|
|
1868
|
+
headers: { 'apikey': key },
|
|
1869
|
+
}, { provider: 'apilayer', action: 'image_crop' });
|
|
1870
|
+
const contentType = response.headers.get('content-type') || '';
|
|
1871
|
+
if (contentType.includes('image/')) {
|
|
1872
|
+
return { success: true, provider: 'apilayer', action: 'image_crop', data: { message: 'Image cropped', content_type: contentType } };
|
|
1873
|
+
}
|
|
1874
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1875
|
+
return { success: true, provider: 'apilayer', action: 'image_crop', data };
|
|
1876
|
+
},
|
|
1877
|
+
|
|
1878
|
+
skills: async (params, creds) => {
|
|
1879
|
+
const key = creds.APILAYER_SKILLAPI_KEY || creds.api_key;
|
|
1880
|
+
const { q } = params;
|
|
1881
|
+
if (!q) return createErrorResult('apilayer', 'skills', 'Missing required param: q', ERROR_CODES.INVALID_PARAMS);
|
|
1882
|
+
|
|
1883
|
+
const url = new URL('https://api.apilayer.com/skills');
|
|
1884
|
+
url.searchParams.set('q', q);
|
|
1885
|
+
|
|
1886
|
+
const response = await fetchWithRetry(url.toString(), {
|
|
1887
|
+
headers: { 'apikey': key },
|
|
1888
|
+
}, { provider: 'apilayer', action: 'skills' });
|
|
1889
|
+
const data = await response.json() as Record<string, unknown>;
|
|
1890
|
+
if (!response.ok) return createErrorResult('apilayer', 'skills', 'Request failed', statusToErrorCode(response.status));
|
|
1891
|
+
return { success: true, provider: 'apilayer', action: 'skills', data };
|
|
1892
|
+
},
|
|
1893
|
+
},
|
|
1647
1894
|
};
|
|
1648
1895
|
|
|
1649
1896
|
// Get available actions for a provider (static handlers only)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hivr Bees Auto-Whitelist
|
|
3
|
+
* Dynamically fetches active agents from Hivr's Convex deployment
|
|
4
|
+
* Falls back to static whitelist if Convex is unreachable
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Hivr PROD Convex deployment
|
|
8
|
+
const HIVR_CONVEX_URL = "https://sensible-quail-275.convex.cloud";
|
|
9
|
+
|
|
10
|
+
// Fallback static whitelist (in case Convex is down)
|
|
11
|
+
const STATIC_WHITELIST = [
|
|
12
|
+
'bytebee',
|
|
13
|
+
'analyzerbee',
|
|
14
|
+
'buildbee',
|
|
15
|
+
'buzzwriter',
|
|
16
|
+
'hivemind',
|
|
17
|
+
'hivesage',
|
|
18
|
+
'symbot',
|
|
19
|
+
'hivrqueen',
|
|
20
|
+
'marketmaven',
|
|
21
|
+
'reconbee',
|
|
22
|
+
'sprintbee',
|
|
23
|
+
'quillbee',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Cache whitelist for 5 minutes
|
|
27
|
+
let cachedWhitelist: string[] | null = null;
|
|
28
|
+
let cacheExpiry: number = 0;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Fetch all active agents from Hivr Convex
|
|
32
|
+
*/
|
|
33
|
+
async function fetchHivrAgents(): Promise<string[]> {
|
|
34
|
+
try {
|
|
35
|
+
// Call Convex HTTP API directly
|
|
36
|
+
const response = await fetch(`${HIVR_CONVEX_URL}/api/query`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
path: 'agents:list',
|
|
43
|
+
args: {},
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
console.warn('[Hivr Whitelist] Convex HTTP API error, using static whitelist');
|
|
49
|
+
return STATIC_WHITELIST;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const agents = await response.json() as any[];
|
|
53
|
+
|
|
54
|
+
if (!agents || !Array.isArray(agents)) {
|
|
55
|
+
console.warn('[Hivr Whitelist] Invalid response from Hivr Convex, using static whitelist');
|
|
56
|
+
return STATIC_WHITELIST;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Extract handles (Hivr uses 'handle', not 'agentId')
|
|
60
|
+
const handles = agents
|
|
61
|
+
.map((a: any) => a.handle?.toLowerCase().trim())
|
|
62
|
+
.filter((h: string | undefined) => h && h.length > 0);
|
|
63
|
+
|
|
64
|
+
console.log(`[Hivr Whitelist] Fetched ${handles.length} agents from Hivr`);
|
|
65
|
+
return handles;
|
|
66
|
+
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('[Hivr Whitelist] Failed to fetch from Hivr Convex:', error);
|
|
69
|
+
return STATIC_WHITELIST;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get current whitelist (cached or fresh)
|
|
75
|
+
*/
|
|
76
|
+
export async function getWhitelist(): Promise<string[]> {
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
|
|
79
|
+
// Return cached if still valid
|
|
80
|
+
if (cachedWhitelist && now < cacheExpiry) {
|
|
81
|
+
return cachedWhitelist;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Fetch fresh whitelist
|
|
85
|
+
cachedWhitelist = await fetchHivrAgents();
|
|
86
|
+
cacheExpiry = now + (5 * 60 * 1000); // 5 minutes
|
|
87
|
+
|
|
88
|
+
return cachedWhitelist;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if agent is authorized
|
|
93
|
+
*/
|
|
94
|
+
export async function isAuthorized(agentId: string | undefined): Promise<boolean> {
|
|
95
|
+
if (!agentId) return false;
|
|
96
|
+
|
|
97
|
+
const whitelist = await getWhitelist();
|
|
98
|
+
const normalized = agentId.toLowerCase().trim();
|
|
99
|
+
|
|
100
|
+
return whitelist.includes(normalized);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Force refresh whitelist (call after adding new bee)
|
|
105
|
+
*/
|
|
106
|
+
export function invalidateCache(): void {
|
|
107
|
+
cachedWhitelist = null;
|
|
108
|
+
cacheExpiry = 0;
|
|
109
|
+
console.log('[Hivr Whitelist] Cache invalidated');
|
|
110
|
+
}
|
package/src/http-api.ts
CHANGED
|
@@ -17,23 +17,7 @@ import { isOpenAPI, executeOpenAPI } from './open-apis.js';
|
|
|
17
17
|
import { executeMetered } from './metered.js';
|
|
18
18
|
import { logAPICall } from './analytics.js';
|
|
19
19
|
import { getMachineFingerprint } from './session.js';
|
|
20
|
-
|
|
21
|
-
// Hivr bees whitelist - these agents get free unlimited access
|
|
22
|
-
const HIVR_BEES_WHITELIST = [
|
|
23
|
-
'bytebee',
|
|
24
|
-
'analyzerbee',
|
|
25
|
-
'buildbee',
|
|
26
|
-
'buzzwriter',
|
|
27
|
-
'hivemind',
|
|
28
|
-
'hivesage',
|
|
29
|
-
'symbot',
|
|
30
|
-
'hivrqueen',
|
|
31
|
-
'marketmaven',
|
|
32
|
-
'reconbee',
|
|
33
|
-
'sprintbee',
|
|
34
|
-
'quillbee',
|
|
35
|
-
// Add more as Hivr grows
|
|
36
|
-
];
|
|
20
|
+
import { isAuthorized, getProduct } from './product-whitelist.js';
|
|
37
21
|
|
|
38
22
|
interface APIRequest {
|
|
39
23
|
provider: string;
|
|
@@ -42,15 +26,6 @@ interface APIRequest {
|
|
|
42
26
|
agentId: string;
|
|
43
27
|
}
|
|
44
28
|
|
|
45
|
-
/**
|
|
46
|
-
* Check if agent is authorized (Hivr bee)
|
|
47
|
-
*/
|
|
48
|
-
function isAuthorized(agentId: string | undefined): boolean {
|
|
49
|
-
if (!agentId) return false;
|
|
50
|
-
const normalized = agentId.toLowerCase().trim();
|
|
51
|
-
return HIVR_BEES_WHITELIST.includes(normalized);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
29
|
/**
|
|
55
30
|
* Parse JSON body from request
|
|
56
31
|
*/
|
|
@@ -97,7 +72,7 @@ async function handleDiscover(req: IncomingMessage, res: ServerResponse, url: UR
|
|
|
97
72
|
return;
|
|
98
73
|
}
|
|
99
74
|
|
|
100
|
-
if (!isAuthorized(agentId || undefined)) {
|
|
75
|
+
if (!(await isAuthorized(agentId || undefined))) {
|
|
101
76
|
sendJSON(res, 403, {
|
|
102
77
|
error: 'Unauthorized',
|
|
103
78
|
message: 'This endpoint is restricted to Hivr bees. Contact admin@nordsym.com for access.',
|
|
@@ -109,15 +84,17 @@ async function handleDiscover(req: IncomingMessage, res: ServerResponse, url: UR
|
|
|
109
84
|
const results = discoverAPIs(query, { category, maxResults });
|
|
110
85
|
const responseTimeMs = Date.now() - startTime;
|
|
111
86
|
|
|
112
|
-
// Log to analytics
|
|
87
|
+
// Log to analytics with product info
|
|
88
|
+
const product = agentId ? getProduct(agentId) : null;
|
|
113
89
|
logAPICall({
|
|
114
90
|
timestamp: new Date().toISOString(),
|
|
115
91
|
provider: 'apiclaw_discovery',
|
|
116
92
|
action: 'discover',
|
|
117
93
|
type: 'open',
|
|
118
|
-
userId:
|
|
94
|
+
userId: agentId || 'unknown',
|
|
119
95
|
success: true,
|
|
120
96
|
latencyMs: responseTimeMs,
|
|
97
|
+
metadata: product ? { product } : undefined,
|
|
121
98
|
});
|
|
122
99
|
|
|
123
100
|
sendJSON(res, 200, {
|
|
@@ -158,10 +135,15 @@ async function handleCallAPI(req: IncomingMessage, res: ServerResponse): Promise
|
|
|
158
135
|
return;
|
|
159
136
|
}
|
|
160
137
|
|
|
161
|
-
|
|
138
|
+
// Check whitelist + access control
|
|
139
|
+
const { isAllowed } = await import('./access-control.js');
|
|
140
|
+
const accessCheck = await isAllowed(agentId, provider);
|
|
141
|
+
|
|
142
|
+
if (!accessCheck.allowed) {
|
|
162
143
|
sendJSON(res, 403, {
|
|
163
|
-
error: '
|
|
164
|
-
message:
|
|
144
|
+
error: 'Access Denied',
|
|
145
|
+
message: accessCheck.reason || 'Not authorized',
|
|
146
|
+
hint: 'Contact admin@nordsym.com for access',
|
|
165
147
|
});
|
|
166
148
|
return;
|
|
167
149
|
}
|
|
@@ -201,16 +183,18 @@ async function handleCallAPI(req: IncomingMessage, res: ServerResponse): Promise
|
|
|
201
183
|
|
|
202
184
|
const latencyMs = Date.now() - startTime;
|
|
203
185
|
|
|
204
|
-
// Log to analytics
|
|
186
|
+
// Log to analytics with product info
|
|
187
|
+
const product = getProduct(agentId);
|
|
205
188
|
logAPICall({
|
|
206
189
|
timestamp: new Date().toISOString(),
|
|
207
190
|
provider,
|
|
208
191
|
action,
|
|
209
192
|
type: apiType!,
|
|
210
|
-
userId:
|
|
193
|
+
userId: agentId,
|
|
211
194
|
success,
|
|
212
195
|
latencyMs,
|
|
213
196
|
error,
|
|
197
|
+
metadata: product ? { product } : undefined,
|
|
214
198
|
});
|
|
215
199
|
|
|
216
200
|
sendJSON(res, success ? 200 : 500, {
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Minimal HTTP API Server for APIClaw
|
|
4
|
+
* Bypasses chain executor imports
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createServer } from 'http';
|
|
8
|
+
import { URL } from 'url';
|
|
9
|
+
|
|
10
|
+
const PORT = parseInt(process.env.PORT || '3001');
|
|
11
|
+
|
|
12
|
+
// Import whitelist directly
|
|
13
|
+
import { isAuthorized, getProduct } from './product-whitelist.js';
|
|
14
|
+
|
|
15
|
+
interface APIRequest {
|
|
16
|
+
provider: string;
|
|
17
|
+
action: string;
|
|
18
|
+
params: Record<string, any>;
|
|
19
|
+
agentId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sendJSON(res: any, status: number, data: any): void {
|
|
23
|
+
res.writeHead(status, {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'Access-Control-Allow-Origin': '*',
|
|
26
|
+
});
|
|
27
|
+
res.end(JSON.stringify(data));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function parseBody<T>(req: any): Promise<T> {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
let body = '';
|
|
33
|
+
req.on('data', (chunk: any) => body += chunk.toString());
|
|
34
|
+
req.on('end', () => {
|
|
35
|
+
try {
|
|
36
|
+
resolve(JSON.parse(body));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
reject(new Error('Invalid JSON'));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const server = createServer(async (req, res) => {
|
|
45
|
+
const url = new URL(req.url || '/', `http://${req.headers.host}`);
|
|
46
|
+
|
|
47
|
+
console.log(`[APIClaw] ${req.method} ${url.pathname}`);
|
|
48
|
+
|
|
49
|
+
// CORS
|
|
50
|
+
if (req.method === 'OPTIONS') {
|
|
51
|
+
res.writeHead(204, {
|
|
52
|
+
'Access-Control-Allow-Origin': '*',
|
|
53
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
54
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
55
|
+
});
|
|
56
|
+
res.end();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Health check
|
|
61
|
+
if (url.pathname === '/health') {
|
|
62
|
+
sendJSON(res, 200, {
|
|
63
|
+
status: 'ok',
|
|
64
|
+
service: 'apiclaw-http-api',
|
|
65
|
+
version: '2.0.0',
|
|
66
|
+
whitelist: 'multi-product',
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Discovery endpoint
|
|
72
|
+
if (url.pathname === '/api/discover' && req.method === 'GET') {
|
|
73
|
+
const query = url.searchParams.get('query');
|
|
74
|
+
const agentId = url.searchParams.get('agentId');
|
|
75
|
+
|
|
76
|
+
if (!query) {
|
|
77
|
+
sendJSON(res, 400, { error: 'Missing query parameter' });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const authorized = await isAuthorized(agentId || undefined);
|
|
82
|
+
|
|
83
|
+
if (!authorized) {
|
|
84
|
+
sendJSON(res, 403, {
|
|
85
|
+
error: 'Unauthorized',
|
|
86
|
+
message: 'This endpoint is restricted. Contact admin@nordsym.com',
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const product = agentId ? getProduct(agentId) : null;
|
|
92
|
+
|
|
93
|
+
sendJSON(res, 200, {
|
|
94
|
+
success: true,
|
|
95
|
+
query,
|
|
96
|
+
agentId,
|
|
97
|
+
product,
|
|
98
|
+
message: 'Whitelist v2.0 active - discovery endpoint placeholder',
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Call API endpoint
|
|
104
|
+
if (url.pathname === '/api/call_api' && req.method === 'POST') {
|
|
105
|
+
try {
|
|
106
|
+
const body = await parseBody<APIRequest>(req);
|
|
107
|
+
const { provider, action, params, agentId } = body;
|
|
108
|
+
|
|
109
|
+
if (!provider || !action || !agentId) {
|
|
110
|
+
sendJSON(res, 400, {
|
|
111
|
+
error: 'Missing required fields',
|
|
112
|
+
required: ['provider', 'action', 'agentId', 'params'],
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const authorized = await isAuthorized(agentId);
|
|
118
|
+
|
|
119
|
+
if (!authorized) {
|
|
120
|
+
sendJSON(res, 403, {
|
|
121
|
+
error: 'Unauthorized',
|
|
122
|
+
message: 'Agent not whitelisted',
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const product = getProduct(agentId);
|
|
128
|
+
|
|
129
|
+
sendJSON(res, 200, {
|
|
130
|
+
success: true,
|
|
131
|
+
agentId,
|
|
132
|
+
provider,
|
|
133
|
+
action,
|
|
134
|
+
product,
|
|
135
|
+
message: 'Whitelist v2.0 active - execution placeholder',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
} catch (e: any) {
|
|
139
|
+
sendJSON(res, 400, { error: e.message });
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 404
|
|
145
|
+
sendJSON(res, 404, { error: 'Not found' });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
server.listen(PORT, () => {
|
|
149
|
+
console.log(`\nš¦ APIClaw HTTP API (Whitelist v2.0)`);
|
|
150
|
+
console.log(` Running on http://localhost:${PORT}`);
|
|
151
|
+
console.log(` GET /health`);
|
|
152
|
+
console.log(` GET /api/discover?query=...&agentId=...`);
|
|
153
|
+
console.log(` POST /api/call_api\n`);
|
|
154
|
+
});
|