spectrawl 0.3.18 → 0.3.22

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.
Files changed (3) hide show
  1. package/README.md +14 -10
  2. package/package.json +2 -2
  3. package/src/server.js +59 -0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The unified web layer for AI agents. Search, browse, authenticate, and act on platforms — one package, self-hosted.
4
4
 
5
- **5,000 free searches/month** via Gemini Grounded Search. Full page scraping, stealth browsing, 24 platform adapters.
5
+ **5,000 free searches/month** via Gemini Grounded Search. Full page scraping, stealth browsing, 19 platform adapters.
6
6
 
7
7
  ## What It Does
8
8
 
@@ -55,7 +55,7 @@ Different tools for different needs.
55
55
  | Returns | Snippets + AI answer | Full page content + snippets |
56
56
  | Self-hosted | No | Yes |
57
57
  | Stealth browsing | No | Yes (Camoufox + Playwright) |
58
- | Platform posting | No | 24 adapters |
58
+ | Platform posting | No | 19 adapters |
59
59
  | Auth management | No | Cookie store + auto-refresh |
60
60
  | Cached repeats | No | <1ms |
61
61
 
@@ -63,15 +63,16 @@ Different tools for different needs.
63
63
 
64
64
  ## Search
65
65
 
66
- Default cascade: **Gemini Grounded → BraveDDG**
66
+ Default cascade: **Gemini Grounded → TavilyBrave**
67
67
 
68
68
  Gemini Grounded Search gives you Google-quality results through the Gemini API. Free tier: 5,000 grounded queries/month.
69
69
 
70
70
  | Engine | Free Tier | Key Required | Default |
71
71
  |--------|-----------|-------------|---------|
72
72
  | **Gemini Grounded** | 5,000/month | `GEMINI_API_KEY` | ✅ Primary |
73
- | Brave | 2,000/month | `BRAVE_API_KEY` | ✅ Fallback |
74
- | DuckDuckGo | Unlimited | None | ✅ Last resort |
73
+ | Tavily | 1,000/month | `TAVILY_API_KEY` | ✅ 1st fallback |
74
+ | Brave | 2,000/month | `BRAVE_API_KEY` | ✅ 2nd fallback |
75
+ | DuckDuckGo | Unlimited | None | Available |
75
76
  | Bing | Unlimited | None | Available |
76
77
  | Serper | 2,500 trial | `SERPER_API_KEY` | Available |
77
78
  | Google CSE | 100/day | `GOOGLE_CSE_KEY` | Available |
@@ -123,9 +124,9 @@ const accounts = await web.auth.getStatus()
123
124
 
124
125
  Cookie refresh cron fires `cookie_expiring` and `cookie_expired` events before accounts go stale.
125
126
 
126
- ## Act — 24 Platform Adapters
127
+ ## Act — 19 Platform Adapters
127
128
 
128
- Post to 24+ platforms with one API:
129
+ Post to 19 platforms with one API:
129
130
 
130
131
  ```js
131
132
  await web.act('github', 'create-issue', { repo: 'user/repo', title: 'Bug report', body: '...' })
@@ -134,7 +135,7 @@ await web.act('devto', 'post', { title: '...', body: '...', tags: ['ai'] })
134
135
  await web.act('huggingface', 'create-repo', { name: 'my-model', type: 'model' })
135
136
  ```
136
137
 
137
- **Live tested:** GitHub ✅, Reddit ✅, Dev.to ✅, HuggingFace ✅, X (reads) ✅
138
+ **Live tested:** GitHub ✅, Reddit ✅, Dev.to ✅, HuggingFace ✅, X (reads) ✅, Hashnode ✅, Discord ✅, Product Hunt
138
139
 
139
140
  | Platform | Auth Method | Actions |
140
141
  |----------|-------------|---------|
@@ -153,7 +154,10 @@ await web.act('huggingface', 'create-repo', { name: 'my-model', type: 'model' })
153
154
  | Quora | Browser automation | answer |
154
155
  | HuggingFace | Hub API | repo, model card, upload |
155
156
  | BetaList | REST API | submit |
156
- | **14 Directories** | Generic adapter | submit |
157
+ | AlternativeTo | Cookie session | submit, claim |
158
+ | DevHunt | Supabase auth | submit, upvote |
159
+ | SaaSHub | Generic adapter | submit |
160
+ | **Generic Directory** | Configurable | submit |
157
161
 
