@seanyao/roll 0.5.0 → 2.602.2

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 (181) hide show
  1. package/CHANGELOG.md +736 -0
  2. package/LICENSE +21 -0
  3. package/README.md +65 -165
  4. package/bin/dream-test-quality-scan +110 -0
  5. package/bin/roll +15030 -814
  6. package/conventions/config.yaml +17 -1
  7. package/conventions/global/AGENTS.md +146 -100
  8. package/conventions/global/CLAUDE.md +1 -21
  9. package/conventions/global/GEMINI.md +8 -22
  10. package/conventions/global/project_rules.md +9 -0
  11. package/conventions/templates/backend-service/AGENTS.md +30 -81
  12. package/conventions/templates/backend-service/GEMINI.md +3 -3
  13. package/conventions/templates/backend-service/project_rules.md +16 -0
  14. package/conventions/templates/cli/AGENTS.md +31 -58
  15. package/conventions/templates/cli/CLAUDE.md +3 -5
  16. package/conventions/templates/cli/GEMINI.md +3 -3
  17. package/conventions/templates/cli/project_rules.md +16 -0
  18. package/conventions/templates/frontend-only/AGENTS.md +29 -64
  19. package/conventions/templates/frontend-only/GEMINI.md +3 -3
  20. package/conventions/templates/frontend-only/project_rules.md +14 -0
  21. package/conventions/templates/fullstack/AGENTS.md +31 -79
  22. package/conventions/templates/fullstack/CLAUDE.md +1 -1
  23. package/conventions/templates/fullstack/GEMINI.md +3 -3
  24. package/conventions/templates/fullstack/project_rules.md +15 -0
  25. package/lib/README.md +42 -0
  26. package/lib/__pycache__/github_sync.cpython-314.pyc +0 -0
  27. package/lib/__pycache__/loop-fmt.cpython-314.pyc +0 -0
  28. package/lib/__pycache__/loop_result_eval.cpython-314.pyc +0 -0
  29. package/lib/__pycache__/loop_unstick.cpython-314.pyc +0 -0
  30. package/lib/__pycache__/model_prices.cpython-314.pyc +0 -0
  31. package/lib/__pycache__/prices_fetcher.cpython-314.pyc +0 -0
  32. package/lib/__pycache__/roll-home.cpython-314.pyc +0 -0
  33. package/lib/__pycache__/roll-loop-status.cpython-314.pyc +0 -0
  34. package/lib/__pycache__/roll_git.cpython-314.pyc +0 -0
  35. package/lib/__pycache__/roll_render.cpython-314.pyc +0 -0
  36. package/lib/__pycache__/slides-render.cpython-314.pyc +0 -0
  37. package/lib/agent_usage/README.md +49 -0
  38. package/lib/agent_usage/__init__.py +108 -0
  39. package/lib/agent_usage/__pycache__/__init__.cpython-314.pyc +0 -0
  40. package/lib/agent_usage/__pycache__/gemini.cpython-314.pyc +0 -0
  41. package/lib/agent_usage/__pycache__/kimi.cpython-314.pyc +0 -0
  42. package/lib/agent_usage/__pycache__/openai.cpython-314.pyc +0 -0
  43. package/lib/agent_usage/__pycache__/pi.cpython-314.pyc +0 -0
  44. package/lib/agent_usage/__pycache__/pi_emit.cpython-314.pyc +0 -0
  45. package/lib/agent_usage/__pycache__/qwen.cpython-314.pyc +0 -0
  46. package/lib/agent_usage/gemini.py +127 -0
  47. package/lib/agent_usage/kimi.py +278 -0
  48. package/lib/agent_usage/kimi_emit.py +123 -0
  49. package/lib/agent_usage/openai.py +126 -0
  50. package/lib/agent_usage/pi.py +200 -0
  51. package/lib/agent_usage/pi_emit.py +135 -0
  52. package/lib/agent_usage/qwen.py +128 -0
  53. package/lib/backfill-pi-usage.py +243 -0
  54. package/lib/changelog_audit.py +155 -0
  55. package/lib/changelog_generate.py +263 -0
  56. package/lib/context_feed_budget.sh +194 -0
  57. package/lib/github_sync.py +876 -0
  58. package/lib/i18n/README.md +54 -0
  59. package/lib/i18n/agent.sh +75 -0
  60. package/lib/i18n/alert.sh +20 -0
  61. package/lib/i18n/backlog.sh +96 -0
  62. package/lib/i18n/brief.sh +5 -0
  63. package/lib/i18n/changelog.sh +5 -0
  64. package/lib/i18n/ci.sh +15 -0
  65. package/lib/i18n/debug.sh +0 -0
  66. package/lib/i18n/doctor.sh +44 -0
  67. package/lib/i18n/dream.sh +0 -0
  68. package/lib/i18n/init.sh +91 -0
  69. package/lib/i18n/lang.sh +10 -0
  70. package/lib/i18n/loop.sh +140 -0
  71. package/lib/i18n/migrate.sh +74 -0
  72. package/lib/i18n/offboard.sh +31 -0
  73. package/lib/i18n/onboard.sh +0 -0
  74. package/lib/i18n/peer.sh +41 -0
  75. package/lib/i18n/peer_help.sh +25 -0
  76. package/lib/i18n/peer_reset.sh +7 -0
  77. package/lib/i18n/peer_status.sh +5 -0
  78. package/lib/i18n/prices.sh +3 -0
  79. package/lib/i18n/prices_refresh.sh +17 -0
  80. package/lib/i18n/prices_show.sh +7 -0
  81. package/lib/i18n/propose.sh +0 -0
  82. package/lib/i18n/release.sh +0 -0
  83. package/lib/i18n/research.sh +0 -0
  84. package/lib/i18n/review_pr.sh +0 -0
  85. package/lib/i18n/sentinel.sh +0 -0
  86. package/lib/i18n/setup.sh +3 -0
  87. package/lib/i18n/shared.sh +157 -0
  88. package/lib/i18n/skills/roll-brief.sh +47 -0
  89. package/lib/i18n/skills/roll-build.sh +97 -0
  90. package/lib/i18n/skills/roll-design.sh +18 -0
  91. package/lib/i18n/skills/roll-fix.sh +53 -0
  92. package/lib/i18n/skills/roll-loop.sh +28 -0
  93. package/lib/i18n/skills/roll-onboard.sh +33 -0
  94. package/lib/i18n/skills_catalog.sh +30 -0
  95. package/lib/i18n/slides.sh +3 -0
  96. package/lib/i18n/slides_build.sh +38 -0
  97. package/lib/i18n/slides_delete.sh +19 -0
  98. package/lib/i18n/slides_list.sh +14 -0
  99. package/lib/i18n/slides_logs.sh +12 -0
  100. package/lib/i18n/slides_new.sh +15 -0
  101. package/lib/i18n/slides_preview.sh +14 -0
  102. package/lib/i18n/slides_templates.sh +7 -0
  103. package/lib/i18n/status.sh +21 -0
  104. package/lib/i18n/update.sh +24 -0
  105. package/lib/i18n.sh +211 -0
  106. package/lib/loop-exit-summary.py +393 -0
  107. package/lib/loop-fmt.py +589 -0
  108. package/lib/loop_pick_agent.py +316 -0
  109. package/lib/loop_result_eval.py +469 -0
  110. package/lib/loop_unstick.py +180 -0
  111. package/lib/model_prices.py +194 -0
  112. package/lib/prices/README.md +35 -0
  113. package/lib/prices/snapshot-2026-05-22.json +22 -0
  114. package/lib/prices/snapshot-2026-05-23-deepseek.json +15 -0
  115. package/lib/prices/snapshot-2026-05-23-kimi.json +15 -0
  116. package/lib/prices_fetcher.py +285 -0
  117. package/lib/roll-backlog.py +225 -0
  118. package/lib/roll-brief.py +286 -0
  119. package/lib/roll-help.py +158 -0
  120. package/lib/roll-home.py +556 -0
  121. package/lib/roll-init.py +156 -0
  122. package/lib/roll-loop-status.py +1683 -0
  123. package/lib/roll-loop-story.py +191 -0
  124. package/lib/roll-onboard-render.py +378 -0
  125. package/lib/roll-peer.py +252 -0
  126. package/lib/roll-plan-validate.py +386 -0
  127. package/lib/roll-setup.py +102 -0
  128. package/lib/roll-status.py +367 -0
  129. package/lib/roll_git.py +41 -0
  130. package/lib/roll_render.py +414 -0
  131. package/lib/slides/components/README.md +123 -0
  132. package/lib/slides/components/cards-2.html +9 -0
  133. package/lib/slides/components/cards-3.html +9 -0
  134. package/lib/slides/components/cards-4.html +9 -0
  135. package/lib/slides/components/compare.html +22 -0
  136. package/lib/slides/components/highlight.html +9 -0
  137. package/lib/slides/components/pipeline.html +12 -0
  138. package/lib/slides/components/plain.html +7 -0
  139. package/lib/slides/components/quote.html +4 -0
  140. package/lib/slides/components/timeline.html +9 -0
  141. package/lib/slides/templates/introduction-v3.html +571 -0
  142. package/lib/slides/templates/pitch.html +0 -0
  143. package/lib/slides-render.py +778 -0
  144. package/lib/slides-validate.py +357 -0
  145. package/lib/test_quality_gate.py +143 -0
  146. package/package.json +8 -7
  147. package/skills/roll-.changelog/SKILL.md +406 -33
  148. package/skills/roll-.clarify/SKILL.md +5 -2
  149. package/skills/roll-.dream/SKILL.md +374 -0
  150. package/skills/roll-.echo/SKILL.md +5 -2
  151. package/skills/roll-.qa/SKILL.md +57 -3
  152. package/skills/roll-.review/SKILL.md +42 -3
  153. package/skills/roll-brief/SKILL.md +209 -0
  154. package/skills/roll-build/SKILL.md +308 -63
  155. package/skills/roll-debug/SKILL.md +341 -162
  156. package/skills/roll-debug/injectable-bb.js +263 -0
  157. package/skills/roll-deck/SKILL.md +296 -0
  158. package/skills/roll-design/ENGINEERING_CHECKLIST.md +1 -1
  159. package/skills/roll-design/SKILL.md +733 -94
  160. package/skills/roll-doc/SKILL.md +595 -0
  161. package/skills/roll-doctor/SKILL.md +192 -0
  162. package/skills/roll-fix/SKILL.md +149 -32
  163. package/skills/{roll-jot → roll-idea}/SKILL.md +18 -10
  164. package/skills/roll-loop/SKILL.md +579 -0
  165. package/skills/roll-notes/SKILL.md +103 -0
  166. package/skills/roll-onboard/SKILL.md +234 -0
  167. package/skills/roll-peer/SKILL.md +336 -0
  168. package/skills/roll-propose/SKILL.md +157 -0
  169. package/skills/roll-review-pr/SKILL.md +58 -0
  170. package/skills/roll-sentinel/SKILL.md +11 -2
  171. package/skills/roll-spar/SKILL.md +8 -6
  172. package/template/.github/workflows/ci.yml +5 -2
  173. package/template/AGENTS.md +20 -74
  174. package/skills/roll-research/SKILL.md +0 -307
  175. package/skills/roll-research/references/schema.json +0 -162
  176. package/skills/roll-research/scripts/md_to_pdf.py +0 -289
  177. package/tools/roll-fetch/SKILL.md +0 -182
  178. package/tools/roll-fetch/package.json +0 -15
  179. package/tools/roll-fetch/smart-web-fetch.js +0 -558
  180. package/tools/roll-probe/SKILL.md +0 -84
  181. /package/template/{BACKLOG.md → .roll/backlog.md} +0 -0
