commitshow 0.1.3 → 0.1.5
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 +17 -6
- package/dist/index.js +2 -1
- package/dist/lib/api.js +5 -3
- package/dist/lib/render.js +94 -49
- package/package.json +1 -1
package/dist/commands/audit.js
CHANGED
|
@@ -4,6 +4,7 @@ import { renderAudit, renderMarkdown, renderJson, renderUpsell, renderQuotaFoote
|
|
|
4
4
|
import { c } from '../lib/colors.js';
|
|
5
5
|
export async function audit(args) {
|
|
6
6
|
const asJson = args.includes('--json');
|
|
7
|
+
const force = args.includes('--refresh') || args.includes('--force');
|
|
7
8
|
const positional = args.find(a => !a.startsWith('--'));
|
|
8
9
|
let target;
|
|
9
10
|
try {
|
|
@@ -16,11 +17,17 @@ export async function audit(args) {
|
|
|
16
17
|
}
|
|
17
18
|
throw err;
|
|
18
19
|
}
|
|
19
|
-
if (!asJson)
|
|
20
|
-
|
|
20
|
+
if (!asJson) {
|
|
21
|
+
if (force)
|
|
22
|
+
console.log(c.dim(`Refreshing audit for ${target.slug}…`));
|
|
23
|
+
else
|
|
24
|
+
console.log(c.dim(`Auditing ${target.slug}…`));
|
|
25
|
+
}
|
|
21
26
|
// 1. Try cached/registered flow first — avoid re-running Claude if we
|
|
22
27
|
// already have the snapshot. Covers all full-audition projects.
|
|
23
|
-
|
|
28
|
+
// --refresh / --force skips this entirely and goes straight to audit-preview
|
|
29
|
+
// with force=true (counts against IP + URL + global rate limits).
|
|
30
|
+
const project = force ? null : await findProjectByGithubUrl(target.github_url);
|
|
24
31
|
if (project) {
|
|
25
32
|
const [snapshot, standing] = await Promise.all([
|
|
26
33
|
fetchLatestSnapshot(project.id),
|
|
@@ -77,9 +84,13 @@ export async function audit(args) {
|
|
|
77
84
|
}
|
|
78
85
|
// 2. Unregistered repo — kick off a preview audit. Full Claude depth,
|
|
79
86
|
// no season entry. Rate-limited server-side.
|
|
80
|
-
if (!asJson)
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
if (!asJson) {
|
|
88
|
+
if (force)
|
|
89
|
+
console.log(c.dim('Forcing a fresh audit · counts against your daily IP cap…'));
|
|
90
|
+
else
|
|
91
|
+
console.log(c.dim('First time on commit.show for this repo — running a preview audit…'));
|
|
92
|
+
}
|
|
93
|
+
const result = await runPreviewAudit(target.github_url, undefined, { force });
|
|
83
94
|
// Error envelope
|
|
84
95
|
if ('error' in result) {
|
|
85
96
|
const err = result;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { status } from './commands/status.js';
|
|
|
5
5
|
import { login } from './commands/login.js';
|
|
6
6
|
import { whoami } from './commands/whoami.js';
|
|
7
7
|
import { c } from './lib/colors.js';
|
|
8
|
-
const VERSION = '0.1.
|
|
8
|
+
const VERSION = '0.1.5';
|
|
9
9
|
const USAGE = `
|
|
10
10
|
${c.bold(c.gold('commit.show'))} ${c.dim(`v${VERSION}`)} ${c.muted('—')} ${c.cream('audit any vibe-coded project from your terminal.')}
|
|
11
11
|
|
|
@@ -22,6 +22,7 @@ ${c.muted('COMMANDS')}
|
|
|
22
22
|
|
|
23
23
|
${c.muted('FLAGS')}
|
|
24
24
|
${c.gold('--json')} stable machine-readable output (for agents · CI · jq pipes)
|
|
25
|
+
${c.gold('--refresh')} bypass the 7-day cache · re-run a fresh audit ${c.dim('(counts against IP cap)')}
|
|
25
26
|
|
|
26
27
|
${c.muted('TARGET FORMS')} ${c.dim('(default: cwd)')}
|
|
27
28
|
${c.cream('commitshow audit')} ${c.dim('# cwd · git remote origin')}
|
package/dist/lib/api.js
CHANGED
|
@@ -61,12 +61,14 @@ export async function fetchStanding(projectId) {
|
|
|
61
61
|
const rows = await rest(`/season_standings?project_id=eq.${projectId}&limit=1`);
|
|
62
62
|
return rows[0] ?? null;
|
|
63
63
|
}
|
|
64
|
-
/** Kicks off (or returns cached) a preview audit. 202 → poll; 200 → done.
|
|
65
|
-
|
|
64
|
+
/** Kicks off (or returns cached) a preview audit. 202 → poll; 200 → done.
|
|
65
|
+
* force=true skips the 7-day cache and forces a fresh analyze-project run.
|
|
66
|
+
* Counts against IP + URL + global rate limits (real Claude spend). */
|
|
67
|
+
export async function runPreviewAudit(githubUrl, liveUrl, opts = {}) {
|
|
66
68
|
const res = await fetch(`${baseUrl()}/functions/v1/audit-preview`, {
|
|
67
69
|
method: 'POST',
|
|
68
70
|
headers: headers(),
|
|
69
|
-
body: JSON.stringify({ github_url: githubUrl, live_url: liveUrl }),
|
|
71
|
+
body: JSON.stringify({ github_url: githubUrl, live_url: liveUrl, force: opts.force === true }),
|
|
70
72
|
});
|
|
71
73
|
const body = await res.json().catch(() => ({ error: 'invalid_json' }));
|
|
72
74
|
if (res.status === 202)
|
package/dist/lib/render.js
CHANGED
|
@@ -60,6 +60,27 @@ function centerPad(s, w) {
|
|
|
60
60
|
const left = Math.floor(total / 2);
|
|
61
61
|
return ' '.repeat(left) + s + ' '.repeat(total - left);
|
|
62
62
|
}
|
|
63
|
+
// ── Single source of truth for box drawing ─────────────────────
|
|
64
|
+
// Every panel uses 58-char outer width (1 corner + 56 interior + 1 corner)
|
|
65
|
+
// so screenshots line up. Helper takes the *visible* length so colored
|
|
66
|
+
// content (multiple ANSI spans) renders at the right padding without
|
|
67
|
+
// having to strip escape codes at runtime.
|
|
68
|
+
const BOX_W = 58; // outer width including both corners
|
|
69
|
+
const INSIDE_W = BOX_W - 2; // chars between │ and │
|
|
70
|
+
const CONTENT_W = INSIDE_W - 2; // chars between '│ ' and ' │'
|
|
71
|
+
const boxTop = () => c.muted('┌' + '─'.repeat(INSIDE_W) + '┐');
|
|
72
|
+
const boxBottom = () => c.muted('└' + '─'.repeat(INSIDE_W) + '┘');
|
|
73
|
+
const boxBlank = () => c.muted('│' + ' '.repeat(INSIDE_W) + '│');
|
|
74
|
+
/**
|
|
75
|
+
* Render a content row inside the box with proper padding.
|
|
76
|
+
* @param visibleLen number of visible chars in `colored` (for padding math)
|
|
77
|
+
* @param colored the rendered string (may contain ANSI escapes)
|
|
78
|
+
* @param leftMargin extra spaces inside the box, after the leading `│ `
|
|
79
|
+
*/
|
|
80
|
+
function boxRow(visibleLen, colored, leftMargin = 0) {
|
|
81
|
+
const padding = Math.max(0, CONTENT_W - leftMargin - visibleLen);
|
|
82
|
+
return c.muted('│ ') + ' '.repeat(leftMargin) + colored + ' '.repeat(padding) + c.muted(' │');
|
|
83
|
+
}
|
|
63
84
|
/** Strip ANSI for width math on colored strings. */
|
|
64
85
|
function visibleLength(s) {
|
|
65
86
|
// eslint-disable-next-line no-control-regex
|
|
@@ -82,11 +103,11 @@ export function renderAudit(view) {
|
|
|
82
103
|
const { project: p, snapshot, standing } = view;
|
|
83
104
|
const total = p.score_total ?? 0;
|
|
84
105
|
// Header
|
|
85
|
-
const bar = '─'.repeat(58);
|
|
86
106
|
const lines = [];
|
|
87
|
-
lines.push(
|
|
88
|
-
lines.push(
|
|
89
|
-
|
|
107
|
+
lines.push(boxTop());
|
|
108
|
+
lines.push(boxRow(
|
|
109
|
+
/* visibleLen */ 'commit.show · Audit report'.length, c.bold(c.gold('commit.show')) + c.muted(' · ') + c.cream('Audit report')));
|
|
110
|
+
lines.push(boxBottom());
|
|
90
111
|
lines.push('');
|
|
91
112
|
// Project title line
|
|
92
113
|
const name = p.project_name ?? 'untitled';
|
|
@@ -124,14 +145,20 @@ export function renderAudit(view) {
|
|
|
124
145
|
const strengths = asStringArray(snapshot?.rich_analysis?.scout_brief?.strengths, 3);
|
|
125
146
|
const concerns = asStringArray(snapshot?.rich_analysis?.scout_brief?.weaknesses, 2);
|
|
126
147
|
if (strengths.length > 0 || concerns.length > 0) {
|
|
127
|
-
|
|
148
|
+
// strengths/concerns each render as `↑ ` (2 visible) + truncated line.
|
|
149
|
+
// Total visible-line budget inside the box is CONTENT_W chars; reserve
|
|
150
|
+
// 2 for the arrow + space, leaving CONTENT_W - 2 for the bullet text.
|
|
151
|
+
const bulletWidth = CONTENT_W - 2;
|
|
152
|
+
lines.push(' ' + boxTop());
|
|
128
153
|
for (const s of strengths) {
|
|
129
|
-
|
|
154
|
+
const txt = truncate(s, bulletWidth);
|
|
155
|
+
lines.push(' ' + boxRow(2 + txt.length, c.teal('↑ ') + c.cream(txt)));
|
|
130
156
|
}
|
|
131
157
|
for (const s of concerns) {
|
|
132
|
-
|
|
158
|
+
const txt = truncate(s, bulletWidth);
|
|
159
|
+
lines.push(' ' + boxRow(2 + txt.length, c.scarlet('↓ ') + c.cream(txt)));
|
|
133
160
|
}
|
|
134
|
-
lines.push(' ' +
|
|
161
|
+
lines.push(' ' + boxBottom());
|
|
135
162
|
lines.push('');
|
|
136
163
|
}
|
|
137
164
|
// Standings + delta
|
|
@@ -339,29 +366,32 @@ export function renderAuditError(err, projectName, projectUrl) {
|
|
|
339
366
|
const label = AUDIT_ERROR_LABEL[err.type] ?? AUDIT_ERROR_LABEL.anthropic_other;
|
|
340
367
|
const detail = AUDIT_ERROR_DETAIL[err.type] ?? AUDIT_ERROR_DETAIL.anthropic_other;
|
|
341
368
|
const lines = [];
|
|
342
|
-
const
|
|
343
|
-
lines.push(' ' +
|
|
344
|
-
lines.push(' ' +
|
|
345
|
-
lines.push(' ' +
|
|
369
|
+
const titleVisible = `commit.show · ${label}`;
|
|
370
|
+
lines.push(' ' + boxTop());
|
|
371
|
+
lines.push(' ' + boxRow(titleVisible.length, c.bold(c.gold('commit.show')) + c.muted(' · ') + c.scarlet(label)));
|
|
372
|
+
lines.push(' ' + boxBlank());
|
|
346
373
|
if (projectName) {
|
|
347
|
-
|
|
348
|
-
lines.push(' ' +
|
|
374
|
+
const repoLine = `Repo: ${projectName}`;
|
|
375
|
+
lines.push(' ' + boxRow(repoLine.length, c.cream(repoLine)));
|
|
376
|
+
lines.push(' ' + boxBlank());
|
|
349
377
|
}
|
|
350
|
-
for (const w of wrapText(detail,
|
|
351
|
-
lines.push(' ' +
|
|
378
|
+
for (const w of wrapText(detail, CONTENT_W)) {
|
|
379
|
+
lines.push(' ' + boxRow(w.length, c.cream(w)));
|
|
352
380
|
}
|
|
353
381
|
if (err.retry_after_seconds && err.retry_after_seconds > 0) {
|
|
354
|
-
lines.push(' ' +
|
|
382
|
+
lines.push(' ' + boxBlank());
|
|
355
383
|
const t = `Retry after ~${untilHuman(err.retry_after_seconds)}`;
|
|
356
|
-
lines.push(' ' +
|
|
384
|
+
lines.push(' ' + boxRow(t.length, c.dim(t)));
|
|
357
385
|
}
|
|
358
|
-
lines.push(' ' +
|
|
386
|
+
lines.push(' ' + boxBlank());
|
|
359
387
|
const statusLine = `Status check: commitshow status ${projectName ?? '<repo>'}`;
|
|
360
|
-
lines.push(' ' +
|
|
388
|
+
lines.push(' ' + boxRow(Math.min(statusLine.length, CONTENT_W), c.dim(statusLine.slice(0, CONTENT_W))));
|
|
361
389
|
if (projectUrl) {
|
|
362
|
-
|
|
390
|
+
const urlMax = CONTENT_W - 'Web view: '.length;
|
|
391
|
+
const urlText = projectUrl.length > urlMax ? projectUrl.slice(0, urlMax - 1) + '…' : projectUrl;
|
|
392
|
+
lines.push(' ' + boxRow('Web view: '.length + urlText.length, c.dim('Web view: ') + c.cream(urlText)));
|
|
363
393
|
}
|
|
364
|
-
lines.push(' ' +
|
|
394
|
+
lines.push(' ' + boxBottom());
|
|
365
395
|
return lines.join('\n');
|
|
366
396
|
}
|
|
367
397
|
function timeUntil(isoTarget) {
|
|
@@ -407,26 +437,30 @@ function bar(filled, total, width = 20) {
|
|
|
407
437
|
}
|
|
408
438
|
export function renderRateLimitDeny(opts) {
|
|
409
439
|
const lines = [];
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
lines.push(' ' +
|
|
413
|
-
lines.push(' ' + c.
|
|
414
|
-
lines.push(' ' +
|
|
440
|
+
const reasonLabel = REASON_LABEL[opts.reason] ?? opts.reason;
|
|
441
|
+
const titleVisible = `Rate limit · ${reasonLabel}`;
|
|
442
|
+
lines.push(' ' + boxTop());
|
|
443
|
+
lines.push(' ' + boxRow(titleVisible.length, c.bold(c.scarlet('Rate limit')) + c.muted(' · ') + c.cream(reasonLabel)));
|
|
444
|
+
lines.push(' ' + boxBlank());
|
|
445
|
+
// Count + bar row · "5/5 " (5 chars) + 20-char bar = 25 visible
|
|
446
|
+
const counter = `${opts.count}/${opts.limit} `;
|
|
447
|
+
lines.push(' ' + boxRow(counter.length + 20, c.cream(counter) + bar(opts.count, opts.limit)));
|
|
415
448
|
if (opts.quota) {
|
|
416
|
-
const reset = timeUntil(opts.quota.reset_at)
|
|
417
|
-
lines.push(' ' +
|
|
449
|
+
const reset = `resets in ${timeUntil(opts.quota.reset_at)}`;
|
|
450
|
+
lines.push(' ' + boxRow(reset.length, c.dim(reset)));
|
|
418
451
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
lines.push(' ' + c.muted('│ ') + c.cream(w) + ' '.repeat(56 - w.length) + c.muted('│'));
|
|
452
|
+
for (const w of wrapText(opts.message, CONTENT_W)) {
|
|
453
|
+
lines.push(' ' + boxRow(w.length, c.cream(w)));
|
|
422
454
|
}
|
|
423
455
|
if (opts.reason === 'url_cap') {
|
|
424
|
-
|
|
456
|
+
const tip = 'Tip: cached audit (< 7d) is free — commitshow status <repo>';
|
|
457
|
+
lines.push(' ' + boxRow(tip.length, c.dim(tip)));
|
|
425
458
|
}
|
|
426
459
|
if (opts.reason === 'ip_cap' && opts.quota?.ip.tier === 'anon') {
|
|
427
|
-
|
|
460
|
+
const tip = 'Sign in (commit.show) for a higher daily cap.';
|
|
461
|
+
lines.push(' ' + boxRow(tip.length, c.dim(tip)));
|
|
428
462
|
}
|
|
429
|
-
lines.push(' ' +
|
|
463
|
+
lines.push(' ' + boxBottom());
|
|
430
464
|
return lines.join('\n');
|
|
431
465
|
}
|
|
432
466
|
function wrapText(s, width) {
|
|
@@ -449,24 +483,35 @@ function wrapText(s, width) {
|
|
|
449
483
|
}
|
|
450
484
|
export function renderUpsell() {
|
|
451
485
|
const lines = [];
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
lines.push(' ' +
|
|
456
|
-
lines.push(' ' + c.
|
|
486
|
+
const titleVisible = 'Preview · not entered in the season';
|
|
487
|
+
const headVisible = 'Audition to unlock:';
|
|
488
|
+
const ctaVisible = '→ https://commit.show/submit';
|
|
489
|
+
lines.push(' ' + boxTop());
|
|
490
|
+
lines.push(' ' + boxRow(titleVisible.length, c.bold(c.gold('Preview')) + c.muted(' · ') + c.cream('not entered in the season')));
|
|
491
|
+
lines.push(' ' + boxBlank());
|
|
492
|
+
lines.push(' ' + boxRow(headVisible.length, c.cream(headVisible)));
|
|
493
|
+
// Backstage = our brand for the prompt-extraction analysis (Phase 2 brief).
|
|
494
|
+
// Lead the unlock list with it because it's the most concrete, immediate
|
|
495
|
+
// payoff on signup: see how the project was built (delegation map, failure
|
|
496
|
+
// log, decision archaeology) and unlock +15-20 audit points typical.
|
|
497
|
+
// Tags are 15-char column-aligned so the · separator lines up vertically.
|
|
498
|
+
// Tags column-padded to 16 chars (1 trailing space guaranteed so the · in
|
|
499
|
+
// `rest` always has a visual gap from the tag, even when the tag fills 15).
|
|
457
500
|
const items = [
|
|
458
|
-
'
|
|
459
|
-
'
|
|
460
|
-
'
|
|
461
|
-
'
|
|
462
|
-
'
|
|
501
|
+
{ tag: 'Backstage ', rest: '· build process + prompts · +15 pts', tone: c.gold },
|
|
502
|
+
{ tag: 'Scout forecasts ', rest: '· human verdicts (30% of score)', tone: c.teal },
|
|
503
|
+
{ tag: 'Season ranking ', rest: '· top 20% graduate per cycle', tone: c.teal },
|
|
504
|
+
{ tag: 'Hall of Fame ', rest: '· permanent archive + badge', tone: c.teal },
|
|
505
|
+
{ tag: 'Live applauds ', rest: '· notifications on reactions', tone: c.teal },
|
|
506
|
+
{ tag: 'Recommit loop ', rest: '· weekly delta + share card', tone: c.teal },
|
|
463
507
|
];
|
|
464
508
|
for (const it of items) {
|
|
465
|
-
|
|
509
|
+
const visible = `→ ${it.tag}${it.rest}`;
|
|
510
|
+
lines.push(' ' + boxRow(visible.length, it.tone('→ ') + c.cream(it.tag) + c.muted(it.rest)));
|
|
466
511
|
}
|
|
467
|
-
lines.push(' ' +
|
|
468
|
-
lines.push(' ' +
|
|
469
|
-
lines.push(' ' +
|
|
512
|
+
lines.push(' ' + boxBlank());
|
|
513
|
+
lines.push(' ' + boxRow(ctaVisible.length, c.gold(ctaVisible)));
|
|
514
|
+
lines.push(' ' + boxBottom());
|
|
470
515
|
return lines.join('\n');
|
|
471
516
|
}
|
|
472
517
|
export function writeAuditJson(dir, json) {
|