commitshow 0.1.1 → 0.1.3

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.
@@ -1,6 +1,6 @@
1
1
  import { resolveTarget, TargetError } from '../lib/target.js';
2
2
  import { findProjectByGithubUrl, fetchLatestSnapshot, fetchStanding, runPreviewAudit, waitForPreviewSnapshot, } from '../lib/api.js';
3
- import { renderAudit, renderMarkdown, renderJson, renderUpsell, renderQuotaFooter, renderRateLimitDeny, writeAuditMarkdown, writeAuditJson, } from '../lib/render.js';
3
+ import { renderAudit, renderMarkdown, renderJson, renderUpsell, renderQuotaFooter, renderRateLimitDeny, renderAuditError, writeAuditMarkdown, writeAuditJson, } from '../lib/render.js';
4
4
  import { c } from '../lib/colors.js';
5
5
  export async function audit(args) {
6
6
  const asJson = args.includes('--json');
@@ -27,6 +27,30 @@ export async function audit(args) {
27
27
  fetchStanding(project.id),
28
28
  ]);
29
29
  const view = { project, snapshot, standing };
30
+ // The snapshot may carry an audit-engine error (Claude quota exceeded,
31
+ // rate limit, etc.). Render the friendly explanation panel and exit
32
+ // 2 so CI scripts can detect "engine unavailable" without conflating
33
+ // it with a genuine low score.
34
+ const auditErr = snapshot?.rich_analysis?.error;
35
+ if (auditErr) {
36
+ if (asJson) {
37
+ process.stdout.write(JSON.stringify({
38
+ error: 'audit_engine_error',
39
+ reason: auditErr.type,
40
+ message: auditErr.message ?? null,
41
+ retry_after: auditErr.retry_after_seconds ?? null,
42
+ project: { id: project.id, name: project.project_name, github_url: project.github_url },
43
+ }) + '\n');
44
+ }
45
+ else {
46
+ console.error('');
47
+ console.error(renderAuditError({ type: auditErr.type, message: auditErr.message ?? undefined,
48
+ retry_after_seconds: auditErr.retry_after_seconds ?? null,
49
+ http_status: auditErr.http_status }, project.project_name, `https://commit.show/projects/${project.id}`));
50
+ console.error('');
51
+ }
52
+ return 2;
53
+ }
30
54
  if (asJson) {
31
55
  process.stdout.write(renderJson(view) + '\n');
32
56
  }
@@ -106,6 +130,30 @@ export async function audit(args) {
106
130
  envelope = result;
107
131
  }
108
132
  const view = { project: envelope.project, snapshot: envelope.snapshot, standing: null };