@@ -1,558 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Smart Web Fetch Skill - Simplified 3-Layer Strategy
4
- * 三层策略: Tavily → LLM Native → Browser
5
- * 移除 mcporter, 直接 HTTP 调用, Key 从环境变量获取
6
- */
7
-
8
- const { execSync } = require('child_process');
9
- const https = require('https');
10
-
11
- // Configuration
12
- const TAVILY_TIMEOUT = 30000;
13
- const BROWSER_TIMEOUT = 90000;
14
- const MIN_CONTENT_LENGTH = 200;
15
- const MAX_RETRIES = 2;
16
-
17
- // Blocked content keywords
18
- const BLOCKED_KEYWORDS = [
19
- // Chinese
20
- '验证', 'captcha', '请登录', '环境异常', '登录后', '需要验证',
21
- '请完成验证', '安全检查', '访问受限', 'blocked', 'access denied',
22
- '拖动滑块', '完成拼图', '点击验证', '继续访问', '登录查看',
23
- '验证后即可', '异常访问', '安全验证', '人机验证',
24
- // English
25
- 'verify', 'verification', 'complete the verification', 'captcha required',
26
- 'please log in', 'sign in to', 'access denied', 'blocked',
27
- 'security check', 'human verification', 'prove you\'re human'
28
- ];
29
-
30
- /**
31
- * Check if content is blocked or low quality
32
- */
33
- function isBlockedOrLowQuality(content, source = 'unknown') {
34
- if (!content || content.length < MIN_CONTENT_LENGTH) {
35
- return { blocked: true, reason: 'Content too short or empty', severity: 'high' };
36
- }
37
-
38
- const lowerContent = content.toLowerCase();
39
- const foundKeywords = [];
40
-
41
- for (const keyword of BLOCKED_KEYWORDS) {
42
- if (lowerContent.includes(keyword.toLowerCase())) {
43
- foundKeywords.push(keyword);
44
- }
45
- }
46
-
47
- if (foundKeywords.length > 0) {
48
- const isLikelyBlocked = foundKeywords.some(k =>
49
- ['验证', 'captcha', '环境异常', '请登录', '拖动滑块'].includes(k)
50
- );
51
- return {
52
- blocked: isLikelyBlocked,
53
- reason: `Detected keywords: ${foundKeywords.slice(0, 3).join(', ')}`,
54
- severity: isLikelyBlocked ? 'high' : 'medium'
55
- };
56
- }
57
-
58
- return { blocked: false };
59
- }
60
-
61
- /**
62
- * Calculate content quality score
63
- */
64
- function calculateQualityScore(content) {
65
- if (!content) return 0;
66
-
67
- let score = 0;
68
- const length = content.length;
69
-
70
- // Length score (0-30)
71
- score += Math.min(Math.log10(length) * 10, 30);
72
-
73
- // Content density (0-25)
74
- const wordCount = content.split(/\s+/).length;
75
- const avgWordLength = content.length / wordCount;
76
- if (avgWordLength > 3 && avgWordLength < 15) score += 15;
77
- if (content.match(/[。\.]/g)?.length > 5) score += 10;
78
-
79
- // Structure indicators (0-25)
80
- if (content.includes('#') || content.includes('##')) score += 8;
81
- if (content.includes('###')) score += 5;
82
- if (content.includes('- ') || content.includes('* ')) score += 6;
83
- if (content.includes('```')) score += 6;
84
-
85
- // Rich content indicators (0-20)
86
- if (content.match(/\[.*?\]\(.*?\)/)) score += 5;
87
- if (content.match(/\!\[.*?\]\(.*?\)/)) score += 5;
88
- if (content.match(/\*\*.*?\*\*/)) score += 5;
89
- if (content.match(/`.*?`/)) score += 5;
90
-
91
- return Math.min(score / 100, 1.0);
92
- }
93
-
94
- /**
95
- * Level 1: Tavily API (HTTP direct call)
96
- */
97
- function tryTavily(url, retries = 0) {
98
- console.error(`[SmartFetch] Level 1: Trying Tavily for: ${url}`);
99
-
100
- const apiKey = process.env.TAVILY_API_KEY;
101
- if (!apiKey) {
102
- return {
103
- success: false,
104
- tool: 'tavily',
105
- error: 'TAVILY_API_KEY not set in environment',
106
- needs_fallback: true
107
- };
108
- }
109
-
110
- return new Promise((resolve) => {
111
- const postData = JSON.stringify({
112
- urls: [url],
113
- api_key: apiKey,
114
- extract_depth: 'advanced',
115
- include_images: false
116
- });
117
-
118
- const options = {
119
- hostname: 'api.tavily.com',
120
- path: '/extract',
121
- method: 'POST',
122
- headers: {
123
- 'Content-Type': 'application/json',
124
- 'Content-Length': Buffer.byteLength(postData)
125
- },
126
- timeout: TAVILY_TIMEOUT
127
- };
128
-
129
- const req = https.request(options, (res) => {
130
- let data = '';
131
-
132
- res.on('data', chunk => data += chunk);
133
-
134
- res.on('end', () => {
135
- try {
136
- const response = JSON.parse(data);
137
-
138
- if (response.results && response.results[0]) {
139
- const result = response.results[0];
140
- const content = result.raw_content || result.content || '';
141
-
142
- if (content.length > MIN_CONTENT_LENGTH) {
143
- resolve({
144
- success: true,
145
- tool: 'tavily',
146
- content: content,
147
- title: result.title || '',
148
- url: result.url || url
149
- });
150
- } else {
151
- resolve({
152
- success: false,
153
- tool: 'tavily',
154
- error: 'Content too short',
155
- needs_fallback: true
156
- });
157
- }
158
- } else if (response.error) {
159
- resolve({
160
- success: false,
161
- tool: 'tavily',
162
- error: response.error,
163
- needs_fallback: true
164
- });
165
- } else {
166
- resolve({
167
- success: false,
168
- tool: 'tavily',
169
- error: 'No results',
170
- needs_fallback: true
171
- });
172
- }
173
- } catch (e) {
174
- resolve({
175
- success: false,
176
- tool: 'tavily',
177
- error: `Parse error: ${e.message}`,
178
- needs_fallback: true
179
- });
180
- }
181
- });
182
- });
183
-
184
- req.on('error', (err) => {
185
- if (retries < MAX_RETRIES) {
186
- console.error(`[SmartFetch] Tavily error, retrying... (${retries + 1}/${MAX_RETRIES})`);
187
- resolve(tryTavily(url, retries + 1));
188
- } else {
189
- resolve({
190
- success: false,
191
- tool: 'tavily',
192
- error: err.message,
193
- needs_fallback: true
194
- });
195
- }
196
- });
197
-
198
- req.on('timeout', () => {
199
- req.destroy();
200
- if (retries < MAX_RETRIES) {
201
- console.error(`[SmartFetch] Tavily timeout, retrying... (${retries + 1}/${MAX_RETRIES})`);
202
- resolve(tryTavily(url, retries + 1));
203
- } else {
204
- resolve({
205
- success: false,
206
- tool: 'tavily',
207
- error: 'Timeout',
208
- needs_fallback: true
209
- });
210
- }
211
- });
212
-
213
- req.write(postData);
214
- req.end();
215
- });
216
- }
217
-
218
- /**
219
- * Level 2: LLM Native Fetch (return instruction for caller)
220
- */
221
- function tryLLMNative(url) {
222
- console.error(`[SmartFetch] Level 2: LLM Native Fetch for: ${url}`);
223
-
224
- return {
225
- success: false,
226
- tool: 'llm_native',
227
- error: 'LLM Native fetch requires caller to use FetchURL tool',
228
- instruction: `Use FetchURL tool to fetch "${url}" and return the content`,
229
- needs_fallback: true,
230
- native_fetch: true,
231
- url: url
232
- };
233
- }
234
-
235
- /**
236
- * Check if browser-use is installed locally
237
- */
238
- function isBrowserUseInstalled() {
239
- try {
240
- execSync('/opt/homebrew/bin/python3.11 -c "import browser_use"', {
241
- encoding: 'utf-8',
242
- timeout: 5000,
243
- stdio: 'pipe'
244
- });
245
- return true;
246
- } catch (e) {
247
- return false;
248
- }
249
- }
250
-
251
- /**
252
- * Level 3: Browser Automation (Local first, then Cloud)
253
- */
254
- async function tryBrowser(url) {
255
- console.error(`[SmartFetch] Level 3: Trying Browser automation for: ${url}`);
256
-
257
- // Try local browser-use first
258
- if (isBrowserUseInstalled()) {
259
- console.error('[SmartFetch] Using local browser-use...');
260
-
261
- try {
262
- const result = execSync(
263
- `/opt/homebrew/bin/python3.11 -c "
264
- import asyncio
265
- import sys
266
- from browser_use import Browser, BrowserConfig
267
-
268
- async def fetch():
269
- browser = Browser(config=BrowserConfig(headless=True))
270
- await browser.start()
271
- try:
272
- page = await browser.get_current_page()
273
- await page.goto('${url}', wait_until='networkidle')
274
- content = await page.content()
275
- title = await page.title()
276
- print(f'TITLE:{title}')
277
- print('---CONTENT---')
278
- print(content)
279
- finally:
280
- await browser.stop()
281
-
282
- asyncio.run(fetch())
283
- "`,
284
- {
285
- encoding: 'utf-8',
286
- timeout: BROWSER_TIMEOUT,
287
- stdio: ['pipe', 'pipe', 'pipe']
288
- }
289
- );
290
-
291
- // Parse output
292
- const lines = result.split('\n');
293
- let title = '';
294
- let content = result;
295
-
296
- for (const line of lines) {
297
- if (line.startsWith('TITLE:')) {
298
- title = line.substring(6);
299
- } else if (line === '---CONTENT---') {
300
- const idx = lines.indexOf(line);
301
- content = lines.slice(idx + 1).join('\n');
302
- break;
303
- }
304
- }
305
-
306
- // Convert HTML to text (simple)
307
- const textContent = content
308
- .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
309
- .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
310
- .replace(/<[^>]+>/g, ' ')
311
- .replace(/\s+/g, ' ')
312
- .replace(/&lt;/g, '<')
313
- .replace(/&gt;/g, '>')
314
- .replace(/&amp;/g, '&')
315
- .replace(/&quot;/g, '"')
316
- .trim();
317
-
318
- if (textContent.length > MIN_CONTENT_LENGTH) {
319
- return {
320
- success: true,
321
- tool: 'browser_local',
322
- content: textContent,
323
- title: title,
324
- url: url
325
- };
326
- }
327
-
328
- return {
329
- success: false,
330
- tool: 'browser_local',
331
- error: 'Content too short'
332
- };
333
-
334
- } catch (error) {
335
- console.error(`[SmartFetch] Local browser failed: ${error.message.split('\n')[0]}`);
336
- // Fall through to cloud
337
- }
338
- } else {
339
- console.error('[SmartFetch] Local browser-use not installed, trying cloud...');
340
- }
341
-
342
- // Try Cloud browser-use (if API key available)
343
- const cloudApiKey = process.env.BROWSER_USE_API_KEY;
344
- if (!cloudApiKey) {
345
- return {
346
- success: false,
347
- tool: 'browser',
348
- error: 'Browser automation failed. Local browser-use not installed, and BROWSER_USE_API_KEY not set for cloud.'
349
- };
350
- }
351
-
352
- // Cloud browser-use would be implemented here
353
- // For now, return error with setup instructions
354
- return {
355
- success: false,
356
- tool: 'browser_cloud',
357
- error: 'Cloud browser-use not yet implemented. Please install local browser-use: pip install browser-use && playwright install chromium'
358
- };
359
- }
360
-
361
- /**
362
- * Smart fetch with 3-layer fallback
363
- * Strategy: Tavily → LLM Native → Browser
364
- */
365
- async function smartFetch(url, options = {}) {
366
- const method = options.method || 'auto';
367
- const skipQualityCheck = options.skipQualityCheck || false;
368
-
369
- console.error(`[SmartFetch] Starting fetch for: ${url} (method: ${method})`);
370
-
371
- // Explicit method selection
372
- if (method !== 'auto') {
373
- switch (method) {
374
- case 'tavily': return await tryTavily(url);
375
- case 'native': return tryLLMNative(url);
376
- case 'browser': return await tryBrowser(url);
377
- default: return { success: false, error: 'Unknown method' };
378
- }
379
- }
380
-
381
- // Auto mode: 3-layer cascade
382
-
383
- // Level 1: Tavily
384
- const tavilyResult = await tryTavily(url);
385
-
386
- if (tavilyResult.success) {
387
- const quality = isBlockedOrLowQuality(tavilyResult.content, 'tavily');
388
-
389
- if (skipQualityCheck || !quality.blocked) {
390
- const score = calculateQualityScore(tavilyResult.content);
391
- console.error(`[SmartFetch] ✓ Tavily succeeded (quality: ${score.toFixed(2)})`);
392
-
393
- return {
394
- ...tavilyResult,
395
- fallback_used: false,
396
- quality_score: score,
397
- quality_check: 'passed'
398
- };
399
- }
400
-
401
- console.error(`[SmartFetch] Tavily content blocked: ${quality.reason}`);
402
- } else {
403
- console.error(`[SmartFetch] Tavily failed: ${tavilyResult.error}`);
404
-
405
- // If Tavily key not set, skip to next level
406
- if (!tavilyResult.needs_fallback) {
407
- return tavilyResult;
408
- }
409
- }
410
-
411
- // Level 2: LLM Native Fetch
412
- console.error('[SmartFetch] Falling back to LLM Native...');
413
- const nativeResult = tryLLMNative(url);
414
-
415
- // Return instruction for caller to handle
416
- return {
417
- ...nativeResult,
418
- fallback_used: true,
419
- fallback_chain: ['tavily']
420
- };
421
-
422
- // Note: Browser (Level 3) is called by the agent if native fetch fails
423
- }
424
-
425
- /**
426
- * Smart search with Tavily (HTTP direct)
427
- */
428
- function smartSearch(query, maxResults = 5) {
429
- console.error(`[SmartFetch] Searching: ${query}`);
430
-
431
- const apiKey = process.env.TAVILY_API_KEY;
432
- if (!apiKey) {
433
- return {
434
- success: false,
435
- error: 'TAVILY_API_KEY not set in environment'
436
- };
437
- }
438
-
439
- return new Promise((resolve) => {
440
- const postData = JSON.stringify({
441
- query: query,
442
- api_key: apiKey,
443
- max_results: maxResults,
444
- search_depth: 'advanced',
445
- include_answer: true
446
- });
447
-
448
- const options = {
449
- hostname: 'api.tavily.com',
450
- path: '/search',
451
- method: 'POST',
452
- headers: {
453
- 'Content-Type': 'application/json',
454
- 'Content-Length': Buffer.byteLength(postData)
455
- },
456
- timeout: TAVILY_TIMEOUT
457
- };
458
-
459
- const req = https.request(options, (res) => {
460
- let data = '';
461
- res.on('data', chunk => data += chunk);
462
- res.on('end', () => {
463
- try {
464
- const response = JSON.parse(data);
465
- resolve({
466
- success: true,
467
- query: query,
468
- results: response.results || [],
469
- answer: response.answer || ''
470
- });
471
- } catch (e) {
472
- resolve({ success: false, error: `Parse error: ${e.message}` });
473
- }
474
- });
475
- });
476
-
477
- req.on('error', (err) => {
478
- resolve({ success: false, error: err.message });
479
- });
480
-
481
- req.on('timeout', () => {
482
- req.destroy();
483
- resolve({ success: false, error: 'Timeout' });
484
- });
485
-
486
- req.write(postData);
487
- req.end();
488
- });
489
- }
490
-
491
- // CLI interface
492
- if (require.main === module) {
493
- const args = process.argv.slice(2);
494
- const command = args[0];
495
-
496
- if (command === 'fetch') {
497
- const url = args[1];
498
- const method = args[2] || 'auto';
499
-
500
- if (!url) {
501
- console.log(JSON.stringify({ error: 'URL required' }));
502
- process.exit(1);
503
- }
504
-
505
- smartFetch(url, { method }).then(result => {
506
- console.log(JSON.stringify(result, null, 2));
507
- }).catch(err => {
508
- console.log(JSON.stringify({ error: err.message }));
509
- process.exit(1);
510
- });
511
- }
512
-
513
- else if (command === 'search') {
514
- const query = args[1];
515
- const maxResults = parseInt(args[2]) || 5;
516
-
517
- if (!query) {
518
- console.log(JSON.stringify({ error: 'Query required' }));
519
- process.exit(1);
520
- }
521
-
522
- smartSearch(query, maxResults).then(result => {
523
- console.log(JSON.stringify(result, null, 2));
524
- });
525
- }
526
-
527
- else {
528
- console.log(`
529
- Smart Web Fetch Skill - 3-Layer Strategy
530
-
531
- Usage:
532
- smart-web-fetch fetch <url> [method]
533
- smart-web-fetch search <query> [max_results]
534
-
535
- Methods: auto (default), tavily, native, browser
536
-
537
- 3-Layer Strategy:
538
- 1. Tavily - AI extraction, best quality (needs TAVILY_API_KEY)
539
- 2. LLM Native - Use FetchURL tool (for agents with native capability)
540
- 3. Browser - Local browser-use (fallback for stubborn pages)
541
-
542
- Environment Variables:
543
- TAVILY_API_KEY - Required for Tavily API
544
- BROWSER_USE_API_KEY - Optional for cloud browser (local preferred)
545
-
546
- Examples:
547
- smart-web-fetch fetch https://example.com
548
- smart-web-fetch fetch https://example.com tavily
549
- smart-web-fetch search "OpenAI GPT-5" 10
550
-
551
- Install local browser:
552
- pip install browser-use
553
- playwright install chromium
554
- `);
555
- }
556
- }
557
-
558
- module.exports = { smartFetch, smartSearch, tryTavily, tryLLMNative, tryBrowser };
@@ -1,84 +0,0 @@
1
- ---
2
- hidden: true
3
- name: roll-probe
4
- description: Node discovery and health check for Roll environment. Find machines on LAN by name, check node health, verify OpenClaw Gateway status. Use when user asks to find a machine, check node status, diagnose gateway issues, or verify node connectivity.
5
- ---
6
-
7
- # Roll Probe
8
-
9
- **Node discovery and health check tool** - for node management and status diagnosis in Roll environments.
10
-
11
- ## Capabilities
12
-
13
- 1. **Node Discovery** - Discover machines on LAN
14
- - Discover SSH services via Bonjour/mDNS
15
- - Resolve `.local` hostnames
16
- - Supports aliases: orin, seanclaw, apeclaw
17
-
18
- 2. **Node Health Check** - Check node health
19
- - OpenClaw Gateway process status
20
- - Port listening check
21
- - Health endpoint verification
22
- - Log viewing
23
-
24
- ## Usage
25
-
26
- ```bash
27
- # Discover machines
28
- $roll-checker find <machine-name>
29
-
30
- # Check node health
31
- $roll-checker health <hostname>
32
-
33
- # Full diagnosis
34
- $roll-checker diagnose <machine-name>
35
- ```
36
-
37
- ## Node Discovery
38
-
39
- Uses Bonjour `_ssh._tcp` service discovery:
40
-
41
- ```bash
42
- # Browse all SSH services
43
- dns-sd -B _ssh._tcp local
44
-
45
- # Resolve a specific service
46
- dns-sd -L "Sean's Claw Machine" _ssh._tcp local
47
- dns-sd -G v4v6 Seans-Claw-Machine.local
48
- ```
49
-
50
- **Known aliases**:
51
- - `orin` / `nv-orin` → nv-orin.local
52
- - `seanclaw` → Seans-Claw-Machine.local
53
- - `apeclaw` → Ape's Claw Machine
54
-
55
- ## Health Check
56
-
57
- Check procedure for Orin/OpenClaw hosts:
58
-
59
- ```bash
60
- # 1. Identity verification
61
- ssh -o BatchMode=yes -o ConnectTimeout=10 nvidia@nv-orin.local 'hostname && whoami'
62
-
63
- # 2. Process check
64
- ps -ef | grep -i "openclaw\|gateway" | grep -v grep
65
-
66
- # 3. Port check
67
- ss -ltnp | grep -E "18789|18791|18792"
68
-
69
- # 4. Health endpoint
70
- for p in 18789 18791 18792; do
71
- curl -fsS http://127.0.0.1:$p/health || true
72
- done
73
- ```
74
-
75
- ## Dynamic Host Resolution
76
-
77
- Priority order:
78
- 1. `nvidia@nv-orin.local` (Bonjour hostname)
79
- 2. Current `.local` hostname (via discovery)
80
- 3. Current IP (via discovery)
81
-
82
- ## References
83
-
84
- - `scripts/find_ssh_machine.py` - SSH machine discovery script
File without changes