@seanyao/roll 0.5.0 → 2.602.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/CHANGELOG.md +717 -0
- package/LICENSE +21 -0
- package/README.md +65 -165
- package/bin/dream-test-quality-scan +110 -0
- package/bin/roll +14897 -815
- package/conventions/config.yaml +17 -1
- package/conventions/global/AGENTS.md +146 -100
- package/conventions/global/CLAUDE.md +1 -21
- package/conventions/global/GEMINI.md +8 -22
- package/conventions/global/project_rules.md +9 -0
- package/conventions/templates/backend-service/AGENTS.md +30 -81
- package/conventions/templates/backend-service/GEMINI.md +3 -3
- package/conventions/templates/backend-service/project_rules.md +16 -0
- package/conventions/templates/cli/AGENTS.md +31 -58
- package/conventions/templates/cli/CLAUDE.md +3 -5
- package/conventions/templates/cli/GEMINI.md +3 -3
- package/conventions/templates/cli/project_rules.md +16 -0
- package/conventions/templates/frontend-only/AGENTS.md +29 -64
- package/conventions/templates/frontend-only/GEMINI.md +3 -3
- package/conventions/templates/frontend-only/project_rules.md +14 -0
- package/conventions/templates/fullstack/AGENTS.md +31 -79
- package/conventions/templates/fullstack/CLAUDE.md +1 -1
- package/conventions/templates/fullstack/GEMINI.md +3 -3
- package/conventions/templates/fullstack/project_rules.md +15 -0
- package/lib/README.md +42 -0
- package/lib/__pycache__/github_sync.cpython-314.pyc +0 -0
- package/lib/__pycache__/loop-fmt.cpython-314.pyc +0 -0
- package/lib/__pycache__/loop_result_eval.cpython-314.pyc +0 -0
- package/lib/__pycache__/loop_unstick.cpython-314.pyc +0 -0
- package/lib/__pycache__/model_prices.cpython-314.pyc +0 -0
- package/lib/__pycache__/prices_fetcher.cpython-314.pyc +0 -0
- package/lib/__pycache__/roll-home.cpython-314.pyc +0 -0
- package/lib/__pycache__/roll-loop-status.cpython-314.pyc +0 -0
- package/lib/__pycache__/roll_git.cpython-314.pyc +0 -0
- package/lib/__pycache__/roll_render.cpython-314.pyc +0 -0
- package/lib/__pycache__/slides-render.cpython-314.pyc +0 -0
- package/lib/agent_usage/README.md +49 -0
- package/lib/agent_usage/__init__.py +108 -0
- package/lib/agent_usage/__pycache__/__init__.cpython-314.pyc +0 -0
- package/lib/agent_usage/__pycache__/gemini.cpython-314.pyc +0 -0
- package/lib/agent_usage/__pycache__/kimi.cpython-314.pyc +0 -0
- package/lib/agent_usage/__pycache__/openai.cpython-314.pyc +0 -0
- package/lib/agent_usage/__pycache__/pi.cpython-314.pyc +0 -0
- package/lib/agent_usage/__pycache__/pi_emit.cpython-314.pyc +0 -0
- package/lib/agent_usage/__pycache__/qwen.cpython-314.pyc +0 -0
- package/lib/agent_usage/gemini.py +127 -0
- package/lib/agent_usage/kimi.py +278 -0
- package/lib/agent_usage/kimi_emit.py +123 -0
- package/lib/agent_usage/openai.py +126 -0
- package/lib/agent_usage/pi.py +200 -0
- package/lib/agent_usage/pi_emit.py +135 -0
- package/lib/agent_usage/qwen.py +128 -0
- package/lib/backfill-pi-usage.py +243 -0
- package/lib/changelog_audit.py +155 -0
- package/lib/changelog_generate.py +263 -0
- package/lib/context_feed_budget.sh +194 -0
- package/lib/github_sync.py +876 -0
- package/lib/i18n/README.md +54 -0
- package/lib/i18n/agent.sh +75 -0
- package/lib/i18n/alert.sh +20 -0
- package/lib/i18n/backlog.sh +96 -0
- package/lib/i18n/brief.sh +5 -0
- package/lib/i18n/changelog.sh +5 -0
- package/lib/i18n/ci.sh +15 -0
- package/lib/i18n/debug.sh +0 -0
- package/lib/i18n/doctor.sh +44 -0
- package/lib/i18n/dream.sh +0 -0
- package/lib/i18n/init.sh +91 -0
- package/lib/i18n/lang.sh +10 -0
- package/lib/i18n/loop.sh +140 -0
- package/lib/i18n/migrate.sh +74 -0
- package/lib/i18n/offboard.sh +31 -0
- package/lib/i18n/onboard.sh +0 -0
- package/lib/i18n/peer.sh +41 -0
- package/lib/i18n/peer_help.sh +25 -0
- package/lib/i18n/peer_reset.sh +7 -0
- package/lib/i18n/peer_status.sh +5 -0
- package/lib/i18n/prices.sh +3 -0
- package/lib/i18n/prices_refresh.sh +17 -0
- package/lib/i18n/prices_show.sh +7 -0
- package/lib/i18n/propose.sh +0 -0
- package/lib/i18n/release.sh +0 -0
- package/lib/i18n/research.sh +0 -0
- package/lib/i18n/review_pr.sh +0 -0
- package/lib/i18n/sentinel.sh +0 -0
- package/lib/i18n/setup.sh +3 -0
- package/lib/i18n/shared.sh +157 -0
- package/lib/i18n/skills/roll-brief.sh +47 -0
- package/lib/i18n/skills/roll-build.sh +97 -0
- package/lib/i18n/skills/roll-design.sh +18 -0
- package/lib/i18n/skills/roll-fix.sh +53 -0
- package/lib/i18n/skills/roll-loop.sh +28 -0
- package/lib/i18n/skills/roll-onboard.sh +33 -0
- package/lib/i18n/skills_catalog.sh +30 -0
- package/lib/i18n/slides.sh +3 -0
- package/lib/i18n/slides_build.sh +38 -0
- package/lib/i18n/slides_delete.sh +19 -0
- package/lib/i18n/slides_list.sh +14 -0
- package/lib/i18n/slides_logs.sh +12 -0
- package/lib/i18n/slides_new.sh +15 -0
- package/lib/i18n/slides_preview.sh +14 -0
- package/lib/i18n/slides_templates.sh +7 -0
- package/lib/i18n/status.sh +21 -0
- package/lib/i18n/update.sh +24 -0
- package/lib/i18n.sh +211 -0
- package/lib/loop-exit-summary.py +393 -0
- package/lib/loop-fmt.py +589 -0
- package/lib/loop_pick_agent.py +316 -0
- package/lib/loop_result_eval.py +469 -0
- package/lib/loop_unstick.py +180 -0
- package/lib/model_prices.py +186 -0
- package/lib/prices/README.md +35 -0
- package/lib/prices/snapshot-2026-05-22.json +22 -0
- package/lib/prices/snapshot-2026-05-23-deepseek.json +15 -0
- package/lib/prices/snapshot-2026-05-23-kimi.json +14 -0
- package/lib/prices_fetcher.py +285 -0
- package/lib/roll-backlog.py +225 -0
- package/lib/roll-brief.py +286 -0
- package/lib/roll-help.py +158 -0
- package/lib/roll-home.py +556 -0
- package/lib/roll-init.py +156 -0
- package/lib/roll-loop-status.py +1683 -0
- package/lib/roll-loop-story.py +191 -0
- package/lib/roll-onboard-render.py +378 -0
- package/lib/roll-peer.py +252 -0
- package/lib/roll-plan-validate.py +386 -0
- package/lib/roll-setup.py +102 -0
- package/lib/roll-status.py +367 -0
- package/lib/roll_git.py +41 -0
- package/lib/roll_render.py +414 -0
- package/lib/slides/components/README.md +123 -0
- package/lib/slides/components/cards-2.html +9 -0
- package/lib/slides/components/cards-3.html +9 -0
- package/lib/slides/components/cards-4.html +9 -0
- package/lib/slides/components/compare.html +22 -0
- package/lib/slides/components/highlight.html +9 -0
- package/lib/slides/components/pipeline.html +12 -0
- package/lib/slides/components/plain.html +7 -0
- package/lib/slides/components/quote.html +4 -0
- package/lib/slides/components/timeline.html +9 -0
- package/lib/slides/templates/introduction-v3.html +571 -0
- package/lib/slides/templates/pitch.html +0 -0
- package/lib/slides-render.py +778 -0
- package/lib/slides-validate.py +357 -0
- package/lib/test_quality_gate.py +143 -0
- package/package.json +8 -7
- package/skills/roll-.changelog/SKILL.md +406 -33
- package/skills/roll-.clarify/SKILL.md +5 -2
- package/skills/roll-.dream/SKILL.md +374 -0
- package/skills/roll-.echo/SKILL.md +5 -2
- package/skills/roll-.qa/SKILL.md +57 -3
- package/skills/roll-.review/SKILL.md +42 -3
- package/skills/roll-brief/SKILL.md +209 -0
- package/skills/roll-build/SKILL.md +308 -63
- package/skills/roll-debug/SKILL.md +341 -162
- package/skills/roll-debug/injectable-bb.js +263 -0
- package/skills/roll-deck/SKILL.md +296 -0
- package/skills/roll-design/ENGINEERING_CHECKLIST.md +1 -1
- package/skills/roll-design/SKILL.md +727 -94
- package/skills/roll-doc/SKILL.md +595 -0
- package/skills/roll-doctor/SKILL.md +192 -0
- package/skills/roll-fix/SKILL.md +149 -32
- package/skills/{roll-jot → roll-idea}/SKILL.md +18 -10
- package/skills/roll-loop/SKILL.md +578 -0
- package/skills/roll-notes/SKILL.md +103 -0
- package/skills/roll-onboard/SKILL.md +234 -0
- package/skills/roll-peer/SKILL.md +336 -0
- package/skills/roll-propose/SKILL.md +157 -0
- package/skills/roll-review-pr/SKILL.md +58 -0
- package/skills/roll-sentinel/SKILL.md +11 -2
- package/skills/roll-spar/SKILL.md +8 -6
- package/template/.github/workflows/ci.yml +5 -2
- package/template/AGENTS.md +20 -74
- package/skills/roll-research/SKILL.md +0 -307
- package/skills/roll-research/references/schema.json +0 -162
- package/skills/roll-research/scripts/md_to_pdf.py +0 -289
- package/tools/roll-fetch/SKILL.md +0 -182
- package/tools/roll-fetch/package.json +0 -15
- package/tools/roll-fetch/smart-web-fetch.js +0 -558
- package/tools/roll-probe/SKILL.md +0 -84
- /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(/</g, '<')
|
|
313
|
-
.replace(/>/g, '>')
|
|
314
|
-
.replace(/&/g, '&')
|
|
315
|
-
.replace(/"/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
|