@clawhub-lj-hao-082d451f90
Automatically analyze websites for performance metrics and audit issues using Lighthouse.
---
name: Chrome DevTools Auto Analyzer
slug: chrome-devtools-auto-analyzer
version: 1.1.1
homepage: https://github.com/user/chrome-devtools-auto-analyzer
description: Automatically analyze websites for performance metrics and audit issues using Lighthouse.
metadata: {"clawic":{"emoji":"🤖","requires":{"bins":["node","npm"],"npm":["lighthouse","chrome-launcher"]},"os":["linux","darwin","win32"]}}
---
## When to Use
User wants to automatically analyze a website URL for performance metrics, accessibility, SEO, or best practices without manual Chrome DevTools interaction.
## Core Rules
1. **Verify prerequisites** — Check Node.js (v18+) and packages installed: `node --version && npm list lighthouse chrome-launcher`
2. **Run Lighthouse audit** — Execute `node automation-script.js <URL> [--mobile] [--output=DIR]` to collect all metrics.
3. **Extract and present metrics** — Use tables for scores (0-100) and metrics with status indicators (⚪ Good, 🟡 Warning, 🔴 Critical).
4. **Identify critical issues** — Flag metrics outside thresholds: LCP>2.5s, CLS>0.1, INP>200ms, TBT>200ms, Performance<50.
5. **Provide actionable fixes** — For each issue, include specific code fixes from `audit-checklist.md` and `metrics-reference.md`.
6. **Save results** — Store JSON and HTML reports in `./results/`. Ask user before saving to memory for tracking.
7. **Handle errors gracefully** — If audit fails, check `troubleshooting.md` for common solutions (Chrome not found, ES module errors, memory issues).
## Quick Reference
| Topic | File |
|-------|------|
| Automation script | `automation-script.js` |
| Metrics & thresholds | `metrics-reference.md` |
| Fix checklists | `audit-checklist.md` |
| Setup guide | `setup.md` |
| Quick start | `quick-start.md` |
| Troubleshooting | `troubleshooting.md` |
| Memory tracking | `memory-template.md` |
## Data Storage
After analysis, offer to save: URL, timestamp, scores, critical issues, report path. Always ask before saving to memory.
## Security & Privacy
- Runs locally in headless Chrome — no external data transmission
- Warn before analyzing authenticated URLs
- JSON reports contain URL structure — handle sensitive paths carefully
## Common Commands
```bash
# Quick audit (desktop)
node automation-script.js https://example.com
# Mobile emulation
node automation-script.js https://example.com --mobile
# Custom output directory
node automation-script.js https://example.com --output=./reports
# Lighthouse CLI directly
lighthouse https://example.com --output=json --output-path=report.json
```
## Troubleshooting Quick Reference
| Error | Solution |
|-------|----------|
| "lighthouse is not a function" | Add `"type": "module"` to package.json |
| "Chrome not found" | Install Chrome or use `npm install puppeteer` |
| "JavaScript heap out of memory" | Run with `node --max-old-space-size=4096` |
| "Port already in use" | Specify different port in script config |
FILE:_meta.json
{
"ownerId": "user",
"slug": "chrome-devtools-auto-analyzer",
"version": "1.1.1",
"publishedAt": 1742428800000,
"readme": "SKILL.md",
"license": "MIT"
}
FILE:audit-checklist.md
# Audit Checklist
Complete checklists for Accessibility, Best Practices, and SEO audits with Lighthouse audit IDs and fix examples.
---
## Accessibility Audit Checklist
### Critical Issues (WCAG A/AA)
| Audit ID | Issue | WCAG Level | Fix Example |
|----------|-------|------------|-------------|
| `color-contrast` | Background/foreground contrast <4.5:1 | AA | `color: #333; background: #fff;` |
| `image-alt` | Images missing alt text | A | `<img src="logo.png" alt="Company Logo">` |
| `label` | Form inputs without labels | A | `<label for="email">Email</label><input id="email">` |
| `link-name` | Links without accessible names | A | `<a href="/home">Go to Homepage</a>` |
| `button-name` | Buttons without accessible names | A | `<button aria-label="Close modal">×</button>` |
| `html-has-lang` | HTML element missing lang attribute | A | `<html lang="en">` |
| `valid-lang` | Invalid lang attribute value | A | `<html lang="en-US">` |
| `heading-order` | Skipped heading levels | A | Use h1→h2→h3 sequentially |
| `document-title` | Document missing title | A | `<title>Page Title</title>` |
| `meta-refresh` | Auto-redirecting page | A | Remove `<meta http-equiv="refresh">` |
### Additional Accessibility Checks
| Audit ID | Issue | Priority |
|----------|-------|----------|
| `aria-allowed-attr` | Invalid ARIA attributes | High |
| `aria-hidden-body` | aria-hidden on body | High |
| `aria-required-attr` | Missing required ARIA attributes | High |
| `aria-roles` | Invalid ARIA role values | High |
| `aria-valid-attr` | Malformed ARIA attributes | High |
| `aria-valid-attr-value` | Invalid ARIA attribute values | High |
| `autocomplete-valid` | Invalid autocomplete values | Medium |
| `axis` | Invalid table axis attribute | Low |
| `blink` | Blinking content detected | Medium |
| `bypass` | No skip links for keyboard users | AA |
| `definition-list` | Definition list markup errors | Low |
| `dlitem` | Definition list item outside list | Low |
| `duplicate-id` | Duplicate ID attributes | Medium |
| `duplicate-id-active` | Duplicate IDs in interactive elements | High |
| `frame-title` | iframes missing title | A |
| `input-image-alt` | Input type="image" missing alt | A |
| `landmark-one-main` | Multiple or missing main landmarks | AA |
| `layout-table` | Layout tables not marked as such | Low |
| `list` | List markup errors | Low |
| `listitem` | List items outside lists | Low |
| `marquee` | Marquee content detected | Low |
| `object-alt` | Object elements missing alt | A |
| `region` | Content outside landmarks | AA |
| `scope-attr` | Invalid scope attributes | Low |
| `select-name` | Select elements without labels | A |
| `skip-link` | Skip links not functional | AA |
| `tabindex` | tabindex > 0 | AA |
| `td-headers-attr` | Table cell headers attribute errors | Low |
| `th-has-data-cells` | Data tables missing headers | A |
| `valid-lang` | Invalid lang values | A |
| `video-caption` | Videos missing captions | A |
### Accessibility Fix Examples
**1. Fix Color Contrast:**
```css
/* Before: Contrast ratio 3.2:1 */
.text { color: #999; background: #fff; }
/* After: Contrast ratio 7.5:1 */
.text { color: #333; background: #fff; }
```
**2. Add Image Alt Text:**
```html
<!-- Before -->
<img src="product.jpg">
<!-- After -->
<img src="product.jpg" alt="Blue wireless headphones on white background">
```
**3. Add Form Labels:**
```html
<!-- Before -->
<input type="email" placeholder="Enter email">
<!-- After -->
<label for="email">Email Address</label>
<input type="email" id="email" name="email" required>
```
**4. Fix Heading Order:**
```html
<!-- Before: Skips h2 -->
<h1>Main Title</h1>
<h3>Subsection</h3>
<!-- After: Sequential -->
<h1>Main Title</h1>
<h2>Section Title</h2>
<h3>Subsection</h3>
```
---
## Best Practices Audit Checklist
### Trust & Safety
| Audit ID | Issue | Severity | Fix |
|----------|-------|----------|-----|
| `is-on-https` | Page not using HTTPS | Critical | Enable HTTPS, redirect HTTP to HTTPS |
| `uses-http2` | Not using HTTP/2 | Medium | Enable HTTP/2 on server |
| `no-vulnerable-libraries` | Known vulnerable JavaScript libraries | High | Update libraries to latest versions |
| `csp-xss` | CSP not effective against XSS | Medium | Implement Content Security Policy |
### Modern Web Development
| Audit ID | Issue | Severity | Fix |
|----------|-------|----------|-----|
| `uses-passive-event-listeners` | Non-passive touch event listeners | Low | Add `{ passive: true }` to touch handlers |
| `no-document-write` | Uses document.write() | High | Replace with DOM manipulation methods |
| `deprecations` | Uses deprecated APIs | Medium | Update to modern APIs |
| `geolocation-on-start` | Requests geolocation on load | Medium | Request geolocation on user interaction |
| `notification-on-start` | Requests notifications on load | Medium | Request notifications on user interaction |
### User Experience
| Audit ID | Issue | Severity | Fix |
|----------|-------|----------|-----|
| `image-aspect-ratio` | Images with incorrect aspect ratio | Medium | Use correct width/height or aspect-ratio CSS |
| `image-size-responsive` | Images not sized for viewport | Medium | Use srcset for responsive images |
| `doctype` | Missing or invalid doctype | Medium | Add `<!DOCTYPE html>` |
| `charset` | Missing charset declaration | Medium | Add `<meta charset="utf-8">` |
| `js-libraries` | Outdated JavaScript libraries | Medium | Update to latest stable versions |
### Best Practices Fix Examples
**1. Add Passive Event Listeners:**
```javascript
// Before
element.addEventListener('touchstart', handler);
// After
element.addEventListener('touchstart', handler, { passive: true });
```
**2. Replace document.write():**
```javascript
// Before
document.write('<div>Content</div>');
// After
const div = document.createElement('div');
div.textContent = 'Content';
document.body.appendChild(div);
```
**3. Add CSP Header:**
```
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
```
**4. Update Vulnerable Libraries:**
```bash
# Check for vulnerabilities
npm audit
# Update vulnerable packages
npm update jquery
# or
npm install jquery@latest --save
```
---
## SEO Audit Checklist
### Content Best Practices
| Audit ID | Issue | Impact | Fix |
|----------|-------|--------|-----|
| `document-title` | Document missing title | High | Add descriptive `<title>` tag |
| `meta-description` | Document missing meta description | Medium | Add `<meta name="description">` |
| `http-status-code` | Page returns non-200 status | Critical | Fix server errors, redirects |
| `link-text` | Links without descriptive text | Medium | Use meaningful link text |
| `crawlable-anchors` | Links not crawlable | High | Use proper `<a href>` elements |
| `is-crawlable` | Page blocked from indexing | Critical | Remove noindex, allow in robots.txt |
| `robots-txt` | Invalid robots.txt | High | Fix robots.txt syntax |
| `image-alt` | Images missing alt text | Medium | Add descriptive alt attributes |
| `hreflang` | Missing hreflang for localized pages | Medium | Add hreflang tags for translations |
| `canonical` | Missing or invalid canonical URL | High | Add `<link rel="canonical">` |
### Mobile Friendly
| Audit ID | Issue | Impact | Fix |
|----------|-------|--------|-----|
| `viewport` | Missing viewport meta tag | Critical | Add `<meta name="viewport" content="width=device-width, initial-scale=1">` |
| `font-size` | Font sizes too small | Medium | Use minimum 16px for body text |
| `tap-targets` | Tap targets too small/close | Medium | Make buttons ≥48×48px with spacing |
| `content-width` | Content wider than screen | High | Use responsive design |
### Structured Data
| Audit ID | Issue | Impact | Fix |
|----------|-------|--------|-----|
| `structured-data` | Invalid structured data | Medium | Fix schema.org markup |
### SEO Fix Examples
**1. Add Meta Description:**
```html
<head>
<meta name="description" content="Learn how to optimize your website performance with our comprehensive guide. Step-by-step tutorials and best practices.">
</head>
```
**2. Add Canonical URL:**
```html
<head>
<link rel="canonical" href="https://example.com/preferred-url">
</head>
```
**3. Fix Link Text:**
```html
<!-- Before: Non-descriptive -->
<a href="/product/123">Click here</a>
<!-- After: Descriptive -->
<a href="/product/123">View Product Details</a>
```
**4. Add Hreflang Tags:**
```html
<head>
<link rel="alternate" hreflang="en" href="https://example.com/en/page">
<link rel="alternate" hreflang="es" href="https://example.com/es/page">
<link rel="alternate" hreflang="x-default" href="https://example.com/page">
</head>
```
**5. Add Structured Data (JSON-LD):**
```html
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Article Title",
"author": {
"@type": "Person",
"name": "Author Name"
},
"datePublished": "2026-03-19"
}
</script>
```
---
## Audit Extraction Script
Extract all audit issues from Lighthouse result:
```javascript
function extractAuditIssues(lhr, category) {
const audits = lhr.audits;
const categoryAudits = lhr.categories[category]?.auditRefs || [];
const issues = [];
for (const ref of categoryAudits) {
const audit = audits[ref.id];
if (audit && audit.score !== null && audit.score < 1) {
issues.push({
id: ref.id,
title: audit.title,
description: audit.description,
score: audit.score,
displayValue: audit.displayValue,
weight: ref.weight,
details: audit.details,
});
}
}
// Sort by weight (importance)
issues.sort((a, b) => b.weight - a.weight);
return issues;
}
// Usage
const accessibilityIssues = extractAuditIssues(lhr, 'accessibility');
const seoIssues = extractAuditIssues(lhr, 'seo');
const bestPracticesIssues = extractAuditIssues(lhr, 'best-practices');
```
---
## Priority Matrix
### Critical (Fix Immediately)
- HTTPS not enabled
- Page returns 4xx/5xx errors
- Page blocked from indexing
- Missing viewport meta tag
- Critical accessibility blockers
### High (Fix This Week)
- Poor Core Web Vitals scores
- Missing alt text on images
- Form inputs without labels
- Vulnerable JavaScript libraries
- Invalid robots.txt
### Medium (Fix This Month)
- Color contrast issues
- Missing meta descriptions
- Non-descriptive link text
- Heading order issues
- Missing canonical URLs
### Low (Fix When Possible)
- Minor markup issues
- Deprecated API usage
- Non-passive event listeners
- Missing structured data
FILE:automation-script.js
#!/usr/bin/env node
/**
* Chrome DevTools Auto Analyzer
* Automated website analysis using Lighthouse and Puppeteer
*
* Usage: node automation-script.js <URL> [options]
* Example: node automation-script.js https://example.com --mobile --output=results
*/
import lighthouse from 'lighthouse';
import * as chromeLauncher from 'chrome-launcher';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Configuration
const CONFIG = {
logLevel: 'info',
output: 'json',
onlyCategories: ['performance', 'accessibility', 'best-practices', 'seo'],
emulatedFormFactor: 'desktop', // 'mobile' or 'desktop'
numberOfRuns: 1,
};
/**
* Run Lighthouse audit and extract metrics
* @param {string} url - URL to analyze
* @param {object} options - Lighthouse options
* @returns {Promise<object>} - Extracted metrics and issues
*/
async function runLighthouseAudit(url, options = {}) {
const flags = {
logLevel: CONFIG.logLevel,
output: CONFIG.output,
onlyCategories: CONFIG.onlyCategories,
emulatedFormFactor: options.mobile ? 'mobile' : 'desktop',
...options,
};
console.log(`🚀 Starting Lighthouse audit for: url`);
console.log(`📱 Device: flags.emulatedFormFactor`);
// Launch Chrome
const chrome = await chromeLauncher.launch({
chromeFlags: [
'--headless',
'--no-sandbox',
'--disable-gpu',
'--disable-dev-shm-usage',
],
});
flags.port = chrome.port;
try {
// Run Lighthouse
const runnerResult = await lighthouse(url, flags);
if (!runnerResult || !runnerResult.lhr) {
throw new Error('Lighthouse audit failed to return results');
}
// Extract metrics
const results = extractMetrics(runnerResult.lhr);
console.log('✅ Audit complete!');
return results;
} finally {
await chrome.kill();
}
}
/**
* Extract performance metrics and audit issues from Lighthouse result
* @param {object} lhr - Lighthouse Result object
* @returns {object} - Structured results
*/
function extractMetrics(lhr) {
const audits = lhr.audits;
const categories = lhr.categories;
// Performance metrics
const performanceMetrics = {
FCP: getValue(audits['first-contentful-paint']),
LCP: getValue(audits['largest-contentful-paint']),
CLS: getValue(audits['cumulative-layout-shift']),
TBT: getValue(audits['total-blocking-time']),
SI: getValue(audits['speed-index']),
INP: getValue(audits['interaction-to-next-paint']),
TTI: getValue(audits['interactive']),
TTFB: getValue(audits['server-response-time']),
};
// Category scores
const scores = {
Performance: Math.round((categories.performance?.score || 0) * 100),
Accessibility: Math.round((categories.accessibility?.score || 0) * 100),
'Best Practices': Math.round((categories['best-practices']?.score || 0) * 100),
SEO: Math.round((categories.seo?.score || 0) * 100),
};
// Extract failed audits (issues)
const issues = {
performance: getFailedAudits(audits, [
'largest-contentful-paint',
'cumulative-layout-shift',
'total-blocking-time',
'speed-index',
'first-contentful-paint',
'interactive',
]),
accessibility: getFailedAudits(audits, [
'color-contrast',
'image-alt',
'label',
'link-name',
'heading-order',
'button-name',
'html-has-lang',
'valid-lang',
]),
bestPractices: getFailedAudits(audits, [
'is-on-https',
'uses-http2',
'uses-passive-event-listeners',
'no-document-write',
'geolocation-on-start',
'notification-on-start',
'deprecations',
'errors-in-console',
]),
seo: getFailedAudits(audits, [
'document-title',
'meta-description',
'http-status-code',
'link-text',
'crawlable-anchors',
'is-crawlable',
'robots-txt',
'image-alt',
'hreflang',
'canonical',
]),
};
return {
url: lhr.finalDisplayedUrl,
timestamp: lhr.fetchTime,
device: lhr.configSettings?.emulatedFormFactor || 'desktop',
scores,
performanceMetrics,
issues,
rawLhr: lhr,
};
}
/**
* Get numeric value from audit
* @param {object} audit - Lighthouse audit object
* @returns {number|string} - Numeric value or 'N/A'
*/
function getValue(audit) {
if (!audit || audit.score === null || audit.score === undefined) {
return 'N/A';
}
if (audit.numericValue !== undefined) {
// Convert milliseconds to seconds for display
if (audit.numericUnit === 'millisecond') {
return Math.round(audit.numericValue);
}
return Math.round(audit.numericValue * 100) / 100;
}
return audit.score;
}
/**
* Get failed audits for specific IDs
* @param {object} audits - All audits
* @param {string[]} auditIds - Audit IDs to check
* @returns {Array} - Failed audits with details
*/
function getFailedAudits(audits, auditIds) {
const failed = [];
for (const id of auditIds) {
const audit = audits[id];
if (audit && audit.score !== null && audit.score < 1) {
failed.push({
id,
title: audit.title || id,
description: audit.description || '',
score: audit.score,
numericValue: audit.numericValue,
displayValue: audit.displayValue,
details: audit.details,
});
}
}
return failed;
}
/**
* Get fix suggestions for common issues
* @param {string} auditId - Audit ID
* @param {number} value - Metric value
* @returns {string} - Fix suggestion
*/
function getFixSuggestion(auditId, value) {
const fixes = {
'cumulative-layout-shift': value > 0.25
? '🔴 CRITICAL: Add width/height to images, reserve space for ads/embeds, use CSS aspect-ratio'
: '🟡 Add explicit dimensions to dynamic content, preload critical fonts',
'largest-contentful-paint': value > 4000
? '🔴 CRITICAL: Optimize server response, preload LCP image, remove render-blocking resources'
: '🟡 Compress images, use CDN, reduce JavaScript execution',
'total-blocking-time': value > 600
? '🔴 CRITICAL: Break up long tasks, defer non-critical JavaScript, use web workers'
: '🟡 Code split, remove unused JavaScript, optimize event listeners',
'speed-index': value > 5800
? '🔴 CRITICAL: Minimize above-fold content, inline critical CSS, defer non-critical resources'
: '🟡 Optimize image delivery, enable text compression',
'first-contentful-paint': value > 3000
? '🔴 CRITICAL: Reduce server response time, eliminate render-blocking resources'
: '🟡 Preload critical assets, optimize CSS/JavaScript delivery',
'interactive': value > 7300
? '🔴 CRITICAL: Reduce JavaScript execution time, minimize main-thread work'
: '🟡 Defer offscreen images, reduce third-party scripts',
'color-contrast': '🔴 Increase contrast ratio to at least 4.5:1 for normal text',
'image-alt': '🔴 Add descriptive alt attributes to all images',
'label': '🔴 Associate labels with form inputs using for/id attributes',
'link-name': '🔴 Use descriptive link text that makes sense out of context',
'heading-order': '🔴 Fix heading hierarchy - use h1→h2→h3 sequentially',
'document-title': '🔴 Add a descriptive <title> element in the <head>',
'meta-description': '🔴 Add meta description (150-160 characters)',
'is-on-https': '🔴 Enable HTTPS and redirect all HTTP traffic',
'uses-http2': '🟡 Enable HTTP/2 on your server for better performance',
'no-document-write': '🔴 Replace document.write() with modern DOM manipulation',
};
return fixes[auditId] || '🔧 Review documentation for fix guidance';
}
/**
* Format results for console output
* @param {object} results - Analysis results
*/
function printResults(results) {
console.log('\n' + '='.repeat(70));
console.log(`📊 Analysis Results for: results.url`);
console.log(`🕐 Timestamp: results.timestamp`);
console.log(`📱 Device: results.device`);
console.log('='.repeat(70));
// Scores
console.log('\n📈 Category Scores:');
for (const [category, score] of Object.entries(results.scores)) {
const status = getScoreStatus(score);
console.log(` category: score/100 status`);
}
// Performance Metrics
console.log('\n⚡ Core Web Vitals & Performance Metrics:');
for (const [metric, value] of Object.entries(results.performanceMetrics)) {
if (value !== 'N/A') {
const unit = ['CLS'].includes(metric) ? '' : 'ms';
const status = getMetricStatus(metric, value);
const threshold = getMetricThreshold(metric);
console.log(` metric: valueunit status (target: threshold)`);
}
}
// Issues with fix suggestions
const allIssues = [
...results.issues.performance.map(i => ({ ...i, category: 'Performance' })),
...results.issues.accessibility.map(i => ({ ...i, category: 'Accessibility' })),
...results.issues.bestPractices.map(i => ({ ...i, category: 'Best Practices' })),
...results.issues.seo.map(i => ({ ...i, category: 'SEO' })),
];
if (allIssues.length > 0) {
console.log('\n❌ Critical Issues & Fixes:\n');
allIssues.forEach(issue => {
const fix = getFixSuggestion(issue.id, issue.numericValue);
console.log(` [issue.category] issue.title`);
if (issue.displayValue) console.log(` Value: issue.displayValue`);
console.log(` Fix: fix\n`);
});
} else {
console.log('\n✅ No critical issues found! Great job!\n');
}
// Summary
const perfScore = results.scores.Performance;
const accScore = results.scores.Accessibility;
const bpScore = results.scores['Best Practices'];
const seoScore = results.scores.SEO;
const overall = Math.round((perfScore + accScore + bpScore + seoScore) / 4);
console.log('='.repeat(70));
console.log(`📊 Overall Score: overall/100 getScoreStatus(overall)`);
console.log('='.repeat(70));
console.log('\n💡 Tip: Run with --mobile flag to test mobile performance\n');
}
/**
* Get score status emoji
* @param {number} score - Score 0-100
* @returns {string} - Emoji status
*/
function getScoreStatus(score) {
if (score >= 90) return '⚪ Good';
if (score >= 50) return '🟡 Needs Improvement';
return '🔴 Poor';
}
/**
* Get metric status emoji
* @param {string} metric - Metric name
* @param {number} value - Metric value
* @returns {string} - Emoji status
*/
function getMetricStatus(metric, value) {
const thresholds = {
FCP: { good: 1800, poor: 3000 },
LCP: { good: 2500, poor: 4000 },
CLS: { good: 0.1, poor: 0.25 },
TBT: { good: 200, poor: 600 },
SI: { good: 3400, poor: 5800 },
INP: { good: 200, poor: 500 },
};
const threshold = thresholds[metric];
if (!threshold) return '';
if (value <= threshold.good) return '⚪ Good';
if (value <= threshold.poor) return '🟡 Needs Improvement';
return '🔴 Poor';
}
/**
* Get metric target threshold string
* @param {string} metric - Metric name
* @returns {string} - Target threshold
*/
function getMetricThreshold(metric) {
const targets = {
FCP: '<1800ms',
LCP: '<2500ms',
CLS: '<0.1',
TBT: '<200ms',
SI: '<3400ms',
INP: '<200ms',
TTI: '<3800ms',
TTFB: '<800ms',
};
return targets[metric] || 'N/A';
}
/**
* Save results to JSON file
* @param {object} results - Analysis results
* @param {string} outputPath - Output directory
*/
function saveResults(results, outputPath = './results') {
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true });
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const urlSafe = results.url.replace(/[^a-zA-Z0-9]/g, '_');
const filename = `urlSafe_timestamp.json`;
const filepath = path.join(outputPath, filename);
// Save full results
fs.writeFileSync(filepath, JSON.stringify(results, null, 2));
console.log(`\n💾 Results saved to: filepath`);
// Save HTML report
const htmlReport = results.rawLhr.report;
if (htmlReport) {
const htmlPath = path.join(outputPath, `urlSafe_timestamp.html`);
fs.writeFileSync(htmlPath, htmlReport);
console.log(`📄 HTML report saved to: htmlPath`);
}
return filepath;
}
/**
* Main execution
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('Usage: node automation-script.js <URL> [options]');
console.log('Options:');
console.log(' --mobile Run with mobile emulation');
console.log(' --output=DIR Save results to directory');
console.log(' --help Show this help');
process.exit(1);
}
const url = args.find(arg => !arg.startsWith('--'));
const mobile = args.includes('--mobile');
const outputArg = args.find(arg => arg.startsWith('--output='));
const outputDir = outputArg ? outputArg.split('=')[1] : './results';
if (!url) {
console.error('❌ Error: URL is required');
process.exit(1);
}
try {
// Run audit
const results = await runLighthouseAudit(url, { mobile });
// Print results
printResults(results);
// Save results
saveResults(results, outputDir);
// Exit with appropriate code
const hasCriticalIssues =
results.scores.Performance < 50 ||
results.scores.Accessibility < 50 ||
results.scores['Best Practices'] < 50 ||
results.scores.SEo < 50;
process.exit(hasCriticalIssues ? 1 : 0);
} catch (error) {
console.error('❌ Error during analysis:', error.message);
process.exit(1);
}
}
// Run if executed directly
if (process.argv[1] && process.argv[1].includes('automation-script.js')) {
main();
}
export { runLighthouseAudit, extractMetrics, printResults, saveResults };
FILE:memory-template.md
# Memory Template — Chrome DevTools Auto Analyzer
Use this template to track automated website analysis results over time.
## Memory Structure
```markdown
## Website Analysis History
### [Website Name/Domain]
**URL:** `https://example.com`
**First Analyzed:** YYYY-MM-DD
**Last Analyzed:** YYYY-MM-DD
**Device:** Mobile/Desktop/Both
**Analysis Tool:** Lighthouse Auto Analyzer v1.0.0
#### Audit Scores History
| Date | Device | Performance | Accessibility | Best Practices | SEO | Notes |
|------|--------|-------------|---------------|----------------|-----|-------|
| YYYY-MM-DD | Desktop | 00 | 00 | 00 | 00 | Initial audit |
| YYYY-MM-DD | Mobile | 00 | 00 | 00 | 00 | After [fix] |
| YYYY-MM-DD | Desktop | 00 | 00 | 00 | 00 | After [fix] |
#### Core Web Vitals History
| Date | Device | LCP | CLS | INP | FCP | TBT | SI |
|------|--------|-----|-----|-----|-----|-----|-----|
| YYYY-MM-DD | Desktop | 0.0s | 0.00 | 0ms | 0.0s | 0ms | 0.0s |
| YYYY-MM-DD | Mobile | 0.0s | 0.00 | 0ms | 0.0s | 0ms | 0.0s |
#### Current Status (Last Audit)
**Overall Health:**
- Performance: ⚪ Good / 🟡 Needs Improvement / 🔴 Poor
- Accessibility: ⚪ Good / 🟡 Needs Improvement / 🔴 Poor
- Best Practices: ⚪ Good / 🟡 Needs Improvement / 🔴 Poor
- SEO: ⚪ Good / 🟡 Needs Improvement / 🔴 Poor
**Core Web Vitals:**
| Metric | Value | Target | Status |
|--------|-------|--------|--------|
| LCP | 0.0s | <2.5s | ⚪/🟡/🔴 |
| CLS | 0.00 | <0.1 | ⚪/🟡/🔴 |
| INP | 0ms | <200ms | ⚪/🟡/🔴 |
| FCP | 0.0s | <1.8s | ⚪/🟡/🔴 |
| TBT | 0ms | <200ms | ⚪/🟡/🔴 |
| SI | 0.0s | <3.4s | ⚪/🟡/🔴 |
#### Critical Issues (Unresolved)
**Performance:**
- [ ] Issue description (impact: high/medium/low)
- [ ] Issue description (impact: high/medium/low)
**Accessibility:**
- [ ] Issue description (WCAG level: A/AA/AAA)
- [ ] Issue description (WCAG level: A/AA/AAA)
**Best Practices:**
- [ ] Issue description (severity: critical/high/medium/low)
- [ ] Issue description (severity: critical/high/medium/low)
**SEO:**
- [ ] Issue description (impact: high/medium/low)
- [ ] Issue description (impact: high/medium/low)
#### Fix Progress
- [x] Completed: [Description] - Date fixed
- [ ] In Progress: [Description]
- [ ] Planned: [Description]
- [ ] Blocked: [Description]
#### Report Files
| Date | Type | Path |
|------|------|------|
| YYYY-MM-DD | JSON | `./reports/url_YYYY-MM-DD.json` |
| YYYY-MM-DD | HTML | `./reports/url_YYYY-MM-DD.html` |
#### Trends
**Improving:**
- Metric 1: +X points from baseline
- Metric 2: -X ms from baseline
**Regressing:**
- Metric 3: -X points from baseline
- Metric 4: +X ms from baseline
**Stable:**
- Metric 5: No significant change
```
---
## When to Update Memory
### Create New Entry When:
- First analysis of a new website URL
- User requests historical tracking
- Comparing multiple environments (staging vs production)
- Analyzing competitor websites
### Update Existing Entry When:
- Re-running audits on tracked URLs (add new row to history tables)
- User fixes issues and wants to track improvements
- Scores change by >5 points
- New critical issues discovered
- Fix progress changes (update checklist)
### Ask User Before:
- Creating memory entries (always confirm)
- Deleting old entries
- Marking issues as resolved
- Sharing data externally
---
## Example Entry
```markdown
### Example E-commerce Site
**URL:** `https://shop.example.com`
**First Analyzed:** 2026-03-19
**Last Analyzed:** 2026-03-25
**Device:** Both
**Analysis Tool:** Lighthouse Auto Analyzer v1.0.0
#### Audit Scores History
| Date | Device | Performance | Accessibility | Best Practices | SEO | Notes |
|------|--------|-------------|---------------|----------------|-----|-------|
| 2026-03-19 | Desktop | 72 | 85 | 92 | 90 | Initial audit |
| 2026-03-19 | Mobile | 65 | 85 | 92 | 90 | Mobile typically slower |
| 2026-03-22 | Desktop | 78 | 88 | 95 | 92 | After image optimization |
| 2026-03-25 | Desktop | 85 | 92 | 95 | 95 | After accessibility fixes |
#### Core Web Vitals History
| Date | Device | LCP | CLS | INP | FCP | TBT | SI |
|------|--------|-----|-----|-----|-----|-----|-----|
| 2026-03-19 | Desktop | 3200ms | 0.15 | 220ms | 1800ms | 450ms | 4100ms |
| 2026-03-19 | Mobile | 4500ms | 0.22 | 350ms | 2400ms | 680ms | 5800ms |
| 2026-03-22 | Desktop | 2400ms | 0.08 | 180ms | 1400ms | 280ms | 3200ms |
| 2026-03-25 | Desktop | 2100ms | 0.05 | 150ms | 1200ms | 180ms | 2800ms |
#### Current Status (Last Audit: 2026-03-25)
**Overall Health:**
- Performance: ⚪ Good (85/100)
- Accessibility: ⚪ Good (92/100)
- Best Practices: ⚪ Good (95/100)
- SEO: ⚪ Good (95/100)
**Core Web Vitals:**
| Metric | Value | Target | Status |
|--------|-------|--------|--------|
| LCP | 2100ms | <2.5s | ⚪ Good |
| CLS | 0.05 | <0.1 | ⚪ Good |
| INP | 150ms | <200ms | ⚪ Good |
| FCP | 1200ms | <1.8s | ⚪ Good |
| TBT | 180ms | <200ms | ⚪ Good |
| SI | 2800ms | <3.4s | ⚪ Good |
#### Critical Issues (Unresolved)
**Performance:**
- [ ] Third-party analytics script adds 80ms TBT (impact: low)
**Accessibility:**
- [ ] Skip link not visible on focus (WCAG AA) (impact: medium)
**Best Practices:**
- [x] Resolved: jQuery updated to 3.7.1
**SEO:**
- [x] Resolved: Meta description extended to 155 characters
#### Fix Progress
- [x] Completed: Added explicit width/height to product images - 2026-03-20
- [x] Completed: Implemented lazy loading for below-fold images - 2026-03-21
- [x] Completed: Updated jQuery to 3.7.1 - 2026-03-22
- [x] Completed: Fixed color contrast on CTA buttons - 2026-03-24
- [x] Completed: Added alt text to all product images - 2026-03-24
- [ ] In Progress: Implement skip link functionality
- [ ] Planned: Defer non-critical third-party scripts
- [ ] Planned: Implement service worker for caching
#### Report Files
| Date | Type | Path |
|------|------|------|
| 2026-03-19 | JSON | `./reports/shop-example-com_2026-03-19.json` |
| 2026-03-19 | HTML | `./reports/shop-example-com_2026-03-19.html` |
| 2026-03-22 | JSON | `./reports/shop-example-com_2026-03-22.json` |
| 2026-03-25 | JSON | `./reports/shop-example-com_2026-03-25.json` |
| 2026-03-25 | HTML | `./reports/shop-example-com_2026-03-25.html` |
#### Trends
**Improving:**
- Performance: +13 points from baseline (72 → 85)
- LCP: -1100ms from baseline (3200ms → 2100ms)
- CLS: -0.10 from baseline (0.15 → 0.05)
- Accessibility: +7 points from baseline (85 → 92)
**Regressing:**
- None
**Stable:**
- SEO: Consistent 90-95 range
- Best Practices: Consistent 92-95 range
```
---
## Comparison Template
When comparing multiple URLs or environments:
```markdown
### Comparison: Production vs Staging
| Metric | Production | Staging | Difference | Target |
|--------|------------|---------|------------|--------|
| Performance | 00 | 00 | +/-0 | >80 |
| Accessibility | 00 | 00 | +/-0 | >90 |
| Best Practices | 00 | 00 | +/-0 | >90 |
| SEO | 00 | 00 | +/-0 | >90 |
| LCP | 0.0s | 0.0s | +/-0.0s | <2.5s |
| CLS | 0.00 | 0.00 | +/-0.00 | <0.1 |
| INP | 0ms | 0ms | +/-0ms | <200ms |
**Analysis:**
- Staging performs [better/worse] than production
- Key differences: [Summary]
- Recommendation: [Action items]
```
---
## Competitor Analysis Template
```markdown
### Competitor Analysis - E-commerce Sites
| Site | Performance | Accessibility | Best Practices | SEO | LCP | CLS | Notes |
|------|-------------|---------------|----------------|-----|-----|-----|-------|
| shop.example.com | 85 | 92 | 95 | 95 | 2.1s | 0.05 | Our site |
| competitor-a.com | 78 | 88 | 90 | 92 | 2.8s | 0.12 | Larger images |
| competitor-b.com | 92 | 95 | 98 | 98 | 1.5s | 0.02 | Industry leader |
| competitor-c.com | 65 | 75 | 85 | 88 | 3.5s | 0.18 | Poor optimization |
**Industry Benchmarks:**
- Average Performance: 78
- Average LCP: 2.6s
- Average CLS: 0.10
**Our Position:**
- Performance: Above average (+7)
- LCP: Better than average (-0.5s)
- CLS: Better than average (-0.05)
```
---
## Status Indicators
Use consistently across all entries:
| Symbol | Meaning |
|--------|---------|
| ⚪ | Good (meets target) |
| 🟡 | Needs Improvement (close to target) |
| 🔴 | Poor (far from target) |
| ⬆️ | Improved from previous |
| ⬇️ | Regressed from previous |
| ➡️ | No significant change |
| ✅ | Issue resolved |
| [ ] | Issue pending |
| [~] | Issue in progress |
| [!] | Issue blocked |
---
## Automated Update Script
Use this script to append results to memory:
```javascript
function appendToMemory(url, results, memoryPath) {
const date = new Date().toISOString().split('T')[0];
const scores = results.scores;
const metrics = results.performanceMetrics;
const entry = `
| date | results.device | scores.Performance | scores.Accessibility | scores['Best Practices'] | scores.SEo | metrics.LCPms | metrics.CLS | metrics.INPms |
`;
// Append to markdown table in memory file
// Implementation depends on your memory system
}
```
FILE:metrics-reference.md
# Performance Metrics Reference
Core Web Vitals and performance metrics with thresholds, extraction paths, and optimization strategies.
## Core Web Vitals (Google's Official Metrics)
### LCP - Largest Contentful Paint
**What it measures:** Time to render the largest visible content element (image, video, text block).
| Rating | Threshold |
|--------|-----------|
| ⚪ Good | < 2.5s |
| 🟡 Needs Improvement | 2.5s - 4.0s |
| 🔴 Poor | > 4.0s |
**Lighthouse audit ID:** `largest-contentful-paint`
**JSON extraction path:**
```javascript
lhr.audits['largest-contentful-paint'].numericValue // milliseconds
lhr.audits['largest-contentful-paint'].displayValue // formatted string
lhr.audits['largest-contentful-paint'].score // 0-1
```
**Common causes of poor LCP:**
- Slow server response times
- Render-blocking JavaScript/CSS
- Large unoptimized images
- Client-side rendering
**Optimization strategies:**
1. Optimize server configuration (caching, CDN)
2. Preload critical resources: `<link rel="preload" as="image" href="hero.jpg">`
3. Compress and resize images (WebP/AVIF format)
4. Remove unused CSS/JavaScript
5. Use SSR or SSG instead of CSR
---
### CLS - Cumulative Layout Shift
**What it measures:** Sum of unexpected layout shifts during page lifecycle.
| Rating | Threshold |
|--------|-----------|
| ⚪ Good | < 0.1 |
| 🟡 Needs Improvement | 0.1 - 0.25 |
| 🔴 Poor | > 0.25 |
**Lighthouse audit ID:** `cumulative-layout-shift`
**JSON extraction path:**
```javascript
lhr.audits['cumulative-layout-shift'].numericValue
lhr.audits['cumulative-layout-shift'].score
```
**Common causes of poor CLS:**
- Images without explicit dimensions
- Ads/embeds without reserved space
- Dynamically injected content
- Web fonts causing FOIT/FOUT
- Late-loading CSS
**Optimization strategies:**
1. Always include `width` and `height` on images/video
2. Reserve space for ads and embeds
3. Use `aspect-ratio` CSS property
4. Preload critical web fonts
5. Avoid inserting content above existing content
---
### INP - Interaction to Next Paint
**What it measures:** Responsiveness to user interactions throughout page lifecycle (replaced FID in 2024).
| Rating | Threshold |
|--------|-----------|
| ⚪ Good | < 200ms |
| 🟡 Needs Improvement | 200ms - 500ms |
| 🔴 Poor | > 500ms |
**Lighthouse audit ID:** `interaction-to-next-paint`
**JSON extraction path:**
```javascript
lhr.audits['interaction-to-next-paint'].numericValue
lhr.audits['interaction-to-next-paint'].score
```
**Common causes of poor INP:**
- Long tasks blocking main thread
- Heavy JavaScript execution
- Synchronous event handlers
- Layout thrashing during interactions
**Optimization strategies:**
1. Break up long tasks (>50ms)
2. Use web workers for heavy computations
3. Debounce/throttle input handlers
4. Use `requestIdleCallback` for non-critical work
5. Optimize CSS selectors
---
## Additional Performance Metrics
### FCP - First Contentful Paint
**What it measures:** Time from navigation to first content render (text, images, SVG, non-white canvas).
| Rating | Threshold |
|--------|-----------|
| ⚪ Good | < 1.8s |
| 🟡 Needs Improvement | 1.8s - 3.0s |
| 🔴 Poor | > 3.0s |
**Lighthouse audit ID:** `first-contentful-paint`
**JSON extraction path:**
```javascript
lhr.audits['first-contentful-paint'].numericValue
lhr.audits['first-contentful-paint'].score
```
---
### TBT - Total Blocking Time
**What it measures:** Sum of time periods where main thread was blocked >50ms between FCP and Time to Interactive.
| Rating | Threshold |
|--------|-----------|
| ⚪ Good | < 200ms |
| 🟡 Needs Improvement | 200ms - 600ms |
| 🔴 Poor | > 600ms |
**Lighthouse audit ID:** `total-blocking-time`
**JSON extraction path:**
```javascript
lhr.audits['total-blocking-time'].numericValue
lhr.audits['total-blocking-time'].score
```
**Common causes of poor TBT:**
- Excessive JavaScript execution
- Large script bundles
- Inefficient event listeners
- Third-party scripts
**Optimization strategies:**
1. Code splitting and lazy loading
2. Remove unused JavaScript
3. Defer non-critical scripts
4. Use `async` for third-party scripts
5. Minimize main thread work
---
### SI - Speed Index
**What it measures:** How quickly content is visually populated during page load.
| Rating | Threshold |
|--------|-----------|
| ⚪ Good | < 3.4s |
| 🟡 Needs Improvement | 3.4s - 5.8s |
| 🔴 Poor | > 5.8s |
**Lighthouse audit ID:** `speed-index`
**JSON extraction path:**
```javascript
lhr.audits['speed-index'].numericValue
lhr.audits['speed-index'].score
```
---
### TTI - Time to Interactive
**What it measures:** Time until page is fully interactive (responds to input within 50ms).
| Rating | Threshold |
|--------|-----------|
| ⚪ Good | < 3.8s |
| 🟡 Needs Improvement | 3.8s - 7.3s |
| 🔴 Poor | > 7.3s |
**Lighthouse audit ID:** `interactive`
**JSON extraction path:**
```javascript
lhr.audits['interactive'].numericValue
lhr.audits['interactive'].score
```
---
### TTFB - Time to First Byte
**What it measures:** Time from navigation to first byte of response from server.
| Rating | Threshold |
|--------|-----------|
| ⚪ Good | < 800ms |
| 🟡 Needs Improvement | 800ms - 1800ms |
| 🔴 Poor | > 1800ms |
**Lighthouse audit ID:** `server-response-time`
**JSON extraction path:**
```javascript
lhr.audits['server-response-time'].numericValue
lhr.audits['server-response-time'].score
```
**Optimization strategies:**
1. Use CDN for static assets
2. Enable server-side caching
3. Optimize database queries
4. Use HTTP/2 or HTTP/3
5. Reduce server processing time
---
## Metrics Extraction Script
Complete extraction from Lighthouse JSON:
```javascript
function extractAllMetrics(lhr) {
const audits = lhr.audits;
return {
// Core Web Vitals
LCP: {
value: audits['largest-contentful-paint']?.numericValue,
score: audits['largest-contentful-paint']?.score,
displayValue: audits['largest-contentful-paint']?.displayValue,
status: getMetricStatus('LCP', audits['largest-contentful-paint']?.numericValue),
},
CLS: {
value: audits['cumulative-layout-shift']?.numericValue,
score: audits['cumulative-layout-shift']?.score,
displayValue: audits['cumulative-layout-shift']?.displayValue,
status: getMetricStatus('CLS', audits['cumulative-layout-shift']?.numericValue),
},
INP: {
value: audits['interaction-to-next-paint']?.numericValue,
score: audits['interaction-to-next-paint']?.score,
displayValue: audits['interaction-to-next-paint']?.displayValue,
status: getMetricStatus('INP', audits['interaction-to-next-paint']?.numericValue),
},
// Additional metrics
FCP: {
value: audits['first-contentful-paint']?.numericValue,
score: audits['first-contentful-paint']?.score,
displayValue: audits['first-contentful-paint']?.displayValue,
status: getMetricStatus('FCP', audits['first-contentful-paint']?.numericValue),
},
TBT: {
value: audits['total-blocking-time']?.numericValue,
score: audits['total-blocking-time']?.score,
displayValue: audits['total-blocking-time']?.displayValue,
status: getMetricStatus('TBT', audits['total-blocking-time']?.numericValue),
},
SI: {
value: audits['speed-index']?.numericValue,
score: audits['speed-index']?.score,
displayValue: audits['speed-index']?.displayValue,
status: getMetricStatus('SI', audits['speed-index']?.numericValue),
},
TTI: {
value: audits['interactive']?.numericValue,
score: audits['interactive']?.score,
displayValue: audits['interactive']?.displayValue,
status: getMetricStatus('TTI', audits['interactive']?.numericValue),
},
TTFB: {
value: audits['server-response-time']?.numericValue,
score: audits['server-response-time']?.score,
displayValue: audits['server-response-time']?.displayValue,
status: getMetricStatus('TTFB', audits['server-response-time']?.numericValue),
},
};
}
function getMetricStatus(metric, value) {
if (value === undefined || value === null) return 'N/A';
const thresholds = {
LCP: { good: 2500, poor: 4000 },
CLS: { good: 0.1, poor: 0.25 },
INP: { good: 200, poor: 500 },
FCP: { good: 1800, poor: 3000 },
TBT: { good: 200, poor: 600 },
SI: { good: 3400, poor: 5800 },
TTI: { good: 3800, poor: 7300 },
TTFB: { good: 800, poor: 1800 },
};
const t = thresholds[metric];
if (!t) return '';
if (value <= t.good) return '⚪ Good';
if (value <= t.poor) return '🟡 Needs Improvement';
return '🔴 Poor';
}
```
---
## Performance Score Calculation
Lighthouse Performance score is a weighted combination:
| Metric | Weight |
|--------|--------|
| LCP | 25% |
| FCP | 10% |
| TBT | 30% |
| CLS | 25% |
| SI | 10% |
**Note:** Weights may vary by Lighthouse version. Check `lhr.configSettings` for current weights.
---
## Mobile vs Desktop Thresholds
Mobile thresholds are more lenient due to hardware limitations:
| Metric | Desktop Good | Mobile Good |
|--------|--------------|-------------|
| LCP | < 2.5s | < 2.5s |
| FCP | < 1.5s | < 1.8s |
| TBT | < 150ms | < 200ms |
| SI | < 3.0s | < 3.4s |
---
## Quick Reference Table
| Metric | ID | Good | Poor | Unit |
|--------|-----|------|------|------|
| LCP | `largest-contentful-paint` | <2500 | >4000 | ms |
| CLS | `cumulative-layout-shift` | <0.1 | >0.25 | score |
| INP | `interaction-to-next-paint` | <200 | >500 | ms |
| FCP | `first-contentful-paint` | <1800 | >3000 | ms |
| TBT | `total-blocking-time` | <200 | >600 | ms |
| SI | `speed-index` | <3400 | >5800 | ms |
| TTI | `interactive` | <3800 | >7300 | ms |
| TTFB | `server-response-time` | <800 | >1800 | ms |
FILE:package.json
{
"type": "module",
"name": "chrome-devtools-auto-analyzer",
"version": "1.1.1",
"description": "Automated website analysis using Lighthouse",
"main": "automation-script.js",
"scripts": {
"analyze": "node automation-script.js",
"test": "node automation-script.js https://example.com"
},
"dependencies": {
"chrome-launcher": "^1.2.1",
"lighthouse": "^12.8.2"
}
}
FILE:quick-start.md
# Quick Start — Chrome DevTools Auto Analyzer
Get started in 5 minutes with automated website analysis.
---
## 1-Minute Setup
### Step 1: Install Dependencies
```bash
cd /path/to/chrome-devtools-auto-analyzer
npm install
```
### Step 2: Verify Installation
```bash
node --version # Should be v18+
npm list lighthouse chrome-launcher
```
### Step 3: Run Your First Audit
```bash
node automation-script.js https://example.com
```
**That's it!** You'll see results like:
```
======================================================================
📊 Analysis Results for: https://example.com
🕐 Timestamp: 2026-03-19T10:30:00.000Z
📱 Device: desktop
======================================================================
📈 Category Scores:
Performance: 85/100 ⚪ Good
Accessibility: 92/100 ⚪ Good
Best Practices: 100/100 ⚪ Good
SEO: 95/100 ⚪ Good
⚡ Core Web Vitals & Performance Metrics:
FCP: 1200ms ⚪ Good (target: <1800ms)
LCP: 2100ms ⚪ Good (target: <2500ms)
CLS: 0.05 ⚪ Good (target: <0.1)
TBT: 150ms ⚪ Good (target: <200ms)
SI: 2800ms ⚪ Good (target: <3400ms)
✅ No critical issues found! Great job!
======================================================================
📊 Overall Score: 93/100 ⚪ Good
======================================================================
💡 Tip: Run with --mobile flag to test mobile performance
💾 Results saved to: results/example_com_2026-03-19T10-30-00.json
```
---
## Common Commands
### Desktop Audit
```bash
node automation-script.js https://example.com
```
### Mobile Audit
```bash
node automation-script.js https://example.com --mobile
```
### Custom Output Directory
```bash
node automation-script.js https://example.com --output=./my-reports
```
### View HTML Report
```bash
# HTML reports are saved alongside JSON files
open results/example_com_*.html # macOS
xdg-open results/example_com_*.html # Linux
start results/example_com_*.html # Windows
```
---
## Understanding Results
### Score Ratings
| Score | Rating | Emoji |
|-------|--------|-------|
| 90-100 | Good | ⚪ |
| 50-89 | Needs Improvement | 🟡 |
| 0-49 | Poor | 🔴 |
### Core Web Vitals Targets
| Metric | Good | Needs Improvement | Poor |
|--------|------|-------------------|------|
| LCP | <2.5s | 2.5-4.0s | >4.0s |
| CLS | <0.1 | 0.1-0.25 | >0.25 |
| INP | <200ms | 200-500ms | >500ms |
| FCP | <1.8s | 1.8-3.0s | >3.0s |
| TBT | <200ms | 200-600ms | >600ms |
---
## Fixing Issues
When issues are found, the script provides fix suggestions:
```
❌ Critical Issues & Fixes:
[Performance] Cumulative Layout Shift
Value: 0.83
Fix: 🔴 CRITICAL: Add width/height to images, reserve space for ads/embeds, use CSS aspect-ratio
[Accessibility] Image elements have [alt] attributes
Fix: 🔴 Add descriptive alt attributes to all images
```
### Common Fixes
**High CLS (>0.1):**
```html
<!-- Add width and height to images -->
<img src="hero.jpg" width="1200" height="630" alt="Hero">
```
**Missing Alt Text:**
```html
<!-- Add descriptive alt text -->
<img src="product.jpg" alt="Blue wireless headphones on white background">
```
**Poor LCP (>2.5s):**
```html
<!-- Preload critical images -->
<link rel="preload" as="image" href="hero.jpg">
```
---
## Troubleshooting
### "lighthouse is not a function"
```bash
# Fix: Add to package.json
echo '{"type": "module"}' > package.json
```
### "Chrome not found"
```bash
# Install Chrome
# Ubuntu/Debian:
wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome-stable_current_amd64.deb
# Or use Puppeteer's bundled Chromium:
npm install puppeteer
```
### "JavaScript heap out of memory"
```bash
node --max-old-space-size=4096 automation-script.js https://example.com
```
**See [`troubleshooting.md`](troubleshooting.md) for more solutions.**
---
## Next Steps
1. **Analyze multiple pages:**
```bash
for url in https://example.com https://example.com/about; do
node automation-script.js $url
done
```
2. **Track progress over time:**
- Save results to memory using the skill's memory template
- Compare scores before/after optimizations
3. **Integrate with CI/CD:**
- Add to GitHub Actions (see [`setup.md`](setup.md))
- Set up Lighthouse CI for automated checks
4. **Learn more:**
- [`metrics-reference.md`](metrics-reference.md) - Detailed metric explanations
- [`audit-checklist.md`](audit-checklist.md) - Complete audit checklists
- [`troubleshooting.md`](troubleshooting.md) - Common errors and solutions
---
## Need Help?
- **Quick fixes:** See [`troubleshooting.md`](troubleshooting.md)
- **Detailed setup:** See [`setup.md`](setup.md)
- **Metric details:** See [`metrics-reference.md`](metrics-reference.md)
FILE:setup.md
# Setup — Chrome DevTools Auto Analyzer
Installation and setup instructions for automated website analysis.
> **⚡ Quick Start:** For fastest setup, see [`quick-start.md`](quick-start.md) for the 5-minute guide.
---
## Prerequisites
### Required Software
| Software | Minimum Version | Purpose |
|----------|-----------------|---------|
| Node.js | 18.x | JavaScript runtime |
| npm | 9.x | Package manager |
| Chrome/Chromium | 115+ | Browser for audits |
### Verify Installation
```bash
# Check Node.js version
node --version
# Expected: v18.x.x or higher
# Check npm version
npm --version
# Expected: 9.x.x or higher
# Check if Chrome is available
google-chrome --version # Linux
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version # macOS
```
---
## Installation Steps
### Step 1: Create Project Directory
```bash
mkdir website-analyzer
cd website-analyzer
npm init -y
```
### Step 2: Install Dependencies
```bash
# Install Lighthouse and Chrome launcher
npm install lighthouse chrome-launcher
# Optional: Install Puppeteer for custom metrics
npm install puppeteer
```
### Step 3: Configure ES Modules (Required)
Lighthouse v10+ requires ES modules. Add to `package.json`:
```bash
# Method 1: Using echo
echo '{
"type": "module",
"dependencies": {
"lighthouse": "^12.x",
"chrome-launcher": "^1.x"
}
}' > package.json
# Method 2: Edit manually
# Add "type": "module" to package.json
```
**⚠️ Important:** Without this step, you'll get "lighthouse is not a function" error.
### Step 4: Copy Automation Script
Copy `automation-script.js` from this skill to your project directory:
```bash
cp /path/to/skill/automation-script.js ./analyze.js
```
### Step 5: Make Script Executable (Optional)
```bash
chmod +x analyze.js
```
---
## Quick Start
### Run Your First Audit
```bash
# Basic audit (desktop)
node analyze.js https://example.com
# Mobile emulation
node analyze.js https://example.com --mobile
# Save to custom output directory
node analyze.js https://example.com --output=./my-results
```
### Expected Output
```
🚀 Starting Lighthouse audit for: https://example.com
📱 Device: desktop
✅ Audit complete!
============================================================
📊 Analysis Results for: https://example.com
🕐 Timestamp: 2026-03-19T10:30:00.000Z
📱 Device: desktop
============================================================
📈 Category Scores:
Performance: 85/100 ⚪ Good
Accessibility: 92/100 ⚪ Good
Best Practices: 100/100 ⚪ Good
SEO: 95/100 ⚪ Good
⚡ Performance Metrics:
FCP: 1200ms ⚪ Good
LCP: 2100ms ⚪ Good
CLS: 0.05 ⚪ Good
TBT: 150ms ⚪ Good
SI: 2800ms ⚪ Good
INP: 180ms ⚪ Good
❌ Critical Issues:
Performance:
🔴 Largest Contentful Paint element took too long to load
Value: 2.1 s
💾 Results saved to: ./results/example_com_2026-03-19T10-30-00-000Z.json
📄 HTML report saved to: ./results/example_com_2026-03-19T10-30-00-000Z.html
```
---
## Configuration Options
### Command Line Flags
| Flag | Description | Example |
|------|-------------|---------|
| `--mobile` | Use mobile emulation | `node analyze.js URL --mobile` |
| `--output=DIR` | Save results to directory | `node analyze.js URL --output=./reports` |
| `--help` | Show help message | `node analyze.js --help` |
### Modify Script Configuration
Edit `automation-script.js` to customize defaults:
```javascript
const CONFIG = {
logLevel: 'info', // 'silent', 'error', 'warn', 'info', 'verbose'
output: 'json', // 'json', 'html', 'csv'
onlyCategories: ['performance', 'accessibility', 'best-practices', 'seo'],
emulatedFormFactor: 'desktop', // 'mobile' or 'desktop'
numberOfRuns: 1, // Number of runs for averaging
};
```
---
## Advanced Usage
### Run Lighthouse CLI Directly
```bash
# Full audit with JSON output
lighthouse https://example.com --output=json --output-path=report.json
# Performance only
lighthouse https://example.com --only-categories=performance
# Mobile emulation
lighthouse https://example.com --emulated-form-factor=mobile
# Custom throttling
lighthouse https://example.com --throttling.cpuSlowdownMultiplier=4
# View report in browser
lighthouse https://example.com --view
```
### Use Lighthouse as Node Module
```javascript
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
async function audit(url) {
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
const options = {
logLevel: 'info',
output: 'json',
onlyCategories: ['performance'],
port: chrome.port,
};
const result = await lighthouse(url, options);
console.log('Performance score:', result.lhr.categories.performance.score * 100);
await chrome.kill();
}
audit('https://example.com');
```
### Batch Analysis
Create a script to analyze multiple URLs:
```javascript
// batch-analyze.js
const { runLighthouseAudit, saveResults } = require('./analyze');
const urls = [
'https://example.com',
'https://example.com/about',
'https://example.com/products',
];
async function batchAnalyze() {
for (const url of urls) {
try {
const results = await runLighthouseAudit(url);
saveResults(results, './batch-results');
} catch (error) {
console.error(`Failed to analyze url:`, error.message);
}
}
}
batchAnalyze();
```
---
## CI/CD Integration
### GitHub Actions
```yaml
# .github/workflows/lighthouse.yml
name: Lighthouse Audit
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: |
npm install -g lighthouse
npm install chrome-launcher
- name: Run Lighthouse
run: |
lighthouse https://your-site.com \
--output=json \
--output-path=./lighthouse-results.json \
--emulated-form-factor=mobile
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: lighthouse-results
path: ./lighthouse-results.json
```
### Lighthouse CI
```bash
# Install LHCI
npm install -g @lhci/cli
# Create config
cat > lighthouserc.js << 'EOF'
module.exports = {
ci: {
collect: {
url: ['https://example.com'],
numberOfRuns: 3,
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.8 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
},
},
},
};
EOF
# Run LHCI
lhci autorun
```
---
## Troubleshooting
### Chrome Not Found
**Error:** `Chrome could not be found`
**Solutions:**
```bash
# Linux: Install Chrome
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome-stable_current_amd64.deb
# macOS: Install Chrome
open https://www.google.com/chrome/
# Or use Puppeteer's bundled Chromium
npm install puppeteer
```
### Headless Chrome Fails
**Error:** `Failed to launch Chrome`
**Solutions:**
```bash
# Linux: Install dependencies
sudo apt-get install -y \
libnss3 \
libnspr4 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libxkbcommon0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxrandr2 \
libgbm1 \
libasound2 \
libpango-1.0-0 \
libcairo2
```
### Port Already in Use
**Error:** `Port 9222 is already in use`
**Solution:** Specify a different port:
```javascript
const chrome = await chromeLauncher.launch({
chromeFlags: ['--headless'],
port: 9223, // Different port
});
```
### Memory Issues
**Error:** `JavaScript heap out of memory`
**Solution:** Increase Node.js memory limit:
```bash
node --max-old-space-size=4096 analyze.js https://example.com
```
---
## Troubleshooting Quick Reference
| Error | Solution |
|-------|----------|
| "lighthouse is not a function" | Add `"type": "module"` to package.json |
| "Chrome not found" | Install Chrome or `npm install puppeteer` |
| "Failed to launch Chrome" | Install Linux dependencies (see troubleshooting.md) |
| "Port already in use" | Kill Chrome or use different port |
| "Heap out of memory" | Use `--max-old-space-size=4096` |
| "INP: N/A" | Normal - requires user interaction |
**For detailed troubleshooting, see [`troubleshooting.md`](troubleshooting.md).**
---
## Security Considerations
1. **Authentication:** Do not run automated audits on pages requiring authentication without proper security measures.
2. **Rate Limiting:** When analyzing multiple URLs, add delays to avoid overwhelming servers:
```javascript
await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second delay
```
3. **Data Privacy:** JSON reports may contain URL structure and page content. Store securely.
4. **Third-Party Scripts:** Audits may trigger third-party tracking. Consider using ad blockers or network interception.
---
## Next Steps
After setup:
1. **Run your first audit:** `node analyze.js https://example.com`
2. **Review results:** Check `./results/` directory
3. **Understand metrics:** See [`metrics-reference.md`](metrics-reference.md)
4. **Fix issues:** See [`audit-checklist.md`](audit-checklist.md)
5. **Track progress:** Use [`memory-template.md`](memory-template.md)
6. **Get help:** See [`troubleshooting.md`](troubleshooting.md)
**For fastest setup, see [`quick-start.md`](quick-start.md) for the 5-minute guide.**
FILE:troubleshooting.md
# Troubleshooting — Chrome DevTools Auto Analyzer
Common errors and solutions for automated website analysis.
---
## Installation Errors
### "lighthouse is not a function"
**Error:**
```
TypeError: lighthouse is not a function
```
**Cause:** Lighthouse v10+ uses ES modules, but package.json is missing `"type": "module"`.
**Solution:**
```bash
# Add to package.json
echo '{
"type": "module",
"dependencies": {
"lighthouse": "^12.x",
"chrome-launcher": "^1.x"
}
}' > package.json
```
---
### "Cannot find module 'lighthouse'"
**Error:**
```
Error: Cannot find module 'lighthouse'
```
**Cause:** Dependencies not installed.
**Solution:**
```bash
npm install lighthouse chrome-launcher
```
---
## Chrome/Browser Errors
### "Chrome could not be found"
**Error:**
```
Error: Chrome could not be found
```
**Cause:** Chrome/Chromium not installed or not in PATH.
**Solutions:**
**Option 1: Install Chrome**
```bash
# Ubuntu/Debian
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome-stable_current_amd64.deb
# macOS
open https://www.google.com/chrome/
# Windows
# Download from https://www.google.com/chrome/
```
**Option 2: Use Puppeteer's bundled Chromium**
```bash
npm install puppeteer
```
**Option 3: Specify Chrome path**
```javascript
const chrome = await chromeLauncher.launch({
chromePath: '/usr/bin/google-chrome', // Linux
// chromePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', // macOS
chromeFlags: ['--headless']
});
```
---
### "Failed to launch Chrome"
**Error:**
```
Error: Failed to launch Chrome!
```
**Cause:** Missing system dependencies (Linux) or sandbox issues.
**Solution (Linux):**
```bash
sudo apt-get install -y \
libnss3 \
libnspr4 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libxkbcommon0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxrandr2 \
libgbm1 \
libasound2 \
libpango-1.0-0 \
libcairo2
```
**Solution (Docker/Container):**
```javascript
chromeFlags: [
'--headless',
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage'
]
```
---
### "Port 9222 is already in use"
**Error:**
```
Error: Port 9222 is already in use
```
**Cause:** Another Chrome instance is using the debugging port.
**Solution:**
```javascript
// Specify different port
const chrome = await chromeLauncher.launch({
port: 9223, // Different port
chromeFlags: ['--headless']
});
```
Or kill existing Chrome:
```bash
# Linux
pkill -f chrome
pkill -f chromium
# macOS
killall "Google Chrome"
# Windows
taskkill /F /IM chrome.exe
```
---
## Memory Errors
### "JavaScript heap out of memory"
**Error:**
```
FATAL ERROR: Ineffective mark-compacts near heap limit
Allocation failed - JavaScript heap out of memory
```
**Cause:** Node.js default memory limit (2GB) exceeded.
**Solution:**
```bash
# Increase memory limit
node --max-old-space-size=4096 automation-script.js https://example.com
# Or set environment variable
export NODE_OPTIONS="--max-old-space-size=4096"
```
---
### "Timeout waiting for page load"
**Error:**
```
Error: Navigation timeout exceeded
```
**Cause:** Page takes too long to load.
**Solution:**
```javascript
// Increase timeout in script
const flags = {
...options,
maxWaitForLoad: 60000, // 60 seconds
};
```
---
## Network Errors
### "Failed to fetch URL"
**Error:**
```
Error: Failed to fetch URL: https://example.com
```
**Cause:** Network issues, DNS resolution failure, or site is down.
**Solutions:**
1. Check internet connection
2. Verify URL is accessible in browser
3. Check DNS resolution: `nslookup example.com`
4. Try with different network
---
### "INP: N/A" in results
**Output:**
```
INP: N/A
```
**Cause:** INP (Interaction to Next Paint) requires user interaction. Headless audits don't simulate interactions.
**Solution:** This is normal for automated audits. For INP measurement:
1. Use Chrome DevTools manually
2. Use Puppeteer to simulate interactions
3. Use field data from Chrome UX Report (CrUX)
---
## Audit Quality Issues
### "Performance score varies between runs"
**Issue:** Scores differ by 5-10 points between runs.
**Cause:** Network variability, server response times, system load.
**Solutions:**
1. Run multiple audits and average: `numberOfRuns: 3`
2. Use simulated throttling (default)
3. Run from consistent environment
4. Clear cache between runs
```javascript
const CONFIG = {
numberOfRuns: 3, // Average multiple runs
};
```
---
### "CLS score is very high"
**Issue:** CLS > 0.25 (Poor)
**Common Causes:**
1. Images without width/height attributes
2. Ads/embeds without reserved space
3. Dynamically injected content
4. Web fonts causing layout shifts
**Solutions:**
```html
<!-- Add explicit dimensions -->
<img src="hero.jpg" width="1200" height="630" alt="Hero">
<!-- Use CSS aspect-ratio -->
.image-container {
aspect-ratio: 16 / 9;
}
<!-- Reserve space for ads -->
.ad-container {
min-height: 250px;
}
<!-- Preload critical fonts -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
```
---
### "LCP score is poor"
**Issue:** LCP > 4.0s
**Common Causes:**
1. Slow server response
2. Large unoptimized images
3. Render-blocking resources
4. Client-side rendering
**Solutions:**
```html
<!-- Preload LCP image -->
<link rel="preload" as="image" href="hero.jpg">
<!-- Use modern image formats -->
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero">
</picture>
<!-- Defer non-critical CSS -->
<link rel="preload" as="style" href="critical.css">
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">
```
---
## CI/CD Integration Issues
### "LHCI fails in GitHub Actions"
**Error:**
```
Error: Chrome not found
```
**Solution (GitHub Actions):**
```yaml
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Chrome
run: |
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
sudo apt-get update
sudo apt-get install -y google-chrome-stable
- name: Install dependencies
run: npm install lighthouse chrome-launcher
- name: Run Lighthouse
run: node automation-script.js https://example.com
```
---
## Performance Optimization
### "Audit takes too long"
**Issue:** Audit takes >2 minutes.
**Solutions:**
1. Reduce categories to audit:
```javascript
const flags = {
onlyCategories: ['performance'], // Only what you need
};
```
2. Use throttling:
```javascript
const flags = {
throttlingMethod: 'simulate', // Faster than devtools
};
```
3. Skip resource-intensive audits:
```javascript
const flags = {
skipAudits: ['screenshot-thumbnails', 'full-page-screenshot'],
};
```
---
## Getting Help
If issues persist:
1. **Check logs:** Run with `--logLevel=verbose`
2. **Update packages:** `npm update lighthouse chrome-launcher`
3. **Check versions:** Ensure Node.js v18+, Lighthouse v12+
4. **Report bugs:** Include error message, versions, and OS
---
## Quick Reference
| Error | Quick Fix |
|-------|-----------|
| "lighthouse is not a function" | Add `"type": "module"` to package.json |
| "Chrome not found" | Install Chrome or `npm install puppeteer` |
| "Heap out of memory" | `node --max-old-space-size=4096` |
| "Port in use" | Change port or kill Chrome |
| "Failed to launch" | Install Linux dependencies |
| "INP: N/A" | Normal - requires user interaction |
| "High CLS" | Add width/height to images |
| "High LCP" | Preload images, optimize server |
Read light intensity from USB sensors with real-time monitoring, filtering, and threshold detection.
---
name: USB Light Sensor Reader
slug: usb-light-sensor-reader
version: 1.0.2
description: Read light intensity from USB sensors with real-time monitoring, filtering, and threshold detection.
metadata: {"clawdbot":{"emoji":"☀️","requires":{"bins":["python3"]},"os":["linux"]}}
---
## When to Use
User wants to read light intensity (lux) from USB-connected light sensor or check ambient light levels.
## Core Rules
1. **Verify Hardware First** — Confirm sensor is at `/dev/ttyUSB0` and user is in `dialout` group.
2. **Always Initialize** — Call `sensor.connect()` before reading. Waits 1s for warmup.
3. **Use Filtered Data** — `read_lux()` returns 5-sample moving average. Use `read_raw()` for unfiltered values.
4. **Default Thresholds** — Dark: < 100 lux, Bright: > 500 lux. Adjust based on environment.
5. **Disconnect on Exit** — Always call `sensor.disconnect()` to release serial port.
## Data Storage
No persistent storage. Sensor data read in real-time from serial port.
## External Endpoints
| Endpoint | Purpose |
|----------|---------|
| `/dev/ttyUSB0` | Serial read |
## Quick Reference
| Topic | File |
|-------|------|
| Setup & examples | `setup.md` |
| Troubleshooting | `setup.md` |
## Security Notes
- Declares external hardware dependency (USB sensor)
- No network access or environment variables required
FILE:README.md
# USB Light Sensor Reader
A skill for reading light intensity from USB-connected light sensors with real-time monitoring, filtering, and threshold detection.
## Features
- **Real-time Monitoring** - Continuous light intensity readings
- **Moving Average Filter** - 5-sample smoothing for stable readings
- **Threshold Detection** - Dark/bright environment detection
- **Raw Data Access** - Option to read unfiltered values
## Requirements
- Python 3
- USB light sensor connected to `/dev/ttyUSB0`
- `pyserial` package
- User in `dialout` group (Linux)
## Installation
```bash
npx clawhub install usb-light-sensor-reader
```
## Quick Start
```python
from sensor import Sensor
# Initialize and connect
sensor = Sensor(port='/dev/ttyUSB0')
sensor.connect()
# Read light intensity
lux = sensor.read_lux()
print(f"Light: {lux:.2f} lux")
# Check environment
if sensor.is_dark():
print("Environment is dark")
# Disconnect when done
sensor.disconnect()
```
## API Reference
### Class: `Sensor`
#### Constructor
```python
Sensor(port='/dev/ttyUSB0', baudrate=9600, timeout=1, filter_size=5)
```
| Parameter | Default | Description |
|-----------|---------|-------------|
| `port` | `/dev/ttyUSB0` | Serial port |
| `baudrate` | `9600` | Baud rate |
| `timeout` | `1` | Timeout in seconds |
| `filter_size` | `5` | Moving average window size |
#### Methods
| Method | Description | Returns |
|--------|-------------|---------|
| `connect()` | Connect to sensor | `bool` |
| `disconnect()` | Disconnect from sensor | `None` |
| `read_lux()` | Read filtered light intensity | `float` |
| `read_raw()` | Read unfiltered light intensity | `float` |
| `get_lux()` | Get latest reading | `float` |
| `is_dark(threshold=100)` | Check if dark | `bool` |
| `is_bright(threshold=500)` | Check if bright | `bool` |
## Sensor Protocol
Sensor outputs format: `brightness: XXX.XXLux\r\n`
| Setting | Value |
|---------|-------|
| Port | `/dev/ttyUSB0` |
| Baudrate | `9600` |
| Format | Text line with lux value |
## Usage Examples
### Continuous Monitoring
```python
from sensor import Sensor
import time
sensor = Sensor()
sensor.connect()
try:
while True:
lux = sensor.read_lux()
print(f"\r{lux:.2f} lux", end='', flush=True)
time.sleep(1)
except KeyboardInterrupt:
sensor.disconnect()
```
### Custom Threshold
```python
from sensor import Sensor
sensor = Sensor()
sensor.connect()
# Custom threshold for dark detection
if sensor.is_dark(threshold=50):
print("Very dark environment")
sensor.disconnect()
```
### Larger Filter for Smoother Readings
```python
# 10-sample moving average for smoother data
sensor = Sensor(filter_size=10)
sensor.connect()
```
## Troubleshooting
| Issue | Solution |
|-------|----------|
| `Permission denied` | Run `sudo usermod -a -G dialout $USER`, then logout/login |
| `Connection failed` | Check `ls /dev/ttyUSB*` for device |
| Returns `None` | Wait 1 second after connect for sensor warmup |
| Data not changing | Check sensor exposure, may need calibration |
## License
MIT
FILE:USAGE-GUIDE.md
# USB Relay & Light Sensor Skills - Usage Guide
Complete guide for installing and using the USB Relay Control and Light Sensor Reader skills with AI agents.
## Quick Start
Already have system configured? Here's the fastest way to get started:
```bash
# 1. Install ClawHub CLI
npm install -g clawhub
# 2. Login
npx clawhub login
# 3. Install skills
npx clawhub install control-usb-relay
npx clawhub install usb-light-sensor-reader
# 4. Install NanoBot
pip3 install nanobot-ai
# 5. Configure NanoBot
nanobot init
# 6. Start using with AI
nanobot start
```
Then tell NanoBot: *"Use the control-usb-relay skill to turn on the device"*
---
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Installation](#installation)
3. [Hardware Setup](#hardware-setup)
4. [Using with AI Agents](#using-with-ai-agents)
5. [Usage Examples](#usage-examples)
6. [Troubleshooting](#troubleshooting)
---
## Prerequisites
### System Requirements
- **OS:** Linux (Ubuntu, Debian, Fedora, etc.)
- **Python:** 3.8 or higher
- **Hardware:** USB relay module and/or USB light sensor
- **Permissions:** User must be in `dialout` group for serial port access
### Hardware Requirements
| Device | Port | Description |
|--------|------|-------------|
| USB Relay | `/dev/ttyUSB1` | 1-channel or multi-channel relay module |
| USB Light Sensor | `/dev/ttyUSB0` | BH1750 or similar lux sensor |
---
## Installation
### Step 1: Install Node.js (if not installed)
```bash
# Ubuntu/Debian
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
# Verify installation
node --version
npm --version
```
### Step 2: Install ClawHub CLI
```bash
# Install globally via npm
npm install -g clawhub
# Or use npx without global installation
npx clawhub --version
```
### Step 3: Login to ClawHub
```bash
# Login (opens browser for authentication)
npx clawhub login
# Verify login
npx clawhub whoami
```
### Step 4: Install Python Dependencies
```bash
# Install pyserial for USB communication
pip3 install pyserial
# Or for specific user
pip3 install --user pyserial
```
### Step 5: Configure Serial Port Permissions
```bash
# Add user to dialout group
sudo usermod -a -G dialout $USER
# Apply changes (logout and login, or reboot)
# Verify with:
groups $USER
```
### Step 6: Install the Skills
```bash
# Create skills directory (if not exists)
mkdir -p skills
cd skills
# Install USB Relay Control skill
npx clawhub install control-usb-relay
# Install USB Light Sensor Reader skill
npx clawhub install usb-light-sensor-reader
# List installed skills
npx clawhub list
```
### Step 7: Install NanoBot
```bash
# Clone NanoBot repository
git clone https://github.com/nanobot-ai/nanobot.git
cd nanobot
# Install dependencies
pip3 install -r requirements.txt
# Or install via pip
pip3 install nanobot-ai
# Verify installation
nanobot --version
```
#### NanoBot Configuration
After installation, configure NanoBot to use your skills:
```bash
# Initialize NanoBot configuration
nanobot init
# Or manually create config file
mkdir -p ~/.nanobot
```
Create `~/.nanobot/config.yaml`:
```yaml
skills:
directory: ~/nanobot_demo/nanobot_deom/skills
enabled:
- control-usb-relay
- usb-light-sensor-reader
hardware:
relay_port: /dev/ttyUSB1
sensor_port: /dev/ttyUSB0
model:
provider: openai # or anthropic, qwen, etc.
api_key: your-api-key-here
```
#### Starting NanoBot
```bash
# Start NanoBot interactive mode
nanobot start
# Or run with specific config
nanobot run --config ~/.nanobot/config.yaml
```
---
## Hardware Setup
### Connecting USB Relay
1. Connect the USB relay module to USB port (typically appears as `/dev/ttyUSB1`)
2. Connect devices to relay terminals (NO, COM, NC)
3. Verify connection:
```bash
ls -l /dev/ttyUSB*
```
### Connecting USB Light Sensor
1. Connect the light sensor module to USB port (typically appears as `/dev/ttyUSB0`)
2. Ensure sensor is exposed to light source
3. Verify connection:
```bash
ls -l /dev/ttyUSB*
```
### Testing Hardware
```bash
# Test relay
cd skills/control-usb-relay
python3 relay.py
# Test sensor
cd skills/usb-light-sensor-reader
python3 sensor.py
```
---
## Using with AI Agents
### Supported AI Agents
These skills work with any AI agent that supports ClawHub skills:
- **NanoBot** - Lightweight AI assistant (recommended for hardware control)
- **OpenCLAW** - Open-source AI agent framework
- **Qwen Code** - AI coding assistant
- **Cursor** - AI-powered IDE
- **Windsurf** - AI development environment
### NanoBot Integration
After installing NanoBot (Step 7), the skills are automatically available.
### Prompting AI Agents
When working with AI agents, use clear prompts that reference the skills:
#### Example Prompts
**For Relay Control:**
```
Use the control-usb-relay skill to turn on the connected device.
```
**For Sensor Reading:**
```
Read the current light level using the usb-light-sensor-reader skill.
```
**For Automation:**
```
Create an automation that turns on the relay when the light sensor detects
darkness (below 100 lux) and turns it off when it's bright (above 500 lux).
```
---
## Usage Examples
### Example 1: Basic Relay Control
**Prompt:**
```
Turn on the USB relay to power the connected device.
```
**Expected AI Action:**
```python
from relay import Relay
relay = Relay(port='/dev/ttyUSB1')
if relay.connect():
relay.turn_on()
print("Relay turned ON")
relay.disconnect()
```
### Example 2: Reading Light Sensor
**Prompt:**
```
Check the current ambient light level in the room.
```
**Expected AI Action:**
```python
from sensor import Sensor
sensor = Sensor(port='/dev/ttyUSB0')
if sensor.connect():
lux = sensor.read_lux()
print(f"Current light level: {lux:.2f} lux")
sensor.disconnect()
```
### Example 3: Automated Light Control
**Prompt:**
```
Set up automatic light control - turn on the lamp when it gets dark
and turn it off when it's bright enough.
```
**Expected AI Action:**
```python
from sensor import Sensor
from relay import Relay
import time
sensor = Sensor(port='/dev/ttyUSB0')
relay = Relay(port='/dev/ttyUSB1')
sensor.connect()
relay.connect()
try:
while True:
lux = sensor.read_lux()
print(f"Light: {lux:.2f} lux")
if lux < 100: # Dark
if not relay.is_on():
relay.turn_on()
print("Turning ON light")
elif lux > 500: # Bright
if relay.is_on():
relay.turn_off()
print("Turning OFF light")
time.sleep(5)
except KeyboardInterrupt:
sensor.disconnect()
relay.disconnect()
```
### Example 4: Status Check
**Prompt:**
```
Check if the relay is currently on and what the light level is.
```
**Expected AI Action:**
```python
from sensor import Sensor
from relay import Relay
sensor = Sensor()
relay = Relay()
sensor.connect()
relay.connect()
print(f"Relay status: {'ON' if relay.is_on() else 'OFF'}")
print(f"Light level: {sensor.read_lux():.2f} lux")
sensor.disconnect()
relay.disconnect()
```
### Example 5: Scheduled Operation
**Prompt:**
```
Create a schedule to turn on the device for 30 minutes every evening
when the light level drops below 150 lux.
```
**Expected AI Action:**
```python
from sensor import Sensor
from relay import Relay
import time
from datetime import datetime
sensor = Sensor(port='/dev/ttyUSB0')
relay = Relay(port='/dev/ttyUSB1')
sensor.connect()
relay.connect()
evening_start = 18 # 6 PM
evening_end = 23 # 11 PM
duration_minutes = 30
device_on = False
try:
while True:
now = datetime.now()
hour = now.hour
# Check if within evening hours
if evening_start <= hour <= evening_end:
lux = sensor.read_lux()
# Turn on if dark and not already on
if lux < 150 and not device_on:
relay.turn_on()
device_on = True
print(f"Device ON at {now.strftime('%H:%M')}")
start_time = time.time()
# Turn off after duration
if device_on:
elapsed = (time.time() - start_time) / 60
if elapsed >= duration_minutes:
relay.turn_off()
device_on = False
print(f"Device OFF after {duration_minutes} minutes")
time.sleep(30) # Check every 30 seconds
except KeyboardInterrupt:
if device_on:
relay.turn_off()
sensor.disconnect()
relay.disconnect()
```
---
## Troubleshooting
### Common Issues
| Issue | Cause | Solution |
|-------|-------|----------|
| `Permission denied` | User not in dialout group | `sudo usermod -a -G dialout $USER`, then logout/login |
| `Connection failed` | Wrong port or device not connected | Check `ls /dev/ttyUSB*` and verify connections |
| `No click sound` | Wrong protocol or external power needed | Verify relay protocol and check power supply |
| `Returns None` | Sensor warming up | Wait 1-2 seconds after connect() |
| `Port busy` | Another process using port | Kill other processes: `lsof /dev/ttyUSB0` |
### Diagnostic Commands
```bash
# Check USB devices
lsusb
# Check serial ports
ls -l /dev/ttyUSB*
# Check port usage
lsof /dev/ttyUSB0
lsof /dev/ttyUSB1
# Check user groups
groups $USER
# Test serial communication
python3 -c "import serial; print(serial.tools.list_ports.comports())"
```
### Getting Help
```bash
# View skill documentation
npx clawhub inspect control-usb-relay
npx clawhub inspect usb-light-sensor-reader
# Update skills
npx clawhub update control-usb-relay
npx clawhub update usb-light-sensor-reader
# Reinstall skills
npx clawhub uninstall control-usb-relay
npx clawhub install control-usb-relay
```
---
## Quick Reference
### Relay Commands
| Action | Method | Protocol |
|--------|--------|----------|
| Turn ON | `relay.turn_on()` | `A0 01 01 A2` |
| Turn OFF | `relay.turn_off()` | `A0 01 00 A1` |
| Toggle | `relay.toggle()` | Auto-detects state |
| Check status | `relay.is_on()` | Returns bool |
### Sensor Commands
| Action | Method | Threshold |
|--------|--------|-----------|
| Read light | `sensor.read_lux()` | Returns float |
| Check dark | `sensor.is_dark(100)` | Default: < 100 lux |
| Check bright | `sensor.is_bright(500)` | Default: > 500 lux |
| Raw reading | `sensor.read_raw()` | No filter |
---
## Additional Resources
- [ClawHub Documentation](https://clawhub.ai/docs)
- [NanoBot Documentation](https://nanobot.ai/docs)
- [NanoBot GitHub](https://github.com/nanobot-ai/nanobot)
- [OpenCLAW GitHub](https://github.com/openclaw/openclaw)
- [pyserial Documentation](https://pyserial.readthedocs.io/)
---
*Last updated: 2026-03-17*
*Skills version: 1.0.1*
FILE:_meta.json
{
"ownerId": "LJ-Hao",
"slug": "usb-light-sensor-reader",
"version": "1.0.2",
"publishedAt": 1773500000000
}
FILE:requirements.txt
pyserial>=3.5
FILE:sensor.py
#!/usr/bin/env python3
"""
Light Sensor Module
"""
import serial
import time
import re
class Sensor:
"""USB Light Sensor Class"""
def __init__(self, port='/dev/ttyUSB0', baudrate=9600, timeout=1, filter_size=5):
"""
Initialize Light Sensor
Args:
port: Serial port, default '/dev/ttyUSB0'
baudrate: Baud rate, default 9600
timeout: Timeout in seconds, default 1
filter_size: Moving average filter window size, default 5
"""
self.port = port
self.baudrate = baudrate
self.timeout = timeout
self.filter_size = filter_size
self.serial = None
self.latest_lux = None
self.lux_history = []
def connect(self):
"""
Connect to Sensor
Returns:
bool: True if connection successful
"""
try:
self.serial = serial.Serial(
port=self.port,
baudrate=self.baudrate,
timeout=self.timeout
)
print(f"Connected to light sensor: {self.port}")
# Warm up sensor
self._warmup()
return True
except serial.SerialException as e:
print(f"Failed to connect to sensor: {e}")
return False
def _warmup(self):
"""Warm up sensor"""
print(" Warming up sensor...")
time.sleep(1)
count = 0
while count < 20:
if self.serial.in_waiting > 0:
self.serial.readline()
count += 1
time.sleep(0.05)
def disconnect(self):
"""Disconnect from Sensor"""
if self.serial and self.serial.is_open:
self.serial.close()
def read_lux(self):
"""
Read Light Intensity (lux) - with moving average filter
Returns:
float: Light intensity value, None if read failed
"""
if not self.serial or not self.serial.is_open:
return self.latest_lux
try:
if self.serial.in_waiting > 0:
line = self.serial.readline().decode('utf-8', errors='ignore').strip()
match = re.search(r'brightness:\s*([\d.]+)\s*Lux', line, re.IGNORECASE)
if match:
lux = float(match.group(1))
# Add to history
self.lux_history.append(lux)
if len(self.lux_history) > self.filter_size:
self.lux_history.pop(0)
# Return moving average
self.latest_lux = sum(self.lux_history) / len(self.lux_history)
return self.latest_lux
return self.latest_lux
except Exception as e:
print(f"Failed to read sensor data: {e}")
return None
def read_raw(self):
"""
Read Raw Light Intensity (no filter)
Returns:
float: Raw light intensity value, None if read failed
"""
if not self.serial or not self.serial.is_open:
return None
try:
if self.serial.in_waiting > 0:
line = self.serial.readline().decode('utf-8', errors='ignore').strip()
match = re.search(r'brightness:\s*([\d.]+)\s*Lux', line, re.IGNORECASE)
if match:
return float(match.group(1))
return None
except Exception as e:
print(f"Failed to read sensor data: {e}")
return None
def get_lux(self):
"""
Get Latest Light Intensity Value
Returns:
float: Light intensity value
"""
return self.latest_lux
def is_dark(self, threshold=100):
"""
Check if Environment is Dark
Args:
threshold: Threshold below which is considered dark, default 100 lux
Returns:
bool: True if dark
"""
if self.latest_lux is None:
return False
return self.latest_lux < threshold
def is_bright(self, threshold=500):
"""
Check if Environment is Bright
Args:
threshold: Threshold above which is considered bright, default 500 lux
Returns:
bool: True if bright
"""
if self.latest_lux is None:
return False
return self.latest_lux > threshold
if __name__ == "__main__":
# Test example
sensor = Sensor()
if sensor.connect():
print("Sensor initialized")
print("\nReading 10 light intensity values:")
for i in range(10):
lux = sensor.read_lux()
if lux:
print(f" {lux:.2f} lux")
time.sleep(0.5)
print(f"\nCurrent light intensity: {sensor.get_lux():.2f} lux")
print(f"Is dark (<100 lux): {sensor.is_dark()}")
print(f"Is bright (>500 lux): {sensor.is_bright()}")
sensor.disconnect()
print("\nSensor disconnected")
FILE:setup.md
# Setup — USB Light Sensor Reader
## Prerequisites
- USB light sensor connected to `/dev/ttyUSB0`
- Python 3 with `pyserial` installed
- User in `dialout` group (for serial port access)
## Quick Start
```bash
# Install dependencies
pip3 install pyserial
# Add user to dialout group (if needed)
sudo usermod -a -G dialout $USER
# Test sensor
python3 sensor.py
```
## Usage Examples
### Basic Reading
```python
from sensor import Sensor
sensor = Sensor(port='/dev/ttyUSB0')
if sensor.connect():
lux = sensor.read_lux()
print(f"Light: {lux:.2f} lux")
sensor.disconnect()
```
### Continuous Monitoring
```python
from sensor import Sensor
import time
sensor = Sensor()
sensor.connect()
try:
while True:
lux = sensor.read_lux()
print(f"\r{lux:.2f} lux", end='', flush=True)
time.sleep(1)
except KeyboardInterrupt:
sensor.disconnect()
```
### Threshold Detection
```python
from sensor import Sensor
sensor = Sensor()
sensor.connect()
lux = sensor.read_lux()
if sensor.is_dark(threshold=100):
print("Environment is dark")
elif sensor.is_bright(threshold=500):
print("Environment is bright")
else:
print(f"Light level: {lux:.2f} lux")
sensor.disconnect()
```
### Custom Filter Size
```python
# Larger filter = smoother but slower response
sensor = Sensor(filter_size=10) # 10-sample moving average
sensor.connect()
```
## Troubleshooting
| Issue | Solution |
|-------|----------|
| `Permission denied` | `sudo usermod -a -G dialout $USER`, then logout/login |
| `Connection failed` | Check `ls /dev/ttyUSB*` for device |
| Returns `None` | Wait 1 second after connect, sensor is warming up |
| Data not changing | Check sensor exposure, may need calibration |
## Sensor Protocol
Sensor outputs: `brightness: XXX.XXLux\r\n`
- Port: `/dev/ttyUSB0`
- Baudrate: `9600`
- Format: Text line with lux value
Control USB relay modules with on/off switching, state tracking, and automation support.
---
name: Control USB Relay
slug: control-usb-relay
version: 1.0.2
description: Control USB relay modules with on/off switching, state tracking, and automation support.
metadata: {"clawdbot":{"emoji":"🔌","requires":{"bins":["python3"]},"os":["linux"]}}
---
## When to Use
User wants to control a USB relay module to switch devices on/off or automate based on sensor input.
## Core Rules
1. **Verify Hardware First** — Confirm relay is at `/dev/ttyUSB1` and user is in `dialout` group.
2. **Always Initialize** — Call `relay.connect()` before any control. Wait 500ms for connection.
3. **Use Correct Protocol** — ON: `A0 01 01 A2`, OFF: `A0 01 00 A1`. Different modules may use different protocols.
4. **State May Drift** — `get_status()` tracks last command. May differ if manual override occurred.
5. **Disconnect on Exit** — Always call `relay.disconnect()` to release serial port.
## Data Storage
No persistent storage. Relay state tracked in memory during session only.
## External Endpoints
| Endpoint | Purpose |
|----------|---------|
| `/dev/ttyUSB1` | Serial control |
## Quick Reference
| Topic | File |
|-------|------|
| Setup & examples | `setup.md` |
| Troubleshooting | `setup.md` |
## Security Notes
- Physical device control — confirm with user before switching
- Some relays need external 5V/12V power supply
FILE:README.md
# Control USB Relay
A skill for controlling USB relay modules with on/off switching, state tracking, and automation support.
## Features
- **On/Off Control** - Turn relay on or off with simple commands
- **Toggle Function** - Switch to opposite state
- **State Tracking** - Track last known relay state
- **Automation Ready** - Integrate with sensors for automated control
## Requirements
- Python 3
- USB relay module connected to `/dev/ttyUSB1`
- `pyserial` package
- User in `dialout` group (Linux)
## Installation
```bash
npx clawhub install control-usb-relay
```
## Quick Start
```python
from relay import Relay
# Initialize and connect
relay = Relay(port='/dev/ttyUSB1')
relay.connect()
# Control the relay
relay.turn_on() # Turn on
relay.turn_off() # Turn off
relay.toggle() # Toggle state
# Check status
print(f"Relay is: {'ON' if relay.is_on() else 'OFF'}")
# Disconnect when done
relay.disconnect()
```
## API Reference
### Class: `Relay`
#### Constructor
```python
Relay(port='/dev/ttyUSB1', baudrate=9600, timeout=1)
```
| Parameter | Default | Description |
|-----------|---------|-------------|
| `port` | `/dev/ttyUSB1` | Serial port |
| `baudrate` | `9600` | Baud rate |
| `timeout` | `1` | Timeout in seconds |
#### Methods
| Method | Description | Returns |
|--------|-------------|---------|
| `connect()` | Connect to relay | `bool` |
| `disconnect()` | Disconnect from relay | `None` |
| `turn_on()` | Turn relay on | `bool` |
| `turn_off()` | Turn relay off | `bool` |
| `toggle()` | Toggle relay state | `bool` |
| `is_on()` | Check if relay is on | `bool` |
| `is_off()` | Check if relay is off | `bool` |
| `get_status()` | Get current state | `bool` |
## Protocol
This skill uses the 4-byte command format:
| Command | Hex Bytes | Action |
|---------|-----------|--------|
| ON | `A0 01 01 A2` | Close relay |
| OFF | `A0 01 00 A1` | Open relay |
> **Note:** Different relay modules may use different protocols. Verify your device compatibility.
## Troubleshooting
| Issue | Solution |
|-------|----------|
| No click sound | Verify protocol matches your device |
| `Permission denied` | Run `sudo usermod -a -G dialout $USER`, then logout/login |
| `Connection failed` | Check `ls /dev/ttyUSB*` for device |
| Relay doesn't stay | Check external power supply |
## Hardware Notes
- **JQC-3FF-S-Z** — Relay component rating (5V coil)
- **USB Module** — May need external 5V/12V power
- **CH340** — Common USB-to-serial chip in relay modules
## License
MIT
FILE:USAGE-GUIDE.md
# USB Relay & Light Sensor Skills - Usage Guide
Complete guide for installing and using the USB Relay Control and Light Sensor Reader skills with AI agents.
## Quick Start
Already have system configured? Here's the fastest way to get started:
```bash
# 1. Install ClawHub CLI
npm install -g clawhub
# 2. Login
npx clawhub login
# 3. Install skills
npx clawhub install control-usb-relay
npx clawhub install usb-light-sensor-reader
# 4. Install NanoBot
pip3 install nanobot-ai
# 5. Configure NanoBot
nanobot init
# 6. Start using with AI
nanobot start
```
Then tell NanoBot: *"Use the control-usb-relay skill to turn on the device"*
---
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Installation](#installation)
3. [Hardware Setup](#hardware-setup)
4. [Using with AI Agents](#using-with-ai-agents)
5. [Usage Examples](#usage-examples)
6. [Troubleshooting](#troubleshooting)
---
## Prerequisites
### System Requirements
- **OS:** Linux (Ubuntu, Debian, Fedora, etc.)
- **Python:** 3.8 or higher
- **Hardware:** USB relay module and/or USB light sensor
- **Permissions:** User must be in `dialout` group for serial port access
### Hardware Requirements
| Device | Port | Description |
|--------|------|-------------|
| USB Relay | `/dev/ttyUSB1` | 1-channel or multi-channel relay module |
| USB Light Sensor | `/dev/ttyUSB0` | BH1750 or similar lux sensor |
---
## Installation
### Step 1: Install Node.js (if not installed)
```bash
# Ubuntu/Debian
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
# Verify installation
node --version
npm --version
```
### Step 2: Install ClawHub CLI
```bash
# Install globally via npm
npm install -g clawhub
# Or use npx without global installation
npx clawhub --version
```
### Step 3: Login to ClawHub
```bash
# Login (opens browser for authentication)
npx clawhub login
# Verify login
npx clawhub whoami
```
### Step 4: Install Python Dependencies
```bash
# Install pyserial for USB communication
pip3 install pyserial
# Or for specific user
pip3 install --user pyserial
```
### Step 5: Configure Serial Port Permissions
```bash
# Add user to dialout group
sudo usermod -a -G dialout $USER
# Apply changes (logout and login, or reboot)
# Verify with:
groups $USER
```
### Step 6: Install the Skills
```bash
# Create skills directory (if not exists)
mkdir -p skills
cd skills
# Install USB Relay Control skill
npx clawhub install control-usb-relay
# Install USB Light Sensor Reader skill
npx clawhub install usb-light-sensor-reader
# List installed skills
npx clawhub list
```
### Step 7: Install NanoBot
```bash
# Clone NanoBot repository
git clone https://github.com/nanobot-ai/nanobot.git
cd nanobot
# Install dependencies
pip3 install -r requirements.txt
# Or install via pip
pip3 install nanobot-ai
# Verify installation
nanobot --version
```
#### NanoBot Configuration
After installation, configure NanoBot to use your skills:
```bash
# Initialize NanoBot configuration
nanobot init
# Or manually create config file
mkdir -p ~/.nanobot
```
Create `~/.nanobot/config.yaml`:
```yaml
skills:
directory: ~/nanobot_demo/nanobot_deom/skills
enabled:
- control-usb-relay
- usb-light-sensor-reader
hardware:
relay_port: /dev/ttyUSB1
sensor_port: /dev/ttyUSB0
model:
provider: openai # or anthropic, qwen, etc.
api_key: your-api-key-here
```
#### Starting NanoBot
```bash
# Start NanoBot interactive mode
nanobot start
# Or run with specific config
nanobot run --config ~/.nanobot/config.yaml
```
---
## Hardware Setup
### Connecting USB Relay
1. Connect the USB relay module to USB port (typically appears as `/dev/ttyUSB1`)
2. Connect devices to relay terminals (NO, COM, NC)
3. Verify connection:
```bash
ls -l /dev/ttyUSB*
```
### Connecting USB Light Sensor
1. Connect the light sensor module to USB port (typically appears as `/dev/ttyUSB0`)
2. Ensure sensor is exposed to light source
3. Verify connection:
```bash
ls -l /dev/ttyUSB*
```
### Testing Hardware
```bash
# Test relay
cd skills/control-usb-relay
python3 relay.py
# Test sensor
cd skills/usb-light-sensor-reader
python3 sensor.py
```
---
## Using with AI Agents
### Supported AI Agents
These skills work with any AI agent that supports ClawHub skills:
- **NanoBot** - Lightweight AI assistant (recommended for hardware control)
- **OpenCLAW** - Open-source AI agent framework
- **Qwen Code** - AI coding assistant
- **Cursor** - AI-powered IDE
- **Windsurf** - AI development environment
### NanoBot Integration
After installing NanoBot (Step 7), the skills are automatically available.
### Prompting AI Agents
When working with AI agents, use clear prompts that reference the skills:
#### Example Prompts
**For Relay Control:**
```
Use the control-usb-relay skill to turn on the connected device.
```
**For Sensor Reading:**
```
Read the current light level using the usb-light-sensor-reader skill.
```
**For Automation:**
```
Create an automation that turns on the relay when the light sensor detects
darkness (below 100 lux) and turns it off when it's bright (above 500 lux).
```
---
## Usage Examples
### Example 1: Basic Relay Control
**Prompt:**
```
Turn on the USB relay to power the connected device.
```
**Expected AI Action:**
```python
from relay import Relay
relay = Relay(port='/dev/ttyUSB1')
if relay.connect():
relay.turn_on()
print("Relay turned ON")
relay.disconnect()
```
### Example 2: Reading Light Sensor
**Prompt:**
```
Check the current ambient light level in the room.
```
**Expected AI Action:**
```python
from sensor import Sensor
sensor = Sensor(port='/dev/ttyUSB0')
if sensor.connect():
lux = sensor.read_lux()
print(f"Current light level: {lux:.2f} lux")
sensor.disconnect()
```
### Example 3: Automated Light Control
**Prompt:**
```
Set up automatic light control - turn on the lamp when it gets dark
and turn it off when it's bright enough.
```
**Expected AI Action:**
```python
from sensor import Sensor
from relay import Relay
import time
sensor = Sensor(port='/dev/ttyUSB0')
relay = Relay(port='/dev/ttyUSB1')
sensor.connect()
relay.connect()
try:
while True:
lux = sensor.read_lux()
print(f"Light: {lux:.2f} lux")
if lux < 100: # Dark
if not relay.is_on():
relay.turn_on()
print("Turning ON light")
elif lux > 500: # Bright
if relay.is_on():
relay.turn_off()
print("Turning OFF light")
time.sleep(5)
except KeyboardInterrupt:
sensor.disconnect()
relay.disconnect()
```
### Example 4: Status Check
**Prompt:**
```
Check if the relay is currently on and what the light level is.
```
**Expected AI Action:**
```python
from sensor import Sensor
from relay import Relay
sensor = Sensor()
relay = Relay()
sensor.connect()
relay.connect()
print(f"Relay status: {'ON' if relay.is_on() else 'OFF'}")
print(f"Light level: {sensor.read_lux():.2f} lux")
sensor.disconnect()
relay.disconnect()
```
### Example 5: Scheduled Operation
**Prompt:**
```
Create a schedule to turn on the device for 30 minutes every evening
when the light level drops below 150 lux.
```
**Expected AI Action:**
```python
from sensor import Sensor
from relay import Relay
import time
from datetime import datetime
sensor = Sensor(port='/dev/ttyUSB0')
relay = Relay(port='/dev/ttyUSB1')
sensor.connect()
relay.connect()
evening_start = 18 # 6 PM
evening_end = 23 # 11 PM
duration_minutes = 30
device_on = False
try:
while True:
now = datetime.now()
hour = now.hour
# Check if within evening hours
if evening_start <= hour <= evening_end:
lux = sensor.read_lux()
# Turn on if dark and not already on
if lux < 150 and not device_on:
relay.turn_on()
device_on = True
print(f"Device ON at {now.strftime('%H:%M')}")
start_time = time.time()
# Turn off after duration
if device_on:
elapsed = (time.time() - start_time) / 60
if elapsed >= duration_minutes:
relay.turn_off()
device_on = False
print(f"Device OFF after {duration_minutes} minutes")
time.sleep(30) # Check every 30 seconds
except KeyboardInterrupt:
if device_on:
relay.turn_off()
sensor.disconnect()
relay.disconnect()
```
---
## Troubleshooting
### Common Issues
| Issue | Cause | Solution |
|-------|-------|----------|
| `Permission denied` | User not in dialout group | `sudo usermod -a -G dialout $USER`, then logout/login |
| `Connection failed` | Wrong port or device not connected | Check `ls /dev/ttyUSB*` and verify connections |
| `No click sound` | Wrong protocol or external power needed | Verify relay protocol and check power supply |
| `Returns None` | Sensor warming up | Wait 1-2 seconds after connect() |
| `Port busy` | Another process using port | Kill other processes: `lsof /dev/ttyUSB0` |
### Diagnostic Commands
```bash
# Check USB devices
lsusb
# Check serial ports
ls -l /dev/ttyUSB*
# Check port usage
lsof /dev/ttyUSB0
lsof /dev/ttyUSB1
# Check user groups
groups $USER
# Test serial communication
python3 -c "import serial; print(serial.tools.list_ports.comports())"
```
### Getting Help
```bash
# View skill documentation
npx clawhub inspect control-usb-relay
npx clawhub inspect usb-light-sensor-reader
# Update skills
npx clawhub update control-usb-relay
npx clawhub update usb-light-sensor-reader
# Reinstall skills
npx clawhub uninstall control-usb-relay
npx clawhub install control-usb-relay
```
---
## Quick Reference
### Relay Commands
| Action | Method | Protocol |
|--------|--------|----------|
| Turn ON | `relay.turn_on()` | `A0 01 01 A2` |
| Turn OFF | `relay.turn_off()` | `A0 01 00 A1` |
| Toggle | `relay.toggle()` | Auto-detects state |
| Check status | `relay.is_on()` | Returns bool |
### Sensor Commands
| Action | Method | Threshold |
|--------|--------|-----------|
| Read light | `sensor.read_lux()` | Returns float |
| Check dark | `sensor.is_dark(100)` | Default: < 100 lux |
| Check bright | `sensor.is_bright(500)` | Default: > 500 lux |
| Raw reading | `sensor.read_raw()` | No filter |
---
## Additional Resources
- [ClawHub Documentation](https://clawhub.ai/docs)
- [NanoBot Documentation](https://nanobot.ai/docs)
- [NanoBot GitHub](https://github.com/nanobot-ai/nanobot)
- [OpenCLAW GitHub](https://github.com/openclaw/openclaw)
- [pyserial Documentation](https://pyserial.readthedocs.io/)
---
*Last updated: 2026-03-17*
*Skills version: 1.0.1*
FILE:_meta.json
{
"ownerId": "LJ-Hao",
"slug": "control-usb-relay",
"version": "1.0.2",
"publishedAt": 1773500000000
}
FILE:relay.py
#!/usr/bin/env python3
"""
Relay Control Module
"""
import serial
import time
class Relay:
"""USB Relay Control Class"""
def __init__(self, port='/dev/ttyUSB1', baudrate=9600, timeout=1):
"""
Initialize Relay
Args:
port: Serial port, default '/dev/ttyUSB1'
baudrate: Baud rate, default 9600
timeout: Timeout in seconds, default 1
"""
self.port = port
self.baudrate = baudrate
self.timeout = timeout
self.serial = None
self.relay_state = False
def connect(self):
"""
Connect to Relay
Returns:
bool: True if connection successful
"""
try:
self.serial = serial.Serial(
port=self.port,
baudrate=self.baudrate,
timeout=self.timeout
)
time.sleep(0.5)
return True
except serial.SerialException as e:
print(f"Failed to connect to relay: {e}")
return False
def disconnect(self):
"""Disconnect from Relay"""
if self.serial and self.serial.is_open:
self.serial.close()
def turn_on(self):
"""
Turn on Relay
Command: A0 01 01 A2
Returns:
bool: True if operation successful
"""
return self._send_command(bytes([0xA0, 0x01, 0x01, 0xA2]), True)
def turn_off(self):
"""
Turn off Relay
Command: A0 01 00 A1
Returns:
bool: True if operation successful
"""
return self._send_command(bytes([0xA0, 0x01, 0x00, 0xA1]), False)
def toggle(self):
"""
Toggle Relay State
Returns:
bool: True if operation successful
"""
if self.relay_state:
return self.turn_off()
else:
return self.turn_on()
def _send_command(self, cmd, state):
"""
Send Command to Relay
Args:
cmd: Command bytes
state: Target state
Returns:
bool: True if send successful
"""
if not self.serial or not self.serial.is_open:
return False
try:
self.serial.write(cmd)
time.sleep(0.1)
# Read response (if any)
if self.serial.in_waiting > 0:
self.serial.read(self.serial.in_waiting)
self.relay_state = state
return True
except Exception as e:
print(f"Failed to send command: {e}")
return False
def get_status(self):
"""
Get Relay Status
Returns:
bool: Current state
"""
return self.relay_state
def is_on(self):
"""
Check if Relay is ON
Returns:
bool: True if ON
"""
return self.relay_state
def is_off(self):
"""
Check if Relay is OFF
Returns:
bool: True if OFF
"""
return not self.relay_state
if __name__ == "__main__":
# Test example
relay = Relay()
if relay.connect():
print("Relay connected successfully")
print("Turning ON relay...")
relay.turn_on()
time.sleep(1)
print("Turning OFF relay...")
relay.turn_off()
time.sleep(1)
print("Toggling relay...")
relay.toggle()
time.sleep(1)
print(f"Current state: {'ON' if relay.get_status() else 'OFF'}")
relay.disconnect()
print("Relay disconnected")
FILE:requirements.txt
pyserial>=3.5
FILE:setup.md
# Setup — Control USB Relay
## Prerequisites
- USB Relay module connected to `/dev/ttyUSB1`
- Python 3 with `pyserial` installed
- User in `dialout` group (for serial port access)
- External power supply (if required by relay module)
## Quick Start
```bash
# Install dependencies
pip3 install pyserial
# Add user to dialout group (if needed)
sudo usermod -a -G dialout $USER
# Test relay
python3 relay.py
```
## Usage Examples
### Basic Control
```python
from relay import Relay
relay = Relay(port='/dev/ttyUSB1')
if relay.connect():
relay.turn_on() # Turn on
relay.turn_off() # Turn off
relay.disconnect()
```
### Toggle State
```python
from relay import Relay
relay = Relay()
relay.connect()
relay.toggle() # Switch to opposite state
print(f"Relay is now: {'ON' if relay.is_on() else 'OFF'}")
relay.disconnect()
```
### Automated Control with Sensor
```python
from sensor import Sensor
from relay import Relay
import time
sensor = Sensor(port='/dev/ttyUSB0')
relay = Relay(port='/dev/ttyUSB1')
sensor.connect()
relay.connect()
try:
while True:
lux = sensor.read_lux()
if lux < 100: # Dark
relay.turn_on()
elif lux > 500: # Bright
relay.turn_off()
time.sleep(1)
except KeyboardInterrupt:
sensor.disconnect()
relay.disconnect()
```
## Troubleshooting
| Issue | Solution |
|-------|----------|
| No click sound | Verify protocol matches your device |
| `Permission denied` | `sudo usermod -a -G dialout $USER`, logout/login |
| `Connection failed` | Check `ls /dev/ttyUSB*` for device |
| Relay doesn't stay | Check external power supply |
## Relay Protocol
This skill uses the 4-byte command format:
| Command | Hex Bytes | Action |
|---------|-----------|--------|
| ON | `A0 01 01 A2` | Close relay |
| OFF | `A0 01 00 A1` | Open relay |
**Note:** Different relay modules may use different protocols. Verify your device compatibility.
## Hardware Notes
- **JQC-3FF-S-Z** — Relay component rating (5V coil)
- **USB Module** — May need external 5V/12V power
- **CH340** — Common USB-to-serial chip in relay modules