133
+ // Same audit-engine error check as the cached path. The polled snapshot
134
+ // can carry a Claude failure even though the audit-preview Edge Function
135
+ // returned 202 (the failure happened in the background analyze-project).
136
+ const polledErr = envelope.snapshot?.rich_analysis?.error;
137
+ if (polledErr) {
138
+ if (asJson) {
139
+ process.stdout.write(JSON.stringify({
140
+ error: 'audit_engine_error',
141
+ reason: polledErr.type,
142
+ message: polledErr.message ?? null,
143
+ retry_after: polledErr.retry_after_seconds ?? null,
144
+ project: { id: envelope.project.id, name: envelope.project.project_name, github_url: envelope.project.github_url },
145
+ quota: envelope.quota,
146
+ }) + '\n');
147
+ }
148
+ else {
149
+ console.error('');
150
+ console.error(renderAuditError({ type: polledErr.type, message: polledErr.message ?? undefined,
151
+ retry_after_seconds: polledErr.retry_after_seconds ?? null,
152
+ http_status: polledErr.http_status }, envelope.project.project_name, `https://commit.show/projects/${envelope.project.id}`));
153
+ console.error('');
154
+ }
155
+ return 2;
156
+ }
109
157
  if (asJson) {
110
158
  // Inject quota into the v1 schema as an additive field — schema_version
111
159
  // unchanged because additive-only fields don't bump it.
@@ -11,6 +11,7 @@ function rgb(r, g, b) {
11
11
  }
12
12
  export const c = {
13
13
  gold: rgb(0xF0, 0xC0, 0x40),
14
+ goldDeep: rgb(0xD4, 0xA8, 0x38), // slightly darker · used for big-digit hero
14
15
  cream: rgb(0xF8, 0xF5, 0xEE),
15
16
  teal: rgb(0x00, 0xD4, 0xAA),
16
17
  scarlet: rgb(0xC8, 0x10, 0x2E),
@@ -94,19 +94,22 @@ export function renderAudit(view) {
94
94
  lines.push(' ' + c.bold(c.cream(name)) + ' ' + c.muted(slug));
95
95
  lines.push('');
96
96
  // Hero score · big-digit ASCII for X-share screenshots.
97
- // Renders the score number 5 rows tall, color-coded by tier band.
98
- // Width budget: 3-digit (e.g. "100") = 17 cols centered in 58.
99
- const tone = scoreTone(total);
97
+ // Always brand gold (slightly deeper tone for screenshot legibility) so
98
+ // the wordmark + score read as one cohesive brand mark. Band info is
99
+ // surfaced in the small caption underneath instead of via color.
100
100
  const bigRows = bigText(String(total));
101
101
  const bigWidth = bigRows[0].length;
102
102
  const leftPad = Math.floor((58 - bigWidth) / 2);
103
103
  for (const row of bigRows) {
104
- lines.push(' ' + ' '.repeat(leftPad) + tone(row));
104
+ lines.push(' ' + ' '.repeat(leftPad) + c.goldDeep(row));
105
105
  }
106
- // Caption beneath the big number — small "/ 100 · band"
106
+ // Caption · small "/ 100 · band" · band tinted so the signal lives there.
107
107
  const band = total >= 75 ? 'strong' : total >= 50 ? 'mid' : 'weak';
108
+ const bandTone = scoreTone(total);
108
109
  const caption = `/ 100 · ${band}`;
109
- lines.push(' ' + ' '.repeat(Math.floor((58 - caption.length) / 2)) + c.muted(caption));
110
+ // Center the caption (visible chars only color codes don't take width).
111
+ const capPad = Math.floor((58 - caption.length) / 2);
112
+ lines.push(' ' + ' '.repeat(capPad) + c.muted('/ 100 · ') + bandTone(band));
110
113
  lines.push('');
111
114
  // 3-axis bars
112
115
  const auditLine = ` Audit ${pad(`${p.score_auto}/50`, 7)} ${scoreBar(p.score_auto, 50)}`;
@@ -298,6 +301,69 @@ export function toAgentShape(view) {
298
301
  export function renderJson(view) {
299
302
  return JSON.stringify(toAgentShape(view), null, 2);
300
303
  }
304
+ // ── Audit-engine error panel ────────────────────────────────────────
305
+ // Rendered when the snapshot exists but rich_analysis.error is set —
306
+ // i.e., the Claude call itself failed (quota, rate limit, network). The
307
+ // project's auto-50 signals may still be valid; we explain that and tell
308
+ // the user when fresh audits will resume.
309
+ const AUDIT_ERROR_LABEL = {
310
+ anthropic_quota_exceeded: 'Daily audit budget reached',
311
+ anthropic_rate_limited: 'Audit engine rate-limited',
312
+ anthropic_overloaded: 'Audit engine overloaded',
313
+ anthropic_auth_error: 'Audit engine auth issue',
314
+ anthropic_other: 'Audit engine error',
315
+ claude_returned_no_data: 'Audit engine returned no data',
316
+ network_error: 'Audit engine network error',
317
+ };
318
+ const AUDIT_ERROR_DETAIL = {
319
+ anthropic_quota_exceeded: "commit.show paused fresh audits until the daily budget refills. " +
320
+ "Cached audits (any repo audited in the last 7 days) still work normally.",
321
+ anthropic_rate_limited: "Too many fresh audits in a short window. Wait a minute and retry. " +
322
+ "Cached results stay available.",
323
+ anthropic_overloaded: "The audit engine is briefly overloaded. Retry in a minute or two.",
324
+ anthropic_auth_error: "commit.show's API key needs attention. Cached results still work.",
325
+ anthropic_other: "Something on the audit engine side blocked this run. Try again later.",
326
+ claude_returned_no_data: "The audit engine ran but returned an empty response. Try again.",
327
+ network_error: "The audit engine couldn't be reached. Check your connection or retry.",
328
+ };
329
+ function untilHuman(seconds) {
330
+ if (seconds < 60)
331
+ return `${seconds}s`;
332
+ if (seconds < 3600)
333
+ return `${Math.round(seconds / 60)}m`;
334
+ if (seconds < 86400)
335
+ return `${Math.round(seconds / 3600)}h`;
336
+ return `${Math.round(seconds / 86400)}d`;
337
+ }
338
+ export function renderAuditError(err, projectName, projectUrl) {
339
+ const label = AUDIT_ERROR_LABEL[err.type] ?? AUDIT_ERROR_LABEL.anthropic_other;
340
+ const detail = AUDIT_ERROR_DETAIL[err.type] ?? AUDIT_ERROR_DETAIL.anthropic_other;
341
+ const lines = [];
342
+ const horiz = '─'.repeat(58);
343
+ lines.push(' ' + c.muted('┌' + horiz + '┐'));
344
+ lines.push(' ' + c.muted('│ ') + c.bold(c.gold('commit.show')) + c.muted(' · ') + c.scarlet(label) + ' '.repeat(Math.max(0, 58 - 14 - label.length)) + c.muted('│'));
345
+ lines.push(' ' + c.muted('│' + ' '.repeat(58) + '│'));
346
+ if (projectName) {
347
+ lines.push(' ' + c.muted('│ ') + c.cream(`Repo: ${projectName}`) + ' '.repeat(Math.max(0, 56 - 6 - projectName.length)) + c.muted('│'));
348
+ lines.push(' ' + c.muted('│' + ' '.repeat(58) + '│'));
349
+ }
350
+ for (const w of wrapText(detail, 54)) {
351
+ lines.push(' ' + c.muted('│ ') + c.cream(w) + ' '.repeat(Math.max(0, 56 - w.length)) + c.muted('│'));
352
+ }
353
+ if (err.retry_after_seconds && err.retry_after_seconds > 0) {
354
+ lines.push(' ' + c.muted('│' + ' '.repeat(58) + '│'));
355
+ const t = `Retry after ~${untilHuman(err.retry_after_seconds)}`;
356
+ lines.push(' ' + c.muted('│ ') + c.dim(t) + ' '.repeat(Math.max(0, 56 - t.length)) + c.muted('│'));
357
+ }
358
+ lines.push(' ' + c.muted('│' + ' '.repeat(58) + '│'));
359
+ const statusLine = `Status check: commitshow status ${projectName ?? '<repo>'}`;
360
+ lines.push(' ' + c.muted('│ ') + c.dim(statusLine) + ' '.repeat(Math.max(0, 56 - statusLine.length)) + c.muted('│'));
361
+ if (projectUrl) {
362
+ lines.push(' ' + c.muted('│ ') + c.dim('Web view: ') + c.cream(projectUrl.length > 44 ? projectUrl.slice(0, 41) + '…' : projectUrl) + ' '.repeat(Math.max(0, 46 - Math.min(44, projectUrl.length))) + c.muted('│'));
363
+ }
364
+ lines.push(' ' + c.muted('└' + horiz + '┘'));
365
+ return lines.join('\n');
366
+ }
301
367
  function timeUntil(isoTarget) {
302
368
  const ms = Math.max(0, new Date(isoTarget).getTime() - Date.now());
303
369
  const h = Math.floor(ms / 3_600_000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commitshow",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "commit.show CLI — audit any vibe-coded project from your terminal.",
5
5
  "type": "module",
6
6
  "bin": {