commitshow 0.3.12 → 0.3.15
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/commands/login.js +119 -10
- package/dist/commands/whoami.js +49 -7
- package/dist/lib/api.js +31 -1
- package/dist/lib/render.js +11 -0
- 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/commands/login.js
CHANGED
|
@@ -1,16 +1,125 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// commitshow login — device-flow authorization.
|
|
2
|
+
//
|
|
3
|
+
// Default flow:
|
|
4
|
+
// 1. POST /functions/v1/cli-link-init · receive { code, poll_token, verification_url }
|
|
5
|
+
// 2. Print the code + URL · open the URL in the user's browser (unless --no-open)
|
|
6
|
+
// 3. Poll /functions/v1/cli-link-poll every 2s until 'ok' (token returned),
|
|
7
|
+
// 'expired', or timeout (10 min).
|
|
8
|
+
// 4. Save the api_token + member info to ~/.commitshow/config.json
|
|
9
|
+
//
|
|
10
|
+
// --token mode: skip the browser handshake and accept a pre-minted JWT
|
|
11
|
+
// (useful for headless / CI environments).
|
|
12
|
+
//
|
|
13
|
+
// --no-open: don't auto-launch the browser, just print the URL.
|
|
14
|
+
import { readConfig, writeConfig } from '../lib/config.js';
|
|
5
15
|
import { c } from '../lib/colors.js';
|
|
6
|
-
|
|
16
|
+
const POLL_INTERVAL_MS = 2000;
|
|
17
|
+
const POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
18
|
+
const DEFAULT_BASE_URL = 'https://tekemubwihsjdzittoqf.supabase.co';
|
|
19
|
+
const DEFAULT_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InRla2VtdWJ3aWhzamR6aXR0b3FmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzY0MzQ1NzUsImV4cCI6MjA5MjAxMDU3NX0.n2K-3lFVvlXQx-bV9evdNRSQCtG5oC4uQushxB2ja9Y';
|
|
20
|
+
function baseUrl() {
|
|
21
|
+
return readConfig().base_url ?? DEFAULT_BASE_URL;
|
|
22
|
+
}
|
|
23
|
+
function tryOpen(url) {
|
|
24
|
+
// Best-effort cross-platform open. Failure is silent — user still has
|
|
25
|
+
// the URL printed and can copy/paste manually.
|
|
26
|
+
const cmd = process.platform === 'darwin' ? 'open'
|
|
27
|
+
: process.platform === 'win32' ? 'cmd'
|
|
28
|
+
: 'xdg-open';
|
|
29
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
30
|
+
try {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
|
+
const { spawn } = require('node:child_process');
|
|
33
|
+
spawn(cmd, args, { stdio: 'ignore', detached: true }).unref();
|
|
34
|
+
}
|
|
35
|
+
catch { /* ignore */ }
|
|
36
|
+
}
|
|
37
|
+
async function fetchUser(token) {
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch(`${baseUrl()}/auth/v1/user`, {
|
|
40
|
+
headers: { apikey: DEFAULT_ANON_KEY, Authorization: `Bearer ${token}` },
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok)
|
|
43
|
+
return null;
|
|
44
|
+
const j = await res.json();
|
|
45
|
+
return { id: j.id, email: j.email ?? null };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function login(args) {
|
|
52
|
+
const noOpen = args.includes('--no-open');
|
|
53
|
+
const tokenIdx = args.indexOf('--token');
|
|
54
|
+
const presetToken = tokenIdx >= 0 ? args[tokenIdx + 1] : null;
|
|
55
|
+
// Headless / CI path · skip the browser handshake.
|
|
56
|
+
if (presetToken) {
|
|
57
|
+
const user = await fetchUser(presetToken);
|
|
58
|
+
if (!user) {
|
|
59
|
+
console.error(c.scarlet('✗ Token rejected · invalid or expired.'));
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
writeConfig({ ...readConfig(), token: presetToken, member_id: user.id, display_name: user.email ?? undefined });
|
|
63
|
+
console.log(c.gold('✓ Logged in · token saved to ~/.commitshow/config.json'));
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
// 1. Init the device-flow.
|
|
67
|
+
let init;
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch(`${baseUrl()}/functions/v1/cli-link-init`, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: { apikey: DEFAULT_ANON_KEY, Authorization: `Bearer ${DEFAULT_ANON_KEY}`, 'Content-Type': 'application/json' },
|
|
72
|
+
body: '{}',
|
|
73
|
+
});
|
|
74
|
+
init = await res.json();
|
|
75
|
+
if (!res.ok || !init.code || !init.poll_token) {
|
|
76
|
+
console.error(c.scarlet(`✗ Init failed: ${init.error ?? `HTTP ${res.status}`}`));
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
console.error(c.scarlet(`✗ Init network error: ${e?.message ?? e}`));
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log(c.cream(' Authorize commitshow CLI to act on your account.'));
|
|
7
86
|
console.log('');
|
|
8
|
-
console.log(c.
|
|
87
|
+
console.log(` Verification code: ${c.gold(init.code)}`);
|
|
88
|
+
console.log(` Approve at: ${init.verification_url}`);
|
|
9
89
|
console.log('');
|
|
10
|
-
console.log(c.
|
|
11
|
-
console.log(c.muted(' Write commands (submit, re-audit, install) unlock once the device-flow'));
|
|
12
|
-
console.log(c.muted(' endpoint ships — tracked as a V1 item in the roadmap.'));
|
|
90
|
+
console.log(c.dim(' Waiting for approval (10 min timeout)…'));
|
|
13
91
|
console.log('');
|
|
14
|
-
|
|
92
|
+
if (!noOpen && init.verification_url)
|
|
93
|
+
tryOpen(init.verification_url);
|
|
94
|
+
// 2. Poll until approved / expired / timeout.
|
|
95
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
96
|
+
while (Date.now() < deadline) {
|
|
97
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(`${baseUrl()}/functions/v1/cli-link-poll`, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: { apikey: DEFAULT_ANON_KEY, Authorization: `Bearer ${DEFAULT_ANON_KEY}`, 'Content-Type': 'application/json' },
|
|
102
|
+
body: JSON.stringify({ poll_token: init.poll_token }),
|
|
103
|
+
});
|
|
104
|
+
const resp = await res.json();
|
|
105
|
+
if (resp.status === 'ok' && resp.api_token) {
|
|
106
|
+
writeConfig({ ...readConfig(), token: resp.api_token, member_id: resp.user_id ?? undefined });
|
|
107
|
+
console.log(c.gold(' ✓ Authorized · token saved to ~/.commitshow/config.json'));
|
|
108
|
+
const user = await fetchUser(resp.api_token);
|
|
109
|
+
if (user?.email)
|
|
110
|
+
writeConfig({ ...readConfig(), display_name: user.email });
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
if (resp.status === 'expired' || resp.status === 'consumed') {
|
|
114
|
+
console.error(c.scarlet(` ✗ ${resp.message ?? resp.status}`));
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
// status === 'pending' · keep polling.
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// transient · keep polling.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
console.error(c.scarlet(' ✗ Timed out waiting for approval (10 min). Re-run commitshow login.'));
|
|
15
124
|
return 1;
|
|
16
125
|
}
|
package/dist/commands/whoami.js
CHANGED
|
@@ -1,14 +1,56 @@
|
|
|
1
|
-
import { readConfig } from '../lib/config.js';
|
|
1
|
+
import { readConfig, writeConfig } from '../lib/config.js';
|
|
2
2
|
import { c } from '../lib/colors.js';
|
|
3
|
-
|
|
3
|
+
const DEFAULT_BASE_URL = 'https://tekemubwihsjdzittoqf.supabase.co';
|
|
4
|
+
const DEFAULT_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InRla2VtdWJ3aWhzamR6aXR0b3FmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzY0MzQ1NzUsImV4cCI6MjA5MjAxMDU3NX0.n2K-3lFVvlXQx-bV9evdNRSQCtG5oC4uQushxB2ja9Y';
|
|
5
|
+
async function verifyToken(token) {
|
|
6
|
+
try {
|
|
7
|
+
const res = await fetch(`${DEFAULT_BASE_URL}/auth/v1/user`, {
|
|
8
|
+
headers: { apikey: DEFAULT_ANON_KEY, Authorization: `Bearer ${token}` },
|
|
9
|
+
});
|
|
10
|
+
if (!res.ok)
|
|
11
|
+
return null;
|
|
12
|
+
const j = await res.json();
|
|
13
|
+
return { id: j.id, email: j.email ?? null };
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function whoami(args) {
|
|
4
20
|
const cfg = readConfig();
|
|
5
|
-
|
|
21
|
+
// --logout · convenience flag · clears token from local config.
|
|
22
|
+
if (args.includes('--logout')) {
|
|
23
|
+
if (!cfg.token) {
|
|
24
|
+
console.log(c.dim('Already signed out.'));
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
const next = { ...cfg };
|
|
28
|
+
delete next.token;
|
|
29
|
+
delete next.member_id;
|
|
30
|
+
delete next.display_name;
|
|
31
|
+
delete next.refresh_token;
|
|
32
|
+
writeConfig(next);
|
|
33
|
+
console.log(c.gold('✓ Signed out · token cleared from ~/.commitshow/config.json'));
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
if (!cfg.token) {
|
|
6
37
|
console.log(c.muted('Not signed in.'));
|
|
7
|
-
console.log(c.dim('
|
|
38
|
+
console.log(c.dim(' Run `commitshow login` to authorize a 90-day API token.'));
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
// Verify the saved token is still valid (not expired or revoked).
|
|
42
|
+
const user = await verifyToken(cfg.token);
|
|
43
|
+
if (!user) {
|
|
44
|
+
console.log(c.scarlet('✗ Token rejected (expired or revoked).'));
|
|
45
|
+
console.log(c.dim(' Re-run `commitshow login`.'));
|
|
8
46
|
return 1;
|
|
9
47
|
}
|
|
10
|
-
console.log(
|
|
11
|
-
if (cfg.
|
|
12
|
-
console.log(
|
|
48
|
+
console.log('');
|
|
49
|
+
if (cfg.display_name)
|
|
50
|
+
console.log(` ${c.cream(cfg.display_name)}`);
|
|
51
|
+
if (user.email && user.email !== cfg.display_name)
|
|
52
|
+
console.log(` email: ${user.email}`);
|
|
53
|
+
console.log(` member id: ${user.id}`);
|
|
54
|
+
console.log('');
|
|
13
55
|
return 0;
|
|
14
56
|
}
|
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
|
@@ -401,6 +401,17 @@ export function renderAudit(view) {
|
|
|
401
401
|
lines.push(' '.repeat(footerPad) + c.gold(wordmark));
|
|
402
402
|
return lines.join('\n');
|
|
403
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
|
+
}
|
|
404
415
|
function vibeChecklistLines(vc) {
|
|
405
416
|
const out = [];
|
|
406
417
|
// 1. Webhook idempotency
|