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.
- package/dist/commands/audit.js +49 -1
- package/dist/lib/colors.js +1 -0
- package/dist/lib/render.js +72 -6
- package/package.json +1 -1
package/dist/commands/audit.js
CHANGED
|
@@ -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.
|
package/dist/lib/colors.js
CHANGED
|
@@ -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),
|
package/dist/lib/render.js
CHANGED
|
@@ -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
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
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) +
|
|
104
|
+
lines.push(' ' + ' '.repeat(leftPad) + c.goldDeep(row));
|
|
105
105
|
}
|
|
106
|
-
// Caption
|
|
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
|
-
|
|
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);
|