create-claude-cabinet 0.29.5 → 0.29.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-cabinet",
3
- "version": "0.29.5",
3
+ "version": "0.29.6",
4
4
  "description": "Claude Cabinet — opinionated process scaffolding for Claude Code projects",
5
5
  "bin": {
6
6
  "create-claude-cabinet": "bin/create-claude-cabinet.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-cabinet/site-audit",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Comprehensive deployed-site quality audit engine for Claude Cabinet. Runs checks across performance, accessibility, security, SEO, content, DNS, and privacy against a deployed URL; single-site and comparison modes; standalone HTML report.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,12 +2,12 @@ export const checkId = 'axe-core';
2
2
  export const tool = 'axe-core (WCAG AA)';
3
3
 
4
4
  export async function detect(executor) {
5
- const r = await executor.spawn('npx', ['@axe-core/cli', '--version'], { timeoutMs: 15_000 });
5
+ const r = await executor.spawn('axe', ['--version'], { timeoutMs: 15_000 });
6
6
  return r.code === 0;
7
7
  }
8
8
 
9
9
  export async function run(url, executor) {
10
- return executor.spawn('npx', ['@axe-core/cli', url, '--exit'], { timeoutMs: 60_000 });
10
+ return executor.spawn('axe', [url, '--exit'], { timeoutMs: 60_000 });
11
11
  }
12
12
 
13
13
  const IMPACT_TO_SEVERITY = { critical: 'critical', serious: 'serious', moderate: 'moderate', minor: 'info' };
@@ -7,12 +7,12 @@ export const tool = 'Blacklight (tracker detection)';
7
7
  export const defaultTimeoutMs = 120_000;
8
8
 
9
9
  export async function detect(executor) {
10
- const r = await executor.spawn('npx', ['@themarkup/blacklight-collector', '--help'], { timeoutMs: 15_000 });
10
+ const r = await executor.spawn('blacklight-collector', ['--help'], { timeoutMs: 15_000 });
11
11
  return r.code === 0 || r.stdout.includes('blacklight');
12
12
  }
13
13
 
14
14
  export async function run(url, executor) {
15
- return executor.spawn('npx', ['@themarkup/blacklight-collector', url, '--json'], { timeoutMs: 120_000 });
15
+ return executor.spawn('blacklight-collector', [url, '--json'], { timeoutMs: 120_000 });
16
16
  }
17
17
 
18
18
  const TRACKER_SEVERITY = {
@@ -3,13 +3,13 @@ export const tool = 'Lighthouse';
3
3
  export const defaultTimeoutMs = 120_000;
4
4
 
5
5
  export async function detect(executor) {
6
- const r = await executor.spawn('npx', ['lighthouse', '--version'], { timeoutMs: 15_000 });
6
+ const r = await executor.spawn('lighthouse', ['--version'], { timeoutMs: 15_000 });
7
7
  return r.code === 0;
8
8
  }
9
9
 
10
10
  export async function run(url, executor) {
11
- const r = await executor.spawn('npx', [
12
- 'lighthouse', url,
11
+ const r = await executor.spawn('lighthouse', [
12
+ url,
13
13
  '--output=json',
14
14
  '--chrome-flags=--headless=new --no-sandbox',
15
15
  '--only-categories=performance,accessibility,best-practices,seo',
@@ -2,12 +2,12 @@ export const checkId = 'linkinator';
2
2
  export const tool = 'Linkinator (broken links)';
3
3
 
4
4
  export async function detect(executor) {
5
- const r = await executor.spawn('npx', ['linkinator', '--version'], { timeoutMs: 15_000 });
5
+ const r = await executor.spawn('linkinator', ['--version'], { timeoutMs: 15_000 });
6
6
  return r.code === 0;
7
7
  }
8
8
 
9
9
  export async function run(url, executor) {
10
- return executor.spawn('npx', ['linkinator', url, '--format', 'json', '--timeout', '10000'], { timeoutMs: 60_000 });
10
+ return executor.spawn('linkinator', [url, '--format', 'json', '--timeout', '10000'], { timeoutMs: 60_000 });
11
11
  }
12
12
 
13
13
  export function normalize(raw, durationMs) {
@@ -2,13 +2,13 @@ export const checkId = 'observatory';
2
2
  export const tool = 'MDN HTTP Observatory';
3
3
 
4
4
  export async function detect(executor) {
5
- const r = await executor.spawn('npx', ['@mdn/mdn-http-observatory', '--version'], { timeoutMs: 15_000 });
5
+ const r = await executor.spawn('mdn-http-observatory', ['--version'], { timeoutMs: 15_000 });
6
6
  return r.code === 0;
7
7
  }
8
8
 
9
9
  export async function run(url, executor) {
10
10
  const hostname = new URL(url).hostname;
11
- return executor.spawn('npx', ['@mdn/mdn-http-observatory', hostname], { timeoutMs: 60_000 });
11
+ return executor.spawn('mdn-http-observatory', [hostname], { timeoutMs: 60_000 });
12
12
  }
13
13
 
14
14
  const GRADE_SCORES = { 'A+': 100, 'A': 95, 'A-': 90, 'B+': 85, 'B': 80, 'B-': 75, 'C+': 70, 'C': 65, 'C-': 60, 'D+': 55, 'D': 50, 'D-': 45, 'F': 20 };
@@ -2,12 +2,12 @@ export const checkId = 'pa11y';
2
2
  export const tool = 'Pa11y (WCAG AAA)';
3
3
 
4
4
  export async function detect(executor) {
5
- const r = await executor.spawn('npx', ['pa11y', '--version'], { timeoutMs: 15_000 });
5
+ const r = await executor.spawn('pa11y', ['--version'], { timeoutMs: 15_000 });
6
6
  return r.code === 0;
7
7
  }
8
8
 
9
9
  export async function run(url, executor) {
10
- return executor.spawn('npx', ['pa11y', url, '--reporter', 'json', '--standard', 'WCAG2AAA'], { timeoutMs: 60_000 });
10
+ return executor.spawn('pa11y', [url, '--reporter', 'json', '--standard', 'WCAG2AAA'], { timeoutMs: 60_000 });
11
11
  }
12
12
 
13
13
  const TYPE_TO_SEVERITY = { error: 'serious', warning: 'moderate', notice: 'info' };
@@ -7,13 +7,13 @@ export const tool = 'Unlighthouse (full-site crawl)';
7
7
  export const defaultTimeoutMs = 300_000;
8
8
 
9
9
  export async function detect(executor) {
10
- const r = await executor.spawn('npx', ['unlighthouse', '--version'], { timeoutMs: 15_000 });
10
+ const r = await executor.spawn('unlighthouse', ['--version'], { timeoutMs: 15_000 });
11
11
  return r.code === 0;
12
12
  }
13
13
 
14
14
  export async function run(url, executor) {
15
- return executor.spawn('npx', [
16
- 'unlighthouse', '--site', url, '--ci', '--reporter', 'json',
15
+ return executor.spawn('unlighthouse', [
16
+ '--site', url, '--ci', '--reporter', 'json',
17
17
  ], { timeoutMs: 300_000 });
18
18
  }
19
19
 
@@ -180,13 +180,16 @@ export async function auditSite(url, checks, opts = {}) {
180
180
 
181
181
  const start = Date.now();
182
182
  let available;
183
+ let detectError = '';
183
184
  try {
184
185
  available = await check.detect(executor);
185
- } catch {
186
+ } catch (err) {
186
187
  available = false;
188
+ detectError = err instanceof Error ? err.message : String(err);
187
189
  }
188
190
 
189
191
  if (!available) {
192
+ const hint = detectError ? ` (${detectError})` : ' (binary not found in PATH)';
190
193
  return {
191
194
  checkId: check.checkId,
192
195
  tool: check.tool,
@@ -196,7 +199,7 @@ export async function auditSite(url, checks, opts = {}) {
196
199
  severity: null,
197
200
  findings: [],
198
201
  durationMs: Date.now() - start,
199
- reason: `${check.tool} not available`,
202
+ reason: `${check.tool} not available${hint}`,
200
203
  };
201
204
  }
202
205
 
@@ -137,10 +137,12 @@ export function generateSummary(delta) {
137
137
  const biggest = sorted[0];
138
138
 
139
139
  let summary = '';
140
+ const sA = hostnameLabel(delta.urlA);
141
+ const sB = hostnameLabel(delta.urlB);
140
142
  if (bWins > aWins) {
141
- summary += `Site B outperforms Site A on ${bWins} of ${scored.length} scored dimension${scored.length > 1 ? 's' : ''}`;
143
+ summary += `${esc(sB)} outperforms ${esc(sA)} on ${bWins} of ${scored.length} scored dimension${scored.length > 1 ? 's' : ''}`;
142
144
  } else if (aWins > bWins) {
143
- summary += `Site A outperforms Site B on ${aWins} of ${scored.length} scored dimension${scored.length > 1 ? 's' : ''}`;
145
+ summary += `${esc(sA)} outperforms ${esc(sB)} on ${aWins} of ${scored.length} scored dimension${scored.length > 1 ? 's' : ''}`;
144
146
  } else {
145
147
  summary += `Sites are evenly matched across ${scored.length} scored dimension${scored.length > 1 ? 's' : ''}`;
146
148
  }
@@ -236,7 +238,13 @@ function classifyFindings(a, b) {
236
238
  * @param {import('./diff.mjs').DeltaReport} delta
237
239
  * @returns {string}
238
240
  */
241
+ function hostnameLabel(url) {
242
+ try { return new URL(url).hostname; } catch { return url; }
243
+ }
244
+
239
245
  export function renderComparison(delta) {
246
+ const labelA = hostnameLabel(delta.urlA);
247
+ const labelB = hostnameLabel(delta.urlB);
240
248
  const title = `Site Comparison — ${delta.urlA} vs ${delta.urlB}`;
241
249
 
242
250
  let html = head(title);
@@ -248,14 +256,14 @@ export function renderComparison(delta) {
248
256
  const { aOnly, bOnly } = delta.summary;
249
257
  if (aOnly > 0 || bOnly > 0) {
250
258
  html += `<div class="asymmetric-warning">Asymmetric availability: `;
251
- if (aOnly > 0) html += `${aOnly} check${aOnly > 1 ? 's' : ''} ran only for Site A. `;
252
- if (bOnly > 0) html += `${bOnly} check${bOnly > 1 ? 's' : ''} ran only for Site B. `;
259
+ if (aOnly > 0) html += `${aOnly} check${aOnly > 1 ? 's' : ''} ran only for ${esc(labelA)}. `;
260
+ if (bOnly > 0) html += `${bOnly} check${bOnly > 1 ? 's' : ''} ran only for ${esc(labelB)}. `;
253
261
  html += `Deltas for one-sided checks show N/A.</div>`;
254
262
  }
255
263
 
256
264
  // ── Comparison grid with drill-down links ──
257
265
  html += '<div style="background:#fff;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,.05);overflow:hidden;margin-bottom:2rem">';
258
- html += `<div class="compare-row"><span>Check</span><span>Site A</span><span>Site B</span><span>Delta</span></div>`;
266
+ html += `<div class="compare-row"><span>Check</span><span>${esc(labelA)}</span><span>${esc(labelB)}</span><span>Delta</span></div>`;
259
267
 
260
268
  for (const d of delta.deltas) {
261
269
  const aDisplay = d.a && d.a.status !== 'skip'
@@ -308,8 +316,8 @@ export function renderComparison(delta) {
308
316
  if (d.availability === 'both') {
309
317
  const { shared, aOnly: aOnlyF, bOnly: bOnlyF } = classifyFindings(d.a, d.b);
310
318
  html += '<div class="side-by-side">';
311
- html += `<div class="site-column"><h4>Site A</h4>${compareCardFindings('Site A only', aOnlyF, d.a)}</div>`;
312
- html += `<div class="site-column"><h4>Site B</h4>${compareCardFindings('Site B only', bOnlyF, d.b)}</div>`;
319
+ html += `<div class="site-column"><h4>${esc(labelA)}</h4>${compareCardFindings(esc(labelA) + ' only', aOnlyF, d.a)}</div>`;
320
+ html += `<div class="site-column"><h4>${esc(labelB)}</h4>${compareCardFindings(esc(labelB) + ' only', bOnlyF, d.b)}</div>`;
313
321
  html += '</div>';
314
322
  if (shared.length) {
315
323
  html += `<div class="finding-group-label" style="margin-top:1rem">Shared issues (both sites)</div>${renderFindings(shared)}`;