158
162
  Built-in rate limiting, content dedup (MD5, 24h window), and dead letter queue for retries.
159
163
 
@@ -216,7 +220,7 @@ npx spectrawl install-stealth # download Camoufox browser
216
220
  ```json
217
221
  {
218
222
  "search": {
219
- "cascade": ["gemini-grounded", "brave", "ddg"],
223
+ "cascade": ["gemini-grounded", "tavily", "brave"],
220
224
  "scrapeTop": 5
221
225
  },
222
226
  "cache": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "spectrawl",
3
- "version": "0.3.18",
4
- "description": "The unified web layer for AI agents. Search (6 engines), stealth browse (Camoufox + Playwright), auth (cookies, multi-account), act (24 adapters, 30+ platforms), proxy rotation. Self-hosted, free.",
3
+ "version": "0.3.22",
4
+ "description": "The unified web layer for AI agents. Search (8 engines), stealth browse, auth, act on 24 platforms. Self-hosted.",
5
5
  "main": "src/index.js",
6
6
  "types": "index.d.ts",
7
7
  "bin": {
package/src/server.js CHANGED
@@ -61,6 +61,65 @@ const server = http.createServer(async (req, res) => {
61
61
  return json(res, result)
62
62
  }
63
63
 
64
+ // Threads OAuth callback
65
+ if (req.method === 'GET' && path === '/auth/callback/threads') {
66
+ const code = url.searchParams.get('code')
67
+ const errParam = url.searchParams.get('error')
68
+ if (errParam) {
69
+ res.writeHead(200, { 'Content-Type': 'text/html' })
70
+ return res.end(`<h2>❌ Auth error: ${errParam}</h2>`)
71
+ }
72
+ if (!code) {
73
+ res.writeHead(400, { 'Content-Type': 'text/html' })
74
+ return res.end('<h2>❌ No code received</h2>')
75
+ }
76
+ try {
77
+ // Exchange code for token
78
+ const fetch = require('node:https')
79
+ const params = new URLSearchParams({
80
+ client_id: '1574846783732558',
81
+ client_secret: 'f8589ca3523b0ea5bab3fac2c2ae4c15',
82
+ code,
83
+ grant_type: 'authorization_code',
84
+ redirect_uri: 'https://gateway.xanos.org/auth/callback/threads'
85
+ })
86
+ const tokenRes = await new Promise((resolve, reject) => {
87
+ const postData = params.toString()
88
+ const options = {
89
+ hostname: 'graph.threads.net',
90
+ path: '/oauth/access_token',
91
+ method: 'POST',
92
+ headers: {
93
+ 'Content-Type': 'application/x-www-form-urlencoded',
94
+ 'Content-Length': Buffer.byteLength(postData)
95
+ }
96
+ }
97
+ const req2 = fetch.request(options, (r) => {
98
+ let data = ''
99
+ r.on('data', chunk => data += chunk)
100
+ r.on('end', () => resolve(JSON.parse(data)))
101
+ })
102
+ req2.on('error', reject)
103
+ req2.write(postData)
104
+ req2.end()
105
+ })
106
+ // Save to credentials
107
+ const fs = require('fs')
108
+ const credsPath = '/root/.openclaw/workspace-dijiclaw/.openclaw/credentials/threads-api.json'
109
+ const creds = JSON.parse(fs.readFileSync(credsPath, 'utf8'))
110
+ creds.user_token = tokenRes.access_token
111
+ creds.user_id = tokenRes.user_id
112
+ creds.token_type = tokenRes.token_type
113
+ creds.note = 'User token saved via OAuth callback'
114
+ fs.writeFileSync(credsPath, JSON.stringify(creds, null, 2))
115
+ res.writeHead(200, { 'Content-Type': 'text/html' })
116
+ return res.end('<h2>✅ Threads connected! You can close this tab.</h2>')
117
+ } catch (e) {
118
+ res.writeHead(500, { 'Content-Type': 'text/html' })
119
+ return res.end(`<h2>❌ Token exchange failed: ${e.message}</h2>`)
120
+ }
121
+ }
122
+
64
123
  return error(res, 404, 'Not found')
65
124
  } catch (err) {
66
125
  console.error('Server error:', err)