commitshow 0.3.10 → 0.3.14
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 +15 -3
- package/dist/lib/api.js +31 -1
- package/dist/lib/render.js +59 -28
- package/package.json +1 -1
package/dist/commands/audit.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { resolveTarget, verifyRemoteExists, 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, renderAuditError, writeAuditMarkdown, writeAuditJson, } from '../lib/render.js';
|
|
3
|
+
import { renderAudit, renderMarkdown, renderJson, renderUpsell, renderStarCta, renderQuotaFooter, renderRateLimitDeny, renderAuditError, writeAuditMarkdown, writeAuditJson, } from '../lib/render.js';
|
|
4
4
|
import { c } from '../lib/colors.js';
|
|
5
5
|
import { Spinner } from '../lib/spinner.js';
|
|
6
6
|
export async function audit(args) {
|
|
7
7
|
const asJson = args.includes('--json');
|
|
8
8
|
const force = args.includes('--refresh') || args.includes('--force');
|
|
9
|
-
|
|
9
|
+
// --source=<name> · self-reported call origin (claude-code · cursor ·
|
|
10
|
+
// antigravity · gemini-cli · codex · raw-cli · etc.). Falls back to
|
|
11
|
+
// the COMMITSHOW_SOURCE env var (used by IDE plugins that wrap the
|
|
12
|
+
// CLI) and ultimately empty string. Surfaced in /admin > CLI 사용
|
|
13
|
+
// tab as a distribution chart.
|
|
14
|
+
const sourceFlag = args.find(a => a.startsWith('--source='))?.split('=')[1]
|
|
15
|
+
?? args[args.indexOf('--source') + 1]?.replace(/^-/, '') // tolerate --source X
|
|
16
|
+
?? process.env.COMMITSHOW_SOURCE
|
|
17
|
+
?? null;
|
|
18
|
+
const positional = args.find(a => !a.startsWith('--') && a !== sourceFlag);
|
|
10
19
|
let target;
|
|
11
20
|
try {
|
|
12
21
|
target = resolveTarget(positional);
|
|
@@ -93,6 +102,9 @@ export async function audit(args) {
|
|
|
93
102
|
console.log('');
|
|
94
103
|
console.log(renderUpsell(project.github_url ?? target.github_url));
|
|
95
104
|
}
|
|
105
|
+
// Star CTA always lands last · highest-leverage moment for a star.
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log(renderStarCta());
|
|
96
108
|
console.log('');
|
|
97
109
|
}
|
|
98
110
|
if (target.kind === 'local') {
|
|
@@ -115,7 +127,7 @@ export async function audit(args) {
|
|
|
115
127
|
else
|
|
116
128
|
console.log(c.dim('First time on commit.show for this repo — running a preview audit…'));
|
|
117
129
|
}
|
|
118
|
-
const result = await runPreviewAudit(target.github_url, undefined, { force });
|
|
130
|
+
const result = await runPreviewAudit(target.github_url, undefined, { force, source: sourceFlag });
|
|
119
131
|
// Error envelope
|
|
120
132
|
if ('error' in result) {
|
|
121
133
|
const err = result;
|
package/dist/lib/api.js
CHANGED
|
@@ -18,12 +18,37 @@ const DEFAULT_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhY
|
|
|
18
18
|
function baseUrl() {
|
|
19
19
|
return readConfig().base_url ?? DEFAULT_BASE_URL;
|
|
20
20
|
}
|
|
21
|
+
// Self-identifying User-Agent so the server can break down CLI hits by
|
|
22
|
+
// version + runtime + platform (visible in /admin > CLI 사용 탭).
|
|
23
|
+
// Anonymous — no PII, just version/runtime breakdown for traction signal.
|
|
24
|
+
// Format: 'commitshow-cli/<ver> node/<v> <platform>-<arch>'
|
|
25
|
+
// e.g. 'commitshow-cli/0.3.13 node/v20.10.0 darwin-arm64'
|
|
26
|
+
const CLI_USER_AGENT = (() => {
|
|
27
|
+
// package.json version is bundled at build time via tsc resolution; we
|
|
28
|
+
// still hardcode a fallback so a stripped binary doesn't crash.
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
30
|
+
let version = 'unknown';
|
|
31
|
+
try {
|
|
32
|
+
// Lazy-require so this import doesn't break in Deno-like envs.
|
|
33
|
+
// The build script copies package.json next to the bundle.
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
35
|
+
const pkg = require('../../package.json');
|
|
36
|
+
if (typeof pkg?.version === 'string')
|
|
37
|
+
version = pkg.version;
|
|
38
|
+
}
|
|
39
|
+
catch { /* fall through · version stays 'unknown' */ }
|
|
40
|
+
const node = process.version || 'node';
|
|
41
|
+
const plat = `${process.platform}-${process.arch}`;
|
|
42
|
+
return `commitshow-cli/${version} node/${node} ${plat}`;
|
|
43
|
+
})();
|
|
21
44
|
function headers(extra = {}) {
|
|
22
45
|
const cfg = readConfig();
|
|
23
46
|
return {
|
|
24
47
|
apikey: DEFAULT_ANON_KEY,
|
|
25
48
|
Authorization: `Bearer ${cfg.token ?? DEFAULT_ANON_KEY}`,
|
|
26
49
|
'Content-Type': 'application/json',
|
|
50
|
+
'User-Agent': CLI_USER_AGENT,
|
|
51
|
+
'X-Commitshow-Source': process.env.COMMITSHOW_SOURCE ?? '',
|
|
27
52
|
...extra,
|
|
28
53
|
};
|
|
29
54
|
}
|
|
@@ -68,7 +93,12 @@ export async function runPreviewAudit(githubUrl, liveUrl, opts = {}) {
|
|
|
68
93
|
const res = await fetch(`${baseUrl()}/functions/v1/audit-preview`, {
|
|
69
94
|
method: 'POST',
|
|
70
95
|
headers: headers(),
|
|
71
|
-
body: JSON.stringify({
|
|
96
|
+
body: JSON.stringify({
|
|
97
|
+
github_url: githubUrl,
|
|
98
|
+
live_url: liveUrl,
|
|
99
|
+
force: opts.force === true,
|
|
100
|
+
source: opts.source ?? null,
|
|
101
|
+
}),
|
|
72
102
|
});
|
|
73
103
|
const body = await res.json().catch(() => ({ error: 'invalid_json' }));
|
|
74
104
|
if (res.status === 202)
|
package/dist/lib/render.js
CHANGED
|
@@ -266,42 +266,57 @@ export function renderAudit(view) {
|
|
|
266
266
|
lines.push(' ' + boxBottom());
|
|
267
267
|
lines.push('');
|
|
268
268
|
}
|
|
269
|
-
// Hero score ·
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
const bigWidth = bigRows[0].length;
|
|
275
|
-
const leftPad = Math.floor((58 - bigWidth) / 2);
|
|
276
|
-
for (const row of bigRows) {
|
|
277
|
-
lines.push(' ' + ' '.repeat(leftPad) + c.pixelInk(row));
|
|
278
|
-
}
|
|
279
|
-
// Breathing room between the hero ASCII and the small caption. Without
|
|
280
|
-
// it the "/ 100 · walk-on · strong" line glues to the bottom of the
|
|
281
|
-
// digits and the score reads as one block.
|
|
282
|
-
lines.push('');
|
|
283
|
-
// Caption · small "/ 100 · band" · band tinted so the signal lives there.
|
|
284
|
-
// Walk-on track gets an extra middle segment + a sub-line surfacing the
|
|
285
|
-
// 95 max so users read the score in the right context (88 walk-on ≠ 88
|
|
286
|
-
// league · perfect walk-on caps at 95 because Scout+Community pillars
|
|
287
|
-
// structurally unevaluated).
|
|
269
|
+
// Hero score · trophy plate. Score + caption wrapped in a double-line
|
|
270
|
+
// ╔═╗ box so the X/Twitter screenshot has a single hero artifact you can
|
|
271
|
+
// crop to. Box is intentionally wider than tall (≈3:2) — square in cell
|
|
272
|
+
// count is too tall once the 6-row ANSI Shadow + 1 caption + 2 padding
|
|
273
|
+
// rows are stacked. Outer 58-col layout still centers the box.
|
|
288
274
|
const band = total >= 75 ? 'strong' : total >= 50 ? 'mid' : 'weak';
|
|
289
275
|
const bandTone = scoreTone(total);
|
|
276
|
+
const bigRows = bigText(String(total));
|
|
277
|
+
const bigWidth = bigRows[0].length;
|
|
278
|
+
// Inner content width = the longer of (digit width, caption width) + a
|
|
279
|
+
// small breathing margin on each side. Box outer = inner + 2 (frame).
|
|
290
280
|
const captionVisible = isWalkOn
|
|
291
281
|
? `/ 100 · walk-on · ${band}`
|
|
292
282
|
: `/ 100 · ${band}`;
|
|
293
|
-
const
|
|
283
|
+
const SCORE_PAD = 4; // 2 cells of breathing room on each side of widest line
|
|
284
|
+
const scoreInsideW = Math.max(bigWidth, captionVisible.length) + SCORE_PAD;
|
|
285
|
+
const scoreOuterW = scoreInsideW + 2;
|
|
286
|
+
// Center the trophy box inside the 58-col layout
|
|
287
|
+
const trophyLeftPad = Math.max(0, Math.floor((58 - scoreOuterW) / 2));
|
|
288
|
+
const trophyIndent = ' ' + ' '.repeat(trophyLeftPad);
|
|
289
|
+
const trophyTop = c.muted('╔' + '═'.repeat(scoreInsideW) + '╗');
|
|
290
|
+
const trophyBottom = c.muted('╚' + '═'.repeat(scoreInsideW) + '╝');
|
|
291
|
+
const trophyBlank = c.muted('║' + ' '.repeat(scoreInsideW) + '║');
|
|
292
|
+
// Center a colored line inside the box. visibleLen is the *visible*
|
|
293
|
+
// (ANSI-stripped) cell count of the colored content.
|
|
294
|
+
const trophyRow = (visibleLen, colored) => {
|
|
295
|
+
const pad = Math.max(0, scoreInsideW - visibleLen);
|
|
296
|
+
const lp = Math.floor(pad / 2);
|
|
297
|
+
const rp = pad - lp;
|
|
298
|
+
return c.muted('║') + ' '.repeat(lp) + colored + ' '.repeat(rp) + c.muted('║');
|
|
299
|
+
};
|
|
300
|
+
lines.push(trophyIndent + trophyTop);
|
|
301
|
+
lines.push(trophyIndent + trophyBlank);
|
|
302
|
+
for (const row of bigRows) {
|
|
303
|
+
lines.push(trophyIndent + trophyRow(bigWidth, c.pixelInk(row)));
|
|
304
|
+
}
|
|
305
|
+
lines.push(trophyIndent + trophyBlank);
|
|
306
|
+
// Caption row · band-tinted so the signal lives on the band word.
|
|
307
|
+
const captionColored = isWalkOn
|
|
308
|
+
? c.muted('/ 100 · ') + c.gold('walk-on') + c.muted(' · ') + bandTone(band)
|
|
309
|
+
: c.muted('/ 100 · ') + bandTone(band);
|
|
310
|
+
lines.push(trophyIndent + trophyRow(captionVisible.length, captionColored));
|
|
311
|
+
lines.push(trophyIndent + trophyBottom);
|
|
312
|
+
// Walk-on sub-caption stays OUTSIDE the trophy — it's an explanation
|
|
313
|
+
// of the cap, not part of the headline. Keeps the box clean and tight
|
|
314
|
+
// for screenshot crops.
|
|
294
315
|
if (isWalkOn) {
|
|
295
|
-
lines.push(' ' + ' '.repeat(capPad)
|
|
296
|
-
+ c.muted('/ 100 · ') + c.gold('walk-on') + c.muted(' · ') + bandTone(band));
|
|
297
|
-
// Sub-caption explaining the 5pt headroom · centered, dim
|
|
298
316
|
const subVisible = 'audition unlocks final 5 · max walk-on score 95';
|
|
299
317
|
const subPad = Math.floor((58 - subVisible.length) / 2);
|
|
300
318
|
lines.push(' ' + ' '.repeat(subPad) + c.muted(subVisible));
|
|
301
319
|
}
|
|
302
|
-
else {
|
|
303
|
-
lines.push(' ' + ' '.repeat(capPad) + c.muted('/ 100 · ') + bandTone(band));
|
|
304
|
-
}
|
|
305
320
|
lines.push('');
|
|
306
321
|
// Axis bars · league shows all three; walk-on shows Audit only and
|
|
307
322
|
// surfaces Scout + Community as locked-with-unlock-hint rows.
|
|
@@ -347,8 +362,13 @@ export function renderAudit(view) {
|
|
|
347
362
|
lines.push(' ' + boxRow(2 + detailTrunc.length, c.muted(' ') + c.muted(detailTrunc)));
|
|
348
363
|
// Third line: evidence (if any) · only on fail/warn so success cases stay compact
|
|
349
364
|
if (it.evidence && (it.status === 'fail' || it.status === 'warn')) {
|
|
350
|
-
|
|
351
|
-
|
|
365
|
+
// `' → '` is 4 visible chars (two leading spaces + arrow + space)
|
|
366
|
+
// — boxRow needs the full visible length or its right │ lands 2
|
|
367
|
+
// cells too far right and the row "bursts" out of the box.
|
|
368
|
+
// Cap evTrunc at 48 so even on the widest evidence string the row
|
|
369
|
+
// stays inside CONTENT_W (54): 4 prefix + 48 evidence + 2 padding.
|
|
370
|
+
const evTrunc = truncate(it.evidence, 48);
|
|
371
|
+
lines.push(' ' + boxRow(4 + evTrunc.length, c.muted(' → ') + c.muted(evTrunc)));
|
|
352
372
|
}
|
|
353
373
|
}
|
|
354
374
|
lines.push(' ' + boxBottom());
|
|
@@ -381,6 +401,17 @@ export function renderAudit(view) {
|
|
|
381
401
|
lines.push(' '.repeat(footerPad) + c.gold(wordmark));
|
|
382
402
|
return lines.join('\n');
|
|
383
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Star CTA · single line, brand-colored. The CLI prints this last on a
|
|
406
|
+
* successful audit (after renderAudit + any renderUpsell box) — the
|
|
407
|
+
* audit just delivered concrete value (concerns + score), which is the
|
|
408
|
+
* highest-leverage moment to ask for a GitHub star. Caller controls the
|
|
409
|
+
* placement so it always lands at the bottom of the full output.
|
|
410
|
+
*/
|
|
411
|
+
export function renderStarCta() {
|
|
412
|
+
return ' ' + c.muted('★ Like what you see? Star us · ')
|
|
413
|
+
+ c.cream('github.com/commitshow/commitshow');
|
|
414
|
+
}
|
|
384
415
|
function vibeChecklistLines(vc) {
|
|
385
416
|
const out = [];
|
|
386
417
|
// 1. Webhook idempotency
|